core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
scene.hpp
Go to the documentation of this file.
1// core — runtime-dispatched SDF scene nodes: the layer that turns the compile-time leaves into
2// geometry a solver can be handed at run time.
3//
4// Layer 0 rung 1 of suite/docs/ANALYTIC_SDF_GEOMETRY.md. This header contains NO signed-distance
5// formulas: every leaf case dispatches into peclet::core::geom::prim (geom/primitives.hpp), and the
6// only arithmetic here is the conformal transform stack and the CSG combinators. voro keeps its
7// zero-overhead compile-time provider path over the leaves; dem and flow get the runtime scene
8// their Python-composed, per-particle heterogeneity requires.
9//
10// WHY RUNTIME DISPATCH IS FREE ENOUGH: dem already pays a per-probe `switch` on shape kind in its
11// hottest kernel today (narrowphase.hpp sdfEvalShape), and a simulation carries few distinct
12// shapes, so the branch is warp-coherent. flow's geometry derivation is setup-phase, where dispatch
13// cost is irrelevant.
14//
15// TRANSFORMS ARE CONFORMAL ONLY (contract 1): translation + quaternion + ISOTROPIC scale. A
16// non-uniform scale destroys the signed-distance property (d_world = s * d_canonical holds only for
17// conformal maps); anisotropy belongs in primitive parameters (box half-extents, ellipsoid
18// semi-axes). This is exactly dem's per-particle (pos, quat, scale x globalScale) contract, which
19// is what makes the rung-3 port a relocation.
20//
21// C++17-clean so it can be pulled into CUDA translation units.
22#ifndef PECLET_CORE_GEOM_SCENE_HPP
23#define PECLET_CORE_GEOM_SCENE_HPP
24
27
28namespace peclet::core::geom {
29
52
56
71enum class GridExtension : int { kObject = 0, kContainer = 1, kClamp = 2 };
72
74template <class Real>
82
84template <class Real>
85struct Transform {
87 Quat<Real> rotation{0, 0, 0, 1};
89};
90
94template <class Real>
96 return scale(invRotate(tr.rotation, sub(p, tr.translation)), Real(1) / tr.scale);
97}
98
100template <class Real>
101struct ShapeNode {
103 int aux0 = -1;
104 int aux1 = -1;
105 Real params[8] = {Real(1), 0, 0, 0, 0, 0, 0, 0};
107};
108
112template <class T>
113struct PoolPtr {
114 const T* data = nullptr;
115 PECLET_HD T operator()(long i) const { return data[i]; }
116};
117template <class T>
118struct TablePtr {
119 const T* data = nullptr;
120 PECLET_HD const T& operator()(long i) const { return data[i]; }
121};
122
126template <class Real, class Pool>
128 const Real fx = (p.x - d.origin.x) * d.invSpacing.x;
129 const Real fy = (p.y - d.origin.y) * d.invSpacing.y;
130 const Real fz = (p.z - d.origin.z) * d.invSpacing.z;
131 const Real cx = detail::hdMin(detail::hdMax(fx, Real(0)), Real(d.nx - 1));
132 const Real cy = detail::hdMin(detail::hdMax(fy, Real(0)), Real(d.ny - 1));
133 const Real cz = detail::hdMin(detail::hdMax(fz, Real(0)), Real(d.nz - 1));
134 const int ix = (int)cx, iy = (int)cy, iz = (int)cz;
135 const int ix1 = ix < d.nx - 1 ? ix + 1 : ix;
136 const int iy1 = iy < d.ny - 1 ? iy + 1 : iy;
137 const int iz1 = iz < d.nz - 1 ? iz + 1 : iz;
138 const Real tx = cx - ix, ty = cy - iy, tz = cz - iz;
139 const long nxny = (long)d.nx * d.ny;
140 const int off = d.offset;
141 const auto at = [&](int x, int y, int z) {
142 return (Real)pool(off + (long)z * nxny + (long)y * d.nx + x);
143 };
144 const Real c00 = at(ix, iy, iz) * (1 - tx) + at(ix1, iy, iz) * tx;
145 const Real c10 = at(ix, iy1, iz) * (1 - tx) + at(ix1, iy1, iz) * tx;
146 const Real c01 = at(ix, iy, iz1) * (1 - tx) + at(ix1, iy, iz1) * tx;
147 const Real c11 = at(ix, iy1, iz1) * (1 - tx) + at(ix1, iy1, iz1) * tx;
148 const Real c0 = c00 * (1 - ty) + c10 * ty;
149 const Real c1 = c01 * (1 - ty) + c11 * ty;
150 const Real val = c0 * (1 - tz) + c1 * tz;
151 if (d.extension == GridExtension::kClamp)
152 return val; // flat at the box face -- skip the residual (and its three divisions) entirely
153 const Real rx = (d.invSpacing.x > Real(0)) ? (fx - cx) / d.invSpacing.x : Real(0);
154 const Real ry = (d.invSpacing.y > Real(0)) ? (fy - cy) / d.invSpacing.y : Real(0);
155 const Real rz = (d.invSpacing.z > Real(0)) ? (fz - cz) / d.invSpacing.z : Real(0);
156 const Real residual = detail::hdSqrt(rx * rx + ry * ry + rz * rz);
157 return (d.extension == GridExtension::kObject) ? val + residual : val - residual;
158}
159
162template <class Real, class Grids, class Pool>
164 const Pool& pool) {
165 switch (n.kind) {
166 case kSphere:
167 return prim::Sphere<Real>{n.params[0]}.eval(p);
168 case kBox:
169 return prim::Box<Real>{n.params[0], n.params[1], n.params[2]}.eval(p);
170 case kHollowCylinder:
171 return prim::HollowCylinder<Real>{n.params[0], n.params[1], n.params[2]}.eval(p);
173 return prim::HollowCylinderShell<Real>{n.params[0], n.params[1], n.params[2]}.eval(p);
174 case kCapsule:
175 return prim::Capsule<Real>{n.params[0], n.params[1]}.eval(p);
176 case kTorus:
177 return prim::Torus<Real>{n.params[0], n.params[1]}.eval(p);
178 case kCone:
179 return prim::Cone<Real>{n.params[0], n.params[1], n.params[2]}.eval(p);
180 case kEllipsoid:
181 return prim::Ellipsoid<Real>{n.params[0], n.params[1], n.params[2]}.eval(p);
182 case kSuperquadric:
183 return prim::Superquadric<Real>{n.params[0], n.params[1], n.params[2], n.params[3]}.eval(p);
184 case kGrid:
185 return sampleGrid(p, grids(n.aux0), pool);
186 default:
187 return Real(1e9); // unknown kind: same "infinitely far" sentinel dem's sdfEval returns
188 }
189}
190
198template <class Real>
199struct Instance {
200 int shapeRoot = 0;
201 int materialId = -1;
207 false;
209};
210
214template <class Real>
216 return inst.centerPinned ? inst.center : inst.transform.translation;
217}
218template <class Real>
222
228template <class Real>
229struct SceneView {
230 const ShapeNode<Real>* nodes = nullptr;
231 int nodeCount = 0;
232 const GridDesc<Real>* grids = nullptr;
233 int gridCount = 0;
234 const float* samples = nullptr;
235 long sampleCount = 0;
236 const Instance<Real>* instances = nullptr;
238};
239
243constexpr int kMaxTreeDepth = 16;
244
253template <class Real, class Nodes, class Grids, class Pool>
254PECLET_HD Real evalTree(const Nodes& nodes, int nodeCount, int root, Vec3<Real> p,
255 const Grids& grids, const Pool& pool) {
256 struct Frame {
257 int node;
258 int stage;
260 Real scale;
261 Real left;
262 };
264 int sp = 0;
265 Real result = Real(1e9);
266
267 if (root < 0 || root >= nodeCount)
268 return result;
269
270 {
271 const auto& n = nodes(root);
272 stack[0] = Frame{root, 0, toCanonical(n.transform, p), n.transform.scale, Real(0)};
273 sp = 1;
274 }
275
276 while (sp > 0) {
277 Frame& f = stack[sp - 1];
278 const auto& n = nodes(f.node);
279
280 if (n.kind < kCsgBase) { // leaf: evaluate and unwind
281 result = f.scale * evalLeaf(n, f.can, grids, pool);
282 --sp;
283 } else if (f.stage == 0) { // descend into the left child
284 if (n.aux0 < 0 || n.aux0 >= nodeCount || sp >= kMaxTreeDepth)
285 return Real(1e9);
286 f.stage = 1;
287 const auto& c = nodes(n.aux0);
288 stack[sp] = Frame{n.aux0, 0, toCanonical(c.transform, f.can), c.transform.scale, Real(0)};
289 ++sp;
290 continue;
291 } else if (f.stage == 1) { // left value in hand; descend into the right child
292 if (n.aux1 < 0 || n.aux1 >= nodeCount || sp >= kMaxTreeDepth)
293 return Real(1e9);
294 f.left = result;
295 f.stage = 2;
296 const auto& c = nodes(n.aux1);
297 stack[sp] = Frame{n.aux1, 0, toCanonical(c.transform, f.can), c.transform.scale, Real(0)};
298 ++sp;
299 continue;
300 } else { // both children evaluated: combine
301 const Real l = f.left, r = result;
303 if (n.kind == kUnion)
305 else if (n.kind == kIntersection)
307 else // kDifference: L minus R
308 combined = detail::hdMax(l, -r);
309 result = f.scale * combined;
310 --sp;
311 }
312 }
313 return result;
314}
315
319template <class Real, class Grids, class Pool>
321 const Pool& pool) {
322 switch (n.kind) {
323 case kSphere:
324 return prim::Sphere<Real>{n.params[0]}.grad(p);
325 case kBox:
326 return prim::Box<Real>{n.params[0], n.params[1], n.params[2]}.grad(p);
327 case kHollowCylinder:
328 return prim::HollowCylinder<Real>{n.params[0], n.params[1], n.params[2]}.grad(p);
330 return prim::HollowCylinderShell<Real>{n.params[0], n.params[1], n.params[2]}.grad(p);
331 case kCapsule:
332 return prim::Capsule<Real>{n.params[0], n.params[1]}.grad(p);
333 case kTorus:
334 return prim::Torus<Real>{n.params[0], n.params[1]}.grad(p);
335 case kCone:
336 return prim::Cone<Real>{n.params[0], n.params[1], n.params[2]}.grad(p);
337 case kEllipsoid:
338 return prim::Ellipsoid<Real>{n.params[0], n.params[1], n.params[2]}.grad(p);
339 case kSuperquadric:
340 return prim::Superquadric<Real>{n.params[0], n.params[1], n.params[2], n.params[3]}.grad(p);
341 case kGrid: {
342 const GridDesc<Real>& d = grids(n.aux0);
343 const Real hx = Real(0.5) / d.invSpacing.x, hy = Real(0.5) / d.invSpacing.y,
344 hz = Real(0.5) / d.invSpacing.z;
345 return Vec3<Real>{(sampleGrid(Vec3<Real>{p.x + hx, p.y, p.z}, d, pool) -
346 sampleGrid(Vec3<Real>{p.x - hx, p.y, p.z}, d, pool)) /
347 (Real(2) * hx),
348 (sampleGrid(Vec3<Real>{p.x, p.y + hy, p.z}, d, pool) -
349 sampleGrid(Vec3<Real>{p.x, p.y - hy, p.z}, d, pool)) /
350 (Real(2) * hy),
351 (sampleGrid(Vec3<Real>{p.x, p.y, p.z + hz}, d, pool) -
352 sampleGrid(Vec3<Real>{p.x, p.y, p.z - hz}, d, pool)) /
353 (Real(2) * hz)};
354 }
355 default:
356 return Vec3<Real>{0, 0, 0};
357 }
358}
359
376template <class Real, class Nodes, class Grids, class Pool>
377PECLET_HD Real evalTreeGrad(const Nodes& nodes, int nodeCount, int root, Vec3<Real> p,
378 const Grids& grids, const Pool& pool, Vec3<Real>& grad) {
379 struct Frame {
380 int node;
381 int stage;
383 Real scale;
384 Real left;
386 };
388 int sp = 0;
389 Real result = Real(1e9);
390 Vec3<Real> resultGrad{0, 0, 0};
391 grad = resultGrad;
392
393 if (root < 0 || root >= nodeCount)
394 return result;
395
396 {
397 const auto& n = nodes(root);
398 stack[0] = Frame{
399 root, 0, toCanonical(n.transform, p), n.transform.scale, Real(0), Vec3<Real>{0, 0, 0}};
400 sp = 1;
401 }
402
403 while (sp > 0) {
404 Frame& f = stack[sp - 1];
405 const auto& n = nodes(f.node);
406
407 if (n.kind < kCsgBase) { // leaf: value + closed-form gradient, rotated into the parent frame
408 result = f.scale * evalLeaf(n, f.can, grids, pool);
409 resultGrad = rotate(n.transform.rotation, leafGrad(n, f.can, grids, pool));
410 --sp;
411 } else if (f.stage == 0) {
412 if (n.aux0 < 0 || n.aux0 >= nodeCount || sp >= kMaxTreeDepth) {
413 grad = Vec3<Real>{0, 0, 0};
414 return Real(1e9);
415 }
416 f.stage = 1;
417 const auto& c = nodes(n.aux0);
418 stack[sp] = Frame{n.aux0,
419 0,
420 toCanonical(c.transform, f.can),
421 c.transform.scale,
422 Real(0),
423 Vec3<Real>{0, 0, 0}};
424 ++sp;
425 continue;
426 } else if (f.stage == 1) {
427 if (n.aux1 < 0 || n.aux1 >= nodeCount || sp >= kMaxTreeDepth) {
428 grad = Vec3<Real>{0, 0, 0};
429 return Real(1e9);
430 }
431 f.left = result;
432 f.leftGrad = resultGrad;
433 f.stage = 2;
434 const auto& c = nodes(n.aux1);
435 stack[sp] = Frame{n.aux1,
436 0,
437 toCanonical(c.transform, f.can),
438 c.transform.scale,
439 Real(0),
440 Vec3<Real>{0, 0, 0}};
441 ++sp;
442 continue;
443 } else { // combine: same value expressions as evalTree; gradient = the active branch's
444 const Real l = f.left, r = result;
446 Vec3<Real> g;
447 if (n.kind == kUnion) {
449 g = (l <= r) ? f.leftGrad : resultGrad;
450 } else if (n.kind == kIntersection) {
452 g = (l >= r) ? f.leftGrad : resultGrad;
453 } else { // kDifference: max(l, -r); the right branch's gradient enters NEGATED
454 combined = detail::hdMax(l, -r);
455 g = (l >= -r) ? f.leftGrad : Vec3<Real>{-resultGrad.x, -resultGrad.y, -resultGrad.z};
456 }
457 result = f.scale * combined;
458 resultGrad = rotate(n.transform.rotation, g);
459 --sp;
460 }
461 }
462 grad = resultGrad;
463 return result;
464}
465
469template <class Real>
471 if (i < 0 || i >= sc.instanceCount)
472 return Real(1e9);
473 const Instance<Real>& inst = sc.instances[i];
474 const Vec3<Real> q = toCanonical(inst.transform, p);
475 const Real d = evalTree<Real>(TablePtr<ShapeNode<Real>>{sc.nodes}, sc.nodeCount, inst.shapeRoot,
476 q, TablePtr<GridDesc<Real>>{sc.grids}, PoolPtr<float>{sc.samples});
477 return inst.transform.scale * d;
478}
479
483template <class Real>
485 int nCandidates) {
486 Real m = Real(1e9);
487 for (int k = 0; k < nCandidates; ++k) {
488 const Real d = evalInstance(sc, candidates[k], p);
489 if (d < m)
490 m = d;
491 }
492 return m;
493}
494
498template <class Real>
500 Real m = Real(1e9);
501 for (int i = 0; i < sc.instanceCount; ++i) {
502 const Real d = evalInstance(sc, i, p);
503 if (d < m)
504 m = d;
505 }
506 return m;
507}
508
509} // namespace peclet::core::geom
510
511#endif // PECLET_CORE_GEOM_SCENE_HPP
PECLET_HD Real hdMin(Real a, Real b)
Definition portable.hpp:155
PECLET_HD Real hdMax(Real a, Real b)
Definition portable.hpp:151
PECLET_HD Real hdSqrt(Real v)
Definition portable.hpp:143
constexpr int kMaxTreeDepth
Maximum shape-tree depth the iterative evaluator supports.
Definition scene.hpp:243
PECLET_HD Real evalTreeGrad(const Nodes &nodes, int nodeCount, int root, Vec3< Real > p, const Grids &grids, const Pool &pool, Vec3< Real > &grad)
evalTree WITH its analytic gradient, one traversal (Layer 0's runtime layer catching up with the comp...
Definition scene.hpp:377
ShapeKind
Node kind tag.
Definition scene.hpp:35
@ kSphere
params[0] = radius
Definition scene.hpp:37
@ kDifference
max(L, -R) — L minus R
Definition scene.hpp:50
@ kEllipsoid
params[0..2] = rx, ry, rz (BOUND, under-estimates)
Definition scene.hpp:44
@ kTorus
params[0..1] = majorRadius, minorRadius (exact, y axis)
Definition scene.hpp:42
@ kIntersection
max(L, R)
Definition scene.hpp:49
@ kBox
params[0..2] = hx, hy, hz
Definition scene.hpp:39
@ kSuperquadric
params[0..3] = rx, ry, rz, exponent (BOUND, under-estimates)
Definition scene.hpp:45
@ kGrid
sampled field; aux0 = index into the GridDesc table
Definition scene.hpp:36
@ kHollowCylinderShell
params[0..2] = rOuter, rInner, height (sign-exact, z axis)
Definition scene.hpp:40
@ kCapsule
params[0..1] = radius, halfLength (exact, y axis)
Definition scene.hpp:41
@ kCone
params[0..2] = rBottom, rTop, halfHeight (exact, y axis)
Definition scene.hpp:43
@ kHollowCylinder
params[0..2] = rOuter, height, thickness (distance-exact, y axis)
Definition scene.hpp:38
@ kUnion
min(L, R) — aux0/aux1 = child node indices
Definition scene.hpp:48
PECLET_HD Real evalCandidates(const SceneView< Real > &sc, Vec3< Real > p, const int *candidates, int nCandidates)
Signed distance of a scene over a CANDIDATE LIST (contract 9): the union of the listed instances,...
Definition scene.hpp:484
PECLET_HD Vec3< Real > instanceVelocity(const Instance< Real > &inst, Vec3< Real > p)
Definition scene.hpp:219
PECLET_HD Vec3< Real > instanceCentre(const Instance< Real > &inst)
Rigid-body surface velocity of instance inst at world point p: linVel + angVel x (p - c),...
Definition scene.hpp:215
PECLET_HD Real evalLeaf(const ShapeNode< Real > &n, Vec3< Real > p, const Grids &grids, const Pool &pool)
Evaluate a single LEAF node at a point already in that node's canonical frame.
Definition scene.hpp:163
PECLET_HD Real evalTree(const Nodes &nodes, int nodeCount, int root, Vec3< Real > p, const Grids &grids, const Pool &pool)
Evaluate the shape tree rooted at root at point p (in the root's parent frame).
Definition scene.hpp:254
PECLET_HD Vec3< Real > leafGrad(const ShapeNode< Real > &n, Vec3< Real > p, const Grids &grids, const Pool &pool)
Closed-form gradient of one leaf in its canonical frame (unnormalised, mirroring the prim::* contract...
Definition scene.hpp:320
PECLET_HD Real evalScene(const SceneView< Real > &sc, Vec3< Real > p)
Convenience: the union of EVERY instance.
Definition scene.hpp:499
PECLET_HD Real sampleGrid(Vec3< Real > p, const GridDesc< Real > &d, const Pool &pool)
Trilinear sample of a grid field, with the clamp-plus-residual off-grid extension selected by d....
Definition scene.hpp:127
GridExtension
NOT YET A NODE KIND: prim::Rounded (offset/rounding) stays a compile-time wrapper for now.
Definition scene.hpp:71
PECLET_HD Real evalInstance(const SceneView< Real > &sc, int i, Vec3< Real > p)
Signed distance of ONE instance at world point p.
Definition scene.hpp:470
PECLET_HD Vec3< Real > toCanonical(const Transform< Real > &tr, Vec3< Real > p)
Map a point from the parent frame into this transform's canonical frame.
Definition scene.hpp:95
PECLET_HD Vec3< Real > rotate(Quat< Real > q, Vec3< Real > v)
Rotate v by q.
Definition portable.hpp:81
PECLET_HD Vec3< Real > add(Vec3< Real > a, Vec3< Real > b)
Definition portable.hpp:58
PECLET_HD Vec3< Real > invRotate(Quat< Real > q, Vec3< Real > v)
Rotate v by the conjugate of q — dem invRotateVector.
Definition portable.hpp:89
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
PECLET_HD Vec3< Real > sub(Vec3< Real > a, Vec3< Real > b)
Definition portable.hpp:62
PECLET_HD Vec3< Real > scale(Vec3< Real > a, Real s)
Definition portable.hpp:66
PECLET_HD Vec3< Real > cross(Vec3< Real > a, Vec3< Real > b)
Definition portable.hpp:74
double Real
Default host floating type. Device kernels may use float; conversions happen at the boundary.
Definition types.hpp:18
#define PECLET_HD
Definition portable.hpp:35
Descriptor for one sampled field inside a shared sample pool.
Definition scene.hpp:75
GridExtension extension
Definition scene.hpp:80
Vec3< Real > invSpacing
1 / node spacing, per axis (dem stores the inverse too)
Definition scene.hpp:79
int offset
start index of this field's samples in the pool
Definition scene.hpp:77
One placed copy of a shape tree (contract 7).
Definition scene.hpp:199
int materialId
consumer-defined; -1 = unset
Definition scene.hpp:201
bool centerPinned
true: center is an explicit world point (the origin included); false: the centre follows the body (co...
Definition scene.hpp:206
Vec3< Real > center
reference point of the rotation, in world coordinates
Definition scene.hpp:205
Transform< Real > transform
Definition scene.hpp:202
int shapeRoot
index of the root ShapeNode of this instance's tree
Definition scene.hpp:200
Adaptors so a raw pointer can stand in for a Kokkos View (Views already provide operator()),...
Definition scene.hpp:113
PECLET_HD T operator()(long i) const
Definition scene.hpp:115
Non-owning bundle of everything a scene evaluation needs (resolved decision 4).
Definition scene.hpp:229
const Instance< Real > * instances
Definition scene.hpp:236
const GridDesc< Real > * grids
Definition scene.hpp:232
const ShapeNode< Real > * nodes
Definition scene.hpp:230
One node of a flat shape tree. POD, trivially copyable, safe in a Kokkos View.
Definition scene.hpp:101
int aux0
CSG: left child index. kGrid: GridDesc table index.
Definition scene.hpp:103
Transform< Real > transform
Definition scene.hpp:106
int aux1
CSG: right child index.
Definition scene.hpp:104
PECLET_HD const T & operator()(long i) const
Definition scene.hpp:120
Conformal placement: canonical -> parent frame. Identity by default.
Definition scene.hpp:85
Axis-aligned solid box of half-extents (hx, hy, hz), centred on the origin.
Solid capsule about the y axis: a segment from (0,-halfLength,0) to (0,+halfLength,...
Solid capped cone (frustum) about the y axis: radius rBottom at y = -halfHeight, radius rTop at y = +...
Solid ellipsoid with semi-axes (rx, ry, rz), centred on the origin.
Solid tube wall about the z axis as a CSG intersection of half-spaces: max(r - rOuter,...
Solid tube wall about the y axis: outer radius rOuter, full height height, wall thickness thickness (...
Solid ball of radius radius, centred on the origin.
Solid superquadric with semi-axes (rx, ry, rz) and exponent e: the implicit |x/rx|^e + |y/ry|^e + |z/...
Solid torus with its axis along y: tube of minor radius minorRadius swept around a circle of radius m...