Skip to main content

oxiphysics_gpu/sdf_compute/
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#[allow(unused_imports)]
8use super::functions_2::*;
9use rayon::prelude::*;
10
11/// Boolean CSG operations combining two [`SdfShape`] primitives.
12#[derive(Debug, Clone)]
13pub enum SdfCombine {
14    /// Union: min(d_a, d_b).
15    Union(SdfShape, SdfShape),
16    /// Intersection: max(d_a, d_b).
17    Intersection(SdfShape, SdfShape),
18    /// Subtraction of b from a: max(d_a, -d_b).
19    Subtraction(SdfShape, SdfShape),
20    /// Smooth union with blending factor k.
21    SmoothUnion(SdfShape, SdfShape, f64),
22}
23impl SdfCombine {
24    /// Signed distance from point `p` to this combined shape.
25    pub fn signed_distance(&self, p: [f64; 3]) -> f64 {
26        match self {
27            SdfCombine::Union(a, b) => a.signed_distance(p).min(b.signed_distance(p)),
28            SdfCombine::Intersection(a, b) => a.signed_distance(p).max(b.signed_distance(p)),
29            SdfCombine::Subtraction(a, b) => a.signed_distance(p).max(-b.signed_distance(p)),
30            SdfCombine::SmoothUnion(a, b, k) => {
31                let da = a.signed_distance(p);
32                let db = b.signed_distance(p);
33                let h = (0.5 + 0.5 * (db - da) / k).clamp(0.0, 1.0);
34                db * (1.0 - h) + da * h - k * h * (1.0 - h)
35            }
36        }
37    }
38}
39/// An analytic signed distance shape primitive.
40#[derive(Debug, Clone)]
41pub enum SdfShape {
42    /// A sphere with a given centre and radius.
43    Sphere {
44        /// Centre of the sphere.
45        center: [f64; 3],
46        /// Radius.
47        r: f64,
48    },
49    /// An axis-aligned box specified by centre and half-extents.
50    Box3 {
51        /// Centre of the box.
52        center: [f64; 3],
53        /// Half-extents in each axis.
54        half: [f64; 3],
55    },
56    /// A capsule (cylinder with hemispherical caps) between two end-points.
57    Capsule {
58        /// First end-point.
59        a: [f64; 3],
60        /// Second end-point.
61        b: [f64; 3],
62        /// Radius of the capsule.
63        r: f64,
64    },
65}
66impl SdfShape {
67    /// Signed distance from point `p` to this shape.
68    ///
69    /// Negative values are inside the shape.
70    pub fn signed_distance(&self, p: [f64; 3]) -> f64 {
71        match self {
72            SdfShape::Sphere { center, r } => {
73                let dx = p[0] - center[0];
74                let dy = p[1] - center[1];
75                let dz = p[2] - center[2];
76                (dx * dx + dy * dy + dz * dz).sqrt() - r
77            }
78            SdfShape::Box3 { center, half } => {
79                let qx = (p[0] - center[0]).abs() - half[0];
80                let qy = (p[1] - center[1]).abs() - half[1];
81                let qz = (p[2] - center[2]).abs() - half[2];
82                let ext = (qx.max(0.0).powi(2) + qy.max(0.0).powi(2) + qz.max(0.0).powi(2)).sqrt();
83                let interior = qx.max(qy).max(qz).min(0.0);
84                ext + interior
85            }
86            SdfShape::Capsule { a, b, r } => {
87                let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
88                let ap = [p[0] - a[0], p[1] - a[1], p[2] - a[2]];
89                let ab_len2 = ab[0] * ab[0] + ab[1] * ab[1] + ab[2] * ab[2];
90                let t = if ab_len2 < 1e-12 {
91                    0.0
92                } else {
93                    ((ap[0] * ab[0] + ap[1] * ab[1] + ap[2] * ab[2]) / ab_len2).clamp(0.0, 1.0)
94                };
95                let closest = [a[0] + t * ab[0], a[1] + t * ab[1], a[2] + t * ab[2]];
96                let dx = p[0] - closest[0];
97                let dy = p[1] - closest[1];
98                let dz = p[2] - closest[2];
99                (dx * dx + dy * dy + dz * dz).sqrt() - r
100            }
101        }
102    }
103}
104/// A 3-D SDF grid filled analytically via [`generate_sdf_grid`].
105///
106/// Separate from the existing [`SdfGrid`] to keep naming unambiguous.
107#[derive(Debug, Clone)]
108pub struct GpuSdfGrid {
109    /// Flat storage: index = `ix * ny * nz + iy * nz + iz`.
110    pub data: Vec<f64>,
111    /// Number of cells in x.
112    pub nx: usize,
113    /// Number of cells in y.
114    pub ny: usize,
115    /// Number of cells in z.
116    pub nz: usize,
117    /// World-space origin of the (0,0,0) corner.
118    pub origin: [f64; 3],
119    /// Uniform cell spacing.
120    pub cell_size: f64,
121}
122impl GpuSdfGrid {
123    /// Create a new grid filled with zeros.
124    pub fn new(nx: usize, ny: usize, nz: usize, origin: [f64; 3], cell_size: f64) -> Self {
125        Self {
126            data: vec![0.0_f64; nx * ny * nz],
127            nx,
128            ny,
129            nz,
130            origin,
131            cell_size,
132        }
133    }
134    /// Flat index for cell `(ix, iy, iz)`.
135    #[inline]
136    pub fn index(&self, ix: usize, iy: usize, iz: usize) -> usize {
137        ix * self.ny * self.nz + iy * self.nz + iz
138    }
139    /// Read the value at cell `(ix, iy, iz)`.
140    #[inline]
141    pub fn get(&self, ix: usize, iy: usize, iz: usize) -> f64 {
142        self.data[self.index(ix, iy, iz)]
143    }
144    /// World-space centre of cell `(ix, iy, iz)`.
145    #[inline]
146    pub fn cell_center(&self, ix: usize, iy: usize, iz: usize) -> [f64; 3] {
147        [
148            self.origin[0] + (ix as f64 + 0.5) * self.cell_size,
149            self.origin[1] + (iy as f64 + 0.5) * self.cell_size,
150            self.origin[2] + (iz as f64 + 0.5) * self.cell_size,
151        ]
152    }
153    /// Trilinear interpolation of the SDF at world-space point `p`.
154    ///
155    /// Points outside the grid are clamped to the nearest grid value.
156    pub fn sample_trilinear(&self, p: [f64; 3]) -> f64 {
157        let fx = (p[0] - self.origin[0]) / self.cell_size - 0.5;
158        let fy = (p[1] - self.origin[1]) / self.cell_size - 0.5;
159        let fz = (p[2] - self.origin[2]) / self.cell_size - 0.5;
160        let ix = fx.floor().clamp(0.0, (self.nx - 1) as f64) as usize;
161        let iy = fy.floor().clamp(0.0, (self.ny - 1) as f64) as usize;
162        let iz = fz.floor().clamp(0.0, (self.nz - 1) as f64) as usize;
163        let tx = (fx - ix as f64).clamp(0.0, 1.0);
164        let ty = (fy - iy as f64).clamp(0.0, 1.0);
165        let tz = (fz - iz as f64).clamp(0.0, 1.0);
166        let nx1 = (ix + 1).min(self.nx - 1);
167        let ny1 = (iy + 1).min(self.ny - 1);
168        let nz1 = (iz + 1).min(self.nz - 1);
169        let c000 = self.get(ix, iy, iz);
170        let c100 = self.get(nx1, iy, iz);
171        let c010 = self.get(ix, ny1, iz);
172        let c110 = self.get(nx1, ny1, iz);
173        let c001 = self.get(ix, iy, nz1);
174        let c101 = self.get(nx1, iy, nz1);
175        let c011 = self.get(ix, ny1, nz1);
176        let c111 = self.get(nx1, ny1, nz1);
177        let c00 = c000 * (1.0 - tx) + c100 * tx;
178        let c10 = c010 * (1.0 - tx) + c110 * tx;
179        let c01 = c001 * (1.0 - tx) + c101 * tx;
180        let c11 = c011 * (1.0 - tx) + c111 * tx;
181        let c0 = c00 * (1.0 - ty) + c10 * ty;
182        let c1 = c01 * (1.0 - ty) + c11 * ty;
183        c0 * (1.0 - tz) + c1 * tz
184    }
185    /// Estimate the SDF gradient at `p` using central finite differences.
186    ///
187    /// The step size is `cell_size * 0.5`.
188    pub fn gradient_at(&self, p: [f64; 3]) -> [f64; 3] {
189        let h = self.cell_size * 0.5;
190        let gx = (self.sample_trilinear([p[0] + h, p[1], p[2]])
191            - self.sample_trilinear([p[0] - h, p[1], p[2]]))
192            / (2.0 * h);
193        let gy = (self.sample_trilinear([p[0], p[1] + h, p[2]])
194            - self.sample_trilinear([p[0], p[1] - h, p[2]]))
195            / (2.0 * h);
196        let gz = (self.sample_trilinear([p[0], p[1], p[2] + h])
197            - self.sample_trilinear([p[0], p[1], p[2] - h]))
198            / (2.0 * h);
199        [gx, gy, gz]
200    }
201}
202/// Sphere tracing (ray marching) result.
203pub struct SphereTraceResult {
204    /// Whether the ray hit the surface.
205    pub hit: bool,
206    /// Position of the hit point (if hit).
207    pub position: [f64; 3],
208    /// Distance traveled along the ray.
209    pub t: f64,
210    /// Number of iterations used.
211    pub iterations: usize,
212}
213/// A 3-D signed distance field stored on a uniform Cartesian grid.
214pub struct SdfGrid {
215    /// Number of cells in the x direction.
216    pub nx: usize,
217    /// Number of cells in the y direction.
218    pub ny: usize,
219    /// Number of cells in the z direction.
220    pub nz: usize,
221    /// Uniform cell spacing (same in all directions).
222    pub dx: f64,
223    /// World-space coordinate of the (0,0,0) grid corner.
224    pub origin: [f64; 3],
225    /// Flat storage: index = `i*ny*nz + j*nz + k`.
226    pub values: Vec<f64>,
227}
228impl SdfGrid {
229    /// Create a new grid filled with `f64::MAX`.
230    pub fn new(nx: usize, ny: usize, nz: usize, dx: f64, origin: [f64; 3]) -> Self {
231        let n = nx * ny * nz;
232        Self {
233            nx,
234            ny,
235            nz,
236            dx,
237            origin,
238            values: vec![f64::MAX; n],
239        }
240    }
241    /// Flat index for cell `(i, j, k)`.
242    #[inline]
243    pub fn index(&self, i: usize, j: usize, k: usize) -> usize {
244        i * self.ny * self.nz + j * self.nz + k
245    }
246    /// World-space centre of cell `(i, j, k)`.
247    #[inline]
248    pub fn world_pos(&self, i: usize, j: usize, k: usize) -> [f64; 3] {
249        [
250            self.origin[0] + (i as f64 + 0.5) * self.dx,
251            self.origin[1] + (j as f64 + 0.5) * self.dx,
252            self.origin[2] + (k as f64 + 0.5) * self.dx,
253        ]
254    }
255    /// Read the SDF value at `(i, j, k)`.
256    #[inline]
257    pub fn get(&self, i: usize, j: usize, k: usize) -> f64 {
258        self.values[self.index(i, j, k)]
259    }
260    /// Write the SDF value at `(i, j, k)`.
261    #[inline]
262    pub fn set(&mut self, i: usize, j: usize, k: usize, v: f64) {
263        let idx = self.index(i, j, k);
264        self.values[idx] = v;
265    }
266    /// Fill the grid with the signed distance to a sphere.
267    pub fn compute_sphere_sdf(&mut self, center: [f64; 3], radius: f64) {
268        let _nx = self.nx;
269        let ny = self.ny;
270        let nz = self.nz;
271        let dx = self.dx;
272        let origin = self.origin;
273        self.values.par_iter_mut().enumerate().for_each(|(idx, v)| {
274            let i = idx / (ny * nz);
275            let j = (idx / nz) % ny;
276            let k = idx % nz;
277            let px = origin[0] + (i as f64 + 0.5) * dx;
278            let py = origin[1] + (j as f64 + 0.5) * dx;
279            let pz = origin[2] + (k as f64 + 0.5) * dx;
280            let dist =
281                ((px - center[0]).powi(2) + (py - center[1]).powi(2) + (pz - center[2]).powi(2))
282                    .sqrt();
283            *v = dist - radius;
284        });
285    }
286    /// Fill the grid with the signed distance to an axis-aligned box.
287    pub fn compute_box_sdf(&mut self, box_center: [f64; 3], half_extents: [f64; 3]) {
288        let _nx = self.nx;
289        let ny = self.ny;
290        let nz = self.nz;
291        let dx = self.dx;
292        let origin = self.origin;
293        self.values.par_iter_mut().enumerate().for_each(|(idx, v)| {
294            let i = idx / (ny * nz);
295            let j = (idx / nz) % ny;
296            let k = idx % nz;
297            let px = origin[0] + (i as f64 + 0.5) * dx - box_center[0];
298            let py = origin[1] + (j as f64 + 0.5) * dx - box_center[1];
299            let pz = origin[2] + (k as f64 + 0.5) * dx - box_center[2];
300            let qx = px.abs() - half_extents[0];
301            let qy = py.abs() - half_extents[1];
302            let qz = pz.abs() - half_extents[2];
303            let ext = (qx.max(0.0).powi(2) + qy.max(0.0).powi(2) + qz.max(0.0).powi(2)).sqrt();
304            let interior = qx.max(qy).max(qz).min(0.0);
305            *v = ext + interior;
306        });
307    }
308    /// Fill the grid with the signed distance to an infinite cylinder
309    /// aligned along the z-axis.
310    pub fn compute_cylinder_sdf(&mut self, center: [f64; 2], radius: f64, half_height: f64) {
311        let ny = self.ny;
312        let nz = self.nz;
313        let dx = self.dx;
314        let origin = self.origin;
315        self.values.par_iter_mut().enumerate().for_each(|(idx, v)| {
316            let i = idx / (ny * nz);
317            let j = (idx / nz) % ny;
318            let k = idx % nz;
319            let px = origin[0] + (i as f64 + 0.5) * dx - center[0];
320            let py = origin[1] + (j as f64 + 0.5) * dx - center[1];
321            let pz = origin[2] + (k as f64 + 0.5) * dx;
322            let r = (px * px + py * py).sqrt();
323            let d_radial = r - radius;
324            let d_axial = pz.abs() - half_height;
325            let ext = (d_radial.max(0.0).powi(2) + d_axial.max(0.0).powi(2)).sqrt();
326            let interior = d_radial.max(d_axial).min(0.0);
327            *v = ext + interior;
328        });
329    }
330    /// Fill the grid with the signed distance to a torus
331    /// centred at the origin in the xz-plane.
332    pub fn compute_torus_sdf(&mut self, center: [f64; 3], major_radius: f64, minor_radius: f64) {
333        let ny = self.ny;
334        let nz = self.nz;
335        let dx = self.dx;
336        let origin = self.origin;
337        self.values.par_iter_mut().enumerate().for_each(|(idx, v)| {
338            let i = idx / (ny * nz);
339            let j = (idx / nz) % ny;
340            let k = idx % nz;
341            let px = origin[0] + (i as f64 + 0.5) * dx - center[0];
342            let py = origin[1] + (j as f64 + 0.5) * dx - center[1];
343            let pz = origin[2] + (k as f64 + 0.5) * dx - center[2];
344            let q_x = (px * px + pz * pz).sqrt() - major_radius;
345            let dist = (q_x * q_x + py * py).sqrt() - minor_radius;
346            *v = dist;
347        });
348    }
349    /// Estimate the gradient of the SDF at `(i, j, k)` using central differences.
350    pub fn gradient_at(&self, i: usize, j: usize, k: usize) -> [f64; 3] {
351        let two_dx = 2.0 * self.dx;
352        let gx = if i == 0 {
353            (self.get(i + 1, j, k) - self.get(i, j, k)) / self.dx
354        } else if i + 1 == self.nx {
355            (self.get(i, j, k) - self.get(i - 1, j, k)) / self.dx
356        } else {
357            (self.get(i + 1, j, k) - self.get(i - 1, j, k)) / two_dx
358        };
359        let gy = if j == 0 {
360            (self.get(i, j + 1, k) - self.get(i, j, k)) / self.dx
361        } else if j + 1 == self.ny {
362            (self.get(i, j, k) - self.get(i, j - 1, k)) / self.dx
363        } else {
364            (self.get(i, j + 1, k) - self.get(i, j - 1, k)) / two_dx
365        };
366        let gz = if k == 0 {
367            (self.get(i, j, k + 1) - self.get(i, j, k)) / self.dx
368        } else if k + 1 == self.nz {
369            (self.get(i, j, k) - self.get(i, j, k - 1)) / self.dx
370        } else {
371            (self.get(i, j, k + 1) - self.get(i, j, k - 1)) / two_dx
372        };
373        [gx, gy, gz]
374    }
375    /// Total number of cells in the grid.
376    #[inline]
377    pub fn total_cells(&self) -> usize {
378        self.nx * self.ny * self.nz
379    }
380    /// Trilinear interpolation of the SDF at an arbitrary world-space point.
381    ///
382    /// Returns `None` if the point is outside the grid.
383    pub fn sample(&self, pos: [f64; 3]) -> Option<f64> {
384        let fx = (pos[0] - self.origin[0]) / self.dx - 0.5;
385        let fy = (pos[1] - self.origin[1]) / self.dx - 0.5;
386        let fz = (pos[2] - self.origin[2]) / self.dx - 0.5;
387        if fx < 0.0 || fy < 0.0 || fz < 0.0 {
388            return None;
389        }
390        let ix = fx as usize;
391        let iy = fy as usize;
392        let iz = fz as usize;
393        if ix + 1 >= self.nx || iy + 1 >= self.ny || iz + 1 >= self.nz {
394            return None;
395        }
396        let tx = fx - ix as f64;
397        let ty = fy - iy as f64;
398        let tz = fz - iz as f64;
399        let c000 = self.get(ix, iy, iz);
400        let c100 = self.get(ix + 1, iy, iz);
401        let c010 = self.get(ix, iy + 1, iz);
402        let c110 = self.get(ix + 1, iy + 1, iz);
403        let c001 = self.get(ix, iy, iz + 1);
404        let c101 = self.get(ix + 1, iy, iz + 1);
405        let c011 = self.get(ix, iy + 1, iz + 1);
406        let c111 = self.get(ix + 1, iy + 1, iz + 1);
407        let c00 = c000 * (1.0 - tx) + c100 * tx;
408        let c10 = c010 * (1.0 - tx) + c110 * tx;
409        let c01 = c001 * (1.0 - tx) + c101 * tx;
410        let c11 = c011 * (1.0 - tx) + c111 * tx;
411        let c0 = c00 * (1.0 - ty) + c10 * ty;
412        let c1 = c01 * (1.0 - ty) + c11 * ty;
413        Some(c0 * (1.0 - tz) + c1 * tz)
414    }
415    /// Compute the gradient at an arbitrary point using trilinear interpolation
416    /// of gradient values.
417    pub fn gradient_at_point(&self, pos: [f64; 3]) -> Option<[f64; 3]> {
418        let fx = (pos[0] - self.origin[0]) / self.dx - 0.5;
419        let fy = (pos[1] - self.origin[1]) / self.dx - 0.5;
420        let fz = (pos[2] - self.origin[2]) / self.dx - 0.5;
421        if fx < 0.0 || fy < 0.0 || fz < 0.0 {
422            return None;
423        }
424        let ix = fx as usize;
425        let iy = fy as usize;
426        let iz = fz as usize;
427        if ix + 1 >= self.nx || iy + 1 >= self.ny || iz + 1 >= self.nz {
428            return None;
429        }
430        let eps = self.dx * 0.5;
431        let gx = (self.sample([pos[0] + eps, pos[1], pos[2]]).unwrap_or(0.0)
432            - self.sample([pos[0] - eps, pos[1], pos[2]]).unwrap_or(0.0))
433            / (2.0 * eps);
434        let gy = (self.sample([pos[0], pos[1] + eps, pos[2]]).unwrap_or(0.0)
435            - self.sample([pos[0], pos[1] - eps, pos[2]]).unwrap_or(0.0))
436            / (2.0 * eps);
437        let gz = (self.sample([pos[0], pos[1], pos[2] + eps]).unwrap_or(0.0)
438            - self.sample([pos[0], pos[1], pos[2] - eps]).unwrap_or(0.0))
439            / (2.0 * eps);
440        Some([gx, gy, gz])
441    }
442}
443/// A triangle in 3-D space.
444#[derive(Debug, Clone)]
445pub struct Triangle {
446    /// Three vertex positions.
447    pub v: [[f64; 3]; 3],
448}
449/// Query result for closest point / normal / distance.
450#[derive(Debug, Clone)]
451pub struct DistanceQuery {
452    /// Signed distance at the query point.
453    pub distance: f64,
454    /// Normal direction at the query point (normalised gradient).
455    pub normal: [f64; 3],
456    /// Closest point on the surface (approximate, from gradient ray march).
457    pub closest_point: [f64; 3],
458    /// Whether the query point is inside the surface.
459    pub is_inside: bool,
460}