12constexpr float kPi = 3.14159265358979323846f;
18 void quad(
float ax,
float ay,
float az,
float bx,
float by,
float bz,
float cx,
float cy,
19 float cz,
float dx,
float dy,
float dz,
float nx,
float ny,
float nz) {
20 const uint32_t
b = uint32_t(
mesh.getVertexCount());
29 void box(
float x0,
float y0,
float z0,
float x1,
float y1,
float z1) {
30 if (x1 <= x0 || y1 <= y0 || z1 <= z0)
return;
31 quad(x1,y0,z0, x1,y1,z0, x1,y1,z1, x1,y0,z1, 1,0,0);
32 quad(x0,y0,z1, x0,y1,z1, x0,y1,z0, x0,y0,z0, -1,0,0);
33 quad(x0,y1,z0, x0,y1,z1, x1,y1,z1, x1,y1,z0, 0,1,0);
34 quad(x0,y0,z1, x0,y0,z0, x1,y0,z0, x1,y0,z1, 0,-1,0);
35 quad(x1,y0,z1, x1,y1,z1, x0,y1,z1, x0,y0,z1, 0,0,1);
36 quad(x0,y0,z0, x0,y1,z0, x1,y1,z0, x1,y0,z0, 0,0,-1);
40 const float y1 = y0 +
height;
41 for (
int i=0; i<
sides; ++i) {
42 float a0=2*kPi*float(i)/float(
sides), a1=2*kPi*float(i+1)/float(
sides);
45 float am=(a0+a1)*0.5f;
49 quad(x0,y0,z0, x0,y1,z0, x1,y1,z1, x1,y0,z1,
50 std::cos(am),0,std::sin(am));
51 const uint32_t cap = uint32_t(
mesh.getVertexCount());
52 mesh.addVertex(
cx, y1, cz, 0, 1, 0, 0.5f, 0.5f);
53 mesh.addVertex(x1, y1, z1, 0, 1, 0, 0.5f + 0.5f * std::cos(a1),
54 0.5f + 0.5f * std::sin(a1));
55 mesh.addVertex(x0, y1, z0, 0, 1, 0, 0.5f + 0.5f * std::cos(a0),
56 0.5f + 0.5f * std::sin(a0));
57 mesh.addTriangle(cap, cap + 1, cap + 2);
62void crenelsX(Builder &
b,
float x0,
float x1,
float y,
float z,
float depth,
63 float merlonW,
float merlonH) {
64 int n=std::max(2,
int(std::floor((x1-x0)/merlonW)));
65 float pitch=(x1-x0)/
float(
n);
69void crenelsZ(Builder &
b,
float z0,
float z1,
float y,
float x,
float depth,
70 float merlonW,
float merlonH) {
71 int n=std::max(2,
int(std::floor((z1-z0)/merlonW)));
72 float pitch=(z1-z0)/
float(
n);
76void stairX(Builder &
b,
float x0,
float y0,
float z,
float run,
float rise,
float width,
77 float stepHeight,
bool reverse=
false) {
78 int steps=std::max(2,
int(std::ceil(rise/stepHeight)));
80 for(
int i=0;i<
steps;++i) {
83 float xa=reverse ? x0+(
steps-1-i)*
dx : x0+i*
dx;
89 int detail,
int &merlons) {
92 int n=std::max(6,
int(std::floor(2*kPi*
radius/merlonW)));
93 for(
int i=0;i<
n;i+=2) {
94 float a=2*kPi*(float(i)+.5f)/
float(
n), tang=2*kPi*
radius/float(
n)*.42f;
105 const float width=std::max(16.f,
p.getFloat(
"width",42.f));
106 const float depth=std::max(16.f,
p.getFloat(
"depth",36.f));
107 const int rings=std::clamp(
p.getInt(
"rings",2),1,4);
108 const float wallH=std::max(2.f,
p.getFloat(
"wallHeight",6.f));
109 const float wallT=std::clamp(
p.getFloat(
"wallThickness",1.8f),.5f,5.f);
110 const float towerR=std::max(wallT,
p.getFloat(
"towerRadius",3.3f));
111 const float towerH=std::max(wallH+1.f,
p.getFloat(
"towerHeight",9.f));
112 const int sides=std::clamp(
p.getInt(
"towerSides",12),6,32);
113 const float spacing=std::max(8.f,
p.getFloat(
"towerSpacing",16.f));
114 const float gateW=std::clamp(
p.getFloat(
"gateWidth",5.f),2.f,
width*.3f);
115 const float merlonW=std::max(.35f,
p.getFloat(
"merlonWidth",1.25f));
116 const float stairW=std::max(1.f,
p.getFloat(
"stairWidth",2.f));
117 const float stepH=std::clamp(
p.getFloat(
"stepHeight",.28f),.12f,.5f);
118 const int keepFloors=std::clamp(
p.getInt(
"keepFloors",3),1,8);
119 const int detail=std::clamp(
p.getInt(
"detail",2),0,2);
120 const float ringInset=std::max(wallT*2.5f,
p.getFloat(
"ringInset",std::min(
width,
depth)*.13f));
121 const float ringHeightStep=std::clamp(
p.getFloat(
"ringHeightStep",.22f),-.25f,.75f);
122 const float towerHeightStep=std::clamp(
p.getFloat(
"towerHeightStep",.14f),-.25f,.75f);
123 const float floorH=std::max(2.f,
p.getFloat(
"floorHeight",3.2f));
124 const float keepW=std::clamp(
p.getFloat(
"keepWidth",
width*.25f),6.f,
width*.55f);
125 const float keepD=std::clamp(
p.getFloat(
"keepDepth",
depth*.24f),6.f,
depth*.55f);
126 const int courtyardBuildings=std::clamp(
p.getInt(
"courtyardBuildings",3),0,12);
127 if(
width <= 2*towerR+2*wallT ||
depth <= 2*towerR+2*wallT) {
128 error=
"mesh.castle: width/depth too small for towerRadius and wallThickness";
return false;
130 out.
clear(); out.
reserve(40000,120000); Builder
b{out,std::max(0.f,
p.getFloat(
"uvRepeat",.5f))};
131 std::mt19937 rng(
p.getSeed());
132 int towers=0,walls=0,stairs=0,merlons=0,actualRings=0;
133 for(
int ring=0;ring<rings;++ring) {
134 float inset=float(ring)*ringInset;
136 if(hx<towerR*1.8f || hz<towerR*1.8f)
break;
138 float h=wallH*(1.f+ringHeightStep*float(ring));
140 b.mesh.setActiveGroup(
"walls");
141 b.box(-hx,0,-hz,-gateW*.5f,
h,-hz+wallT);
b.box(gateW*.5f,0,-hz,hx,
h,-hz+wallT);
142 b.box(-hx,0,hz-wallT,hx,
h,hz);
b.box(-hx,0,-hz+wallT,-hx+wallT,
h,hz-wallT);
143 b.box(hx-wallT,0,-hz+wallT,hx,
h,hz-wallT); walls+=4;
145 b.mesh.setActiveGroup(
"battlements");
146 crenelsX(
b,-hx,hx,
h,-hz,wallT,merlonW,wallT*.7f);
147 crenelsX(
b,-hx,hx,
h,hz,wallT,merlonW,wallT*.7f);
148 crenelsZ(
b,-hz,hz,
h,-hx,wallT,merlonW,wallT*.7f);
149 crenelsZ(
b,-hz,hz,
h,hx,wallT,merlonW,wallT*.7f);
151 float tr=towerR*(1.f-.12f*float(ring)), th=towerH*(1.f+towerHeightStep*
float(ring));
152 b.mesh.setActiveGroup(
"towers");
153 for(
float sx:{-1.f,1.f})
for(
float sz:{-1.f,1.f}) {
158 int nx=std::max(0,
int(std::floor(2*hx/
spacing))-1);
159 int nz=std::max(0,
int(std::floor(2*hz/
spacing))-1);
160 for(
int i=1;i<=
nx;++i){
float x=-hx+2*hx*i/float(
nx+1); tower(
b,
x,hz,
tr*.78f,th*.88f,
sides,merlonW,
detail,merlons); ++towers;}
161 for(
int i=1;i<=
nz;++i){
float z=-hz+2*hz*i/float(
nz+1); tower(
b,hx,
z,
tr*.78f,th*.88f,
sides,merlonW,
detail,merlons); ++towers;}
164 const float gateTowerR=
tr*.72f;
165 const float gateTowerX=gateW*.5f+gateTowerR*.72f;
166 b.mesh.setActiveGroup(
"gatehouses");
167 tower(
b,-gateTowerX,-hz,gateTowerR,th*.9f,
sides,merlonW,
detail,merlons);
168 tower(
b, gateTowerX,-hz,gateTowerR,th*.9f,
sides,merlonW,
detail,merlons); towers+=2;
169 b.box(-gateW*.5f,
h*.62f,-hz-wallT*.2f,gateW*.5f,
h,-hz+wallT*1.2f);
171 b.mesh.setActiveGroup(
"stairs");
172 stairX(
b, -hx + wallT, 0.f, -hz + wallT + stairW * .55f,
173 h * 1.35f,
h, stairW, stepH);
174 b.box(-hx+wallT+
h*1.35f-stairW*.5f,
h-wallT*.25f,-hz+wallT,
175 -hx+wallT+
h*1.35f+stairW*.5f,
h,-hz+wallT+stairW);
179 float kw=keepW,kd=keepD;
180 b.mesh.setActiveGroup(
"keep");
181 for(
int f=0;
f<keepFloors;++
f) {
182 float setback=float(
f)*.35f,
y=float(
f)*floorH;
183 b.box(-kw*.5f+setback,
y,-kd*.5f+setback,kw*.5f-setback,
y+floorH,kd*.5f-setback);
185 b.mesh.setActiveGroup(
"stairs");
186 stairX(
b,-kw*.5f+setback,
y,-kd*.5f-stairW*.55f,kw-2*setback,floorH,stairW,stepH,(
f%2)==1);
187 b.mesh.setActiveGroup(
"keep");
191 float ky=keepFloors*floorH;
192 crenelsX(
b,-kw*.5f,kw*.5f,ky,-kd*.5f,wallT*.55f,merlonW*.8f,wallT*.65f);
193 crenelsX(
b,-kw*.5f,kw*.5f,ky,kd*.5f,wallT*.55f,merlonW*.8f,wallT*.65f);
194 crenelsZ(
b,-kd*.5f,kd*.5f,ky,-kw*.5f,wallT*.55f,merlonW*.8f,wallT*.65f);
195 crenelsZ(
b,-kd*.5f,kd*.5f,ky,kw*.5f,wallT*.55f,merlonW*.8f,wallT*.65f);
198 b.mesh.setActiveGroup(
"gatehouses");
199 b.box(-gateW*.38f, .02f, -
depth*.5f-wallT*2.5f,
200 gateW*.38f, .18f, -kd*.5f);
202 b.mesh.setActiveGroup(
"courtyard");
203 if(
detail>1)
for(
int i=0;i<courtyardBuildings;++i) {
204 std::uniform_real_distribution<float> jitter(-1.f,1.f);
205 const float lane=float(i)-float(courtyardBuildings-1)*.5f;
206 float x=lane*kw*.72f+jitter(rng),
z=kd*.85f+jitter(rng);
207 b.box(
x-2,0,
z-1.8f,
x+2,2.6f+
float(i%3)*.35f,
z+1.8f);
209 float scale=std::max(.01f,
p.getFloat(
"scale",1.f));
211 if(out.
empty()){
error=
"mesh.castle: generated empty mesh";
return false;}
212 out.
setMeta(
"algorithm",
"mesh.castle"); out.
setMeta(
"rings",std::to_string(actualRings));
213 out.
setMeta(
"wallSections",std::to_string(walls)); out.
setMeta(
"towerCount",std::to_string(towers));
214 out.
setMeta(
"stairFlights",std::to_string(stairs)); out.
setMeta(
"keepFloors",std::to_string(keepFloors));
215 out.
setMeta(
"merlonCount",std::to_string(merlons)); out.
setMeta(
"seed",std::to_string(
p.getSeed()));
217 out.
setMeta(
"courtyardBuildings",std::to_string(
detail>1?courtyardBuildings:0));
228 schema.params.push_back(std::move(param));
234 schema.params.push_back(std::move(param));
236 addFloat(
"width",
"Width", 42.f, 16.f, 1000.f, .5f,
"Outer east-west extent");
237 addFloat(
"depth",
"Depth", 36.f, 16.f, 1000.f, .5f,
"Outer north-south extent");
238 addInt(
"rings",
"Wall Rings", 2, 1, 4,
"Concentric defensive wall tiers");
239 addFloat(
"ringInset",
"Ring Inset", 5.f, .5f, 100.f, .1f,
"Distance between wall tiers");
240 addFloat(
"ringHeightStep",
"Ring Height Step", .22f, -.25f, .75f, .01f,
"Height multiplier added per inner tier");
241 addFloat(
"wallHeight",
"Wall Height", 6.f, 2.f, 100.f, .1f,
"Outer wall-walk height");
242 addFloat(
"wallThickness",
"Wall Thickness", 1.8f, .5f, 5.f, .1f,
"Wall body and walk thickness");
243 addFloat(
"towerRadius",
"Tower Radius", 3.3f, .5f, 100.f, .1f,
"Corner tower radius");
244 addFloat(
"towerHeight",
"Tower Height", 9.f, 3.f, 200.f, .1f,
"Outer tower height");
245 addFloat(
"towerHeightStep",
"Tower Height Step", .14f, -.25f, .75f, .01f,
"Height multiplier added per inner tier");
246 addInt(
"towerSides",
"Tower Sides", 12, 6, 32,
"Radial tower tessellation");
247 addFloat(
"towerSpacing",
"Tower Spacing", 16.f, 8.f, 200.f, .5f,
"Maximum interval-tower spacing");
248 addFloat(
"gateWidth",
"Gate Width", 5.f, 2.f, 100.f, .1f,
"Aligned gate passage width");
249 addFloat(
"merlonWidth",
"Merlon Width", 1.25f, .35f, 20.f, .05f,
"Battlement unit width");
250 addFloat(
"keepWidth",
"Keep Width", 10.5f, 6.f, 500.f, .5f,
"Central keep width");
251 addFloat(
"keepDepth",
"Keep Depth", 8.64f, 6.f, 500.f, .5f,
"Central keep depth");
252 addInt(
"keepFloors",
"Keep Floors", 3, 1, 8,
"Number of accessible keep tiers");
253 addFloat(
"floorHeight",
"Floor Height", 3.2f, 2.f, 50.f, .1f,
"Keep floor-to-floor height");
254 addFloat(
"stairWidth",
"Stair Width", 2.f, 1.f, 50.f, .1f,
"Physical stair-flight width");
255 addFloat(
"stepHeight",
"Step Height", .28f, .12f, .5f, .01f,
"Physical riser height");
256 addInt(
"courtyardBuildings",
"Courtyard Buildings", 3, 0, 12,
"Seeded courtyard building count");
257 addInt(
"detail",
"Detail", 2, 0, 2,
"0 structural, 1 battlements, 2 full");
258 addFloat(
"uvRepeat",
"UV Repeat", .5f, 0.f, 100.f, .05f,
"Texture repeats per world unit");
259 addFloat(
"scale",
"Scale", 1.f, .01f, 100.f, .01f,
"Uniform output scale");
std::array< float, 3 > scale
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
const std::vector< float > & positions() const
Positions.
void reserve(int vertexCount, int indexCount)
Reserve.
void setMeta(const std::string &key, const std::string &value)
Sets the meta.
EVENGINE_API_DOMAINS public API.
Owning, typed generation parameters.
void registerCastleMeshRecipe(MeshRecipeRegistry &r)
Register mesh.castle in the built-in mesh recipe registry.
bool generateCastleMesh(const Params &p, MeshBuild &out, std::string &error)
Build a complete, walkable, multi-ring medieval castle.
static ParamDescriptor floating(std::string key, std::string label, float defaultValue, float minimum, float maximum, float step)
Construct a bounded floating-point descriptor.
static ParamDescriptor integer(std::string key, std::string label, int defaultValue, int minimum, int maximum, int step=1)
Construct a bounded integer descriptor.
Complete metadata and parameter schema shared by every procgen recipe family.
std::vector< ParamDescriptor > params