core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
primitives.hpp
Go to the documentation of this file.
1// core — device-callable analytic SDF leaf primitives: THE single source of the formulas.
2//
3// Layer 0 rung 0 of suite/docs/ANALYTIC_SDF_GEOMETRY.md. These leaves are the only place a signed
4// distance formula is written in the suite; the runtime ShapeNode/SceneView layer (geom/scene.hpp)
5// dispatches to them and contains no formulas of its own, and voro's compile-time provider template
6// consumes them directly. dem's dem_portable.hpp and voro's voro/sdf.hpp are ported onto them in
7// rungs 3-4.
8//
9// CONVENTIONS
10// * Sign: sdf < 0 inside solid, > 0 in the void, 0 on the surface (docs/CONVENTIONS.md).
11// * Leaves are ORIGIN-CENTRED in their canonical frame. Placement (translation, rotation,
12// isotropic scale) is the caller's / the scene transform's job — never a primitive parameter.
13// This is dem's contract (canonical body space + per-particle pos/quat/scale) and is why the
14// dem port in rung 3 is a relocation rather than a rewrite.
15// * Canonical axes are ADOPTED FROM dem VERBATIM (HollowCylinder about y). Canonicalising to a
16// single axis would insert a rotation whose float rounding breaks the bit-exact dem port.
17// * Exactness is per-primitive and load-bearing for consumers — see the `exact_distance` flag on
18// each leaf. Sphere/Box/HollowCylinder are true Euclidean distances; HollowCylinderShell is
19// sign-correct only (a lower bound in magnitude). Consumers needing wall crossings must bracket
20// and root-find on eval() along the segment, which needs only sign correctness.
21//
22// FAITHFULNESS
23// Every formula below is transcribed operation-for-operation (including association order and the
24// fmax/fmin vs `<` distinction) from its origin, so a float instantiation reproduces the origin
25// bit-for-bit. Origins are cited per leaf. Do not "simplify" these expressions.
26//
27// ANNOTATIONS (rung 2). Every leaf carries two compile-time flags, and they mean exactly this:
28// exact_distance — eval() is the true Euclidean signed distance, not merely sign-correct.
29// Layer 2's crossing/aperture code branches on this. UNDER-claiming costs a
30// little accuracy; OVER-claiming costs correctness, so when in doubt it is
31// false and the gate measures the actual bound behaviour.
32// analytic_gradient — grad() is closed form rather than finite differences.
33//
34// grad() IS ALWAYS THE GRADIENT OF THAT LEAF'S OWN eval(), never of some idealised distance the
35// eval only approximates. That single invariant is what lets the gate check every primitive the
36// same way (grad ~= FD(eval) everywhere off the kinks) instead of special-casing the
37// approximate leaves. Gradients are NOT normalised — |grad| = 1 is a property the exact leaves
38// have and the gate verifies, not something imposed by construction.
39//
40// C++17-clean so it can be pulled into CUDA translation units.
41#ifndef PECLET_CORE_GEOM_PRIMITIVES_HPP
42#define PECLET_CORE_GEOM_PRIMITIVES_HPP
43
45
46// NAMESPACE: the leaves live in peclet::core::geom::prim, NOT peclet::core::geom, because
47// geom/sdf.hpp already owns non-template host shapes of the same names (Sphere, Box,
48// HollowCylinder, Complement) that existing consumers -- refineToSdf, the AMR/diffusion tests, the
49// VTI path -- depend on. Those stay as the host concept + I/O layer and delegate INTO these leaves;
50// see docs/ANALYTIC_SDF_GEOMETRY.md Layer 0.
52
54
58template <class Shape, class Real>
60 const Real den = Real(2) * h;
61 const Real gx = s.eval(Vec3<Real>{p.x + h, p.y, p.z}) - s.eval(Vec3<Real>{p.x - h, p.y, p.z});
62 const Real gy = s.eval(Vec3<Real>{p.x, p.y + h, p.z}) - s.eval(Vec3<Real>{p.x, p.y - h, p.z});
63 const Real gz = s.eval(Vec3<Real>{p.x, p.y, p.z + h}) - s.eval(Vec3<Real>{p.x, p.y, p.z - h});
64 return Vec3<Real>{gx / den, gy / den, gz / den};
65}
66
69template <class Real>
70struct Sphere {
73 static constexpr bool exact_distance = true;
74 static constexpr bool analytic_gradient = true;
75
77 // len3(p) - radius, with dot3's left-to-right association preserved.
78 return detail::hdSqrt(p.x * p.x + p.y * p.y + p.z * p.z) - radius;
79 }
80
82 const Real l = detail::hdSqrt(p.x * p.x + p.y * p.y + p.z * p.z);
83 // At the centre the gradient is undefined (the medial axis is the single point p=0); pick +x
84 // so callers never see a NaN. Any direction is equally wrong there.
85 return (l > Real(0)) ? Vec3<Real>{p.x / l, p.y / l, p.z / l} : Vec3<Real>{1, 0, 0};
86 }
87};
88
91template <class Real>
92struct Box {
94 Real hx = Real(1), hy = Real(1), hz = Real(1);
95 static constexpr bool exact_distance = true;
96 static constexpr bool analytic_gradient = true;
97
99 const Real dx = detail::hdAbs(p.x) - hx;
100 const Real dy = detail::hdAbs(p.y) - hy;
101 const Real dz = detail::hdAbs(p.z) - hz;
102 const Real ox = detail::hdMax(dx, Real(0));
103 const Real oy = detail::hdMax(dy, Real(0));
104 const Real oz = detail::hdMax(dz, Real(0));
105 const Real outside = detail::hdSqrt(ox * ox + oy * oy + oz * oz);
106 // Nesting order hdMax(dx, hdMax(dy, dz)) is dem's — keep it.
108 return outside + inside;
109 }
110
112 const Real sx = p.x < Real(0) ? Real(-1) : Real(1);
113 const Real sy = p.y < Real(0) ? Real(-1) : Real(1);
114 const Real sz = p.z < Real(0) ? Real(-1) : Real(1);
115 const Real dx = detail::hdAbs(p.x) - hx;
116 const Real dy = detail::hdAbs(p.y) - hy;
117 const Real dz = detail::hdAbs(p.z) - hz;
118 if (dx > Real(0) || dy > Real(0) || dz > Real(0)) {
119 // Outside: the gradient is the direction to the nearest face/edge/corner.
120 const Real ox = detail::hdMax(dx, Real(0));
121 const Real oy = detail::hdMax(dy, Real(0));
122 const Real oz = detail::hdMax(dz, Real(0));
123 const Real l = detail::hdSqrt(ox * ox + oy * oy + oz * oz);
124 return (l > Real(0)) ? Vec3<Real>{sx * ox / l, sy * oy / l, sz * oz / l}
125 : Vec3<Real>{sx, 0, 0};
126 }
127 // Inside: eval reduces to max(dx,dy,dz), so the gradient is the unit axis of the LEAST
128 // negative term -- the nearest face.
129 if (dx >= dy && dx >= dz)
130 return Vec3<Real>{sx, 0, 0};
131 if (dy >= dz)
132 return Vec3<Real>{0, sy, 0};
133 return Vec3<Real>{0, 0, sz};
134 }
135};
136
145template <class Real>
149 static constexpr bool exact_distance = true;
150 static constexpr bool analytic_gradient = true;
151
153 const Real r = detail::hdSqrt(p.x * p.x + p.z * p.z);
154 const Real rMid = rOuter - thickness * Real(0.5);
155 const Real dx = detail::hdAbs(r - rMid) - thickness * Real(0.5);
156 const Real dy = detail::hdAbs(p.y) - height * Real(0.5);
157 const Real ox = detail::hdMax(dx, Real(0));
158 const Real oy = detail::hdMax(dy, Real(0));
159 const Real outside = detail::hdSqrt(ox * ox + oy * oy);
161 return outside + inside;
162 }
163
165 // The tube is a 2-D box in the (r, y) half-plane; differentiate there and map the radial
166 // component back through dr/dp = (x/r, 0, z/r).
167 const Real r = detail::hdSqrt(p.x * p.x + p.z * p.z);
168 const Real rMid = rOuter - thickness * Real(0.5);
169 const Real a = detail::hdAbs(r - rMid) - thickness * Real(0.5);
170 const Real b = detail::hdAbs(p.y) - height * Real(0.5);
171 const Real sr = (r - rMid) < Real(0) ? Real(-1) : Real(1); // outward across the wall
172 const Real sy = p.y < Real(0) ? Real(-1) : Real(1);
173 Real gr, gy;
174 if (a > Real(0) || b > Real(0)) {
175 const Real oa = detail::hdMax(a, Real(0)), ob = detail::hdMax(b, Real(0));
176 const Real l = detail::hdSqrt(oa * oa + ob * ob);
177 gr = (l > Real(0)) ? oa / l : Real(1);
178 gy = (l > Real(0)) ? ob / l : Real(0);
179 } else if (a >= b) {
180 gr = Real(1);
181 gy = Real(0);
182 } else {
183 gr = Real(0);
184 gy = Real(1);
185 }
186 // On the axis (r = 0) the radial direction is undefined; fall back to +x.
187 const Real ux = (r > Real(0)) ? p.x / r : Real(1);
188 const Real uz = (r > Real(0)) ? p.z / r : Real(0);
189 return Vec3<Real>{ux * gr * sr, gy * sy, uz * gr * sr};
190 }
191};
192
203template <class Real>
206 Real rOuter = Real(1), rInner = Real(0.5), height = Real(1);
207 static constexpr bool exact_distance = false;
208 static constexpr bool analytic_gradient = true;
209
213 // `<`-ordered max (std::max / voro's ternaries), NOT fmax — see detail::cmpMax.
214 Real m = r - rOuter;
215 m = detail::cmpMax(m, rInner - r);
216 m = detail::cmpMax(m, detail::hdAbs(z) - height * Real(0.5));
217 return m;
218 }
219
221 return evalRZ(detail::hdSqrt(p.x * p.x + p.y * p.y), p.z);
222 }
223
225 // eval is a max of three half-space terms: the gradient is that of whichever is active.
226 const Real r = detail::hdSqrt(p.x * p.x + p.y * p.y);
227 const Real tOuter = r - rOuter;
228 const Real tInner = rInner - r;
229 const Real tAxial = detail::hdAbs(p.z) - height * Real(0.5);
230 const Real ux = (r > Real(0)) ? p.x / r : Real(1);
231 const Real uy = (r > Real(0)) ? p.y / r : Real(0);
232 if (tOuter >= tInner && tOuter >= tAxial)
233 return Vec3<Real>{ux, uy, 0}; // outward radial
234 if (tInner >= tAxial)
235 return Vec3<Real>{-ux, -uy, 0}; // inward radial (into the bore)
236 return Vec3<Real>{0, 0, p.z < Real(0) ? Real(-1) : Real(1)};
237 }
238};
239
243template <class Real>
244struct Capsule {
247 static constexpr bool exact_distance = true;
248 static constexpr bool analytic_gradient = true;
249
252 return detail::hdSqrt(p.x * p.x + qy * qy + p.z * p.z) - radius;
253 }
254
257 const Real l = detail::hdSqrt(p.x * p.x + qy * qy + p.z * p.z);
258 return (l > Real(0)) ? Vec3<Real>{p.x / l, qy / l, p.z / l} : Vec3<Real>{1, 0, 0};
259 }
260};
261
264template <class Real>
265struct Torus {
268 static constexpr bool exact_distance = true;
269 static constexpr bool analytic_gradient = true;
270
272 const Real lr = detail::hdSqrt(p.x * p.x + p.z * p.z);
273 const Real qx = lr - majorRadius;
274 return detail::hdSqrt(qx * qx + p.y * p.y) - minorRadius;
275 }
276
278 const Real lr = detail::hdSqrt(p.x * p.x + p.z * p.z);
279 const Real qx = lr - majorRadius;
280 const Real l = detail::hdSqrt(qx * qx + p.y * p.y);
281 if (l <= Real(0)) // on the spine circle: gradient undefined
282 return Vec3<Real>{1, 0, 0};
283 const Real ux = (lr > Real(0)) ? p.x / lr : Real(1);
284 const Real uz = (lr > Real(0)) ? p.z / lr : Real(0);
285 const Real gr = qx / l;
286 return Vec3<Real>{ux * gr, p.y / l, uz * gr};
287 }
288};
289
298template <class Real>
299struct Cone {
302 static constexpr bool exact_distance = true;
303 static constexpr bool analytic_gradient = false; // FD fallback: the piecewise form is not worth
304 // a hand-derived gradient at this rung
305
307 const Real qx = detail::hdSqrt(p.x * p.x + p.z * p.z);
308 const Real qy = p.y;
309 // (a) nearest point on the end caps, clamped to the annulus of the relevant radius
310 const Real rCap = (qy < Real(0)) ? rBottom : rTop;
311 const Real ax = qx - detail::hdMin(qx, rCap);
312 const Real ay = detail::hdAbs(qy) - halfHeight;
313 // (b) nearest point on the lateral edge, as a clamped projection onto the slant segment
314 const Real k1x = rTop, k1y = halfHeight;
315 const Real k2x = rTop - rBottom, k2y = Real(2) * halfHeight;
316 const Real d2k2 = k2x * k2x + k2y * k2y;
317 const Real tRaw = ((k1x - qx) * k2x + (k1y - qy) * k2y) / ((d2k2 > Real(0)) ? d2k2 : Real(1));
318 const Real t = detail::hdMin(detail::hdMax(tRaw, Real(0)), Real(1));
319 const Real bx = qx - k1x + k2x * t;
320 const Real by = qy - k1y + k2y * t;
321 // Inside iff we are on the solid side of BOTH the lateral surface and the caps.
322 const Real sgn = (bx < Real(0) && ay < Real(0)) ? Real(-1) : Real(1);
323 const Real da = ax * ax + ay * ay;
324 const Real db = bx * bx + by * by;
326 }
327
330 PECLET_HD Vec3<Real> grad(Vec3<Real> p) const { return gradient(*this, p, Real(1e-4)); }
331};
332
339template <class Real>
340struct Ellipsoid {
342 Real rx = Real(1), ry = Real(1), rz = Real(1);
343 static constexpr bool exact_distance = false;
344 static constexpr bool analytic_gradient = false;
345
347 const Real ax = p.x / rx, ay = p.y / ry, az = p.z / rz;
348 const Real k0 = detail::hdSqrt(ax * ax + ay * ay + az * az);
349 const Real bx = p.x / (rx * rx), by = p.y / (ry * ry), bz = p.z / (rz * rz);
350 const Real k1 = detail::hdSqrt(bx * bx + by * by + bz * bz);
351 if (k1 <= Real(0)) // the centre: return the inradius, negative (inside)
352 return -detail::hdMin(rx, detail::hdMin(ry, rz));
353 return k0 * (k0 - Real(1)) / k1;
354 }
355
358 PECLET_HD Vec3<Real> grad(Vec3<Real> p) const { return gradient(*this, p, Real(1e-4)); }
359};
360
369template <class Real>
372 Real rx = Real(1), ry = Real(1), rz = Real(1), e = Real(2);
373 static constexpr bool exact_distance = false;
374 static constexpr bool analytic_gradient = false;
375
377 const Real ux = detail::hdAbs(p.x) / rx;
378 const Real uy = detail::hdAbs(p.y) / ry;
379 const Real uz = detail::hdAbs(p.z) / rz;
380 const Real f = detail::hdPow(ux, e) + detail::hdPow(uy, e) + detail::hdPow(uz, e) - Real(1);
381 // |grad f| = e * sqrt( sum_i ( u_i^(e-1) / r_i )^2 )
382 const Real gx = detail::hdPow(ux, e - Real(1)) / rx;
383 const Real gy = detail::hdPow(uy, e - Real(1)) / ry;
384 const Real gz = detail::hdPow(uz, e - Real(1)) / rz;
385 const Real gn = e * detail::hdSqrt(gx * gx + gy * gy + gz * gz);
387 if (!(gn > Real(0))) // the centre (and any point where the implicit gradient vanishes)
388 return -inradius;
389 // CLAMP AT THE INRADIUS. |grad f| -> 0 as p -> 0, so the raw quotient f/|grad f| dives to -inf
390 // near the centre: the rung-2 gate measured a Lipschitz constant of 779 and estimates 4x too
391 // deep before this clamp. No interior point can be farther inside than the largest ball that
392 // fits, so clamping there is both a hard geometric bound and what restores sane behaviour.
393 return detail::hdMax(f / gn, -inradius);
394 }
395
398 PECLET_HD Vec3<Real> grad(Vec3<Real> p) const { return gradient(*this, p, Real(1e-4)); }
399};
400
404template <class Shape>
405struct Rounded {
406 using value_type = typename Shape::value_type;
409 static constexpr bool exact_distance = Shape::exact_distance;
410 static constexpr bool analytic_gradient = Shape::analytic_gradient;
411
412 template <class Real>
414 return shape.eval(p) - radius;
415 }
416 template <class Real>
418 return shape.grad(p); // a constant offset does not change the gradient
419 }
420};
421
423template <class Shape>
426 static constexpr bool exact_distance = Shape::exact_distance;
427 static constexpr bool analytic_gradient = Shape::analytic_gradient;
428
429 template <class Real>
431 return -shape.eval(p);
432 }
433 template <class Real>
435 const Vec3<Real> g = shape.grad(p);
436 return Vec3<Real>{-g.x, -g.y, -g.z};
437 }
438};
439
440} // namespace peclet::core::geom::prim
441
442#endif // PECLET_CORE_GEOM_PRIMITIVES_HPP
PECLET_HD Real hdMin(Real a, Real b)
Definition portable.hpp:155
PECLET_HD Real hdPow(Real a, Real b)
Definition portable.hpp:159
PECLET_HD Real cmpMax(Real a, Real b)
Comparison-based max, <-ordered (ties keep the earlier operand) — std::max semantics,...
Definition portable.hpp:180
PECLET_HD Real hdMax(Real a, Real b)
Definition portable.hpp:151
PECLET_HD Real hdAbs(Real v)
Definition portable.hpp:147
PECLET_HD Real hdSqrt(Real v)
Definition portable.hpp:143
PECLET_HD Vec3< Real > gradient(const Shape &s, Vec3< Real > p, Real h)
Generic outward normal by central differences — the fallback for any leaf until rung 2 supplies close...
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
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
Real-templated POD 3-vector — the geometry layer's point type on host and device.
Definition portable.hpp:45
Axis-aligned solid box of half-extents (hx, hy, hz), centred on the origin.
PECLET_HD Real eval(Vec3< Real > p) const
static constexpr bool exact_distance
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
static constexpr bool analytic_gradient
Solid capsule about the y axis: a segment from (0,-halfLength,0) to (0,+halfLength,...
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
static constexpr bool analytic_gradient
static constexpr bool exact_distance
PECLET_HD Real eval(Vec3< Real > p) const
Negation: solid and void swap. Composes with any leaf (a container is the complement of a body).
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
static constexpr bool analytic_gradient
static constexpr bool exact_distance
PECLET_HD Real eval(Vec3< Real > p) const
Solid capped cone (frustum) about the y axis: radius rBottom at y = -halfHeight, radius rTop at y = +...
static constexpr bool exact_distance
PECLET_HD Real eval(Vec3< Real > p) const
static constexpr bool analytic_gradient
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
Finite differences over this leaf's own eval (the piecewise capped-cone form has no gradient worth ha...
Solid ellipsoid with semi-axes (rx, ry, rz), centred on the origin.
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
Finite differences over this leaf's own eval (the normalised implicit has no gradient worth hand-deri...
static constexpr bool analytic_gradient
static constexpr bool exact_distance
PECLET_HD Real eval(Vec3< Real > p) const
Solid tube wall about the z axis as a CSG intersection of half-spaces: max(r - rOuter,...
PECLET_HD Real eval(Vec3< Real > p) const
PECLET_HD Real evalRZ(Real r, Real z) const
Cross-section form: r is the in-plane radius, z the axial coordinate.
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
Solid tube wall about the y axis: outer radius rOuter, full height height, wall thickness thickness (...
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
PECLET_HD Real eval(Vec3< Real > p) const
Offset (rounded) shape: the inner solid grown outward by radius.
static constexpr bool exact_distance
typename Shape::value_type value_type
PECLET_HD Real eval(Vec3< Real > p) const
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
static constexpr bool analytic_gradient
Solid ball of radius radius, centred on the origin.
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
PECLET_HD Real eval(Vec3< Real > p) const
static constexpr bool exact_distance
static constexpr bool analytic_gradient
Solid superquadric with semi-axes (rx, ry, rz) and exponent e: the implicit |x/rx|^e + |y/ry|^e + |z/...
PECLET_HD Real eval(Vec3< Real > p) const
static constexpr bool analytic_gradient
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
Finite differences over this leaf's own eval (the Taubin estimate has no gradient worth hand-deriving...
Solid torus with its axis along y: tube of minor radius minorRadius swept around a circle of radius m...
PECLET_HD Real eval(Vec3< Real > p) const
PECLET_HD Vec3< Real > grad(Vec3< Real > p) const
static constexpr bool exact_distance
static constexpr bool analytic_gradient