载入中...
搜索中...
未找到
ObjectBuildLayer.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
4
5#include <algorithm>
6#include <cmath>
7#include <iomanip>
8#include <limits>
9#include <sstream>
10
11namespace eve::procgen {
12namespace {
13
14bool finiteRange(float minimum, float maximum) {
15 return std::isfinite(minimum) && std::isfinite(maximum) && minimum <= maximum;
16}
17
18Result<void> validateRange(const ObjectTransformRange& range) {
19 if (!finiteRange(range.minPitch, range.maxPitch) || !finiteRange(range.minYaw, range.maxYaw) ||
20 !finiteRange(range.minRoll, range.maxRoll) || !finiteRange(range.minScaleX, range.maxScaleX) ||
21 !finiteRange(range.minScaleY, range.maxScaleY) || !finiteRange(range.minScaleZ, range.maxScaleZ))
23 "random transform ranges must be finite and ordered",
24 "transform", {}, "procgen.objectBuild"));
25 if (range.minScaleX == 0.f || range.maxScaleX == 0.f || range.minScaleY == 0.f || range.maxScaleY == 0.f ||
26 range.minScaleZ == 0.f || range.maxScaleZ == 0.f)
28 "random scale bounds must be non-zero", "transform.scale", {},
29 "procgen.objectBuild"));
30 return Result<void>::success();
31}
32
33std::uint64_t mix(std::uint64_t value) {
34 value += 0x9e3779b97f4a7c15ULL;
35 value = (value ^ (value >> 30U)) * 0xbf58476d1ce4e5b9ULL;
36 value = (value ^ (value >> 27U)) * 0x94d049bb133111ebULL;
37 return value ^ (value >> 31U);
38}
39
40struct RandomStream {
41 std::uint64_t state;
42 std::uint64_t next() {
43 state = mix(state);
44 return state;
45 }
46 float unit() { return float(next() >> 40U) * (1.f / 16777216.f); }
47 float between(float minimum, float maximum) { return minimum + (maximum - minimum) * unit(); }
48};
49
50std::uint64_t pointKey(const ProcgenPoint& point, std::size_t index, std::uint32_t seed) {
51 if (point.id != 0) return mix(point.id ^ (std::uint64_t(seed) << 32U));
52 const auto qx = std::int64_t(std::llround(double(point.x) * 1000.0));
53 const auto qy = std::int64_t(std::llround(double(point.y) * 1000.0));
54 const auto qz = std::int64_t(std::llround(double(point.z) * 1000.0));
55 return mix(std::uint64_t(qx) ^ (mix(std::uint64_t(qy)) << 1U) ^ (mix(std::uint64_t(qz)) << 2U) ^
56 std::uint64_t(index) ^ (std::uint64_t(seed) << 32U));
57}
58
59float orientedYaw(const ProcgenPoint& point, const PointSet& orientation, float cellSize, bool invert) {
60 struct Direction {
61 float x;
62 float z;
63 float yaw;
64 };
65 const Direction cardinal[] = {
66 {0.f, 1.f, invert ? 90.f : 270.f}, {1.f, 0.f, 0.f}, {0.f, -1.f, invert ? 270.f : 90.f}, {-1.f, 0.f, 180.f}};
67 const float tolerance = std::max(0.0001f, cellSize * 0.01f);
68 const auto matches = [&](float x, float z) {
69 return std::any_of(orientation.points().begin(), orientation.points().end(), [&](const ProcgenPoint& other) {
70 return std::abs(other.x - x) <= tolerance && std::abs(other.z - z) <= tolerance;
71 });
72 };
73 for (const auto& direction : cardinal)
74 if (matches(point.x + direction.x * cellSize, point.z + direction.z * cellSize)) return direction.yaw;
75 const Direction diagonal[] = {{1.f, 1.f, 90.f}, {1.f, -1.f, 0.f}, {-1.f, -1.f, 180.f}, {-1.f, 1.f, 180.f}};
76 for (const auto& direction : diagonal)
77 if (matches(point.x + direction.x * cellSize, point.z + direction.z * cellSize)) return direction.yaw;
78 return 0.f;
79}
80
81void applyRandomTransform(ProcgenPoint& point, const ObjectTransformRange& range, RandomStream& random) {
82 point.pitch += random.between(range.minPitch, range.maxPitch);
83 point.yaw += random.between(range.minYaw, range.maxYaw);
84 point.roll += random.between(range.minRoll, range.maxRoll);
85 if (range.uniformScale) {
86 const float scale = random.between(range.minScaleX, range.maxScaleX);
87 point.scaleX *= scale;
88 point.scaleY *= scale;
89 point.scaleZ *= scale;
90 } else {
91 point.scaleX *= random.between(range.minScaleX, range.maxScaleX);
92 point.scaleY *= random.between(range.minScaleY, range.maxScaleY);
93 point.scaleZ *= random.between(range.minScaleZ, range.maxScaleZ);
94 }
95}
96
97Result<void> tagPoint(PointSet& output, int index, const std::string& asset, std::string role, std::int64_t parent) {
98 auto result = output.trySetStringAttribute(index, "asset", asset);
99 if (!result.ok()) return result;
100 result = output.trySetStringAttribute(index, "object_role", role);
101 if (!result.ok()) return result;
102 return output.trySetIntAttribute(index, "parent_index", parent);
103}
104
105} // namespace
106
108 if (asset.empty())
110 {}, "procgen.objectBuild"));
111 if (!std::isfinite(weight) || weight <= 0.f)
113 "asset weight must be finite and positive", asset, {},
114 "procgen.objectBuild"));
115 assets_.push_back({std::move(asset), weight});
116 return Result<void>::success();
117}
118
119void ObjectBuildLayer::clearAssets() { assets_.clear(); }
120void ObjectBuildLayer::setSeed(std::uint32_t seed) noexcept { seed_ = seed == 0 ? 1 : seed; }
121void ObjectBuildLayer::setLayerOffset(float x, float y, float z) noexcept {
122 offsetX_ = x;
123 offsetY_ = y;
124 offsetZ_ = z;
125}
126
128 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) || x == 0.f || y == 0.f || z == 0.f)
130 "layer scale must be finite and non-zero", "scale", {},
131 "procgen.objectBuild"));
132 scaleX_ = x;
133 scaleY_ = y;
134 scaleZ_ = z;
135 return Result<void>::success();
136}
137
139 if (!std::isfinite(radius) || radius < 0.f)
141 "position radius must be finite and non-negative", {}, {},
142 "procgen.objectBuild"));
143 positionRadius_ = radius;
144 return Result<void>::success();
145}
146
148 auto valid = validateRange(range);
149 if (!valid.ok()) return valid;
150 transform_ = range;
151 return Result<void>::success();
152}
153
154Result<void> ObjectBuildLayer::setRandomRotation(float minPitch, float maxPitch, float minYaw, float maxYaw,
155 float minRoll, float maxRoll) {
156 auto candidate = transform_;
157 candidate.minPitch = minPitch;
158 candidate.maxPitch = maxPitch;
159 candidate.minYaw = minYaw;
160 candidate.maxYaw = maxYaw;
161 candidate.minRoll = minRoll;
162 candidate.maxRoll = maxRoll;
163 return setRandomTransform(candidate);
164}
165
166Result<void> ObjectBuildLayer::setRandomScale(float minX, float maxX, float minY, float maxY, float minZ, float maxZ,
167 bool uniform) {
168 auto candidate = transform_;
169 candidate.minScaleX = minX;
170 candidate.maxScaleX = maxX;
171 candidate.minScaleY = minY;
172 candidate.maxScaleY = maxY;
173 candidate.minScaleZ = minZ;
174 candidate.maxScaleZ = maxZ;
175 candidate.uniformScale = uniform;
176 return setRandomTransform(candidate);
177}
178
179Result<void> ObjectBuildLayer::setOrientation(float cellSize, float yawOffset, bool invert) {
180 if (!std::isfinite(cellSize) || cellSize <= 0.f)
182 DiagnosticCode::InvalidArgument, "orientation cell size must be positive", {}, {}, "procgen.objectBuild"));
183 if (!std::isfinite(yawOffset))
185 DiagnosticCode::InvalidArgument, "orientation yaw offset must be finite", {}, {}, "procgen.objectBuild"));
186 orient_ = true;
187 orientationCellSize_ = cellSize;
188 orientationYawOffset_ = yawOffset;
189 invertOrientation_ = invert;
190 return Result<void>::success();
191}
192
193void ObjectBuildLayer::disableOrientation() noexcept { orient_ = false; }
194
195void ObjectBuildLayer::setPlaceOnTop(bool enabled, bool useLowest, float topOffset) noexcept {
196 placeOnTop_ = enabled;
197 useLowest_ = useLowest;
198 topOffset_ = std::isfinite(topOffset) ? topOffset : 0.f;
199}
200
203 if (asset.empty())
205 "child.asset", {}, "procgen.objectBuild"));
206 if (count < 0)
208 DiagnosticCode::InvalidArgument, "child count must be non-negative", asset, {}, "procgen.objectBuild"));
209 if (!std::isfinite(radius) || radius < 0.f)
211 "child radius must be finite and non-negative", asset, {},
212 "procgen.objectBuild"));
213 auto valid = validateRange(range);
214 if (!valid.ok()) return valid;
215 children_.push_back({std::move(asset), count, radius, range});
216 return Result<void>::success();
217}
218
219Result<void> ObjectBuildLayer::addChild(std::string asset, int count, float radius, float minScale, float maxScale,
220 float minYaw, float maxYaw) {
222 range.minScaleX = minScale;
223 range.maxScaleX = maxScale;
224 range.minScaleY = minScale;
225 range.maxScaleY = maxScale;
226 range.minScaleZ = minScale;
227 range.maxScaleZ = maxScale;
228 range.uniformScale = true;
229 range.minYaw = minYaw;
230 range.maxYaw = maxYaw;
231 return addChildRule(std::move(asset), count, radius, range);
232}
233
234void ObjectBuildLayer::clearChildRules() { children_.clear(); }
235
237 if (assets_.empty())
239 DiagnosticCode::InvalidArgument, "object build layer has no assets", "assets", {}, "procgen.objectBuild"));
240 if (orient_ && !orientation)
242 DiagnosticCode::InvalidArgument, "orientation is enabled but no orientation layer was supplied", {}, {},
243 "procgen.objectBuild"));
244 float totalWeight = 0.f;
245 for (const auto& asset : assets_) totalWeight += asset.weight;
246 if (!std::isfinite(totalWeight) || totalWeight <= 0.f)
248 DiagnosticCode::InvalidArgument, "total asset weight is invalid", {}, {}, "procgen.objectBuild"));
249
250 std::size_t childCount = 0;
251 for (const auto& child : children_) childCount += std::size_t(child.count);
253 output.reserve(source.points().size() * (childCount + 1));
254 for (std::size_t sourceIndex = 0; sourceIndex < source.points().size(); ++sourceIndex) {
255 const auto& input = source.points()[sourceIndex];
256 RandomStream random{pointKey(input, sourceIndex, seed_)};
257 float selection = random.unit() * totalWeight;
258 const Asset* selected = &assets_.back();
259 for (const auto& asset : assets_) {
260 selection -= asset.weight;
261 if (selection < 0.f) {
262 selected = &asset;
263 break;
264 }
265 }
266
267 auto appended = output.appendPointFrom(source, sourceIndex);
268 if (!appended.ok()) return Result<PointSet>::failure(appended.status());
269 const int parentIndex = appended.value();
270 auto& parent = output.mutablePoint(std::size_t(parentIndex));
271 if (positionRadius_ > 0.f) {
272 const float angle = random.unit() * 6.283185307179586f;
273 const float radius = std::sqrt(random.unit()) * positionRadius_;
274 parent.x += std::cos(angle) * radius;
275 parent.z += std::sin(angle) * radius;
276 }
277 parent.x += offsetX_;
278 parent.y += offsetY_;
279 parent.z += offsetZ_;
280 if (placeOnTop_) {
281 const char* attribute = useLowest_ ? "surface_lowest_y" : "surface_highest_y";
282 if (source.hasFloatAttribute(int(sourceIndex), attribute))
283 parent.y = source.getFloatAttribute(int(sourceIndex), attribute, parent.y) + topOffset_ + offsetY_;
284 }
285 applyRandomTransform(parent, transform_, random);
286 parent.scaleX *= scaleX_;
287 parent.scaleY *= scaleY_;
288 parent.scaleZ *= scaleZ_;
289 if (orient_)
290 parent.yaw = orientedYaw(input, *orientation, orientationCellSize_, invertOrientation_) +
291 orientationYawOffset_ + random.between(transform_.minYaw, transform_.maxYaw);
292 auto tagged = tagPoint(output, parentIndex, selected->id, "parent", -1);
293 if (!tagged.ok()) return Result<PointSet>::failure(tagged.status());
294
295 for (std::size_t ruleIndex = 0; ruleIndex < children_.size(); ++ruleIndex) {
296 const auto& rule = children_[ruleIndex];
297 RandomStream childRandom{mix(pointKey(input, sourceIndex, seed_) ^ (std::uint64_t(ruleIndex + 1) << 32U))};
298 for (int childOrdinal = 0; childOrdinal < rule.count; ++childOrdinal) {
299 ProcgenPoint child = parent;
300 child.id = derivePointId(parent.id != 0 ? parent.id : pointKey(input, sourceIndex, seed_),
301 std::uint64_t(ruleIndex) * 1000000ULL + std::uint64_t(childOrdinal + 1));
302 const float angle = childRandom.unit() * 6.283185307179586f;
303 const float radius = std::sqrt(childRandom.unit()) * rule.radius;
304 child.x += std::cos(angle) * radius;
305 child.z += std::sin(angle) * radius;
306 child.pitch = 0.f;
307 child.yaw = 0.f;
308 child.roll = 0.f;
309 child.scaleX = 1.f;
310 child.scaleY = 1.f;
311 child.scaleZ = 1.f;
312 applyRandomTransform(child, rule.transform, childRandom);
313 const int childIndex = output.appendPoint(child);
314 tagged = tagPoint(output, childIndex, rule.asset, "child", parentIndex);
315 if (!tagged.ok()) return Result<PointSet>::failure(tagged.status());
316 }
317 }
318 }
319 return Result<PointSet>::success(std::move(output));
320}
321
323 std::ostringstream out;
324 out << "EVPCG_OBJECT_LAYER 1\n" << std::setprecision(9);
325 out << "SEED " << seed_ << '\n';
326 out << "OFFSET " << offsetX_ << ' ' << offsetY_ << ' ' << offsetZ_ << '\n';
327 out << "SCALE " << scaleX_ << ' ' << scaleY_ << ' ' << scaleZ_ << '\n';
328 out << "POSITION_RADIUS " << positionRadius_ << '\n';
329 out << "RANDOM " << transform_.minPitch << ' ' << transform_.maxPitch << ' ' << transform_.minYaw << ' '
330 << transform_.maxYaw << ' ' << transform_.minRoll << ' ' << transform_.maxRoll << ' ' << transform_.minScaleX
331 << ' ' << transform_.maxScaleX << ' ' << transform_.minScaleY << ' ' << transform_.maxScaleY << ' '
332 << transform_.minScaleZ << ' ' << transform_.maxScaleZ << ' ' << int(transform_.uniformScale) << '\n';
333 out << "ORIENTATION " << int(orient_) << ' ' << orientationCellSize_ << ' ' << orientationYawOffset_ << ' '
334 << int(invertOrientation_) << '\n';
335 out << "PLACE " << int(placeOnTop_) << ' ' << int(useLowest_) << ' ' << topOffset_ << '\n';
336 for (const auto& asset : assets_) out << "ASSET " << std::quoted(asset.id) << ' ' << asset.weight << '\n';
337 for (const auto& child : children_) {
338 const auto& t = child.transform;
339 out << "CHILD " << std::quoted(child.asset) << ' ' << child.count << ' ' << child.radius << ' ' << t.minPitch
340 << ' ' << t.maxPitch << ' ' << t.minYaw << ' ' << t.maxYaw << ' ' << t.minRoll << ' ' << t.maxRoll << ' '
341 << t.minScaleX << ' ' << t.maxScaleX << ' ' << t.minScaleY << ' ' << t.maxScaleY << ' ' << t.minScaleZ
342 << ' ' << t.maxScaleZ << ' ' << int(t.uniformScale) << '\n';
343 }
344 out << "END\n";
345 return out.str();
346}
347
349 ObjectBuildLayer replacement;
350 std::istringstream input{std::string(definition)};
351 std::string magic;
352 int version = 0;
353 if (!(input >> magic >> version) || magic != "EVPCG_OBJECT_LAYER" || version != 1)
354 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid object layer header",
355 {}, {}, "procgen.objectBuild"));
356 std::string line;
357 std::getline(input, line);
358 bool ended = false;
359 while (std::getline(input, line)) {
360 if (line.empty()) continue;
361 std::istringstream record(line);
362 std::string kind;
363 record >> kind;
364 if (kind == "END") {
365 ended = true;
366 break;
367 }
368 if (kind == "SEED") {
369 std::uint32_t seed = 0;
370 if (!(record >> seed))
372 {}, {}, "procgen.objectBuild"));
373 replacement.setSeed(seed);
374 } else if (kind == "OFFSET") {
375 float x = 0.f, y = 0.f, z = 0.f;
376 if (!(record >> x >> y >> z) || !std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z))
378 {}, {}, "procgen.objectBuild"));
379 replacement.setLayerOffset(x, y, z);
380 } else if (kind == "SCALE") {
381 float x = 0.f, y = 0.f, z = 0.f;
382 if (!(record >> x >> y >> z))
384 {}, {}, "procgen.objectBuild"));
385 auto applied = replacement.setLayerScale(x, y, z);
386 if (!applied.ok()) return applied;
387 } else if (kind == "POSITION_RADIUS") {
388 float radius = 0.f;
389 if (!(record >> radius))
391 DiagnosticCode::InvalidArgument, "invalid POSITION_RADIUS record", {}, {}, "procgen.objectBuild"));
392 auto applied = replacement.setPositionRadius(radius);
393 if (!applied.ok()) return applied;
394 } else if (kind == "RANDOM") {
396 int uniform = 0;
397 if (!(record >> range.minPitch >> range.maxPitch >> range.minYaw >> range.maxYaw >> range.minRoll >>
398 range.maxRoll >> range.minScaleX >> range.maxScaleX >> range.minScaleY >> range.maxScaleY >>
399 range.minScaleZ >> range.maxScaleZ >> uniform) ||
400 (uniform != 0 && uniform != 1))
402 {}, {}, "procgen.objectBuild"));
403 range.uniformScale = uniform != 0;
404 auto applied = replacement.setRandomTransform(range);
405 if (!applied.ok()) return applied;
406 } else if (kind == "ORIENTATION") {
407 int enabled = 0, invert = 0;
408 float cell = 0.f, offset = 0.f;
409 if (!(record >> enabled >> cell >> offset >> invert) || (enabled != 0 && enabled != 1) ||
410 (invert != 0 && invert != 1))
412 DiagnosticCode::InvalidArgument, "invalid ORIENTATION record", {}, {}, "procgen.objectBuild"));
413 if (enabled) {
414 auto applied = replacement.setOrientation(cell, offset, invert != 0);
415 if (!applied.ok()) return applied;
416 }
417 } else if (kind == "PLACE") {
418 int enabled = 0, lowest = 0;
419 float offset = 0.f;
420 if (!(record >> enabled >> lowest >> offset) || (enabled != 0 && enabled != 1) ||
421 (lowest != 0 && lowest != 1) || !std::isfinite(offset))
423 {}, {}, "procgen.objectBuild"));
424 replacement.setPlaceOnTop(enabled != 0, lowest != 0, offset);
425 } else if (kind == "ASSET") {
426 std::string asset;
427 float weight = 0.f;
428 if (!(record >> std::quoted(asset) >> weight))
430 {}, {}, "procgen.objectBuild"));
431 auto applied = replacement.addAsset(std::move(asset), weight);
432 if (!applied.ok()) return applied;
433 } else if (kind == "CHILD") {
434 std::string asset;
435 int count = 0, uniform = 0;
436 float radius = 0.f;
438 if (!(record >> std::quoted(asset) >> count >> radius >> range.minPitch >> range.maxPitch >> range.minYaw >>
439 range.maxYaw >> range.minRoll >> range.maxRoll >> range.minScaleX >> range.maxScaleX >>
440 range.minScaleY >> range.maxScaleY >> range.minScaleZ >> range.maxScaleZ >> uniform) ||
441 (uniform != 0 && uniform != 1))
443 {}, {}, "procgen.objectBuild"));
444 range.uniformScale = uniform != 0;
445 auto applied = replacement.addChildRule(std::move(asset), count, radius, range);
446 if (!applied.ok()) return applied;
447 } else {
449 "unknown object layer record: " + kind, {}, {},
450 "procgen.objectBuild"));
451 }
452 record >> std::ws;
453 if (!record.eof())
455 DiagnosticCode::InvalidArgument, "trailing object layer record data", {}, {}, "procgen.objectBuild"));
456 }
457 if (!ended)
459 DiagnosticCode::InvalidArgument, "object layer END record is missing", {}, {}, "procgen.objectBuild"));
460 *this = std::move(replacement);
461 return Result<void>::success();
462}
463
464} // namespace eve::procgen
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
Stable, structured diagnostics shared by engine modules.
EvpackChunkInput input
Definition Evpack.cpp:170
float maximum[3]
float minimum[3]
TokenKind kind
size_t offset
Range range
std::array< float, 3 > scale
std::int32_t parent
bool ended
bool valid
bool diagonal
float radius
std::uint32_t seed
Definition PointSet.cpp:807
float t
RoadLaneDirection direction
std::uint32_t count
Cell cell
TacticalUnit * unit
Battle::Random random
uint32_t index
const UnitySourceAsset & source
glm::vec3 point
float qy
float qx
float angle
float qz
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
const T & value() const &
Borrow the value from a const lvalue after checking success.
Definition Result.h:308
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Deterministic TileWorldCreator-style object build layer.
Result< void > setOrientation(float cellSize, float yawOffset, bool invert)
Enable orientation toward the first matching N/E/S/W then diagonal orientation point.
Result< void > deserializeDefinition(std::string_view definition)
Atomically replace settings from schema version 1; unknown records are rejected.
Result< void > setRandomTransform(const ObjectTransformRange &range)
Configure independent random Euler and scale intervals for parent objects.
Result< void > addChildRule(std::string asset, int count, float radius, const ObjectTransformRange &range)
Add a child-spawn rule with its own deterministic transform stream.
Result< void > addAsset(std::string asset, float weight)
Add a weighted asset candidate. Zero and negative weights are rejected.
std::string serializeDefinition() const
Serialize all layer settings using the versioned EVPCG object-layer schema.
Result< void > setLayerScale(float x, float y, float z)
Set fixed component-wise scale multiplier. Values must be non-zero.
void setPlaceOnTop(bool enabled, bool useLowest, float topOffset) noexcept
Project output Y to surface_lowest_y or surface_highest_y, plus offset, when present.
void setLayerOffset(float x, float y, float z) noexcept
Set fixed layer translation applied after source transforms.
Result< PointSet > build(const PointSet &source, const PointSet *orientation=nullptr) const
Build attributed object points from source points.
Result< void > setRandomScale(float minX, float maxX, float minY, float maxY, float minZ, float maxZ, bool uniform)
Update only the parent scale randomization interval.
void clearChildRules()
Clears child rules.
Result< void > addChild(std::string asset, int count, float radius, float minScale, float maxScale, float minYaw, float maxYaw)
Script-friendly child rule using uniform scale and yaw ranges.
Result< void > setPositionRadius(float radius)
Set circular XZ position jitter radius.
void setSeed(std::uint32_t seed) noexcept
Set the named deterministic random stream seed.
Result< void > setRandomRotation(float minPitch, float maxPitch, float minYaw, float maxYaw, float minRoll, float maxRoll)
Update only the parent Euler randomization interval.
Script-friendly collection of attributed 3D samples.
Definition PointSet.h:59
void reserve(std::size_t count)
Reserve point storage without changing point or attribute row counts.
Definition PointSet.cpp:60
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
std::uint64_t derivePointId(std::uint64_t namespaceId, std::uint64_t ordinal)
Deterministically derive a non-zero stable point identity.
Definition PointSet.cpp:469
bool enabled
Random transform interval used by one object build rule.
One deterministic sample used by script-first procedural pipelines.
Definition PointSet.h:17
std::string id
Definition Widget.h:17