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 original SDF tree (as an evaluated function table over a grid is
208 /// impractical, so we store the sampler function as a closure via callback).
209 pub(super) inner_sdf: f64,
210 /// Offset value (positive = expand outward, negative = shrink inward).
211 pub offset: f64,
212 /// Bounding box for the original shape (used for gradient estimation).
213 pub bbox_min: [f64; 3],
214 /// Maximum bounding extent for gradient computation.
215 pub bbox_max: [f64; 3],
216}
217impl CsgOffsetSurface {
218 /// Create an offset wrapper (placeholder; use `csg_offset_sdf` for actual evaluation).
219 pub fn new(offset: f64, bbox_min: [f64; 3], bbox_max: [f64; 3]) -> Self {
220 Self {
221 inner_sdf: 0.0,
222 offset,
223 bbox_min,
224 bbox_max,
225 }
226 }
227}
228/// A cell in a marching-cubes grid.
229#[derive(Debug, Clone)]
230pub struct MarchingCell {
231 /// Min corner of the cell.
232 pub min: [f64; 3],
233 /// Cell side length.
234 pub step: f64,
235 /// SDF values at the 8 corners (index = ix*4 + iy*2 + iz).
236 pub corner_values: [f64; 8],
237}
238impl MarchingCell {
239 /// Return `true` if any corner is inside (negative SDF) and any is outside.
240 pub fn has_surface(&self) -> bool {
241 let has_inside = self.corner_values.iter().any(|&v| v < 0.0);
242 let has_outside = self.corner_values.iter().any(|&v| v >= 0.0);
243 has_inside && has_outside
244 }
245 /// Linear interpolation of position along edge between corner `i0` and `i1`.
246 fn edge_vertex(&self, i0: usize, i1: usize) -> [f64; 3] {
247 let offsets: [[f64; 3]; 8] = [
248 [0.0, 0.0, 0.0],
249 [self.step, 0.0, 0.0],
250 [self.step, self.step, 0.0],
251 [0.0, self.step, 0.0],
252 [0.0, 0.0, self.step],
253 [self.step, 0.0, self.step],
254 [self.step, self.step, self.step],
255 [0.0, self.step, self.step],
256 ];
257 let v0 = self.corner_values[i0];
258 let v1 = self.corner_values[i1];
259 let t = if (v1 - v0).abs() > 1e-14 {
260 v0 / (v0 - v1)
261 } else {
262 0.5
263 };
264 let p0 = add(self.min, offsets[i0]);
265 let p1 = add(self.min, offsets[i1]);
266 lerp3(p0, p1, t)
267 }
268 /// Extract surface vertices from this cell (one per crossing edge).
269 pub fn extract_vertices(&self) -> Vec<[f64; 3]> {
270 if !self.has_surface() {
271 return vec![];
272 }
273 let edges: [(usize, usize); 12] = [
274 (0, 1),
275 (1, 2),
276 (2, 3),
277 (3, 0),
278 (4, 5),
279 (5, 6),
280 (6, 7),
281 (7, 4),
282 (0, 4),
283 (1, 5),
284 (2, 6),
285 (3, 7),
286 ];
287 edges
288 .iter()
289 .filter_map(|&(i0, i1)| {
290 let v0 = self.corner_values[i0];
291 let v1 = self.corner_values[i1];
292 if v0.signum() != v1.signum() {
293 Some(self.edge_vertex(i0, i1))
294 } else {
295 None
296 }
297 })
298 .collect()
299 }
300}
301/// An axis-aligned box defined by centre and half-extents.
302pub struct SdfBox {
303 /// World-space centre.
304 pub center: [f64; 3],
305 /// Half-extents along each axis.
306 pub half_extents: [f64; 3],
307}
308impl SdfBox {
309 /// Create a new box.
310 pub fn new(center: [f64; 3], half_extents: [f64; 3]) -> Self {
311 Self {
312 center,
313 half_extents,
314 }
315 }
316}
317/// An infinite cylinder along the Y axis.
318pub struct SdfCylinder {
319 /// World-space centre (on the axis).
320 pub center: [f64; 3],
321 /// Cylinder radius.
322 pub radius: f64,
323}
324impl SdfCylinder {
325 /// Create a new cylinder.
326 pub fn new(center: [f64; 3], radius: f64) -> Self {
327 Self { center, radius }
328 }
329}
330/// CSG difference: region inside `a` but outside `b`.
331///
332/// SDF = max(sdf_a, -sdf_b).
333pub struct CsgDifference {
334 /// The base shape.
335 pub a: Box<dyn ImplicitSurface>,
336 /// The shape to subtract.
337 pub b: Box<dyn ImplicitSurface>,
338}
339impl CsgDifference {
340 /// Create a new difference.
341 pub fn new(a: Box<dyn ImplicitSurface>, b: Box<dyn ImplicitSurface>) -> Self {
342 Self { a, b }
343 }
344}
345/// Smooth CSG union using the polynomial smooth-min kernel.
346///
347/// SDF = smooth_min(sdf_a, sdf_b, k).
348pub struct CsgSmoothUnion {
349 /// First operand.
350 pub a: Box<dyn ImplicitSurface>,
351 /// Second operand.
352 pub b: Box<dyn ImplicitSurface>,
353 /// Smoothing factor (larger -> smoother blend).
354 pub k: f64,
355}
356impl CsgSmoothUnion {
357 /// Create a new smooth union.
358 pub fn new(a: Box<dyn ImplicitSurface>, b: Box<dyn ImplicitSurface>, k: f64) -> Self {
359 Self { a, b, k }
360 }
361}
362/// Smooth CSG intersection.
363///
364/// SDF = smooth_max(sdf_a, sdf_b, k).
365pub struct CsgSmoothIntersection {
366 /// First operand.
367 pub a: Box<dyn ImplicitSurface>,
368 /// Second operand.
369 pub b: Box<dyn ImplicitSurface>,
370 /// Smoothing factor.
371 pub k: f64,
372}
373impl CsgSmoothIntersection {
374 /// Create a new smooth intersection.
375 pub fn new(a: Box<dyn ImplicitSurface>, b: Box<dyn ImplicitSurface>, k: f64) -> Self {
376 Self { a, b, k }
377 }
378}
379/// Classification of a point relative to a plane.
380#[derive(Debug, Clone, Copy, PartialEq)]
381pub enum PlaneSide {
382 /// Point is on the front (positive) side.
383 Front,
384 /// Point is on the back (negative) side.
385 Back,
386 /// Point is on the plane (within tolerance).
387 OnPlane,
388}
389/// A finite capped cylinder aligned with the Y axis.
390pub struct SdfCappedCylinder {
391 /// World-space center.
392 pub center: [f64; 3],
393 /// Cylinder radius.
394 pub radius: f64,
395 /// Half-height (distance from centre to cap).
396 pub half_height: f64,
397}
398impl SdfCappedCylinder {
399 /// Create a new capped cylinder.
400 pub fn new(center: [f64; 3], radius: f64, half_height: f64) -> Self {
401 Self {
402 center,
403 radius,
404 half_height,
405 }
406 }
407}
408/// Rounded box: axis-aligned box with rounded edges.
409///
410/// SDF = SdfBox.sdf(p) - rounding_radius
411pub struct SdfRoundedBox {
412 /// World-space centre.
413 pub center: [f64; 3],
414 /// Half-extents (before rounding).
415 pub half_extents: [f64; 3],
416 /// Corner rounding radius.
417 pub radius: f64,
418}
419impl SdfRoundedBox {
420 /// Create a new rounded box.
421 pub fn new(center: [f64; 3], half_extents: [f64; 3], radius: f64) -> Self {
422 Self {
423 center,
424 half_extents,
425 radius,
426 }
427 }
428}