载入中...
搜索中...
未找到
PointGraph.cpp
浏览该文件的文档.
2
3#include "procgen/Biome.h"
6
7#include <algorithm>
8#include <chrono>
9#include <cmath>
10#include <functional>
11#include <limits>
12#include <sstream>
13
14namespace eve::procgen {
15namespace {
16
17struct OperationParam {
18 const char* key;
19 const char* kind;
20 const char* defaultValue;
21};
22struct OperationSpec {
23 const char* id;
24 int inputs;
25 std::vector<OperationParam> params;
26};
27
28const std::vector<OperationSpec>& operationSpecs() {
29 static const std::vector<OperationSpec> value = {
30 {"input", 0, {}},
31 {"get.spatial", 0, {{"binding", "string", ""}, {"spacing", "float", "1"}, {"seed", "int", "1"},
32 {"jitter", "float", "0"}}},
33 {"get.landscape", 0, {{"binding", "string", ""}, {"spacing", "float", "1"}, {"seed", "int", "1"},
34 {"jitter", "float", "0"}}},
35 {"get.spline", 0, {{"binding", "string", ""}, {"spacing", "float", "1"}, {"seed", "int", "1"},
36 {"jitter", "float", "0"}}},
37 {"get.points", 0, {{"binding", "string", ""}}},
38 {"get.actor", 0, {{"binding", "string", ""}}},
39 {"spatial.sample", 0, {{"binding", "string", ""}, {"spacing", "float", "1"}, {"seed", "int", "1"}, {"jitter", "float", "0"}}},
40 {"mesh.sample", 0, {{"binding", "string", ""}, {"spacing", "float", "1"}, {"seed", "int", "1"}, {"jitter", "float", "0"}}},
41 {"grid.sample", 0, {{"width", "int", "8"}, {"depth", "int", "8"}, {"spacing", "float", "1"},
42 {"seed", "int", "1"}, {"jitter", "float", "0"},
43 {"originX", "float", "0"}, {"originY", "float", "0"}, {"originZ", "float", "0"}}},
44 {"poisson.sample", 0, {{"width", "int", "32"}, {"depth", "int", "32"}, {"radius", "float", "2"},
45 {"seed", "int", "1"}, {"maxPoints", "int", "1000"},
46 {"originX", "float", "0"}, {"originY", "float", "0"}, {"originZ", "float", "0"}}},
47 {"spatial.filter", 1, {{"binding", "string", ""}, {"invert", "bool", "false"}}},
48 {"spatial.project", 1, {{"binding", "string", ""}}},
49 {"spline.sample", 1, {{"spacing", "float", "1"}, {"seed", "int", "1"}, {"lateralJitter", "float", "0"}}},
50 {"spline.filter.distance", 2, {{"minDistance", "float", "0"}, {"maxDistance", "float", "1"}}},
51 {"biome.generate", 0, {{"binding", "string", ""}, {"spacing", "float", "1"}, {"seed", "int", "1"},
52 {"jitter", "float", "0"}}},
53 {"grammar.generate", 1, {{"grammar", "string", ""}, {"seed", "int", "1"},
54 {"acceptIncomplete", "bool", "true"}}},
55 {"merge", 2, {}},
56 {"points.union", 2, {}},
57 {"points.intersect", 2, {}},
58 {"points.difference", 2, {}},
59 {"copy.points", 2, {{"maxPoints", "int", "100000"},
60 {"inheritTargetAttributes", "bool", "true"}}},
61 {"density.remap", 1, {{"inputMin", "float", "0"}, {"inputMax", "float", "1"},
62 {"outputMin", "float", "0"}, {"outputMax", "float", "1"},
63 {"clamp", "bool", "true"}}},
64 {"density.from.normal", 1, {{"minDegrees", "float", "0"}, {"maxDegrees", "float", "90"},
65 {"outputMin", "float", "0"}, {"outputMax", "float", "1"},
66 {"invert", "bool", "false"}}},
67 {"landscape.sample", 1, {{"binding", "string", ""}, {"project", "bool", "false"},
68 {"attribute", "string", "$Density"},
69 {"inputMin", "float", "0"}, {"inputMax", "float", "0"},
70 {"outputMin", "float", "0"}, {"outputMax", "float", "1"},
71 {"clamp", "bool", "true"}, {"invert", "bool", "false"}}},
72 {"texture.sample", 1, {{"binding", "string", ""}, {"attribute", "string", "$Density"},
73 {"inputMin", "float", "0"}, {"inputMax", "float", "1"},
74 {"outputMin", "float", "0"}, {"outputMax", "float", "1"},
75 {"clamp", "bool", "true"}, {"invert", "bool", "false"}}},
76 {"attribute.math.float", 1, {{"attribute", "string", ""},
77 {"outputAttribute", "string", ""},
78 {"operation", "string", "multiply"},
79 {"operand", "float", "1"},
80 {"defaultValue", "float", "0"}}},
81 {"transform", 1, {{"x", "float", "0"}, {"y", "float", "0"}, {"z", "float", "0"},
82 {"yaw", "float", "0"}, {"scaleX", "float", "1"},
83 {"scaleY", "float", "1"}, {"scaleZ", "float", "1"}}},
84 {"bounds.modify", 1, {{"scaleX", "float", "1"}, {"scaleY", "float", "1"},
85 {"scaleZ", "float", "1"}, {"padX", "float", "0"},
86 {"padY", "float", "0"}, {"padZ", "float", "0"}}},
87 {"filter.float", 1, {{"attribute", "string", ""}, {"min", "float", "0"},
88 {"max", "float", "1"}, {"invert", "bool", "false"}}},
89 {"filter.slope", 1, {{"minDegrees", "float", "0"},
90 {"maxDegrees", "float", "90"}}},
91 {"filter.string", 1, {{"attribute", "string", ""}, {"value", "string", ""},
92 {"invert", "bool", "false"}}},
93 {"attribute.set.float", 1, {{"attribute", "string", ""}, {"value", "float", "0"}}},
94 {"attribute.set.string", 1, {{"attribute", "string", ""}, {"value", "string", ""}}},
95 {"attribute.set.int", 1, {{"attribute", "string", ""}, {"value", "int", "0"}}},
96 {"attribute.set.bool", 1, {{"attribute", "string", ""}, {"value", "bool", "false"}}},
97 {"attribute.set.vector", 1, {{"attribute", "string", ""}, {"x", "float", "0"},
98 {"y", "float", "0"}, {"z", "float", "0"}}},
99 {"attribute.copy", 1, {{"source", "string", ""}, {"target", "string", ""}}},
100 {"attribute.rename", 1, {{"from", "string", ""}, {"to", "string", ""}}},
101 {"attribute.delete", 1, {{"attribute", "string", ""}}},
102 {"attribute.transfer", 2, {{"attribute", "string", ""}, {"outputAttribute", "string", ""},
103 {"mode", "string", "nearest"}}},
104 {"attribute.compare.float", 1, {{"attribute", "string", ""}, {"comparison", "string", "gt"},
105 {"operand", "float", "0"}, {"outputAttribute", "string", "match"},
106 {"defaultValue", "float", "0"}}},
107 {"attribute.select.float", 1, {{"conditionAttribute", "string", ""},
108 {"trueAttribute", "string", ""},
109 {"falseAttribute", "string", ""},
110 {"outputAttribute", "string", ""},
111 {"trueDefault", "float", "0"},
112 {"falseDefault", "float", "0"}}},
113 {"filter.int", 1, {{"attribute", "string", ""}, {"min", "int", "0"},
114 {"max", "int", "0"}, {"invert", "bool", "false"}}},
115 {"filter.bool", 1, {{"attribute", "string", ""}, {"value", "bool", "true"},
116 {"invert", "bool", "false"}}},
117 {"attribute.partition", 1, {{"attribute", "string", ""}, {"outputAttribute", "string", "partition"},
118 {"mode", "string", "value"}}},
119 {"attribute.noise.float", 1, {{"attribute", "string", "noise"}, {"seed", "int", "1"},
120 {"frequency", "float", "1"}, {"amplitude", "float", "1"},
121 {"offset", "float", "0"}}},
122 {"attribute.math.int", 1, {{"attribute", "string", ""}, {"outputAttribute", "string", ""},
123 {"operation", "string", "add"}, {"operand", "int", "1"},
124 {"defaultValue", "int", "0"}}},
125 {"attribute.math.vector", 1, {{"attribute", "string", ""}, {"outputAttribute", "string", ""},
126 {"operation", "string", "scale"}, {"operandX", "float", "1"},
127 {"operandY", "float", "1"}, {"operandZ", "float", "1"},
128 {"defaultX", "float", "0"}, {"defaultY", "float", "0"},
129 {"defaultZ", "float", "0"}}},
130 {"attribute.set.data.float", 1, {{"attribute", "string", ""}, {"value", "float", "0"}}},
131 {"attribute.set.data.int", 1, {{"attribute", "string", ""}, {"value", "int", "0"}}},
132 {"attribute.set.data.string", 1, {{"attribute", "string", ""}, {"value", "string", ""}}},
133 {"spawn.mesh", 1, {{"seed", "int", "1"}, {"attribute", "string", "mesh"},
134 {"mesh0", "string", ""}, {"weight0", "float", "1"},
135 {"mesh1", "string", ""}, {"weight1", "float", "0"},
136 {"mesh2", "string", ""}, {"weight2", "float", "0"},
137 {"mesh3", "string", ""}, {"weight3", "float", "0"}}},
138 {"density.cull", 1, {{"seed", "int", "1"}, {"multiplier", "float", "1"}}},
139 {"self.prune", 1, {{"radius", "float", "1"}}},
140 {"jitter", 1, {{"seed", "int", "1"}, {"x", "float", "0"}, {"z", "float", "0"}}},
141 {"debug.disable", 1, {{"enabled", "bool", "true"}}},
142 {"debug.inspect", 1, {}},
143 {"branch", 2, {{"condition", "bool", "false"}}},
144 {"subgraph", 1, {}},
145 };
146 return value;
147}
148
149bool operationKnown(const std::string& operation) {
150 const auto& values = operationSpecs();
151 return std::any_of(values.begin(), values.end(),
152 [&](const OperationSpec& value) { return operation == value.id; });
153}
154
155const OperationSpec* operationSpec(const std::string& operation) {
156 const auto& values = operationSpecs();
157 const auto found = std::find_if(values.begin(), values.end(),
158 [&](const OperationSpec& value) { return operation == value.id; });
159 return found == values.end() ? nullptr : &*found;
160}
161
162const OperationParam* operationParam(const OperationSpec* spec, const std::string& key) {
163 if (!spec) return nullptr;
164 const auto found = std::find_if(spec->params.begin(), spec->params.end(),
165 [&](const OperationParam& value) { return key == value.key; });
166 return found == spec->params.end() ? nullptr : &*found;
167}
168
169} // namespace
170
171PointGraph::PointGraph() : pointCompute_(std::make_shared<PointCompute>()) {}
172
173bool PointGraph::addNode(const std::string& id, const std::string& operation) {
174 if (id.empty() || !operationKnown(operation) || nodes_.find(id) != nodes_.end()) return false;
175 nodes_[id].id = id;
176 nodes_[id].operation = operation;
177 nodeOrder_.push_back(id);
178 invalidateTopology(id);
179 invalidateFrom(id);
180 return true;
181}
182
183bool PointGraph::removeNode(const std::string& id) {
184 if (nodes_.find(id) == nodes_.end()) return false;
185 invalidateFrom(id);
186 nodes_.erase(id);
187 nodeOrder_.erase(std::remove(nodeOrder_.begin(), nodeOrder_.end(), id), nodeOrder_.end());
188 for (auto it = parameterOrder_.begin(); it != parameterOrder_.end();) {
189 const auto binding = parameters_.find(*it);
190 if (binding != parameters_.end() && binding->second.nodeId == id) {
191 floatOverrides_.erase(*it);
192 intOverrides_.erase(*it);
193 stringOverrides_.erase(*it);
194 parameters_.erase(binding);
195 it = parameterOrder_.erase(it);
196 } else {
197 ++it;
198 }
199 }
200 for (auto& [otherId, node] : nodes_)
201 for (auto& input : node.inputs)
202 if (input == id) input.clear();
203 invalidateTopology(id);
204 return true;
205}
206
207bool PointGraph::hasNode(const std::string& id) const { return nodes_.find(id) != nodes_.end(); }
208int PointGraph::getNodeCount() const { return int(nodeOrder_.size()); }
209std::string PointGraph::getNodeId(int index) const {
210 return index >= 0 && index < int(nodeOrder_.size()) ? nodeOrder_[size_t(index)] : std::string();
211}
212std::string PointGraph::getNodeOperation(const std::string& id) const {
213 const auto found = nodes_.find(id);
214 return found == nodes_.end() ? std::string() : found->second.operation;
215}
216
217bool PointGraph::connect(const std::string& fromId, const std::string& toId, int inputIndex) {
218 const auto from = nodes_.find(fromId);
219 const auto to = nodes_.find(toId);
220 const auto* spec = to == nodes_.end() ? nullptr : operationSpec(to->second.operation);
221 if (from == nodes_.end() || to == nodes_.end() || !spec || inputIndex < 0 ||
222 inputIndex >= spec->inputs || fromId == toId)
223 return false;
224 to->second.inputs[inputIndex] = fromId;
225 invalidateTopology(toId);
226 invalidateFrom(toId);
227 return true;
228}
229
230bool PointGraph::disconnect(const std::string& toId, int inputIndex) {
231 const auto to = nodes_.find(toId);
232 if (to == nodes_.end() || inputIndex < 0 || inputIndex > 1 ||
233 to->second.inputs[inputIndex].empty())
234 return false;
235 to->second.inputs[inputIndex].clear();
236 invalidateTopology(toId);
237 invalidateFrom(toId);
238 return true;
239}
240
241std::string PointGraph::getInputNode(const std::string& nodeId, int inputIndex) const {
242 const auto found = nodes_.find(nodeId);
243 return found != nodes_.end() && inputIndex >= 0 && inputIndex <= 1
244 ? found->second.inputs[inputIndex]
245 : std::string();
246}
247
248bool PointGraph::setNodePoints(const std::string& id, PointSet* points) {
249 const auto found = nodes_.find(id);
250 if (found == nodes_.end() || !points) return false;
251 if (maxNodeOutputPoints_ > 0 && points->getCount() > maxNodeOutputPoints_) {
252 error_ = "input exceeds node point budget at node: " + id;
253 return false;
254 }
255 found->second.points = *points;
256 found->second.hasPoints = true;
257 invalidateFrom(id);
258 return true;
259}
260
261bool PointGraph::setNodeSpatial(const std::string& id, SpatialData* spatial) {
262 const auto found = nodes_.find(id);
263 if (found == nodes_.end() || !spatial) return false;
264 found->second.spatial = std::make_shared<SpatialData>(*spatial);
265 invalidateFrom(id);
266 return true;
267}
268
270 if (name.empty() || !spatial)
271 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "binding name and spatial data are required", "binding"));
272 auto& binding = bindings_[name];
273 if (std::find(bindingOrder_.begin(), bindingOrder_.end(), name) == bindingOrder_.end())
274 bindingOrder_.push_back(name);
275 binding.spatial = std::make_shared<SpatialData>(*spatial);
276 binding.points.reset();
277 binding.isSpatial = true;
278 invalidate();
279 return Result<void>::success();
280}
281
283 if (name.empty() || !points)
285 "binding name and point set are required", "binding"));
286 auto& binding = bindings_[name];
287 if (std::find(bindingOrder_.begin(), bindingOrder_.end(), name) == bindingOrder_.end())
288 bindingOrder_.push_back(name);
289 binding.points = std::make_shared<PointSet>(*points);
290 binding.spatial.reset();
291 binding.isSpatial = false;
292 invalidate();
293 return Result<void>::success();
294}
295
297 const auto found = bindings_.find(name);
298 if (found == bindings_.end())
299 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "binding not found: " + name, "binding"));
300 bindings_.erase(found);
301 bindingOrder_.erase(std::remove(bindingOrder_.begin(), bindingOrder_.end(), name), bindingOrder_.end());
302 invalidate();
303 return Result<void>::success();
304}
305
307 if (bindings_.empty()) return;
308 bindings_.clear();
309 bindingOrder_.clear();
310 invalidate();
311}
312
313int PointGraph::getBindingCount() const { return int(bindingOrder_.size()); }
314
315std::string PointGraph::getBindingName(int index) const {
316 return index >= 0 && index < int(bindingOrder_.size()) ? bindingOrder_[size_t(index)] : std::string();
317}
318
319std::string PointGraph::getBindingType(const std::string& name) const {
320 const auto found = bindings_.find(name);
321 if (found == bindings_.end()) return {};
322 return found->second.isSpatial ? "spatial" : "points";
323}
324
325Result<std::shared_ptr<SpatialData>> PointGraph::resolveNodeSpatial(const Node& node) const {
326 if (node.spatial) return Result<std::shared_ptr<SpatialData>>::success(node.spatial);
327 const std::string binding = stringValue(node, "binding", {});
328 if (binding.empty())
330 Diagnostic::error(DiagnosticCode::InvalidArgument, "node has no spatial data", node.id));
331 const auto found = bindings_.find(binding);
332 if (found == bindings_.end() || !found->second.isSpatial || !found->second.spatial)
334 DiagnosticCode::NotFound, "spatial binding not found: " + binding, node.id));
335 return Result<std::shared_ptr<SpatialData>>::success(found->second.spatial);
336}
337
338Result<std::shared_ptr<PointSet>> PointGraph::resolveBindingPoints(const std::string& name) const {
339 const auto found = bindings_.find(name);
340 if (found == bindings_.end() || found->second.isSpatial || !found->second.points)
341 return Result<std::shared_ptr<PointSet>>::failure(
342 Diagnostic::error(DiagnosticCode::NotFound, "points binding not found: " + name, name));
343 return Result<std::shared_ptr<PointSet>>::success(found->second.points);
344}
345
346Result<PointGraphInspectReport> PointGraph::inspectNode(const std::string& id, int sampleLimit) const {
348 report.nodeId = id;
349 const auto found = nodes_.find(id);
350 if (found == nodes_.end())
352 Diagnostic::error(DiagnosticCode::NotFound, "unknown node: " + id, id));
353 if (!found->second.cacheValid)
355 Diagnostic::error(DiagnosticCode::Failed, "node output is not cached; execute first: " + id, id));
356 const PointSet& points = found->second.cache;
357 report.pointCount = points.getCount();
358
359 const auto addBuiltin = [&](const char* name) {
361 info.name = name;
362 info.type = "builtin";
363 info.domain = "builtin";
364 report.columns.push_back(std::move(info));
365 };
366 addBuiltin("$Density");
367 addBuiltin("$Position.X");
368 addBuiltin("$Position.Y");
369 addBuiltin("$Position.Z");
370 addBuiltin("$Normal.X");
371 addBuiltin("$Normal.Y");
372 addBuiltin("$Normal.Z");
373 addBuiltin("$Rotation.Yaw");
374 addBuiltin("$Scale.X");
375 addBuiltin("$Seed");
376
377 const AttributeTable& table = points.attributes();
378 for (std::size_t i = 0; i < table.columnCount(); ++i) {
380 info.name = std::string(table.columnName(i));
381 info.domain = "elements";
382 const auto type = table.typeOf(info.name);
383 if (!type) info.type = "unknown";
384 else if (*type == ProcgenAttributeType::Float) info.type = "float";
385 else if (*type == ProcgenAttributeType::Int) info.type = "int";
386 else if (*type == ProcgenAttributeType::Bool) info.type = "bool";
387 else if (*type == ProcgenAttributeType::Vector) info.type = "vector";
388 else info.type = "string";
389 report.columns.push_back(std::move(info));
390 }
391 const AttributeTable& data = points.dataAttributes();
392 for (std::size_t i = 0; i < data.columnCount(); ++i) {
394 info.name = std::string(data.columnName(i));
395 info.domain = "data";
396 const auto type = data.typeOf(info.name);
397 if (!type) info.type = "unknown";
398 else if (*type == ProcgenAttributeType::Float) info.type = "float";
399 else if (*type == ProcgenAttributeType::Int) info.type = "int";
400 else if (*type == ProcgenAttributeType::Bool) info.type = "bool";
401 else if (*type == ProcgenAttributeType::Vector) info.type = "vector";
402 else info.type = "string";
403 report.columns.push_back(std::move(info));
404 }
405
406 if (sampleLimit > 0 && report.pointCount > 0) {
407 report.sampleCount = std::min(sampleLimit, report.pointCount);
408 report.sampleFloats.reserve(size_t(report.sampleCount) * 4);
409 for (int i = 0; i < report.sampleCount; ++i) {
410 report.sampleFloats.push_back(points.getDensity(i));
411 report.sampleFloats.push_back(points.points()[size_t(i)].x);
412 report.sampleFloats.push_back(points.points()[size_t(i)].y);
413 report.sampleFloats.push_back(points.points()[size_t(i)].z);
414 }
415 }
416 return Result<PointGraphInspectReport>::success(std::move(report));
417}
418
419
420bool PointGraph::setNodeBiomeRules(const std::string& id, BiomeRules* rules) {
421 const auto found = nodes_.find(id);
422 if (found == nodes_.end() || found->second.operation != "biome.generate" || !rules)
423 return false;
424 found->second.biomeRules = std::make_shared<BiomeRules>(*rules);
425 invalidateFrom(id);
426 return true;
427}
428
429bool PointGraph::setNodeShapeGrammar(const std::string& id, ShapeGrammar* grammar) {
430 const auto found = nodes_.find(id);
431 if (found == nodes_.end() || found->second.operation != "grammar.generate" || !grammar)
432 return false;
433 found->second.shapeGrammar = std::make_shared<ShapeGrammar>(*grammar);
434 invalidateFrom(id);
435 return true;
436}
437
438bool PointGraph::setNodeFloat(const std::string& id, const std::string& key, float value) {
439 const auto found = nodes_.find(id);
440 const auto* spec = found == nodes_.end() ? nullptr : operationSpec(found->second.operation);
441 const auto* parameter = operationParam(spec, key);
442 if (!parameter || std::string(parameter->kind) != "float") return false;
443 found->second.floats[key] = value;
444 invalidateFrom(id);
445 return true;
446}
447
448bool PointGraph::setNodeInt(const std::string& id, const std::string& key, int value) {
449 const auto found = nodes_.find(id);
450 const auto* spec = found == nodes_.end() ? nullptr : operationSpec(found->second.operation);
451 const auto* parameter = operationParam(spec, key);
452 if (!parameter ||
453 (std::string(parameter->kind) != "int" && std::string(parameter->kind) != "bool"))
454 return false;
455 found->second.ints[key] = value;
456 invalidateFrom(id);
457 return true;
458}
459
460bool PointGraph::setNodeString(const std::string& id, const std::string& key,
461 const std::string& value) {
462 const auto found = nodes_.find(id);
463 const auto* spec = found == nodes_.end() ? nullptr : operationSpec(found->second.operation);
464 const auto* parameter = operationParam(spec, key);
465 if (!parameter || std::string(parameter->kind) != "string") return false;
466 found->second.strings[key] = value;
467 invalidateFrom(id);
468 return true;
469}
470
471bool PointGraph::exposeParameter(const std::string& name, const std::string& nodeId,
472 const std::string& key) {
473 const auto node = nodes_.find(nodeId);
474 if (name.empty() || key.empty() || node == nodes_.end() || parameters_.count(name) != 0)
475 return false;
476 const OperationSpec* spec = operationSpec(node->second.operation);
477 if (!spec) return false;
478 const auto* parameter = operationParam(spec, key);
479 if (!parameter) return false;
480 for (const auto& [existingName, binding] : parameters_)
481 if (binding.nodeId == nodeId && binding.key == key) return false;
482 parameters_.emplace(name, ParameterBinding{nodeId, key, parameter->kind});
483 parameterOrder_.push_back(name);
484 ++revision_;
485 return true;
486}
487
488int PointGraph::getParameterCount() const { return int(parameterOrder_.size()); }
489std::string PointGraph::getParameterName(int index) const {
490 return index >= 0 && index < int(parameterOrder_.size()) ? parameterOrder_[size_t(index)]
491 : std::string();
492}
493std::string PointGraph::getParameterKind(const std::string& name) const {
494 const auto found = parameters_.find(name);
495 return found == parameters_.end() ? std::string() : found->second.kind;
496}
497bool PointGraph::hasParameterOverride(const std::string& name) const {
498 return floatOverrides_.count(name) != 0 || intOverrides_.count(name) != 0 ||
499 stringOverrides_.count(name) != 0;
500}
501bool PointGraph::setParameterFloat(const std::string& name, float value) {
502 const auto found = parameters_.find(name);
503 if (found == parameters_.end() || found->second.kind != "float") return false;
504 floatOverrides_[name] = value;
505 invalidateFrom(found->second.nodeId);
506 return true;
507}
508bool PointGraph::setParameterInt(const std::string& name, int value) {
509 const auto found = parameters_.find(name);
510 if (found == parameters_.end() || (found->second.kind != "int" &&
511 found->second.kind != "bool"))
512 return false;
513 intOverrides_[name] = value;
514 invalidateFrom(found->second.nodeId);
515 return true;
516}
517bool PointGraph::setParameterString(const std::string& name, const std::string& value) {
518 const auto found = parameters_.find(name);
519 if (found == parameters_.end() || found->second.kind != "string") return false;
520 stringOverrides_[name] = value;
521 invalidateFrom(found->second.nodeId);
522 return true;
523}
524float PointGraph::getParameterFloat(const std::string& name, float fallback) const {
525 const auto found = parameters_.find(name);
526 if (found == parameters_.end() || found->second.kind != "float") return fallback;
527 return floatValue(nodes_.at(found->second.nodeId), found->second.key, fallback);
528}
529int PointGraph::getParameterInt(const std::string& name, int fallback) const {
530 const auto found = parameters_.find(name);
531 if (found == parameters_.end() || (found->second.kind != "int" &&
532 found->second.kind != "bool"))
533 return fallback;
534 return intValue(nodes_.at(found->second.nodeId), found->second.key, fallback);
535}
536std::string PointGraph::getParameterString(const std::string& name,
537 const std::string& fallback) const {
538 const auto found = parameters_.find(name);
539 if (found == parameters_.end() || found->second.kind != "string") return fallback;
540 return stringValue(nodes_.at(found->second.nodeId), found->second.key, fallback);
541}
542bool PointGraph::clearParameterOverride(const std::string& name) {
543 const auto found = parameters_.find(name);
544 if (found == parameters_.end() || !hasParameterOverride(name)) return false;
545 floatOverrides_.erase(name);
546 intOverrides_.erase(name);
547 stringOverrides_.erase(name);
548 invalidateFrom(found->second.nodeId);
549 return true;
550}
551
552bool PointGraph::setNodeSubgraph(const std::string& id, PointGraph* graph,
553 const std::string& inputNode, const std::string& outputNode) {
554 const auto found = nodes_.find(id);
555 if (found == nodes_.end() || found->second.operation != "subgraph" || !graph ||
556 !graph->hasNode(inputNode) || !graph->hasNode(outputNode))
557 return false;
558 found->second.subgraph = std::make_shared<PointGraph>(*graph);
559 found->second.subgraphInput = inputNode;
560 found->second.subgraphOutput = outputNode;
561 invalidateFrom(id);
562 return true;
563}
564
565PointSet* PointGraph::execute(const std::string& outputId) {
566 auto result = executeResult(outputId);
567 error_ = result.ok() ? std::string{} : result.status().describe();
568 if (!result.ok()) return nullptr;
569 return new PointSet(std::move(result).takeValue());
570}
571
572Result<PointSet> PointGraph::executeResult(std::string_view outputId) {
573 const std::string output(outputId);
574 metrics_.clear();
575 cacheHitCount_ = 0;
576 evaluatedNodes_ = 0;
577 lastCancelled_ = false;
578 computeFallbackReason_.clear();
579 ++executionCount_;
580 if (cancelRequested_) {
581 lastCancelled_ = true;
583 Diagnostic::error(DiagnosticCode::Cancelled, "execution cancelled", output, {}, "procgen.pointGraph"));
584 }
585 auto compiled = compileExecutionPlan(output);
586 if (!compiled.ok()) return Result<PointSet>::failure(compiled.status());
587 std::unordered_map<std::string, int> states;
588 auto result = evaluate(output, states);
589 if (!result.ok()) {
590 lastCancelled_ = result.status().code() == StatusCode::Cancelled;
591 return Result<PointSet>::failure(result.status());
592 }
593 return Result<PointSet>::success(result.value().get());
594}
595
596void PointGraph::setExecutionNodeBudget(int nodes) { executionNodeBudget_ = std::max(0, nodes); }
597int PointGraph::getExecutionNodeBudget() const { return executionNodeBudget_; }
599 const int clamped = std::max(0, points);
600 if (maxNodeOutputPoints_ == clamped) return;
601 maxNodeOutputPoints_ = clamped;
602 clearCache();
603}
604int PointGraph::getMaxNodeOutputPoints() const { return maxNodeOutputPoints_; }
605bool PointGraph::setComputePolicy(const std::string& policy) {
606 if (policy != "auto" && policy != "gpu" && policy != "cpu") return false;
607 if (computePolicy_ == policy) return true;
608 computePolicy_ = policy;
609 clearCache();
610 return true;
611}
612std::string PointGraph::getComputePolicy() const { return computePolicy_; }
614 const int clamped = std::max(0, points);
615 if (computeMinimumPoints_ == clamped) return;
616 computeMinimumPoints_ = clamped;
617 clearCache();
618}
619int PointGraph::getComputeMinimumPoints() const { return computeMinimumPoints_; }
620void PointGraph::requestCancel() { cancelRequested_ = true; }
622 cancelRequested_ = false;
623 lastCancelled_ = false;
624}
625bool PointGraph::wasCancelled() const { return lastCancelled_; }
626
628 auto result = validateResult();
629 error_ = result.ok() ? std::string{} : result.status().describe();
630 return result.ok();
631}
632
634 std::unordered_map<std::string, int> states;
635 for (const auto& id : nodeOrder_) {
636 auto result = validateNode(id, states);
637 if (!result.ok()) return result;
638 }
639 return Result<void>::success();
640}
641
642std::string PointGraph::getError() const { return error_; }
644 for (auto& [id, node] : nodes_) {
645 node.cacheValid = false;
646 node.deferredTransformValid = false;
647 }
648 metrics_.clear();
649}
650int PointGraph::getExecutionCount() const { return executionCount_; }
651int PointGraph::getCacheHitCount() const { return cacheHitCount_; }
652int PointGraph::getCompiledSegmentCount() const { return executionPlan_ ? int(executionPlan_->segments.size()) : 0; }
653uint64_t PointGraph::getExecutionPlanBuildCount() const { return executionPlanBuildCount_; }
654uint64_t PointGraph::getRevision() const { return revision_; }
655int PointGraph::getMetricCount() const { return int(metrics_.size()); }
656std::string PointGraph::getMetricNodeId(int index) const {
657 return index >= 0 && index < int(metrics_.size()) ? metrics_[size_t(index)].id : std::string();
658}
660 return index >= 0 && index < int(metrics_.size()) ? metrics_[size_t(index)].outputCount : 0;
661}
663 return index >= 0 && index < int(metrics_.size()) ? metrics_[size_t(index)].milliseconds : 0.f;
664}
666 return index >= 0 && index < int(metrics_.size()) && metrics_[size_t(index)].cacheHit;
667}
668std::string PointGraph::getMetricBackend(int index) const {
669 return index >= 0 && index < int(metrics_.size()) ? metrics_[size_t(index)].backend : std::string();
670}
671std::string PointGraph::getComputeFallbackReason() const { return computeFallbackReason_; }
672uint64_t PointGraph::getComputeUploadCount() const { return pointCompute_->getUploadCount(); }
673uint64_t PointGraph::getComputeDispatchCount() const { return pointCompute_->getDispatchCount(); }
674uint64_t PointGraph::getComputeReadbackCount() const { return pointCompute_->getReadbackCount(); }
675uint64_t PointGraph::getComputeBufferReuseCount() const { return pointCompute_->getBufferReuseCount(); }
676uint64_t PointGraph::getComputePeakBufferBytes() const { return pointCompute_->getPeakBufferBytes(); }
677int PointGraph::getLastFusedTransformCount() const { return pointCompute_->getLastFusedTransformCount(); }
679 return index >= 0 && index < int(metrics_.size()) ? metrics_[size_t(index)].minX : 0.f;
680}
682 return index >= 0 && index < int(metrics_.size()) ? metrics_[size_t(index)].minY : 0.f;
683}
685 return index >= 0 && index < int(metrics_.size()) ? metrics_[size_t(index)].minZ : 0.f;
686}
688 return index >= 0 && index < int(metrics_.size()) ? metrics_[size_t(index)].maxX : 0.f;
689}
691 return index >= 0 && index < int(metrics_.size()) ? metrics_[size_t(index)].maxY : 0.f;
692}
694 return index >= 0 && index < int(metrics_.size()) ? metrics_[size_t(index)].maxZ : 0.f;
695}
697 return index >= 0 && index < int(metrics_.size())
698 ? metrics_[size_t(index)].averageDensity
699 : 0.f;
700}
701PointSet* PointGraph::getNodeOutput(const std::string& id) const { return materializeNodeOutput(id); }
702
703PointSet* PointGraph::materializeNodeOutput(const std::string& id) const {
704 const auto found = nodes_.find(id);
705 if (found == nodes_.end()) return nullptr;
706 const Node& node = found->second;
707 if (node.cacheValid) return new PointSet(node.cache);
708 if (!node.deferredTransformValid || node.operation != "transform" || node.inputs[0].empty()) return nullptr;
709 std::unique_ptr<PointSet> input(materializeNodeOutput(node.inputs[0]));
710 if (!input) return nullptr;
711 return new PointSet(transformPointSet(*input, floatValue(node, "x", 0.f), floatValue(node, "y", 0.f),
712 floatValue(node, "z", 0.f), floatValue(node, "yaw", 0.f),
713 floatValue(node, "scaleX", 1.f), floatValue(node, "scaleY", 1.f),
714 floatValue(node, "scaleZ", 1.f)));
715}
716
717std::string PointGraph::debugReport() const {
718 std::ostringstream out;
719 out << "nodes=" << nodes_.size() << " executions=" << executionCount_ << " cacheHits=" << cacheHitCount_
720 << " planBuilds=" << executionPlanBuildCount_ << " segments=" << getCompiledSegmentCount();
721 for (const auto& metric : metrics_)
722 out << "\n"
723 << metric.id << " count=" << metric.outputCount << " ms=" << metric.milliseconds
724 << " backend=" << metric.backend << (metric.cacheHit ? " cached" : "");
725 if (!error_.empty()) out << "\nerror=" << error_;
726 return out.str();
727}
728
729int PointGraph::getOperationCount() { return int(operationSpecs().size()); }
731 return index >= 0 && index < int(operationSpecs().size())
732 ? operationSpecs()[size_t(index)].id
733 : std::string();
734}
736 const auto* spec = operationSpec(operation);
737 return spec ? spec->inputs : -1;
738}
740 const auto* spec = operationSpec(operation);
741 return spec ? int(spec->params.size()) : 0;
742}
743std::string PointGraph::getOperationParamKey(const std::string& operation, int index) {
744 const auto* spec = operationSpec(operation);
745 return spec && index >= 0 && index < int(spec->params.size())
746 ? spec->params[size_t(index)].key
747 : std::string();
748}
749std::string PointGraph::getOperationParamKind(const std::string& operation, int index) {
750 const auto* spec = operationSpec(operation);
751 return spec && index >= 0 && index < int(spec->params.size())
752 ? spec->params[size_t(index)].kind
753 : std::string();
754}
755std::string PointGraph::getOperationParamDefault(const std::string& operation, int index) {
756 const auto* spec = operationSpec(operation);
757 return spec && index >= 0 && index < int(spec->params.size())
758 ? spec->params[size_t(index)].defaultValue
759 : std::string();
760}
761
762void PointGraph::invalidate() { clearCache(); }
763
764void PointGraph::invalidateFrom(const std::string& id) {
765 ++revision_;
766 std::vector<std::string> dirty{id};
767 std::unordered_map<std::string, bool> visited;
768 for (size_t cursor = 0; cursor < dirty.size(); ++cursor) {
769 const std::string current = dirty[cursor];
770 if (visited[current]) continue;
771 visited[current] = true;
772 const auto found = nodes_.find(current);
773 if (found != nodes_.end()) {
774 found->second.cacheValid = false;
775 found->second.deferredTransformValid = false;
776 }
777 for (const auto& [candidateId, candidate] : nodes_) {
778 if (candidate.inputs[0] == current || candidate.inputs[1] == current)
779 dirty.push_back(candidateId);
780 }
781 }
782 metrics_.clear();
783}
784float PointGraph::floatValue(const Node& node, const std::string& key, float fallback) const {
785 for (const auto& name : parameterOrder_) {
786 const auto& binding = parameters_.at(name);
787 if (binding.nodeId == node.id && binding.key == key) {
788 const auto overridden = floatOverrides_.find(name);
789 if (overridden != floatOverrides_.end()) return overridden->second;
790 }
791 }
792 const auto found = node.floats.find(key);
793 return found == node.floats.end() ? fallback : found->second;
794}
795int PointGraph::intValue(const Node& node, const std::string& key, int fallback) const {
796 for (const auto& name : parameterOrder_) {
797 const auto& binding = parameters_.at(name);
798 if (binding.nodeId == node.id && binding.key == key) {
799 const auto overridden = intOverrides_.find(name);
800 if (overridden != intOverrides_.end()) return overridden->second;
801 }
802 }
803 const auto found = node.ints.find(key);
804 return found == node.ints.end() ? fallback : found->second;
805}
806std::string PointGraph::stringValue(const Node& node, const std::string& key,
807 const std::string& fallback) const {
808 for (const auto& name : parameterOrder_) {
809 const auto& binding = parameters_.at(name);
810 if (binding.nodeId == node.id && binding.key == key) {
811 const auto overridden = stringOverrides_.find(name);
812 if (overridden != stringOverrides_.end()) return overridden->second;
813 }
814 }
815 const auto found = node.strings.find(key);
816 return found == node.strings.end() ? fallback : found->second;
817}
818
819} // namespace eve::procgen
ActionParameterOperation operation
double value
std::string output
std::string from
eve::action::ActionSpatialBinding spatial
std::string nodeId
std::map< std::string, Var > values
EvpackChunkInput input
Definition Evpack.cpp:170
std::uint32_t key
int inputs
Definition GridGraph.cpp:23
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
TokenKind kind
std::string name
std::vector< BvhNode > nodes
std::map< std::string, std::vector< std::string > > graph
Definition Package.cpp:59
eve::action::ActionVfxBinding binding
bool dirty
std::string id
Definition PlayHost.cpp:108
std::shared_ptr< const std::vector< glm::vec2 > > points
const RoadNode * node
bool found
double current
std::size_t cursor
float size
Definition TreeMesh.cpp:156
uint32_t index
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
Schema-bearing column store aligned with a PointSet's point rows.
Data-driven biome distribution rules compatible with PointGraph and scene batches.
Definition Biome.h:40
Backend-neutral GPU kernels used by PointGraph with deterministic CPU fallback.
EVENGINE_API_DOMAINS public API.
Definition PointGraph.h:66
void resetCancellation()
Resets cancellation.
uint64_t getComputePeakBufferBytes() const
Return peak bytes reserved in each reusable point-compute buffer.
static std::string getOperationParamDefault(const std::string &operation, int index)
Returns the operation param default.
bool setParameterFloat(const std::string &name, float value)
Override an exposed floating-point parameter for this graph instance.
float getMetricMinZ(int index) const
Return the cached minimum output Z coordinate.
uint64_t getComputeBufferReuseCount() const
Return executions that reused allocated point-compute buffers.
bool setNodeFloat(const std::string &id, const std::string &key, float value)
Sets the node float.
bool removeNode(const std::string &id)
Remove a node and every edge that references it.
Result< PointSet > executeResult(std::string_view outputId)
Evaluate an output and return an independent owning point snapshot.
void setExecutionNodeBudget(int nodes)
Limit uncached nodes evaluated by one execute call; zero disables the limit.
bool exposeParameter(const std::string &name, const std::string &nodeId, const std::string &key)
Expose one reflected node parameter for per-instance overrides.
std::string getBindingName(int index) const
Return a binding name by stable insertion index.
float getMetricMilliseconds(int index) const
Returns the metric milliseconds.
void clearCache()
Clears cache.
static int getOperationCount()
Returns the operation count.
Result< void > clearBinding(const std::string &name)
Remove one named binding.
bool setNodeString(const std::string &id, const std::string &key, const std::string &value)
Sets the node string.
bool wasCancelled() const
Was cancelled.
float getMetricMaxX(int index) const
Return the cached maximum output X coordinate.
int getMetricCount() const
Returns the metric count.
std::string getInputNode(const std::string &nodeId, int inputIndex) const
Returns the input node.
std::string getNodeOperation(const std::string &id) const
Returns the node operation.
bool connect(const std::string &fromId, const std::string &toId, int inputIndex=0)
Connect an output to input slot 0 or 1. Replaces that input connection.
uint64_t getRevision() const
Monotonic topology/parameter revision used by asset and preview caches.
std::string getComputeFallbackReason() const
Return why the latest eligible GPU node used the CPU fallback.
int getComputeMinimumPoints() const
Return the minimum point count for automatic GPU dispatch.
PointSet * getNodeOutput(const std::string &id) const
Copy the cached/debug output of a node after execution.
uint64_t getComputeReadbackCount() const
Return successful point-compute readbacks for this graph executor.
void requestCancel()
Cancel subsequent execution until resetCancellation is called.
int getExecutionCount() const
Returns the execution count.
uint64_t getExecutionPlanBuildCount() const
Return how many execution plans this graph instance has compiled.
int getNodeCount() const
Returns the node count.
bool setComputePolicy(const std::string &policy)
Select auto, gpu, or cpu point execution; GPU failures fall back to CPU.
PointSet * execute(const std::string &outputId)
Compatibility-only projection of executeResult; caller deletes the result, nullptr means failure.
std::string getBindingType(const std::string &name) const
Return spatial, points, or empty when the name is unbound.
int getLastFusedTransformCount() const
Return logical transforms in the latest successful fused dispatch.
bool isMetricCacheHit(int index) const
True when metric cache hit.
std::string getComputePolicy() const
Return the configured point compute policy.
bool validate()
Compatibility-only bool projection of validateResult; diagnostics are rendered by getError().
static int getOperationParamCount(const std::string &operation)
Returns the operation param count.
int getMaxNodeOutputPoints() const
Return the per-node point output limit; zero means unlimited.
static std::string getOperationId(int index)
Returns the operation id.
int getExecutionNodeBudget() const
Returns the execution node budget.
std::string getMetricBackend(int index) const
Return cpu, vulkan, or webgpu for one evaluated node.
float getMetricAverageDensity(int index) const
Return the mean point density of one node output.
Result< void > validateResult() const
Validate configured nodes and nested graphs without executing them.
int getBindingCount() const
Return the number of named bindings.
Result< void > setBindingSpatial(const std::string &name, SpatialData *spatial)
Bind named SpatialData for get.* / optional binding= parameters. External runtime slot: not serialize...
bool setNodeSubgraph(const std::string &id, PointGraph *graph, const std::string &inputNode, const std::string &outputNode)
Assign an immutable nested graph.
void setMaxNodeOutputPoints(int points)
Limit points produced by any node; zero disables the limit.
int getParameterCount() const
Return the number of exposed graph parameters.
int getCompiledSegmentCount() const
Return logical CPU/GPU segments in the execution plan compiled for the latest output.
float getMetricMinY(int index) const
Return the cached minimum output Y coordinate.
bool setNodeSpatial(const std::string &id, SpatialData *spatial)
Assign copied spatial data to a spatial sample, filter, projection, or biome node.
float getMetricMaxY(int index) const
Return the cached maximum output Y coordinate.
uint64_t getComputeUploadCount() const
Return successful point-compute uploads for this graph executor.
bool clearParameterOverride(const std::string &name)
Remove an instance override and restore the asset default.
static std::string getOperationParamKind(const std::string &operation, int index)
Returns the operation param kind.
float getParameterFloat(const std::string &name, float fallback) const
Return the effective floating-point parameter value.
Result< PointGraphInspectReport > inspectNode(const std::string &id, int sampleLimit=0) const
Build an inspect snapshot for a cached node output after execute.
std::string getParameterName(int index) const
Return an exposed parameter name by stable insertion index.
Result< void > setBindingPoints(const std::string &name, PointSet *points)
Bind named PointSet for get.points / get.actor. External runtime slot: not serialized with serializeD...
std::string getError() const
Render the last compatibility execute/validate or legacy authoring error; canonical Result calls do n...
int getCacheHitCount() const
Returns the cache hit count.
std::string getParameterKind(const std::string &name) const
Return the reflected kind of an exposed parameter.
bool disconnect(const std::string &toId, int inputIndex=0)
Remove one input connection.
bool hasParameterOverride(const std::string &name) const
Report whether an instance override is active.
bool setNodeInt(const std::string &id, const std::string &key, int value)
Sets the node int.
void clearBindings()
Remove every named binding.
float getMetricMaxZ(int index) const
Return the cached maximum output Z coordinate.
std::string getMetricNodeId(int index) const
Returns the metric node id.
std::string getNodeId(int index) const
Returns the node id.
float getMetricMinX(int index) const
Return cached output bounds for editor/debug visualization.
bool setNodeShapeGrammar(const std::string &id, ShapeGrammar *grammar)
Assign a copied shape grammar to a grammar.generate node.
bool setNodePoints(const std::string &id, PointSet *points)
Assign a copied PointSet to an input node.
std::string getParameterString(const std::string &name, const std::string &fallback) const
Return the effective string parameter value.
uint64_t getComputeDispatchCount() const
Return successful point-compute dispatches for this graph executor.
bool setParameterString(const std::string &name, const std::string &value)
Override an exposed string parameter for this graph instance.
int getParameterInt(const std::string &name, int fallback) const
Return the effective integer or Boolean parameter value.
static std::string getOperationParamKey(const std::string &operation, int index)
Returns the operation param key.
bool hasNode(const std::string &id) const
True when node.
int getMetricOutputCount(int index) const
Returns the metric output count.
bool setParameterInt(const std::string &name, int value)
Override an exposed integer or Boolean parameter for this graph instance.
bool addNode(const std::string &id, const std::string &operation)
Add a node using one of the operations returned by operationAt().
bool setNodeBiomeRules(const std::string &id, BiomeRules *rules)
Assign copied biome rules to a biome.generate node.
std::string debugReport() const
Debug report.
void setComputeMinimumPoints(int points)
Set the minimum point count for auto GPU dispatch.
static int getOperationInputCount(const std::string &operation)
Returns the operation input count.
Script-friendly collection of attributed 3D samples.
Definition PointSet.h:59
Module-based shape grammar expanded continuously along a 3D polyline.
Queryable spatial input for procedural pipelines.
Definition SpatialData.h:35
const char * defaultValue
std::vector< ParamSpec > params
PointSet transformPointSet(const PointSet &input, float translateX, float translateY, float translateZ, float yawDegrees, float scaleX, float scaleY, float scaleZ)
Apply translation, yaw rotation and non-uniform scale to points and their transforms.
Definition PointSet.cpp:886
ActionSpatialBinding spatial
A data-flow graph for attributed procedural points.
Definition PointGraph.h:49
Snapshot of one node output for editor Attribute/Inspect panels.
Definition PointGraph.h:56
std::vector< float > sampleFloats
Optional sampled float values: columns-major flattened [column * sampleCount + point].
Definition PointGraph.h:61
std::vector< PointGraphColumnInfo > columns
Definition PointGraph.h:59
glm::uvec4 info