34[[nodiscard]]
float dotOf(HexVec3
a, HexVec3
b)
noexcept {
return a.x *
b.x +
a.y *
b.y +
a.z *
b.z; }
36[[nodiscard]]
float lengthOf(HexVec3
v)
noexcept {
return std::sqrt(dotOf(
v,
v)); }
38[[nodiscard]] HexVec3 crossOf(HexVec3
a, HexVec3
b)
noexcept {
39 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};
42[[nodiscard]] HexVec3
normalize(HexVec3
v)
noexcept {
43 const float len = lengthOf(
v);
44 if (!(len > 1e-9f))
return HexVec3{0.f, 1.f, 0.f};
45 const float inverse = 1.f / len;
46 return HexVec3{
v.x * inverse,
v.y * inverse,
v.z * inverse};
50[[nodiscard]] HexVec3 slerpDirection(HexVec3
a, HexVec3
b,
float t)
noexcept {
51 const float cosine = std::clamp(dotOf(
a,
b), -1.f, 1.f);
52 const float angle = std::acos(cosine);
56 const float sine = std::sin(
angle);
57 const float wa = std::sin((1.f -
t) *
angle) / sine;
58 const float wb = std::sin(
t *
angle) / sine;
59 return HexVec3{
a.x * wa +
b.x * wb,
a.y * wa +
b.y * wb,
a.z * wa +
b.z * wb};
69[[nodiscard]] EdgeVertices sphereEdge(HexVec3
a, HexVec3
b)
noexcept {
72 const float ra = lengthOf(
a);
73 const float rb = lengthOf(
b);
77 for (std::int32_t i = 0; i < 5; ++i) {
78 const float t =
static_cast<float>(i) * 0.25f;
79 const float radius = ra + (rb - ra) *
t;
80 *samples[i] = slerpDirection(dirA, dirB,
t) *
radius;
93[[nodiscard]] HexVec3 sphereTerraceLerp(HexVec3
a, HexVec3
b, std::int32_t
step)
noexcept {
97 const float ra = lengthOf(
a);
98 const float rb = lengthOf(
b);
99 return direction * (ra + (rb - ra) * vertical);
103[[nodiscard]] EdgeVertices sphereEdgeTerraceLerp(
const EdgeVertices&
a,
const EdgeVertices&
b,
104 std::int32_t
step)
noexcept {
106 result.v1 = sphereTerraceLerp(
a.v1,
b.v1,
step);
107 result.v2 = sphereTerraceLerp(
a.v2,
b.v2,
step);
108 result.v3 = sphereTerraceLerp(
a.v3,
b.v3,
step);
109 result.v4 = sphereTerraceLerp(
a.v4,
b.v4,
step);
110 result.v5 = sphereTerraceLerp(
a.v5,
b.v5,
step);
122[[nodiscard]]
bool perturbationDisabled() noexcept {
124 static const bool disabled = std::getenv(
"EVP_NO_PERTURB") !=
nullptr;
144[[nodiscard]] HexVec3 tangentPerturb(
const HexNoise& noise, HexVec3
position,
float strength)
noexcept {
145 if (perturbationDisabled())
return position;
151 const HexVec3
axis = std::fabs(
up.
y) < 0.9f ? HexVec3{0.f, 1.f, 0.f} : HexVec3{1.f, 0.f, 0.f};
152 const HexVec3 tangentX =
normalize(crossOf(axis,
up));
153 const HexVec3 tangentY = crossOf(
up, tangentX);
163[[nodiscard]] std::int32_t terrainOf(
const HexCellData*
cell)
noexcept {
164 return cell ==
nullptr ? 0 :
cell->values.terrainType();
168[[nodiscard]] HexVec3 sampleAt(
const EdgeVertices&
edge, std::int32_t
index)
noexcept {
170 case 0:
return edge.v1;
171 case 1:
return edge.v2;
172 case 2:
return edge.v3;
173 case 3:
return edge.v4;
174 default:
return edge.v5;
179[[nodiscard]] std::int32_t elevationOf(
const HexCellData*
cell)
noexcept {
180 return cell ==
nullptr ? 0 :
cell->values.elevation();
184[[nodiscard]]
bool samePoint(HexVec3
a, HexVec3
b)
noexcept {
188 constexpr float kEpsilon = 1e-3f;
189 return std::abs(
a.x -
b.x) < kEpsilon && std::abs(
a.y -
b.y) < kEpsilon && std::abs(
a.z -
b.z) < kEpsilon;
207[[nodiscard]]
bool facesInward(HexVec3
a, HexVec3
b, HexVec3
c)
noexcept {
208 const HexVec3
ab =
b -
a;
209 const HexVec3
ac =
c -
a;
235 [[nodiscard]]
static float perturbScaleFromEnvironment()
noexcept {
237 static const float scale = [] {
238 if (
const char*
value = std::getenv(
"EVP_PERTURB_SCALE"))
return static_cast<float>(std::strtod(
value,
nullptr));
247 SphereMesher(
const HexSphereMap& map, HexMeshData& out) noexcept
248 : map_(map), out_(out),
253 map.cellSpacing() * perturbScaleFromEnvironment()),
266 [[nodiscard]] HexVec3 solidCornerOf(
HexSphereCell cell, std::int32_t corner)
const noexcept {
267 const HexVec3 mixed =
269 return mixed * map_.surfaceRadius(
cell);
273 [[nodiscard]] EdgeVertices solidEdgeOf(
HexSphereCell cell, std::int32_t
d)
const noexcept {
274 const std::int32_t
edges = map_.neighborCount(
cell);
275 const std::int32_t nextD = (
d + 1) %
edges;
276 return sphereEdge(solidCornerOf(
cell,
d), solidCornerOf(
cell, nextD));
287 const std::int32_t back = map_.oppositeDirection(
cell,
d);
288 const std::int32_t nEdges = map_.neighborCount(neighbour);
289 if (back < 0 || nEdges <= 0)
return EdgeVertices{};
290 return sphereEdge(solidCornerOf(neighbour, (back + 1) % nEdges), solidCornerOf(neighbour, back));
294 [[nodiscard]]
bool riverThrough(
HexSphereCell cell, std::int32_t
d,
const HexCellData* cellData)
const noexcept {
295 if (cellData !=
nullptr && cellData->flags.hasRiverThrough(
static_cast<HexDirection>(
d)))
return true;
298 const HexCellData* other = map_.cellAt(neighbour);
299 const std::int32_t back = map_.oppositeDirection(
cell,
d);
300 return other !=
nullptr && back >= 0 && other->flags.hasRiverThrough(
static_cast<HexDirection>(back));
307 const HexCellData* cellData = map_.cellAt(
cell);
308 if (cellData ==
nullptr)
return;
310 const HexVec3
center = map_.cellPosition(
cell);
311 const std::int32_t
edges = map_.neighborCount(
cell);
312 for (std::int32_t
d = 0;
d <
edges; ++
d) {
313 EdgeVertices near = solidEdgeOf(
cell,
d);
314 if (riverThrough(
cell,
d, cellData)) {
316 near.v3 =
normalize(near.v3) * (lengthOf(near.v3) + streamBedDrop_);
318 appendEdgeFan(
center, near, cellData);
322 const HexCellData* neighbourData = map_.cellAt(neighbour);
323 if (neighbourData ==
nullptr)
continue;
324 appendConnection(
cell,
d, near, cellData, neighbour, neighbourData);
329 void appendConnection(
HexSphereCell cell, std::int32_t
d,
const EdgeVertices& near,
const HexCellData* cellData,
335 if (
cell > neighbour)
return;
337 const EdgeVertices far = farEdgeOf(
cell,
d, neighbour);
342 switch (map_.edgeTypeTo(
cell, neighbour)) {
344 appendBlendStrip(near, far, nearWeights, farWeights, cellData, neighbourData);
347 appendEdgeTerraces(near, nearWeights, far, farWeights, cellData, neighbourData);
354 for (std::int32_t i = 0; i < 4; ++i) {
355 emitQuadVertical(sampleAt(near, i), sampleAt(near, i + 1), sampleAt(far, i), sampleAt(far, i + 1),
356 nearWeights, nearWeights, farWeights, farWeights, cellData, neighbourData, cellData);
361 appendCorner(
cell,
d, cellData, neighbour, neighbourData);
376 const HexCellData* neighbourData) {
377 const std::int32_t
edges = map_.neighborCount(
cell);
378 const std::int32_t nextD = (
d + 1) %
edges;
381 const HexCellData* nextData = map_.cellAt(nextCell);
382 if (nextData ==
nullptr)
return;
390 if (nextCell <
cell)
return;
395 const std::int32_t back = map_.oppositeDirection(
cell,
d);
396 const std::int32_t back2 = map_.oppositeDirection(
cell, nextD);
397 const std::int32_t nextEdges = map_.neighborCount(nextCell);
398 if (back < 0 || back2 < 0 || nextEdges <= 0)
return;
400 CornerCells corner{};
401 corner.up = solidCornerOf(
cell, nextD);
402 corner.right = solidCornerOf(neighbour, back);
403 corner.left = solidCornerOf(nextCell, (back2 + 1) % nextEdges);
404 corner.upCell = cellData;
405 corner.rightCell = neighbourData;
406 corner.leftCell = nextData;
407 appendCornerTriangles(corner);
411 void appendCornerTriangles(
const CornerCells& corner) {
412 const std::array<HexVec3, 3>
positions{corner.up, corner.left, corner.right};
413 const std::array<const HexCellData*, 3>
cells{corner.upCell, corner.leftCell, corner.rightCell};
414 std::array<std::int32_t, 3>
order{0, 1, 2};
422 std::int32_t swaps = 0;
423 for (std::int32_t i = 0; i < 2; ++i) {
424 for (std::int32_t j = i + 1; j < 3; ++j) {
425 if (elevationOf(
cells[
static_cast<std::size_t
>(
order[
static_cast<std::size_t
>(j)])]) <
426 elevationOf(
cells[
static_cast<std::size_t
>(
order[
static_cast<std::size_t
>(i)])])) {
427 std::swap(
order[
static_cast<std::size_t
>(i)],
order[
static_cast<std::size_t
>(j)]);
434 const HexVec3 low =
positions[
static_cast<std::size_t
>(
order[1])];
435 const HexVec3 high =
positions[
static_cast<std::size_t
>(
order[2])];
436 const HexCellData* bottomCell =
cells[
static_cast<std::size_t
>(
order[0])];
437 const HexCellData* lowCell =
cells[
static_cast<std::size_t
>(
order[1])];
438 const HexCellData* highCell =
cells[
static_cast<std::size_t
>(
order[2])];
446 if (samePoint(
bottom, low) && samePoint(
bottom, high))
return;
448 const bool previousMirror = mirrorCorner_;
449 const bool previousIn = inCorner_;
450 cornerParity_ = (swaps % 2) != 0;
451 mirrorCorner_ = cornerParity_;
454 mirrorCorner_ = cornerParity_;
455 cornerTerraces(
bottom, low, high, bottomCell, lowCell, highCell);
459 mirrorCorner_ = cornerParity_;
460 cornerTerracesToApex(high, highCell,
bottom, bottomCell, low, lowCell);
463 mirrorCorner_ = cornerParity_;
464 cornerTerracesToApex(
bottom, bottomCell, low, lowCell, high, highCell);
466 mirrorCorner_ = cornerParity_;
467 cornerTerracesCliff(
bottom, low, high, bottomCell, lowCell, highCell);
469 mirrorCorner_ = cornerParity_;
470 cornerCliffTerraces(
bottom, low, high, bottomCell, lowCell, highCell);
482 mirrorCorner_ = cornerParity_;
484 appendBoundaryTriangle(
bottom,
w, low,
w, high,
w, bottomCell, lowCell, highCell);
486 mirrorCorner_ = cornerParity_;
493 mirrorCorner_ = previousMirror;
494 inCorner_ = previousIn;
498 void cornerTerraces(
const HexVec3&
bottom,
const HexVec3&
left,
const HexVec3&
right,
const HexCellData* bottomCell,
504 HexVec3 lastLeft =
bottom;
505 HexVec3 lastRight =
bottom;
506 HexTerrainWeights lastLeftWeights = bottomWeights;
507 HexTerrainWeights lastRightWeights = bottomWeights;
512 const HexVec3 boundaryLeft = sphereTerraceLerp(
bottom,
left,
step);
521 emitTriangle(boundaryLeft,
bottom, boundaryRight, wl, lastLeftWeights, wr, bottomCell,
leftCell,
523 }
else if (!samePoint(boundaryLeft, lastLeft) || !samePoint(boundaryRight, lastRight)) {
527 emitQuadForward(boundaryLeft, boundaryRight, lastLeft, lastRight, wl, wr, lastLeftWeights,
530 lastLeft = boundaryLeft;
531 lastRight = boundaryRight;
532 lastLeftWeights = wl;
533 lastRightWeights = wr;
535 if (!samePoint(lastLeft,
left) || !samePoint(lastRight,
right)) {
540 emitQuadForward(
left,
right, lastLeft, lastRight, leftWeights, rightWeights, lastLeftWeights,
557 void cornerTerracesToApex(
const HexVec3&
left,
const HexCellData*
leftCell,
const HexVec3&
right,
558 const HexCellData*
rightCell,
const HexVec3& apex,
const HexCellData* apexCell) {
563 HexVec3 lastLeft =
left;
564 HexVec3 lastRight =
right;
565 HexTerrainWeights lastLeftWeights = leftWeights;
566 HexTerrainWeights lastRightWeights = rightWeights;
571 const HexVec3 bl = sphereTerraceLerp(
left, apex,
step);
572 const HexVec3 br = sphereTerraceLerp(
right, apex,
step);
574 emitQuadForward(lastLeft, lastRight, bl, br, lastLeftWeights, lastRightWeights, wl, wr, apexCell,
578 lastLeftWeights = wl;
579 lastRightWeights = wr;
581 emitTriangle(lastLeft, apex, lastRight, lastLeftWeights, apexWeights, lastRightWeights, apexCell,
leftCell,
596 void appendBoundaryTriangle(HexVec3 apex,
const HexTerrainWeights& apexWeights, HexVec3
from,
597 const HexTerrainWeights& fromWeights, HexVec3
to,
598 const HexTerrainWeights& toWeights,
const HexCellData* t0,
const HexCellData* t1,
599 const HexCellData* t2) {
602 const bool ladderStartsAtApex = samePoint(apex,
from);
604 HexTerrainWeights lastWeights = fromWeights;
608 const HexVec3 rung = sphereTerraceLerp(
from,
to,
step);
609 if (
step > 1 || !ladderStartsAtApex) {
610 emitTriangle(rung, last, apex,
w, lastWeights, apexWeights, t0, t1, t2);
615 emitTriangle(
to, last, apex, toWeights, lastWeights, apexWeights, t0, t1, t2);
633 void cornerTerracesCliff(
const HexVec3&
bottom,
const HexVec3& low,
const HexVec3& high,
634 const HexCellData* bottomCell,
const HexCellData* lowCell,
const HexCellData* highCell) {
642 appendBoundaryTriangle(
bottom,
w,
bottom,
w, low,
w, bottomCell, lowCell, highCell);
643 appendBoundaryTriangle(
bottom,
w, low,
w, high,
w, bottomCell, lowCell, highCell);
647 appendBoundaryTriangle(high,
w,
bottom,
w, low,
w, bottomCell, lowCell, highCell);
659 void cornerCliffTerraces(
const HexVec3&
bottom,
const HexVec3& low,
const HexVec3& high,
660 const HexCellData* bottomCell,
const HexCellData* lowCell,
const HexCellData* highCell) {
663 appendBoundaryTriangle(
bottom,
w,
bottom,
w, high,
w, bottomCell, lowCell, highCell);
664 appendBoundaryTriangle(
bottom,
w, high,
w, low,
w, bottomCell, lowCell, highCell);
666 appendBoundaryTriangle(low,
w,
bottom,
w, high,
w, bottomCell, lowCell, highCell);
679 void vertexAt(HexVec3 perturbed,
const HexTerrainWeights&
weights,
const HexCellData* t0,
const HexCellData* t1,
680 const HexCellData* t2) {
683 out_.addVertex(perturbed,
u,
v);
687 [[nodiscard]] HexVec3 perturbedPosition(HexVec3
position)
const noexcept {
688 return tangentPerturb(map_.noise(),
position, perturbStrength_);
700 [[nodiscard]]
bool reverseWinding() const noexcept {
return !mirrorCorner_; }
703 [[nodiscard]]
static bool degenerate(HexVec3
a, HexVec3
b, HexVec3
c)
noexcept {
704 return samePoint(
a,
b) || samePoint(
b,
c) || samePoint(
a,
c);
708 void emitTriangle(
const HexVec3& p0,
const HexVec3& p1,
const HexVec3& p2,
const HexTerrainWeights&
w,
709 const HexCellData* t0,
const HexCellData* t1,
const HexCellData* t2) {
713 const HexVec3 a0 = perturbedPosition(p0);
714 const HexVec3 a1 = perturbedPosition(p1);
715 const HexVec3 a2 = perturbedPosition(p2);
716 if (degenerate(a0, a1, a2))
return;
717 const std::uint32_t
i0 =
static_cast<std::uint32_t
>(out_.vertexCount());
718 const bool reverse = reverseWinding();
719 vertexAt(a0,
w, t0, t1, t2);
720 vertexAt(a1,
w, t0, t1, t2);
721 vertexAt(a2,
w, t0, t1, t2);
723 out_.addTriangle(
i0,
i0 + 2u,
i0 + 1u);
725 out_.addTriangle(
i0,
i0 + 1u,
i0 + 2u);
729 void emitTriangle(
const HexVec3& p0,
const HexVec3& p1,
const HexVec3& p2,
const HexTerrainWeights& w0,
730 const HexTerrainWeights& w1,
const HexTerrainWeights& w2,
const HexCellData* t0,
731 const HexCellData* t1,
const HexCellData* t2) {
732 const HexVec3 a0 = perturbedPosition(p0);
733 const HexVec3 a1 = perturbedPosition(p1);
734 const HexVec3 a2 = perturbedPosition(p2);
735 if (degenerate(a0, a1, a2))
return;
736 const std::uint32_t
i0 =
static_cast<std::uint32_t
>(out_.vertexCount());
737 const bool reverse = reverseWinding();
738 vertexAt(a0, w0, t0, t1, t2);
739 vertexAt(a1, w1, t0, t1, t2);
740 vertexAt(a2, w2, t0, t1, t2);
742 out_.addTriangle(
i0,
i0 + 2u,
i0 + 1u);
744 out_.addTriangle(
i0,
i0 + 1u,
i0 + 2u);
754 void emitQuadForward(
const HexVec3& p0,
const HexVec3& p1,
const HexVec3& p2,
const HexVec3& p3,
755 const HexTerrainWeights& w0,
const HexTerrainWeights& w1,
const HexTerrainWeights& w2,
756 const HexTerrainWeights& w3,
const HexCellData* t0,
const HexCellData* t1,
757 const HexCellData* t2) {
758 const std::uint32_t
i0 =
static_cast<std::uint32_t
>(out_.vertexCount());
759 const HexVec3 a0 = perturbedPosition(p0);
760 const HexVec3 a1 = perturbedPosition(p1);
761 const HexVec3 a2 = perturbedPosition(p2);
762 const HexVec3 a3 = perturbedPosition(p3);
765 const bool firstOk = !degenerate(a0, a2, a1);
766 const bool secondOk = !degenerate(a1, a2, a3);
767 if (!firstOk && !secondOk)
return;
768 vertexAt(a0, w0, t0, t1, t2);
769 vertexAt(a1, w1, t0, t1, t2);
770 vertexAt(a2, w2, t0, t1, t2);
771 vertexAt(a3, w3, t0, t1, t2);
774 const bool reverse = reverseWinding();
777 out_.addTriangle(
i0,
i0 + 1u,
i0 + 2u);
779 out_.addTriangle(
i0,
i0 + 2u,
i0 + 1u);
783 out_.addTriangle(
i0 + 2u,
i0 + 1u,
i0 + 3u);
785 out_.addTriangle(
i0 + 1u,
i0 + 2u,
i0 + 3u);
790 void emitQuadVertical(
const HexVec3& p0,
const HexVec3& p1,
const HexVec3& p2,
const HexVec3& p3,
791 const HexTerrainWeights& w0,
const HexTerrainWeights& w1,
const HexTerrainWeights& w2,
792 const HexTerrainWeights& w3,
const HexCellData* t0,
const HexCellData* t1,
793 const HexCellData* t2) {
794 emitQuadForward(p0, p1, p2, p3, w0, w1, w2, w3, t0, t1, t2);
798 void appendEdgeFan(HexVec3
center,
const EdgeVertices&
edge,
const HexCellData* cellData) {
801 for (std::int32_t i = 0; i < 4; ++i) {
802 emitTriangle(
center, samples[i], samples[i + 1],
w, cellData, cellData, cellData);
807 void appendBlendStrip(
const EdgeVertices& near,
const EdgeVertices& far,
const HexTerrainWeights& nearWeights,
808 const HexTerrainWeights& farWeights,
const HexCellData* nearCell,
809 const HexCellData* farCell) {
810 const HexVec3 nearPositions[5] = {near.v1, near.v2, near.v3, near.v4, near.v5};
811 const HexVec3 farPositions[5] = {far.v1, far.v2, far.v3, far.v4, far.v5};
812 for (std::int32_t i = 0; i < 4; ++i) {
813 const float t0 =
static_cast<float>(i) * 0.25f;
814 const float t1 =
static_cast<float>(i + 1) * 0.25f;
819 emitQuadForward(nearPositions[i], nearPositions[i + 1], farPositions[i], farPositions[i + 1], w0, w0, w1,
820 w1, nearCell, farCell, nearCell);
825 void appendEdgeTerraces(EdgeVertices near,
const HexTerrainWeights& nearWeights, EdgeVertices far,
826 const HexTerrainWeights& farWeights,
const HexCellData* nearCell,
827 const HexCellData* farCell) {
833 const bool nearIsLow = elevationOf(nearCell) <= elevationOf(farCell);
834 const EdgeVertices& low = nearIsLow ? near : far;
835 const EdgeVertices& high = nearIsLow ? far : near;
836 const HexTerrainWeights& lowW = nearIsLow ? nearWeights : farWeights;
837 const HexTerrainWeights& highW = nearIsLow ? farWeights : nearWeights;
840 HexTerrainWeights previousW = lowW;
844 const EdgeVertices
boundary = sphereEdgeTerraceLerp(low, high,
step);
853 appendEdgeBand(
previous, high, previousW, highW, nearCell, farCell);
855 appendEdgeBand(high,
previous, highW, previousW, nearCell, farCell);
867 void appendEdgeBand(
const EdgeVertices&
from,
const EdgeVertices&
to,
const HexTerrainWeights& fromWeights,
868 const HexTerrainWeights& toWeights,
const HexCellData* nearCell,
const HexCellData* farCell) {
869 for (std::int32_t i = 0; i < 4; ++i) {
870 emitQuadForward(sampleAt(
from, i), sampleAt(
from, i + 1), sampleAt(
to, i), sampleAt(
to, i + 1),
871 fromWeights, fromWeights, toWeights, toWeights, nearCell, farCell, nearCell);
875 const HexSphereMap& map_;
877 float perturbStrength_;
878 float streamBedDrop_;
888 bool mirrorCorner_ =
false;
890 bool inCorner_ =
false;
892 bool cornerParity_ =
false;
896class SphereWaterMesher {
898 SphereWaterMesher(
const HexSphereMap& map, HexMeshData& out) noexcept : map_(map), out_(out) {}
913 [[nodiscard]]
float waterRadius(std::int32_t
waterLevel)
const noexcept {
915 return map_.sphereRadius() + (
static_cast<float>(
waterLevel) +
offset) * map_.elevationStep();
919 const HexCellData* data = map_.cellAt(
cell);
920 if (data ==
nullptr || !data->values.isUnderwater())
return;
925 const std::int32_t
edges = map_.neighborCount(
cell);
926 for (std::int32_t
d = 0;
d <
edges; ++
d) {
927 const HexCellData* neighbour = map_.cellAt(map_.neighbor(
cell,
d));
928 if (neighbour ==
nullptr || !neighbour->values.isUnderwater()) {
934 const float radius = waterRadius(data->values.waterLevel());
936 const std::int32_t corners = map_.cornerCountOf(
cell);
937 for (std::int32_t k = 0; k < corners; ++k) {
938 const HexVec3
a = map_.cornerDirection(
cell, k) *
radius;
939 const HexVec3
b = map_.cornerDirection(
cell, (k + 1) % corners) *
radius;
940 const std::uint32_t
i0 =
static_cast<std::uint32_t
>(out_.vertexCount());
941 out_.addVertex(
center, shore, 0.f);
942 out_.addVertex(
a, shore, 0.f);
943 out_.addVertex(
b, shore, 0.f);
947 out_.addTriangle(
i0,
i0 + 2u,
i0 + 1u);
949 out_.addTriangle(
i0,
i0 + 1u,
i0 + 2u);
953 const HexSphereMap& map_;
966 SphereMesher mesher(map, out);
978 SphereWaterMesher mesher(map, out);
std::vector< float > positions
Per-chunk mesh generation for every hex map surface.
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
Mesh generation for the spherical hex map surface.
HexCoordinates to
Cell the unit walks towards on this segment.
std::array< float, 3 > position
std::array< float, 3 > scale
graphics::Canvas * previous
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
RoadLaneDirection direction
CommandLogBoundary boundary
Anchor rule, see above.
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 constexpr int kTerracesPerSlope
Terraces generated per slope.
static constexpr float kVerticalTerraceStepSize
Vertical fraction of one terrace interpolation step.
static constexpr float kElevationStep
Vertical distance between two elevation levels.
static constexpr float kHorizontalTerraceStepSize
Horizontal fraction of one terrace interpolation step.
static constexpr float kWaterElevationOffset
Elevation offset of a water surface relative to the water level.
static constexpr float kCellPerturbStrength
Strength of the XZ position perturbation.
static constexpr float kOuterRadius
Outer (corner) radius of one hex cell in world units.
static constexpr float kStreamBedElevationOffset
Elevation offset of a carved river bed relative to the cell.
static constexpr float kSolidFactor
Factor of the solid, uniform-colour hex inscribed in a cell.
An editable hex map wrapped onto a sphere.
bool empty() const noexcept
Whether the map holds any cell.
void buildSphereWaterMesh(const HexSphereMap &map, HexMeshData &out)
Builds the ocean surface of a spherical hex map.
constexpr HexEdgeType edgeType(int elevation1, int elevation2) noexcept
The relationship between two elevations (single-step changes are slopes).
HexEdgeType
Relationship between two neighbouring cells of different elevation.
constexpr HexSphereCell kNoHexSphereCell
Returned by a spherical cell query that has no answer.
std::int32_t HexSphereCell
Dense identifier of one cell of a spherical hex topology.
void buildSphereTerrainMesh(const HexSphereMap &map, HexMeshData &out)
Builds the ground, terrace and cliff surface of a spherical hex map.
HexDirection
Hex facing directions, counter-clockwise from north-east.
Vec2 normalize(const Vec2 &a)
Normalize.
int axis(int64_t a, size_t rank)
Axis.
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.