载入中...
搜索中...
未找到
PointGraphExecution.cpp
浏览该文件的文档.
1#include <algorithm>
2#include <chrono>
3#include <cmath>
4#include <limits>
5#include "procgen/Biome.h"
9
10namespace eve::procgen {
11namespace {
12Status nestedFailure(const std::string& id, const Status& status) {
13 std::vector<Diagnostic> diagnostics;
14 for (const auto& diagnostic : status.diagnostics())
15 diagnostics.emplace_back(diagnostic.code(), diagnostic.severity(), diagnostic.message(),
16 id + "/" + diagnostic.path(), diagnostic.details(), diagnostic.source());
17 return Status(status.code(), std::move(diagnostics));
18}
19
20#if defined(_MSC_VER)
21#define EV_POINTGRAPH_NOINLINE __declspec(noinline)
22#else
23#define EV_POINTGRAPH_NOINLINE __attribute__((noinline))
24#endif
25
26// Out-of-line so Diagnostic/Result temporaries stay out of evaluate()'s MSVC Debug frame
27// (deep transform chains recurse evaluate ↔ evaluateTransformSegment and overflow ~1MB stacks).
28EV_POINTGRAPH_NOINLINE ResultRef<const PointSet> nodeFailure(DiagnosticCode code, const std::string& id,
29 std::string message) {
31 Diagnostic::error(code, std::move(message), id, {}, "procgen.pointGraph"));
32}
33
34EV_POINTGRAPH_NOINLINE Result<OptionalRef<const PointSet>> segmentFailure(DiagnosticCode code, const std::string& id,
35 std::string message) {
36 return Result<OptionalRef<const PointSet>>::failure(
37 Diagnostic::error(code, std::move(message), id, {}, "procgen.pointGraph"));
38}
39
40uint64_t nowNanoseconds() {
41 return uint64_t(
42 std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch())
43 .count());
44}
45
46PointGraphNodeMetric makeMetric(const std::string& id, const PointSet& points, float milliseconds, bool cacheHit,
47 const std::string& backend = "cpu") {
48 PointGraphNodeMetric metric;
49 metric.id = id;
50 metric.outputCount = points.getCount();
51 metric.milliseconds = milliseconds;
52 metric.cacheHit = cacheHit;
53 metric.backend = backend;
54 if (points.empty()) return metric;
55 const auto& first = points.points().front();
56 metric.minX = metric.maxX = first.x;
57 metric.minY = metric.maxY = first.y;
58 metric.minZ = metric.maxZ = first.z;
59 double densitySum = 0.0;
60 for (const auto& point : points.points()) {
61 metric.minX = std::min(metric.minX, point.x);
62 metric.minY = std::min(metric.minY, point.y);
63 metric.minZ = std::min(metric.minZ, point.z);
64 metric.maxX = std::max(metric.maxX, point.x);
65 metric.maxY = std::max(metric.maxY, point.y);
66 metric.maxZ = std::max(metric.maxZ, point.z);
67 densitySum += point.density;
68 }
69 metric.averageDensity = float(densitySum / double(points.getCount()));
70 return metric;
71}
72
73uint64_t spatialSampleUpperBound(const SpatialData& spatial, float spacing) {
74 if (!spatial.hasBounds() || spacing <= 0.f) return 0;
75 const auto axisCount = [spacing](float minimum, float maximum) {
76 if (maximum <= minimum) return uint64_t(1);
77 return uint64_t(std::floor(double(maximum - minimum) / double(spacing) + 0.001)) + 1;
78 };
79 const uint64_t x = axisCount(spatial.getMinX(), spatial.getMaxX());
80 const uint64_t y =
81 spatial.getKind() == "surface.heightfield" ? uint64_t(1) : axisCount(spatial.getMinY(), spatial.getMaxY());
82 const uint64_t z = axisCount(spatial.getMinZ(), spatial.getMaxZ());
83 const uint64_t maximum = std::numeric_limits<uint64_t>::max();
84 if (x > maximum / y) return maximum;
85 const uint64_t xy = x * y;
86 return xy > maximum / z ? maximum : xy * z;
87}
88
89} // namespace
90
91ResultRef<const PointSet> PointGraph::evaluate(const std::string& id, std::unordered_map<std::string, int>& states) {
92 const auto found = nodes_.find(id);
93 if (found == nodes_.end()) {
94 return nodeFailure(DiagnosticCode::NotFound, id, "unknown node: " + id);
95 }
96 Node& node = found->second;
97 if (node.cacheValid) {
98 ++cacheHitCount_;
99 PointGraphNodeMetric metric = node.cacheMetric;
100 metric.milliseconds = 0.f;
101 metric.cacheHit = true;
102 metrics_.push_back(std::move(metric));
103 return ResultRef<const PointSet>::success(std::cref(node.cache));
104 }
105 if (states[id] == 1) {
106 return nodeFailure(DiagnosticCode::Conflict, id, "cycle at node: " + id);
107 }
108 if (cancelRequested_) {
109 lastCancelled_ = true;
110 return nodeFailure(DiagnosticCode::Cancelled, id, "execution cancelled at node: " + id);
111 }
112 if (node.operation == "transform") {
113 auto segment = evaluateTransformSegment(id, states);
114 if (!segment.ok()) return ResultRef<const PointSet>::failure(segment.status());
115 auto value = std::move(segment).takeValue();
117 }
118 states[id] = 1;
119 const uint64_t started = nowNanoseconds();
120 std::string nodeBackend = "cpu";
121 const PointSet* first = nullptr;
122 const PointSet* second = nullptr;
123 if (!node.inputs[0].empty()) {
124 auto input = evaluate(node.inputs[0], states);
125 if (!input.ok()) return ResultRef<const PointSet>::failure(input.status());
126 first = &input.value().get();
127 }
128 if (!node.inputs[1].empty()) {
129 auto input = evaluate(node.inputs[1], states);
130 if (!input.ok()) return ResultRef<const PointSet>::failure(input.status());
131 second = &input.value().get();
132 }
133 if (executionNodeBudget_ > 0 && evaluatedNodes_ >= executionNodeBudget_) {
134 lastCancelled_ = true;
135 return nodeFailure(DiagnosticCode::Cancelled, id, "execution node budget exceeded at node: " + id);
136 }
137 ++evaluatedNodes_;
138
139 if (node.operation == "input") {
140 if (!node.hasPoints)
141 return nodeFailure(DiagnosticCode::InvalidArgument, id, "input node has no points: " + id);
142 else
143 node.cache = node.points;
144
145 } else if (node.operation == "get.points" || node.operation == "get.actor") {
146 const std::string binding = stringValue(node, "binding", {});
147 if (binding.empty())
148 return nodeFailure(DiagnosticCode::InvalidArgument, id, node.operation + " requires binding: " + id);
149 auto pointsResult = resolveBindingPoints(binding);
150 if (!pointsResult.ok())
151 return ResultRef<const PointSet>::failure(nestedFailure(id, pointsResult.status()));
152 node.cache = *pointsResult.value();
153 } else if (node.operation == "spatial.sample" || node.operation == "get.spatial" ||
154 node.operation == "get.landscape" || node.operation == "get.spline") {
155 const float spacing = floatValue(node, "spacing", 1.f);
156 auto spatialResult = resolveNodeSpatial(node);
157 if (!spatialResult.ok())
158 return ResultRef<const PointSet>::failure(nestedFailure(id, spatialResult.status()));
159 const auto spatial = spatialResult.value();
160 if (node.operation == "get.landscape" && spatial->getKind() != "surface.heightfield")
161 return nodeFailure(DiagnosticCode::InvalidArgument, id, "get.landscape requires surface.heightfield binding: " + id);
162 if (node.operation == "get.spline" && spatial->getKind() != "spline")
163 return nodeFailure(DiagnosticCode::InvalidArgument, id, "get.spline requires spline binding: " + id);
164 if (maxNodeOutputPoints_ > 0 &&
165 spatialSampleUpperBound(*spatial, spacing) > uint64_t(maxNodeOutputPoints_))
166 return nodeFailure(DiagnosticCode::InvalidArgument, id, node.operation + " exceeds node point budget at node: " + id);
167 node.cache =
168 spatial->sample(spacing, uint32_t(intValue(node, "seed", 1)), floatValue(node, "jitter", 0.f));
169 } else if (node.operation == "mesh.sample") {
170 const float spacing = floatValue(node, "spacing", 1.f);
171 auto spatialResult = resolveNodeSpatial(node);
172 if (!spatialResult.ok())
173 return ResultRef<const PointSet>::failure(nestedFailure(id, spatialResult.status()));
174 const auto spatial = spatialResult.value();
175 if (spatial->getKind() != "surface.mesh")
176 return nodeFailure(DiagnosticCode::InvalidArgument, id, "mesh.sample requires surface.mesh spatial data: " + id);
177 else if (maxNodeOutputPoints_ > 0 &&
178 spatialSampleUpperBound(*spatial, spacing) > uint64_t(maxNodeOutputPoints_))
179 return nodeFailure(DiagnosticCode::InvalidArgument, id, "mesh.sample exceeds node point budget at node: " + id);
180 else
181 node.cache =
182 spatial->sample(spacing, uint32_t(intValue(node, "seed", 1)), floatValue(node, "jitter", 0.f));
183 } else if (node.operation == "grid.sample") {
184 const int width = intValue(node, "width", 8);
185 const int depth = intValue(node, "depth", 8);
186 const float spacing = floatValue(node, "spacing", 1.f);
187 if (width <= 0 || depth <= 0 || spacing <= 0.f)
188 return nodeFailure(DiagnosticCode::InvalidArgument, id, "grid.sample requires positive width, depth, and spacing: " + id);
189 const uint64_t expected = uint64_t(width) * uint64_t(depth);
190 if (maxNodeOutputPoints_ > 0 && expected > uint64_t(maxNodeOutputPoints_))
191 return nodeFailure(DiagnosticCode::InvalidArgument, id, "grid.sample exceeds node point budget at node: " + id);
192 node.cache = sampleGridPoints(width, depth, spacing, uint32_t(intValue(node, "seed", 1)),
193 floatValue(node, "jitter", 0.f));
194 const float originX = floatValue(node, "originX", 0.f);
195 const float originY = floatValue(node, "originY", 0.f);
196 const float originZ = floatValue(node, "originZ", 0.f);
197 if (originX != 0.f || originY != 0.f || originZ != 0.f)
198 node.cache = transformPointSet(node.cache, originX, originY, originZ, 0.f, 1.f, 1.f, 1.f);
199 } else if (node.operation == "poisson.sample") {
200 const int width = intValue(node, "width", 32);
201 const int depth = intValue(node, "depth", 32);
202 const float radius = floatValue(node, "radius", 2.f);
203 const int maxPoints = intValue(node, "maxPoints", 1000);
204 if (width < 0 || depth < 0 || radius <= 0.f || maxPoints < 0)
205 return nodeFailure(DiagnosticCode::InvalidArgument, id, "poisson.sample requires non-negative width/depth/maxPoints and positive radius: " + id);
206 if (maxNodeOutputPoints_ > 0 && uint64_t(maxPoints) > uint64_t(maxNodeOutputPoints_))
207 return nodeFailure(DiagnosticCode::InvalidArgument, id, "poisson.sample exceeds node point budget at node: " + id);
208 node.cache = poissonDiskPoints(width, depth, radius, uint32_t(intValue(node, "seed", 1)), maxPoints);
209 const float originX = floatValue(node, "originX", 0.f);
210 const float originY = floatValue(node, "originY", 0.f);
211 const float originZ = floatValue(node, "originZ", 0.f);
212 if (originX != 0.f || originY != 0.f || originZ != 0.f)
213 node.cache = transformPointSet(node.cache, originX, originY, originZ, 0.f, 1.f, 1.f, 1.f);
214 } else if (node.operation == "spatial.filter") {
215 auto spatialResult = resolveNodeSpatial(node);
216 if (!first || !spatialResult.ok())
217 return nodeFailure(DiagnosticCode::InvalidArgument, id, "spatial.filter requires input and spatial data: " + id);
218 else
219 node.cache = spatialResult.value()->filter(*first, intValue(node, "invert", 0) != 0);
220 } else if (node.operation == "spatial.project") {
221 auto spatialResult = resolveNodeSpatial(node);
222 if (!first || !spatialResult.ok())
223 return nodeFailure(DiagnosticCode::InvalidArgument, id, "spatial.project requires input and spatial data: " + id);
224 else
225 node.cache = spatialResult.value()->project(*first);
226 } else if (node.operation == "spline.sample") {
227 const float spacing = floatValue(node, "spacing", 1.f);
228 if (!first)
229 return nodeFailure(DiagnosticCode::InvalidArgument, id, "spline.sample requires control points: " + id);
230 else if (spacing <= 0.f)
231 return nodeFailure(DiagnosticCode::InvalidArgument, id, "spline.sample spacing must be positive: " + id);
232 else if (first->getCount() < 2)
233 return nodeFailure(DiagnosticCode::InvalidArgument, id, "spline.sample needs at least two control points: " + id);
234 else
235 node.cache = samplePolylinePoints(*first, spacing, uint32_t(intValue(node, "seed", 1)),
236 floatValue(node, "lateralJitter", 0.f));
237 } else if (node.operation == "spline.filter.distance") {
238 if (!first || !second)
239 return nodeFailure(DiagnosticCode::InvalidArgument, id, "spline.filter.distance requires points and control inputs: " + id);
240 else if (second->getCount() < 2)
241 return nodeFailure(DiagnosticCode::InvalidArgument, id, "spline.filter.distance needs at least two control points: " + id);
242 else
243 node.cache = filterPointsBySplineDistance(*first, *second, floatValue(node, "minDistance", 0.f),
244 floatValue(node, "maxDistance", 1.f));
245 } else if (node.operation == "biome.generate") {
246 const float spacing = floatValue(node, "spacing", 1.f);
247 if (!node.spatial || !node.biomeRules)
248 return nodeFailure(DiagnosticCode::InvalidArgument, id, "biome.generate requires spatial data and biome rules: " + id);
249 else if (maxNodeOutputPoints_ > 0 &&
250 spatialSampleUpperBound(*node.spatial, spacing) > uint64_t(maxNodeOutputPoints_))
251 return nodeFailure(DiagnosticCode::InvalidArgument, id, "biome.generate exceeds node point budget at node: " + id);
252 else {
253 std::unique_ptr<PointSet> result(node.biomeRules->generate(
254 node.spatial.get(), spacing, uint32_t(intValue(node, "seed", 1)), floatValue(node, "jitter", 0.f)));
255 if (!result)
256 return nodeFailure(DiagnosticCode::Failed, id, "biome.generate failed at " + id + ": " + node.biomeRules->getError());
257 else
258 node.cache = *result;
259 }
260 } else if (node.operation == "grammar.generate") {
261 if (!first || !node.shapeGrammar)
262 return nodeFailure(DiagnosticCode::InvalidArgument, id, "grammar.generate requires input and shape grammar: " + id);
263 else {
264 std::unique_ptr<PointSet> result(node.shapeGrammar->generate(
265 stringValue(node, "grammar"), const_cast<PointSet*>(first), uint32_t(intValue(node, "seed", 1)),
266 intValue(node, "acceptIncomplete", 1) != 0));
267 if (!result)
268 return nodeFailure(DiagnosticCode::Failed, id, "grammar.generate failed at " + id + ": " + node.shapeGrammar->getError());
269 else
270 node.cache = *result;
271 }
272 } else if (node.operation == "merge") {
273 const uint64_t outputCount = first && second ? uint64_t(first->getCount()) + uint64_t(second->getCount()) : 0;
274 if (!first || !second)
275 return nodeFailure(DiagnosticCode::InvalidArgument, id, "merge requires two inputs: " + id);
276 else if (maxNodeOutputPoints_ > 0 && outputCount > uint64_t(maxNodeOutputPoints_))
277 return nodeFailure(DiagnosticCode::InvalidArgument, id, "merge exceeds node point budget at node: " + id);
278 else
279 node.cache = mergePointSets(*first, *second);
280 } else if (node.operation == "points.union" || node.operation == "points.intersect" ||
281 node.operation == "points.difference") {
282 if (!first || !second)
283 return nodeFailure(DiagnosticCode::InvalidArgument, id, node.operation + " requires two inputs: " + id);
284 else if (node.operation == "points.union")
285 node.cache = unionPointSets(*first, *second);
286 else if (node.operation == "points.intersect")
288 else
290 } else if (node.operation == "copy.points") {
291 const int maxPoints = intValue(node, "maxPoints", 100000);
292 const uint64_t outputCount = first && second ? uint64_t(first->getCount()) * uint64_t(second->getCount()) : 0;
293 if (!first || !second)
294 return nodeFailure(DiagnosticCode::InvalidArgument, id, "copy.points requires source and target inputs: " + id);
295 else if (maxPoints < 0 || outputCount > uint64_t(maxPoints))
296 return nodeFailure(DiagnosticCode::InvalidArgument, id, "copy.points output exceeds maxPoints at node: " + id);
297 else if (maxNodeOutputPoints_ > 0 && outputCount > uint64_t(maxNodeOutputPoints_))
298 return nodeFailure(DiagnosticCode::InvalidArgument, id, "copy.points exceeds node point budget at node: " + id);
299 else
300 node.cache = copyPointsToTargets(*first, *second, intValue(node, "inheritTargetAttributes", 1) != 0);
301 } else if (node.operation == "density.remap") {
302 const float inputMin = floatValue(node, "inputMin", 0.f);
303 const float inputMax = floatValue(node, "inputMax", 1.f);
304 if (!first)
305 return nodeFailure(DiagnosticCode::InvalidArgument, id, "density.remap requires input: " + id);
306 else if (inputMin == inputMax)
307 return nodeFailure(DiagnosticCode::InvalidArgument, id, "density.remap requires a non-zero input range: " + id);
308 else
309 node.cache = remapPointDensity(*first, inputMin, inputMax, floatValue(node, "outputMin", 0.f),
310 floatValue(node, "outputMax", 1.f), intValue(node, "clamp", 1) != 0);
311 } else if (node.operation == "density.from.normal") {
312 if (!first)
313 return nodeFailure(DiagnosticCode::InvalidArgument, id, "density.from.normal requires input: " + id);
314 else
315 node.cache =
316 densityFromNormal(*first, floatValue(node, "minDegrees", 0.f), floatValue(node, "maxDegrees", 90.f),
317 floatValue(node, "outputMin", 0.f), floatValue(node, "outputMax", 1.f),
318 intValue(node, "invert", 0) != 0);
319 } else if (node.operation == "landscape.sample" || node.operation == "texture.sample") {
320 if (!first)
321 return nodeFailure(DiagnosticCode::InvalidArgument, id, node.operation + " requires input: " + id);
322 auto spatialResult = resolveNodeSpatial(node);
323 if (!spatialResult.ok())
324 return ResultRef<const PointSet>::failure(nestedFailure(id, spatialResult.status()));
325 const auto spatial = spatialResult.value();
326 if (node.operation == "landscape.sample" && spatial->getKind() != "surface.heightfield")
327 return nodeFailure(DiagnosticCode::InvalidArgument, id, "landscape.sample requires surface.heightfield: " + id);
328 if (node.operation == "texture.sample" && spatial->getKind() != "volume.texture_mask")
329 return nodeFailure(DiagnosticCode::InvalidArgument, id, "texture.sample requires volume.texture_mask: " + id);
330 std::string attribute = stringValue(node, "attribute", "$Density");
331 if (attribute.empty()) attribute = "$Density";
332 if (!isPointFloatChannel(attribute))
333 return nodeFailure(DiagnosticCode::InvalidArgument, id, node.operation + " has unknown float channel at node: " + id);
335 if (node.operation == "landscape.sample" && intValue(node, "project", 0) != 0) source = spatial->project(source);
336 node.cache = sampleSpatialOntoChannel(source, *spatial, attribute, floatValue(node, "inputMin", 0.f),
337 floatValue(node, "inputMax", node.operation == "texture.sample" ? 1.f : 0.f),
338 floatValue(node, "outputMin", 0.f), floatValue(node, "outputMax", 1.f),
339 intValue(node, "clamp", 1) != 0, intValue(node, "invert", 0) != 0);
340 } else if (node.operation == "attribute.math.float") {
341 const std::string attribute = stringValue(node, "attribute");
342 std::string outputAttribute = stringValue(node, "outputAttribute");
343 if (outputAttribute.empty()) outputAttribute = attribute;
344 const std::string operation = stringValue(node, "operation", "multiply");
345 const float operand = floatValue(node, "operand", 1.f);
346 const bool operationKnown = operation == "add" || operation == "subtract" || operation == "multiply" ||
347 operation == "divide" || operation == "min" || operation == "max";
348 if (!first)
349 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.math.float requires input: " + id);
350 else if (attribute.empty())
351 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.math.float requires an attribute: " + id);
352 else if (!isPointFloatChannel(attribute) || !isPointFloatChannel(outputAttribute))
353 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.math.float has unknown float channel at node: " + id);
354 else if (!operationKnown)
355 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.math.float has invalid operation at node: " + id);
356 else if (operation == "divide" && operand == 0.f)
357 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.math.float cannot divide by zero at node: " + id);
358 else
359 node.cache = mathPointFloatAttribute(*first, attribute, outputAttribute, operation, operand,
360 floatValue(node, "defaultValue", 0.f));
361 } else if (node.operation == "transform") {
362 if (!first)
363 return nodeFailure(DiagnosticCode::InvalidArgument, id, "transform requires input: " + id);
364 else {
365 const bool gpuEligible =
366 !first->empty() &&
367 (computePolicy_ == "gpu" || (computePolicy_ == "auto" && first->getCount() >= computeMinimumPoints_));
368 const bool gpuComplete =
369 gpuEligible &&
370 pointCompute_->transform(*first, node.cache, floatValue(node, "x", 0.f), floatValue(node, "y", 0.f),
371 floatValue(node, "z", 0.f), floatValue(node, "yaw", 0.f),
372 floatValue(node, "scaleX", 1.f), floatValue(node, "scaleY", 1.f),
373 floatValue(node, "scaleZ", 1.f));
374 if (gpuComplete) {
375#ifdef EVENGINE_WEBGPU
376 nodeBackend = "webgpu";
377#else
378 nodeBackend = "vulkan";
379#endif
380 } else {
381 if (gpuEligible) computeFallbackReason_ = pointCompute_->getError();
382 node.cache = transformPointSet(*first, floatValue(node, "x", 0.f), floatValue(node, "y", 0.f),
383 floatValue(node, "z", 0.f), floatValue(node, "yaw", 0.f),
384 floatValue(node, "scaleX", 1.f), floatValue(node, "scaleY", 1.f),
385 floatValue(node, "scaleZ", 1.f));
386 }
387 }
388 } else if (node.operation == "bounds.modify") {
389 if (!first)
390 return nodeFailure(DiagnosticCode::InvalidArgument, id, "bounds.modify requires input: " + id);
391 else
392 node.cache =
393 modifyPointBounds(*first, floatValue(node, "scaleX", 1.f), floatValue(node, "scaleY", 1.f),
394 floatValue(node, "scaleZ", 1.f), floatValue(node, "padX", 0.f),
395 floatValue(node, "padY", 0.f), floatValue(node, "padZ", 0.f));
396 } else if (node.operation == "filter.float") {
397 if (!first)
398 return nodeFailure(DiagnosticCode::InvalidArgument, id, "filter.float requires input: " + id);
399 else
400 node.cache = filterPointFloatAttribute(*first, stringValue(node, "attribute"), floatValue(node, "min", 0.f),
401 floatValue(node, "max", 1.f), intValue(node, "invert", 0) != 0);
402 } else if (node.operation == "filter.string") {
403 if (!first)
404 return nodeFailure(DiagnosticCode::InvalidArgument, id, "filter.string requires input: " + id);
405 else
406 node.cache = filterPointStringAttribute(*first, stringValue(node, "attribute"), stringValue(node, "value"),
407 intValue(node, "invert", 0) != 0);
408 } else if (node.operation == "filter.slope") {
409 if (!first)
410 return nodeFailure(DiagnosticCode::InvalidArgument, id, "filter.slope requires input: " + id);
411 else
412 node.cache =
413 filterPointSlope(*first, floatValue(node, "minDegrees", 0.f), floatValue(node, "maxDegrees", 90.f));
414 } else if (node.operation == "attribute.set.float") {
415 if (!first)
416 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.set.float requires input: " + id);
417 else {
418 node.cache = *first;
419 const std::string attribute = stringValue(node, "attribute");
420 if (attribute.empty() || !isPointFloatChannel(attribute))
421 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.set.float requires attribute: " + id);
422 else {
423 const float value = floatValue(node, "value", 0.f);
424 for (int i = 0; i < node.cache.getCount(); ++i) {
425 auto written = writePointFloatChannel(node.cache, i, attribute, value);
426 if (!written.ok())
427 return ResultRef<const PointSet>::failure(nestedFailure(id, written.status()));
428 }
429 }
430 }
431 } else if (node.operation == "attribute.set.string") {
432 if (!first)
433 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.set.string requires input: " + id);
434 else {
435 node.cache = *first;
436 const std::string attribute = stringValue(node, "attribute");
437 if (attribute.empty() || attribute.front() == '$')
438 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.set.string requires attribute: " + id);
439 else
440 for (int i = 0; i < node.cache.getCount(); ++i)
441 node.cache.setStringAttribute(i, attribute, stringValue(node, "value"));
442 }
443 } else if (node.operation == "attribute.set.int") {
444 if (!first)
445 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.set.int requires input: " + id);
446 else {
447 const std::string attribute = stringValue(node, "attribute");
448 if (attribute.empty() || attribute.front() == '$')
449 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.set.int requires attribute: " + id);
450 node.cache = setPointIntAttribute(*first, attribute, intValue(node, "value", 0));
451 }
452 } else if (node.operation == "attribute.set.bool") {
453 if (!first)
454 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.set.bool requires input: " + id);
455 else {
456 const std::string attribute = stringValue(node, "attribute");
457 if (attribute.empty() || attribute.front() == '$')
458 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.set.bool requires attribute: " + id);
459 node.cache = setPointBoolAttribute(*first, attribute, intValue(node, "value", 0) != 0);
460 }
461 } else if (node.operation == "attribute.set.vector") {
462 if (!first)
463 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.set.vector requires input: " + id);
464 else {
465 const std::string attribute = stringValue(node, "attribute");
466 if (attribute.empty() || attribute.front() == '$')
467 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.set.vector requires attribute: " + id);
468 node.cache = setPointVectorAttribute(*first, attribute, floatValue(node, "x", 0.f),
469 floatValue(node, "y", 0.f), floatValue(node, "z", 0.f));
470 }
471 } else if (node.operation == "attribute.copy") {
472 if (!first)
473 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.copy requires input: " + id);
474 else {
475 auto copied = copyPointAttribute(*first, stringValue(node, "source"), stringValue(node, "target"));
476 if (!copied.ok()) return ResultRef<const PointSet>::failure(nestedFailure(id, copied.status()));
477 node.cache = std::move(copied).takeValue();
478 }
479 } else if (node.operation == "attribute.rename") {
480 if (!first)
481 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.rename requires input: " + id);
482 else {
483 auto renamed = renamePointAttribute(*first, stringValue(node, "from"), stringValue(node, "to"));
484 if (!renamed.ok()) return ResultRef<const PointSet>::failure(nestedFailure(id, renamed.status()));
485 node.cache = std::move(renamed).takeValue();
486 }
487 } else if (node.operation == "attribute.delete") {
488 if (!first)
489 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.delete requires input: " + id);
490 else {
491 auto deleted = deletePointAttribute(*first, stringValue(node, "attribute"));
492 if (!deleted.ok()) return ResultRef<const PointSet>::failure(nestedFailure(id, deleted.status()));
493 node.cache = std::move(deleted).takeValue();
494 }
495 } else if (node.operation == "attribute.transfer") {
496 if (!first || !second)
497 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.transfer requires two inputs: " + id);
498 else {
499 auto transferred =
500 transferPointAttribute(*first, *second, stringValue(node, "attribute"),
501 stringValue(node, "outputAttribute"), stringValue(node, "mode", "nearest"));
502 if (!transferred.ok())
503 return ResultRef<const PointSet>::failure(nestedFailure(id, transferred.status()));
504 node.cache = std::move(transferred).takeValue();
505 }
506 } else if (node.operation == "attribute.compare.float") {
507 if (!first)
508 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.compare.float requires input: " + id);
509 else {
510 auto compared = comparePointFloatAttribute(
511 *first, stringValue(node, "attribute"), stringValue(node, "comparison", "gt"),
512 floatValue(node, "operand", 0.f), stringValue(node, "outputAttribute", "match"),
513 floatValue(node, "defaultValue", 0.f));
514 if (!compared.ok()) return ResultRef<const PointSet>::failure(nestedFailure(id, compared.status()));
515 node.cache = std::move(compared).takeValue();
516 }
517 } else if (node.operation == "attribute.select.float") {
518 if (!first)
519 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.select.float requires input: " + id);
520 else {
521 auto selected = selectPointFloatAttribute(
522 *first, stringValue(node, "conditionAttribute"), stringValue(node, "trueAttribute"),
523 stringValue(node, "falseAttribute"), stringValue(node, "outputAttribute"),
524 floatValue(node, "trueDefault", 0.f), floatValue(node, "falseDefault", 0.f));
525 if (!selected.ok()) return ResultRef<const PointSet>::failure(nestedFailure(id, selected.status()));
526 node.cache = std::move(selected).takeValue();
527 }
528 } else if (node.operation == "filter.int") {
529 if (!first)
530 return nodeFailure(DiagnosticCode::InvalidArgument, id, "filter.int requires input: " + id);
531 else
532 node.cache = filterPointIntAttribute(*first, stringValue(node, "attribute"), intValue(node, "min", 0),
533 intValue(node, "max", 0), intValue(node, "invert", 0) != 0);
534 } else if (node.operation == "filter.bool") {
535 if (!first)
536 return nodeFailure(DiagnosticCode::InvalidArgument, id, "filter.bool requires input: " + id);
537 else
538 node.cache = filterPointBoolAttribute(*first, stringValue(node, "attribute"),
539 intValue(node, "value", 1) != 0, intValue(node, "invert", 0) != 0);
540
541 } else if (node.operation == "attribute.partition") {
542 if (!first)
543 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.partition requires input: " + id);
544 else {
545 const std::string attribute = stringValue(node, "attribute");
546 if (attribute.empty())
547 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.partition requires attribute: " + id);
548 node.cache = partitionPointAttribute(*first, attribute, stringValue(node, "outputAttribute", "partition"),
549 stringValue(node, "mode", "value"));
550 }
551 } else if (node.operation == "attribute.noise.float") {
552 if (!first)
553 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.noise.float requires input: " + id);
554 else
555 node.cache = noisePointFloatAttribute(*first, stringValue(node, "attribute", "noise"),
556 uint32_t(intValue(node, "seed", 1)),
557 floatValue(node, "frequency", 1.f),
558 floatValue(node, "amplitude", 1.f),
559 floatValue(node, "offset", 0.f));
560 } else if (node.operation == "attribute.math.int") {
561 if (!first)
562 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.math.int requires input: " + id);
563 else {
564 const std::string attribute = stringValue(node, "attribute");
565 if (attribute.empty())
566 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.math.int requires attribute: " + id);
567 node.cache = mathPointIntAttribute(*first, attribute, stringValue(node, "outputAttribute"),
568 stringValue(node, "operation", "add"),
569 intValue(node, "operand", 1), intValue(node, "defaultValue", 0));
570 }
571 } else if (node.operation == "attribute.math.vector") {
572 if (!first)
573 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.math.vector requires input: " + id);
574 else {
575 const std::string attribute = stringValue(node, "attribute");
576 if (attribute.empty())
577 return nodeFailure(DiagnosticCode::InvalidArgument, id, "attribute.math.vector requires attribute: " + id);
579 *first, attribute, stringValue(node, "outputAttribute"), stringValue(node, "operation", "scale"),
580 floatValue(node, "operandX", 1.f), floatValue(node, "operandY", 1.f), floatValue(node, "operandZ", 1.f),
581 floatValue(node, "defaultX", 0.f), floatValue(node, "defaultY", 0.f), floatValue(node, "defaultZ", 0.f));
582 }
583 } else if (node.operation == "attribute.set.data.float" || node.operation == "attribute.set.data.int" ||
584 node.operation == "attribute.set.data.string") {
585 if (!first)
586 return nodeFailure(DiagnosticCode::InvalidArgument, id, node.operation + " requires input: " + id);
587 else {
588 node.cache = *first;
589 const std::string attribute = stringValue(node, "attribute");
590 if (attribute.empty())
591 return nodeFailure(DiagnosticCode::InvalidArgument, id, node.operation + " requires attribute: " + id);
592 Result<void> written = Result<void>::success();
593 if (node.operation == "attribute.set.data.float")
594 written = setPointDataFloatAttribute(node.cache, attribute, floatValue(node, "value", 0.f));
595 else if (node.operation == "attribute.set.data.int")
596 written = setPointDataIntAttribute(node.cache, attribute, intValue(node, "value", 0));
597 else
598 written = setPointDataStringAttribute(node.cache, attribute, stringValue(node, "value"));
599 if (!written.ok())
600 return ResultRef<const PointSet>::failure(nestedFailure(id, written.status()));
601 }
602 } else if (node.operation == "spawn.mesh") {
603 if (!first)
604 return nodeFailure(DiagnosticCode::InvalidArgument, id, "spawn.mesh requires input: " + id);
605 else {
606 const std::string attribute = stringValue(node, "attribute", "mesh");
607 if (attribute.empty())
608 return nodeFailure(DiagnosticCode::InvalidArgument, id, "spawn.mesh requires attribute: " + id);
609 const std::string meshes[4] = {stringValue(node, "mesh0"), stringValue(node, "mesh1"),
610 stringValue(node, "mesh2"), stringValue(node, "mesh3")};
611 const float weights[4] = {floatValue(node, "weight0", 1.f), floatValue(node, "weight1", 0.f),
612 floatValue(node, "weight2", 0.f), floatValue(node, "weight3", 0.f)};
613 bool hasEntry = false;
614 for (int entry = 0; entry < 4; ++entry)
615 if (!meshes[entry].empty() && weights[entry] > 0.f) hasEntry = true;
616 if (!hasEntry)
617 return nodeFailure(DiagnosticCode::InvalidArgument, id, "spawn.mesh requires at least one weighted mesh: " + id);
618 node.cache = assignWeightedMeshAttribute(*first, uint32_t(intValue(node, "seed", 1)), attribute, meshes,
619 weights, 4);
620 }
621 } else if (node.operation == "density.cull") {
622 if (!first)
623 return nodeFailure(DiagnosticCode::InvalidArgument, id, "density.cull requires input: " + id);
624 else
625 node.cache =
626 densityCullPoints(*first, uint32_t(intValue(node, "seed", 1)), floatValue(node, "multiplier", 1.f));
627 } else if (node.operation == "self.prune") {
628 if (!first)
629 return nodeFailure(DiagnosticCode::InvalidArgument, id, "self.prune requires input: " + id);
630 else
631 node.cache = selfPrunePoints(*first, floatValue(node, "radius", 1.f));
632 } else if (node.operation == "jitter") {
633 if (!first)
634 return nodeFailure(DiagnosticCode::InvalidArgument, id, "jitter requires input: " + id);
635 else
636 node.cache = jitterPointPositions(*first, uint32_t(intValue(node, "seed", 1)), floatValue(node, "x", 0.f),
637 floatValue(node, "z", 0.f));
638 } else if (node.operation == "debug.disable") {
639 if (!first)
640 return nodeFailure(DiagnosticCode::InvalidArgument, id, "debug.disable requires input: " + id);
641 else if (intValue(node, "enabled", 1) == 0)
642 node.cache = PointSet{};
643 else
644 node.cache = *first;
645 } else if (node.operation == "debug.inspect") {
646 if (!first)
647 return nodeFailure(DiagnosticCode::InvalidArgument, id, "debug.inspect requires input: " + id);
648 else
649 node.cache = *first;
650 } else if (node.operation == "branch") {
651 const bool condition = intValue(node, "condition", 0) != 0;
652 if ((condition && !first) || (!condition && !second))
653 return nodeFailure(DiagnosticCode::InvalidArgument, id, "branch requires both selected inputs: " + id);
654 else
655 node.cache = condition ? *first : *second;
656 } else if (node.operation == "subgraph") {
657 if (!first || !node.subgraph)
658 return nodeFailure(DiagnosticCode::InvalidArgument, id, "subgraph requires input and graph: " + id);
659 else {
660 node.subgraph->setMaxNodeOutputPoints(maxNodeOutputPoints_);
661 node.subgraph->setComputePolicy(computePolicy_);
662 node.subgraph->setComputeMinimumPoints(computeMinimumPoints_);
663 if (!node.subgraph->setNodePoints(node.subgraphInput, const_cast<PointSet*>(first)))
664 return nodeFailure(DiagnosticCode::InvalidArgument, id, "subgraph input is invalid: " + node.subgraphInput);
665 else {
666 auto result = node.subgraph->executeResult(node.subgraphOutput);
667 if (!result.ok()) return ResultRef<const PointSet>::failure(nestedFailure(id, result.status()));
668 node.cache = std::move(result).takeValue();
669 }
670 }
671 }
672
673 if (maxNodeOutputPoints_ > 0 && node.cache.getCount() > maxNodeOutputPoints_) {
674 node.cache.clear();
675 return nodeFailure(DiagnosticCode::InvalidArgument, id, node.operation + " exceeds node point budget at node: " + id);
676 }
677 node.cacheValid = true;
678 states[id] = 2;
679 const float elapsed = float(nowNanoseconds() - started) * 0.000001f;
680 node.cacheMetric = makeMetric(id, node.cache, elapsed, false, nodeBackend);
681 metrics_.push_back(node.cacheMetric);
682 return ResultRef<const PointSet>::success(std::cref(node.cache));
683}
684
685Result<OptionalRef<const PointSet>> PointGraph::evaluateTransformSegment(const std::string& id,
686 std::unordered_map<std::string, int>& states) {
687 if (!executionPlan_) return Result<OptionalRef<const PointSet>>::success(std::nullopt);
688 const auto segmentEntry = executionPlan_->segmentByOutput.find(id);
689 if (segmentEntry == executionPlan_->segmentByOutput.end())
690 return Result<OptionalRef<const PointSet>>::success(std::nullopt);
691 const ExecutionSegment& planned = executionPlan_->segments[segmentEntry->second];
692 if (!planned.gpuTransformChain || planned.nodes.size() < 2)
693 return Result<OptionalRef<const PointSet>>::success(std::nullopt);
694 const std::vector<std::string>& chain = planned.nodes;
695 if (std::any_of(chain.begin(), chain.end(),
696 [&](const std::string& nodeId) { return nodes_.at(nodeId).cacheValid; }))
697 return Result<OptionalRef<const PointSet>>::success(std::nullopt);
698 const std::string& cursor = nodes_.at(chain.front()).inputs[0];
699
700 auto evaluated = evaluate(cursor, states);
701 if (!evaluated.ok()) return Result<OptionalRef<const PointSet>>::failure(evaluated.status());
702 const PointSet* input = &evaluated.value().get();
703 const bool gpuEligible =
704 !input->empty() &&
705 (computePolicy_ == "gpu" || (computePolicy_ == "auto" && input->getCount() >= computeMinimumPoints_));
706 if (!gpuEligible) return Result<OptionalRef<const PointSet>>::success(std::nullopt);
707 if (executionNodeBudget_ > 0 && evaluatedNodes_ + int(chain.size()) > executionNodeBudget_) {
708 lastCancelled_ = true;
709 return segmentFailure(DiagnosticCode::Cancelled, id, "execution node budget exceeded at node: " + id);
710 }
711
712 std::vector<PointCompute::Transform> transforms;
713 transforms.reserve(chain.size());
714 for (const std::string& nodeId : chain) {
715 const Node& transformNode = nodes_.at(nodeId);
716 transforms.push_back({floatValue(transformNode, "x", 0.f), floatValue(transformNode, "y", 0.f),
717 floatValue(transformNode, "z", 0.f), floatValue(transformNode, "yaw", 0.f),
718 floatValue(transformNode, "scaleX", 1.f), floatValue(transformNode, "scaleY", 1.f),
719 floatValue(transformNode, "scaleZ", 1.f)});
720 }
721
723 const uint64_t started = nowNanoseconds();
724 if (!pointCompute_->transformChain(*input, output, transforms)) {
725 computeFallbackReason_ = pointCompute_->getError();
726 return Result<OptionalRef<const PointSet>>::success(std::nullopt);
727 }
728
729 evaluatedNodes_ += int(chain.size());
730 for (const std::string& nodeId : chain) {
731 Node& transformNode = nodes_.at(nodeId);
732 transformNode.deferredTransformValid = true;
733 states[nodeId] = 2;
734 }
735 Node& finalNode = nodes_.at(id);
736 finalNode.cache = std::move(output);
737 finalNode.cacheValid = true;
738 finalNode.deferredTransformValid = false;
739#ifdef EVENGINE_WEBGPU
740 const std::string backend = "webgpu";
741#else
742 const std::string backend = "vulkan";
743#endif
744 const float elapsed = float(nowNanoseconds() - started) * 0.000001f;
745 finalNode.cacheMetric = makeMetric(id, finalNode.cache, elapsed, false, backend);
746 metrics_.push_back(finalNode.cacheMetric);
747 return Result<OptionalRef<const PointSet>>::success(std::cref(finalNode.cache));
748}
749
750Result<void> PointGraph::validateNode(const std::string& id, std::unordered_map<std::string, int>& states) const {
751 if (states[id] == 2) return Result<void>::success();
752 if (states[id] == 1) {
753 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "cycle at node: " + id, id, {}, "procgen.pointGraph"));
754 }
755 const auto found = nodes_.find(id);
756 if (found == nodes_.end() || getOperationInputCount(found->second.operation) < 0) {
757 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid node: " + id, id, {}, "procgen.pointGraph"));
758 }
759 states[id] = 1;
760 for (const auto& input : found->second.inputs) {
761 if (!input.empty()) {
762 auto result = validateNode(input, states);
763 if (!result.ok()) return result;
764 }
765 }
766 const auto& node = found->second;
767 if (node.operation == "input" && !node.hasPoints)
768 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "input node has no points: " + id, id, {}, "procgen.pointGraph"));
769 else if (node.operation == "get.points" || node.operation == "get.actor") {
770 if (stringValue(node, "binding").empty())
771 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, node.operation + " requires binding: " + id, id, {}, "procgen.pointGraph"));
772 auto points = resolveBindingPoints(stringValue(node, "binding"));
773 if (!points.ok()) return Result<void>::failure(nestedFailure(id, points.status()));
774 } else if (node.operation == "get.spatial" || node.operation == "get.landscape" ||
775 node.operation == "get.spline" || node.operation == "spatial.sample" ||
776 node.operation == "mesh.sample" || node.operation == "spatial.filter" ||
777 node.operation == "spatial.project" || node.operation == "biome.generate" ||
778 node.operation == "landscape.sample" || node.operation == "texture.sample") {
779 auto spatial = resolveNodeSpatial(node);
780 if (!spatial.ok()) return Result<void>::failure(nestedFailure(id, spatial.status()));
781 if (node.operation == "get.landscape" && spatial.value()->getKind() != "surface.heightfield")
782 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "get.landscape requires surface.heightfield binding: " + id, id, {}, "procgen.pointGraph"));
783 if (node.operation == "get.spline" && spatial.value()->getKind() != "spline")
784 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "get.spline requires spline binding: " + id, id, {}, "procgen.pointGraph"));
785 if (node.operation == "mesh.sample" && spatial.value()->getKind() != "surface.mesh")
786 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "mesh.sample requires surface.mesh spatial data: " + id, id, {}, "procgen.pointGraph"));
787 if (node.operation == "landscape.sample" && spatial.value()->getKind() != "surface.heightfield")
788 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "landscape.sample requires surface.heightfield: " + id, id, {}, "procgen.pointGraph"));
789 if (node.operation == "texture.sample" && spatial.value()->getKind() != "volume.texture_mask")
790 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "texture.sample requires volume.texture_mask: " + id, id, {}, "procgen.pointGraph"));
791 } else if (node.operation == "grid.sample") {
792 if (intValue(node, "width", 8) <= 0 || intValue(node, "depth", 8) <= 0 ||
793 floatValue(node, "spacing", 1.f) <= 0.f)
794 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "grid.sample requires positive width, depth, and spacing: " + id, id, {}, "procgen.pointGraph"));
795 } else if (node.operation == "poisson.sample") {
796 if (intValue(node, "width", 32) < 0 || intValue(node, "depth", 32) < 0 ||
797 floatValue(node, "radius", 2.f) <= 0.f || intValue(node, "maxPoints", 1000) < 0)
798 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "poisson.sample requires non-negative width/depth/maxPoints and positive radius: " + id, id, {}, "procgen.pointGraph"));
799 }
800 else if (node.operation == "biome.generate" && !node.biomeRules)
801 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "node has no biome rules: " + id, id, {}, "procgen.pointGraph"));
802 else if (node.operation == "grammar.generate" && !node.shapeGrammar)
803 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "node has no shape grammar: " + id, id, {}, "procgen.pointGraph"));
804 else if (node.operation == "subgraph" && (!node.subgraph || node.inputs[0].empty()))
805 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "subgraph is not configured: " + id, id, {}, "procgen.pointGraph"));
806 else if (node.operation == "subgraph") {
807 PointGraph nested = *node.subgraph;
808 PointSet externalInput;
809 if (!nested.setNodePoints(node.subgraphInput, &externalInput))
810 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid subgraph input: " + node.subgraphInput, id, {}, "procgen.pointGraph"));
811 auto result = nested.validateResult();
812 if (!result.ok()) return Result<void>::failure(nestedFailure(id, result.status()));
813 } else {
814 const int inputCount = getOperationInputCount(node.operation);
815 for (int index = 0; index < inputCount; ++index)
816 if (node.inputs[index].empty()) {
817 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, node.operation + " requires input " + std::to_string(index) + ": " + id, id, {}, "procgen.pointGraph"));
818 }
819 }
820 states[id] = 2;
821 return Result<void>::success();
822}
823
824} // namespace eve::procgen
ActionParameterOperation operation
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
eve::action::ActionSpatialBinding spatial
eve::Value condition
std::string nodeId
EvpackChunkInput input
Definition Evpack.cpp:170
std::string message
DiagnosticCode code
float maximum[3]
float minimum[3]
wgpu::PopErrorScopeStatus status
int inputs
Definition GridGraph.cpp:23
std::int32_t second
std::int32_t first
std::uint32_t width
double & milliseconds
Definition OnnxGpgpu.cpp:19
eve::action::ActionVfxBinding binding
float radius
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
#define EV_POINTGRAPH_NOINLINE
std::shared_ptr< const std::vector< glm::vec2 > > points
const RoadNode * node
bool found
std::uint32_t count
std::size_t cursor
float weights[3]
int spacing
uint32_t index
const UnitySourceAsset & source
std::uint32_t depth
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
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 int getOperationInputCount(const std::string &operation)
Returns the operation input count.
eve::StatusCode Status
PointSet samplePolylinePoints(const PointSet &controlPoints, float spacing, uint32_t seed, float lateralJitter)
Sample polyline points.
Definition PointSet.cpp:757
PointSet densityFromNormal(const PointSet &input, float minDegrees, float maxDegrees, float outputMin, float outputMax, bool invert)
Remap surface slope (from normals) into density.
PointSet filterPointStringAttribute(const PointSet &input, const std::string &name, const std::string &value, bool invert)
Select points whose named string attribute equals a value.
Result< PointSet > deletePointAttribute(const PointSet &input, const std::string &name)
Delete one metadata column.
PointSet mathPointIntAttribute(const PointSet &input, const std::string &attribute, const std::string &outputAttribute, const std::string &operation, std::int64_t operand, std::int64_t defaultValue)
Apply integer math to an int metadata column.
bool isPointFloatChannel(std::string_view name) noexcept
Return whether name is a valid float channel (builtin selector or metadata name).
PointSet unionPointSets(const PointSet &first, const PointSet &second)
Stable union by non-zero point id, with legacy position-and-seed fallback.
Definition PointSet.cpp:852
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
PointSet filterPointIntAttribute(const PointSet &input, const std::string &name, std::int64_t minValue, std::int64_t maxValue, bool invert)
Select points whose named int attribute lies in an inclusive range.
Result< PointSet > copyPointAttribute(const PointSet &input, const std::string &source, const std::string &target)
Copy one float channel or typed metadata column onto another name.
EVENGINE_API_DOMAINS Result< void > setPointDataFloatAttribute(PointSet &points, const std::string &attribute, float value)
Write a float into the PointSet @Data domain.
Result< PointSet > selectPointFloatAttribute(const PointSet &input, const std::string &conditionAttribute, const std::string &trueAttribute, const std::string &falseAttribute, const std::string &outputAttribute, float trueDefault, float falseDefault)
Select between two float channels using a bool metadata condition.
PointSet filterPointSlope(const PointSet &input, float minDegrees, float maxDegrees)
Filter point slope.
Definition PointSet.cpp:621
PointSet assignWeightedMeshAttribute(const PointSet &input, uint32_t seed, const std::string &attribute, const std::string *meshes, const float *weights, int entryCount)
Deterministically assign a weighted mesh path string attribute.
EVENGINE_API_DOMAINS PointSet noisePointFloatAttribute(const PointSet &input, const std::string &attribute, uint32_t seed, float frequency, float amplitude, float offset)
Write deterministic float noise into a metadata attribute.
PointSet mathPointFloatAttribute(const PointSet &input, const std::string &attribute, const std::string &outputAttribute, const std::string &operation, float operand, float defaultValue)
Apply one scalar operation to a float metadata attribute or $ selector.
PointSet poissonDiskPoints(int width, int depth, float radius, uint32_t seed, int maxPoints)
Bridson blue-noise (Poisson disk) samples in a width x depth area (XZ, y=0).
Definition PointSet.cpp:502
PointSet filterPointsBySplineDistance(const PointSet &input, const PointSet &controlPoints, float minDistance, float maxDistance)
Filter points by spline distance.
Definition PointSet.cpp:649
EVENGINE_API_DOMAINS PointSet filterPointFloatAttribute(const PointSet &input, const std::string &name, float minValue, float maxValue, bool invert)
Select points whose named float attribute or $ selector lies in an inclusive range.
PointSet mathPointVectorAttribute(const PointSet &input, const std::string &attribute, const std::string &outputAttribute, const std::string &operation, float operandX, float operandY, float operandZ, float defaultX, float defaultY, float defaultZ)
Apply vector math to a vector metadata column.
PointSet setPointIntAttribute(const PointSet &input, const std::string &attribute, std::int64_t value)
Write a constant int metadata column on every point.
EVENGINE_API_DOMAINS PointSet partitionPointAttribute(const PointSet &input, const std::string &attribute, const std::string &outputAttribute, const std::string &mode)
Assign partition indices from a string/int attribute (mode: value|hash).
Result< void > setPointDataStringAttribute(PointSet &points, const std::string &attribute, const std::string &value)
Write a string into the PointSet @Data domain.
Result< void > setPointDataIntAttribute(PointSet &points, const std::string &attribute, std::int64_t value)
Write an int into the PointSet @Data domain.
PointSet intersectPointSets(const PointSet &first, const PointSet &second)
Keep first-set points whose stable or legacy identity occurs in the second set.
Definition PointSet.cpp:864
PointSet sampleSpatialOntoChannel(const PointSet &input, const SpatialData &spatial, std::string_view channel, float inputMin, float inputMax, float outputMin, float outputMax, bool clampOutput, bool invert)
Remap SpatialData::sampleScalar onto a float channel ($Density or metadata). When inputMin equals inp...
PointSet mergePointSets(const PointSet &first, const PointSet &second)
Concatenate two attributed point collections while preserving order.
Definition PointSet.cpp:795
Result< void > writePointFloatChannel(PointSet &points, int index, std::string_view name, float value)
Write a float metadata column or closed $ point-field selector.
PointSet copyPointsToTargets(const PointSet &source, const PointSet &targets, bool inheritTargetAttributes)
Instantiate source points relative to targets in stable target-major order.
Definition PointSet.cpp:926
PointSet selfPrunePoints(const PointSet &input, float radius)
Self prune points.
Definition PointSet.cpp:737
PointSet modifyPointBounds(const PointSet &input, float scaleX, float scaleY, float scaleZ, float padX, float padY, float padZ)
Scale and pad each point's local bounds about its center.
PointSet densityCullPoints(const PointSet &input, uint32_t seed, float multiplier)
Deterministically keep points according to density and a root seed.
Result< PointSet > transferPointAttribute(const PointSet &target, const PointSet &source, const std::string &attribute, const std::string &outputAttribute, const std::string &mode)
Transfer one attribute from source onto target points.
PointSet differencePointSets(const PointSet &first, const PointSet &second)
Remove first-set points whose stable or legacy identity occurs in the second set.
Definition PointSet.cpp:875
PointSet setPointVectorAttribute(const PointSet &input, const std::string &attribute, float x, float y, float z)
Write a constant vector metadata column on every point.
PointSet setPointBoolAttribute(const PointSet &input, const std::string &attribute, bool value)
Write a constant bool metadata column on every point.
PointSet jitterPointPositions(const PointSet &input, uint32_t seed, float amountX, float amountZ)
Jitter point positions.
Definition PointSet.cpp:724
PointSet remapPointDensity(const PointSet &input, float inputMin, float inputMax, float outputMin, float outputMax, bool clampOutput)
Linearly remap point density between ranges with optional output clamping.
PointSet sampleGridPoints(int width, int depth, float spacing, uint32_t seed, float jitter)
Sample grid points.
Definition PointSet.cpp:480
Result< PointSet > renamePointAttribute(const PointSet &input, const std::string &from, const std::string &to)
Rename one metadata column.
Result< PointSet > comparePointFloatAttribute(const PointSet &input, const std::string &attribute, const std::string &comparison, float operand, const std::string &outputAttribute, float defaultValue)
Compare a float channel against an operand and write a bool metadata column.
PointSet filterPointBoolAttribute(const PointSet &input, const std::string &name, bool value, bool invert)
Select points whose named bool attribute equals a value.
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
bool started
Definition Graphics.cpp:183