107 std::string *
error) {
108 if (
s.cellsX <= 0 ||
s.cellsY <= 0 ||
s.originX < 0 ||
s.originY < 0 ||
110 s.lod < 0 ||
s.lod > 20 || !std::isfinite(
s.cellSize) ||
s.cellSize <= 0.f ||
111 !std::isfinite(
s.heightScale) || !std::isfinite(
s.skirtDepth) ||
s.skirtDepth < 0.f) {
112 if (
error) *
error =
"terrain mesh: invalid bounds or settings";
116 if (
error) *
error =
"terrain mesh: layer dimensions do not match heightmap";
119 const int step = 1 <<
s.lod;
120 const std::vector<int> xs = sampleAxis(
s.originX,
s.cellsX,
step);
121 const std::vector<int> ys = sampleAxis(
s.originY,
s.cellsY,
step);
122 const auto [minIt, maxIt] = std::minmax_element(hm.
data().begin(), hm.
data().end());
123 const float minH = *minIt, rangeH = *maxIt - *minIt;
124 out = {}; out.lodStep_ =
step; out.originX_ =
s.originX; out.originY_ =
s.originY;
126 const int cols = int(xs.size()),
rows = int(ys.size());
127 out.splatWidth_ =
cols; out.splatHeight_ =
rows;
129 auto addVertex = [&](
int gx,
int gy,
float yOffset,
const std::array<float, 3> &
n) {
130 out.mesh_.
addVertex(
float(gx -
s.originX) *
s.cellSize,
131 hm.
height(gx, gy) *
s.heightScale + yOffset,
132 float(gy -
s.originY) *
s.cellSize,
n[0],
n[1],
n[2],
133 float(gx -
s.originX) / float(
s.cellsX),
134 float(gy -
s.originY) / float(
s.cellsY));
135 const auto weights = weightsAt(hm, layers, gx, gy, normalAt(hm, gx, gy,
s.cellSize,
s.heightScale), minH, rangeH);
136 out.weights_.insert(out.weights_.end(),
weights.begin(),
weights.end());
140 for (
int gy : ys)
for (
int gx : xs) addVertex(gx, gy, 0.f, normalAt(hm, gx, gy,
s.cellSize,
s.heightScale));
142 for (
int y = 0;
y + 1 <
rows; ++
y)
for (
int x = 0;
x + 1 <
cols; ++
x) {
143 const uint32_t
a = uint32_t(
y *
cols +
x),
b =
a + 1,
c =
a + uint32_t(
cols),
d =
c + 1;
147 if (
s.skirtDepth > 0.f) {
148 struct Edge { std::vector<std::pair<int, int>>
points; std::array<float, 3>
normal; };
149 std::array<Edge, 4>
edges;
150 for (
int gx : xs)
edges[0].
points.emplace_back(gx, ys.front());
151 for (
int gy : ys)
edges[1].
points.emplace_back(xs.back(), gy);
152 for (
auto it = xs.rbegin(); it != xs.rend(); ++it)
edges[2].
points.emplace_back(*it, ys.back());
153 for (
auto it = ys.rbegin(); it != ys.rend(); ++it)
edges[3].
points.emplace_back(xs.front(), *it);
154 edges[0].normal = {0.f, 0.f, -1.f};
edges[1].normal = {1.f, 0.f, 0.f};
155 edges[2].normal = {0.f, 0.f, 1.f};
edges[3].normal = {-1.f, 0.f, 0.f};
158 for (
const auto &[gx, gy] :
edge.points) {
159 addVertex(gx, gy, 0.f,
edge.normal); addVertex(gx, gy, -
s.skirtDepth,
edge.normal);
161 for (uint32_t i = 0; i + 1 <
edge.points.size(); ++i) {
162 const uint32_t topA =
start + i * 2, bottomA = topA + 1, topB = topA + 2, bottomB = topB + 1;
167 out.mesh_.
setMeta(
"kind",
"terrain.chunk");
168 out.mesh_.
setMeta(
"lod", std::to_string(
s.lod));
169 out.mesh_.
setMeta(
"originX", std::to_string(
s.originX));
170 out.mesh_.
setMeta(
"originY", std::to_string(
s.originY));
225 std::string *
error) {
226 if (
s.cellsX <= 0 ||
s.cellsY <= 0 ||
s.originX < 0 ||
s.originY < 0 ||
229 !std::isfinite(
s.cellSize) ||
s.cellSize <= 0.f ||
230 !std::isfinite(
s.heightScale) || !std::isfinite(
s.minWidth) ||
231 !std::isfinite(
s.maxWidth) ||
s.minWidth <= 0.f ||
s.maxWidth <
s.minWidth ||
232 !std::isfinite(
s.heightOffset) || !std::isfinite(
s.minSurfaceSlope) ||
233 !std::isfinite(
s.maxSurfaceSlope) ||
s.minSurfaceSlope < 0.f ||
234 s.maxSurfaceSlope <=
s.minSurfaceSlope) {
235 if (
error) *
error =
"terrain river mesh: invalid bounds or settings";
239 static constexpr std::array<int, 8> flowDx{-1, 0, 1, -1, 1, -1, 0, 1};
240 static constexpr std::array<int, 8> flowDy{-1, -1, -1, 0, 0, 1, 1, 1};
241 const size_t layerCells = size_t(layers.
getWidth()) * size_t(layers.
getHeight());
242 std::vector<float> channelFlow(layerCells, 0.f);
243 std::vector<uint16_t> riverIndegree(layerCells, 0);
245 const size_t i = size_t(
y * layers.
getWidth() +
x);
249 if (direction < 0 || direction >= 8)
continue;
252 layers.
isRiver(rx, ry)) ++riverIndegree[size_t(ry * layers.
getWidth() + rx)];
254 std::queue<size_t> riverQueue;
255 for (
size_t i = 0; i < layerCells; ++i)
256 if (channelFlow[i] > 0.f && riverIndegree[i] == 0) riverQueue.push(i);
257 while (!riverQueue.empty()) {
258 const size_t i = riverQueue.front(); riverQueue.pop();
259 const int x = int(i %
size_t(layers.
getWidth()));
260 const int y = int(i /
size_t(layers.
getWidth()));
262 if (direction < 0 || direction >= 8)
continue;
265 !layers.
isRiver(rx, ry))
continue;
266 const size_t receiver = size_t(ry * layers.
getWidth() + rx);
267 channelFlow[receiver] = std::max(channelFlow[receiver], channelFlow[i]);
268 if (riverIndegree[receiver] > 0 && --riverIndegree[receiver] == 0)
269 riverQueue.push(receiver);
272 for (
float flow : channelFlow) maxFlow = std::max(maxFlow, flow);
273 std::vector<uint8_t> calmReach(layerCells, 0);
276 if (!layers.
isRiver(
x,
y) || d < 0 || d >= 8)
continue;
277 const int rx =
x + flowDx[size_t(
d)], ry =
y + flowDy[size_t(
d)];
279 const float run =
s.cellSize * std::sqrt(
float(flowDx[
size_t(
d)] * flowDx[
size_t(
d)] +
280 flowDy[
size_t(
d)] * flowDy[
size_t(
d)]));
281 const float rise = std::abs(hm.
height(rx, ry) - hm.
height(
x,
y)) *
s.heightScale;
282 const float grade = rise /
run;
283 calmReach[size_t(
y * layers.
getWidth() +
x)] =
284 uint8_t(grade >=
s.minSurfaceSlope && grade <
s.maxSurfaceSlope);
288 std::vector<uint8_t> closedReach = calmReach;
290 const size_t i = size_t(
y * layers.
getWidth() +
x);
292 if (calmReach[i] || !layers.
isRiver(
x,
y) || d < 0 || d >= 8)
continue;
293 const int rx =
x + flowDx[size_t(
d)], ry =
y + flowDy[size_t(
d)];
294 const size_t receiver = size_t(ry * layers.
getWidth() + rx);
295 if (!calmReach[receiver])
continue;
296 bool calmDonor =
false;
297 for (
int incoming = 0; incoming < 8 && !calmDonor; ++incoming) {
298 const int nx =
x + flowDx[size_t(incoming)],
ny =
y + flowDy[size_t(incoming)];
300 if (nd < 0 || nd >= 8)
continue;
302 calmDonor = calmReach[
n] &&
nx + flowDx[size_t(nd)] ==
x &&
303 ny + flowDy[size_t(nd)] ==
y;
305 if (calmDonor) closedReach[i] = 1;
307 auto strongestDonor = [&](
int sx,
int sy) {
308 float donorFlow = -1.f;
309 int donorX =
sx, donorY =
sy;
310 for (
int candidate = 0; candidate < 8; ++candidate) {
311 const int dxCell =
sx + flowDx[size_t(candidate)];
312 const int dyCell =
sy + flowDy[size_t(candidate)];
313 if (dxCell < 0 || dyCell < 0 || dxCell >= layers.
getWidth() ||
316 if (donorDirection < 0 || donorDirection >= 8 ||
317 dxCell + flowDx[
size_t(donorDirection)] !=
sx ||
318 dyCell + flowDy[
size_t(donorDirection)] !=
sy)
continue;
320 if (flow > donorFlow) {
321 donorFlow = flow; donorX = dxCell; donorY = dyCell;
324 return std::array<int, 2>{donorX, donorY};
326 auto receiver = [&](
int sx,
int sy) {
328 if (direction < 0 || direction >= 8)
return std::array<int, 2>{
sx,
sy};
329 const int receiverX =
sx + flowDx[size_t(
direction)];
330 const int receiverY =
sy + flowDy[size_t(
direction)];
331 if (receiverX < 0 || receiverY < 0 || receiverX >= layers.
getWidth() ||
332 receiverY >= layers.
getHeight())
return std::array<int, 2>{
sx,
sy};
333 return std::array<int, 2>{receiverX, receiverY};
335 auto smoothCenter = [&](
int sx,
int sy) {
341 const auto pMinus1 = strongestDonor(
sx,
sy);
342 const auto pMinus2 = strongestDonor(pMinus1[0], pMinus1[1]);
343 const auto pPlus1 = receiver(
sx,
sy);
344 const auto pPlus2 = receiver(pPlus1[0], pPlus1[1]);
345 return std::array<float, 2>{
346 (float(pMinus2[0]) + 4.f * float(pMinus1[0]) + 6.f * float(
sx) +
347 4.f * float(pPlus1[0]) + float(pPlus2[0])) / 16.f,
348 (
float(pMinus2[1]) + 4.f * float(pMinus1[1]) + 6.f * float(
sy) +
349 4.f * float(pPlus1[1]) + float(pPlus2[1])) / 16.f};
351 auto sampleHeight = [&](
float sx,
float sy) {
354 const int x0 = int(std::floor(
sx)), y0 = int(std::floor(
sy));
355 const int x1 = std::min(x0 + 1, hm.
getWidth() - 1);
356 const int y1 = std::min(y0 + 1, hm.
getHeight() - 1);
357 const float tx =
sx - float(x0), ty =
sy - float(y0);
358 return std::lerp(std::lerp(hm.
height(x0, y0), hm.
height(x1, y0), tx),
359 std::lerp(hm.
height(x0, y1), hm.
height(x1, y1), tx), ty);
361 for (
int y =
s.originY;
y <=
s.originY +
s.cellsY; ++
y) {
362 for (
int x =
s.originX;
x <=
s.originX +
s.cellsX; ++
x) {
364 if (!layers.
isRiver(
x,
y) || direction < 0 || direction >= 8)
continue;
365 if (!closedReach[
size_t(
y * layers.
getWidth() +
x)])
continue;
366 const int receiverX =
x + flowDx[size_t(
direction)];
367 const int receiverY =
y + flowDy[size_t(
direction)];
368 const auto startCenter = smoothCenter(
x,
y);
369 const auto endCenter = smoothCenter(receiverX, receiverY);
370 const float tx0 = endCenter[0] - startCenter[0];
371 const float tz0 = endCenter[1] - startCenter[1];
372 const float tangentLengthSquared = tx0 * tx0 + tz0 * tz0;
373 if (tangentLengthSquared <= 1e-8f)
continue;
374 const float invLength = 1.f / std::sqrt(tangentLengthSquared);
375 const float tx = tx0 * invLength, tz = tz0 * invLength;
376 auto widthAt = [&](
int sampleX,
int sampleY) {
380 const float discharge = channelFlow[size_t(sampleY * layers.
getWidth() + sampleX)];
381 const float areaScale = maxFlow > 1.f
382 ? std::pow(std::clamp(discharge / maxFlow, 0.f, 1.f), 0.36f)
384 const float orderScale = std::clamp(
385 (
float(layers.
getStreamOrder(sampleX, sampleY)) - 1.f) / 4.f, 0.f, 1.f);
386 const float flowScale = std::max(areaScale, orderScale * 0.82f);
387 return 0.5f * std::lerp(
s.minWidth,
s.maxWidth, flowScale);
389 const float startWidth = widthAt(
x,
y);
390 const float endWidth = layers.
isRiver(receiverX, receiverY)
391 ? widthAt(receiverX, receiverY) : startWidth;
396 if (std::hypot(startFlowX, startFlowY) < 0.1f) {
397 startFlowX = tx; startFlowY = tz;
399 if (std::hypot(endFlowX, endFlowY) < 0.1f) {
400 endFlowX = tx; endFlowY = tz;
402 const float chordCells = std::sqrt(tangentLengthSquared);
403 auto curvePoint = [&](
float t) {
404 const float t2 =
t *
t, t3 = t2 *
t;
405 const float h00 = 2.f * t3 - 3.f * t2 + 1.f;
406 const float h10 = t3 - 2.f * t2 +
t;
407 const float h01 = -2.f * t3 + 3.f * t2;
408 const float h11 = t3 - t2;
409 return std::array<float, 2>{
410 h00 * startCenter[0] + h10 * startFlowX * chordCells +
411 h01 * endCenter[0] + h11 * endFlowX * chordCells,
412 h00 * startCenter[1] + h10 * startFlowY * chordCells +
413 h01 * endCenter[1] + h11 * endFlowY * chordCells};
415 constexpr int curveSegments = 5;
416 std::array<std::array<float, 2>, curveSegments + 1>
curve{};
418 curve[
size_t(
point)] = curvePoint(
float(
point) /
float(curveSegments));
422 const int following = std::min(curveSegments,
point + 1);
425 const float t = float(
point) / float(curveSegments);
434 float frameX = std::lerp(startFlowX, endFlowX,
t);
435 float frameY = std::lerp(startFlowY, endFlowY,
t);
439 if (frameLength < 1e-5f) {
441 frameLength = std::max(1e-5f, std::hypot(
frameX,
frameY));
443 const float qpx = -
frameY / frameLength;
444 const float qpz =
frameX / frameLength;
445 const float width = std::lerp(startWidth, endWidth,
t);
447 const float qx = (
q[0] - float(
s.originX)) *
s.cellSize;
448 const float qz = (
q[1] - float(
s.originY)) *
s.cellSize;
449 const float widthCells =
width /
s.cellSize;
450 const float centerBed = sampleHeight(
q[0],
q[1]);
451 const float leftBed = sampleHeight(
q[0] - qpx * widthCells,
452 q[1] - qpz * widthCells);
453 const float rightBed = sampleHeight(
q[0] + qpx * widthCells,
454 q[1] + qpz * widthCells);
459 const float qy = std::max({centerBed, leftBed, rightBed}) *
460 s.heightScale +
s.heightOffset;
462 0.f, 1.f, 0.f, 0.f,
t);
464 0.f, 1.f, 0.f, 1.f,
t);
466 for (
int segment = 0; segment < curveSegments; ++segment) {
467 const uint32_t
a = stripStart + uint32_t(segment * 2);
468 const uint32_t
b =
a + 1u,
c =
a + 2u, dVertex =
a + 3u;
474 out.
setMeta(
"kind",
"terrain.rivers");