载入中...
搜索中...
未找到
TerrainGenerationSession.cpp
浏览该文件的文档.
2#include <bit>
3#include <limits>
4#include <optional>
5#include <string_view>
6#include <type_traits>
7#include <variant>
8#include "common/Value.h"
14
15namespace eve::procgen {
16using namespace raster_detail;
17namespace {
18constexpr std::size_t kMaximumSessionSnapshotBytes = 128U * 1024U * 1024U;
19constexpr std::uint32_t kMaximumSessionCommands = 65536;
20struct SessionWriter {
21 std::string bytes;
22 template <class T> void scalar(T value) {
23 if constexpr (std::is_enum_v<T>) scalar(static_cast<std::underlying_type_t<T>>(value));
24 else if constexpr (std::is_same_v<T, bool>) scalar(static_cast<std::uint8_t>(value));
25 else if constexpr (std::is_same_v<T, float>) scalar(std::bit_cast<std::uint32_t>(value));
26 else if constexpr (std::is_same_v<T, double>) scalar(std::bit_cast<std::uint64_t>(value));
27 else {
28 using U = std::make_unsigned_t<T>;
29 U bits = static_cast<U>(value);
30 for (std::size_t i = 0; i < sizeof(T); ++i) bytes.push_back(static_cast<char>((bits >> (i * 8)) & 255U));
31 }
32 }
33};
34struct SessionReader {
35 std::string_view bytes;
36 std::size_t offset = 0;
37 template <class T> bool scalar(T& value) {
38 if constexpr (std::is_enum_v<T>) {
39 std::underlying_type_t<T> raw{}; if (!scalar(raw)) return false; value = static_cast<T>(raw); return true;
40 } else if constexpr (std::is_same_v<T, bool>) {
41 std::uint8_t raw{}; if (!scalar(raw) || raw > 1) return false; value = raw != 0; return true;
42 } else if constexpr (std::is_same_v<T, float>) {
43 std::uint32_t raw{}; if (!scalar(raw)) return false; value = std::bit_cast<float>(raw); return std::isfinite(value);
44 } else if constexpr (std::is_same_v<T, double>) {
45 std::uint64_t raw{}; if (!scalar(raw)) return false; value = std::bit_cast<double>(raw); return std::isfinite(value);
46 } else {
47 if (bytes.size() - offset < sizeof(T)) return false;
48 using U = std::make_unsigned_t<T>; U bits = 0;
49 for (std::size_t i = 0; i < sizeof(T); ++i)
50 bits |= static_cast<U>(static_cast<unsigned char>(bytes[offset++])) << (i * 8);
51 value = static_cast<T>(bits); return true;
52 }
53 }
54};
55void writeSessionHeightmap(SessionWriter& writer, const Heightmap& map) {
56 writer.scalar(map.getWidth()); writer.scalar(map.getHeight());
57 for (float value : map.data()) writer.scalar(value);
58}
59bool readSessionHeightmap(SessionReader& reader, Heightmap& map) {
60 int width{}, height{};
61 if (!reader.scalar(width) || !reader.scalar(height) || width <= 0 || height <= 0 || width > INT_MAX / height)
62 return false;
63 Heightmap candidate(width, height);
64 for (float& value : candidate.data()) if (!reader.scalar(value)) return false;
65 map = std::move(candidate); return true;
66}
67std::string encodeSessionHex(std::string_view bytes) {
68 static constexpr char digits[] = "0123456789abcdef";
69 std::string result; result.reserve(bytes.size() * 2);
70 for (unsigned char value : bytes) { result.push_back(digits[value >> 4]); result.push_back(digits[value & 15]); }
71 return result;
72}
73Result<std::string> decodeSessionHex(std::string_view text) {
74 if ((text.size() & 1U) != 0 || text.size() / 2 > kMaximumSessionSnapshotBytes)
76 "terrain.session.restore: invalid payload size"));
77 auto nibble = [](char c) { return c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1; };
78 std::string result(text.size() / 2, '\0');
79 for (std::size_t i = 0; i < result.size(); ++i) {
80 int high = nibble(text[i * 2]), low = nibble(text[i * 2 + 1]);
81 if (high < 0 || low < 0) return Result<std::string>::failure(Diagnostic::error(
82 DiagnosticCode::InvalidArgument, "terrain.session.restore: invalid payload hex"));
83 result[i] = static_cast<char>((high << 4) | low);
84 }
85 return Result<std::string>::success(std::move(result));
86}
87struct SessionState {
88 Heightmap terrain, sediment;
89 TerrainWaterField water;
90};
91struct SimulationResetRecord {
92 float depth;
93};
94struct ThermalRecord {
95 TerrainThermalSettings settings;
96};
97struct HydraulicRecord {
98 TerrainWaterSettings water;
99 TerrainSedimentSettings sediment;
100 TerrainThermalSettings thermal;
102};
103Result<int> execute(SessionState& state, const SimulationResetRecord& c) {
104 auto result = state.water.reset(state.terrain.getWidth(), state.terrain.getHeight(), c.depth);
105 if (!result.ok()) return result;
106 std::fill(state.sediment.data().begin(), state.sediment.data().end(), 0.0F);
107 return result;
108}
109Result<int> execute(SessionState& state, const ThermalRecord& c) {
110 return applyTerrainThermal(state.terrain, state.sediment, c.settings);
111}
112Result<int> execute(SessionState& state, const HydraulicRecord& c) {
113 return state.water.advanceHydraulic(state.terrain, state.sediment, c.water, c.sediment, c.thermal, c.iterations);
114}
115struct StampRecord {
116 Heightmap stamp, local, global;
117 TerrainStampSettings settings;
118};
119struct ContrastRecord {
120 Heightmap mask;
122};
123struct SmoothRecord {
124 Heightmap mask;
125 TerrainSmoothSettings settings;
126};
127struct RidgeRecord {
128 Heightmap mask;
129 TerrainRidgeSettings settings;
130};
131struct TerraceRecord {
132 Heightmap mask;
133 TerrainTerraceSettings settings;
134};
135struct PowerRecord {
136 Heightmap mask;
137 float power;
138};
139struct CurveRecord {
140 Heightmap mask, curve;
142};
143struct MixRecord {
144 Heightmap local, global;
145 TerrainHeightMixSettings settings;
146};
147Result<int> execute(Heightmap& h, const StampRecord& c) {
148 return applyTerrainStamp(h, c.stamp, c.settings, c.local, c.global);
149}
150Result<int> execute(Heightmap& h, const ContrastRecord& c) {
151 return applyTerrainContrast(h, c.mask, c.strength, c.featureSize);
152}
153Result<int> execute(Heightmap& h, const SmoothRecord& c) { return applyTerrainSmooth(h, c.mask, c.settings); }
154Result<int> execute(Heightmap& h, const RidgeRecord& c) { return applyTerrainRidges(h, c.mask, c.settings); }
155Result<int> execute(Heightmap& h, const TerraceRecord& c) { return applyTerrainTerrace(h, c.mask, c.settings); }
156Result<int> execute(Heightmap& h, const PowerRecord& c) { return applyTerrainPower(h, c.mask, c.power); }
157Result<int> execute(Heightmap& h, const CurveRecord& c) {
158 return applyTerrainHeightCurve(h, c.mask, c.curve, c.minimum, c.maximum);
159}
160Result<int> execute(Heightmap& h, const MixRecord& c) {
161 return applyTerrainHeightMix(h, c.local, c.global, c.settings);
162}
163template <class Record>
164Result<int> execute(SessionState& state, const Record& record) {
165 return execute(state.terrain, record);
166}
167} // namespace
169 std::variant<StampRecord, ContrastRecord, SmoothRecord, RidgeRecord, TerraceRecord, PowerRecord, CurveRecord,
170 MixRecord, SimulationResetRecord, ThermalRecord, HydraulicRecord>
172 bool enabled = true;
173};
174namespace {
175#define SESSION_STAMP_FIELDS(X) X(originX) X(originZ) X(spacingX) X(spacingZ) X(centerX) X(centerZ) X(width) X(depth) X(rotation) X(amplitude) X(baseHeight) X(blendStrength) X(operation)
176#define SESSION_SMOOTH_FIELDS(X) X(radius) X(verticality) X(strength)
177#define SESSION_RIDGE_FIELDS(X) X(mixStrength) X(exponent) X(strength) X(minimum) X(maximum) X(passes)
178#define SESSION_TERRACE_FIELDS(X) X(count) X(bevel) X(strength)
179#define SESSION_MIX_FIELDS(X) X(minimum) X(maximum) X(midpoint) X(strength) X(clipMinimum) X(clipMaximum)
180#define SESSION_WATER_FIELDS(X) X(spacingX) X(spacingZ) X(heightScale) X(waterScale) X(dt) X(precipitation) X(evaporation) X(flowAcceleration)
181#define SESSION_SEDIMENT_FIELDS(X) X(effect) X(depositRate) X(bankDeposit) X(bedDeposit)
182#define SESSION_THERMAL_FIELDS(X) X(spacingX) X(spacingZ) X(heightScale) X(reposeSlope) X(dt) X(iterations)
183#define SESSION_WRITE_FIELD(name) writer.scalar(value.name);
184#define SESSION_READ_FIELD(name) if (!reader.scalar(value.name)) return false;
185template <class T> void writeSessionValue(SessionWriter& writer, const T& value);
186template <class T> bool readSessionValue(SessionReader& reader, T& value);
187template <> void writeSessionValue(SessionWriter& writer, const TerrainStampSettings& value) { SESSION_STAMP_FIELDS(SESSION_WRITE_FIELD) }
188template <> bool readSessionValue(SessionReader& reader, TerrainStampSettings& value) { SESSION_STAMP_FIELDS(SESSION_READ_FIELD) return true; }
189template <> void writeSessionValue(SessionWriter& writer, const TerrainSmoothSettings& value) { SESSION_SMOOTH_FIELDS(SESSION_WRITE_FIELD) }
190template <> bool readSessionValue(SessionReader& reader, TerrainSmoothSettings& value) { SESSION_SMOOTH_FIELDS(SESSION_READ_FIELD) return true; }
191template <> void writeSessionValue(SessionWriter& writer, const TerrainRidgeSettings& value) { SESSION_RIDGE_FIELDS(SESSION_WRITE_FIELD) }
192template <> bool readSessionValue(SessionReader& reader, TerrainRidgeSettings& value) { SESSION_RIDGE_FIELDS(SESSION_READ_FIELD) return true; }
193template <> void writeSessionValue(SessionWriter& writer, const TerrainTerraceSettings& value) { SESSION_TERRACE_FIELDS(SESSION_WRITE_FIELD) }
194template <> bool readSessionValue(SessionReader& reader, TerrainTerraceSettings& value) { SESSION_TERRACE_FIELDS(SESSION_READ_FIELD) return true; }
195template <> void writeSessionValue(SessionWriter& writer, const TerrainHeightMixSettings& value) { SESSION_MIX_FIELDS(SESSION_WRITE_FIELD) }
196template <> bool readSessionValue(SessionReader& reader, TerrainHeightMixSettings& value) { SESSION_MIX_FIELDS(SESSION_READ_FIELD) return true; }
197template <> void writeSessionValue(SessionWriter& writer, const TerrainWaterSettings& value) { SESSION_WATER_FIELDS(SESSION_WRITE_FIELD) }
198template <> bool readSessionValue(SessionReader& reader, TerrainWaterSettings& value) { SESSION_WATER_FIELDS(SESSION_READ_FIELD) return true; }
199template <> void writeSessionValue(SessionWriter& writer, const TerrainSedimentSettings& value) { SESSION_SEDIMENT_FIELDS(SESSION_WRITE_FIELD) }
200template <> bool readSessionValue(SessionReader& reader, TerrainSedimentSettings& value) { SESSION_SEDIMENT_FIELDS(SESSION_READ_FIELD) return true; }
201template <> void writeSessionValue(SessionWriter& writer, const TerrainThermalSettings& value) { SESSION_THERMAL_FIELDS(SESSION_WRITE_FIELD) }
202template <> bool readSessionValue(SessionReader& reader, TerrainThermalSettings& value) { SESSION_THERMAL_FIELDS(SESSION_READ_FIELD) return true; }
203#undef SESSION_WRITE_FIELD
204#undef SESSION_READ_FIELD
205#undef SESSION_STAMP_FIELDS
206#undef SESSION_SMOOTH_FIELDS
207#undef SESSION_RIDGE_FIELDS
208#undef SESSION_TERRACE_FIELDS
209#undef SESSION_MIX_FIELDS
210#undef SESSION_WATER_FIELDS
211#undef SESSION_SEDIMENT_FIELDS
212#undef SESSION_THERMAL_FIELDS
213} // namespace
216 std::shared_ptr<const SessionState> current;
217 std::vector<Command> commands;
218 int cursor = 0;
221 std::optional<SessionState> replayCandidate;
222 int replayNext = 0;
223 Impl() = default;
224 Impl(const Impl& other)
225 : baseline(other.baseline), current(other.current), commands(other.commands), cursor(other.cursor),
226 access(other.access) {}
232 auto next = std::make_shared<SessionState>();
233 next->terrain = baseline;
234 next->sediment = Heightmap(baseline.getWidth(), baseline.getHeight());
235 auto initialized = next->water.reset(baseline.getWidth(), baseline.getHeight());
236 if (!initialized.ok()) return initialized;
237 for (int i = 0; i < cursor; ++i) {
238 const auto& c = commands[size_t(i)];
239 if (!c.enabled) continue;
240 auto result = std::visit([&](const auto& operation) { return execute(*next, operation); }, c.operation);
241 if (!result.ok()) return result;
242 }
243 int changed = 0;
244 for (size_t i = 0; i < baseline.data().size(); ++i)
245 changed += next->terrain.data()[i] != (current ? current->terrain.data()[i] : baseline.data()[i]);
246 current = std::move(next);
247 return Result<int>::success(changed);
248 }
249};
255 return impl_ ? impl_->access : TerrainSessionAccess::Editable;
256}
258 return impl_ ? impl_->replayStatus : TerrainSessionRunStatus::Idle;
259}
261 return impl_ ? impl_->replayNext : 0;
262}
263int TerrainGenerationSession::getOperationCount() const noexcept { return impl_ ? int(impl_->commands.size()) : 0; }
264int TerrainGenerationSession::getAppliedCount() const noexcept { return impl_ ? impl_->cursor : 0; }
268 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.session: unknown access policy"));
269 if (impl_ && impl_->replayStatus == TerrainSessionRunStatus::Pending)
271 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.session: cancel pending replay first"));
272 if (!impl_) impl_ = std::make_unique<Impl>();
273 impl_->access = access;
274 return Result<void>::success();
275}
278 return invalid("terrain.session.reset: editable session and finite baseline required");
279 auto candidate = std::make_unique<Impl>();
280 candidate->baseline = baseline;
281 auto initialized = candidate->rebuild();
282 if (!initialized.ok()) return initialized;
283 impl_.swap(candidate);
284 return Result<int>::success(int(baseline.data().size()));
285}
290Result<int> TerrainGenerationSession::append(Command command) {
291 if (!impl_ || !impl_->editable() || impl_->cursor == INT_MAX)
292 return invalid("terrain.session.append: initialized editable session required");
293 auto candidate = std::make_unique<Impl>(*impl_);
294 candidate->replayStatus = TerrainSessionRunStatus::Idle;
295 candidate->replayCandidate.reset();
296 candidate->replayNext = 0;
297 candidate->commands.resize(size_t(candidate->cursor));
298 candidate->commands.push_back(std::move(command));
299 ++candidate->cursor;
300 auto result = candidate->rebuild();
301 if (!result.ok()) return result;
302 impl_.swap(candidate);
303 return result;
304}
305
307 return append(Command{ContrastRecord{mask, strength, featureSize}});
308}
319 return append(Command{PowerRecord{mask, power}});
320}
322 float maximum) {
323 return append(Command{CurveRecord{mask, curve, minimum, maximum}});
324}
330 return append(Command{SimulationResetRecord{depth}});
331}
341 if (!impl_ || !impl_->current || !validRaster(output) || output.getWidth() != impl_->baseline.getWidth() ||
342 output.getHeight() != impl_->baseline.getHeight())
343 return invalid("terrain.session: initialized simulation and matching finite output required");
344 return publish(output, impl_->current->sediment.data());
345}
347 if (!impl_ || !impl_->current) return invalid("terrain.session: initialized simulation required");
348 return impl_->current->water.exportChannel(output, channel);
349}
350Result<int> TerrainGenerationSession::moveCursor(int delta) {
351 if (!impl_ || !impl_->editable() || (delta < 0 && impl_->cursor == 0) ||
352 (delta > 0 && size_t(impl_->cursor) == impl_->commands.size()))
353 return invalid("terrain.session: unavailable history or locked/empty session");
354 auto candidate = std::make_unique<Impl>(*impl_);
355 candidate->replayStatus = TerrainSessionRunStatus::Idle;
356 candidate->replayCandidate.reset();
357 candidate->replayNext = 0;
358 candidate->cursor += delta;
359 auto result = candidate->rebuild();
360 if (!result.ok()) return result;
361 impl_.swap(candidate);
362 return result;
363}
366 if (!impl_ || !impl_->editable())
369 "terrain.session.beginReplay: initialized editable session required"));
370 SessionState candidate;
371 candidate.terrain = impl_->baseline;
372 candidate.sediment = Heightmap(impl_->baseline.getWidth(), impl_->baseline.getHeight());
373 auto initialized = candidate.water.reset(impl_->baseline.getWidth(), impl_->baseline.getHeight());
374 if (!initialized.ok()) return Result<TerrainSessionRunStatus>::failure(initialized.status());
375 impl_->replayCandidate = std::move(candidate);
376 impl_->replayNext = 0;
377 impl_->replayStatus = TerrainSessionRunStatus::Pending;
378 return Result<TerrainSessionRunStatus>::success(impl_->replayStatus);
379}
381 if (!impl_ || impl_->replayStatus != TerrainSessionRunStatus::Pending || !impl_->replayCandidate ||
382 maxOperations <= 0)
385 "terrain.session.stepReplay: pending replay and positive operation budget required"));
386 int visited = 0;
387 while (impl_->replayNext < impl_->cursor && visited < maxOperations) {
388 const auto& command = impl_->commands[static_cast<std::size_t>(impl_->replayNext)];
389 if (command.enabled) {
390 auto executed = std::visit(
391 [&](const auto& operation) { return execute(*impl_->replayCandidate, operation); }, command.operation);
392 if (!executed.ok()) {
393 impl_->replayCandidate.reset();
394 impl_->replayStatus = TerrainSessionRunStatus::Failed;
395 return Result<TerrainSessionRunStatus>::failure(executed.status());
396 }
397 }
398 ++impl_->replayNext;
399 ++visited;
400 }
401 if (impl_->replayNext == impl_->cursor) {
402 impl_->current = std::make_shared<const SessionState>(std::move(*impl_->replayCandidate));
403 impl_->replayCandidate.reset();
404 impl_->replayStatus = TerrainSessionRunStatus::Completed;
405 }
406 return Result<TerrainSessionRunStatus>::success(impl_->replayStatus);
407}
409 if (!impl_ || impl_->replayStatus != TerrainSessionRunStatus::Pending)
411 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.session.cancelReplay: no pending replay"));
412 impl_->replayCandidate.reset();
413 impl_->replayStatus = TerrainSessionRunStatus::Cancelled;
414 return Result<TerrainSessionRunStatus>::success(impl_->replayStatus);
415}
416Result<int> TerrainGenerationSession::undo() { return moveCursor(-1); }
419 if (!impl_ || !impl_->editable() || index < 0 || size_t(index) >= impl_->commands.size())
420 return invalid("terrain.session: valid operation and editable session required");
421 auto candidate = std::make_unique<Impl>(*impl_);
422 candidate->replayStatus = TerrainSessionRunStatus::Idle;
423 candidate->replayCandidate.reset();
424 candidate->replayNext = 0;
425 candidate->commands[size_t(index)].enabled = enabled;
426 auto result = candidate->rebuild();
427 if (!result.ok()) return result;
428 impl_.swap(candidate);
429 return result;
430}
432 if (!impl_ || index < 0 || size_t(index) >= impl_->commands.size())
434 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.session: invalid operation index"));
435 return Result<bool>::success(impl_->commands[size_t(index)].enabled);
436}
438 if (!impl_ || !impl_->current || !validRaster(output) || output.getWidth() != impl_->current->terrain.getWidth() ||
439 output.getHeight() != impl_->current->terrain.getHeight())
440 return invalid("terrain.session: initialized terrain and matching finite output required");
441 return publish(output, impl_->current->terrain.data());
442}
443
445 if (!impl_ || !impl_->current || impl_->replayStatus == TerrainSessionRunStatus::Pending)
447 DiagnosticCode::InvalidArgument, "terrain.session.snapshot: initialized idle session required"));
448 if (impl_->commands.size() > kMaximumSessionCommands)
450 DiagnosticCode::InvalidArgument, "terrain.session.snapshot: command count exceeds limit"));
451 SessionWriter writer;
452 writeSessionHeightmap(writer, impl_->baseline);
453 writer.scalar(impl_->cursor);
454 writer.scalar(impl_->access);
455 writer.scalar(static_cast<std::uint32_t>(impl_->commands.size()));
456 for (const auto& command : impl_->commands) {
457 writer.scalar(command.enabled);
458 writer.scalar(static_cast<std::uint8_t>(command.operation.index()));
459 std::visit([&](const auto& value) {
460 using T = std::decay_t<decltype(value)>;
461 if constexpr (std::is_same_v<T, StampRecord>) {
462 writeSessionHeightmap(writer, value.stamp); writeSessionHeightmap(writer, value.local);
463 writeSessionHeightmap(writer, value.global); writeSessionValue(writer, value.settings);
464 } else if constexpr (std::is_same_v<T, ContrastRecord>) {
465 writeSessionHeightmap(writer, value.mask); writer.scalar(value.strength); writer.scalar(value.featureSize);
466 } else if constexpr (std::is_same_v<T, SmoothRecord> || std::is_same_v<T, RidgeRecord> ||
467 std::is_same_v<T, TerraceRecord>) {
468 writeSessionHeightmap(writer, value.mask); writeSessionValue(writer, value.settings);
469 } else if constexpr (std::is_same_v<T, PowerRecord>) {
470 writeSessionHeightmap(writer, value.mask); writer.scalar(value.power);
471 } else if constexpr (std::is_same_v<T, CurveRecord>) {
472 writeSessionHeightmap(writer, value.mask); writeSessionHeightmap(writer, value.curve);
473 writer.scalar(value.minimum); writer.scalar(value.maximum);
474 } else if constexpr (std::is_same_v<T, MixRecord>) {
475 writeSessionHeightmap(writer, value.local); writeSessionHeightmap(writer, value.global);
476 writeSessionValue(writer, value.settings);
477 } else if constexpr (std::is_same_v<T, SimulationResetRecord>) {
478 writer.scalar(value.depth);
479 } else if constexpr (std::is_same_v<T, ThermalRecord>) {
480 writeSessionValue(writer, value.settings);
481 } else {
482 writeSessionValue(writer, value.water); writeSessionValue(writer, value.sediment);
483 writeSessionValue(writer, value.thermal); writer.scalar(value.iterations);
484 }
485 }, command.operation);
486 }
487 if (writer.bytes.size() > kMaximumSessionSnapshotBytes)
489 DiagnosticCode::InvalidArgument, "terrain.session.snapshot: payload exceeds size limit"));
490 Value::Object root{{"payload", encodeSessionHex(writer.bytes)},
491 {"schema", "eve.procgen.terrain-generation-session"}, {"version", 1}};
492 return Value(std::move(root)).toJson();
493}
494
496 if ((impl_ && (impl_->access == TerrainSessionAccess::Locked ||
497 impl_->replayStatus == TerrainSessionRunStatus::Pending)) ||
498 json.size() > kMaximumSessionSnapshotBytes * 2 + 1024)
500 DiagnosticCode::InvalidArgument, "terrain.session.restore: editable idle session and bounded JSON required"));
501 auto decoded = Value::fromJson(json);
502 if (!decoded.ok()) return Result<void>::failure(decoded.status());
503 const auto* root = decoded.value().getIf<Value::Object>();
504 if (!root || root->size() != 3 || !root->contains("schema") || !root->contains("version") ||
505 !root->contains("payload"))
507 DiagnosticCode::InvalidArgument, "terrain.session.restore: missing or unknown root fields"));
508 const auto* schema = root->at("schema").getIf<std::string>();
509 const auto* version = root->at("version").getIf<std::int64_t>();
510 const auto* payload = root->at("payload").getIf<std::string>();
511 if (!schema || *schema != "eve.procgen.terrain-generation-session" || !version || *version != 1 || !payload)
513 DiagnosticCode::InvalidArgument, "terrain.session.restore: unsupported schema or version"));
514 auto bytes = decodeSessionHex(*payload);
515 if (!bytes.ok()) return Result<void>::failure(bytes.status());
516 SessionReader reader{bytes.value()};
517 auto candidate = std::make_unique<Impl>();
518 std::uint32_t count{};
519 if (!readSessionHeightmap(reader, candidate->baseline) || !reader.scalar(candidate->cursor) ||
520 !reader.scalar(candidate->access) || !reader.scalar(count) || count > kMaximumSessionCommands ||
521 candidate->cursor < 0 || candidate->cursor > static_cast<int>(count) ||
522 (candidate->access != TerrainSessionAccess::Editable && candidate->access != TerrainSessionAccess::Locked))
524 DiagnosticCode::InvalidArgument, "terrain.session.restore: invalid session header"));
525 candidate->commands.reserve(count);
526 for (std::uint32_t index = 0; index < count; ++index) {
527 bool enabled{}; std::uint8_t kind{};
528 if (!reader.scalar(enabled) || !reader.scalar(kind) || kind > 10)
530 DiagnosticCode::InvalidArgument, "terrain.session.restore: invalid command header"));
531 Command command; command.enabled = enabled;
532 bool valid = true;
533 switch (kind) {
534 case 0: { StampRecord v; valid = readSessionHeightmap(reader, v.stamp) && readSessionHeightmap(reader, v.local) && readSessionHeightmap(reader, v.global) && readSessionValue(reader, v.settings); command.operation = std::move(v); break; }
535 case 1: { ContrastRecord v; valid = readSessionHeightmap(reader, v.mask) && reader.scalar(v.strength) && reader.scalar(v.featureSize); command.operation = std::move(v); break; }
536 case 2: { SmoothRecord v; valid = readSessionHeightmap(reader, v.mask) && readSessionValue(reader, v.settings); command.operation = std::move(v); break; }
537 case 3: { RidgeRecord v; valid = readSessionHeightmap(reader, v.mask) && readSessionValue(reader, v.settings); command.operation = std::move(v); break; }
538 case 4: { TerraceRecord v; valid = readSessionHeightmap(reader, v.mask) && readSessionValue(reader, v.settings); command.operation = std::move(v); break; }
539 case 5: { PowerRecord v; valid = readSessionHeightmap(reader, v.mask) && reader.scalar(v.power); command.operation = std::move(v); break; }
540 case 6: { CurveRecord v; valid = readSessionHeightmap(reader, v.mask) && readSessionHeightmap(reader, v.curve) && reader.scalar(v.minimum) && reader.scalar(v.maximum); command.operation = std::move(v); break; }
541 case 7: { MixRecord v; valid = readSessionHeightmap(reader, v.local) && readSessionHeightmap(reader, v.global) && readSessionValue(reader, v.settings); command.operation = std::move(v); break; }
542 case 8: { SimulationResetRecord v; valid = reader.scalar(v.depth); command.operation = v; break; }
543 case 9: { ThermalRecord v; valid = readSessionValue(reader, v.settings); command.operation = v; break; }
544 default: { HydraulicRecord v; valid = readSessionValue(reader, v.water) && readSessionValue(reader, v.sediment) && readSessionValue(reader, v.thermal) && reader.scalar(v.iterations); command.operation = v; break; }
545 }
547 DiagnosticCode::InvalidArgument, "terrain.session.restore: invalid command payload"));
548 candidate->commands.push_back(std::move(command));
549 }
550 if (reader.offset != reader.bytes.size())
552 DiagnosticCode::InvalidArgument, "terrain.session.restore: trailing payload data"));
553 auto validation = std::make_unique<Impl>(*candidate);
554 validation->cursor = static_cast<int>(validation->commands.size());
555 for (auto& command : validation->commands) command.enabled = true;
556 auto validated = validation->rebuild();
557 if (!validated.ok()) return Result<void>::failure(validated.status());
558 auto rebuilt = candidate->rebuild();
559 if (!rebuilt.ok()) return Result<void>::failure(rebuilt.status());
560 impl_.swap(candidate);
561 return Result<void>::success();
562}
563} // namespace eve::procgen
ActionParameterOperation operation
double value
Value::Object payload
int root
Definition AnimSmr.cpp:119
std::string output
std::string terrain
int mask
float maximum[3]
float minimum[3]
float v
std::int32_t c
int h
std::uint32_t height
std::uint32_t width
std::string text
TokenKind kind
std::string local
size_t offset
std::uint64_t bytes
bool valid
float power
Definition RockMesh.cpp:23
std::uint32_t count
std::size_t cursor
TerrainWaterField water
Heightmap curve
TerrainThermalSettings settings
#define SESSION_WRITE_FIELD(name)
#define SESSION_THERMAL_FIELDS(X)
#define SESSION_SMOOTH_FIELDS(X)
#define SESSION_SEDIMENT_FIELDS(X)
#define SESSION_WATER_FIELDS(X)
#define SESSION_MIX_FIELDS(X)
Heightmap stamp
#define SESSION_TERRACE_FIELDS(X)
TerrainThermalSettings thermal
Heightmap global
#define SESSION_STAMP_FIELDS(X)
#define SESSION_RIDGE_FIELDS(X)
#define SESSION_READ_FIELD(name)
Heightmap sediment
int iterations
Definition TreeMesh.cpp:311
uint32_t index
std::uint32_t depth
Owning, renderer-independent dynamic values.
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
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
std::map< std::string, Value > Object
Definition Value.h:34
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 in-memory terrain operation session; records native stamps, scalar effects and erosion....
Result< int > resetSimulation(float depth=0)
Record a simulation reset: zero sediment, flux and velocity; set uniform nonnegative water depth....
Result< TerrainSessionRunStatus > beginReplay()
Start replaying the applied history into a private candidate without changing current terrain.
Result< std::string > snapshotJson() const
Serialize schema version 1 with the complete replayable operation history.
Result< TerrainSessionRunStatus > stepReplay(int maxOperations)
Execute at most maxOperations commands and publish once when the candidate completes.
Result< int > power(const Heightmap &mask, float power)
Record power transform; copies all borrowed inputs and values.
Result< int > copySediment(Heightmap &output) const
Copy signed current sediment into a matching finite output, including while locked.
Result< bool > operationEnabled(int index) const
Read a command's enabled flag, or InvalidArgument for an out-of-range history index.
Result< int > exportWater(Heightmap &output, TerrainWaterChannel channel) const
Copy a raw water channel into a matching finite output, including while locked.
Result< int > terrace(const Heightmap &mask, const TerrainTerraceSettings &settings)
Record terracing; copies all borrowed inputs and values.
Result< int > contrast(const Heightmap &mask, float strength, float featureSize)
Record nine-tap contrast; copies all borrowed inputs and values.
Result< int > heightCurve(const Heightmap &mask, const Heightmap &curve, float minimum, float maximum)
Record height-curve transform; copies all borrowed inputs and values.
int getReplayCompletedOperations() const noexcept
Return commands visited by the current or most recent staged replay.
Result< int > hydraulic(const TerrainWaterSettings &water, const TerrainSedimentSettings &sediment, const TerrainThermalSettings &thermal, int iterations)
Record complete water/sediment/thermal iterations using preceding simulation state....
Result< int > copyTerrain(Heightmap &output) const
Copy current terrain into an existing matching finite output; allowed while locked.
Result< int > ridges(const Heightmap &mask, const TerrainRidgeSettings &settings)
Record ridge iteration; copies all borrowed inputs and values.
Result< int > setOperationEnabled(int index, bool enabled)
Toggle a command and replay the applied prefix atomically; index is history-local,...
Result< void > setAccess(TerrainSessionAccess access)
Change only the edit policy; unlocking is always permitted, invalid enum values are rejected.
Result< int > smooth(const Heightmap &mask, const TerrainSmoothSettings &settings)
Record two-pass smoothing; copies all borrowed inputs and values.
Result< int > heightMix(const Heightmap &local, const Heightmap &global, const TerrainHeightMixSettings &settings)
Record height mixing; copies all borrowed inputs and values.
Result< int > redo()
Move the applied prefix forward one command and replay; no redo returns InvalidArgument.
TerrainSessionAccess getAccess() const noexcept
Read current edit policy; moved-from sessions are editable.
Result< void > restoreJson(const std::string &json)
Atomically restore schema version 1 and rebuild the applied prefix before publication.
int getOperationCount() const noexcept
Count all recorded commands, including the redo suffix.
TerrainGenerationSession()
Construct an empty editable session.
Result< int > stamp(const Heightmap &stamp, const TerrainStampSettings &settings, const Heightmap &localMask, const Heightmap &globalMask)
Append an owned stamp command, truncating redo only on success; locked sessions reject it.
Result< TerrainSessionRunStatus > cancelReplay()
Cancel a pending replay and discard its private candidate without changing current terrain.
Result< int > reset(const Heightmap &baseline)
Copy a finite baseline, zero sediment/water and clear history atomically; locked sessions reject rese...
Result< int > undo()
Move the applied prefix back one command and replay; no history returns InvalidArgument.
Result< int > replay()
Re-evaluate the applied history prefix from baseline atomically; locked sessions reject it.
Result< int > thermal(const TerrainThermalSettings &settings)
Record thermal erosion using accumulated sediment; water is preserved. Settings are copied.
TerrainSessionRunStatus getReplayStatus() const noexcept
Return the staged replay lifecycle status.
int getAppliedCount() const noexcept
Count commands in the applied prefix, including disabled commands.
~TerrainGenerationSession()
Destroy owned terrain and command snapshots.
Result< int > publish(Heightmap &target, std::vector< float > candidate)
Publish.
bool validRaster(const Heightmap &map)
Valid raster.
Result< int > invalid(std::string message)
Invalid.
Result< int > applyTerrainTerrace(Heightmap &target, const Heightmap &mask, const TerrainTerraceSettings &settings)
Terrace scalar heights using nearest-even rounding and the shader's one-sided bevel.
Result< int > applyTerrainContrast(Heightmap &target, const Heightmap &mask, float strength, float featureSize)
Apply the nine-tap Pcg contrast equation to native scalar heights.
TerrainSessionRunStatus
Observable lifecycle of a caller-driven staged session replay.
Result< int > applyTerrainHeightMix(Heightmap &target, const Heightmap &local, const Heightmap &global, const TerrainHeightMixSettings &settings)
Mix centered local brush heights into terrain, then globally mask and clip.
Result< int > applyTerrainSmooth(Heightmap &target, const Heightmap &mask, const TerrainSmoothSettings &settings)
Apply Pcg's horizontal then vertical seven-pair weighted smooth passes.
Result< int > applyTerrainPower(Heightmap &target, const Heightmap &mask, float power)
Apply the Pcg PowerOf control: lerp(height,pow(height,4-power),mask).
TerrainSessionAccess
Edit policy for the owned operation history and terrain.
TerrainWaterChannel
Stored scalar channels; Pcg erosion masks read FluxRight or VelocityX, not vector magnitude.
Result< int > applyTerrainStamp(Heightmap &target, const Heightmap &stamp, const TerrainStampSettings &s, const Heightmap &localMask, const Heightmap &globalMask)
Apply a rotated, bilinearly sampled stamp; publish only after all checks succeed.
Result< int > applyTerrainRidges(Heightmap &target, const Heightmap &mask, const TerrainRidgeSettings &settings)
Apply ordered horizontal/vertical ridge powers, neighbor mixing and per-pass clipping.
Result< int > applyTerrainThermal(Heightmap &heights, Heightmap &sediment, const TerrainThermalSettings &settings)
Apply the source thermal equation, publishing height and sediment together.
Result< int > applyTerrainHeightCurve(Heightmap &target, const Heightmap &mask, const Heightmap &curve, float minimum, float maximum)
Apply the height-transform shader's smoothstep and one-row height curve.
std::vector< RuntimeTensor > execute(const Node &n, const std::vector< const RuntimeTensor * > &in, OnnxCompute *compute)
Execute.
bool enabled
std::variant< StampRecord, ContrastRecord, SmoothRecord, RidgeRecord, TerraceRecord, PowerRecord, CurveRecord, MixRecord, SimulationResetRecord, ThermalRecord, HydraulicRecord > operation
Mix-height scalar controls; delta scale is maximum-minimum, independent of clipping bounds....
Ridge controls in caller height units, with explicit clipping bounds. Defaults match the Pcg packed s...
Effective sediment coefficients from Hydraulic.compute, after UI conversion. The source hardcodes har...
Two-pass smoothing controls; radius in texels, verticality in [-1,1], strength in [0,...
Value configuration for a rectangular stamp in world X/Z coordinates.
Terrace frequency in inverse height units, interior bevel in [0,1], strength in [0,...
Explicit simulation controls for the Pcg eight-neighbor thermal kernel. Height differences are multip...
Explicit constants for the source hydraulic water-flow equations, after UI unit conversion.