载入中...
搜索中...
未找到
MeshModifierGraph.cpp
浏览该文件的文档.
2
5
6#include <algorithm>
7#include <array>
8#include <cmath>
9#include <functional>
10#include <limits>
11#include <unordered_map>
12#include <unordered_set>
13
14namespace eve::procgen_editing {
15namespace {
16
17EditorDiagnostic meshGraphDiagnostic(const char* rule, DiagnosticSeverity severity, std::string message) {
19 std::move(message));
20}
21
22EditorValue defaultValue(const std::string& kind, const std::string& encoded) {
23 if (kind == "int") return EditorValue(static_cast<std::int64_t>(std::stoll(encoded)));
24 if (kind == "float") return EditorValue(std::stod(encoded));
25 return EditorValue(encoded);
26}
27
28const GraphPinRecord* findPin(const GraphDocumentData& graph, const GraphPinId& id) {
29 for (const auto& node : graph.nodes) {
30 const auto found =
31 std::find_if(node.pins.begin(), node.pins.end(), [&](const GraphPinRecord& pin) { return pin.id == id; });
32 if (found != node.pins.end()) return &*found;
33 }
34 return nullptr;
35}
36
37const GraphNodeRecord* findNode(const GraphDocumentData& graph, const GraphNodeId& id) {
38 const auto found = std::find_if(graph.nodes.begin(), graph.nodes.end(),
39 [&](const GraphNodeRecord& node) { return node.id == id; });
40 return found == graph.nodes.end() ? nullptr : &*found;
41}
42
43int inputIndex(const GraphNodeRecord& node, const GraphPinId& pin) {
44 int index = 0;
45 for (const auto& candidate : node.pins) {
46 if (candidate.direction != GraphPinDirection::Input) continue;
47 if (candidate.id == pin) return index;
48 ++index;
49 }
50 return -1;
51}
52
53Result<void> validateTopology(const GraphDocumentData& graph) {
54 std::unordered_map<std::string, const GraphNodeRecord*> nodes;
55 std::unordered_map<std::string, std::vector<std::string>> dependencies;
56 std::unordered_set<std::string> connectedInputs;
57 for (const auto& node : graph.nodes) {
58 if (node.id.empty() || !nodes.emplace(node.id.value(), &node).second)
60 "mesh modifier node ids must be non-empty and unique",
61 node.id.value(), {}, "procgen.meshModifier.editor"));
63 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "unknown mesh modifier operation",
64 node.type, {}, "procgen.meshModifier.editor"));
65 }
66 for (const auto& edge : graph.edges) {
67 const auto* from = findPin(graph, edge.from);
68 const auto* to = findPin(graph, edge.to);
69 if (!from || !to || from->direction != GraphPinDirection::Output || to->direction != GraphPinDirection::Input ||
70 from->type != "mesh" || to->type != "mesh" || from->node == to->node)
71 return Result<void>::failure(Diagnostic::error(DiagnosticCode::TypeMismatch, "invalid mesh modifier edge",
72 edge.id.value(), {}, "procgen.meshModifier.editor"));
73 const auto* target = findNode(graph, to->node);
74 if (!target || inputIndex(*target, to->id) < 0)
76 "mesh modifier edge target input is missing",
77 edge.id.value(), {}, "procgen.meshModifier.editor"));
78 if (!connectedInputs.insert(to->id.value()).second)
80 "mesh modifier input has multiple connections",
81 to->id.value(), {}, "procgen.meshModifier.editor"));
82 dependencies[to->node.value()].push_back(from->node.value());
83 }
84 for (const auto& node : graph.nodes) {
85 const int requiredInputs = procgen::MeshModifierGraph::operationInputCount(node.type);
86 for (int input = 0; input < requiredInputs; ++input) {
87 const auto pin = node.id.value() + ".in" + std::to_string(input);
88 if (!connectedInputs.contains(pin))
90 "required mesh modifier input is disconnected", pin, {},
91 "procgen.meshModifier.editor"));
92 }
93 }
94 std::unordered_set<std::string> visiting;
95 std::unordered_set<std::string> visited;
96 std::function<Result<void>(const std::string&)> visit = [&](const std::string& node) {
97 if (visited.contains(node)) return Result<void>::success();
98 if (!visiting.insert(node).second)
100 "mesh modifier graph contains a cycle", node, {},
101 "procgen.meshModifier.editor"));
102 for (const auto& dependency : dependencies[node]) {
103 auto result = visit(dependency);
104 if (!result.ok()) return result;
105 }
106 visiting.erase(node);
107 visited.insert(node);
108 return Result<void>::success();
109 };
110 for (const auto& [id, unused] : nodes) {
111 (void)unused;
112 auto result = visit(id);
113 if (!result.ok()) return result;
114 }
115 return Result<void>::success();
116}
117
118Result<void> populateRuntime(const GraphDocumentData& graph, procgen::MeshModifierGraph& runtime) {
119 for (const auto& node : graph.nodes) {
120 auto added = runtime.addNode(node.id.value(), node.type);
121 if (!added.ok()) return added;
122 const auto* properties = node.properties.getIf<EditorValue::Object>();
123 if (!properties)
125 "mesh modifier node properties must be an object",
126 node.id.value(), {}, "procgen.meshModifier.editor"));
127 for (const auto& [key, value] : *properties) {
128 if (key == "splinePath" && (node.type == "deform.splinePath" || node.type == "mesh.splineTube" ||
129 node.type == "mesh.splineRibbon" || node.type == "mesh.splineExtrude")) {
131 if (!path.ok()) return Result<void>::failure(path.status());
132 auto bound = runtime.setNodeSplinePath(node.id.value(), path.value());
133 if (!bound.ok()) return bound;
134 continue;
135 }
136 if (key == "splineProfile" && node.type == "mesh.splineExtrude") {
137 const auto* object = value.getIf<EditorValue::Object>();
138 if (!object)
140 "spline profile must be an object", node.id.value(),
141 {}, "procgen.meshModifier.editor"));
142 const auto closedValue = object->find("closed");
143 const auto pointsValue = object->find("points");
144 const auto* closed = closedValue != object->end() ? closedValue->second.getIf<bool>() : nullptr;
145 const auto* points =
146 pointsValue != object->end() ? pointsValue->second.getIf<EditorValue::Array>() : nullptr;
147 if (!closed || !points)
149 "spline profile requires closed and points",
150 node.id.value(), {}, "procgen.meshModifier.editor"));
151 procgen::SplineProfile profile;
152 profile.closed = *closed;
153 for (const auto& pointValue : *points) {
154 const auto* point = pointValue.getIf<EditorValue::Object>();
155 if (!point)
157 Diagnostic::error(DiagnosticCode::TypeMismatch, "spline profile point must be an object",
158 node.id.value(), {}, "procgen.meshModifier.editor"));
159 const auto xValue = point->find("x");
160 const auto yValue = point->find("y");
161 const auto* x = xValue != point->end() ? xValue->second.getIf<double>() : nullptr;
162 const auto* y = yValue != point->end() ? yValue->second.getIf<double>() : nullptr;
163 if (!x || !y || !std::isfinite(*x) || !std::isfinite(*y))
165 Diagnostic::error(DiagnosticCode::TypeMismatch, "spline profile points require finite x/y",
166 node.id.value(), {}, "procgen.meshModifier.editor"));
167 profile.points.push_back({static_cast<float>(*x), static_cast<float>(*y)});
168 }
169 auto bound = runtime.setNodeSplineProfile(node.id.value(), profile);
170 if (!bound.ok()) return bound;
171 continue;
172 }
173 int parameterIndex = -1;
175 for (int index = 0; index < count; ++index)
177 parameterIndex = index;
178 break;
179 }
180 if (parameterIndex < 0)
182 Diagnostic::error(DiagnosticCode::NotFound, "unknown mesh modifier property",
183 node.id.value() + "." + key, {}, "procgen.meshModifier.editor"));
184 const std::string kind = procgen::MeshModifierGraph::operationParamKind(node.type, parameterIndex);
185 auto applyProperty = [&]() -> Result<void> {
186 if (kind == "float") {
187 if (const auto* number = value.getIf<double>();
188 number && std::isfinite(*number) && std::abs(*number) <= std::numeric_limits<float>::max())
189 return runtime.setNodeFloat(node.id.value(), key, static_cast<float>(*number));
190 } else if (kind == "int") {
191 if (const auto* integer = value.getIf<std::int64_t>();
192 integer && *integer >= std::numeric_limits<int>::min() &&
193 *integer <= std::numeric_limits<int>::max())
194 return runtime.setNodeInt(node.id.value(), key, static_cast<int>(*integer));
195 } else if (kind == "string") {
196 if (const auto* text = value.getIf<std::string>())
197 return runtime.setNodeString(node.id.value(), key, *text);
198 }
200 Diagnostic::error(DiagnosticCode::TypeMismatch, "mesh modifier property has the wrong type",
201 node.id.value() + "." + key, {}, "procgen.meshModifier.editor"));
202 };
203 auto applied = applyProperty();
204 if (!applied.ok()) return applied;
205 }
206 }
207 for (const auto& edge : graph.edges) {
208 const auto* from = findPin(graph, edge.from);
209 const auto* to = findPin(graph, edge.to);
210 if (!from || !to)
212 "mesh modifier edge references a missing pin",
213 edge.id.value(), {}, "procgen.meshModifier.editor"));
214 const auto* target = findNode(graph, to->node);
215 if (!target)
217 "mesh modifier edge target node is missing", edge.id.value(),
218 {}, "procgen.meshModifier.editor"));
219 auto connected = runtime.connect(from->node.value(), to->node.value(), inputIndex(*target, to->id));
220 if (!connected.ok()) return connected;
221 }
222 return Result<void>::success();
223}
224
225} // namespace
226
228 const GraphPinRecord& to) const {
230 decision.allowed = from.direction == GraphPinDirection::Output && to.direction == GraphPinDirection::Input &&
231 from.type == "mesh" && to.type == "mesh" && from.node != to.node;
232 if (!decision.allowed)
233 decision.diagnostics.push_back(
234 meshGraphDiagnostic("editor.mesh-modifier.invalid-connection", DiagnosticSeverity::Error,
235 "Mesh modifier connections require mesh output-to-input pins on distinct nodes"));
236 return decision;
237}
238
240 const std::string& operation) const {
242 return eve::editing::failed<GraphNodeRecord>(EditorStatus::Rejected,
243 RuleId("editor.mesh-modifier.unknown-operation"),
244 "Unknown mesh modifier operation");
246 node.id = id;
247 node.type = operation;
250 for (int i = 0; i < parameterCount; ++i) {
254 }
255 if (operation == "deform.splinePath" || operation == "mesh.splineTube" || operation == "mesh.splineRibbon" ||
256 operation == "mesh.splineExtrude") {
257 SplinePathDocument path(id.value() + ".splinePath");
258 properties["splinePath"] = path.snapshotValue();
259 }
260 if (operation == "mesh.splineExtrude") {
262 for (const auto& point :
263 std::array<std::array<double, 2>, 4>{{{{-0.5, -0.5}}, {{0.5, -0.5}}, {{0.5, 0.5}}, {{-0.5, 0.5}}}})
264 points.emplace_back(EditorValue::Object{{"x", EditorValue(point[0])}, {"y", EditorValue(point[1])}});
265 properties["splineProfile"] =
266 EditorValue(EditorValue::Object{{"closed", EditorValue(true)}, {"points", EditorValue(std::move(points))}});
267 }
268 node.properties = EditorValue(std::move(properties));
270 for (int input = 0; input < inputCount; ++input)
271 node.pins.push_back(
272 {GraphPinId(id.value() + ".in" + std::to_string(input)), id, "mesh", GraphPinDirection::Input});
273 node.pins.push_back({GraphPinId(id.value() + ".out"), id, "mesh", GraphPinDirection::Output});
274 return eve::editing::applied<GraphNodeRecord>(std::move(node));
275}
276
277MeshModifierGraphCompileResult MeshModifierGraphDomain::compile(const GraphDocumentData& graph) const {
279 result.documentRevision = graph.revision;
280 if (graph.domain != domain()) {
281 result.status = EditorStatus::Rejected;
282 result.diagnostics.push_back(meshGraphDiagnostic("editor.mesh-modifier.wrong-domain", DiagnosticSeverity::Error,
283 "Graph is not a procgen.meshModifier document"));
284 return result;
285 }
286 if (graph.schemaVersion != 1) {
287 result.status = EditorStatus::Unsupported;
288 result.diagnostics.push_back(meshGraphDiagnostic("editor.mesh-modifier.unsupported-schema",
289 DiagnosticSeverity::Error,
290 "Unsupported mesh modifier graph schema version"));
291 return result;
292 }
293 auto topology = validateTopology(graph);
294 if (!topology.ok()) {
295 result.status = EditorStatus::Rejected;
296 result.diagnostics.push_back(meshGraphDiagnostic("editor.mesh-modifier.invalid-topology",
297 DiagnosticSeverity::Error, topology.status().describe()));
298 return result;
299 }
301 auto populated = populateRuntime(graph, runtime);
302 if (!populated.ok()) {
303 result.status = EditorStatus::Rejected;
304 result.diagnostics.push_back(meshGraphDiagnostic("editor.mesh-modifier.compile-failed",
305 DiagnosticSeverity::Error, populated.status().describe()));
306 return result;
307 }
308 result.status = EditorStatus::Applied;
309 return result;
310}
311
312MeshModifierGraphPreviewResult MeshModifierGraphDomain::preview(const GraphDocumentData& graph,
313 const std::string& inputNode,
315 const std::string& outputNode) const {
317 result.documentRevision = graph.revision;
318 const auto compiled = compile(graph);
319 result.diagnostics = compiled.diagnostics;
320 if (compiled.status != EditorStatus::Applied) {
321 result.status = compiled.status;
322 return result;
323 }
325 auto populated = populateRuntime(graph, runtime);
326 if (!populated.ok()) {
327 result.status = EditorStatus::Failed;
328 result.diagnostics.push_back(meshGraphDiagnostic("editor.mesh-modifier.preview-compile",
329 DiagnosticSeverity::Error, populated.status().describe()));
330 return result;
331 }
332 if (!inputNode.empty()) {
333 auto bound = runtime.setNodeMesh(inputNode, input);
334 if (!bound.ok()) {
335 result.status = EditorStatus::Rejected;
336 result.diagnostics.push_back(meshGraphDiagnostic("editor.mesh-modifier.preview-input",
337 DiagnosticSeverity::Error, bound.status().describe()));
338 return result;
339 }
340 }
341 auto output = runtime.executeResult(outputNode);
342 if (!output.ok()) {
343 result.status = EditorStatus::Failed;
344 result.diagnostics.push_back(meshGraphDiagnostic("editor.mesh-modifier.preview-execution",
345 DiagnosticSeverity::Error, output.status().describe()));
346 return result;
347 }
348 result.mesh = std::move(output).takeValue();
349 result.segmentCount = runtime.compiledSegmentCount();
350 result.fusedOperationCount = runtime.fusedOperationCount();
351 result.status = EditorStatus::Applied;
352 return result;
353}
354
355} // namespace eve::procgen_editing
LogicalId target
ActionParameterOperation operation
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
std::string output
std::string from
EvpackChunkInput input
Definition Evpack.cpp:170
std::string message
std::uint32_t key
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string text
TokenKind kind
std::map< std::string, PropertyRule > properties
std::vector< BvhNode > nodes
std::map< std::string, std::vector< std::string > > graph
Definition Package.cpp:59
Topology topology
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
std::shared_ptr< const std::vector< glm::vec2 > > points
const RoadNode * node
const RoadEdge * edge
double number
bool found
std::uint32_t count
std::set< std::string > visiting
uint32_t index
std::vector< int > edges
glm::vec3 point
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
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
Deterministic owning value tree shared by authoring hosts.
std::vector< Value > Array
std::map< std::string, Value > Object
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
Deterministic CPU data-flow graph for procedural mesh deformation and composition.
static std::string operationParamDefault(std::string_view operation, int index)
Return encoded reflected parameter default.
static int operationParamCount(std::string_view operation)
Return reflected parameter count.
static std::string operationParamKind(std::string_view operation, int index)
Return reflected parameter kind: float, int, or string.
static std::string operationParamKey(std::string_view operation, int index)
Return reflected parameter key.
static int operationInputCount(std::string_view operation)
Return required mesh input count, or -1 for an unknown operation.
Result< GraphNodeRecord > makeNode(const GraphNodeId &id, const std::string &operation) const
Construct an editor node from the runtime operation catalogue.
GraphConnectionDecision canConnect(const GraphPinRecord &from, const GraphPinRecord &to) const override
Allow only mesh output-to-input edges between distinct nodes.
Owning spline authoring document with transactional operations and schema-versioned snapshots.
const char * defaultValue
eve::Severity DiagnosticSeverity
StrongId< RuleIdTag > RuleId
Definition EditingIds.h:60
StrongId< GraphPinIdTag > GraphPinId
Definition EditingIds.h:69
Result< T > applied(T value, std::vector< Diagnostic > diagnostics={})
Construct an Applied result with an owning payload and optional diagnostics.
StrongId< GraphNodeIdTag > GraphNodeId
Definition EditingIds.h:68
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
editing::GraphPinRecord GraphPinRecord
editing::GraphNodeRecord GraphNodeRecord
editing::Diagnostic EditorDiagnostic
Result< procgen::SplinePath > compileSplinePathSnapshot(const EditorValue &snapshot)
Compile a version-one spline snapshot for isolated graph preview.
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
@ TypeMismatch
A stable reference resolved to a different canonical domain type.
Result of domain-specific connection validation.
std::vector< Diagnostic > diagnostics
Immutable graph document value sent to a domain compiler.
Generic graph node independent of its domain.
Stable typed pin owned by a graph node.
Structural compilation result for a procgen.meshModifier graph document.
Revision-tagged owning preview from a mesh modifier graph document.