载入中...
搜索中...
未找到
CastleMesh.cpp
浏览该文件的文档.
3
4#include <algorithm>
5#include <cmath>
6#include <cstdint>
7#include <random>
8
9namespace eve::procgen {
10namespace {
11
12constexpr float kPi = 3.14159265358979323846f;
13
14struct Builder {
15 MeshBuild &mesh;
16 float uv = 0.5f;
17
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());
21 mesh.addVertex(ax, ay, az, nx, ny, nz, 0, 0);
22 mesh.addVertex(bx, by, bz, nx, ny, nz, std::hypot(bx-ax, bz-az)*uv, 0);
23 mesh.addVertex(cx, cy, cz, nx, ny, nz, std::hypot(cx-dx, cz-dz)*uv,
24 std::fabs(cy-by)*uv);
25 mesh.addVertex(dx, dy, dz, nx, ny, nz, 0, std::fabs(dy-ay)*uv);
26 mesh.addTriangle(b, b+1, b+2); mesh.addTriangle(b, b+2, b+3);
27 }
28
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);
37 }
38
39 void cylinder(float cx, float y0, float cz, float radius, float height, int sides) {
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);
43 float x0=cx+std::cos(a0)*radius, z0=cz+std::sin(a0)*radius;
44 float x1=cx+std::cos(a1)*radius, z1=cz+std::sin(a1)*radius;
45 float am=(a0+a1)*0.5f;
46 // CCW from the outside, matching Graphics::newMeshCylinder / Vulkan
47 // front faces. The previous order wound the wall and cap inward, so
48 // back-face culling showed the interior of every tower.
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);
58 }
59 }
60};
61
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);
66 for (int i=0;i<n;i+=2) b.box(x0+i*pitch,y,z-depth*.5f,x0+(i+1)*pitch,y+merlonH,z+depth*.5f);
67}
68
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);
73 for (int i=0;i<n;i+=2) b.box(x-depth*.5f,y,z0+i*pitch,x+depth*.5f,y+merlonH,z0+(i+1)*pitch);
74}
75
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)));
79 float dx=run/float(steps), dy=rise/float(steps);
80 for(int i=0;i<steps;++i) {
81 // Both directions occupy the same [x0, x0 + run] footprint; reverse
82 // changes only which end reaches the upper landing.
83 float xa=reverse ? x0+(steps-1-i)*dx : x0+i*dx;
84 b.box(xa,y0,z-width*.5f,xa+dx,y0+(i+1)*dy,z+width*.5f);
85 }
86}
87
88void tower(Builder &b,float x,float z,float radius,float height,int sides,float merlonW,
89 int detail,int &merlons) {
90 b.cylinder(x,0,z,radius,height,sides);
91 if(detail>0) {
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;
95 float cx=x+std::cos(a)*(radius-tang*.35f), cz=z+std::sin(a)*(radius-tang*.35f);
96 b.box(cx-tang*.5f,height,cz-tang*.5f,cx+tang*.5f,height+radius*.3f,cz+tang*.5f);
97 ++merlons;
98 }
99 }
100}
101
102} // namespace
103
104bool generateCastleMesh(const Params &p, MeshBuild &out, std::string &error) {
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;
129 }
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;
135 float hx=width*.5f-inset, hz=depth*.5f-inset;
136 if(hx<towerR*1.8f || hz<towerR*1.8f) break;
137 ++actualRings;
138 float h=wallH*(1.f+ringHeightStep*float(ring));
139 // South wall is split around a real gate opening.
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;
144 if(detail>0) {
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);
150 }
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}) {
154 tower(b,sx*hx,sz*hz,tr,th,sides,merlonW,detail,merlons); ++towers;
155 }
156 // Interval towers make large castles read as defensible rather than four-corner boxes.
157 if(detail>1) {
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;}
162 }
163 // Gatehouse towers and lintel preserve the opening below.
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);
170 // Each ring has a physical flight onto its wall walk.
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);
176 ++stairs;
177 }
178 // Central keep: stacked floors with setbacks, battlements, and an external stair.
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);
184 if(f+1<keepFloors){
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");
188 ++stairs;
189 }
190 }
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);
196 // A continuous raised threshold makes the aligned passages through all
197 // rings readable and guarantees a walkable route from outside to the keep.
198 b.mesh.setActiveGroup("gatehouses");
199 b.box(-gateW*.38f, .02f, -depth*.5f-wallT*2.5f,
200 gateW*.38f, .18f, -kd*.5f);
201 // Seeded courtyard buildings add controlled variation without changing topology.
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);
208 }
209 float scale=std::max(.01f,p.getFloat("scale",1.f));
210 if(scale!=1.f) for(float &v:out.positions()) v*=scale;
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()));
216 out.setMeta("detail",std::to_string(detail));
217 out.setMeta("courtyardBuildings",std::to_string(detail>1?courtyardBuildings:0));
218 return true;
219}
220
222 RecipeDescriptor schema{"mesh.castle", "Castle", "Mesh", {}};
223 schema.params.push_back(ParamDescriptor::integer("seed", "Seed", 1, 0, 2147483647));
224 auto addFloat = [&](const char *key, const char *label, float value, float minimum,
225 float maximum, float step, const char *description) {
227 param.description = description;
228 schema.params.push_back(std::move(param));
229 };
230 auto addInt = [&](const char *key, const char *label, int value, int minimum,
231 int maximum, const char *description) {
233 param.description = description;
234 schema.params.push_back(std::move(param));
235 };
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");
260 r.registerRecipe(std::move(schema), generateCastleMesh);
261}
262
263} // namespace eve::procgen
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
float uv
int bz
Definition CaveMesh.cpp:114
int ax
Definition CaveMesh.cpp:113
int ay
Definition CaveMesh.cpp:113
int bx
Definition CaveMesh.cpp:114
int az
Definition CaveMesh.cpp:113
int by
Definition CaveMesh.cpp:114
float nx
float nz
float ny
glm::vec4 p[6]
std::string label
float maximum[3]
float minimum[3]
std::uint32_t key
glm::vec3 n
Definition Grass.cpp:63
double r
float v
int h
std::uint32_t height
std::uint32_t width
std::array< float, 3 > scale
std::string description
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float tr
std::weak_ptr< Run > run
Definition OnnxGpgpu.cpp:25
std::string error
Definition Package.cpp:60
float f
float radius
int steps
int detail
Mesh * mesh
float dz
float dy
float dx
int spacing
float step
Definition TreeMesh.cpp:314
int sides
Definition TreeMesh.cpp:322
std::uint32_t depth
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
const std::vector< float > & positions() const
Positions.
Definition MeshBuild.h:125
void reserve(int vertexCount, int indexCount)
Reserve.
Definition MeshBuild.cpp:20
void setMeta(const std::string &key, const std::string &value)
Sets the meta.
bool empty() const
Empty.
void clear()
Clears .
Definition MeshBuild.cpp:8
EVENGINE_API_DOMAINS public API.
Owning, typed generation parameters.
Definition Params.h:27
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.
Definition ParamSchema.h:52
std::vector< ParamDescriptor > params
Definition ParamSchema.h:56