30[[nodiscard]] std::int32_t terrainOf(
const HexCellData*
cell)
noexcept {
31 return cell ==
nullptr ? 0 :
cell->values.terrainType();
35[[nodiscard]] std::int32_t elevationOf(
const HexCellData*
cell)
noexcept {
36 return cell ==
nullptr ? 0 :
cell->values.elevation();
40[[nodiscard]] HexVec3 horizontalPerturb(
const HexNoise& noise, HexVec3
position)
noexcept {
48[[nodiscard]] HexVec3 edgeSample(
const EdgeVertices&
edge, std::int32_t
index)
noexcept {
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;
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;
89 HexMesher(
const HexMap& map, HexMeshData& out) noexcept : map_(map), out_(out) {}
92 void run(std::int32_t chunkIndex) {
95 buildCell(map_.chunkCell(chunkIndex,
column, row));
104 void buildCell(HexCoordinates coordinates) {
105 if (!map_.contains(coordinates))
return;
106 const HexCellData* cellData = map_.cell(coordinates);
107 if (cellData ==
nullptr)
return;
109 const HexVec3
center = map_.cellPosition(coordinates);
114 if (riverThrough(coordinates,
direction, cellData)) {
118 appendEdgeFan(
center, near, cellData);
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);
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);
140 const HexCellData* cellData,
const HexCellData* neighbour,
141 HexCoordinates neighbourCoordinates) {
155 const EdgeVertices far = shiftedEdge(
center,
direction, map_.cellPosition(neighbourCoordinates).y);
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));
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);
188 HexCoordinates nextCoordinates{};
189 if (!map_.getNeighbor(coordinates,
next(
direction), nextCoordinates))
return;
190 const HexCellData* nextCell = map_.cell(nextCoordinates);
192 CornerCells corner{};
198 corner.left.y = map_.cellPosition(nextCoordinates).y;
199 corner.right = far.v5;
206 corner.upCell = cellData;
207 corner.leftCell = nextCell;
208 corner.rightCell = neighbour;
209 appendCorner(corner);
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)]);
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])];
239 cornerTerraces(
bottom, low, high, bottomCell, lowCell, highCell);
243 cornerTerracesToApex(high, highCell,
bottom, bottomCell, low, lowCell);
246 cornerTerracesToApex(
bottom, bottomCell, low, lowCell, high, highCell);
248 cornerTerracesCliff(
bottom, low, high, bottomCell, lowCell, highCell);
250 cornerCliffTerraces(
bottom, low, high, bottomCell, lowCell, highCell);
276 void cornerTerraces(
const HexVec3&
bottom,
const HexVec3&
left,
const HexVec3&
right,
const HexCellData* bottomCell,
285 HexVec3 lastLeft =
bottom;
286 HexVec3 lastRight =
bottom;
287 HexTerrainWeights lastLeftWeights = bottomWeights;
288 HexTerrainWeights lastRightWeights = bottomWeights;
300 emitCornerTriangle(boundaryLeft, boundaryRight,
bottom, wl, wr, bottomWeights, bottomCell,
leftCell,
303 emitQuadUpward(boundaryLeft, boundaryRight, lastLeft, lastRight, wl, wr, lastLeftWeights,
306 lastLeft = boundaryLeft;
307 lastRight = boundaryRight;
308 lastLeftWeights = wl;
309 lastRightWeights = wr;
311 emitQuadUpward(lastLeft, lastRight,
left,
right, lastLeftWeights, lastRightWeights, leftWeights, rightWeights,
339 void cornerTerracesToApex(
const HexVec3&
left,
const HexCellData*
leftCell,
const HexVec3&
right,
340 const HexCellData*
rightCell,
const HexVec3& apex,
const HexCellData* apexCell) {
345 HexVec3 lastLeft =
left;
346 HexVec3 lastRight =
right;
347 HexTerrainWeights lastLeftWeights = leftWeights;
348 HexTerrainWeights lastRightWeights = rightWeights;
356 emitQuadUpward(lastLeft, lastRight, bl, br, lastLeftWeights, lastRightWeights, wl, wr, apexCell,
leftCell,
360 lastLeftWeights = wl;
361 lastRightWeights = wr;
363 emitCornerTriangle(lastLeft, apex, lastRight, lastLeftWeights, apexWeights, lastRightWeights, apexCell,
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) {
385 const bool ladderStartsAtApex = apex.x ==
from.x && apex.y ==
from.y && apex.z ==
from.z;
387 HexTerrainWeights lastWeights = fromWeights;
392 if (
step > 1 || !ladderStartsAtApex) {
393 emitCornerTriangle(rung, last, apex,
w, lastWeights, apexWeights, t0, t1, t2);
398 emitCornerTriangle(
to, last, apex, toWeights, lastWeights, apexWeights, t0, t1, t2);
415 void cornerTerracesCliff(
const HexVec3&
bottom,
const HexVec3& low,
const HexVec3& high,
416 const HexCellData* bottomCell,
const HexCellData* lowCell,
const HexCellData* highCell) {
445 void cornerCliffTerraces(
const HexVec3&
bottom,
const HexVec3& low,
const HexVec3& high,
446 const HexCellData* bottomCell,
const HexCellData* lowCell,
const HexCellData* highCell) {
470 void vertexAt(HexVec3 perturbed,
const HexTerrainWeights&
weights,
const HexCellData* t0,
471 const HexCellData* t1,
const HexCellData* t2) {
474 out_.addVertex(perturbed,
u,
v);
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);
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);
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);
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);
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);
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) {
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);
569 out_.addTriangle(
i0,
i0 + 2u,
i0 + 1u);
570 if (facesDown(a1, a2, a3))
571 out_.addTriangle(
i0 + 1u,
i0 + 3u,
i0 + 2u);
573 out_.addTriangle(
i0 + 1u,
i0 + 2u,
i0 + 3u);
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);
602 emitPerturbedTriangle(a0, a1, a2, w0, w1, w2, t0, t1, t2);
606 void appendEdgeFan(HexVec3
center,
const EdgeVertices&
edge,
const HexCellData* cellData) {
609 for (std::int32_t i = 0; i < 4; ++i) {
610 emitTriangle(
center, samples[i], samples[i + 1],
w, cellData, cellData, cellData);
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;
626 emitQuadForward(edgeSample(near, i), edgeSample(near, i + 1), edgeSample(far, i), edgeSample(far, i + 1),
627 w0, w0, w1, w1, nearCell, farCell, nearCell);
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);
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;
680 HexTerrainWeights previousW = lowW;
683 const HexTerrainWeights
weights =
693 appendEdgeBand(
previous, previousW, high, highW, nearCell, farCell);
695 appendEdgeBand(high, highW,
previous, previousW, nearCell, farCell);
731 if (chunkIndex < 0 || chunkIndex >= map.
chunkCount()) {
736 HexMesher mesher(map, out);
737 mesher.run(chunkIndex);
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.
const HexCellData * leftCell
const HexCellData * rightCell
const HexCellData * upCell
HexCoordinates to
Cell the unit walks towards on this segment.
std::array< float, 3 > position
graphics::Canvas * previous
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
RoadLaneDirection direction
CommandLogBoundary boundary
Anchor rule, see above.
An editable, chunked, pointy-top hex map.
std::int32_t chunkCount() const noexcept
Total number of chunks.
CPU-side triangle soup for one hex chunk surface.
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.
static HexVec3 secondSolidCorner(HexDirection d) noexcept
Second solid corner of direction.
static constexpr int kTerracesPerSlope
Terraces generated per slope.
static HexVec3 bridge(HexDirection d) noexcept
Bridge vector from the solid edge of direction to the neighbour.
static constexpr float kHorizontalTerraceStepSize
Horizontal fraction of one terrace interpolation step.
static constexpr float kCellPerturbStrength
Strength of the XZ position perturbation.
static float streamBedY(int elevation) noexcept
Y coordinate of a river bed inside a cell.
static constexpr int kChunkSizeZ
Chunk size in the Z dimension.
static HexVec3 terraceLerp(HexVec3 a, HexVec3 b, int step) noexcept
Interpolates a position along a terraced slope.
static constexpr int kChunkSizeX
Chunk size in the X dimension.
constexpr HexDirection opposite(HexDirection d) noexcept
The direction opposite to d.
constexpr std::int32_t kHexDirectionCount
Number of hex edges / facing directions.
constexpr HexEdgeType edgeType(int elevation1, int elevation2) noexcept
The relationship between two elevations (single-step changes are slopes).
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.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
HexDirection
Hex facing directions, counter-clockwise from north-east.
double sample(const Heightmap &map, double u, double v)
Sample.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
static EdgeVertices terraceLerp(const EdgeVertices &a, const EdgeVertices &b, int step) noexcept
Terrace-interpolates every sample between two edges.
static float encodeIndices(std::int32_t a, std::int32_t b, std::int32_t c) noexcept
First texture coordinate, carrying the three terrain indices.
static float encodeWeights(float weightB, float weightC) noexcept
Second texture coordinate, carrying the secondary/tertiary weights.
static HexTerrainWeights blend(float t) noexcept
Weights of two cells, t towards the second.
static HexTerrainWeights lerp(const HexTerrainWeights &a, const HexTerrainWeights &b, float t) noexcept
Interpolates two weight sets.
static HexTerrainWeights primary() noexcept
Weights of the first cell alone.