Skip to main content

oxiphysics_geometry/csg/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::ImplicitSurface;
6#[allow(unused_imports)]
7use super::functions::*;
8#[allow(unused_imports)]
9use super::functions_2::*;
10
11/// An infinite plane: `dot(p, normal) - d = 0`.
12pub struct SdfPlane {
13    /// Unit outward normal.
14    pub normal: [f64; 3],
15    /// Plane offset: points with `dot(p, normal) > d` are outside.
16    pub d: f64,
17}
18impl SdfPlane {
19    /// Create a new plane.
20    pub fn new(normal: [f64; 3], d: f64) -> Self {
21        Self {
22            normal: normalize(normal),
23            d,
24        }
25    }
26}
27/// Smooth CSG difference.
28///
29/// SDF = smooth_max(sdf_a, -sdf_b, k).
30pub struct CsgSmoothDifference {
31    /// The base shape.
32    pub a: Box<dyn ImplicitSurface>,
33    /// The shape to subtract.
34    pub b: Box<dyn ImplicitSurface>,
35    /// Smoothing factor.
36    pub k: f64,
37}
38impl CsgSmoothDifference {
39    /// Create a new smooth difference.
40    pub fn new(a: Box<dyn ImplicitSurface>, b: Box<dyn ImplicitSurface>, k: f64) -> Self {
41        Self { a, b, k }
42    }
43}
44/// A cone (infinite half-cone) opening upward from the origin.
45///
46/// Defined by its half-angle in radians. SDF is exact on the surface.
47pub struct SdfCone {
48    /// Apex position.
49    pub apex: [f64; 3],
50    /// Half-angle in radians.
51    pub half_angle: f64,
52    /// Height of the finite cone.
53    pub height: f64,
54}
55impl SdfCone {
56    /// Create a new cone.
57    pub fn new(apex: [f64; 3], half_angle: f64, height: f64) -> Self {
58        Self {
59            apex,
60            half_angle,
61            height,
62        }
63    }
64}
65/// A sphere defined by centre and radius.
66pub struct SdfSphere {
67    /// World-space centre.
68    pub center: [f64; 3],
69    /// Sphere radius (must be positive).
70    pub radius: f64,
71}
72impl SdfSphere {
73    /// Create a new sphere.
74    pub fn new(center: [f64; 3], radius: f64) -> Self {
75        Self { center, radius }
76    }
77}
78/// CSG intersection: the region inside both `a` and `b`.
79///
80/// SDF = max(sdf_a, sdf_b).
81pub struct CsgIntersection {
82    /// First operand.
83    pub a: Box<dyn ImplicitSurface>,
84    /// Second operand.
85    pub b: Box<dyn ImplicitSurface>,
86}
87impl CsgIntersection {
88    /// Create a new intersection.
89    pub fn new(a: Box<dyn ImplicitSurface>, b: Box<dyn ImplicitSurface>) -> Self {
90        Self { a, b }
91    }
92}
93/// A node in a CSG tree.
94///
95/// Each node is either a leaf (wrapping an `ImplicitSurface`) or an interior
96/// node combining two subtrees with a `CsgOp`.
97pub enum CsgTree {
98    /// A leaf primitive.
99    Leaf(Box<dyn ImplicitSurface>),
100    /// An interior combination node.
101    Node {
102        /// The boolean operation to apply.
103        op: CsgOp,
104        /// Left operand.
105        left: Box<CsgTree>,
106        /// Right operand.
107        right: Box<CsgTree>,
108    },
109}
110impl CsgTree {
111    /// Construct a leaf node from any `ImplicitSurface`.
112    pub fn leaf(s: impl ImplicitSurface + 'static) -> Self {
113        CsgTree::Leaf(Box::new(s))
114    }
115    /// Construct a union node.
116    pub fn union(left: CsgTree, right: CsgTree) -> Self {
117        CsgTree::Node {
118            op: CsgOp::Union,
119            left: Box::new(left),
120            right: Box::new(right),
121        }
122    }
123    /// Construct an intersection node.
124    pub fn intersection(left: CsgTree, right: CsgTree) -> Self {
125        CsgTree::Node {
126            op: CsgOp::Intersection,
127            left: Box::new(left),
128            right: Box::new(right),
129        }
130    }
131    /// Construct a difference node (left minus right).
132    pub fn difference(left: CsgTree, right: CsgTree) -> Self {
133        CsgTree::Node {
134            op: CsgOp::Difference,
135            left: Box::new(left),
136            right: Box::new(right),
137        }
138    }
139}
140/// CSG union: the region inside either `a` or `b`.
141///
142/// SDF = min(sdf_a, sdf_b).
143pub struct CsgUnion {
144    /// First operand.
145    pub a: Box<dyn ImplicitSurface>,
146    /// Second operand.
147    pub b: Box<dyn ImplicitSurface>,
148}
149impl CsgUnion {
150    /// Create a new union.
151    pub fn new(a: Box<dyn ImplicitSurface>, b: Box<dyn ImplicitSurface>) -> Self {
152        Self { a, b }
153    }
154}
155/// A torus centred at the origin lying in the XZ plane.
156pub struct SdfTorus {
157    /// Major radius (distance from centre to tube centre).
158    pub major_radius: f64,
159    /// Minor radius (tube radius).
160    pub minor_radius: f64,
161}
162impl SdfTorus {
163    /// Create a new torus.
164    pub fn new(major_radius: f64, minor_radius: f64) -> Self {
165        Self {
166            major_radius,
167            minor_radius,
168        }
169    }
170}
171/// CSG boolean operation type.
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub enum CsgOp {
174    /// Union: region inside either child.
175    Union,
176    /// Intersection: region inside both children.
177    Intersection,
178    /// Difference: region inside the left child but outside the right child.
179    Difference,
180}
181/// A capsule aligned with the Y axis.
182pub struct SdfCapsule {
183    /// Center of the capsule.
184    pub center: [f64; 3],
185    /// Capsule radius.
186    pub radius: f64,
187    /// Half-height of the cylindrical section.
188    pub half_height: f64,
189}
190impl SdfCapsule {
191    /// Create a new capsule.
192    pub fn new(center: [f64; 3], radius: f64, half_height: f64) -> Self {
193        Self {
194            center,
195            radius,
196            half_height,
197        }
198    }
199}
200/// Offset a `CsgTree` surface inward (negative `offset`) or outward (positive).
201///
202/// Returns a wrapper `SDF f(p) = original_sdf(p) - offset` so that the
203/// zero-level set is shifted by `offset` in the normal direction.
204///
205/// The returned object implements `ImplicitSurface`.
206pub struct CsgOffsetSurface {
207    /// The inner SDF function evaluated at any point.
208    inner_sdf: Box<dyn Fn([f64; 3]) -> f64 + Send + Sync>,
209    /// Offset value (positive = expand outward, negative = shrink inward).
210    pub offset: f64,
211}
212impl CsgOffsetSurface {
213    /// Create an offset surface from a closure SDF and an offset distance.
214    pub fn new(sdf: Box<dyn Fn([f64; 3]) -> f64 + Send + Sync>, offset: f64) -> Self {
215        Self {
216            inner_sdf: sdf,
217            offset,
218        }
219    }
220    /// Evaluate the offset SDF: `inner_sdf(p) - offset`.
221    pub fn eval(&self, p: [f64; 3]) -> f64 {
222        (self.inner_sdf)(p) - self.offset
223    }
224    /// Gradient via central finite differences (eps = 1e-4).
225    pub fn finite_diff_gradient(&self, p: [f64; 3]) -> [f64; 3] {
226        const EPS: f64 = 1e-4;
227        [
228            (self.eval([p[0] + EPS, p[1], p[2]]) - self.eval([p[0] - EPS, p[1], p[2]]))
229                / (2.0 * EPS),
230            (self.eval([p[0], p[1] + EPS, p[2]]) - self.eval([p[0], p[1] - EPS, p[2]]))
231                / (2.0 * EPS),
232            (self.eval([p[0], p[1], p[2] + EPS]) - self.eval([p[0], p[1], p[2] - EPS]))
233                / (2.0 * EPS),
234        ]
235    }
236}
237impl ImplicitSurface for CsgOffsetSurface {
238    fn sdf(&self, p: [f64; 3]) -> f64 {
239        self.eval(p)
240    }
241    fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
242        self.finite_diff_gradient(p)
243    }
244}
245/// A cell in a marching-cubes grid.
246#[derive(Debug, Clone)]
247pub struct MarchingCell {
248    /// Min corner of the cell.
249    pub min: [f64; 3],
250    /// Cell side length.
251    pub step: f64,
252    /// SDF values at the 8 corners (index = ix*4 + iy*2 + iz).
253    pub corner_values: [f64; 8],
254}
255impl MarchingCell {
256    /// Return `true` if any corner is inside (negative SDF) and any is outside.
257    pub fn has_surface(&self) -> bool {
258        let has_inside = self.corner_values.iter().any(|&v| v < 0.0);
259        let has_outside = self.corner_values.iter().any(|&v| v >= 0.0);
260        has_inside && has_outside
261    }
262    /// Linear interpolation of position along edge between corner `i0` and `i1`.
263    fn edge_vertex(&self, i0: usize, i1: usize) -> [f64; 3] {
264        let offsets: [[f64; 3]; 8] = [
265            [0.0, 0.0, 0.0],
266            [self.step, 0.0, 0.0],
267            [self.step, self.step, 0.0],
268            [0.0, self.step, 0.0],
269            [0.0, 0.0, self.step],
270            [self.step, 0.0, self.step],
271            [self.step, self.step, self.step],
272            [0.0, self.step, self.step],
273        ];
274        let v0 = self.corner_values[i0];
275        let v1 = self.corner_values[i1];
276        let t = if (v1 - v0).abs() > 1e-14 {
277            v0 / (v0 - v1)
278        } else {
279            0.5
280        };
281        let p0 = add(self.min, offsets[i0]);
282        let p1 = add(self.min, offsets[i1]);
283        lerp3(p0, p1, t)
284    }
285    /// Extract surface vertices from this cell (one per crossing edge).
286    pub fn extract_vertices(&self) -> Vec<[f64; 3]> {
287        if !self.has_surface() {
288            return vec![];
289        }
290        let edges: [(usize, usize); 12] = [
291            (0, 1),
292            (1, 2),
293            (2, 3),
294            (3, 0),
295            (4, 5),
296            (5, 6),
297            (6, 7),
298            (7, 4),
299            (0, 4),
300            (1, 5),
301            (2, 6),
302            (3, 7),
303        ];
304        edges
305            .iter()
306            .filter_map(|&(i0, i1)| {
307                let v0 = self.corner_values[i0];
308                let v1 = self.corner_values[i1];
309                if v0.signum() != v1.signum() {
310                    Some(self.edge_vertex(i0, i1))
311                } else {
312                    None
313                }
314            })
315            .collect()
316    }
317}
318/// An axis-aligned box defined by centre and half-extents.
319pub struct SdfBox {
320    /// World-space centre.
321    pub center: [f64; 3],
322    /// Half-extents along each axis.
323    pub half_extents: [f64; 3],
324}
325impl SdfBox {
326    /// Create a new box.
327    pub fn new(center: [f64; 3], half_extents: [f64; 3]) -> Self {
328        Self {
329            center,
330            half_extents,
331        }
332    }
333}
334/// An infinite cylinder along the Y axis.
335pub struct SdfCylinder {
336    /// World-space centre (on the axis).
337    pub center: [f64; 3],
338    /// Cylinder radius.
339    pub radius: f64,
340}
341impl SdfCylinder {
342    /// Create a new cylinder.
343    pub fn new(center: [f64; 3], radius: f64) -> Self {
344        Self { center, radius }
345    }
346}
347/// CSG difference: region inside `a` but outside `b`.
348///
349/// SDF = max(sdf_a, -sdf_b).
350pub struct CsgDifference {
351    /// The base shape.
352    pub a: Box<dyn ImplicitSurface>,
353    /// The shape to subtract.
354    pub b: Box<dyn ImplicitSurface>,
355}
356impl CsgDifference {
357    /// Create a new difference.
358    pub fn new(a: Box<dyn ImplicitSurface>, b: Box<dyn ImplicitSurface>) -> Self {
359        Self { a, b }
360    }
361}
362/// Smooth CSG union using the polynomial smooth-min kernel.
363///
364/// SDF = smooth_min(sdf_a, sdf_b, k).
365pub struct CsgSmoothUnion {
366    /// First operand.
367    pub a: Box<dyn ImplicitSurface>,
368    /// Second operand.
369    pub b: Box<dyn ImplicitSurface>,
370    /// Smoothing factor (larger -> smoother blend).
371    pub k: f64,
372}
373impl CsgSmoothUnion {
374    /// Create a new smooth union.
375    pub fn new(a: Box<dyn ImplicitSurface>, b: Box<dyn ImplicitSurface>, k: f64) -> Self {
376        Self { a, b, k }
377    }
378}
379/// Smooth CSG intersection.
380///
381/// SDF = smooth_max(sdf_a, sdf_b, k).
382pub struct CsgSmoothIntersection {
383    /// First operand.
384    pub a: Box<dyn ImplicitSurface>,
385    /// Second operand.
386    pub b: Box<dyn ImplicitSurface>,
387    /// Smoothing factor.
388    pub k: f64,
389}
390impl CsgSmoothIntersection {
391    /// Create a new smooth intersection.
392    pub fn new(a: Box<dyn ImplicitSurface>, b: Box<dyn ImplicitSurface>, k: f64) -> Self {
393        Self { a, b, k }
394    }
395}
396/// Classification of a point relative to a plane.
397#[derive(Debug, Clone, Copy, PartialEq)]
398pub enum PlaneSide {
399    /// Point is on the front (positive) side.
400    Front,
401    /// Point is on the back (negative) side.
402    Back,
403    /// Point is on the plane (within tolerance).
404    OnPlane,
405}
406/// A finite capped cylinder aligned with the Y axis.
407pub struct SdfCappedCylinder {
408    /// World-space center.
409    pub center: [f64; 3],
410    /// Cylinder radius.
411    pub radius: f64,
412    /// Half-height (distance from centre to cap).
413    pub half_height: f64,
414}
415impl SdfCappedCylinder {
416    /// Create a new capped cylinder.
417    pub fn new(center: [f64; 3], radius: f64, half_height: f64) -> Self {
418        Self {
419            center,
420            radius,
421            half_height,
422        }
423    }
424}
425/// Rounded box: axis-aligned box with rounded edges.
426///
427/// SDF = SdfBox.sdf(p) - rounding_radius
428pub struct SdfRoundedBox {
429    /// World-space centre.
430    pub center: [f64; 3],
431    /// Half-extents (before rounding).
432    pub half_extents: [f64; 3],
433    /// Corner rounding radius.
434    pub radius: f64,
435}
436impl SdfRoundedBox {
437    /// Create a new rounded box.
438    pub fn new(center: [f64; 3], half_extents: [f64; 3], radius: f64) -> Self {
439        Self {
440            center,
441            half_extents,
442            radius,
443        }
444    }
445}
446
447#[cfg(test)]
448mod csg_offset_tests {
449    use super::super::functions::ImplicitSurface;
450    use super::CsgOffsetSurface;
451
452    fn unit_sphere_sdf(p: [f64; 3]) -> f64 {
453        (p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt() - 1.0
454    }
455
456    #[test]
457    fn test_offset_sphere_eval_inside() {
458        let s = CsgOffsetSurface::new(Box::new(unit_sphere_sdf), 1.0);
459        assert!(s.eval([1.5, 0.0, 0.0]) < 0.0);
460    }
461
462    #[test]
463    fn test_offset_sphere_eval_outside() {
464        let s = CsgOffsetSurface::new(Box::new(unit_sphere_sdf), 1.0);
465        assert!(s.eval([3.5, 0.0, 0.0]) > 0.0);
466    }
467
468    #[test]
469    fn test_offset_sphere_on_surface() {
470        let s = CsgOffsetSurface::new(Box::new(unit_sphere_sdf), 1.0);
471        assert!(s.eval([2.0, 0.0, 0.0]).abs() < 1e-10);
472    }
473
474    #[test]
475    fn test_offset_sphere_gradient_outward() {
476        let s = CsgOffsetSurface::new(Box::new(unit_sphere_sdf), 1.0);
477        let g = s.finite_diff_gradient([2.0, 0.0, 0.0]);
478        assert!(g[0] > 0.5 && g[1].abs() < 0.01 && g[2].abs() < 0.01);
479    }
480
481    #[test]
482    fn test_implicit_surface_trait() {
483        let s = CsgOffsetSurface::new(Box::new(unit_sphere_sdf), 1.0);
484        let t: &dyn ImplicitSurface = &s;
485        assert!(t.sdf([2.5, 0.0, 0.0]) > 0.0);
486        assert!(t.sdf([1.5, 0.0, 0.0]) < 0.0);
487    }
488}