载入中...
搜索中...
未找到
TerrainDerivedMap.cpp
浏览该文件的文档.
3
4#include <numbers>
5
6namespace eve::procgen {
10 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.heightmapSafeSample: finite data required"));
11 return Result<float>::success(source.height(std::clamp(x, 0, source.getWidth() - 1),
12 std::clamp(z, 0, source.getHeight() - 1)));
13}
14
16 if (!raster_detail::validRaster(source) || !std::isfinite(x) || !std::isfinite(z) || x < 0 || x > 1 || z < 0 ||
17 z > 1)
20 "terrain.heightmapNormalizedSample: finite data and coordinates in [0,1] required"));
21 const double px = double(x) * source.getWidth(), pz = double(z) * source.getHeight();
22 const int x0 = std::min(int(px), source.getWidth() - 1), z0 = std::min(int(pz), source.getHeight() - 1);
23 const int x1 = std::min(x0 + 1, source.getWidth() - 1), z1 = std::min(z0 + 1, source.getHeight() - 1);
24 const double tx = px - x0, tz = pz - z0;
25 const double value = (1 - tx) * (1 - tz) * source.height(x0, z0) +
26 (1 - tx) * tz * source.height(x0, z1) + tx * (1 - tz) * source.height(x1, z0) +
27 tx * tz * source.height(x1, z1);
28 return Result<float>::success(float(value));
29}
30
32 if (source.getWidth() == 0 && source.getHeight() == 0 && source.data().empty()) return Result<bool>::success(false);
33 if (source.getWidth() <= 0 || source.getHeight() <= 0 ||
34 source.data().size() != size_t(source.getWidth()) * size_t(source.getHeight()))
36 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.heightmapHasData: malformed storage"));
37 return Result<bool>::success(true);
38}
39
41 auto hasData = terrainHeightmapHasData(source);
42 if (!hasData.ok()) return Result<bool>::failure(hasData.status());
43 if (!hasData.value()) return Result<bool>::success(false);
44 const auto power = [](int value) { return value > 0 && (value & (value - 1)) == 0; };
45 return Result<bool>::success(power(source.getWidth()) && power(source.getHeight()));
46}
47namespace {
48bool compatible(const Heightmap& target, const Heightmap& source) {
50 source.getHeight() >= 2 && target.getWidth() == source.getWidth() && target.getHeight() == source.getHeight();
51}
52double sign(double value) { return value > 0 ? 1.0 : value < 0 ? -1.0 : 0.0; }
53}
54
57 using namespace raster_detail;
58 if (!compatible(target, source) || mode < TerrainHeightmapCurvature::Average ||
60 return invalid("terrain.heightmapCurvature: matching finite 2D rasters and known mode required");
61 constexpr double limit = 10000.0;
62 const int width = source.getWidth(), height = source.getHeight();
63 const double ux = 1.0 / (width - 1.0), uy = 1.0 / (height - 1.0);
64 const auto input = source.data();
65 std::vector<float> output(input.size());
66 auto at = [width](int x, int y) { return size_t(y) * width + x; };
67 auto normalized = [limit](double value) {
68 if (!std::isfinite(value)) value = 0;
69 return std::clamp(value, -limit, limit) / limit * 0.5 + 0.5;
70 };
71 for (int y = 0; y < height; ++y)
72 for (int x = 0; x < width; ++x) {
73 const int xm = std::max(x - 1, 0), xp = std::min(x + 1, width - 1);
74 const int ym = std::max(y - 1, 0), yp = std::min(y + 1, height - 1);
75 const double value = input[at(x, y)], left = input[at(xm, y)], right = input[at(xp, y)];
76 const double bottom = input[at(x, ym)], top = input[at(x, yp)];
77 const double leftBottom = input[at(xm, ym)], leftTop = input[at(xm, yp)];
78 const double rightBottom = input[at(xp, ym)], rightTop = input[at(xp, yp)];
79 const double dx = (right - left) / (2 * ux), dy = (top - bottom) / (2 * uy);
80 const double dxx = (right - 2 * value + left) / (ux * ux);
81 const double dyy = (top - 2 * value + bottom) / (uy * uy);
82 const double dxy = (rightTop - rightBottom - leftTop + leftBottom) / (4 * ux * uy);
83 const double denominator = dx * dx + dy * dy;
84 const double horizontal = normalized(-2 * (dy * dy * dxx + dx * dx * dyy - dx * dy * dxy) / denominator);
85 const double vertical = normalized(-2 * (dx * dx * dxx + dy * dy * dyy + dx * dy * dxy) / denominator);
87 ? horizontal
88 : mode == TerrainHeightmapCurvature::Vertical ? vertical
89 : (horizontal + vertical) * 0.5);
90 }
91 return publish(target, std::move(output));
92}
93
95 using namespace raster_detail;
96 if (!compatible(target, source) || mode < TerrainHeightmapAspect::Aspect || mode > TerrainHeightmapAspect::Easterness)
97 return invalid("terrain.heightmapAspect: matching finite 2D rasters and known mode required");
98 const int width = source.getWidth(), height = source.getHeight();
99 const double ux = 1.0 / (width - 1.0), uy = 1.0 / (height - 1.0);
100 const auto input = source.data();
101 std::vector<float> output(input.size());
102 auto at = [width](int x, int y) { return size_t(y) * width + x; };
103 constexpr double radians = std::numbers::pi / 180.0;
104 for (int y = 0; y < height; ++y)
105 for (int x = 0; x < width; ++x) {
106 const int xm = std::max(x - 1, 0), xp = std::min(x + 1, width - 1);
107 const int ym = std::max(y - 1, 0), yp = std::min(y + 1, height - 1);
108 const double dx = (double(input[at(xp, y)]) - input[at(xm, y)]) / (2 * ux);
109 const double dy = (double(input[at(x, yp)]) - input[at(x, ym)]) / (2 * uy);
110 const double magnitude = std::sqrt(dx * dx + dy * dy);
111 double angle = std::acos(-dy / magnitude) / radians;
112 if (!std::isfinite(angle)) angle = 0;
113 double aspect = 180.0 * (1.0 + sign(dx)) - sign(dx) * angle;
114 if (mode == TerrainHeightmapAspect::Northerness) aspect = std::cos(aspect * radians) * 0.5 + 0.5;
115 else if (mode == TerrainHeightmapAspect::Easterness) aspect = std::sin(aspect * radians) * 0.5 + 0.5;
116 else aspect /= 360.0;
117 if (!isRepresentable(aspect)) return invalid("terrain.heightmapAspect: derived value exceeds float range");
118 output[at(x, y)] = float(aspect);
119 }
120 return publish(target, std::move(output));
121}
122
125 using namespace raster_detail;
126 if (!validRaster(target) || !validRaster(source) || target.getWidth() != source.getWidth() ||
127 target.getHeight() != source.getHeight() || radius < 0 || radius > std::max(source.getWidth(), source.getHeight()) ||
129 mode < TerrainHeightmapNeighborhood::DeNoise || mode > TerrainHeightmapNeighborhood::ShrinkEdges)
130 return invalid("terrain.heightmapNeighborhood: matching finite rasters, valid radius and known mode required");
131 const int width = source.getWidth(), height = source.getHeight();
132 auto output = source.data();
133 auto at = [width](int x, int y) { return size_t(y) * width + x; };
134 const int beginX = mode == TerrainHeightmapNeighborhood::DeNoise ? radius : 0;
135 const int endX = mode == TerrainHeightmapNeighborhood::DeNoise ? width - radius : width;
136 const int beginY = mode == TerrainHeightmapNeighborhood::DeNoise ? radius : 0;
137 const int endY = mode == TerrainHeightmapNeighborhood::DeNoise ? height - radius : height;
138 for (int x = beginX; x < endX; ++x)
139 for (int y = beginY; y < endY; ++y) {
140 float minimum = std::numeric_limits<float>::max();
141 float maximum = std::numeric_limits<float>::lowest();
142 for (int dx = -radius; dx <= radius; ++dx) {
143 const int nx = x + dx;
144 if (nx < 0 || nx >= width) continue;
145 for (int dy = -radius; dy <= radius; ++dy) {
146 if (dx == 0 && dy == 0) continue;
147 const int ny = y + dy;
148 if (ny < 0 || ny >= height) continue;
149 const float neighbor = output[at(nx, ny)];
150 minimum = std::min(minimum, neighbor); maximum = std::max(maximum, neighbor);
151 }
152 }
153 float& value = output[at(x, y)];
155 else if (mode == TerrainHeightmapNeighborhood::GrowEdges && maximum > value) value = (maximum + value) * 0.5F;
156 else if (mode == TerrainHeightmapNeighborhood::ShrinkEdges && minimum < value) value = (minimum + value) * 0.5F;
157 }
158 return publish(target, std::move(output));
159}
160
162 using namespace raster_detail;
163 if (!validRaster(target) || !validRaster(source) || target.getWidth() != source.getWidth() ||
164 target.getHeight() != source.getHeight() || iterations < 0)
165 return invalid("terrain.heightmapSmooth: matching finite rasters and nonnegative iterations required");
166 const int width = source.getWidth(), height = source.getHeight();
167 auto output = source.data();
168 auto at = [width](int x, int y) { return size_t(y) * width + x; };
169 for (int pass = 0; pass < iterations; ++pass)
170 for (int x = 0; x < width; ++x)
171 for (int y = 0; y < height; ++y) {
172 const int left = std::max(0, x - 1), right = std::min(width - 1, x + 1);
173 const int bottom = std::max(0, y - 1), top = std::min(height - 1, y + 1);
174 output[at(x, y)] = std::clamp((output[at(left, y)] + output[at(right, y)] +
175 output[at(x, bottom)] + output[at(x, top)]) * 0.25F,
176 0.F, 1.F);
177 }
178 return publish(target, std::move(output));
179}
180
182 using namespace raster_detail;
183 if (!validRaster(target) || !validRaster(source) || target.getWidth() != source.getWidth() ||
184 target.getHeight() != source.getHeight() || radius < 0)
185 return invalid("terrain.heightmapSmoothRadius: matching finite rasters and nonnegative radius required");
186 radius = std::max(5, radius);
187 const int width = source.getWidth(), height = source.getHeight();
188 auto output = source.data();
189 if (radius < width && radius < height) {
190 const float factor = 1.F / float((2 * radius + 1) * (2 * radius + 1));
191 std::vector<float> filter = source.data();
192 for (float& value : filter) value *= factor;
193 auto at = [width](int x, int y) { return size_t(y) * width + x; };
194 for (int x = radius; x < width - radius; ++x) {
195 int y = radius;
196 float sum = 0.F;
197 for (int i = -radius; i <= radius; ++i)
198 for (int j = -radius; j <= radius; ++j) sum += filter[at(x + j, y + i)];
199 for (++y; y < height - radius; ++y) {
200 for (int j = -radius; j <= radius; ++j) {
201 sum -= filter[at(x + j, y - radius - 1)];
202 sum += filter[at(x + j, y + radius)];
203 }
204 output[at(x, y)] = sum;
205 }
206 }
207 }
208 return publish(target, std::move(output));
209}
210
212 using namespace raster_detail;
213 if (!validRaster(target) || !validRaster(source) || !validRaster(kernel) ||
214 target.getWidth() != source.getWidth() || target.getHeight() != source.getHeight() ||
215 kernel.getWidth() != kernel.getHeight() || kernel.getWidth() % 2 == 0)
216 return invalid("terrain.heightmapConvolve: matching finite rasters and an odd square finite kernel required");
217 const int width = source.getWidth(), height = source.getHeight(), kernelSize = kernel.getWidth();
218 const int radius = kernelSize / 2;
219 auto output = source.data();
220 auto at = [width](int x, int y) { return size_t(y) * width + x; };
221 double divisor = 0.0;
222 for (float value : kernel.data()) divisor += value;
223 if (std::abs(divisor) <= 0.000001) divisor = 1.0;
224 for (int x = 0; x < width; ++x)
225 for (int y = 0; y < height; ++y) {
226 if (x < radius || y < radius || x + radius >= width || y + radius >= height) continue;
227 double sum = 0.0;
228 for (int r = -radius; r <= radius; ++r)
229 for (int j = -radius; j <= radius; ++j)
230 sum += double(output[at(x + r, y + j)]) * kernel.height(r + radius, j + radius);
231 if (!isRepresentable(sum / divisor)) return invalid("terrain.heightmapConvolve: result exceeds float range");
232 output[at(x, y)] = std::clamp(float(sum / divisor), 0.F, 1.F);
233 }
234 return publish(target, std::move(output));
235}
236
238 using namespace raster_detail;
239 if (!validRaster(target) || !validRaster(source) || target.getWidth() != source.getWidth() ||
240 target.getHeight() != source.getHeight() || source.getWidth() < 2 || source.getHeight() < 2)
241 return invalid("terrain.heightmapSlope: matching finite rasters with dimensions at least two required");
242 const int width = source.getWidth(), height = source.getHeight();
243 std::vector<float> output(size_t(width) * height);
244 auto at = [width](int x, int y) { return size_t(y) * width + x; };
245 const double ux = 1.0 / double(width - 1), uy = 1.0 / double(height - 1);
246 for (int y = 0; y < height; ++y)
247 for (int x = 0; x < width; ++x) {
248 const int xp1 = x == width - 1 ? x : x + 1, xn1 = x == 0 ? x : x - 1;
249 const int yp1 = y == height - 1 ? y : y + 1, yn1 = y == 0 ? y : y - 1;
250 const double dx = (double(source.height(xp1, y)) * 0.5 - double(source.height(xn1, y)) * 0.5) /
251 (2.0 * ux);
252 const double dy = (double(source.height(x, yp1)) * 0.5 - double(source.height(x, yn1)) * 0.5) /
253 (2.0 * uy);
254 const double gradient = std::sqrt(dx * dx + dy * dy);
255 const double slope = gradient / std::sqrt(1.0 + gradient * gradient);
256 if (!isRepresentable(slope)) return invalid("terrain.heightmapSlope: derived value exceeds float range");
257 output[at(x, y)] = float(slope);
258 }
259 return publish(target, std::move(output));
260}
261
263 using namespace raster_detail;
264 if (!validRaster(target) || !validRaster(source) || target.getWidth() != source.getWidth() ||
265 target.getHeight() != source.getHeight() || !std::isfinite(divisor) || divisor == 0.F)
266 return invalid("terrain.heightmapQuantize: matching finite rasters and finite nonzero divisor required");
267 auto output = source.data();
268 for (float& value : output) {
269 const double quantized = std::nearbyint(double(value) / divisor) * divisor;
270 if (!isRepresentable(quantized)) return invalid("terrain.heightmapQuantize: result exceeds float range");
271 value = float(quantized);
272 }
273 return publish(target, std::move(output));
274}
275
276namespace {
277bool knownArithmetic(TerrainHeightmapArithmetic operation) {
279}
280double arithmetic(double left, double right, TerrainHeightmapArithmetic operation) {
284 return left / right;
285}
286float pcgResample(const Heightmap& raster, int x, int y, int targetWidth, int targetHeight) {
287 if (raster.getWidth() == targetWidth && raster.getHeight() == targetHeight) return raster.height(x, y);
288 return raster.sampleBilinear(float(x) * raster.getWidth() / targetWidth,
289 float(y) * raster.getHeight() / targetHeight);
290}
291}
292
294 TerrainHeightmapArithmetic operation, bool clampResult,
295 float minValue, float maxValue) {
296 using namespace raster_detail;
297 if (!validRaster(target) || !validRaster(source) || target.getWidth() != source.getWidth() ||
298 target.getHeight() != source.getHeight() || !std::isfinite(operand) || !knownArithmetic(operation) ||
299 (operation == TerrainHeightmapArithmetic::Divide && operand == 0.F) ||
300 (clampResult && (!std::isfinite(minValue) || !std::isfinite(maxValue) || minValue > maxValue)))
301 return invalid("terrain.heightmapScalarArithmetic: finite matching inputs, valid operation and clamp required");
302 auto output = source.data();
303 for (float& value : output) {
304 double result = arithmetic(value, operand, operation);
305 if (clampResult) result = std::clamp(result, double(minValue), double(maxValue));
306 if (!isRepresentable(result)) return invalid("terrain.heightmapScalarArithmetic: result exceeds float range");
307 value = float(result);
308 }
309 return publish(target, std::move(output));
310}
311
314 bool clampResult, float minValue, float maxValue) {
315 using namespace raster_detail;
316 if (!validRaster(target) || !validRaster(source) || !validRaster(operand) ||
317 target.getWidth() != source.getWidth() || target.getHeight() != source.getHeight() ||
318 !knownArithmetic(operation) ||
319 (clampResult && (!std::isfinite(minValue) || !std::isfinite(maxValue) || minValue > maxValue)))
320 return invalid("terrain.heightmapRasterArithmetic: finite inputs, valid operation and clamp required");
321 const int width = source.getWidth(), height = source.getHeight();
322 auto output = source.data();
323 for (int x = 0; x < width; ++x)
324 for (int y = 0; y < height; ++y) {
325 const float right = pcgResample(operand, x, y, width, height);
327 return invalid("terrain.heightmapRasterArithmetic: division by zero");
328 double result = arithmetic(source.height(x, y), right, operation);
329 if (clampResult) result = std::clamp(result, double(minValue), double(maxValue));
330 if (!isRepresentable(result)) return invalid("terrain.heightmapRasterArithmetic: result exceeds float range");
331 output[size_t(y) * width + x] = float(result);
332 }
333 return publish(target, std::move(output));
334}
335
337 const Heightmap& mask) {
338 using namespace raster_detail;
340 target.getWidth() != source.getWidth() || target.getHeight() != source.getHeight())
341 return invalid("terrain.heightmapLerp: finite inputs and target matching source required");
342 const int width = source.getWidth(), height = source.getHeight();
343 auto output = source.data();
344 for (int x = 0; x < width; ++x)
345 for (int y = 0; y < height; ++y) {
346 const double amount = std::clamp(double(pcgResample(mask, x, y, width, height)), 0.0, 1.0);
347 const double start = source.height(x, y), end = pcgResample(values, x, y, width, height);
348 const double result = start + (end - start) * amount;
349 if (!isRepresentable(result)) return invalid("terrain.heightmapLerp: result exceeds float range");
350 output[size_t(y) * width + x] = float(result);
351 }
352 return publish(target, std::move(output));
353}
354
356 TerrainHeightmapTransform transform, float parameter) {
357 using namespace raster_detail;
358 if (!validRaster(target) || !validRaster(source) || target.getWidth() != source.getWidth() ||
359 target.getHeight() != source.getHeight() || transform < TerrainHeightmapTransform::Invert ||
362 !std::isfinite(parameter)))
363 return invalid("terrain.heightmapTransform: matching finite rasters, known transform and parameter required");
364 auto output = source.data();
365 float minimum = 0.F, range = 0.F;
366 if (transform == TerrainHeightmapTransform::Normalise) {
367 const auto [minIt, maxIt] = std::minmax_element(output.begin(), output.end());
368 minimum = *minIt;
369 range = *maxIt - minimum;
370 }
371 for (float& value : output) {
372 double result = value;
373 if (transform == TerrainHeightmapTransform::Invert) result = 1.0 - value;
374 else if (transform == TerrainHeightmapTransform::Normalise && range > 0.F) result = (value - minimum) / range;
375 else if (transform == TerrainHeightmapTransform::Power) result = std::pow(double(value), parameter);
376 else if (transform == TerrainHeightmapTransform::Contrast) result = (double(value) - 0.5) * parameter + 0.5;
377 if (!isRepresentable(result)) return invalid("terrain.heightmapTransform: result exceeds float range");
378 value = float(result);
379 }
380 return publish(target, std::move(output));
381}
382
384 using namespace raster_detail;
387 return invalid("terrain.heightmapCopy: finite rasters and known copy mode required");
388 const int width = target.getWidth(), height = target.getHeight();
389 auto output = target.data();
390 for (int x = 0; x < width; ++x)
391 for (int y = 0; y < height; ++y) {
392 const size_t index = size_t(y) * width + x;
393 const float candidate = pcgResample(source, x, y, width, height);
394 if (mode == TerrainHeightmapCopy::Always ||
395 (mode == TerrainHeightmapCopy::IfLess && candidate < output[index]) ||
396 (mode == TerrainHeightmapCopy::IfGreater && candidate > output[index]))
397 output[index] = candidate;
398 }
399 return publish(target, std::move(output));
400}
401
402Result<int> copyTerrainHeightmapClamped(Heightmap& target, const Heightmap& source, float minValue, float maxValue) {
403 using namespace raster_detail;
404 if (!validRaster(target) || !validRaster(source) || !std::isfinite(minValue) || !std::isfinite(maxValue) ||
405 minValue > maxValue)
406 return invalid("terrain.heightmapCopyClamped: finite rasters and ordered finite bounds required");
407 const int width = target.getWidth(), height = target.getHeight();
408 std::vector<float> output(size_t(width) * height);
409 for (int x = 0; x < width; ++x)
410 for (int y = 0; y < height; ++y)
411 output[size_t(y) * width + x] =
412 std::clamp(pcgResample(source, x, y, width, height), minValue, maxValue);
413 return publish(target, std::move(output));
414}
415
417 using namespace raster_detail;
418 if (!validRaster(source)) return invalid("terrain.heightmapFlip: finite source required");
419 const auto input = source.data();
420 const int oldWidth = source.getWidth(), oldHeight = source.getHeight();
421 Heightmap candidate(oldHeight, oldWidth);
422 for (int x = 0; x < oldWidth; ++x)
423 for (int y = 0; y < oldHeight; ++y) candidate.setHeight(y, x, input[size_t(y) * oldWidth + x]);
424 int changed = int(candidate.data().size());
425 if (target.getWidth() == candidate.getWidth() && target.getHeight() == candidate.getHeight() &&
426 target.data().size() == candidate.data().size()) {
427 changed = 0;
428 for (size_t i = 0; i < candidate.data().size(); ++i) changed += target.data()[i] != candidate.data()[i];
429 }
430 target = std::move(candidate);
431 return Result<int>::success(changed);
432}
433
435 using namespace raster_detail;
439 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.heightmapMeasure: finite raster and known measure required"));
441 return Result<double>::success(*std::min_element(source.data().begin(), source.data().end()));
443 return Result<double>::success(*std::max_element(source.data().begin(), source.data().end()));
444 if (measure == TerrainHeightmapMeasure::BaseLevel) {
445 float level = 0.F;
446 const int width = source.getWidth(), height = source.getHeight();
447 for (int x = 0; x < width; ++x) level = std::max({level, source.height(x, 0), source.height(x, height - 1)});
448 for (int y = 0; y < height; ++y) level = std::max({level, source.height(0, y), source.height(width - 1, y)});
450 }
451 float sum = 0.F;
452 for (int x = 0; x < source.getWidth(); ++x)
453 for (int y = 0; y < source.getHeight(); ++y) sum += source.height(x, y);
454 if (!std::isfinite(sum))
456 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.heightmapMeasure: float sum overflow"));
457 if (measure == TerrainHeightmapMeasure::Average) sum /= float(source.getWidth() * source.getHeight());
458 return Result<double>::success(sum);
459}
460
462 const Heightmap& startHeights, const Heightmap& curves) {
463 using namespace raster_detail;
464 if (!validRaster(target) || !validRaster(source) || !validRaster(startHeights) || !validRaster(curves) ||
465 target.getWidth() != source.getWidth() || target.getHeight() != source.getHeight() ||
466 startHeights.getHeight() != 1 || curves.getHeight() != startHeights.getWidth() || curves.getWidth() < 2)
467 return invalid("terrain.heightmapTerraceQuantize: matching finite rasters, N-by-1 starts and curve rows required");
468 const int count = startHeights.getWidth();
469 for (int i = 0; i < count; ++i) {
470 const float start = startHeights.height(i, 0);
471 if (start > 1.F || (i > 0 && start <= startHeights.height(i - 1, 0)))
472 return invalid("terrain.heightmapTerraceQuantize: starts must be strictly increasing and at most one");
473 }
474 if (startHeights.height(count - 1, 0) >= 1.F)
475 return invalid("terrain.heightmapTerraceQuantize: final terrace must start below one");
476 auto output = source.data();
477 for (float& value : output) {
478 int terrace = count - 1;
479 while (terrace >= 0) {
480 const float start = startHeights.height(terrace, 0);
481 const float next = terrace == count - 1 ? 1.F : startHeights.height(terrace + 1, 0);
482 if (start <= value && value <= next) break;
483 --terrace;
484 }
485 if (terrace < 0)
486 return invalid("terrain.heightmapTerraceQuantize: source height is outside terrace coverage");
487 const double start = startHeights.height(terrace, 0);
488 const double next = terrace == count - 1 ? 1.0 : startHeights.height(terrace + 1, 0);
489 if (next <= start) return invalid("terrain.heightmapTerraceQuantize: final terrace must start below one");
490 const double t = (value - start) / (next - start);
491 const double x = t * (curves.getWidth() - 1);
492 const int x0 = int(x), x1 = std::min(x0 + 1, curves.getWidth() - 1);
493 const double shaped = std::lerp(double(curves.height(x0, terrace)), double(curves.height(x1, terrace)), x - x0);
494 const double result = start + (value - start) * shaped;
495 if (!isRepresentable(result)) return invalid("terrain.heightmapTerraceQuantize: result exceeds float range");
496 value = float(result);
497 }
498 return publish(target, std::move(output));
499}
500
503 using namespace raster_detail;
504 if (!validRaster(source) || source.getWidth() < 2 || source.getHeight() < 2 || !std::isfinite(x) ||
505 !std::isfinite(y) || mode < TerrainHeightmapSlopeQuery::GridForward ||
508 DiagnosticCode::InvalidArgument, "terrain.heightmapSlopeQuery: finite 2D raster, coordinates and known mode required"));
510 const int ix = int(x), iy = int(y);
511 if (x != ix || y != iy || ix < 0 || iy < 0 || ix >= source.getWidth() - 1 || iy >= source.getHeight() - 1)
513 DiagnosticCode::InvalidArgument, "terrain.heightmapSlopeQuery: forward mode requires an interior integer coordinate"));
514 const double center = source.height(ix, iy);
515 const double dx = source.height(ix + 1, iy) - center, dy = source.height(ix, iy + 1) - center;
516 return Result<double>::success(std::sqrt(dx * dx + dy * dy));
517 }
518 if (x < 0.F || x > 1.F || y < 0.F || y > 1.F)
520 DiagnosticCode::InvalidArgument, "terrain.heightmapSlopeQuery: normalized coordinates must be in [0,1]"));
521 auto sampleNormalized = [&source](double u, double v) {
522 return double(source.sampleBilinear(float(u * source.getWidth()), float(v * source.getHeight())));
523 };
524 const double invX = 1.0 / source.getWidth(), invY = 1.0 / source.getHeight();
526 const double dx = sampleNormalized(x + invX * 0.9, y) - sampleNormalized(x - invX * 0.9, y);
527 const double dy = sampleNormalized(x, y + invY * 0.9) - sampleNormalized(x, y - invY * 0.9);
528 return Result<double>::success(std::clamp(std::sqrt(dx * dx + dy * dy) * 10000.0, 0.0, 90.0));
529 }
530 const double center = sampleNormalized(x, y);
531 const double difference = std::abs(sampleNormalized(x - invX, y) - center) +
532 std::abs(sampleNormalized(x + invX, y) - center) +
533 std::abs(sampleNormalized(x, y - invY) - center) +
534 std::abs(sampleNormalized(x, y + invY) - center);
535 return Result<double>::success(difference * 100.0);
536}
537
539 using namespace raster_detail;
540 if (!validRaster(target) || !std::isfinite(value))
541 return invalid("terrain.heightmapFill: finite target and value required");
542 std::vector<float> output(target.data().size(), std::clamp(value, 0.F, 1.F));
543 return publish(target, std::move(output));
544}
545
547 using namespace raster_detail;
548 if (!validRaster(target) || !std::isfinite(value))
549 return invalid("terrain.heightmapSetSafe: finite target and value required");
550 x = std::clamp(x, 0, target.getWidth() - 1);
551 y = std::clamp(y, 0, target.getHeight() - 1);
552 auto output = target.data();
553 output[size_t(y) * target.getWidth() + x] = value;
554 return publish(target, std::move(output));
555}
556
557namespace {
558bool validStrip(const Heightmap& values, int length) {
559 return raster_detail::validRaster(values) && (values.getWidth() == 1 || values.getHeight() == 1) &&
560 int(values.data().size()) == length;
561}
562}
563
565 using namespace raster_detail;
566 if (!validRaster(target) || !validStrip(values, target.getHeight()) || rowX < 0 || rowX >= target.getWidth())
567 return invalid("terrain.heightmapSetRow: finite target, valid X and target-height strip required");
568 const auto strip = values.data();
569 auto output = target.data();
570 for (int y = 0; y < target.getHeight(); ++y) output[size_t(y) * target.getWidth() + rowX] = strip[size_t(y)];
571 return publish(target, std::move(output));
572}
573
575 using namespace raster_detail;
576 if (!validRaster(target) || !validStrip(values, target.getWidth()) || columnZ < 0 ||
577 columnZ >= target.getHeight())
578 return invalid("terrain.heightmapSetColumn: finite target, valid Z and target-width strip required");
579 const auto strip = values.data();
580 auto output = target.data();
581 for (int x = 0; x < target.getWidth(); ++x) output[size_t(columnZ) * target.getWidth() + x] = strip[size_t(x)];
582 return publish(target, std::move(output));
583}
584
586 const int changed = int(target.data().size());
587 target.resize(0, 0);
588 return Result<int>::success(changed);
589}
590}
LogicalId target
ActionParameterOperation operation
double value
Duration start
SQInteger top
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
int mask
float length
Definition CaveMesh.cpp:94
float nx
float ny
float pz
std::map< std::string, Var > values
EvpackChunkInput input
Definition Evpack.cpp:170
float maximum[3]
float minimum[3]
float u
Definition Grass.cpp:233
double r
float v
HexVec3 left
HexVec3 right
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
Range range
int level
float radius
float t
float power
Definition RockMesh.cpp:23
std::string filter
float dy
float dx
std::uint32_t count
int limit
Definition TreeMesh.cpp:164
int iterations
Definition TreeMesh.cpp:311
float size
Definition TreeMesh.cpp:156
uint32_t index
const UnitySourceAsset & source
std::size_t at
float bottom
float angle
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
In-memory terrain heightmap: a dense float grid (row-major, index = y * width + x) materialized from ...
Definition Heightmap.h:21
int getHeight() const
Returns the height.
Definition Heightmap.cpp:17
const std::vector< float > & data() const
Data.
Definition Heightmap.h:48
float height(int x, int y) const
Height.
Definition Heightmap.cpp:28
void setHeight(int x, int y, float h)
Sets the height.
Definition Heightmap.cpp:23
int getWidth() const
Returns the width.
Definition Heightmap.cpp:16
Result< int > publish(Heightmap &target, std::vector< float > candidate)
Publish.
bool validRaster(const Heightmap &map)
Valid raster.
Result< int > invalid(std::string message)
Invalid.
bool isRepresentable(double value)
True when representable.
Result< int > resetTerrainHeightmap(Heightmap &target)
Reset a heightmap to Pcg's empty zero-by-zero state.
TerrainHeightmapNeighborhood
Pcg HeightMap neighborhood mutation modes.
Result< int > quantizeTerrainHeightmapTerraces(Heightmap &target, const Heightmap &source, const Heightmap &startHeights, const Heightmap &curves)
Apply Pcg's curve-driven Quantize overload using one sampled curve per raster row.
Result< int > generateTerrainHeightmapSlope(Heightmap &target, const Heightmap &source)
Generate Pcg HeightMap.SlopeMap's normalized gradient magnitude.
Result< int > setTerrainHeightmapRow(Heightmap &target, int rowX, const Heightmap &values)
Set Pcg's fixed-X row from a one-dimensional strip.
Result< int > copyTerrainHeightmapClamped(Heightmap &target, const Heightmap &source, float minValue, float maxValue)
Copy a Pcg heightmap with inclusive output clamping and normalized resampling.
Result< int > applyTerrainHeightmapRasterArithmetic(Heightmap &target, const Heightmap &source, const Heightmap &operand, TerrainHeightmapArithmetic operation, bool clampResult, float minValue, float maxValue)
Apply a raster Pcg HeightMap arithmetic operation, resampling a differently sized operand.
TerrainHeightmapCurvature
Pcg HeightMap.CurvatureMap modes in source enum order.
Result< int > flipTerrainHeightmap(Heightmap &target, const Heightmap &source)
Transpose a Pcg HeightMap exactly as HeightMap.Flip does.
Result< int > smoothTerrainHeightmap(Heightmap &target, const Heightmap &source, int iterations)
Apply Pcg HeightMap.Smooth's in-place four-neighbor passes.
TerrainHeightmapSlopeQuery
Pcg HeightMap point slope query variants.
Result< int > fillTerrainHeightmap(Heightmap &target, float value)
Fill a finite Pcg heightmap with one value clamped to [0,1].
Result< int > smoothTerrainHeightmapRadius(Heightmap &target, const Heightmap &source, int radius)
Apply Pcg HeightMap.SmoothRadius's scaled sliding-window filter, including its first-row omission.
Result< int > convolveTerrainHeightmap(Heightmap &target, const Heightmap &source, const Heightmap &kernel)
Apply Pcg HeightMap.Convolve with an odd square kernel and source-order in-place feedback.
Result< int > copyTerrainHeightmap(Heightmap &target, const Heightmap &source, TerrainHeightmapCopy mode)
Copy or conditionally merge a Pcg heightmap, resampling when dimensions differ.
Result< int > transformTerrainHeightmap(Heightmap &target, const Heightmap &source, TerrainHeightmapTransform transform, float parameter)
Apply Pcg Invert, Normalise, Power, or Contrast to a finite heightmap.
Result< int > setTerrainHeightmapColumn(Heightmap &target, int columnZ, const Heightmap &values)
Set Pcg's fixed-Z column from a one-dimensional strip.
Result< bool > terrainHeightmapHasData(const Heightmap &source)
Return whether a heightmap contains a valid positive-size sample array.
Result< float > sampleTerrainHeightmapSafe(const Heightmap &source, int x, int z)
Read a finite heightmap at an integer coordinate clamped to its nearest border.
TerrainHeightmapTransform
Pcg HeightMap whole-raster value transforms.
Result< int > quantizeTerrainHeightmap(Heightmap &target, const Heightmap &source, float divisor)
Quantize every sample using Pcg HeightMap.Quantize's Mathf.Round rule.
Result< bool > terrainHeightmapIsPowerOfTwo(const Heightmap &source)
Return whether both positive heightmap dimensions are powers of two; malformed storage fails.
TerrainHeightmapMeasure
Pcg HeightMap read-only scalar measurements.
Result< double > measureTerrainHeightmap(const Heightmap &source, TerrainHeightmapMeasure measure)
Measure Pcg height range, sum, average, or scanner base level from current samples.
Result< int > filterTerrainHeightmapNeighborhood(Heightmap &target, const Heightmap &source, int radius, TerrainHeightmapNeighborhood mode)
Apply Pcg HeightMap.DeNoise, GrowEdges, or ShrinkEdges in source traversal order.
TerrainHeightmapCopy
Pcg HeightMap.Copy conditional modes in source enum order.
Result< float > sampleTerrainHeightmapNormalized(const Heightmap &source, float x, float z)
Read Pcg HeightMap's normalized bilinear sample using x*width and z*depth coordinates.
Result< int > lerpTerrainHeightmap(Heightmap &target, const Heightmap &source, const Heightmap &values, const Heightmap &mask)
Lerp source toward values under a mask using Pcg's clamped Mathf.Lerp semantics.
TerrainHeightmapAspect
Pcg HeightMap.Aspect modes in source enum order.
Result< int > generateTerrainHeightmapAspect(Heightmap &target, const Heightmap &source, TerrainHeightmapAspect mode)
Derive Pcg HeightMap.Aspect's aspect, northerness, or easterness raster.
Result< int > setTerrainHeightmapSafe(Heightmap &target, int x, int y, float value)
Set one sample after clamping its coordinates to the nearest border.
Result< int > applyTerrainHeightmapScalarArithmetic(Heightmap &target, const Heightmap &source, float operand, TerrainHeightmapArithmetic operation, bool clampResult, float minValue, float maxValue)
Apply a scalar Pcg HeightMap arithmetic operation with optional clamping.
TerrainHeightmapArithmetic
Pcg HeightMap pointwise arithmetic operations.
Result< double > measureTerrainHeightmapSlope(const Heightmap &source, float x, float y, TerrainHeightmapSlopeQuery mode)
Evaluate one of Pcg HeightMap's three point-slope formulas.
Result< int > generateTerrainHeightmapCurvature(Heightmap &target, const Heightmap &source, TerrainHeightmapCurvature mode)
Derive Pcg HeightMap.CurvatureMap's normalized differential curvature.