Skip to main content

oxiphysics_geometry/implicit_geometry/
types.rs

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