oxiphysics_geometry/implicit_geometry/types.rs
1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6
7/// Smooth intersection using polynomial blend.
8#[derive(Debug, Clone)]
9pub struct SdfSmoothIntersection<A, B> {
10 /// First operand.
11 pub a: A,
12 /// Second operand.
13 pub b: B,
14 /// Blend radius.
15 pub k: f64,
16}
17impl<A: Sdf, B: Sdf> SdfSmoothIntersection<A, B> {
18 /// Create a smooth intersection.
19 pub fn new(a: A, b: B, k: f64) -> Self {
20 Self { a, b, k }
21 }
22}
23/// Smooth union using the polynomial C¹ blend of Quilez.
24///
25/// `k` controls the blend radius; `k = 0` degenerates to hard union.
26#[derive(Debug, Clone)]
27pub struct SdfSmoothUnion<A, B> {
28 /// First operand.
29 pub a: A,
30 /// Second operand.
31 pub b: B,
32 /// Blend radius (≥ 0).
33 pub k: f64,
34}
35impl<A: Sdf, B: Sdf> SdfSmoothUnion<A, B> {
36 /// Create a smooth union.
37 pub fn new(a: A, b: B, k: f64) -> Self {
38 Self { a, b, k }
39 }
40}
41/// SDF for a hexagonal prism aligned with the Y axis.
42///
43/// `radius` is the circumscribed circle radius; `half_height` is along Y.
44#[derive(Debug, Clone, Copy)]
45pub struct SdfHexagonalPrism {
46 /// Circumscribed radius of the hexagonal cross-section \[m\].
47 pub radius: f64,
48 /// Half-height along Y \[m\].
49 pub half_height: f64,
50}
51impl SdfHexagonalPrism {
52 /// Create a hexagonal prism SDF.
53 pub fn new(radius: f64, half_height: f64) -> Self {
54 Self {
55 radius,
56 half_height,
57 }
58 }
59}
60/// Boolean operation type for the SDF expression tree.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum BoolOp {
63 /// Hard union.
64 Union,
65 /// Hard intersection.
66 Intersection,
67 /// Difference (left minus right).
68 Difference,
69 /// Smooth union (blend radius stored separately).
70 SmoothUnion,
71 /// Smooth intersection.
72 SmoothIntersection,
73 /// Smooth difference.
74 SmoothDifference,
75}
76/// A single contact point between two bodies.
77#[derive(Debug, Clone, Copy)]
78pub struct ContactPoint {
79 /// Contact position in world space.
80 pub position: [f64; 3],
81 /// Contact normal (from B toward A).
82 pub normal: [f64; 3],
83 /// Penetration depth.
84 pub depth: f64,
85}
86/// Translate an SDF by a constant offset.
87#[derive(Debug, Clone)]
88pub struct SdfTranslate<S> {
89 /// Wrapped SDF.
90 pub inner: S,
91 /// Translation vector.
92 pub offset: [f64; 3],
93}
94impl<S: Sdf> SdfTranslate<S> {
95 /// Create a translated SDF.
96 pub fn new(inner: S, offset: [f64; 3]) -> Self {
97 Self { inner, offset }
98 }
99}
100/// Infinite repetition of an SDF along all three axes.
101#[derive(Debug, Clone)]
102pub struct SdfRepeat<S> {
103 /// Wrapped SDF.
104 pub inner: S,
105 /// Cell period in X.
106 pub period_x: f64,
107 /// Cell period in Y.
108 pub period_y: f64,
109 /// Cell period in Z.
110 pub period_z: f64,
111}
112impl<S: Sdf> SdfRepeat<S> {
113 /// Create an infinitely repeated SDF.
114 pub fn new(inner: S, period_x: f64, period_y: f64, period_z: f64) -> Self {
115 Self {
116 inner,
117 period_x,
118 period_y,
119 period_z,
120 }
121 }
122}
123/// Scale an SDF uniformly (preserves exact signed distance).
124#[derive(Debug, Clone)]
125pub struct SdfScale<S> {
126 /// Wrapped SDF.
127 pub inner: S,
128 /// Uniform scale factor (> 0).
129 pub factor: f64,
130}
131impl<S: Sdf> SdfScale<S> {
132 /// Create a scaled SDF.
133 pub fn new(inner: S, factor: f64) -> Self {
134 Self { inner, factor }
135 }
136}
137/// SDF for a torus in the XZ plane centred at the origin.
138#[derive(Debug, Clone, Copy)]
139pub struct SdfTorus {
140 /// Major radius (centre to tube centre) \[m\].
141 pub major: f64,
142 /// Minor radius (tube radius) \[m\].
143 pub minor: f64,
144}
145impl SdfTorus {
146 /// Create a torus SDF.
147 pub fn new(major: f64, minor: f64) -> Self {
148 Self { major, minor }
149 }
150}
151/// SDF for an infinite plane defined by `n·p = d` where `n` is the unit normal.
152///
153/// Points satisfying `n·p > d` are *outside* (positive side).
154#[derive(Debug, Clone, Copy)]
155pub struct SdfPlane {
156 /// Unit outward normal.
157 pub normal: [f64; 3],
158 /// Signed distance of the plane from the origin along `normal`.
159 pub offset: f64,
160}
161impl SdfPlane {
162 /// Create a plane SDF from a (not necessarily unit) normal and offset.
163 pub fn new(normal: [f64; 3], offset: f64) -> Self {
164 Self {
165 normal: norm(normal),
166 offset,
167 }
168 }
169}
170/// Smooth difference (subtract `B` from `A`) with polynomial blend.
171#[derive(Debug, Clone)]
172pub struct SdfSmoothDifference<A, B> {
173 /// Shape to subtract from.
174 pub a: A,
175 /// Shape to subtract.
176 pub b: B,
177 /// Blend radius.
178 pub k: f64,
179}
180impl<A: Sdf, B: Sdf> SdfSmoothDifference<A, B> {
181 /// Create a smooth difference.
182 pub fn new(a: A, b: B, k: f64) -> Self {
183 Self { a, b, k }
184 }
185}
186/// Apply torsion (twist around Y axis) to an SDF.
187///
188/// `strength` is twist angle per unit of Y \[radians/m\].
189#[derive(Debug, Clone)]
190pub struct SdfTwist<S> {
191 /// Wrapped SDF.
192 pub inner: S,
193 /// Twist rate \[rad/m\].
194 pub strength: f64,
195}
196impl<S: Sdf> SdfTwist<S> {
197 /// Create a twisted SDF.
198 pub fn new(inner: S, strength: f64) -> Self {
199 Self { inner, strength }
200 }
201}
202/// A dense 3-D grid of SDF samples.
203#[derive(Debug, Clone)]
204pub struct SdfGrid {
205 /// Number of grid cells along X.
206 pub nx: usize,
207 /// Number of cells along Y.
208 pub ny: usize,
209 /// Number of cells along Z.
210 pub nz: usize,
211 /// World-space coordinates of cell (0,0,0).
212 pub origin: [f64; 3],
213 /// Uniform voxel spacing.
214 pub spacing: f64,
215 /// Flat SDF samples in (z, y, x) major order.
216 pub data: Vec<f64>,
217}
218impl SdfGrid {
219 /// Create an empty grid of given resolution and spacing.
220 pub fn new(nx: usize, ny: usize, nz: usize, origin: [f64; 3], spacing: f64) -> Self {
221 Self {
222 nx,
223 ny,
224 nz,
225 origin,
226 spacing,
227 data: vec![f64::INFINITY; nx * ny * nz],
228 }
229 }
230 /// Sample an SDF and fill the grid.
231 pub fn from_sdf<S: Sdf>(
232 sdf: &S,
233 nx: usize,
234 ny: usize,
235 nz: usize,
236 origin: [f64; 3],
237 spacing: f64,
238 ) -> Self {
239 let mut grid = Self::new(nx, ny, nz, origin, spacing);
240 for iz in 0..nz {
241 for iy in 0..ny {
242 for ix in 0..nx {
243 let p = grid.cell_center(ix, iy, iz);
244 grid.data[iz * ny * nx + iy * nx + ix] = sdf.dist(p);
245 }
246 }
247 }
248 grid
249 }
250 /// World-space position of cell centre `(ix, iy, iz)`.
251 pub fn cell_center(&self, ix: usize, iy: usize, iz: usize) -> [f64; 3] {
252 [
253 self.origin[0] + ix as f64 * self.spacing,
254 self.origin[1] + iy as f64 * self.spacing,
255 self.origin[2] + iz as f64 * self.spacing,
256 ]
257 }
258 /// Get the SDF value at integer index `(ix, iy, iz)`.
259 pub fn get(&self, ix: usize, iy: usize, iz: usize) -> f64 {
260 self.data[iz * self.ny * self.nx + iy * self.nx + ix]
261 }
262 /// Set the SDF value at integer index `(ix, iy, iz)`.
263 pub fn set(&mut self, ix: usize, iy: usize, iz: usize, v: f64) {
264 self.data[iz * self.ny * self.nx + iy * self.nx + ix] = v;
265 }
266 /// Trilinearly interpolate the SDF at world-space point `p`.
267 pub fn interpolate(&self, p: [f64; 3]) -> f64 {
268 let fx = (p[0] - self.origin[0]) / self.spacing;
269 let fy = (p[1] - self.origin[1]) / self.spacing;
270 let fz = (p[2] - self.origin[2]) / self.spacing;
271 let ix = (fx as usize).min(self.nx.saturating_sub(2));
272 let iy = (fy as usize).min(self.ny.saturating_sub(2));
273 let iz = (fz as usize).min(self.nz.saturating_sub(2));
274 let tx = (fx - ix as f64).clamp(0.0, 1.0);
275 let ty = (fy - iy as f64).clamp(0.0, 1.0);
276 let tz = (fz - iz as f64).clamp(0.0, 1.0);
277 let v000 = self.get(ix, iy, iz);
278 let v100 = self.get(ix + 1, iy, iz);
279 let v010 = self.get(ix, iy + 1, iz);
280 let v110 = self.get(ix + 1, iy + 1, iz);
281 let v001 = self.get(ix, iy, iz + 1);
282 let v101 = self.get(ix + 1, iy, iz + 1);
283 let v011 = self.get(ix, iy + 1, iz + 1);
284 let v111 = self.get(ix + 1, iy + 1, iz + 1);
285 let c00 = v000 * (1.0 - tx) + v100 * tx;
286 let c10 = v010 * (1.0 - tx) + v110 * tx;
287 let c01 = v001 * (1.0 - tx) + v101 * tx;
288 let c11 = v011 * (1.0 - tx) + v111 * tx;
289 let c0 = c00 * (1.0 - ty) + c10 * ty;
290 let c1 = c01 * (1.0 - ty) + c11 * ty;
291 c0 * (1.0 - tz) + c1 * tz
292 }
293 /// Apply morphological dilation (grow surface by `delta`).
294 pub fn dilate(&mut self, delta: f64) {
295 for v in self.data.iter_mut() {
296 *v -= delta;
297 }
298 }
299 /// Apply morphological erosion (shrink surface by `delta`).
300 pub fn erode(&mut self, delta: f64) {
301 for v in self.data.iter_mut() {
302 *v += delta;
303 }
304 }
305}
306/// SDF for a rounded cylinder aligned with the Y axis.
307///
308/// Cylinder of `radius` and `half_height`, with hemisphere caps of the same
309/// radius (i.e. a capsule-like shape but with flat lateral surface).
310#[derive(Debug, Clone, Copy)]
311pub struct SdfRoundedCylinder {
312 /// Cylinder radius \[m\].
313 pub radius: f64,
314 /// Half-height of the cylindrical body \[m\].
315 pub half_height: f64,
316 /// Corner rounding radius \[m\].
317 pub rounding: f64,
318}
319impl SdfRoundedCylinder {
320 /// Create a rounded cylinder SDF.
321 pub fn new(radius: f64, half_height: f64, rounding: f64) -> Self {
322 Self {
323 radius,
324 half_height,
325 rounding,
326 }
327 }
328}
329/// SDF intersection of two shapes.
330#[derive(Debug, Clone)]
331pub struct SdfIntersection<A, B> {
332 /// First operand.
333 pub a: A,
334 /// Second operand.
335 pub b: B,
336}
337impl<A: Sdf, B: Sdf> SdfIntersection<A, B> {
338 /// Create an intersection.
339 pub fn new(a: A, b: B) -> Self {
340 Self { a, b }
341 }
342}
343/// Extrude a 2-D XZ-plane profile along the Y axis.
344///
345/// The wrapped function takes `[x, z]` and returns the 2-D signed distance.
346pub struct SdfExtrude<F> {
347 /// 2-D profile function: `f([x,z]) -> signed_distance`.
348 pub profile: F,
349 /// Half-extent along the Y axis.
350 pub half_height: f64,
351}
352impl<F: Fn([f64; 2]) -> f64 + Send + Sync> SdfExtrude<F> {
353 /// Create an extruded SDF.
354 pub fn new(profile: F, half_height: f64) -> Self {
355 Self {
356 profile,
357 half_height,
358 }
359 }
360}
361/// Revolve a 2-D profile curve (given as a function of r and y) around the Y axis.
362///
363/// The wrapped function takes `(r, y)` where `r = sqrt(x²+z²)` and returns
364/// the signed distance in the 2-D profile plane.
365pub struct SdfRevolution<F> {
366 /// 2-D profile function: `f(r, y) -> signed_distance`.
367 pub profile: F,
368}
369impl<F: Fn(f64, f64) -> f64 + Send + Sync> SdfRevolution<F> {
370 /// Create a solid-of-revolution SDF.
371 pub fn new(profile: F) -> Self {
372 Self { profile }
373 }
374}
375/// SDF union of two shapes.
376#[derive(Debug, Clone)]
377pub struct SdfUnion<A, B> {
378 /// First operand.
379 pub a: A,
380 /// Second operand.
381 pub b: B,
382}
383impl<A: Sdf, B: Sdf> SdfUnion<A, B> {
384 /// Create a union.
385 pub fn new(a: A, b: B) -> Self {
386 Self { a, b }
387 }
388}
389/// SDF for a finite cone with apex at origin pointing along +Y.
390///
391/// `half_angle` is the half-apex angle in radians; `height` is the cone height.
392#[derive(Debug, Clone, Copy)]
393pub struct SdfCone {
394 /// Half-apex angle \[rad\].
395 pub half_angle: f64,
396 /// Cone height (distance from apex to base) \[m\].
397 pub height: f64,
398}
399impl SdfCone {
400 /// Create a cone SDF.
401 pub fn new(half_angle: f64, height: f64) -> Self {
402 Self { half_angle, height }
403 }
404}
405/// Expand or contract a shape by a constant offset.
406///
407/// Positive `offset` grows the shape; negative shrinks it.
408#[derive(Debug, Clone)]
409pub struct SdfOffset<S> {
410 /// Wrapped SDF.
411 pub inner: S,
412 /// Offset distance \[m\].
413 pub offset: f64,
414}
415impl<S: Sdf> SdfOffset<S> {
416 /// Create an offset SDF.
417 pub fn new(inner: S, offset: f64) -> Self {
418 Self { inner, offset }
419 }
420}
421/// Isosurface mesh extracted from a grid.
422#[derive(Debug, Clone, Default)]
423pub struct IsoMeshResult {
424 /// Triangle list (each entry is one triangle).
425 pub triangles: Vec<Triangle>,
426 /// Total vertex count (3 × triangle count without sharing).
427 pub vertex_count: usize,
428}
429/// SDF for a square-base pyramid with apex at `(0, height, 0)`.
430///
431/// Base is centred at origin in the XZ plane with half-side `half_base`.
432#[derive(Debug, Clone, Copy)]
433pub struct SdfPyramid {
434 /// Half-side of the square base \[m\].
435 pub half_base: f64,
436 /// Height of the pyramid \[m\].
437 pub height: f64,
438}
439impl SdfPyramid {
440 /// Create a pyramid SDF.
441 pub fn new(half_base: f64, height: f64) -> Self {
442 Self { half_base, height }
443 }
444}
445/// SDF difference: `A` minus `B`.
446#[derive(Debug, Clone)]
447pub struct SdfDifference<A, B> {
448 /// Shape to subtract from.
449 pub a: A,
450 /// Shape to subtract.
451 pub b: B,
452}
453impl<A: Sdf, B: Sdf> SdfDifference<A, B> {
454 /// Create a difference.
455 pub fn new(a: A, b: B) -> Self {
456 Self { a, b }
457 }
458}
459/// Displace an SDF surface with procedural value noise.
460///
461/// The displacement `amplitude` controls how far the surface is pushed;
462/// `scale` controls the noise spatial frequency.
463#[derive(Debug, Clone)]
464pub struct SdfNoiseDisplace<S> {
465 /// Wrapped SDF.
466 pub inner: S,
467 /// Noise spatial frequency scale.
468 pub noise_scale: f64,
469 /// Maximum displacement amplitude.
470 pub amplitude: f64,
471 /// Number of fBm octaves.
472 pub octaves: u32,
473}
474impl<S: Sdf> SdfNoiseDisplace<S> {
475 /// Create a noise-displaced SDF.
476 pub fn new(inner: S, noise_scale: f64, amplitude: f64, octaves: u32) -> Self {
477 Self {
478 inner,
479 noise_scale,
480 amplitude,
481 octaves,
482 }
483 }
484}
485/// SDF approximation for an axis-aligned ellipsoid with semi-axes `radii`.
486///
487/// Uses the Quilez approximation: exact on the boundary, approximate elsewhere.
488#[derive(Debug, Clone, Copy)]
489pub struct SdfEllipsoid {
490 /// Semi-axis lengths (rx, ry, rz).
491 pub radii: [f64; 3],
492}
493impl SdfEllipsoid {
494 /// Create an ellipsoid SDF.
495 pub fn new(rx: f64, ry: f64, rz: f64) -> Self {
496 Self {
497 radii: [rx, ry, rz],
498 }
499 }
500}
501/// A triangle in 3-D space (three vertices).
502#[derive(Debug, Clone, Copy)]
503pub struct Triangle {
504 /// First vertex.
505 pub v0: [f64; 3],
506 /// Second vertex.
507 pub v1: [f64; 3],
508 /// Third vertex.
509 pub v2: [f64; 3],
510}
511impl Triangle {
512 /// Compute the triangle's outward normal (unnormalized).
513 pub fn normal(&self) -> [f64; 3] {
514 cross(sub(self.v1, self.v0), sub(self.v2, self.v0))
515 }
516}
517/// Dynamic SDF expression tree node.
518///
519/// Allows building complex SDF scenes at runtime without generic type nesting.
520pub enum SdfNode {
521 /// A leaf: arbitrary boxed SDF.
522 Leaf(Box<dyn Sdf>),
523 /// A binary combination of two nodes.
524 Binary {
525 /// Binary operation.
526 op: BoolOp,
527 /// Left child.
528 left: Box<SdfNode>,
529 /// Right child.
530 right: Box<SdfNode>,
531 /// Blend radius (used for smooth ops).
532 k: f64,
533 },
534}
535impl SdfNode {
536 /// Create a leaf node from any `Sdf` implementor.
537 pub fn leaf<S: Sdf + 'static>(s: S) -> Self {
538 Self::Leaf(Box::new(s))
539 }
540 /// Combine two nodes with a boolean operation.
541 pub fn combine(op: BoolOp, left: Self, right: Self, k: f64) -> Self {
542 Self::Binary {
543 op,
544 left: Box::new(left),
545 right: Box::new(right),
546 k,
547 }
548 }
549 /// Evaluate the SDF tree at point `p`.
550 pub fn eval(&self, p: [f64; 3]) -> f64 {
551 match self {
552 Self::Leaf(s) => s.dist(p),
553 Self::Binary { op, left, right, k } => {
554 let a = left.eval(p);
555 let b = right.eval(p);
556 match op {
557 BoolOp::Union => a.min(b),
558 BoolOp::Intersection => a.max(b),
559 BoolOp::Difference => a.max(-b),
560 BoolOp::SmoothUnion => sdf_smooth_union(a, b, *k),
561 BoolOp::SmoothIntersection => sdf_smooth_intersection(a, b, *k),
562 BoolOp::SmoothDifference => sdf_smooth_difference(a, b, *k),
563 }
564 }
565 }
566 }
567}
568/// SDF for a triangular prism (equilateral cross-section) aligned with Y axis.
569///
570/// `side` is the equilateral triangle side length; `half_height` is along Y.
571#[derive(Debug, Clone, Copy)]
572pub struct SdfTriangularPrism {
573 /// Triangle side length \[m\].
574 pub side: f64,
575 /// Half-height along Y \[m\].
576 pub half_height: f64,
577}
578impl SdfTriangularPrism {
579 /// Create a triangular prism SDF.
580 pub fn new(side: f64, half_height: f64) -> Self {
581 Self { side, half_height }
582 }
583}
584/// SDF approximation for the Schoen gyroid minimal surface.
585///
586/// The gyroid is an infinitely periodic surface: `sin x cos y + sin y cos z + sin z cos x = 0`.
587/// This is an approximation; the exact gyroid has no closed-form SDF.
588#[derive(Debug, Clone, Copy)]
589pub struct SdfGyroid {
590 /// Spatial frequency scale.
591 pub scale: f64,
592 /// Thickness of the gyroid sheet.
593 pub thickness: f64,
594}
595impl SdfGyroid {
596 /// Create a gyroid SDF.
597 pub fn new(scale: f64, thickness: f64) -> Self {
598 Self { scale, thickness }
599 }
600}
601/// Hollow (shell) version of an SDF: the region between two offset surfaces.
602///
603/// Produces a thin shell of thickness `thickness` around the surface.
604#[derive(Debug, Clone)]
605pub struct SdfShell<S> {
606 /// Wrapped SDF.
607 pub inner: S,
608 /// Shell thickness \[m\] (half on each side of the zero isosurface).
609 pub thickness: f64,
610}
611impl<S: Sdf> SdfShell<S> {
612 /// Create a shell SDF of given `thickness`.
613 pub fn new(inner: S, thickness: f64) -> Self {
614 Self { inner, thickness }
615 }
616}
617/// Apply a bend deformation around the Y axis to an SDF.
618///
619/// `strength` controls how much bending occurs per unit of X.
620#[derive(Debug, Clone)]
621pub struct SdfBend<S> {
622 /// Wrapped SDF.
623 pub inner: S,
624 /// Bend rate \[rad/m\].
625 pub strength: f64,
626}
627impl<S: Sdf> SdfBend<S> {
628 /// Create a bent SDF.
629 pub fn new(inner: S, strength: f64) -> Self {
630 Self { inner, strength }
631 }
632}
633/// SDF for a line-segment tube: a capsule with flat ends (cylinder).
634///
635/// Computes distance to the axis segment `[a, b]` minus `radius`.
636#[derive(Debug, Clone, Copy)]
637pub struct SdfLineSegment {
638 /// Start point of the segment.
639 pub a: [f64; 3],
640 /// End point of the segment.
641 pub b: [f64; 3],
642 /// Tube radius \[m\].
643 pub radius: f64,
644}
645impl SdfLineSegment {
646 /// Create a line-segment SDF.
647 pub fn new(a: [f64; 3], b: [f64; 3], radius: f64) -> Self {
648 Self { a, b, radius }
649 }
650}
651/// Result of a ray-march query.
652#[derive(Debug, Clone, Copy)]
653pub struct RayMarchResult {
654 /// Whether the ray hit the surface.
655 pub hit: bool,
656 /// Distance along the ray to the hit point (if `hit` is true).
657 pub t: f64,
658 /// Hit point in world space.
659 pub point: [f64; 3],
660 /// Number of steps taken.
661 pub steps: u32,
662}
663/// Physics collision proxy that first checks a bounding sphere, then the detailed SDF.
664///
665/// Avoids expensive SDF evaluation for clearly non-colliding points.
666#[derive(Debug, Clone)]
667pub struct SdfBoundedProxy<S> {
668 /// The detailed SDF.
669 pub inner: S,
670 /// Centre of the bounding sphere.
671 pub bsphere_center: [f64; 3],
672 /// Radius of the bounding sphere.
673 pub bsphere_radius: f64,
674}
675impl<S: Sdf> SdfBoundedProxy<S> {
676 /// Create a bounded proxy.
677 pub fn new(inner: S, bsphere_center: [f64; 3], bsphere_radius: f64) -> Self {
678 Self {
679 inner,
680 bsphere_center,
681 bsphere_radius,
682 }
683 }
684 /// Returns true if point `p` is definitely outside the bounding sphere.
685 pub fn outside_bounds(&self, p: [f64; 3]) -> bool {
686 len(sub(p, self.bsphere_center)) > self.bsphere_radius
687 }
688}
689/// SDF for an axis-aligned box centred at the origin.
690///
691/// This is a standalone, self-contained version with `half_extents` per axis.
692#[derive(Debug, Clone, Copy)]
693pub struct SdfBox {
694 /// Half-extents along each axis.
695 pub half_extents: [f64; 3],
696}
697impl SdfBox {
698 /// Create a box SDF.
699 pub fn new(hx: f64, hy: f64, hz: f64) -> Self {
700 Self {
701 half_extents: [hx, hy, hz],
702 }
703 }
704}
705/// Result of an SDF collision query.
706#[derive(Debug, Clone, Copy)]
707pub struct SdfCollisionResult {
708 /// Penetration depth (positive = overlapping).
709 pub depth: f64,
710 /// Contact normal pointing from B toward A (unit vector).
711 pub normal: [f64; 3],
712 /// Contact point (on or near surface).
713 pub contact_point: [f64; 3],
714}
715/// SDF for a sphere of given `radius` centred at the origin.
716#[derive(Debug, Clone, Copy)]
717pub struct SdfSphere {
718 /// Sphere radius \[m\].
719 pub radius: f64,
720}
721impl SdfSphere {
722 /// Create a new sphere SDF with `radius`.
723 pub fn new(radius: f64) -> Self {
724 Self { radius }
725 }
726}