载入中...
搜索中...
未找到
HexTerrainMesh.cpp
浏览该文件的文档.
1#include "hexmap/HexMapMesh.h"
2
4#include "hexmap/HexMap.h"
6#include "hexmap/HexMetrics.h"
7#include "hexmap/HexNoise.h"
8
9#include <algorithm>
10#include <array>
11#include <cstdint>
12
13namespace eve::hexmap {
14
15namespace {
16
30[[nodiscard]] std::int32_t terrainOf(const HexCellData* cell) noexcept {
31 return cell == nullptr ? 0 : cell->values.terrainType();
32}
33
35[[nodiscard]] std::int32_t elevationOf(const HexCellData* cell) noexcept {
36 return cell == nullptr ? 0 : cell->values.elevation();
37}
38
40[[nodiscard]] HexVec3 horizontalPerturb(const HexNoise& noise, HexVec3 position) noexcept {
41 const HexVec4 sample = noise.sample(position.x, position.z);
44 return position;
45}
46
48[[nodiscard]] HexVec3 edgeSample(const EdgeVertices& edge, std::int32_t index) noexcept {
49 switch (index) {
50 case 0: return edge.v1;
51 case 1: return edge.v2;
52 case 2: return edge.v3;
53 case 3: return edge.v4;
54 default: return edge.v5;
55 }
56}
57
68[[nodiscard]] bool facesDown(HexVec3 a, HexVec3 b, HexVec3 c) noexcept {
69 const float abx = b.x - a.x;
70 const float abz = b.z - a.z;
71 const float acx = c.x - a.x;
72 const float acz = c.z - a.z;
73 return (abz * acx - abx * acz) < 0.f;
74}
75
77struct CornerCells {
78 HexVec3 up{};
79 HexVec3 left{};
80 HexVec3 right{};
81 const HexCellData* upCell = nullptr;
82 const HexCellData* leftCell = nullptr;
83 const HexCellData* rightCell = nullptr;
84};
85
87class HexMesher {
88public:
89 HexMesher(const HexMap& map, HexMeshData& out) noexcept : map_(map), out_(out) {}
90
92 void run(std::int32_t chunkIndex) {
93 for (std::int32_t row = 0; row < HexMetrics::kChunkSizeZ; ++row) {
94 for (std::int32_t column = 0; column < HexMetrics::kChunkSizeX; ++column) {
95 buildCell(map_.chunkCell(chunkIndex, column, row));
96 }
97 }
98 }
99
100private:
101 // --- per-cell entry point -----------------------------------------------
102
104 void buildCell(HexCoordinates coordinates) {
105 if (!map_.contains(coordinates)) return;
106 const HexCellData* cellData = map_.cell(coordinates);
107 if (cellData == nullptr) return;
108
109 const HexVec3 center = map_.cellPosition(coordinates);
110 for (std::int32_t index = 0; index < kHexDirectionCount; ++index) {
111 const auto direction = static_cast<HexDirection>(index);
112
113 EdgeVertices near = solidEdge(center, direction);
114 if (riverThrough(coordinates, direction, cellData)) {
115 // Carve a shallow channel along the river centreline (the middle sample only).
116 near.v3.y = HexMetrics::streamBedY(cellData->values.elevation());
117 }
118 appendEdgeFan(center, near, cellData);
119
120 HexCoordinates neighbourCoordinates{};
121 if (!map_.getNeighbor(coordinates, direction, neighbourCoordinates)) continue;
122 const HexCellData* neighbour = map_.cell(neighbourCoordinates);
123 if (neighbour == nullptr) continue;
124 appendConnection(coordinates, center, near, direction, cellData, neighbour, neighbourCoordinates);
125 }
126 }
127
129 [[nodiscard]] bool riverThrough(HexCoordinates coordinates, HexDirection direction,
130 const HexCellData* cellData) const noexcept {
131 if (cellData != nullptr && cellData->flags.hasRiverThrough(direction)) return true;
132 HexCoordinates neighbour{};
133 if (!map_.getNeighbor(coordinates, direction, neighbour)) return false;
134 const HexCellData* other = map_.cell(neighbour);
135 return other != nullptr && other->flags.hasRiverThrough(opposite(direction));
136 }
137
139 void appendConnection(HexCoordinates coordinates, HexVec3 center, const EdgeVertices& near, HexDirection direction,
140 const HexCellData* cellData, const HexCellData* neighbour,
141 HexCoordinates neighbourCoordinates) {
142 // Chunk ownership rule: only the NE, E and SE edges of a cell belong to the
143 // owning chunk, so every shared boundary is emitted exactly once - by the cell
144 // that owns that edge, never by both. The reference gates its whole
145 // `TriangulateConnection` call the same way. Emitting the strip from the other
146 // side too puts two coplanar (or, on a slope, two interpenetrating) copies on
147 // every boundary, which is what makes the terrain look like it has seams.
148 //
149 // An edge is shared by two cells, so half of the six directions is the right
150 // fraction. A corner below is shared by *three* cells and therefore needs its
151 // own, narrower gate; reusing this one emitted two of the three cells at half
152 // of all junctions, i.e. two coincident corner patches.
153 if (static_cast<std::int32_t>(direction) > static_cast<std::int32_t>(HexDirection::SE)) return;
154
155 const EdgeVertices far = shiftedEdge(center, direction, map_.cellPosition(neighbourCoordinates).y);
156
157 const HexTerrainWeights nearWeights = HexTerrainWeights::primary();
158 const HexTerrainWeights farWeights = HexTerrainWeights::blend(1.f);
159
160 switch (map_.edgeTypeTo(coordinates, neighbourCoordinates)) {
161 case HexEdgeType::Flat: appendBlendStrip(near, far, nearWeights, farWeights, cellData, neighbour); break;
163 appendSlopeTerraces(near, nearWeights, far, farWeights, cellData, neighbour,
164 elevationOf(cellData) <= elevationOf(neighbour));
165 break;
167 // A wall is a steep ramp, not a true vertical: `far` is the neighbour's solid edge
168 // bridged horizontally, raised to its elevation. Its winding therefore depends on
169 // which of the two cells owns the edge, exactly like a terrace ladder, so it goes
170 // through the same orientation check. Split into four sub-quads as well: a single
171 // `v1 -> v5` chord would not follow the fan's four-segment border, because
172 // `vertex()` perturbs every sample independently, and that mismatch is an open
173 // seam along the wall's foot.
174 for (std::int32_t i = 0; i < 4; ++i) {
175 emitQuadUpward(edgeSample(near, i), edgeSample(near, i + 1), edgeSample(far, i),
176 edgeSample(far, i + 1), nearWeights, nearWeights, farWeights, farWeights, cellData,
177 neighbour, cellData);
178 }
179 break;
180 }
181
182 // Corner gate, two directions out of six: each junction is shared by three
183 // cells and each of them reaches it from one of its own six corners, so one
184 // third of the (cell, corner) pairs must emit it. `NE`/`E` select exactly one
185 // per junction; adding `SE` selected two at half of them.
186 if (static_cast<std::int32_t>(direction) > static_cast<std::int32_t>(HexDirection::E)) return;
187
188 HexCoordinates nextCoordinates{};
189 if (!map_.getNeighbor(coordinates, next(direction), nextCoordinates)) return;
190 const HexCellData* nextCell = map_.cell(nextCoordinates);
191
192 CornerCells corner{};
193 corner.up = near.v5;
194 // Third corner vertex: this cell's corner carried across by the bridge of the
195 // *next* edge, exactly as the reference builds `e1.v5 + GetBridge(d.Next())`.
196 // Its Y comes from the next cell, so the point belongs to `nextCell`.
197 corner.left = near.v5 + HexMetrics::bridge(next(direction));
198 corner.left.y = map_.cellPosition(nextCoordinates).y;
199 corner.right = far.v5;
200 // `appendCorner` sorts the three points by the elevation of the cell they belong
201 // to, so each point must be paired with its *own* cell: `left` is the next
202 // cell's corner, `right` is the neighbour's (`far` is the neighbour's edge).
203 // Swapping the two picked the wrong terrace/cliff branch at every junction whose
204 // three cells differ in elevation. The sphere backend, which cannot share this
205 // code, pairs them this way.
206 corner.upCell = cellData;
207 corner.leftCell = nextCell;
208 corner.rightCell = neighbour;
209 appendCorner(corner);
210 }
211
212 // --- corner matrix ------------------------------------------------------
213
215 void appendCorner(const CornerCells& corner) {
216 const std::array<HexVec3, 3> positions{corner.up, corner.left, corner.right};
217 const std::array<const HexCellData*, 3> cells{corner.upCell, corner.leftCell, corner.rightCell};
218 std::array<std::int32_t, 3> order{0, 1, 2};
219 for (std::int32_t i = 0; i < 2; ++i) {
220 for (std::int32_t j = i + 1; j < 3; ++j) {
221 if (elevationOf(cells[static_cast<std::size_t>(order[static_cast<std::size_t>(j)])]) <
222 elevationOf(cells[static_cast<std::size_t>(order[static_cast<std::size_t>(i)])])) {
223 std::swap(order[static_cast<std::size_t>(i)], order[static_cast<std::size_t>(j)]);
224 }
225 }
226 }
227
228 const HexVec3 bottom = positions[static_cast<std::size_t>(order[0])];
229 const HexVec3 low = positions[static_cast<std::size_t>(order[1])];
230 const HexVec3 high = positions[static_cast<std::size_t>(order[2])];
231 const HexCellData* bottomCell = cells[static_cast<std::size_t>(order[0])];
232 const HexCellData* lowCell = cells[static_cast<std::size_t>(order[1])];
233 const HexCellData* highCell = cells[static_cast<std::size_t>(order[2])];
234
235 const HexEdgeType lowEdge = edgeType(elevationOf(bottomCell), elevationOf(lowCell));
236 const HexEdgeType highEdge = edgeType(elevationOf(bottomCell), elevationOf(highCell));
237
238 if (lowEdge == HexEdgeType::Slope && highEdge == HexEdgeType::Slope) {
239 cornerTerraces(bottom, low, high, bottomCell, lowCell, highCell);
240 } else if (lowEdge == HexEdgeType::Slope && highEdge == HexEdgeType::Flat) {
241 // `low` is the odd cell out and `high` sits level with `bottom`, so the fan has to
242 // climb from that flat pair to `low` rather than descend from it.
243 cornerTerracesToApex(high, highCell, bottom, bottomCell, low, lowCell);
244 } else if (lowEdge == HexEdgeType::Flat && highEdge == HexEdgeType::Slope) {
245 // Mirror of the above: `bottom` and `low` are level and `high` is the apex.
246 cornerTerracesToApex(bottom, bottomCell, low, lowCell, high, highCell);
247 } else if (lowEdge == HexEdgeType::Slope && highEdge == HexEdgeType::Cliff) {
248 cornerTerracesCliff(bottom, low, high, bottomCell, lowCell, highCell);
249 } else if (lowEdge == HexEdgeType::Cliff && highEdge == HexEdgeType::Slope) {
250 cornerCliffTerraces(bottom, low, high, bottomCell, lowCell, highCell);
251 } else if (edgeType(elevationOf(lowCell), elevationOf(highCell)) == HexEdgeType::Slope) {
252 // Neither side out of `bottom` is a slope: both are cliffs, so both of the corner's
253 // sides there are those walls' single straight end edges, and the only side carrying
254 // rungs is `low -> high`. Fanning from `bottom` puts the two straight chords along the
255 // walls and lays the ladder on the third side.
256 //
257 // The reference rotates its three cells here and hands them to a cliff-corner builder
258 // in that rotated order; those builders read their arguments as bottom/low/high, so a
259 // corner whose three cells all sit at different heights came out with the wrong side
260 // terraced and left open borders. A two-and-three-step rise beside a flat cell is the
261 // smallest pattern that reaches it.
262 appendBoundaryTriangle(bottom, HexTerrainWeights::primary(), low, HexTerrainWeights::primary(), high,
263 HexTerrainWeights::primary(), bottomCell, lowCell, highCell);
264 } else {
265 // Mirror of the reference winding: this engine's front face is the opposite
266 // handedness, so the flat corner fan is emitted high-before-low. "Flat" only
267 // describes the two edges though - with cliffs on both of them these three points
268 // still differ in height, and the elevation sort decides which of them is named
269 // `low`, so the winding is taken from the points.
270 emitCornerTriangle(bottom, high, low, HexTerrainWeights::primary(), HexTerrainWeights::primary(),
271 HexTerrainWeights::primary(), bottomCell, lowCell, highCell);
272 }
273 }
274
276 void cornerTerraces(const HexVec3& bottom, const HexVec3& left, const HexVec3& right, const HexCellData* bottomCell,
277 const HexCellData* leftCell, const HexCellData* rightCell) {
278 const HexTerrainWeights bottomWeights = HexTerrainWeights::primary();
279 const HexTerrainWeights leftWeights = HexTerrainWeights::primary();
280 const HexTerrainWeights rightWeights = HexTerrainWeights::primary();
281
282 // Same reason as the apex fan: the elevation sort permutes these points, so the fan
283 // cannot pick a winding by construction.
284
285 HexVec3 lastLeft = bottom;
286 HexVec3 lastRight = bottom;
287 HexTerrainWeights lastLeftWeights = bottomWeights;
288 HexTerrainWeights lastRightWeights = bottomWeights;
289 for (std::int32_t step = 1; step < HexMetrics::kTerracesPerSlope * 2; ++step) {
290 const float t = static_cast<float>(step) * HexMetrics::kHorizontalTerraceStepSize;
291 const HexTerrainWeights wl = HexTerrainWeights::lerp(bottomWeights, leftWeights, t);
292 const HexTerrainWeights wr = HexTerrainWeights::lerp(bottomWeights, rightWeights, t);
293 const HexVec3 boundaryLeft = HexMetrics::terraceLerp(bottom, left, step);
294 const HexVec3 boundaryRight = HexMetrics::terraceLerp(bottom, right, step);
295
296 // Both sides start at `bottom`, so the first band is a triangle. Emitting it as a
297 // quad would add a zero-area one whose self-edge the welded census reports as an
298 // open seam and whose doubled edge makes its neighbours look non-manifold.
299 if (step == 1) {
300 emitCornerTriangle(boundaryLeft, boundaryRight, bottom, wl, wr, bottomWeights, bottomCell, leftCell,
301 rightCell);
302 } else {
303 emitQuadUpward(boundaryLeft, boundaryRight, lastLeft, lastRight, wl, wr, lastLeftWeights,
304 lastRightWeights, bottomCell, leftCell, rightCell);
305 }
306 lastLeft = boundaryLeft;
307 lastRight = boundaryRight;
308 lastLeftWeights = wl;
309 lastRightWeights = wr;
310 }
311 emitQuadUpward(lastLeft, lastRight, left, right, lastLeftWeights, lastRightWeights, leftWeights, rightWeights,
312 bottomCell, leftCell, rightCell);
313 }
314
339 void cornerTerracesToApex(const HexVec3& left, const HexCellData* leftCell, const HexVec3& right,
340 const HexCellData* rightCell, const HexVec3& apex, const HexCellData* apexCell) {
341 const HexTerrainWeights leftWeights = HexTerrainWeights::primary();
342 const HexTerrainWeights rightWeights = HexTerrainWeights::primary();
343 const HexTerrainWeights apexWeights = HexTerrainWeights::primary();
344
345 HexVec3 lastLeft = left;
346 HexVec3 lastRight = right;
347 HexTerrainWeights lastLeftWeights = leftWeights;
348 HexTerrainWeights lastRightWeights = rightWeights;
349 for (std::int32_t step = 1; step < HexMetrics::kTerracesPerSlope * 2; ++step) {
350 const float t = static_cast<float>(step) * HexMetrics::kHorizontalTerraceStepSize;
351 const HexTerrainWeights wl = HexTerrainWeights::lerp(leftWeights, apexWeights, t);
352 const HexTerrainWeights wr = HexTerrainWeights::lerp(rightWeights, apexWeights, t);
353 const HexVec3 bl = HexMetrics::terraceLerp(left, apex, step);
354 const HexVec3 br = HexMetrics::terraceLerp(right, apex, step);
355
356 emitQuadUpward(lastLeft, lastRight, bl, br, lastLeftWeights, lastRightWeights, wl, wr, apexCell, leftCell,
357 rightCell);
358 lastLeft = bl;
359 lastRight = br;
360 lastLeftWeights = wl;
361 lastRightWeights = wr;
362 }
363 emitCornerTriangle(lastLeft, apex, lastRight, lastLeftWeights, apexWeights, lastRightWeights, apexCell,
365 }
366
379 void appendBoundaryTriangle(HexVec3 apex, const HexTerrainWeights& apexWeights, HexVec3 from,
380 const HexTerrainWeights& fromWeights, HexVec3 to,
381 const HexTerrainWeights& toWeights, const HexCellData* t0,
382 const HexCellData* t1, const HexCellData* t2) {
383 // When the ladder starts at the apex the first band has two coincident corners and covers
384 // nothing, but it would still contribute three directed edges.
385 const bool ladderStartsAtApex = apex.x == from.x && apex.y == from.y && apex.z == from.z;
386 HexVec3 last = from;
387 HexTerrainWeights lastWeights = fromWeights;
388 for (std::int32_t step = 1; step < HexMetrics::kTerracesPerSlope * 2; ++step) {
389 const float t = static_cast<float>(step) * HexMetrics::kHorizontalTerraceStepSize;
390 const HexTerrainWeights w = HexTerrainWeights::lerp(fromWeights, toWeights, t);
391 const HexVec3 rung = HexMetrics::terraceLerp(from, to, step);
392 if (step > 1 || !ladderStartsAtApex) {
393 emitCornerTriangle(rung, last, apex, w, lastWeights, apexWeights, t0, t1, t2);
394 }
395 last = rung;
396 lastWeights = w;
397 }
398 emitCornerTriangle(to, last, apex, toWeights, lastWeights, apexWeights, t0, t1, t2);
399 }
400
415 void cornerTerracesCliff(const HexVec3& bottom, const HexVec3& low, const HexVec3& high,
416 const HexCellData* bottomCell, const HexCellData* lowCell, const HexCellData* highCell) {
417 if (edgeType(elevationOf(lowCell), elevationOf(highCell)) == HexEdgeType::Slope) {
418 // Both `bottom -> low` and `low -> high` carry rungs, and each is measured from its own
419 // lower end - that is the direction the bands along those edges use, and a rung set
420 // taken from the other end is a different set. So the corner is a fan from `bottom`
421 // over the two ladders laid end to end, closed by the straight `high -> bottom` chord.
422 // The two calls meet along the interior `bottom -> low` chord; the first one's bands
423 // cover the sliver between that chord and the `bottom -> low` ladder, the second covers
424 // everything between the chord, the `low -> high` ladder and the cliff chord.
425 appendBoundaryTriangle(bottom, HexTerrainWeights::primary(), bottom, HexTerrainWeights::primary(), low,
426 HexTerrainWeights::primary(), bottomCell, lowCell, highCell);
427 appendBoundaryTriangle(bottom, HexTerrainWeights::primary(), low, HexTerrainWeights::primary(), high,
428 HexTerrainWeights::primary(), bottomCell, lowCell, highCell);
429 } else {
430 // Only `bottom -> low` carries rungs. Fanning from `high` leaves `low -> high` and
431 // `high -> bottom` as single segments, so the wall's end edge is met edge for edge.
432 appendBoundaryTriangle(high, HexTerrainWeights::primary(), bottom, HexTerrainWeights::primary(), low,
433 HexTerrainWeights::primary(), bottomCell, lowCell, highCell);
434 }
435 }
436
445 void cornerCliffTerraces(const HexVec3& bottom, const HexVec3& low, const HexVec3& high,
446 const HexCellData* bottomCell, const HexCellData* lowCell, const HexCellData* highCell) {
447 if (edgeType(elevationOf(highCell), elevationOf(lowCell)) == HexEdgeType::Slope) {
448 // The mirror of the branch above: both `bottom -> high` and `high -> low` carry rungs,
449 // each measured from its own lower end, so the fan runs from `bottom` over the two
450 // ladders laid end to end and the straight `bottom -> low` chord closes it.
451 appendBoundaryTriangle(bottom, HexTerrainWeights::primary(), bottom, HexTerrainWeights::primary(), high,
452 HexTerrainWeights::primary(), bottomCell, lowCell, highCell);
453 appendBoundaryTriangle(bottom, HexTerrainWeights::primary(), high, HexTerrainWeights::primary(), low,
454 HexTerrainWeights::primary(), bottomCell, lowCell, highCell);
455 } else {
456 appendBoundaryTriangle(low, HexTerrainWeights::primary(), bottom, HexTerrainWeights::primary(), high,
457 HexTerrainWeights::primary(), bottomCell, lowCell, highCell);
458 }
459 }
460
461 // --- vertex and triangle primitives -------------------------------------
462
470 void vertexAt(HexVec3 perturbed, const HexTerrainWeights& weights, const HexCellData* t0,
471 const HexCellData* t1, const HexCellData* t2) {
472 const float u = HexTerrainVertexCode::encodeIndices(terrainOf(t0), terrainOf(t1), terrainOf(t2));
474 out_.addVertex(perturbed, u, v);
475 }
476
478 void vertex(HexVec3 position, const HexTerrainWeights& weights, const HexCellData* t0, const HexCellData* t1,
479 const HexCellData* t2) {
480 vertexAt(horizontalPerturb(map_.noise(), position), weights, t0, t1, t2);
481 }
482
484 void emitPerturbedTriangle(HexVec3 a0, HexVec3 a1, HexVec3 a2, const HexTerrainWeights& w0,
485 const HexTerrainWeights& w1, const HexTerrainWeights& w2, const HexCellData* t0,
486 const HexCellData* t1, const HexCellData* t2) {
487 const std::uint32_t i0 = static_cast<std::uint32_t>(out_.vertexCount());
488 vertexAt(a0, w0, t0, t1, t2);
489 vertexAt(a1, w1, t0, t1, t2);
490 vertexAt(a2, w2, t0, t1, t2);
491 out_.addTriangle(i0, i0 + 1u, i0 + 2u);
492 }
493
495 void emitTriangle(const HexVec3& p0, const HexVec3& p1, const HexVec3& p2, const HexTerrainWeights& w,
496 const HexCellData* t0, const HexCellData* t1, const HexCellData* t2) {
497 const std::uint32_t i0 = static_cast<std::uint32_t>(out_.vertexCount());
498 vertex(p0, w, t0, t1, t2);
499 vertex(p1, w, t0, t1, t2);
500 vertex(p2, w, t0, t1, t2);
501 out_.addTriangle(i0, i0 + 1u, i0 + 2u);
502 }
503
505 void emitTriangle(const HexVec3& p0, const HexVec3& p1, const HexVec3& p2, const HexTerrainWeights& w0,
506 const HexTerrainWeights& w1, const HexTerrainWeights& w2, const HexCellData* t0,
507 const HexCellData* t1, const HexCellData* t2) {
508 const std::uint32_t i0 = static_cast<std::uint32_t>(out_.vertexCount());
509 vertex(p0, w0, t0, t1, t2);
510 vertex(p1, w1, t0, t1, t2);
511 vertex(p2, w2, t0, t1, t2);
512 out_.addTriangle(i0, i0 + 1u, i0 + 2u);
513 }
514
523 void emitQuadForward(const HexVec3& p0, const HexVec3& p1, const HexVec3& p2, const HexVec3& p3,
524 const HexTerrainWeights& w0, const HexTerrainWeights& w1, const HexTerrainWeights& w2,
525 const HexTerrainWeights& w3, const HexCellData* t0, const HexCellData* t1,
526 const HexCellData* t2) {
527 const std::uint32_t i0 = static_cast<std::uint32_t>(out_.vertexCount());
528 vertex(p0, w0, t0, t1, t2);
529 vertex(p1, w1, t0, t1, t2);
530 vertex(p2, w2, t0, t1, t2);
531 vertex(p3, w3, t0, t1, t2);
532 out_.addTriangle(i0, i0 + 2u, i0 + 1u);
533 out_.addTriangle(i0 + 1u, i0 + 2u, i0 + 3u);
534 }
535
550 void emitQuadUpward(const HexVec3& p0, const HexVec3& p1, const HexVec3& p2, const HexVec3& p3,
551 const HexTerrainWeights& w0, const HexTerrainWeights& w1, const HexTerrainWeights& w2,
552 const HexTerrainWeights& w3, const HexCellData* t0, const HexCellData* t1,
553 const HexCellData* t2) {
554 // Each triangle is oriented on its own: a quad spanning a terrace or a corner is not
555 // planar, so its two halves can disagree and orienting the first one alone leaves the
556 // other facing down.
557 const HexVec3 a0 = horizontalPerturb(map_.noise(), p0);
558 const HexVec3 a1 = horizontalPerturb(map_.noise(), p1);
559 const HexVec3 a2 = horizontalPerturb(map_.noise(), p2);
560 const HexVec3 a3 = horizontalPerturb(map_.noise(), p3);
561 const std::uint32_t i0 = static_cast<std::uint32_t>(out_.vertexCount());
562 vertexAt(a0, w0, t0, t1, t2);
563 vertexAt(a1, w1, t0, t1, t2);
564 vertexAt(a2, w2, t0, t1, t2);
565 vertexAt(a3, w3, t0, t1, t2);
566 if (facesDown(a0, a2, a1))
567 out_.addTriangle(i0, i0 + 1u, i0 + 2u);
568 else
569 out_.addTriangle(i0, i0 + 2u, i0 + 1u);
570 if (facesDown(a1, a2, a3))
571 out_.addTriangle(i0 + 1u, i0 + 3u, i0 + 2u);
572 else
573 out_.addTriangle(i0 + 1u, i0 + 2u, i0 + 3u);
574 }
575
593 void emitCornerTriangle(const HexVec3& p0, const HexVec3& p1, const HexVec3& p2, const HexTerrainWeights& w0,
594 const HexTerrainWeights& w1, const HexTerrainWeights& w2, const HexCellData* t0,
595 const HexCellData* t1, const HexCellData* t2) {
596 const HexVec3 a0 = horizontalPerturb(map_.noise(), p0);
597 const HexVec3 a1 = horizontalPerturb(map_.noise(), p1);
598 const HexVec3 a2 = horizontalPerturb(map_.noise(), p2);
599 if (facesDown(a0, a1, a2))
600 emitPerturbedTriangle(a0, a2, a1, w0, w2, w1, t0, t1, t2);
601 else
602 emitPerturbedTriangle(a0, a1, a2, w0, w1, w2, t0, t1, t2);
603 }
604
606 void appendEdgeFan(HexVec3 center, const EdgeVertices& edge, const HexCellData* cellData) {
607 const HexTerrainWeights w = HexTerrainWeights::primary();
608 const HexVec3 samples[5] = {edge.v1, edge.v2, edge.v3, edge.v4, edge.v5};
609 for (std::int32_t i = 0; i < 4; ++i) {
610 emitTriangle(center, samples[i], samples[i + 1], w, cellData, cellData, cellData);
611 }
612 }
613
615 void appendBlendStrip(const EdgeVertices& near, const EdgeVertices& far, const HexTerrainWeights& nearWeights,
616 const HexTerrainWeights& farWeights, const HexCellData* nearCell,
617 const HexCellData* farCell) {
618 for (std::int32_t i = 0; i < 4; ++i) {
619 const float t0 = static_cast<float>(i) * 0.25f;
620 const float t1 = static_cast<float>(i + 1) * 0.25f;
621 const HexTerrainWeights w0 = HexTerrainWeights::lerp(nearWeights, farWeights, t0);
622 const HexTerrainWeights w1 = HexTerrainWeights::lerp(nearWeights, farWeights, t1);
623 // Both vertices of an edge share their parameter: the near pair carries w0
624 // and the far pair carries w1. Swapping them makes every quad blend along the
625 // wrong axis and turns the band into a sawtooth.
626 emitQuadForward(edgeSample(near, i), edgeSample(near, i + 1), edgeSample(far, i), edgeSample(far, i + 1),
627 w0, w0, w1, w1, nearCell, farCell, nearCell);
628 }
629 }
630
639 void appendEdgeBand(const EdgeVertices& from, const HexTerrainWeights& fromWeights, const EdgeVertices& to,
640 const HexTerrainWeights& toWeights, const HexCellData* fromCell, const HexCellData* toCell) {
641 for (std::int32_t i = 0; i < 4; ++i) {
642 emitQuadForward(edgeSample(from, i), edgeSample(from, i + 1), edgeSample(to, i), edgeSample(to, i + 1),
643 fromWeights, fromWeights, toWeights, toWeights, fromCell, toCell, fromCell);
644 }
645 }
646
671 void appendSlopeTerraces(const EdgeVertices& near, const HexTerrainWeights& nearWeights, const EdgeVertices& far,
672 const HexTerrainWeights& farWeights, const HexCellData* nearCell,
673 const HexCellData* farCell, bool nearIsLow) {
674 const EdgeVertices& low = nearIsLow ? near : far;
675 const EdgeVertices& high = nearIsLow ? far : near;
676 const HexTerrainWeights& lowW = nearIsLow ? nearWeights : farWeights;
677 const HexTerrainWeights& highW = nearIsLow ? farWeights : nearWeights;
678
679 EdgeVertices previous = low;
680 HexTerrainWeights previousW = lowW;
681 for (std::int32_t step = 1; step < HexMetrics::kTerracesPerSlope * 2; ++step) {
682 const EdgeVertices boundary = EdgeVertices::terraceLerp(low, high, step);
683 const HexTerrainWeights weights =
684 HexTerrainWeights::lerp(lowW, highW, static_cast<float>(step) * HexMetrics::kHorizontalTerraceStepSize);
685 if (nearIsLow)
686 appendEdgeBand(previous, previousW, boundary, weights, nearCell, farCell);
687 else
688 appendEdgeBand(boundary, weights, previous, previousW, nearCell, farCell);
690 previousW = weights;
691 }
692 if (nearIsLow)
693 appendEdgeBand(previous, previousW, high, highW, nearCell, farCell);
694 else
695 appendEdgeBand(high, highW, previous, previousW, nearCell, farCell);
696 }
697
698 // --- edge frames --------------------------------------------------------
699
701 [[nodiscard]] static EdgeVertices solidEdge(HexVec3 center, HexDirection direction) noexcept {
702 return EdgeVertices{center + HexMetrics::firstSolidCorner(direction),
704 }
705
707 [[nodiscard]] static EdgeVertices shiftedEdge(HexVec3 center, HexDirection direction, float targetY) noexcept {
708 const HexVec3 bridge = HexMetrics::bridge(direction);
709 EdgeVertices edge = solidEdge(center, direction);
710 edge.v1 = edge.v1 + bridge;
711 edge.v2 = edge.v2 + bridge;
712 edge.v3 = edge.v3 + bridge;
713 edge.v4 = edge.v4 + bridge;
714 edge.v5 = edge.v5 + bridge;
715 edge.v1.y = targetY;
716 edge.v2.y = targetY;
717 edge.v3.y = targetY;
718 edge.v4.y = targetY;
719 edge.v5.y = targetY;
720 return edge;
721 }
722
723 const HexMap& map_;
724 HexMeshData& out_;
725};
726
727} // namespace
728
729void buildTerrainMesh(const HexMap& map, std::int32_t chunkIndex, HexMeshData& out) {
730 out.clear();
731 if (chunkIndex < 0 || chunkIndex >= map.chunkCount()) {
732 out.finalize();
733 return;
734 }
735
736 HexMesher mesher(map, out);
737 mesher.run(chunkIndex);
738 out.finalize();
739}
740
741} // namespace eve::hexmap
float w
Definition AnimClip.cpp:738
std::string from
int column
uint32_t i0
Definition Grass.cpp:61
float u
Definition Grass.cpp:233
std::vector< float > positions
Axial hex coordinates and world-space conversion.
Per-chunk mesh generation for every hex map surface.
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
const HexCellData * upCell
std::int32_t c
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::array< float, 3 > position
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< std::int32_t > order
graphics::Canvas * previous
std::weak_ptr< Run > run
Definition OnnxGpgpu.cpp:25
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
float t
RoadLaneDirection direction
const RoadEdge * edge
Cell cell
CommandLogBoundary boundary
Anchor rule, see above.
float weights[3]
float step
Definition TreeMesh.cpp:314
uint32_t index
float bottom
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
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 HexVec3 secondSolidCorner(HexDirection d) noexcept
Second solid corner of direction.
Definition HexMetrics.h:204
static constexpr int kTerracesPerSlope
Terraces generated per slope.
Definition HexMetrics.h:122
static HexVec3 bridge(HexDirection d) noexcept
Bridge vector from the solid edge of direction to the neighbour.
Definition HexMetrics.h:214
static constexpr float kHorizontalTerraceStepSize
Horizontal fraction of one terrace interpolation step.
Definition HexMetrics.h:126
static constexpr float kCellPerturbStrength
Strength of the XZ position perturbation.
Definition HexMetrics.h:138
static float streamBedY(int elevation) noexcept
Y coordinate of a river bed inside a cell.
Definition HexMetrics.h:269
static constexpr int kChunkSizeZ
Chunk size in the Z dimension.
Definition HexMetrics.h:158
static HexVec3 terraceLerp(HexVec3 a, HexVec3 b, int step) noexcept
Interpolates a position along a terraced slope.
Definition HexMetrics.h:229
static constexpr int kChunkSizeX
Chunk size in the X dimension.
Definition HexMetrics.h:156
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
EVENGINE_API_WORLD void buildTerrainMesh(const HexMap &map, std::int32_t chunkIndex, HexMeshData &out)
Builds the ground, terrace and cliff surface of one chunk.
HexEdgeType
Relationship between two neighbouring cells of different elevation.
Definition HexMetrics.h:93
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.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Definition Widget.cpp:679
static EdgeVertices terraceLerp(const EdgeVertices &a, const EdgeVertices &b, int step) noexcept
Terrace-interpolates every sample between two edges.
Definition HexMetrics.h:311
static float encodeIndices(std::int32_t a, std::int32_t b, std::int32_t c) noexcept
First texture coordinate, carrying the three terrain indices.
Definition HexMapMesh.h:31
static float encodeWeights(float weightB, float weightC) noexcept
Second texture coordinate, carrying the secondary/tertiary weights.
Definition HexMapMesh.h:35
static HexTerrainWeights blend(float t) noexcept
Weights of two cells, t towards the second.
Definition HexMapMesh.h:47
static HexTerrainWeights lerp(const HexTerrainWeights &a, const HexTerrainWeights &b, float t) noexcept
Interpolates two weight sets.
Definition HexMapMesh.h:53
static HexTerrainWeights primary() noexcept
Weights of the first cell alone.
Definition HexMapMesh.h:45