载入中...
搜索中...
未找到
TerrainWorldWorkspace.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <limits>
6#include <type_traits>
7#include <unordered_set>
8#include <utility>
9#include <vector>
10
11#include "procgen/PointSet.h"
22
23namespace eve::procgen {
24namespace {
25bool powerOfTwo(int value) { return value > 0 && (value & (value - 1)) == 0; }
26bool finitePositive(double value) { return std::isfinite(value) && value > 0; }
27} // namespace
28
38 struct Snapshot {
39 std::vector<Heightmap> heights;
40 std::vector<TerrainSplatmap> splats;
41 std::vector<TerrainDetailLayer> details;
42 std::vector<PointSet> trees, objects, probes;
44 };
46 std::vector<Tile> tiles;
48 std::vector<Snapshot> history;
49 int cursor = 0;
51 std::unique_ptr<Impl> runCandidate;
53 std::size_t runNext = 0;
55
56 Impl() = default;
57 Impl(const Impl& other) : settings(other.settings), last(other.last), history(other.history), cursor(other.cursor) {
58 tiles.reserve(other.tiles.size());
59 for (const auto& source : other.tiles) {
60 Tile tile;
61 tile.name = source.name; tile.heights = source.heights; tile.splat = source.splat;
62 tile.detail = source.detail;
63 tile.trees = source.trees; tile.objects = source.objects; tile.probes = source.probes;
64 tile.originX = source.originX; tile.originZ = source.originZ;
65 tiles.push_back(std::move(tile));
66 }
67 }
69 Snapshot result;
70 for (const auto& tile : tiles) {
71 result.heights.push_back(tile.heights); result.splats.push_back(tile.splat);
72 result.details.push_back(tile.detail);
73 result.trees.push_back(tile.trees); result.objects.push_back(tile.objects);
74 result.probes.push_back(tile.probes);
75 }
76 result.report = last;
77 return result;
78 }
79 void restore(const Snapshot& value) {
80 for (std::size_t i = 0; i < tiles.size(); ++i) {
81 tiles[i].heights = value.heights[i]; tiles[i].splat = value.splats[i];
82 tiles[i].detail = value.details[i];
83 tiles[i].trees = value.trees[i]; tiles[i].objects = value.objects[i];
84 tiles[i].probes = value.probes[i];
85 }
86 last = value.report;
87 }
88 void record() {
89 history.resize(static_cast<std::size_t>(cursor + 1));
90 history.push_back(snapshot());
91 ++cursor;
92 }
93};
94
99
101 if (!impl_ || impl_->runStatus == TerrainWorldRunStatus::Pending || s.tilesX <= 0 || s.tilesZ <= 0 ||
102 s.tilesX > 4096 / s.tilesZ || !finitePositive(s.tileSize) ||
103 !finitePositive(s.tileHeight) || !std::isfinite(s.centerX) || !std::isfinite(s.centerZ) ||
104 s.heightmapResolution < 2 || !powerOfTwo(s.heightmapResolution - 1) ||
105 !powerOfTwo(s.controlTextureResolution) || !powerOfTwo(s.detailResolution) ||
106 s.treeResolution <= 0 || s.objectResolution <= 0 || s.splatLayers <= 0 ||
107 s.defaultSplatLayer < 0 || s.defaultSplatLayer >= s.splatLayers || s.defaultDetailDensity < 0 ||
108 s.namePrefix.empty())
110 DiagnosticCode::InvalidArgument, "terrain.world.create: valid Pcg tile topology and resolutions required"));
111 auto candidate = std::make_unique<Impl>();
112 candidate->settings = s;
113 candidate->tiles.reserve(static_cast<std::size_t>(s.tilesX * s.tilesZ));
114 const double baseX = s.centerX - s.tileSize * s.tilesX * 0.5;
115 const double baseZ = s.centerZ - s.tileSize * s.tilesZ * 0.5;
116 if (!std::isfinite(baseX) || !std::isfinite(baseZ))
118 "terrain.world.create: world origin is not representable"));
119 for (int x = 0; x < s.tilesX; ++x) for (int z = 0; z < s.tilesZ; ++z) {
120 Impl::Tile tile;
121 tile.name = (s.worldMap ? "World Map" : s.namePrefix) + "_" + std::to_string(x) + "_" +
122 std::to_string(z) + (s.nameSuffix.empty() ? "" : "-" + s.nameSuffix);
123 tile.originX = baseX + s.tileSize * x; tile.originZ = baseZ + s.tileSize * z;
124 if (!std::isfinite(tile.originX) || !std::isfinite(tile.originZ))
126 "terrain.world.create: tile origin is not representable"));
127 tile.heights = Heightmap(s.heightmapResolution, s.heightmapResolution);
128 auto splat = tile.splat.initialize(s.controlTextureResolution, s.controlTextureResolution,
129 s.splatLayers, s.defaultSplatLayer);
130 if (!splat.ok()) return Result<int>::failure(splat.status());
131 auto detail = tile.detail.reset(s.detailResolution, s.detailResolution, s.defaultDetailDensity);
132 if (!detail.ok()) return Result<int>::failure(detail.status());
133 candidate->tiles.push_back(std::move(tile));
134 }
135 candidate->history.push_back(candidate->snapshot());
136 impl_.swap(candidate);
137 return Result<int>::success(static_cast<int>(impl_->tiles.size()));
138}
139
140namespace {
141template <class Apply>
142Result<int> mutateWorld(std::unique_ptr<TerrainWorldWorkspace::Impl>& owner, Apply&& apply) {
143 if (!owner || owner->tiles.empty() || owner->runStatus == TerrainWorldRunStatus::Pending)
145 DiagnosticCode::InvalidArgument, "terrain.world: created workspace without pending spawn required"));
146 auto candidate = std::make_unique<TerrainWorldWorkspace::Impl>(*owner);
147 auto result = apply(*candidate);
148 if (!result.ok()) return Result<int>::failure(result.status());
149 candidate->last = std::move(result.value());
150 const int changed = candidate->last.changedSamples;
151 candidate->record();
152 owner.swap(candidate);
153 return Result<int>::success(changed);
154}
155
156Result<TerrainMultiTileReport> applySpawnRule(TerrainWorldWorkspace::Impl& world,
157 const TerrainSpawnRule& variant) {
158 return std::visit(
159 [&](const auto& rule) -> Result<TerrainMultiTileReport> {
160 using Rule = std::decay_t<decltype(rule)>;
161 if (!rule.enabled) return Result<TerrainMultiTileReport>::success({});
162 if constexpr (std::is_same_v<Rule, TerrainSplatSpawnRule>) {
163 std::vector<TerrainSplatTile> tiles;
164 for (auto& tile : world.tiles)
165 tiles.push_back({tile.name, &tile.splat, tile.originX, tile.originZ,
166 world.settings.tileSize, world.settings.tileSize, world.settings.worldMap});
167 return paintTerrainSplatLayerMultiTile(tiles, rule.paint, rule.targetLayer, rule.operation,
168 world.settings.worldMap);
169 } else if constexpr (std::is_same_v<Rule, TerrainDetailSpawnRule>) {
170 auto settings = rule.settings;
171 settings.mode = rule.mode;
172 std::vector<TerrainDetailTile> tiles;
173 for (auto& tile : world.tiles)
174 tiles.push_back({tile.name, &tile.detail, tile.originX, tile.originZ,
175 world.settings.tileSize, world.settings.tileSize, world.settings.worldMap});
176 return applyTerrainDetailMultiTile(tiles, rule.fitness, settings, rule.operation, rule.seed,
177 world.settings.worldMap);
178 } else if constexpr (std::is_same_v<Rule, TerrainTreeSpawnRule>) {
179 std::vector<TerrainTreeTile> tiles;
180 for (auto& tile : world.tiles)
181 tiles.push_back({tile.name, &tile.trees, &tile.heights, tile.originX, tile.originZ,
182 world.settings.tileSize, world.settings.tileSize,
183 world.settings.treeResolution, world.settings.treeResolution,
184 world.settings.worldMap});
185 return applyTerrainTreesMultiTile(tiles, rule.fitness, rule.settings, rule.operation, rule.mode,
186 world.settings.worldMap);
187 } else if constexpr (std::is_same_v<Rule, TerrainObjectSpawnRule>) {
188 std::vector<TerrainObjectTile> tiles;
189 for (auto& tile : world.tiles)
190 tiles.push_back({tile.name, &tile.objects, &tile.heights, tile.originX, tile.originZ,
191 world.settings.tileSize, world.settings.tileSize,
192 world.settings.objectResolution, world.settings.objectResolution,
193 world.settings.worldMap});
194 return applyTerrainObjectsMultiTile(tiles, rule.fitness, rule.settings, rule.operation, rule.mode,
195 world.settings.worldMap);
196 } else if constexpr (std::is_same_v<Rule, TerrainProbeSpawnRule>) {
197 std::vector<TerrainProbeTile> tiles;
198 for (auto& tile : world.tiles)
199 tiles.push_back({tile.name, &tile.probes, &tile.heights, tile.originX, tile.originZ,
200 world.settings.tileSize, world.settings.tileSize,
201 world.settings.objectResolution, world.settings.objectResolution,
202 world.settings.worldMap});
203 return applyTerrainProbesMultiTile(tiles, rule.fitness, rule.settings, rule.operation, rule.mode,
204 world.settings.worldMap);
205 } else {
206 std::vector<TerrainHeightTile> tiles;
207 for (auto& tile : world.tiles)
208 tiles.push_back({tile.name, &tile.heights, tile.originX, tile.originZ,
209 world.settings.tileSize, world.settings.tileSize, world.settings.worldMap});
210 return applyTerrainStampMultiTile(tiles, rule.stamp, rule.operation, rule.localMask,
211 rule.globalMask, world.settings.worldMap);
212 }
213 },
214 variant);
215}
216} // namespace
217
219 const Heightmap& local, const Heightmap& global) {
220 return mutateWorld(impl_, [&](Impl& world) {
221 std::vector<TerrainHeightTile> tiles;
222 for (auto& tile : world.tiles) tiles.push_back({tile.name, &tile.heights, tile.originX, tile.originZ,
223 world.settings.tileSize, world.settings.tileSize,
224 world.settings.worldMap});
225 return applyTerrainStampMultiTile(tiles, stampMap, settings, local, global, world.settings.worldMap);
226 });
227}
228Result<int> TerrainWorldWorkspace::paintSplat(const Heightmap& paint, int layer,
230 return mutateWorld(impl_, [&](Impl& world) {
231 std::vector<TerrainSplatTile> tiles;
232 for (auto& tile : world.tiles) tiles.push_back({tile.name, &tile.splat, tile.originX, tile.originZ,
233 world.settings.tileSize, world.settings.tileSize,
234 world.settings.worldMap});
235 return paintTerrainSplatLayerMultiTile(tiles, paint, layer, operation, world.settings.worldMap);
236 });
237}
238Result<int> TerrainWorldWorkspace::applyDetail(const Heightmap& fitness, const TerrainDetailSettings& settings,
240 std::int32_t seed) {
241 auto configured = settings; configured.mode = mode;
242 return mutateWorld(impl_, [&](Impl& world) {
243 std::vector<TerrainDetailTile> tiles;
244 for (auto& tile : world.tiles) tiles.push_back({tile.name, &tile.detail, tile.originX, tile.originZ,
245 world.settings.tileSize, world.settings.tileSize,
246 world.settings.worldMap});
247 return applyTerrainDetailMultiTile(tiles, fitness, configured, operation, seed, world.settings.worldMap);
248 });
249}
250Result<int> TerrainWorldWorkspace::applyTrees(const Heightmap& fitness,
253 return mutateWorld(impl_, [&](Impl& world) {
254 std::vector<TerrainTreeTile> tiles;
255 for (auto& tile : world.tiles) tiles.push_back({tile.name, &tile.trees, &tile.heights, tile.originX,
256 tile.originZ, world.settings.tileSize, world.settings.tileSize,
257 world.settings.treeResolution, world.settings.treeResolution,
258 world.settings.worldMap});
259 return applyTerrainTreesMultiTile(tiles, fitness, settings, operation, mode, world.settings.worldMap);
260 });
261}
262Result<int> TerrainWorldWorkspace::applyObjects(const Heightmap& fitness,
266 return mutateWorld(impl_, [&](Impl& world) {
267 std::vector<TerrainObjectTile> tiles;
268 for (auto& tile : world.tiles) tiles.push_back({tile.name, &tile.objects, &tile.heights, tile.originX,
269 tile.originZ, world.settings.tileSize, world.settings.tileSize,
270 world.settings.objectResolution, world.settings.objectResolution,
271 world.settings.worldMap});
272 return applyTerrainObjectsMultiTile(tiles, fitness, settings, operation, mode, world.settings.worldMap);
273 });
274}
275
276Result<int> TerrainWorldWorkspace::applyProbes(const Heightmap& fitness,
280 return mutateWorld(impl_, [&](Impl& world) {
281 std::vector<TerrainProbeTile> tiles;
282 for (auto& tile : world.tiles)
283 tiles.push_back({tile.name, &tile.probes, &tile.heights, tile.originX, tile.originZ,
284 world.settings.tileSize, world.settings.tileSize,
285 world.settings.objectResolution, world.settings.objectResolution,
286 world.settings.worldMap});
287 return applyTerrainProbesMultiTile(tiles, fitness, settings, operation, mode, world.settings.worldMap);
288 });
289}
290
291Result<int> TerrainWorldWorkspace::spawn(const TerrainSpawnPlan& plan) {
292 if (plan.rules().empty())
294 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.world.spawn: nonempty spawn plan required"));
295 bool hasEnabled = false;
296 for (const auto& rule : plan.rules())
297 std::visit([&](const auto& value) { hasEnabled = hasEnabled || value.enabled; }, rule);
298 if (!hasEnabled)
300 "terrain.world.spawn: at least one enabled rule required"));
301 return mutateWorld(impl_, [&](Impl& world) -> Result<TerrainMultiTileReport> {
302 TerrainMultiTileReport combined;
303 std::unordered_set<std::string> affectedNames;
304 for (const auto& variant : plan.rules()) {
305 auto applied = applySpawnRule(world, variant);
306 if (!applied.ok()) return Result<TerrainMultiTileReport>::failure(applied.status());
307 auto report = std::move(applied.value());
308 if (report.changedSamples > std::numeric_limits<int>::max() - combined.changedSamples)
310 DiagnosticCode::InvalidArgument, "terrain.world.spawn: changed count exceeds integer range"));
311 combined.changedSamples += report.changedSamples;
312 for (const auto& mapping : report.mappings) affectedNames.insert(mapping.terrainName);
313 combined.mappings.insert(combined.mappings.end(), report.mappings.begin(), report.mappings.end());
314 }
315 combined.affectedTiles = static_cast<int>(affectedNames.size());
316 return Result<TerrainMultiTileReport>::success(std::move(combined));
317 });
318}
319
320Result<int> TerrainWorldWorkspace::spawnBiome(const TerrainBiomePreset& preset) {
321 auto plan = preset.compileForBiome();
322 if (!plan.ok()) return Result<int>::failure(plan.status());
323 return spawn(plan.value());
324}
325
326Result<int> TerrainWorldWorkspace::spawnStamper(const TerrainBiomePreset& preset) {
327 auto plan = preset.compileForStamper();
328 if (!plan.ok()) return Result<int>::failure(plan.status());
329 return spawn(plan.value());
330}
331
332Result<TerrainWorldRunStatus> TerrainWorldWorkspace::beginSpawn(const TerrainSpawnPlan& plan) {
333 if (!impl_ || impl_->tiles.empty() || impl_->runStatus == TerrainWorldRunStatus::Pending || plan.rules().empty())
336 "terrain.world.beginSpawn: created workspace, nonempty plan and no pending spawn required"));
337 bool hasEnabled = false;
338 for (const auto& rule : plan.rules())
339 std::visit([&](const auto& value) { hasEnabled = hasEnabled || value.enabled; }, rule);
340 if (!hasEnabled)
342 DiagnosticCode::InvalidArgument, "terrain.world.beginSpawn: at least one enabled rule required"));
343 impl_->runCandidate = std::make_unique<Impl>(*impl_);
344 impl_->runPlan = plan;
345 impl_->runNext = 0;
346 impl_->runReport = {};
347 impl_->runStatus = TerrainWorldRunStatus::Pending;
348 return Result<TerrainWorldRunStatus>::success(impl_->runStatus);
349}
350
351Result<TerrainWorldRunStatus> TerrainWorldWorkspace::beginSpawnBiome(const TerrainBiomePreset& preset) {
352 auto plan = preset.compileForBiome();
353 if (!plan.ok()) return Result<TerrainWorldRunStatus>::failure(plan.status());
354 return beginSpawn(plan.value());
355}
356
357Result<TerrainWorldRunStatus> TerrainWorldWorkspace::beginSpawnStamper(const TerrainBiomePreset& preset) {
358 auto plan = preset.compileForStamper();
359 if (!plan.ok()) return Result<TerrainWorldRunStatus>::failure(plan.status());
360 return beginSpawn(plan.value());
361}
362
363Result<TerrainWorldRunStatus> TerrainWorldWorkspace::stepSpawn(int maxRules) {
364 if (!impl_ || impl_->runStatus != TerrainWorldRunStatus::Pending || !impl_->runCandidate || maxRules <= 0)
367 "terrain.world.stepSpawn: pending spawn and positive rule budget required"));
368 int visited = 0;
369 while (impl_->runNext < impl_->runPlan.rules().size() && visited < maxRules) {
370 auto applied = applySpawnRule(*impl_->runCandidate, impl_->runPlan.rules()[impl_->runNext]);
371 if (!applied.ok()) {
372 impl_->runCandidate.reset();
373 impl_->runStatus = TerrainWorldRunStatus::Failed;
374 return Result<TerrainWorldRunStatus>::failure(applied.status());
375 }
376 const auto& report = applied.value();
377 if (report.changedSamples > std::numeric_limits<int>::max() - impl_->runReport.changedSamples) {
378 impl_->runCandidate.reset();
379 impl_->runStatus = TerrainWorldRunStatus::Failed;
381 DiagnosticCode::InvalidArgument, "terrain.world.stepSpawn: changed count exceeds integer range"));
382 }
383 impl_->runReport.changedSamples += report.changedSamples;
384 impl_->runReport.mappings.insert(impl_->runReport.mappings.end(), report.mappings.begin(),
385 report.mappings.end());
386 ++impl_->runNext;
387 ++visited;
388 }
389 if (impl_->runNext == impl_->runPlan.rules().size()) {
390 std::unordered_set<std::string> affected;
391 for (const auto& mapping : impl_->runReport.mappings) affected.insert(mapping.terrainName);
392 impl_->runReport.affectedTiles = static_cast<int>(affected.size());
393 auto completed = std::move(impl_->runCandidate);
394 completed->last = std::move(impl_->runReport);
395 completed->record();
396 completed->runStatus = TerrainWorldRunStatus::Completed;
397 completed->runNext = impl_->runNext;
398 impl_.swap(completed);
399 }
400 return Result<TerrainWorldRunStatus>::success(impl_->runStatus);
401}
402
403Result<TerrainWorldRunStatus> TerrainWorldWorkspace::cancelSpawn() {
404 if (!impl_ || impl_->runStatus != TerrainWorldRunStatus::Pending)
406 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.world.cancelSpawn: no pending spawn"));
407 impl_->runCandidate.reset();
408 impl_->runPlan = {};
409 impl_->runStatus = TerrainWorldRunStatus::Cancelled;
410 return Result<TerrainWorldRunStatus>::success(impl_->runStatus);
411}
412
413TerrainWorldRunStatus TerrainWorldWorkspace::getSpawnStatus() const noexcept {
414 return impl_ ? impl_->runStatus : TerrainWorldRunStatus::Idle;
415}
416
417int TerrainWorldWorkspace::getSpawnCompletedRules() const noexcept {
418 return impl_ ? static_cast<int>(impl_->runNext) : 0;
419}
420
421Result<int> TerrainWorldWorkspace::flatten() {
422 return mutateWorld(impl_, [](Impl& world) {
424 for (auto& tile : world.tiles) {
425 int tileChanges = 0;
426 for (float& height : tile.heights.data()) {
427 if (height != 0) {
428 height = 0;
429 ++tileChanges;
430 }
431 }
432 report.changedSamples += tileChanges;
433 report.affectedTiles += tileChanges > 0;
434 }
436 });
437}
438
439Result<int> TerrainWorldWorkspace::setHeightWorldUnits(float heightWorldUnits, float worldHeightSpan) {
440 if (!std::isfinite(heightWorldUnits) || !std::isfinite(worldHeightSpan) || worldHeightSpan <= 0)
443 "terrain.world.setHeightWorldUnits: finite elevation and positive span required"));
444 const float normalized = std::clamp(heightWorldUnits / worldHeightSpan, 0.f, 1.f);
445 return mutateWorld(impl_, [normalized](Impl& world) {
447 for (auto& tile : world.tiles) {
448 int tileChanges = 0;
449 for (float& height : tile.heights.data()) {
450 if (height != normalized) {
451 height = normalized;
452 ++tileChanges;
453 }
454 }
455 report.changedSamples += tileChanges;
456 report.affectedTiles += tileChanges > 0;
457 }
459 });
460}
461
462Result<int> TerrainWorldWorkspace::clearSpawns(const TerrainWorldClearSettings& settings) {
463 if (!settings.details && !settings.trees && !settings.objects && !settings.probes)
465 DiagnosticCode::InvalidArgument, "terrain.world.clearSpawns: at least one domain must be selected"));
466 return mutateWorld(impl_, [&](Impl& world) {
468 const std::string source = std::to_string(settings.sourceNamespace);
469 auto clearPoints = [&](PointSet& points) -> Result<int> {
470 if (settings.sourceNamespace == 0) {
471 const int changed = points.getCount();
472 points.clear();
473 return Result<int>::success(changed);
474 }
475 PointSet retained;
476 retained.reserve(static_cast<std::size_t>(points.getCount()));
477 int changed = 0;
478 for (int i = 0; i < points.getCount(); ++i) {
479 if (points.getStringAttribute(i, "spawnNamespace", "") == source) {
480 ++changed;
481 continue;
482 }
483 auto appended = retained.appendPointFrom(points, static_cast<std::size_t>(i));
484 if (!appended.ok()) return Result<int>::failure(appended.status());
485 }
486 points = std::move(retained);
487 return Result<int>::success(changed);
488 };
489 for (auto& tile : world.tiles) {
490 int tileChanges = 0;
491 if (settings.details) {
492 if (settings.sourceNamespace == 0) {
493 for (int z = 0; z < tile.detail.getHeight(); ++z)
494 for (int x = 0; x < tile.detail.getWidth(); ++x)
495 tileChanges += tile.detail.sample(x, z).value() != 0;
496 auto reset = tile.detail.reset(tile.detail.getWidth(), tile.detail.getHeight(), 0);
497 if (!reset.ok()) return Result<TerrainMultiTileReport>::failure(reset.status());
498 } else {
499 auto cleared = tile.detail.clearResource(settings.sourceNamespace);
500 if (!cleared.ok()) return Result<TerrainMultiTileReport>::failure(cleared.status());
501 tileChanges += cleared.value();
502 }
503 }
504 if (settings.trees) {
505 auto cleared = clearPoints(tile.trees);
506 if (!cleared.ok()) return Result<TerrainMultiTileReport>::failure(cleared.status());
507 tileChanges += cleared.value();
508 }
509 if (settings.objects) {
510 auto cleared = clearPoints(tile.objects);
511 if (!cleared.ok()) return Result<TerrainMultiTileReport>::failure(cleared.status());
512 tileChanges += cleared.value();
513 }
514 if (settings.probes) {
515 auto cleared = clearPoints(tile.probes);
516 if (!cleared.ok()) return Result<TerrainMultiTileReport>::failure(cleared.status());
517 tileChanges += cleared.value();
518 }
519 report.changedSamples += tileChanges;
520 report.affectedTiles += tileChanges > 0;
521 }
523 });
524}
525
526namespace {
527template <class T>
528Result<const TerrainWorldWorkspace::Impl::Tile*> worldTile(const std::unique_ptr<TerrainWorldWorkspace::Impl>& impl,
529 int index) {
530 if (!impl || index < 0 || index >= static_cast<int>(impl->tiles.size()))
532 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.world: tile index out of range"));
533 return Result<const TerrainWorldWorkspace::Impl::Tile*>::success(&impl->tiles[static_cast<std::size_t>(index)]);
534}
535} // namespace
536Result<int> TerrainWorldWorkspace::copyHeightmap(int index, Heightmap& output) const { auto tile = worldTile<int>(impl_, index); if (!tile.ok()) return Result<int>::failure(tile.status()); output = tile.value()->heights; return Result<int>::success(int(output.data().size())); }
537Result<int> TerrainWorldWorkspace::copySplatmap(int index, TerrainSplatmap& output) const { auto tile = worldTile<int>(impl_, index); if (!tile.ok()) return Result<int>::failure(tile.status()); output = tile.value()->splat; return Result<int>::success(output.getWidth() * output.getHeight()); }
538Result<int> TerrainWorldWorkspace::copyDetail(int index, TerrainDetailLayer& output) const { auto tile = worldTile<int>(impl_, index); if (!tile.ok()) return Result<int>::failure(tile.status()); output = tile.value()->detail; return Result<int>::success(output.getWidth() * output.getHeight()); }
539Result<int> TerrainWorldWorkspace::copyTrees(int index, PointSet& output) const { auto tile = worldTile<int>(impl_, index); if (!tile.ok()) return Result<int>::failure(tile.status()); output = tile.value()->trees; return Result<int>::success(output.getCount()); }
540Result<int> TerrainWorldWorkspace::copyObjects(int index, PointSet& output) const { auto tile = worldTile<int>(impl_, index); if (!tile.ok()) return Result<int>::failure(tile.status()); output = tile.value()->objects; return Result<int>::success(output.getCount()); }
541Result<int> TerrainWorldWorkspace::copyProbes(int index, PointSet& output) const { auto tile = worldTile<int>(impl_, index); if (!tile.ok()) return Result<int>::failure(tile.status()); output = tile.value()->probes; return Result<int>::success(output.getCount()); }
542Result<std::string> TerrainWorldWorkspace::getTileName(int index) const { auto tile = worldTile<int>(impl_, index); if (!tile.ok()) return Result<std::string>::failure(tile.status()); return Result<std::string>::success(tile.value()->name); }
543Result<double> TerrainWorldWorkspace::getTileOriginX(int index) const { auto tile = worldTile<int>(impl_, index); if (!tile.ok()) return Result<double>::failure(tile.status()); return Result<double>::success(tile.value()->originX); }
544Result<double> TerrainWorldWorkspace::getTileOriginZ(int index) const { auto tile = worldTile<int>(impl_, index); if (!tile.ok()) return Result<double>::failure(tile.status()); return Result<double>::success(tile.value()->originZ); }
545Result<int> TerrainWorldWorkspace::undo() {
546 if (!impl_ || impl_->runStatus == TerrainWorldRunStatus::Pending || impl_->cursor <= 0)
548 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.world: no undo snapshot"));
549 auto candidate = std::make_unique<Impl>(*impl_);
550 --candidate->cursor;
551 candidate->restore(candidate->history[static_cast<std::size_t>(candidate->cursor)]);
552 const int cursor = candidate->cursor;
553 impl_.swap(candidate);
555}
556
557Result<int> TerrainWorldWorkspace::redo() {
558 if (!impl_ || impl_->runStatus == TerrainWorldRunStatus::Pending ||
559 impl_->cursor + 1 >= static_cast<int>(impl_->history.size()))
561 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.world: no redo snapshot"));
562 auto candidate = std::make_unique<Impl>(*impl_);
563 ++candidate->cursor;
564 candidate->restore(candidate->history[static_cast<std::size_t>(candidate->cursor)]);
565 const int cursor = candidate->cursor;
566 impl_.swap(candidate);
568}
569int TerrainWorldWorkspace::getTileCount() const noexcept { return impl_ ? static_cast<int>(impl_->tiles.size()) : 0; }
570int TerrainWorldWorkspace::getLastChangedSamples() const noexcept { return impl_ ? impl_->last.changedSamples : 0; }
571int TerrainWorldWorkspace::getLastAffectedTiles() const noexcept { return impl_ ? impl_->last.affectedTiles : 0; }
572int TerrainWorldWorkspace::getOperationCount() const noexcept { return impl_ && !impl_->history.empty() ? static_cast<int>(impl_->history.size()) - 1 : 0; }
573int TerrainWorldWorkspace::getAppliedCount() const noexcept { return impl_ ? impl_->cursor : 0; }
574} // namespace eve::procgen
ActionParameterOperation operation
double value
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
const std::string & s
std::string variant
void * impl
std::uint32_t height
std::string local
TileLayer * layer
World3D * world
std::uint32_t seed
Definition PointSet.cpp:807
std::shared_ptr< const std::vector< glm::vec2 > > points
int detail
std::size_t cursor
TerrainThermalSettings settings
Heightmap global
uint32_t index
const UnitySourceAsset & source
std::vector< WfcTile > tiles
Definition WfcSimple.cpp:22
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
const T & value() const &
Borrow the value from a const lvalue after checking success.
Definition Result.h:308
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
Script-friendly collection of attributed 3D samples.
Definition PointSet.h:59
Result< int > appendPointFrom(const PointSet &source, std::size_t sourceIndex)
Append a point and its attributes from another set.
Definition PointSet.cpp:69
void reserve(std::size_t count)
Reserve point storage without changing point or attribute row counts.
Definition PointSet.cpp:60
Own an ordered Pcg-style biome preset made from independent spawner plans. Entries and plans are copi...
Result< TerrainSpawnPlan > compileForStamper() const
Compile active stamper-linked entries into one owned transactional plan.
Result< TerrainSpawnPlan > compileForBiome() const
Compile active biome-controller entries into one owned transactional plan.
Owned integer terrain-detail layer, independent of graphics and resource registries....
Result< int > reset(int width, int height, int count=0)
Reset positive dimensions and uniform nonnegative count atomically.
Own an ordered mixed-domain Pcg-style spawn recipe. Rule IDs are stable and unique....
const std::vector< TerrainSpawnRule > & rules() const noexcept
Return immutable ordered rules for transactional execution.
Owning normalized terrain texture-layer weights with atomic mutation. Layers share one finite width/h...
Result< int > initialize(int width, int height, int layerCount, int defaultLayer=0)
Initialize topology and assign all weight to defaultLayer.
Own a complete Pcg-style terrain grid and its height, splat, detail, tree, object and probe state....
TerrainWorldWorkspace()
Terrain world workspace.
~TerrainWorldWorkspace()
Terrain world workspace.
Result< int > stamp(const Heightmap &stamp, const TerrainStampSettings &settings, const Heightmap &localMask, const Heightmap &globalMask)
Apply one height stamp and record a complete-world snapshot.
Result< TerrainMultiTileReport > applyTerrainObjectsMultiTile(const std::vector< TerrainObjectTile > &tiles, const Heightmap &fitness, const TerrainObjectPlacementSettings &s, const TerrainStampSettings &operationSettings, TerrainObjectOperationMode mode, bool worldMapOperation, const std::vector< std::string > &validTerrainNames)
Apply one Pcg-style game-object rule over mapped terrain tiles atomically.
Result< TerrainMultiTileReport > applyTerrainStampMultiTile(const std::vector< TerrainHeightTile > &tiles, const Heightmap &stamp, const TerrainStampSettings &settings, const Heightmap &localMask, const Heightmap &globalMask, bool worldMapOperation, const std::vector< std::string > &validTerrainNames)
Apply one world-space stamp to all selected intersecting height tiles atomically.
TerrainProbeOperationMode
Mutation performed by a multi-terrain probe rule.
Result< TerrainMultiTileReport > paintTerrainSplatLayerMultiTile(const std::vector< TerrainSplatTile > &tiles, const Heightmap &operationPaint, int targetLayer, const TerrainStampSettings &operationSettings, bool worldMapOperation, const std::vector< std::string > &validTerrainNames)
Paint one shared Pcg Texture-domain operation raster across selected tiles atomically.
Result< TerrainMultiTileReport > applyTerrainTreesMultiTile(const std::vector< TerrainTreeTile > &tiles, const Heightmap &operationFitness, const TerrainTreePlacementSettings &s, const TerrainStampSettings &operationSettings, TerrainTreeOperationMode mode, bool worldMapOperation, const std::vector< std::string > &validTerrainNames)
Apply one Pcg tree distribution window across selected terrain tiles atomically.
TerrainDetailMode
Native detail-density operation corresponding to Pcg SpawnMode.
TerrainWorldRunStatus
Observable lifecycle of a caller-driven staged world spawn.
Result< TerrainMultiTileReport > applyTerrainProbesMultiTile(const std::vector< TerrainProbeTile > &tiles, const Heightmap &fitness, const TerrainProbePlacementSettings &s, const TerrainStampSettings &operation, TerrainProbeOperationMode mode, bool worldMapOperation, const std::vector< std::string > &validNames)
Apply one Pcg probe rule over mapped terrain tiles as one atomic transaction.
Result< TerrainMultiTileReport > applyTerrainDetailMultiTile(const std::vector< TerrainDetailTile > &tiles, const Heightmap &operationFitness, const TerrainDetailSettings &settings, const TerrainStampSettings &operationSettings, int32_t seed, bool worldMapOperation, const std::vector< std::string > &validTerrainNames)
std::variant< TerrainSplatSpawnRule, TerrainDetailSpawnRule, TerrainTreeSpawnRule, TerrainObjectSpawnRule, TerrainProbeSpawnRule, TerrainModifierStampSpawnRule > TerrainSpawnRule
TerrainObjectOperationMode
Mutation performed by a multi-terrain game-object operation.
TerrainTreeOperationMode
Pcg SetTerrainTrees operation branch. Add and Replace both append after upstream clearing policy.
Effective detail rule settings; density is rule density times global spawn density.
Completed multi-tile stamp statistics and Pcg-compatible affected-pixel mappings.
std::vector< TerrainAffectedPixels > mappings
Deterministic Pcg SetTerrainGameObjects scan, collision and resource configuration.
Deterministic value settings for Pcg SetProbes terrain scanning.
Value configuration for a rectangular stamp in world X/Z coordinates.
Explicit deterministic tree placement configuration for one terrain rectangle and resource.
Select Pcg spawn domains to clear in one atomic world operation.
Validated Pcg-style centered terrain-grid topology.