载入中...
搜索中...
未找到
CanonicalPcgCook.cpp
浏览该文件的文档.
2
3#include "common/Value.h"
4
5#include <cmath>
6#include <iomanip>
7#include <limits>
8#include <sstream>
9
10namespace eve::asset {
11namespace {
12
13const Value* field(const Value::Object& object, std::string_view name) {
14 const auto found = object.find(std::string(name));
15 return found == object.end() ? nullptr : &found->second;
16}
17
18bool number(const Value* value, double& output) {
19 if (!value || !value->isNumeric()) return false;
20 output = value->isInt64() ? double(value->asInt()) : value->asDouble();
21 return std::isfinite(output);
22}
23
24void node(std::ostringstream& out, std::string_view id, std::string_view operation) {
25 out << "NODE " << std::quoted(std::string(id)) << ' ' << std::quoted(std::string(operation)) << '\n';
26}
27
28void edge(std::ostringstream& out, std::string_view from, std::string_view to) {
29 out << "EDGE " << std::quoted(std::string(from)) << ' ' << std::quoted(std::string(to)) << " 0\n";
30}
31
32} // namespace
33
35 std::span<const std::uint8_t> definition, std::uint32_t maximumRules,
36 std::uint64_t maximumPlanBytes) {
37 auto parsed = Value::fromJson(std::string_view(reinterpret_cast<const char*>(definition.data()),
38 definition.size()));
39 if (!parsed) return Result<CookedCanonicalPcgGraph>::failure(parsed.status());
40 const auto* root = parsed.value().getIf<Value::Object>();
41 const Value* schema = root ? field(*root, "schema") : nullptr;
42 const Value* version = root ? field(*root, "schemaVersion") : nullptr;
43 const Value* algorithm = root ? field(*root, "algorithm") : nullptr;
44 const Value* rulesValue = root ? field(*root, "rules") : nullptr;
45 const auto* rules = rulesValue ? rulesValue->getIf<Value::Array>() : nullptr;
46 if (!schema || !schema->isString() || schema->asString() != "eve.pcg-graph" ||
47 !version || !version->isInt64() || version->asInt() != 1 || !algorithm ||
48 !algorithm->isString() || algorithm->asString() != "terrain-layer-scatter-v1" || !rules)
50 DiagnosticCode::Unsupported, "unsupported eve.pcg-graph/1 definition", {}, {}, "asset.cook.pcg"));
51 if (rules->empty() || rules->size() > maximumRules)
53 DiagnosticCode::InvalidArgument, "PCG rule count exceeds Cook limits", "$.rules", {}, "asset.cook.pcg"));
54
55 std::ostringstream plan;
56 plan << "EVPCG_POINT_GRAPH 1\n" << std::setprecision(9);
57 std::vector<std::string> outputs;
59 constexpr double radiansToDegrees = 57.29577951308232;
60 for (std::size_t i = 0; i < rules->size(); ++i) {
61 const auto* rule = (*rules)[i].getIf<Value::Object>();
62 const Value* id = rule ? field(*rule, "id") : nullptr;
63 const Value* prototype = rule ? field(*rule, "prototype") : nullptr;
64 const Value* layer = rule ? field(*rule, "layer") : nullptr;
65 const Value* seed = rule ? field(*rule, "seed") : nullptr;
66 double density = 0, minimumSlope = 0, maximumSlope = 0;
67 if (!id || !id->isString() || id->asString().empty() || !prototype ||
68 !prototype->isString() || prototype->asString().empty() || !layer || !layer->isString() ||
69 !seed || !seed->isInt64() || seed->asInt() < 0 ||
70 !number(rule ? field(*rule, "densityPerSquareMeter") : nullptr, density) || density < 0 ||
71 !number(rule ? field(*rule, "minimumSlopeRadians") : nullptr, minimumSlope) ||
72 !number(rule ? field(*rule, "maximumSlopeRadians") : nullptr, maximumSlope) ||
73 minimumSlope < 0 || maximumSlope < minimumSlope || maximumSlope > 3.141592653589793)
75 Diagnostic::error(DiagnosticCode::InvalidArgument, "PCG scatter rule is invalid",
76 "$.rules[" + std::to_string(i) + "]", {}, "asset.cook.pcg"));
77 const std::string suffix = std::to_string(i);
78 const std::string sample = "sample." + suffix;
79 const std::string project = "project." + suffix;
80 const std::string slope = "slope." + suffix;
81 const std::string taggedPrototype = "prototype." + suffix;
82 const std::string taggedLayer = "layer." + suffix;
83 node(plan, sample, "spatial.sample");
84 plan << "FLOAT " << std::quoted(sample) << " \"spacing\" "
85 << (density > 0 ? std::sqrt(1.0 / density) : 1.0) << '\n';
86 plan << "INT " << std::quoted(sample) << " \"seed\" "
87 << (seed->asInt() & 0x7fffffff) << '\n';
88 node(plan, project, "spatial.project"); edge(plan, sample, project);
89 node(plan, slope, "filter.slope"); edge(plan, project, slope);
90 plan << "FLOAT " << std::quoted(slope) << " \"minDegrees\" "
91 << minimumSlope * radiansToDegrees << '\n';
92 plan << "FLOAT " << std::quoted(slope) << " \"maxDegrees\" "
93 << maximumSlope * radiansToDegrees << '\n';
94 std::string scatterOutput = slope;
95 if (density == 0) {
96 const std::string culled = "cull." + suffix;
97 node(plan, culled, "density.cull"); edge(plan, slope, culled);
98 plan << "FLOAT " << std::quoted(culled) << " \"multiplier\" 0\n";
99 scatterOutput = culled;
100 }
101 node(plan, taggedPrototype, "attribute.set.string"); edge(plan, scatterOutput, taggedPrototype);
102 plan << "STRING " << std::quoted(taggedPrototype) << " \"attribute\" \"prototype\"\n"
103 << "STRING " << std::quoted(taggedPrototype) << " \"value\" "
104 << std::quoted(prototype->asString()) << '\n';
105 node(plan, taggedLayer, "attribute.set.string"); edge(plan, taggedPrototype, taggedLayer);
106 plan << "STRING " << std::quoted(taggedLayer) << " \"attribute\" \"layer\"\n"
107 << "STRING " << std::quoted(taggedLayer) << " \"value\" "
108 << std::quoted(layer->asString()) << '\n';
109 outputs.push_back(taggedLayer);
110 slots.emplace_back(Value::Object{{"node", Value(sample)}, {"role", Value("terrain")}});
111 slots.emplace_back(Value::Object{{"node", Value(project)}, {"role", Value("terrain")}});
112 }
113 std::string output = outputs.front();
114 for (std::size_t i = 1; i < outputs.size(); ++i) {
115 const std::string merged = "merge." + std::to_string(i);
116 node(plan, merged, "merge"); edge(plan, output, merged);
117 plan << "EDGE " << std::quoted(outputs[i]) << ' ' << std::quoted(merged) << " 1\n";
118 output = merged;
119 }
120 plan << "END\n";
121 const std::string encodedPlan = plan.str();
122 if (encodedPlan.size() > maximumPlanBytes)
124 DiagnosticCode::InvalidArgument, "compiled PCG plan exceeds Cook budget", {}, {}, "asset.cook.pcg"));
125 Value::Object runtime{{"schema", Value("eve.pcg-graph")},
126 {"schemaVersion", Value(std::int64_t(1))},
127 {"executionPlan", Value("chunk:1")},
128 {"executionPlanFormat", Value("EVPCG_POINT_GRAPH/1")},
129 {"outputNode", Value(output)}, {"spatialSlots", Value(std::move(slots))}};
130 auto encodedDefinition = Value(std::move(runtime)).toJson();
131 if (!encodedDefinition)
132 return Result<CookedCanonicalPcgGraph>::failure(encodedDefinition.status());
134 {{encodedDefinition.value().begin(), encodedDefinition.value().end()},
135 {encodedPlan.begin(), encodedPlan.end()}});
136}
137
138} // namespace eve::asset
ActionParameterOperation operation
double value
int root
Definition AnimSmr.cpp:119
std::string output
std::string from
Canonical PCG graph compilation to runtime execution plans.
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string name
TileLayer * layer
std::string id
Definition PlayHost.cpp:108
std::uint32_t seed
Definition PointSet.cpp:807
float begin
const RoadNode * node
const RoadEdge * edge
double number
bool found
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
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
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Result< std::string > toJson() const
Serialize this value as deterministic compact JSON.
Definition Value.cpp:67
const std::string & asString() const
Return the string payload; the caller must have checked the kind.
Definition Value.cpp:87
bool isInt64() const noexcept
Return true when this value is an Int64.
Definition Value.h:91
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
bool isString() const noexcept
Return true when this value is a string.
Definition Value.h:95
std::int64_t asInt() const
Return the Int64 payload; the caller must have checked the kind.
Definition Value.cpp:69
std::vector< Value > Array
Definition Value.h:33
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
Result< CookedCanonicalPcgGraph > cookCanonicalPcgGraph(std::span< const std::uint8_t > definition, std::uint32_t maximumRules, std::uint64_t maximumPlanBytes)
Compile eve.pcg-graph/1 terrain scatter rules to a bounded PointGraph plan.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
const EditorValue * field(const EditorValue &value, const char *name)