载入中...
搜索中...
未找到
TerrainWaterField.cpp
浏览该文件的文档.
2#include <array>
5
6namespace eve::procgen {
7
10 using namespace raster_detail;
11 if (&target == &source || !validRaster(source) || !validRaster(target) ||
12 target.getWidth() != source.getHeight() || target.getHeight() != source.getWidth() ||
13 !std::isfinite(settings.dropletVolume) || settings.dropletVolume < 0.f ||
14 !std::isfinite(settings.absorptionRate) || settings.absorptionRate <= 0.f ||
15 settings.smoothIterations < 0)
16 return invalid("terrain.waterFlow: transposed finite rasters and valid droplet controls required");
17
18 const int sourceWidth = source.getWidth(), sourceHeight = source.getHeight();
19 std::vector<float> working = source.data();
20 std::vector<float> flow(size_t(sourceWidth) * sourceHeight, 0.f);
21 auto sourceIndex = [sourceWidth](int x, int z) { return size_t(z) * sourceWidth + x; };
22 for (int x0 = 1; x0 < sourceWidth - 1; ++x0) {
23 for (int z0 = 1; z0 < sourceHeight - 1; ++z0) {
24 float volume = settings.dropletVolume;
25 int x = x0, z = z0;
26 while (volume > 0.f) {
27 const size_t index = sourceIndex(x, z);
28 const double deposited = double(flow[index]) + settings.absorptionRate;
29 if (!isRepresentable(deposited)) return invalid("terrain.waterFlow: accumulation exceeds float range");
30 flow[index] = float(deposited);
31 const float nextVolume = volume - settings.absorptionRate;
32 if (nextVolume == volume)
33 return invalid("terrain.waterFlow: absorption is too small to advance finite float volume");
34 volume = nextVolume;
35
36 const float currentHeight = working[index];
37 float nextHeight = currentHeight;
38 int nextX = x, nextZ = z;
39 for (int nx = -1; nx < 2; ++nx)
40 for (int nz = -1; nz < 2; ++nz) {
41 const int testX = x + nx, testZ = z + nz;
42 if (testX < 0 || testX >= sourceWidth || testZ < 0 || testZ >= sourceHeight) continue;
43 const float candidate = working[sourceIndex(testX, testZ)];
44 if (candidate < nextHeight) {
45 nextX = testX; nextZ = testZ; nextHeight = candidate;
46 }
47 }
48 if (currentHeight == nextHeight) {
49 const double pooled = double(working[index]) + settings.absorptionRate;
50 if (!isRepresentable(pooled)) return invalid("terrain.waterFlow: pooled height exceeds float range");
51 working[index] = float(pooled);
52 } else {
53 x = nextX; z = nextZ;
54 }
55 }
56 }
57 }
58
59 std::vector<float> output(flow.size());
60 const int outWidth = target.getWidth(), outHeight = target.getHeight();
61 for (int x = 0; x < sourceWidth; ++x)
62 for (int z = 0; z < sourceHeight; ++z)
63 output[size_t(x) * outWidth + z] = flow[sourceIndex(x, z)];
64 auto safe = [&](int x, int z) -> float {
65 x = std::clamp(x, 0, outWidth - 1); z = std::clamp(z, 0, outHeight - 1);
66 return output[size_t(z) * outWidth + x];
67 };
68 for (int iteration = 0; iteration < settings.smoothIterations; ++iteration)
69 for (int x = 0; x < outWidth; ++x)
70 for (int z = 0; z < outHeight; ++z)
71 output[size_t(z) * outWidth + x] =
72 std::clamp((safe(x - 1, z) + safe(x + 1, z) + safe(x, z - 1) + safe(x, z + 1)) * 0.25F,
73 0.f, 1.f);
74 return publish(target, std::move(output));
75}
76
78 using namespace raster_detail;
79 if (!validRaster(target) || !validRaster(source) || target.getWidth() != source.getWidth() ||
80 target.getHeight() != source.getHeight() || iterations < 0)
81 return invalid("terrain.velocityFlow: matching finite rasters and nonnegative iterations required");
82 constexpr float time = 0.2F;
83 constexpr int left = 0, right = 1, bottom = 2, top = 3;
84 const int width = source.getWidth(), height = source.getHeight();
85 const auto terrain = source.data();
86 std::vector<float> water(terrain.size(), 0.0001F);
87 std::vector<std::array<float, 4>> flow(terrain.size());
88 auto index = [width](int x, int y) { return size_t(y) * width + x; };
89 for (int iteration = 0; iteration < iterations; ++iteration) {
90 for (int y = 0; y < height; ++y)
91 for (int x = 0; x < width; ++x) {
92 const int xm = std::max(x - 1, 0), xp = std::min(x + 1, width - 1);
93 const int ym = std::max(y - 1, 0), yp = std::min(y + 1, height - 1);
94 const size_t i = index(x, y);
95 const std::array<size_t, 4> neighbor = {index(xm, y), index(xp, y), index(x, ym), index(x, yp)};
96 std::array<float, 4> next{};
97 double sum = 0;
98 for (int direction = 0; direction < 4; ++direction) {
99 const double difference = double(water[i]) + terrain[i] - water[neighbor[direction]] -
100 terrain[neighbor[direction]];
101 const double value = std::max(0.0, double(flow[i][direction]) + difference);
102 if (!isRepresentable(value)) return invalid("terrain.velocityFlow: outflow exceeds float range");
103 next[direction] = float(value); sum += value;
104 }
105 if (sum > 0) {
106 const float scale = std::clamp(float(double(water[i]) / (sum * time)), 0.F, 1.F);
107 for (int direction = 0; direction < 4; ++direction) flow[i][direction] = next[direction] * scale;
108 } else {
109 flow[i] = {};
110 }
111 }
112 auto nextWater = water;
113 for (int y = 0; y < height; ++y)
114 for (int x = 0; x < width; ++x) {
115 const size_t i = index(x, y);
116 const double outgoing = double(flow[i][left]) + flow[i][right] + flow[i][bottom] + flow[i][top];
117 double incoming = 0;
118 if (x > 0) incoming += flow[index(x - 1, y)][right];
119 if (x + 1 < width) incoming += flow[index(x + 1, y)][left];
120 if (y > 0) incoming += flow[index(x, y - 1)][top];
121 if (y + 1 < height) incoming += flow[index(x, y + 1)][bottom];
122 const double value = std::max(0.0, double(water[i]) + (incoming - outgoing) * time);
123 if (!isRepresentable(value)) return invalid("terrain.velocityFlow: water value exceeds float range");
124 nextWater[i] = float(value);
125 }
126 water.swap(nextWater);
127 }
128 std::vector<float> output(terrain.size());
129 float minimum = std::numeric_limits<float>::infinity();
130 float maximum = -std::numeric_limits<float>::infinity();
131 for (int y = 0; y < height; ++y)
132 for (int x = 0; x < width; ++x) {
133 const size_t i = index(x, y);
134 const float dl = x == 0 ? 0.F : flow[index(x - 1, y)][right] - flow[i][left];
135 const float dr = x + 1 == width ? 0.F : flow[i][right] - flow[index(x + 1, y)][left];
136 const float dt = y + 1 == height ? 0.F : flow[index(x, y + 1)][bottom] - flow[i][top];
137 const float db = y == 0 ? 0.F : flow[i][bottom] - flow[index(x, y - 1)][top];
138 const double vx = (double(dl) + dr) * 0.5, vy = (double(db) + dt) * 0.5;
139 const double magnitude = std::sqrt(vx * vx + vy * vy);
140 if (!isRepresentable(magnitude)) return invalid("terrain.velocityFlow: velocity exceeds float range");
141 output[i] = float(magnitude); minimum = std::min(minimum, output[i]); maximum = std::max(maximum, output[i]);
142 }
143 const float range = maximum - minimum;
144 if (range < 1e-12F) std::fill(output.begin(), output.end(), 0.F);
145 else for (float& value : output) value = (value - minimum) / range;
146 return publish(target, std::move(output));
147}
148
150 int width = 0, height = 0;
151 std::vector<TerrainWaterSample> cells;
152};
156TerrainWaterField& TerrainWaterField::operator=(TerrainWaterField&&) noexcept = default;
157int TerrainWaterField::getWidth() const noexcept { return impl_ ? impl_->width : 0; }
158int TerrainWaterField::getHeight() const noexcept { return impl_ ? impl_->height : 0; }
159
161 using namespace raster_detail;
162 if (width <= 0 || height <= 0 || width > std::numeric_limits<int>::max() / height || !std::isfinite(depth) ||
163 depth < 0)
164 return invalid(
165 "terrain.water.reset: positive dimensions with int-sized cell count and finite nonnegative depth required");
166 auto candidate = std::make_unique<Impl>();
167 candidate->width = width;
168 candidate->height = height;
169 candidate->cells.resize(size_t(width) * height);
170 for (auto& cell : candidate->cells) cell.depth = depth;
171 impl_.swap(candidate);
173}
174
176 if (!impl_ || x < 0 || z < 0 || x >= impl_->width || z >= impl_->height)
178 DiagnosticCode::InvalidArgument, "terrain.water.sample: coordinate outside initialized field"));
179 return Result<TerrainWaterSample>::success(impl_->cells[size_t(z) * impl_->width + x]);
180}
181
183 using namespace raster_detail;
184 if (!impl_ || !validRaster(target) || target.getWidth() != impl_->width || target.getHeight() != impl_->height)
185 return invalid("terrain.water.exportChannel: matching finite target and initialized field required");
186 float TerrainWaterSample::*member = nullptr;
187 switch (channel) {
195 default: return invalid("terrain.water.exportChannel: unknown channel");
196 }
197 std::vector<float> output(impl_->cells.size());
198 for (size_t i = 0; i < output.size(); ++i) output[i] = impl_->cells[i].*member;
199 return publish(target, std::move(output));
200}
201
203 using namespace raster_detail;
204 auto positive = [](float v) { return std::isfinite(v) && v > 0; };
205 auto nonnegative = [](float v) { return std::isfinite(v) && v >= 0; };
206 if (!impl_ || !validRaster(terrain) || terrain.getWidth() != impl_->width || terrain.getHeight() != impl_->height ||
207 !positive(s.spacingX) || !positive(s.spacingZ) || !positive(s.heightScale) || !positive(s.waterScale) ||
208 !nonnegative(s.dt) || !nonnegative(s.precipitation) || !nonnegative(s.evaporation) ||
209 !std::isfinite(s.flowAcceleration))
210 return invalid("terrain.water.advance: matching finite terrain and finite simulation controls required");
211 if (s.dt == 0) return Result<int>::success(0);
212 const int width = impl_->width, height = impl_->height;
213 const double area = double(s.spacingX) * s.spacingZ;
214 const double capacitance = double(s.dt) * s.flowAcceleration / area;
215 auto next = impl_->cells;
216 auto neighbors = [width, height](int x, int z) {
217 return std::array<size_t, 4>{
218 size_t(z) * width + std::min(x + 1, width - 1), size_t(z) * width + std::max(x - 1, 0),
219 size_t(std::min(z + 1, height - 1)) * width + x, size_t(std::max(z - 1, 0)) * width + x};
220 };
221 // Complete the entire flux field before any neighbor reads from it.
222 for (int z = 0; z < height; ++z)
223 for (int x = 0; x < width; ++x) {
224 const size_t index = size_t(z) * width + x;
225 const auto n = neighbors(x, z);
226 const auto& old = impl_->cells[index];
227 const double surface = double(terrain.data()[index]) + old.depth;
228 const std::array<float, 4> prior = {old.fluxRight, old.fluxLeft, old.fluxBottom, old.fluxTop};
229 std::array<float, 4> flux;
230 for (int direction = 0; direction < 4; ++direction) {
231 const double delta =
232 double(s.heightScale) *
233 (surface - (double(terrain.data()[n[direction]]) + impl_->cells[n[direction]].depth));
234 const double value = std::max(0.0, prior[direction] + delta * capacitance);
235 if (!isRepresentable(value)) return invalid("terrain.water.advance: flux exceeds finite float range");
236 flux[direction] = float(value);
237 }
238 next[index].fluxRight = flux[0];
239 next[index].fluxLeft = flux[1];
240 next[index].fluxBottom = flux[2];
241 next[index].fluxTop = flux[3];
242 }
243 int changed = 0;
244 for (int z = 0; z < height; ++z)
245 for (int x = 0; x < width; ++x) {
246 const size_t index = size_t(z) * width + x;
247 const auto n = neighbors(x, z);
248 const auto& right = next[n[0]];
249 const auto& left = next[n[1]];
250 const auto& bottom = next[n[2]];
251 const auto& top = next[n[3]];
252 const auto& old = impl_->cells[index];
253 auto& cell = next[index];
254 const double inFlow = double(left.fluxRight) + right.fluxLeft + bottom.fluxTop + top.fluxBottom;
255 const double outFlow = double(cell.fluxLeft) + cell.fluxRight + cell.fluxTop + cell.fluxBottom;
256 const double waterOld = double(old.depth) / s.waterScale;
257 const double transported = waterOld + double(s.dt) * (inFlow - outFlow) / area;
258 const double average = 0.5 * (waterOld + transported);
259 const double vx = average == 0
260 ? 0
261 : 0.5 * (double(left.fluxLeft) - cell.fluxRight - right.fluxRight + cell.fluxLeft) /
262 s.spacingX / average;
263 const double vz = average == 0
264 ? 0
265 : 0.5 * (double(top.fluxBottom) - cell.fluxTop - bottom.fluxTop + cell.fluxBottom) /
266 s.spacingZ / average;
267 // Retain the final assignment in SimulateWaterFlow, which overwrites transported water.
268 const double depth = double(s.waterScale) *
269 std::max(waterOld + double(s.dt) * (double(s.precipitation) - s.evaporation), 0.0);
271 return invalid("terrain.water.advance: velocity or depth exceeds finite float range");
272 cell.velocityX = float(vx);
273 cell.velocityZ = float(vz);
274 cell.depth = float(depth);
275 changed += cell.depth != old.depth || cell.velocityX != old.velocityX || cell.velocityZ != old.velocityZ ||
276 cell.fluxRight != old.fluxRight || cell.fluxLeft != old.fluxLeft ||
277 cell.fluxBottom != old.fluxBottom || cell.fluxTop != old.fluxTop;
278 }
279 impl_->cells.swap(next);
280 return Result<int>::success(changed);
281}
282
285 const TerrainSedimentSettings& reaction,
287 using namespace raster_detail;
288 if (!impl_ || &heights == &sediment || !validRaster(heights) || !validRaster(sediment) ||
289 heights.getWidth() != impl_->width || heights.getHeight() != impl_->height ||
290 sediment.getWidth() != impl_->width || sediment.getHeight() != impl_->height || iterations < 0 ||
291 std::any_of(heights.data().begin(), heights.data().end(), [](float h) { return h < 0; }))
292 return invalid(
293 "terrain.hydraulic: initialized field, distinct matching rasters, nonnegative heights/iterations required");
294 if (iterations == 0) return Result<int>::success(0);
295 TerrainWaterField candidate;
296 candidate.impl_ = std::make_unique<Impl>(*impl_);
297 Heightmap nextHeight = heights, nextSediment = sediment;
298 Heightmap vx(impl_->width, impl_->height), vz(impl_->width, impl_->height);
299 for (int iteration = 0; iteration < iterations; ++iteration) {
300 auto flowed = candidate.advance(nextHeight, water);
301 if (!flowed.ok()) return flowed;
302 for (size_t i = 0; i < candidate.impl_->cells.size(); ++i) {
303 vx.data()[i] = candidate.impl_->cells[i].velocityX;
304 vz.data()[i] = candidate.impl_->cells[i].velocityZ;
305 }
306 auto reacted = applyTerrainSediment(nextHeight, nextSediment, vx, vz, water, reaction);
307 if (!reacted.ok()) return reacted;
308 auto settled = applyTerrainThermal(nextHeight, nextSediment, thermal);
309 if (!settled.ok()) return settled;
310 }
311 int changed = 0;
312 for (size_t i = 0; i < impl_->cells.size(); ++i) {
313 const auto& before = impl_->cells[i];
314 const auto& after = candidate.impl_->cells[i];
315 changed += heights.data()[i] != nextHeight.data()[i] || sediment.data()[i] != nextSediment.data()[i] ||
316 before.depth != after.depth || before.velocityX != after.velocityX ||
317 before.velocityZ != after.velocityZ || before.fluxRight != after.fluxRight ||
318 before.fluxLeft != after.fluxLeft || before.fluxBottom != after.fluxBottom ||
319 before.fluxTop != after.fluxTop;
320 }
321 heights.data().swap(nextHeight.data());
322 sediment.data().swap(nextSediment.data());
323 impl_.swap(candidate.impl_);
324 return Result<int>::success(changed);
325}
326} // namespace eve::procgen
LogicalId target
double value
SQInteger top
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
std::string terrain
double volume
const std::string & s
float nx
float nz
float maximum[3]
float minimum[3]
float area
Definition Grass.cpp:62
glm::vec3 n
Definition Grass.cpp:63
float v
HexVec3 left
HexVec3 right
int h
std::uint32_t height
std::uint32_t width
Range range
std::array< float, 3 > scale
RoadLaneDirection direction
Cell cell
TerrainWaterField water
TerrainThermalSettings settings
TerrainThermalSettings thermal
Heightmap sediment
int iterations
Definition TreeMesh.cpp:311
float(ui::Theme::* member)[4]
uint32_t index
const UnitySourceAsset & source
std::uint32_t depth
float bottom
float vz
float vy
float vx
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
int getWidth() const
Returns the width.
Definition Heightmap.cpp:16
Owned water/flux/velocity state for deterministic execution of the Pcg water stage....
Result< int > advanceHydraulic(Heightmap &heights, Heightmap &sediment, const TerrainWaterSettings &water, const TerrainSedimentSettings &reaction, const TerrainThermalSettings &thermal, int iterations)
Advance water, source sediment and thermal stages as one transaction.
~TerrainWaterField()
Destroy all owned simulation buffers; no external registrations are retained.
int getHeight() const noexcept
Return Z sample count, or zero for an empty/moved-from field.
Result< TerrainWaterSample > sample(int x, int z) const
Read an owned value snapshot; invalid coordinates or empty state return InvalidArgument.
Result< int > exportChannel(Heightmap &target, TerrainWaterChannel channel) const
Copy one stored channel into an existing matching raster for mask processing.
TerrainWaterField()
Construct an empty field; reset must succeed before advance or sample.
Result< int > reset(int width, int height, float depth=0)
Reset dimensions, uniform depth and zero flux/velocity atomically.
Result< int > advance(const Heightmap &terrain, const TerrainWaterSettings &settings)
Advance one injected time step, with clamped neighbor indices.
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 > generateTerrainVelocityFlowMap(Heightmap &target, const Heightmap &source, int iterations)
Generate Pcg HeightMap.FlowMap's normalized four-direction velocity magnitude.
TerrainWaterChannel
Stored scalar channels; Pcg erosion masks read FluxRight or VelocityX, not vector magnitude.
EVENGINE_API_DOMAINS Result< int > applyTerrainSediment(Heightmap &heights, Heightmap &sediment, const Heightmap &velocityX, const Heightmap &velocityZ, const TerrainWaterSettings &water, const TerrainSedimentSettings &settings)
Apply the source sediment reaction and backtrace equations with atomic dual output.
Result< int > generateTerrainWaterFlowMap(Heightmap &target, const Heightmap &source, const TerrainWaterFlowMapSettings &settings)
Generate Pcg's legacy transposed water-flow accumulation map.
Result< int > applyTerrainThermal(Heightmap &heights, Heightmap &sediment, const TerrainThermalSettings &settings)
Apply the source thermal equation, publishing height and sediment together.
Effective sediment coefficients from Hydraulic.compute, after UI conversion. The source hardcodes har...
Explicit simulation controls for the Pcg eight-neighbor thermal kernel. Height differences are multip...
std::vector< TerrainWaterSample > cells
Controls Pcg's legacy offline WaterFlowMap droplet tracer.
Value snapshot of one water cell; direction order follows Hydraulic.compute (Z+ is bottom).
Explicit constants for the source hydraulic water-flow equations, after UI unit conversion.