载入中...
搜索中...
未找到
HexTerrain.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <array>
5#include <cmath>
6#include <cstdint>
7#include <limits>
8#include <random>
9#include <vector>
10
11namespace eve::procgen {
12namespace {
13
14enum Biome : int {
15 DeepOcean = 0,
16 Ocean,
17 Coast,
19 Hills,
20 Mountain,
21 Forest,
22 Swamp,
24 Ice,
25 Cliff,
26 River,
27};
28
29struct Cell {
30 float elevation = 0.f;
31 float moisture = 0.f;
32 float temperature = 0.f;
33 float river = 0.f;
34 int biome = Ocean;
36 float blend = 0.f;
37 uint8_t riverEdges = 0;
38 bool lake = false;
39};
40
41uint32_t hash(uint32_t x) {
42 x ^= x >> 16;
43 x *= 0x7feb352du;
44 x ^= x >> 15;
45 x *= 0x846ca68bu;
46 return x ^ (x >> 16);
47}
48
49float valueNoise(float x, float y, uint32_t seed) {
50 const int ix = int(std::floor(x));
51 const int iy = int(std::floor(y));
52 const float fx = x - float(ix);
53 const float fy = y - float(iy);
54 const float sx = fx * fx * (3.f - 2.f * fx);
55 const float sy = fy * fy * (3.f - 2.f * fy);
56 auto sample = [seed](int px, int py) {
57 const uint32_t h = hash(uint32_t(px) * 0x9e3779b9u ^ uint32_t(py) * 0x85ebca6bu ^ seed);
58 return float(h & 0xffffu) / 32767.5f - 1.f;
59 };
60 const float a = std::lerp(sample(ix, iy), sample(ix + 1, iy), sx);
61 const float b = std::lerp(sample(ix, iy + 1), sample(ix + 1, iy + 1), sx);
62 return std::lerp(a, b, sy);
63}
64
65float fbm(float x, float y, uint32_t seed) {
66 float value = 0.f, amplitude = 0.55f, frequency = 1.f, norm = 0.f;
67 for (int octave = 0; octave < 5; ++octave) {
68 value += valueNoise(x * frequency, y * frequency, seed + uint32_t(octave) * 1013u) * amplitude;
69 norm += amplitude;
70 amplitude *= 0.5f;
71 frequency *= 2.03f;
72 }
73 return value / norm;
74}
75
76int indexOf(int q, int r, int width, int height) {
77 return q < 0 || r < 0 || q >= width || r >= height ? -1 : q + r * width;
78}
79
80std::array<int, 6> neighbours(int q, int r, int width, int height) {
81 static constexpr int dq[6] = {1, 0, -1, -1, 0, 1};
82 const int parity = q & 1;
83 const int dr[6] = {parity, 1, parity, parity - 1, -1, parity - 1};
84 std::array<int, 6> result{};
85 for (int i = 0; i < 6; ++i) result[i] = indexOf(q + dq[i], r + dr[i], width, height);
86 return result;
87}
88
89void addRiverArm(MeshBuild& out, float cx, float y, float cz, float radius, int edge,
90 float coverage, uint32_t shapeSeed) {
91 const float angle = 1.0471975512f * (float(edge) + 0.5f);
92 const float dx = std::cos(angle), dz = std::sin(angle);
93 const float ex = cx + dx * radius * 0.94f;
94 const float ez = cz + dz * radius * 0.94f;
95 const float bend = (float(hash(shapeSeed ^ uint32_t(edge) * 0x9e3779b9u) & 0xffffu) /
96 65535.f * 2.f - 1.f) * radius * 0.30f;
97 const float mx = cx + dx * radius * 0.48f - dz * bend;
98 const float mz = cz + dz * radius * 0.48f + dx * bend;
99 const float u = float(River) + 0.04f;
100 const float v = float(River) + std::clamp(coverage, 0.f, 0.999f);
101 const uint32_t base = uint32_t(out.getVertexCount());
102 constexpr int curveSegments = 6;
103 for (int segment = 0; segment <= curveSegments; ++segment) {
104 const float t = float(segment) / float(curveSegments);
105 const float omt = 1.f - t;
106 const float x = omt * omt * cx + 2.f * omt * t * mx + t * t * ex;
107 const float z = omt * omt * cz + 2.f * omt * t * mz + t * t * ez;
108 float tx = 2.f * omt * (mx - cx) + 2.f * t * (ex - mx);
109 float tz = 2.f * omt * (mz - cz) + 2.f * t * (ez - mz);
110 const float invLength = 1.f / std::max(0.0001f, std::sqrt(tx * tx + tz * tz));
111 tx *= invLength;
112 tz *= invLength;
113 const float halfWidth = radius * (0.102f + coverage * 0.064f) *
114 (0.82f + 0.18f * std::sin(t * 3.1415926536f));
115 const float px = -tz, pz = tx;
116 out.addVertex(x + px * halfWidth, y, z + pz * halfWidth,
117 0.f, 1.f, 0.f, u, v);
118 out.addVertex(x - px * halfWidth, y, z - pz * halfWidth,
119 0.f, 1.f, 0.f, u, v);
120 }
121 for (int segment = 0; segment < curveSegments; ++segment) {
122 const uint32_t a = base + uint32_t(segment * 2);
123 out.addTriangle(a, a + 1u, a + 3u);
124 out.addTriangle(a, a + 3u, a + 2u);
125 }
126}
127
128void addRiverJunction(MeshBuild& out, float cx, float y, float cz, float radius,
129 float coverage) {
130 constexpr int segments = 12;
131 const float junctionRadius = radius * (0.105f + coverage * 0.045f);
132 const float u = float(River) + 0.04f;
133 const float v = float(River) + std::clamp(coverage, 0.f, 0.999f);
134 const uint32_t center = uint32_t(out.getVertexCount());
135 out.addVertex(cx, y, cz, 0.f, 1.f, 0.f, u, v);
136 for (int k = 0; k < segments; ++k) {
137 const float angle = 6.2831853072f * float(k) / float(segments);
138 out.addVertex(cx + std::cos(angle) * junctionRadius, y,
139 cz + std::sin(angle) * junctionRadius, 0.f, 1.f, 0.f, u, v);
140 }
141 for (int k = 0; k < segments; ++k)
142 out.addTriangle(center, center + 1u + uint32_t(k),
143 center + 1u + uint32_t((k + 1) % segments));
144}
145
146void addShoreBand(MeshBuild& out, float cx, float y, float cz, float radius, int edge) {
147 const float a0 = 1.0471975512f * float(edge);
148 const float a1 = 1.0471975512f * float((edge + 1) % 6);
149 constexpr float innerScale = 0.89f;
150 const float u = float(Coast) + 0.92f;
151 const float v = float(Ocean) + 0.15f;
152 const uint32_t base = uint32_t(out.getVertexCount());
153 out.addVertex(cx + std::cos(a0) * radius, y, cz + std::sin(a0) * radius,
154 0.f, 1.f, 0.f, u, v);
155 out.addVertex(cx + std::cos(a1) * radius, y, cz + std::sin(a1) * radius,
156 0.f, 1.f, 0.f, u, v);
157 out.addVertex(cx + std::cos(a1) * radius * innerScale, y,
158 cz + std::sin(a1) * radius * innerScale, 0.f, 1.f, 0.f, u, v);
159 out.addVertex(cx + std::cos(a0) * radius * innerScale, y,
160 cz + std::sin(a0) * radius * innerScale, 0.f, 1.f, 0.f, u, v);
161 out.addTriangle(base, base + 1u, base + 2u);
162 out.addTriangle(base, base + 2u, base + 3u);
163}
164
165void classify(Cell& c, float sea) {
166 if (c.elevation < sea - 0.22f) c.biome = DeepOcean;
167 else if (c.temperature < 0.16f) c.biome = Ice;
168 else if (c.elevation < sea - 0.045f) c.biome = Ocean;
169 else if (c.elevation < sea + 0.025f) c.biome = Coast;
170 else if (c.elevation > sea + 0.55f) c.biome = Mountain;
171 else if (c.elevation > sea + 0.35f) c.biome = Hills;
172 else if (c.moisture > 0.68f && c.elevation < sea + 0.16f) c.biome = Swamp;
173 else if (c.moisture > 0.64f && c.temperature > 0.55f) c.biome = Rainforest;
174 else if (c.moisture > 0.56f) c.biome = Forest;
175 else c.biome = Grassland;
176 c.secondary = c.biome;
177}
178
179float surfaceHeight(const Cell& c, float sea, float heightScale) {
180 float y = (c.elevation - sea) * heightScale;
181 if (c.lake) return y - 0.035f * heightScale;
182 if (c.biome == DeepOcean || c.biome == Ocean)
183 return std::min(y, -0.08f * heightScale);
184
185 const float land = std::max(0.f, c.elevation - sea);
186 if (c.biome == Hills) y += (0.08f + land * 0.16f) * heightScale;
187 if (c.biome == Mountain) y += (0.22f + land * 0.38f) * heightScale;
188 if (c.biome == Ice && land > 0.28f) y += land * 0.22f * heightScale;
189 if (c.river > 0.f) y -= (0.045f + 0.035f * c.river) * heightScale;
190 return y;
191}
192
193void addCone(MeshBuild& out, float cx, float baseY, float cz, float radius, float height,
194 int biome, int segments = 6, uint32_t shapeSeed = 0) {
195 const uint32_t base = uint32_t(out.getVertexCount());
196 // 0.92 marks procedural decoration vertices for optional wind animation.
197 const float u = float(biome) + 0.92f;
198 const float v = float(biome);
199 for (int k = 0; k < segments; ++k) {
200 const float a = 6.2831853072f * float(k) / float(segments);
201 const float nx = std::cos(a), nz = std::sin(a);
202 const float radial = shapeSeed == 0 ? 1.f :
203 0.78f + float(hash(shapeSeed + uint32_t(k) * 0x85ebca6bu) & 0xffffu) /
204 65535.f * 0.38f;
205 out.addVertex(cx + nx * radius * radial, baseY, cz + nz * radius * radial,
206 nx * 0.82f, radius / std::max(height, 0.01f), nz * 0.82f, u, v);
207 }
208 const float apexX = shapeSeed == 0 ? cx : cx +
209 (float(hash(shapeSeed ^ 0x41c64e6du) & 0xffffu) / 65535.f - 0.5f) * radius * 0.34f;
210 const float apexZ = shapeSeed == 0 ? cz : cz +
211 (float(hash(shapeSeed ^ 0xc2b2ae35u) & 0xffffu) / 65535.f - 0.5f) * radius * 0.34f;
212 if (shapeSeed != 0) {
213 const uint32_t shoulder = uint32_t(out.getVertexCount());
214 for (int k = 0; k < segments; ++k) {
215 const float a = 6.2831853072f * (float(k) + 0.16f) / float(segments);
216 const float nx = std::cos(a), nz = std::sin(a);
217 const float radial = 0.43f +
218 float(hash(shapeSeed ^ 0xa511e9b3u ^ uint32_t(k) * 0x27d4eb2du) & 0xffffu) /
219 65535.f * 0.22f;
220 out.addVertex(std::lerp(cx, apexX, 0.34f) + nx * radius * radial,
221 baseY + height * (0.43f + radial * 0.16f),
222 std::lerp(cz, apexZ, 0.34f) + nz * radius * radial,
223 nx * 0.88f, 0.34f, nz * 0.88f, u, v);
224 }
225 const uint32_t apex = uint32_t(out.getVertexCount());
226 out.addVertex(apexX, baseY + height, apexZ, 0.f, 1.f, 0.f, u, v);
227 for (int k = 0; k < segments; ++k) {
228 const uint32_t next = uint32_t((k + 1) % segments);
229 const uint32_t lower = base + uint32_t(k);
230 const uint32_t lowerNext = base + next;
231 const uint32_t upper = shoulder + uint32_t(k);
232 const uint32_t upperNext = shoulder + next;
233 out.addTriangle(lower, lowerNext, upperNext);
234 out.addTriangle(lower, upperNext, upper);
235 out.addTriangle(upper, upperNext, apex);
236 }
237 return;
238 }
239 const uint32_t apex = uint32_t(out.getVertexCount());
240 out.addVertex(apexX, baseY + height, apexZ, 0.f, 1.f, 0.f, u, v);
241 for (int k = 0; k < segments; ++k)
242 out.addTriangle(base + uint32_t(k), base + uint32_t((k + 1) % segments), apex);
243}
244
245void addTrunk(MeshBuild& out, float cx, float y, float cz, float radius, float height) {
246 constexpr int segments = 5;
247 const uint32_t base = uint32_t(out.getVertexCount());
248 const float u = float(Cliff) + 0.12f;
249 const float v = float(Forest);
250 for (int level = 0; level < 2; ++level) for (int k = 0; k < segments; ++k) {
251 const float a = 6.2831853072f * float(k) / float(segments);
252 const float nx = std::cos(a), nz = std::sin(a);
253 out.addVertex(cx + nx * radius, y + float(level) * height, cz + nz * radius,
254 nx, 0.f, nz, u, v);
255 }
256 for (int k = 0; k < segments; ++k) {
257 const uint32_t next = uint32_t((k + 1) % segments);
258 out.addTriangle(base + uint32_t(k), base + next, base + uint32_t(segments + k));
259 out.addTriangle(base + next, base + uint32_t(segments) + next,
260 base + uint32_t(segments + k));
261 }
262}
263
264void addCrownBlob(MeshBuild& out, float cx, float y, float cz, float radius, float height,
265 int biome, int segments = 7) {
266 const float u = float(biome) + 0.92f;
267 const float v = float(biome);
268 const uint32_t bottom = uint32_t(out.getVertexCount());
269 out.addVertex(cx, y, cz, 0.f, -1.f, 0.f, u, v);
270 const uint32_t ring = uint32_t(out.getVertexCount());
271 for (int k = 0; k < segments; ++k) {
272 const float a = 6.2831853072f * float(k) / float(segments);
273 const float nx = std::cos(a), nz = std::sin(a);
274 out.addVertex(cx + nx * radius, y + height * 0.43f, cz + nz * radius,
275 nx, 0.25f, nz, u, v);
276 }
277 const uint32_t top = uint32_t(out.getVertexCount());
278 out.addVertex(cx, y + height, cz, 0.f, 1.f, 0.f, u, v);
279 for (int k = 0; k < segments; ++k) {
280 const uint32_t a = ring + uint32_t(k);
281 const uint32_t b = ring + uint32_t((k + 1) % segments);
282 out.addTriangle(bottom, b, a);
283 out.addTriangle(a, b, top);
284 }
285}
286
287void addTree(MeshBuild& out, float x, float y, float z, float scale, int biome,
288 bool broadleaf) {
289 addTrunk(out, x, y, z, scale * 0.075f, scale * 0.48f);
290 if (broadleaf) {
291 const float broad = biome == Rainforest ? 0.46f : 0.39f;
292 addCrownBlob(out, x, y + scale * 0.34f, z, scale * broad, scale * 0.78f,
293 biome, biome == Rainforest ? 8 : 7);
294 if (biome == Rainforest)
295 addCrownBlob(out, x + scale * 0.13f, y + scale * 0.55f, z - scale * 0.09f,
296 scale * 0.31f, scale * 0.62f, biome, 7);
297 } else if (biome == Rainforest) {
298 addCone(out, x, y + scale * 0.34f, z, scale * 0.38f, scale * 0.72f, biome, 7);
299 addCone(out, x, y + scale * 0.68f, z, scale * 0.28f, scale * 0.55f, biome, 7);
300 } else {
301 addCone(out, x, y + scale * 0.30f, z, scale * 0.32f, scale * 0.64f, biome, 6);
302 addCone(out, x, y + scale * 0.58f, z, scale * 0.24f, scale * 0.56f, biome, 6);
303 }
304}
305
306void addTop(MeshBuild& out, float cx, float cz, float y, float radius, const Cell& c) {
307 const uint32_t center = uint32_t(out.getVertexCount());
308 const float u = float(c.biome) + std::clamp(c.blend, 0.f, 0.999f);
309 const float v = float(c.secondary) + std::clamp(c.river, 0.f, 0.999f);
310 out.addVertex(cx, y, cz, 0.f, 1.f, 0.f, u, v);
311 for (int k = 0; k < 6; ++k) {
312 const float a = 1.0471975512f * float(k);
313 out.addVertex(cx + std::cos(a) * radius, y, cz + std::sin(a) * radius,
314 0.f, 1.f, 0.f, u, v);
315 }
316 for (int k = 0; k < 6; ++k)
317 out.addTriangle(center, center + 1u + uint32_t(k), center + 1u + uint32_t((k + 1) % 6));
318}
319
320void addCliff(MeshBuild& out, float cx, float cz, float radius, int edge, float top, float bottom,
321 const Cell& c, uint32_t shapeSeed) {
322 const float a0 = 1.0471975512f * float(edge);
323 const float a1 = 1.0471975512f * float((edge + 1) % 6);
324 const float x0 = cx + std::cos(a0) * radius, z0 = cz + std::sin(a0) * radius;
325 const float x1 = cx + std::cos(a1) * radius, z1 = cz + std::sin(a1) * radius;
326 const float nx = std::sin((a0 + a1) * 0.5f), nz = -std::cos((a0 + a1) * 0.5f);
327 const float relief = std::abs(top - bottom);
328 const bool rockFace = relief > radius * 0.42f;
329 const float u = rockFace ? float(Cliff) + std::min(0.999f, relief)
330 : float(c.biome) + std::clamp(c.blend, 0.f, 0.49f);
331 const float v = rockFace ? float(c.biome)
332 : float(c.secondary) + std::clamp(c.river, 0.f, 0.999f);
333 constexpr int wallSegments = 5;
334 std::array<float, wallSegments + 1> topX{}, topY{}, topZ{}, bottomX{}, bottomY{}, bottomZ{};
335 for (int segment = 0; segment <= wallSegments; ++segment) {
336 const float t = float(segment) / float(wallSegments);
337 const float straightX = std::lerp(x0, x1, t);
338 const float straightZ = std::lerp(z0, z1, t);
339 const uint32_t h = hash(shapeSeed + uint32_t(segment) * 0x9e3779b9u);
340 const float endMask = segment == 0 || segment == wallSegments ? 0.f : 1.f;
341 const float lipScale = rockFace ? 0.26f : 0.12f;
342 const float footScale = rockFace ? 0.34f : 0.16f;
343 const float lipOffset = (float(h & 0xffffu) / 65535.f - 0.42f) *
344 radius * lipScale * endMask;
345 const float footOffset = (float((h >> 16) & 0xffffu) / 65535.f - 0.5f) *
346 radius * footScale * endMask;
347 topX[size_t(segment)] = straightX + nx * lipOffset;
348 topY[size_t(segment)] = top + (rockFace ?
349 (float(hash(h ^ 0x165667b1u) & 0xffffu) / 65535.f - 0.38f) *
350 radius * 0.22f * endMask : 0.f);
351 topZ[size_t(segment)] = straightZ + nz * lipOffset;
352 bottomX[size_t(segment)] = straightX + nx * footOffset;
353 bottomY[size_t(segment)] = bottom + (rockFace ?
354 (float(hash(h ^ 0xd3a2646cu) & 0xffffu) / 65535.f) *
355 std::min(relief * 0.16f, radius * 0.16f) * endMask : 0.f);
356 bottomZ[size_t(segment)] = straightZ + nz * footOffset;
357 }
358 for (int segment = 0; segment < wallSegments; ++segment) {
359 const uint32_t h = hash(shapeSeed ^ uint32_t(segment) * 0x85ebca6bu);
360 const float shadeTurn = (float(h & 0xffffu) / 65535.f - 0.5f) * 0.34f;
361 const float snx = nx + std::cos(a1) * shadeTurn;
362 const float snz = nz + std::sin(a1) * shadeTurn;
363 const uint32_t base = uint32_t(out.getVertexCount());
364 out.addVertex(topX[size_t(segment)], topY[size_t(segment)], topZ[size_t(segment)], snx, 0.f, snz, u, v);
365 out.addVertex(topX[size_t(segment + 1)], topY[size_t(segment + 1)], topZ[size_t(segment + 1)], snx, 0.f, snz, u, v);
366 out.addVertex(bottomX[size_t(segment + 1)], bottomY[size_t(segment + 1)], bottomZ[size_t(segment + 1)], snx, 0.f, snz, u, v);
367 out.addVertex(bottomX[size_t(segment)], bottomY[size_t(segment)], bottomZ[size_t(segment)], snx, 0.f, snz, u, v);
368 out.addTriangle(base, base + 1u, base + 2u);
369 out.addTriangle(base, base + 2u, base + 3u);
370 }
371}
372
373} // namespace
374
375bool generateHexTerrainMesh(const Params& params, MeshBuild& out, std::string& error) {
376 const int width = params.getInt("width", 32);
377 const int height = params.getInt("height", 24);
378 const int riverCount = params.getInt("riverCount", 8);
379 const uint32_t seed = uint32_t(params.getInt("seed", 1));
380 const float radius = params.getFloat("radius", 1.f);
381 const float sea = params.getFloat("seaLevel", 0.43f);
382 const float heightScale = params.getFloat("heightScale", 4.f);
383 const bool decorations = params.getBool("decorations", true);
384 const float vegetationDensity = params.getFloat("vegetationDensity", 1.f);
385 if (width < 2 || height < 2 || width > 256 || height > 256) {
386 error = "mesh.hexterrain: width and height must be in [2, 256]";
387 return false;
388 }
389 if (!(radius > 0.f) || !(heightScale > 0.f) || sea < 0.f || sea > 1.f || riverCount < 0 ||
390 vegetationDensity < 0.f || vegetationDensity > 2.f) {
391 error = "mesh.hexterrain: invalid radius, heightScale, seaLevel or riverCount";
392 return false;
393 }
394
395 std::vector<Cell> cells(size_t(width * height));
396 for (int r = 0; r < height; ++r) for (int q = 0; q < width; ++q) {
397 Cell& c = cells[size_t(q + r * width)];
398 const float nx = (float(q) / float(width - 1)) * 2.f - 1.f;
399 const float ny = (float(r) / float(height - 1)) * 2.f - 1.f;
400 const float continental = 1.f - std::pow(std::min(1.f, std::sqrt(nx * nx + ny * ny)), 1.7f);
401 c.elevation = std::clamp(0.12f + continental * 0.78f + fbm(q * 0.085f, r * 0.085f, seed) * 0.22f, 0.f, 1.f);
402 c.moisture = std::clamp(0.52f + fbm(q * 0.11f, r * 0.11f, seed ^ 0x51f15e5du) * 0.42f, 0.f, 1.f);
403 const float latitude = std::abs(ny);
404 c.temperature = std::clamp(1.f - latitude * 0.92f - std::max(0.f, c.elevation - sea) * 0.48f +
405 fbm(q * 0.06f, r * 0.06f, seed ^ 0xa53a9d1bu) * 0.1f, 0.f, 1.f);
406 classify(c, sea);
407 }
408
409 std::mt19937 rng(seed ? seed : 1u);
410 std::vector<int> riverSources;
411 for (int river = 0; river < riverCount; ++river) {
412 std::vector<uint8_t> visited(size_t(width * height), 0);
413 int current = -1;
414 int lastEdge = -1;
415 float bestSourceScore = -std::numeric_limits<float>::max();
416 for (int attempt = 0; attempt < width * height; ++attempt) {
417 const int candidate = int(rng() % uint32_t(width * height));
418 if (cells[size_t(candidate)].elevation <= sea + 0.34f) continue;
419 float sourceDistance = float(width + height);
420 const int candidateQ = candidate % width;
421 const int candidateR = candidate / width;
422 for (int source : riverSources) {
423 const int dq = candidateQ - source % width;
424 const int dr = candidateR - source / width;
425 sourceDistance = std::min(sourceDistance, std::sqrt(float(dq * dq + dr * dr)));
426 }
427 const float sourceScore = cells[size_t(candidate)].elevation + sourceDistance * 0.014f;
428 if (sourceScore > bestSourceScore) {
429 bestSourceScore = sourceScore;
430 current = candidate;
431 }
432 }
433 if (current >= 0) riverSources.push_back(current);
434 for (int step = 0; current >= 0 && step < width + height; ++step) {
435 Cell& c = cells[size_t(current)];
436 if (c.elevation <= sea + 0.02f) break;
437 visited[size_t(current)] = 1;
438 c.river = std::max(c.river, std::min(0.95f, 0.35f + float(step) * 0.025f));
439 c.secondary = River;
440 int next = -1;
441 int nextEdge = -1;
442 float bestScore = std::numeric_limits<float>::max();
443 const int q = current % width, r = current / width;
444 const auto ns = neighbours(q, r, width, height);
445 for (int edge = 0; edge < 6; ++edge) {
446 const int n = ns[size_t(edge)];
447 if (n >= 0 && !visited[size_t(n)]) {
448 float score = cells[size_t(n)].elevation;
449 const float routeNoise = float(hash(seed ^ uint32_t(current) * 0x9e3779b9u ^
450 uint32_t(edge) * 0x85ebca6bu) & 0xffffu) /
451 65535.f;
452 score += (routeNoise - 0.5f) * 0.055f;
453 if (lastEdge >= 0) {
454 const int delta = (edge - lastEdge + 6) % 6;
455 const int turnSign = ((step / 3 + river) & 1) == 0 ? 1 : -1;
456 const int preferredEdge = (lastEdge + turnSign + 6) % 6;
457 if (edge == lastEdge) score += 0.045f;
458 if (delta == 3) score += 0.30f;
459 if (edge == preferredEdge) score -= 0.060f;
460 }
461 if (cells[size_t(n)].river > 0.f) score -= 0.018f;
462 if (score >= bestScore) continue;
463 bestScore = score;
464 next = n;
465 nextEdge = edge;
466 }
467 }
468 if (next < 0) break;
469 if (cells[size_t(next)].elevation >= c.elevation + 0.075f && step >= 5 &&
470 c.elevation > sea + 0.10f) {
471 c.lake = true;
472 c.biome = Ocean;
473 c.secondary = River;
474 c.river = std::max(c.river, 0.72f);
475 break;
476 }
477 if (cells[size_t(next)].elevation >= c.elevation)
478 cells[size_t(next)].elevation = std::max(sea + 0.01f, c.elevation - 0.018f);
479 c.riverEdges |= uint8_t(1u << uint32_t(nextEdge));
480 cells[size_t(next)].riverEdges |= uint8_t(1u << uint32_t((nextEdge + 3) % 6));
481 current = next;
482 lastEdge = nextEdge;
483 }
484 }
485
486 for (int r = 0; r < height; ++r) for (int q = 0; q < width; ++q) {
487 Cell& c = cells[size_t(q + r * width)];
488 const auto ns = neighbours(q, r, width, height);
489 float bestDifference = 0.f;
490 for (int n : ns) if (n >= 0 && cells[size_t(n)].biome != c.biome) {
491 const float difference = std::abs(c.elevation - cells[size_t(n)].elevation);
492 if (difference >= bestDifference) {
493 bestDifference = difference;
494 c.secondary = cells[size_t(n)].biome;
495 c.blend = std::clamp(0.18f + (1.f - difference) * 0.32f, 0.f, 0.49f);
496 }
497 }
498 if (c.river > 0.f) c.secondary = River;
499 }
500
501 out.clear();
502 out.reserve(width * height * 18, width * height * 30);
503 std::array<int, 12> biomeCounts{};
504 int cliffEdgeCount = 0;
505 int riverCellCount = 0;
506 int lakeCellCount = 0;
507 const float xStep = radius * 1.5f, zStep = radius * 1.7320508076f;
508 for (int r = 0; r < height; ++r) for (int q = 0; q < width; ++q) {
509 const int idx = q + r * width;
510 const Cell& c = cells[size_t(idx)];
511 const float cx = float(q) * xStep;
512 const float cz = (float(r) + float(q & 1) * 0.5f) * zStep;
513 const float y = surfaceHeight(c, sea, heightScale);
514 const auto ns = neighbours(q, r, width, height);
515 ++biomeCounts[size_t(std::clamp(c.biome, 0, 11))];
516 if (c.riverEdges != 0) ++riverCellCount;
517 if (c.lake) ++lakeCellCount;
518 addTop(out, cx, cz, y, radius, c);
519 if (c.riverEdges != 0) {
520 addRiverJunction(out, cx, y + radius * 0.013f, cz, radius, c.river);
521 for (int edge = 0; edge < 6; ++edge)
522 if ((c.riverEdges & uint8_t(1u << uint32_t(edge))) != 0)
523 {
524 const int neighbourIndex = ns[size_t(edge)];
525 const int safeNeighbour = neighbourIndex >= 0 ? neighbourIndex : idx;
526 const uint32_t lo = uint32_t(std::min(idx, safeNeighbour));
527 const uint32_t hi = uint32_t(std::max(idx, safeNeighbour));
528 addRiverArm(out, cx, y + radius * 0.012f, cz, radius, edge, c.river,
529 hash(seed ^ lo * 0x27d4eb2du ^ hi * 0x85ebca6bu));
530 }
531 }
532 for (int edge = 0; edge < 6; ++edge) {
533 float neighbourY = -sea * heightScale;
534 if (ns[size_t(edge)] >= 0) {
535 const Cell& nc = cells[size_t(ns[size_t(edge)])];
536 neighbourY = surfaceHeight(nc, sea, heightScale);
537 }
538 if (y > neighbourY + 0.025f) {
539 addCliff(out, cx, cz, radius, edge, y, neighbourY, c,
540 hash(seed ^ uint32_t(idx) * 0x9e3779b9u ^ uint32_t(edge)));
541 ++cliffEdgeCount;
542 }
543
544 if (c.biome == Coast && ns[size_t(edge)] >= 0 &&
545 cells[size_t(ns[size_t(edge)])].biome <= Ocean)
546 addShoreBand(out, cx, y + radius * 0.014f, cz, radius, edge);
547 }
548
549 if (decorations && (c.biome == Forest || c.biome == Rainforest || c.biome == Swamp)) {
550 const uint32_t cellSeed = hash(seed ^ uint32_t(idx) * 0x9e3779b9u);
551 const int baseCount = c.biome == Rainforest ? 4 : (c.biome == Forest ? 3 : 2);
552 const int count = int(std::round(float(baseCount) * vegetationDensity));
553 for (int tree = 0; tree < count; ++tree) {
554 const uint32_t h = hash(cellSeed + uint32_t(tree) * 0x85ebca6bu);
555 const float a = float(h & 0xffffu) / 65535.f * 6.2831853072f;
556 const float d = (0.16f + float((h >> 16) & 0xffu) / 255.f * 0.36f) * radius;
557 const float variation = 0.82f + float((h >> 24) & 0xffu) / 255.f * 0.38f;
558 const float scale = radius * variation * (c.biome == Swamp ? 0.75f : 1.25f);
559 const int treeBiome = c.biome == Swamp ? Forest : c.biome;
560 const bool broadleaf = c.biome != Forest || ((h >> 12) & 1u) != 0;
561 addTree(out, cx + std::cos(a) * d, y, cz + std::sin(a) * d, scale,
562 treeBiome, broadleaf);
563 }
564 } else if (decorations && c.biome == Mountain) {
565 const uint32_t mountainSeed = hash(seed ^ uint32_t(idx) * 0x27d4eb2du);
566 const float mainHeight = radius *
567 (1.20f + float(mountainSeed & 0xffu) / 255.f * 0.58f);
568 const float mainRadius = radius *
569 (0.48f + float((mountainSeed >> 8) & 0xffu) / 255.f * 0.12f);
570 const float angle = float((mountainSeed >> 16) & 0xffffu) / 65535.f *
571 6.2831853072f;
572 addCone(out, cx - std::cos(angle) * radius * 0.08f, y - radius * 0.04f,
573 cz - std::sin(angle) * radius * 0.08f, mainRadius, mainHeight,
574 Mountain, 8, mountainSeed);
575 addCone(out, cx + std::cos(angle) * radius * 0.28f, y - radius * 0.025f,
576 cz + std::sin(angle) * radius * 0.28f, radius * 0.31f,
577 mainHeight * 0.64f, Mountain, 7, mountainSeed ^ 0x68bc21ebu);
578 if (((mountainSeed >> 30) & 1u) != 0)
579 addCone(out, cx + std::cos(angle + 2.1f) * radius * 0.25f,
580 y - radius * 0.02f,
581 cz + std::sin(angle + 2.1f) * radius * 0.25f,
582 radius * 0.24f, mainHeight * 0.48f, Mountain, 6,
583 mountainSeed ^ 0x02e5be93u);
584 }
585 }
586 out.setMeta("algorithm", "mesh.hexterrain");
587 out.setMeta("uvEncoding", "u=primaryBiome+blend,v=secondaryBiome+riverCoverage");
588 out.setMeta("biomeCount", "12");
589 out.setMeta("shoreGeometry", "edge-bands");
590 out.setMeta("hydrology", "drainage-rivers+basin-lakes+confluences");
591 out.setMeta("riverGeometry", "seeded-quadratic-ribbons");
592 out.setMeta("cliffGeometry", "seeded-segmented-rock-walls");
593 out.setMeta("mountainGeometry", "seeded-offset-three-ring-peaks");
594 static constexpr const char* biomeNames[10] = {
595 "deepOcean", "ocean", "coast", "grassland", "hills",
596 "mountain", "forest", "swamp", "rainforest", "ice",
597 };
598 for (int biome = 0; biome < 10; ++biome)
599 out.setMeta(std::string("cells.") + biomeNames[biome],
600 std::to_string(biomeCounts[size_t(biome)]));
601 out.setMeta("cells.river", std::to_string(riverCellCount));
602 out.setMeta("cells.lake", std::to_string(lakeCellCount));
603 out.setMeta("edges.cliff", std::to_string(cliffEdgeCount));
604 return !out.empty();
605}
606
607} // namespace eve::procgen
double value
SQInteger top
double score
Definition Agent.cpp:50
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 py
float nx
float nz
float ny
float pz
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::array< double, 10 > q
double r
float moisture
float v
std::int32_t c
float elevation
uint8_t riverEdges
int secondary
float river
float blend
int biome
bool lake
float temperature
int h
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
int level
std::string error
Definition Package.cpp:60
int idx
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
float radius
float halfWidth
std::uint32_t seed
Definition PointSet.cpp:807
float d
float t
const RoadEdge * edge
double current
float dz
float dx
std::uint32_t count
float step
Definition TreeMesh.cpp:314
const UnitySourceAsset & source
float bottom
float angle
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
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
Owning, typed generation parameters.
Definition Params.h:27
std::vector< ParamSpec > params
@ Cell
A cell was removed; the out-parameter holds it.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
double sample(const Heightmap &map, double u, double v)
Sample.
bool generateHexTerrainMesh(const Params &params, MeshBuild &out, std::string &error)
Build a deterministic, flat-top hexagonal continent mesh.
Biome
Biome public API.