载入中...
搜索中...
未找到
HexFeatures.cpp
浏览该文件的文档.
1
7
8#include "hexmap/HexCell.h"
10#include "hexmap/HexMap.h"
11#include "hexmap/HexMeshData.h"
12#include "hexmap/HexMetrics.h"
13#include "hexmap/HexNoise.h"
14
15#include <cmath>
16#include <cstdint>
17
18namespace eve::hexmap {
19
20namespace {
21
31[[nodiscard]] HexVec3 horizontalPerturb(const HexNoise& noise, HexVec3 position) noexcept {
32 const HexVec4 sample = noise.sample(position.x, position.z);
35 return position;
36}
37
39[[nodiscard]] float dot(HexVec3 a, HexVec3 b) noexcept { return a.x * b.x + a.y * b.y + a.z * b.z; }
40
42[[nodiscard]] HexVec3 cross(HexVec3 a, HexVec3 b) noexcept {
43 return HexVec3{a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
44}
45
47[[nodiscard]] HexVec3 normalize(HexVec3 v) noexcept {
48 const float length = std::sqrt(dot(v, v));
49 if (length <= 1e-6f) return HexVec3{};
50 return HexVec3{v.x / length, v.y / length, v.z / length};
51}
52
54[[nodiscard]] bool isZero(HexVec3 v) noexcept { return v.x == 0.f && v.y == 0.f && v.z == 0.f; }
55
63[[nodiscard]] HexVec3 yaw(float orientation, HexVec3 v) noexcept {
64 const float angle = orientation * 6.28318530718f;
65 const float cosine = std::cos(angle);
66 const float sine = std::sin(angle);
67 return HexVec3{v.x * cosine - v.z * sine, v.y, v.x * sine + v.z * cosine};
68}
69
71constexpr float kWallCode = 0.f;
73constexpr float kTowerCode = 1.f;
75constexpr float kBridgeCode = 2.f;
77constexpr float kUrbanCode = 3.f;
79constexpr float kFarmCode = 4.f;
81constexpr float kPlantCode = 5.f;
83constexpr float kSpecialCode = 6.f;
84
86constexpr float kBridgeHalfWidth = 0.25f;
88constexpr float kBridgeHalfThickness = 0.15f;
90constexpr float kTowerHalfExtent = 1.5f;
92constexpr float kTowerHeight = 2.f;
93
95void emitTriangle(HexMeshData& out, const HexNoise& noise, const HexVec3& p0, const HexVec3& p1, const HexVec3& p2,
96 float code) {
97 const auto i0 = static_cast<std::uint32_t>(out.vertexCount());
98 out.addVertex(horizontalPerturb(noise, p0), code, 0.f);
99 out.addVertex(horizontalPerturb(noise, p1), code, 0.f);
100 out.addVertex(horizontalPerturb(noise, p2), code, 0.f);
101 out.addTriangle(i0, i0 + 1u, i0 + 2u);
102}
103
113void emitEdgeQuad(HexMeshData& out, const HexNoise& noise, const HexVec3& corner, const HexVec3& e1, const HexVec3& e2,
114 float code) {
115 const auto i0 = static_cast<std::uint32_t>(out.vertexCount());
116 out.addVertex(horizontalPerturb(noise, corner), code, 0.f);
117 out.addVertex(horizontalPerturb(noise, corner + e1), code, 0.f);
118 out.addVertex(horizontalPerturb(noise, corner + e1 + e2), code, 0.f);
119 out.addVertex(horizontalPerturb(noise, corner + e2), code, 0.f);
120 out.addTriangle(i0 + 2u, i0 + 1u, i0);
121 out.addTriangle(i0 + 3u, i0 + 2u, i0);
122}
123
125void emitBoxFace(HexMeshData& out, const HexNoise& noise, HexVec3 centre, HexVec3 e1, HexVec3 e2, float code) {
126 const HexVec3 corner = centre - e1 * 0.5f - e2 * 0.5f;
127 emitEdgeQuad(out, noise, corner, e1, e2, code);
128}
129
139void emitBox(HexMeshData& out, const HexNoise& noise, HexVec3 origin, HexVec3 right, HexVec3 up, HexVec3 fwd,
140 HexVec3 halfExtents, float code) {
141 const HexVec3 r = right * (2.f * halfExtents.x);
142 const HexVec3 u = up * (2.f * halfExtents.y);
143 const HexVec3 f = fwd * (2.f * halfExtents.z);
144
145 // `r`, `u` and `f` are the box's full edge vectors, so each face's two edge
146 // arguments below are complete edges of that face.
147 emitBoxFace(out, noise, origin + r * 0.5f, u, f * -1.f, code);
148 emitBoxFace(out, noise, origin - r * 0.5f, u, f, code);
149 emitBoxFace(out, noise, origin + u * 0.5f, r, f, code);
150 emitBoxFace(out, noise, origin - u * 0.5f, r, f * -1.f, code);
151 emitBoxFace(out, noise, origin - f * 0.5f, r, u, code);
152 emitBoxFace(out, noise, origin + f * 0.5f, r, u * -1.f, code);
153}
154
156class WallMesher {
157public:
158 WallMesher(const HexMap& map, HexMeshData& out) noexcept : map_(map), out_(out) {}
159
161 void run(std::int32_t chunkIndex) {
162 for (std::int32_t row = 0; row < HexMetrics::kChunkSizeZ; ++row) {
163 for (std::int32_t column = 0; column < HexMetrics::kChunkSizeX; ++column) {
164 buildCell(map_.chunkCell(chunkIndex, column, row));
165 }
166 }
167 }
168
169private:
171 void buildCell(HexCoordinates coordinates) {
172 if (!map_.contains(coordinates)) return;
173 const HexCellData* cellData = map_.cell(coordinates);
174 if (cellData == nullptr) return;
175
176 const HexVec3 center = map_.cellPosition(coordinates);
177 for (std::int32_t index = 0; index < kHexDirectionCount; ++index) {
178 const auto direction = static_cast<HexDirection>(index);
179
180 HexCoordinates neighbour{};
181 const bool hasNeighbour = map_.getNeighbor(coordinates, direction, neighbour);
182 const HexCellData* neighbourData = hasNeighbour ? map_.cell(neighbour) : nullptr;
183 const HexVec3 neighbourPosition = hasNeighbour ? map_.cellPosition(neighbour) : HexVec3{};
184
185 const bool isOwnDirection =
186 static_cast<std::int32_t>(direction) <= static_cast<std::int32_t>(HexDirection::SE);
187 const EdgeVertices near = solidEdge(center, direction);
188 const float farY = neighbourData != nullptr ? neighbourPosition.y : near.v1.y;
189 const EdgeVertices far = shiftedEdge(center, direction, farY);
190
191 // Chunk ownership: a wall is emitted for the NE, E and SE edges of a
192 // cell, or when the neighbour is outside the grid. Every interior
193 // edge is the NE/E/SE edge of exactly one of its two cells, so no
194 // wall is generated twice.
195 if (isOwnDirection || neighbourData == nullptr) {
196 const bool hasRiver = map_.hasRiverThrough(coordinates, direction) ||
197 (hasNeighbour && map_.hasRiverThrough(neighbour, opposite(direction)));
198 const bool hasRoad = cellData->flags.hasRoad(direction) ||
199 (hasNeighbour && neighbourData->flags.hasRoad(opposite(direction)));
200 addWall(near, cellData, far, neighbourData, hasRiver, hasRoad);
201
202 if (neighbourData != nullptr && cellData->flags.hasRoad(direction) && hasRiver) {
203 addBridge(center, neighbourPosition);
204 }
205 }
206
207 if (!isOwnDirection) continue;
208
209 HexCoordinates nextCoordinates{};
210 if (!map_.getNeighbor(coordinates, next(direction), nextCoordinates)) continue;
211 const HexCellData* nextCell = map_.cell(nextCoordinates);
212 if (nextCell == nullptr) continue;
213
214 HexVec3 left = near.v5 + HexMetrics::bridge(next(direction));
215 left.y = map_.cellPosition(nextCoordinates).y;
216 // `left` takes its height from the next cell and `far.v5` is the neighbour's
217 // own corner, so each point has to be passed with the cell it belongs to:
218 // `addWallCorner` picks the pivot/left/right wedge from those cells' flags
219 // and elevations.
220 addWallCorner(near.v5, cellData, left, nextCell, far.v5, neighbourData);
221 }
222 }
223
225 void addWall(const EdgeVertices& near, const HexCellData* nearCell, const EdgeVertices& far,
226 const HexCellData* farCell, bool hasRiver, bool hasRoad) {
227 if (farCell == nullptr) return;
228 if (nearCell->flags.isWalled() == farCell->flags.isWalled()) return;
229 if (nearCell->values.isUnderwater() || farCell->values.isUnderwater()) return;
230 if (edgeType(nearCell->values.elevation(), farCell->values.elevation()) == HexEdgeType::Cliff) return;
231
232 addWallSegment(near.v1, far.v1, near.v2, far.v2, false);
233 if (hasRiver || hasRoad) {
234 addWallCap(near.v2, far.v2);
235 addWallCap(far.v4, near.v4);
236 } else {
237 addWallSegment(near.v2, far.v2, near.v3, far.v3, false);
238 addWallSegment(near.v3, far.v3, near.v4, far.v4, false);
239 }
240 addWallSegment(near.v4, far.v4, near.v5, far.v5, false);
241 }
242
244 void addWallSegment(HexVec3 nearLeft, HexVec3 farLeft, HexVec3 nearRight, HexVec3 farRight, bool addTower) {
245 const HexVec3 left = HexMetrics::wallLerp(nearLeft, farLeft);
246 const HexVec3 right = HexMetrics::wallLerp(nearRight, farRight);
247 const HexVec3 leftOffset = HexMetrics::wallThicknessOffset(nearLeft, farLeft);
248 const HexVec3 rightOffset = HexMetrics::wallThicknessOffset(nearRight, farRight);
249
250 const HexVec3 leftLow = left - leftOffset;
251 const HexVec3 rightLow = right - rightOffset;
252 HexVec3 leftTop = leftLow;
253 leftTop.y = left.y + HexMetrics::kWallHeight;
254 HexVec3 rightTop = rightLow;
255 rightTop.y = right.y + HexMetrics::kWallHeight;
256
257 emitEdgeQuad(out_, map_.noise(), leftLow, rightLow - leftLow, leftTop - leftLow, kWallCode);
258
259 const HexVec3 leftInnerLow = left + leftOffset;
260 const HexVec3 rightInnerLow = right + rightOffset;
261 HexVec3 leftInnerTop = leftInnerLow;
262 leftInnerTop.y = left.y + HexMetrics::kWallHeight;
263 HexVec3 rightInnerTop = rightInnerLow;
264 rightInnerTop.y = right.y + HexMetrics::kWallHeight;
265
266 emitEdgeQuad(out_, map_.noise(), rightInnerLow, leftInnerLow - rightInnerLow, rightInnerTop - rightInnerLow,
267 kWallCode);
268 emitEdgeQuad(out_, map_.noise(), leftTop, rightTop - leftTop, leftInnerTop - leftTop, kWallCode);
269
270 if (addTower) addTowerAt(left, right);
271 }
272
274 void addTowerAt(HexVec3 left, HexVec3 right) {
275 const HexVec3 along = right - left;
276 if (dot(along, along) <= 1e-8f) return;
277
278 const HexVec3 fwd = normalize(along);
279 const HexVec3 rightAxis = normalize(cross(fwd, HexVec3{0.f, 1.f, 0.f}));
280 if (isZero(rightAxis)) return;
281
282 const float halfWidth = HexMetrics::kWallThickness * kTowerHalfExtent;
283 const float halfHeight = HexMetrics::kWallThickness * kTowerHeight * 0.5f;
284 const HexVec3 halfExtents{halfWidth, halfHeight, halfWidth};
285 const HexVec3 centre = (left + right) * 0.5f + HexVec3{0.f, halfHeight, 0.f};
286 emitBox(out_, map_.noise(), centre, rightAxis, HexVec3{0.f, 1.f, 0.f}, fwd, halfExtents, kTowerCode);
287 }
288
290 void addWallCap(HexVec3 near, HexVec3 far) {
291 const HexVec3 center = HexMetrics::wallLerp(near, far);
292 const HexVec3 thickness = HexMetrics::wallThicknessOffset(near, far);
293
294 const HexVec3 lowA = center - thickness;
295 const HexVec3 lowB = center + thickness;
296 HexVec3 topA = lowA;
297 topA.y = center.y + HexMetrics::kWallHeight;
298 HexVec3 topB = lowB;
299 topB.y = center.y + HexMetrics::kWallHeight;
300
301 // The cap is the wall's cross-section: topA -> lowA -> lowB -> topB.
302 // `emitEdgeQuad` walks `c`, `c + e1`, `c + e1 + e2`, `c + e2`, so the
303 // second edge has to span the far side at wall height. Using the near
304 // side's offset instead (`lowB - topA`) would drag the fourth corner one
305 // wall height below the base and skew the cap.
306 emitEdgeQuad(out_, map_.noise(), topA, lowA - topA, topB - topA, kWallCode);
307 }
308
310 void addWallWedge(HexVec3 near, HexVec3 far, HexVec3 point) {
311 const HexVec3 center = HexMetrics::wallLerp(near, far);
312 const HexVec3 thickness = HexMetrics::wallThicknessOffset(near, far);
313
314 HexVec3 flatPoint = point;
315 flatPoint.y = center.y;
316 HexVec3 topPoint = flatPoint;
317 topPoint.y = center.y + HexMetrics::kWallHeight;
318
319 const HexVec3 lowA = center - thickness;
320 const HexVec3 lowB = center + thickness;
321 HexVec3 topA = lowA;
322 topA.y = topPoint.y;
323 HexVec3 topB = lowB;
324 topB.y = topPoint.y;
325
326 // Each side spans from its wall centre to the apex, so the pair of edge
327 // vectors has to be ordered for the face to point away from the wall.
328 emitEdgeQuad(out_, map_.noise(), flatPoint, topPoint - flatPoint, lowA - flatPoint, kWallCode);
329 emitEdgeQuad(out_, map_.noise(), flatPoint, lowB - flatPoint, topPoint - flatPoint, kWallCode);
330 emitTriangle(out_, map_.noise(), topPoint, topB, topA, kWallCode);
331 }
332
334 void addWallSegmentAtPivot(HexVec3 pivot, const HexCellData* pivotCell, HexVec3 left, const HexCellData* leftCell,
335 HexVec3 right, const HexCellData* rightCell) {
336 if (pivotCell->values.isUnderwater()) return;
337
338 const bool hasLeftWall = leftCell != nullptr && !leftCell->values.isUnderwater() &&
339 edgeType(pivotCell->values.elevation(), leftCell->values.elevation()) !=
341 const bool hasRightWall = rightCell != nullptr && !rightCell->values.isUnderwater() &&
342 edgeType(pivotCell->values.elevation(), rightCell->values.elevation()) !=
344
345 if (hasLeftWall && hasRightWall) {
346 bool hasTower = false;
347 if (leftCell->values.elevation() == rightCell->values.elevation()) {
348 const HexVec3 midpoint = (pivot + left + right) * (1.f / 3.f);
349 const HexHash hash = HexHashGrid(map_.seed()).sample(midpoint);
350 hasTower = hash.e < kWallTowerThreshold;
351 }
352 addWallSegment(pivot, left, pivot, right, hasTower);
353 } else if (hasLeftWall) {
354 if (leftCell->values.elevation() < rightCell->values.elevation()) {
355 addWallWedge(pivot, left, right);
356 } else {
357 addWallCap(pivot, left);
358 }
359 } else if (hasRightWall) {
360 if (rightCell->values.elevation() < leftCell->values.elevation()) {
361 addWallWedge(right, pivot, left);
362 } else {
363 addWallCap(right, pivot);
364 }
365 }
366 }
367
369 void addWallCorner(HexVec3 c1, const HexCellData* cell1, HexVec3 c2, const HexCellData* cell2, HexVec3 c3,
370 const HexCellData* cell3) {
371 if (cell2 == nullptr || cell3 == nullptr) return;
372 const bool w1 = cell1->flags.isWalled();
373 const bool w2 = cell2->flags.isWalled();
374 const bool w3 = cell3->flags.isWalled();
375
376 if (w1) {
377 if (w2) {
378 if (!w3) addWallSegmentAtPivot(c3, cell3, c1, cell1, c2, cell2);
379 } else if (w3) {
380 addWallSegmentAtPivot(c2, cell2, c3, cell3, c1, cell1);
381 } else {
382 addWallSegmentAtPivot(c1, cell1, c2, cell2, c3, cell3);
383 }
384 } else if (w2) {
385 if (w3) {
386 addWallSegmentAtPivot(c1, cell1, c2, cell2, c3, cell3);
387 } else {
388 addWallSegmentAtPivot(c2, cell2, c3, cell3, c1, cell1);
389 }
390 } else if (w3) {
391 addWallSegmentAtPivot(c3, cell3, c1, cell1, c2, cell2);
392 }
393 }
394
396 void addBridge(HexVec3 center, HexVec3 neighbourPosition) {
397 const HexVec3 span = neighbourPosition - center;
398 const float length = std::sqrt(dot(span, span));
399 if (length <= 1e-6f) return;
400
401 const HexVec3 fwd = span * (1.f / length);
402 const HexVec3 rightAxis = normalize(cross(HexVec3{0.f, 1.f, 0.f}, fwd));
403 if (isZero(rightAxis)) return;
404
405 const float halfWidth = HexMetrics::innerRadius() * kBridgeHalfWidth;
406 const float halfThickness = HexMetrics::kWallHeight * kBridgeHalfThickness;
407 const HexVec3 halfExtents{halfWidth, halfThickness, length * 0.5f};
408 emitBox(out_, map_.noise(), center, rightAxis, HexVec3{0.f, 1.f, 0.f}, fwd, halfExtents, kBridgeCode);
409 }
410
412 [[nodiscard]] static EdgeVertices solidEdge(HexVec3 center, HexDirection direction) noexcept {
413 return EdgeVertices{center + HexMetrics::firstSolidCorner(direction),
415 }
416
418 [[nodiscard]] static EdgeVertices shiftedEdge(HexVec3 center, HexDirection direction, float targetY) noexcept {
419 const HexVec3 bridge = HexMetrics::bridge(direction);
420 EdgeVertices edge = solidEdge(center, direction);
421 edge.v1 = edge.v1 + bridge;
422 edge.v2 = edge.v2 + bridge;
423 edge.v3 = edge.v3 + bridge;
424 edge.v4 = edge.v4 + bridge;
425 edge.v5 = edge.v5 + bridge;
426 edge.v1.y = targetY;
427 edge.v2.y = targetY;
428 edge.v3.y = targetY;
429 edge.v4.y = targetY;
430 edge.v5.y = targetY;
431 return edge;
432 }
433
434 const HexMap& map_;
435 HexMeshData& out_;
436};
437
439class FeatureMesher {
440public:
441 explicit FeatureMesher(const HexMap& map, HexMeshData& out) noexcept
442 : map_(map), out_(out), grid_(map.seed()) {}
443
445 void run(std::int32_t chunkIndex) {
446 for (std::int32_t row = 0; row < HexMetrics::kChunkSizeZ; ++row) {
447 for (std::int32_t column = 0; column < HexMetrics::kChunkSizeX; ++column) {
448 buildCell(map_.chunkCell(chunkIndex, column, row));
449 }
450 }
451 }
452
453private:
455 void buildCell(HexCoordinates coordinates) {
456 if (!map_.contains(coordinates)) return;
457 const HexCellData* cellData = map_.cell(coordinates);
458 if (cellData == nullptr) return;
459 if (cellData->values.isUnderwater()) return;
460
461 const std::int32_t urbanLevel = cellData->values.urbanLevel();
462 const std::int32_t farmLevel = cellData->values.farmLevel();
463 const std::int32_t plantLevel = cellData->values.plantLevel();
464 const std::int32_t special = cellData->values.specialIndex();
465 if (urbanLevel == 0 && farmLevel == 0 && plantLevel == 0 && special == 0) return;
466
467 const HexVec3 position = map_.cellPosition(coordinates);
468 const HexHash hash = grid_.sample(position);
469
470 if (special == 0) {
471 const int urban = pick(urbanLevel, hash.a);
472 const int farm = pick(farmLevel, hash.b);
473 const int plant = pick(plantLevel, hash.c);
474
475 // The reference compares the three collections by their hash and
476 // keeps the lowest one that passed its level's thresholds.
477 std::int32_t collection = -1;
478 float usedHash = hash.a;
479 if (urban >= 0) collection = 0;
480 if (farm >= 0 && (collection < 0 || hash.b < usedHash)) {
481 collection = 1;
482 usedHash = hash.b;
483 }
484 if (plant >= 0 && (collection < 0 || hash.c < usedHash)) collection = 2;
485
486 if (collection == 0) addUrban(position, urbanLevel, hash.e);
487 if (collection == 1) addFarm(position, hash.e);
488 if (collection == 2) addPlant(position, hash.e);
489 }
490
491 if (special != 0) addSpecial(position, hash.e);
492 }
493
501 [[nodiscard]] static int pick(std::int32_t level, float hash) noexcept {
502 if (level <= 0) return -1;
503 for (std::int32_t index = 0; index < kFeatureThresholdCount; ++index) {
504 if (hash < featureThreshold(level - 1, index)) return static_cast<int>(index);
505 }
506 return -1;
507 }
508
510 void addUrban(HexVec3 position, std::int32_t level, float orientation) {
511 const int boxes = level < 1 ? 1 : (level > 3 ? 3 : level);
512 const float total = HexMetrics::kWallHeight * (0.75f + 0.35f * static_cast<float>(level - 1));
513 const float base = HexMetrics::innerRadius() * 0.42f;
514 const float piece = total / static_cast<float>(boxes);
515 // The orientation hash turns the stack so neighbouring cities do not all
516 // face the same way; it is applied as the mesh's yaw about the world up axis.
517 const HexVec3 right = yaw(orientation, HexVec3{1.f, 0.f, 0.f});
518 const HexVec3 fwd = yaw(orientation, HexVec3{0.f, 0.f, 1.f});
519
520 float lift = 0.f;
521 for (int index = 0; index < boxes; ++index) {
522 const float scale = 1.f - 0.25f * static_cast<float>(index);
523 const HexVec3 halfExtents{base * scale, piece * 0.5f, base * scale};
524 const HexVec3 centre = position + HexVec3{0.f, lift + piece * 0.5f, 0.f};
525 emitBox(out_, map_.noise(), centre, right, HexVec3{0.f, 1.f, 0.f}, fwd, halfExtents, kUrbanCode);
526 lift += piece;
527 }
528 }
529
531 void addFarm(HexVec3 position, float orientation) {
532 const float half = HexMetrics::kWallHeight * 0.12f;
533 const HexVec3 right = yaw(orientation, HexVec3{1.f, 0.f, 0.f});
534 const HexVec3 fwd = yaw(orientation, HexVec3{0.f, 0.f, 1.f});
535 const HexVec3 halfExtents{HexMetrics::innerRadius() * 0.5f, half, HexMetrics::innerRadius() * 0.5f};
536 emitBox(out_, map_.noise(), position + HexVec3{0.f, half, 0.f}, right, HexVec3{0.f, 1.f, 0.f}, fwd,
537 halfExtents, kFarmCode);
538 }
539
541 void addPlant(HexVec3 position, float orientation) {
542 const float height = HexMetrics::kWallHeight * 0.55f;
543 const float radius = HexMetrics::innerRadius() * 0.3f;
544 const float waist = position.y + height * 0.35f;
545 const HexVec3 top{position.x, position.y + height, position.z};
546 const HexVec3 bottom{position.x, position.y, position.z};
547 const HexVec3 first = yaw(orientation, HexVec3{radius, 0.f, 0.f});
548 const HexVec3 ring[4] = {HexVec3{position.x + first.x, waist, position.z + first.z},
549 HexVec3{position.x - first.z, waist, position.z + first.x},
550 HexVec3{position.x - first.x, waist, position.z - first.z},
551 HexVec3{position.x + first.z, waist, position.z - first.x}};
552
553 for (int index = 0; index < 4; ++index) {
554 const int next = (index + 1) % 4;
555 emitTriangle(out_, map_.noise(), ring[index], top, ring[next], kPlantCode);
556 emitTriangle(out_, map_.noise(), ring[next], bottom, ring[index], kPlantCode);
557 }
558 }
559
561 void addSpecial(HexVec3 position, float orientation) {
562 const float height = HexMetrics::kWallHeight * 1.5f;
563 const float base = HexMetrics::innerRadius() * 0.3f;
564 const float footer = height * 0.2f;
565 const float shaft = height * 0.55f;
566 const HexVec3 right = yaw(orientation, HexVec3{1.f, 0.f, 0.f});
567 const HexVec3 fwd = yaw(orientation, HexVec3{0.f, 0.f, 1.f});
568
569 emitBox(out_, map_.noise(), position + HexVec3{0.f, footer * 0.5f, 0.f}, right, HexVec3{0.f, 1.f, 0.f}, fwd,
570 HexVec3{base, footer * 0.5f, base}, kSpecialCode);
571 emitBox(out_, map_.noise(), position + HexVec3{0.f, footer + shaft * 0.5f, 0.f}, right, HexVec3{0.f, 1.f, 0.f},
572 fwd, HexVec3{base * 0.6f, shaft * 0.5f, base * 0.6f}, kSpecialCode);
573 }
574
575 const HexMap& map_;
576 HexMeshData& out_;
577 HexHashGrid grid_;
578};
579
580} // namespace
581
583 const auto wrap = [](std::int32_t value) noexcept {
584 std::int32_t wrapped = value % kSize;
585 if (wrapped < 0) wrapped += kSize;
586 return static_cast<std::uint32_t>(wrapped);
587 };
588 const auto grid = [](float coordinate) noexcept {
589 return static_cast<std::int32_t>(std::floor(coordinate * kScale));
590 };
591
592 const std::uint32_t x = wrap(grid(position.x));
593 const std::uint32_t z = wrap(grid(position.z));
594
595 // Five independent streams of one integer hash; the channel constant mirrors
596 // the lane separation used by `HexNoise`.
597 const auto lane = [&](std::uint32_t channel) noexcept {
598 std::uint32_t h = x * 0x9E3779B1u;
599 h ^= z * 0x85EBCA77u;
600 h = (h ^ (h >> 15)) * 0xC2B2AE3Du;
601 h ^= (seed_ + channel * 0x9E3779B9u) * 0x27D4EB2Fu;
602 h = (h ^ (h >> 13)) * 0x165667B1u;
603 h ^= h >> 16;
604 return static_cast<float>(h >> 8) * (1.f / 16777216.f);
605 };
606
608 hash.a = lane(0u);
609 hash.b = lane(1u);
610 hash.c = lane(2u);
611 hash.d = lane(3u);
612 hash.e = lane(4u);
613 return hash;
614}
615
616float featureThreshold(std::int32_t level, std::int32_t index) noexcept {
617 // The reference table has three levels with three entries each; both indices
618 // are clamped so an out-of-range level or entry can never read past it.
619 if (level < 0) level = 0;
620 if (level > 2) level = 2;
621 if (index < 0) index = 0;
623
624 constexpr float kThresholds[3][kFeatureThresholdCount] = {
625 {0.0f, 0.0f, 0.4f},
626 {0.0f, 0.4f, 0.6f},
627 {0.4f, 0.6f, 0.8f},
628 };
629 return kThresholds[static_cast<std::size_t>(level)][static_cast<std::size_t>(index)];
630}
631
632void buildWallMesh(const HexMap& map, std::int32_t chunkIndex, HexMeshData& out) {
633 out.clear();
634 if (map.empty() || chunkIndex < 0 || chunkIndex >= map.chunkCount()) {
635 out.finalize();
636 return;
637 }
638
639 WallMesher mesher(map, out);
640 mesher.run(chunkIndex);
641 out.finalize();
642}
643
644void buildFeatureMesh(const HexMap& map, std::int32_t chunkIndex, HexMeshData& out) {
645 out.clear();
646 if (map.empty() || chunkIndex < 0 || chunkIndex >= map.chunkCount()) {
647 out.finalize();
648 return;
649 }
650
651 FeatureMesher mesher(map, out);
652 mesher.run(chunkIndex);
653 out.finalize();
654}
655
656} // namespace eve::hexmap
double value
SQInteger top
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
float length
Definition CaveMesh.cpp:94
float thickness
Definition CaveMesh.cpp:83
int column
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
DiagnosticCode code
uint32_t i0
Definition Grass.cpp:61
float u
Definition Grass.cpp:233
double r
Packed per-cell state records of the hex map.
Axial hex coordinates and world-space conversion.
Deterministic feature placement for hex decorations and walls.
std::int32_t plant
Editable hex cell grid: topology, queries, picking and authoring.
CPU vertex/index container shared by the hex surface builders.
Pointy-top hex metrics, directions and vertex helpers.
Deterministic asset-free noise for hex perturbation and generation.
float v
HexVec3 up
const HexCellData * leftCell
HexVec3 left
HexVec3 right
const HexCellData * rightCell
std::int32_t first
int h
std::uint32_t height
std::array< float, 3 > position
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
int level
std::weak_ptr< Run > run
Definition OnnxGpgpu.cpp:25
float f
float radius
float halfWidth
float halfHeight
V3 origin
Definition RoadBake.cpp:138
RoadLaneDirection direction
const RoadEdge * edge
uint32_t index
glm::vec3 point
float bottom
float angle
HexHash sample(HexVec3 position) const noexcept
Samples the five-component hash at a world-space position.
An editable, chunked, pointy-top hex map.
Definition HexMap.h:72
std::int32_t chunkCount() const noexcept
Total number of chunks.
Definition HexMap.h:102
bool empty() const noexcept
Whether the map holds any cell.
Definition HexMap.h:92
CPU-side triangle soup for one hex chunk surface.
Definition HexMeshData.h:24
void finalize() noexcept
Computes flat per-face normals. Must be called before upload.
void clear() noexcept
Removes every vertex and index, keeping the allocated capacity.
static HexVec3 firstSolidCorner(HexDirection d) noexcept
First solid corner of direction.
Definition HexMetrics.h:202
static constexpr float kWallThickness
Wall thickness.
Definition HexMetrics.h:152
static HexVec3 wallThicknessOffset(HexVec3 near, HexVec3 far) noexcept
Half-thickness offset applied to a wall segment.
Definition HexMetrics.h:255
static HexVec3 secondSolidCorner(HexDirection d) noexcept
Second solid corner of direction.
Definition HexMetrics.h:204
static HexVec3 wallLerp(HexVec3 near, HexVec3 far) noexcept
Position of the midpoint of a wall between two elevations.
Definition HexMetrics.h:246
static HexVec3 bridge(HexDirection d) noexcept
Bridge vector from the solid edge of direction to the neighbour.
Definition HexMetrics.h:214
static constexpr float kWallHeight
Height of a city/farm wall.
Definition HexMetrics.h:148
static constexpr float kCellPerturbStrength
Strength of the XZ position perturbation.
Definition HexMetrics.h:138
static constexpr int kChunkSizeZ
Chunk size in the Z dimension.
Definition HexMetrics.h:158
static constexpr float innerRadius() noexcept
Inner radius of a hex cell.
Definition HexMetrics.h:176
static constexpr int kChunkSizeX
Chunk size in the X dimension.
Definition HexMetrics.h:156
void buildFeatureMesh(const HexMap &map, std::int32_t chunkIndex, HexMeshData &out)
Builds the urban, farm, plant and special decorations of one chunk.
constexpr float kWallTowerThreshold
Height of a city/farm wall, matching HexMetrics::kWallHeight.
Definition HexFeatures.h:67
float featureThreshold(std::int32_t level, std::int32_t index) noexcept
Feature-placement thresholds of the reference project.
constexpr HexDirection opposite(HexDirection d) noexcept
The direction opposite to d.
Definition HexMetrics.h:65
constexpr std::int32_t kHexDirectionCount
Number of hex edges / facing directions.
Definition HexMetrics.h:62
constexpr HexEdgeType edgeType(int elevation1, int elevation2) noexcept
The relationship between two elevations (single-step changes are slopes).
Definition HexMetrics.h:96
void buildWallMesh(const HexMap &map, std::int32_t chunkIndex, HexMeshData &out)
Builds the wall and bridge geometry of one chunk.
constexpr std::int32_t kFeatureThresholdCount
Number of threshold entries per feature level.
Definition HexFeatures.h:64
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
HexDirection
Hex facing directions, counter-clockwise from north-east.
Definition HexMetrics.h:59
double sample(const Heightmap &map, double u, double v)
Sample.
Vec2 normalize(const Vec2 &a)
Normalize.
Definition UrbanTypes.h:44
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Definition Widget.cpp:679
Five-component pseudo-random value used to pick and orient one feature.
Definition HexFeatures.h:16
Minimal 3-component float vector used by the hex mesh builders.
Definition HexMetrics.h:18