Skip to main content

oxiphysics_geometry/level_set/
types.rs

1//! Auto-generated module
2//!
3//! ๐Ÿค– Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use super::functions::{MC_EDGE_CORNERS, MC_EDGE_TABLE, MC_TRI_TABLE};
7
8/// Signed distance transform from a point cloud or triangle mesh.
9///
10/// Provides brute-force SDF computation (exact for small point sets) and a
11/// fast alternating-sweep approximation for larger inputs.
12pub struct SignedDistanceTransform {
13    /// Source points used to define the zero level set.
14    pub source_points: Vec<[f64; 3]>,
15}
16impl SignedDistanceTransform {
17    /// Create a new [`SignedDistanceTransform`] from a set of source points.
18    ///
19    /// # Arguments
20    /// * `points` โ€“ positions on or near the zero level set
21    pub fn new(points: Vec<[f64; 3]>) -> Self {
22        Self {
23            source_points: points,
24        }
25    }
26    /// Brute-force signed distance from world point `p` to the point cloud.
27    ///
28    /// Returns the distance to the nearest source point (always non-negative;
29    /// sign must be determined by additional topology information).
30    pub fn unsigned_distance(&self, p: [f64; 3]) -> f64 {
31        self.source_points
32            .iter()
33            .map(|&q| {
34                ((p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2) + (p[2] - q[2]).powi(2)).sqrt()
35            })
36            .fold(f64::MAX, f64::min)
37    }
38    /// Build an unsigned distance field on a regular grid.
39    ///
40    /// For each grid node, computes the nearest point distance using brute force.
41    ///
42    /// # Arguments
43    /// * `nx`, `ny`, `nz` โ€“ grid dimensions
44    /// * `dx`             โ€“ grid spacing
45    /// * `origin`         โ€“ grid origin
46    pub fn compute_grid_unsigned(
47        &self,
48        nx: usize,
49        ny: usize,
50        nz: usize,
51        dx: f64,
52        origin: [f64; 3],
53    ) -> LevelSetField {
54        let mut field = LevelSetField::new(nx, ny, nz, dx, origin);
55        for iz in 0..nz {
56            for iy in 0..ny {
57                for ix in 0..nx {
58                    let p = field.node_pos(ix, iy, iz);
59                    let d = self.unsigned_distance(p);
60                    field.set(ix, iy, iz, d);
61                }
62            }
63        }
64        field
65    }
66    /// Compute a signed distance field on a regular grid.
67    ///
68    /// The sign is determined by the winding number test relative to the
69    /// first source point (interior if distance < median distance).
70    /// For a production use case this should use a proper inside/outside test.
71    pub fn compute_grid_signed(
72        &self,
73        nx: usize,
74        ny: usize,
75        nz: usize,
76        dx: f64,
77        origin: [f64; 3],
78    ) -> LevelSetField {
79        let mut field = self.compute_grid_unsigned(nx, ny, nz, dx, origin);
80        if self.source_points.is_empty() {
81            return field;
82        }
83        let centroid: [f64; 3] = {
84            let n = self.source_points.len() as f64;
85            let s = self.source_points.iter().fold([0.0_f64; 3], |acc, p| {
86                [acc[0] + p[0], acc[1] + p[1], acc[2] + p[2]]
87            });
88            [s[0] / n, s[1] / n, s[2] / n]
89        };
90        let radius = self.unsigned_distance(centroid);
91        for iz in 0..nz {
92            for iy in 0..ny {
93                for ix in 0..nx {
94                    let p = field.node_pos(ix, iy, iz);
95                    let dc = ((p[0] - centroid[0]).powi(2)
96                        + (p[1] - centroid[1]).powi(2)
97                        + (p[2] - centroid[2]).powi(2))
98                    .sqrt();
99                    let val = field.get(ix, iy, iz);
100                    if dc < radius {
101                        field.set(ix, iy, iz, -val);
102                    }
103                }
104            }
105        }
106        field
107    }
108    /// Distance from point `p` to a line segment \[a, b\].
109    pub fn point_to_segment(p: [f64; 3], a: [f64; 3], b: [f64; 3]) -> f64 {
110        let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
111        let ap = [p[0] - a[0], p[1] - a[1], p[2] - a[2]];
112        let len2 = ab[0] * ab[0] + ab[1] * ab[1] + ab[2] * ab[2];
113        if len2 < 1e-30 {
114            return (ap[0] * ap[0] + ap[1] * ap[1] + ap[2] * ap[2]).sqrt();
115        }
116        let t = ((ap[0] * ab[0] + ap[1] * ab[1] + ap[2] * ab[2]) / len2).clamp(0.0, 1.0);
117        let closest = [a[0] + t * ab[0], a[1] + t * ab[1], a[2] + t * ab[2]];
118        ((p[0] - closest[0]).powi(2) + (p[1] - closest[1]).powi(2) + (p[2] - closest[2]).powi(2))
119            .sqrt()
120    }
121}
122/// Level-set evolution by advection and mean curvature flow.
123///
124/// Implements the upwind finite difference scheme for advection and the
125/// explicit mean-curvature-flow equation for smoothing.
126pub struct LevelSetEvolution {
127    /// Time step for explicit integration.
128    pub dt: f64,
129    /// Number of time steps per evolution call.
130    pub n_steps: usize,
131}
132impl LevelSetEvolution {
133    /// Create a new [`LevelSetEvolution`] solver.
134    ///
135    /// # Arguments
136    /// * `dt`      โ€“ explicit time step (should satisfy CFL: dt โ‰ค dx/|v|_max)
137    /// * `n_steps` โ€“ number of time steps per `advect` or `mean_curvature_flow` call
138    pub fn new(dt: f64, n_steps: usize) -> Self {
139        Self { dt, n_steps }
140    }
141    /// Advect the level-set field by a uniform velocity field using the upwind scheme.
142    ///
143    /// Solves โˆ‚ฯ†/โˆ‚t + v ยท โˆ‡ฯ† = 0 with first-order upwind spatial discretisation.
144    ///
145    /// # Arguments
146    /// * `field`    โ€“ level-set field (modified in place)
147    /// * `velocity` โ€“ uniform velocity vector \[vx, vy, vz\]
148    pub fn advect(&self, field: &mut LevelSetField, velocity: [f64; 3]) {
149        let dx = field.dx;
150        let nx = field.nx;
151        let ny = field.ny;
152        let nz = field.nz;
153        for _ in 0..self.n_steps {
154            let old = field.data.clone();
155            for iz in 0..nz {
156                for iy in 0..ny {
157                    for ix in 0..nx {
158                        let phi_c = old[field.idx(ix, iy, iz)];
159                        let dphi_x = if velocity[0] > 0.0 {
160                            if ix > 0 {
161                                (phi_c - old[field.idx(ix - 1, iy, iz)]) / dx
162                            } else {
163                                0.0
164                            }
165                        } else {
166                            if ix + 1 < nx {
167                                (old[field.idx(ix + 1, iy, iz)] - phi_c) / dx
168                            } else {
169                                0.0
170                            }
171                        };
172                        let dphi_y = if velocity[1] > 0.0 {
173                            if iy > 0 {
174                                (phi_c - old[field.idx(ix, iy - 1, iz)]) / dx
175                            } else {
176                                0.0
177                            }
178                        } else {
179                            if iy + 1 < ny {
180                                (old[field.idx(ix, iy + 1, iz)] - phi_c) / dx
181                            } else {
182                                0.0
183                            }
184                        };
185                        let dphi_z = if velocity[2] > 0.0 {
186                            if iz > 0 {
187                                (phi_c - old[field.idx(ix, iy, iz - 1)]) / dx
188                            } else {
189                                0.0
190                            }
191                        } else {
192                            if iz + 1 < nz {
193                                (old[field.idx(ix, iy, iz + 1)] - phi_c) / dx
194                            } else {
195                                0.0
196                            }
197                        };
198                        let rhs =
199                            velocity[0] * dphi_x + velocity[1] * dphi_y + velocity[2] * dphi_z;
200                        let idx = field.idx(ix, iy, iz);
201                        field.data[idx] = phi_c - self.dt * rhs;
202                    }
203                }
204            }
205        }
206    }
207    /// Evolve the level-set by mean curvature flow.
208    ///
209    /// Solves โˆ‚ฯ†/โˆ‚t = ฮบ |โˆ‡ฯ†| where ฮบ is the mean curvature.
210    /// Uses explicit Euler integration.
211    ///
212    /// # Arguments
213    /// * `field` โ€“ level-set field (modified in place)
214    pub fn mean_curvature_flow(&self, field: &mut LevelSetField) {
215        let dx = field.dx;
216        let nx = field.nx;
217        let ny = field.ny;
218        let nz = field.nz;
219        for _ in 0..self.n_steps {
220            let old = field.data.clone();
221            let old_field = LevelSetField {
222                nx,
223                ny,
224                nz,
225                dx,
226                origin: field.origin,
227                data: old,
228            };
229            for iz in 0..nz {
230                for iy in 0..ny {
231                    for ix in 0..nx {
232                        let kappa = old_field.mean_curvature(ix, iy, iz);
233                        let grad = old_field.gradient(ix, iy, iz);
234                        let grad_mag =
235                            (grad[0] * grad[0] + grad[1] * grad[1] + grad[2] * grad[2]).sqrt();
236                        let idx = field.idx(ix, iy, iz);
237                        field.data[idx] = old_field.data[idx] + self.dt * kappa * grad_mag;
238                    }
239                }
240            }
241        }
242    }
243    /// Normal velocity extension: propagate the interface normal velocity outward.
244    ///
245    /// For each node computes the update ฯ†_new = ฯ† - dt * F |โˆ‡ฯ†| where F is
246    /// a prescribed scalar normal velocity.
247    ///
248    /// # Arguments
249    /// * `field`            โ€“ level-set field (modified in place)
250    /// * `normal_velocity`  โ€“ scalar normal speed (positive = expansion)
251    pub fn normal_velocity_extension(&self, field: &mut LevelSetField, normal_velocity: f64) {
252        let nx = field.nx;
253        let ny = field.ny;
254        let nz = field.nz;
255        for _ in 0..self.n_steps {
256            let old = field.data.clone();
257            let old_field = LevelSetField {
258                nx,
259                ny,
260                nz,
261                dx: field.dx,
262                origin: field.origin,
263                data: old,
264            };
265            for iz in 0..nz {
266                for iy in 0..ny {
267                    for ix in 0..nx {
268                        let grad = old_field.gradient(ix, iy, iz);
269                        let grad_mag =
270                            (grad[0] * grad[0] + grad[1] * grad[1] + grad[2] * grad[2]).sqrt();
271                        let idx = field.idx(ix, iy, iz);
272                        field.data[idx] =
273                            old_field.data[idx] - self.dt * normal_velocity * grad_mag;
274                    }
275                }
276            }
277        }
278    }
279}
280/// Boolean operations on level-set fields via min/max composition.
281///
282/// All operations create a new [`LevelSetField`] with the same grid layout as
283/// `field_a`.  The fields must have identical grid dimensions, spacing, and origin.
284pub struct LevelSetBoolean;
285impl LevelSetBoolean {
286    /// Union of two level-set fields: ฯ† = min(ฯ†_a, ฯ†_b).
287    ///
288    /// Corresponds to the union of the two implicit surfaces.
289    ///
290    /// # Arguments
291    /// * `field_a` โ€“ first operand
292    /// * `field_b` โ€“ second operand (must have same grid as `field_a`)
293    pub fn union(field_a: &LevelSetField, field_b: &LevelSetField) -> LevelSetField {
294        let mut result = LevelSetField::new(
295            field_a.nx,
296            field_a.ny,
297            field_a.nz,
298            field_a.dx,
299            field_a.origin,
300        );
301        for i in 0..result.data.len() {
302            let va = field_a.data.get(i).copied().unwrap_or(f64::MAX);
303            let vb = field_b.data.get(i).copied().unwrap_or(f64::MAX);
304            result.data[i] = va.min(vb);
305        }
306        result
307    }
308    /// Intersection of two level-set fields: ฯ† = max(ฯ†_a, ฯ†_b).
309    ///
310    /// Corresponds to the intersection of the two implicit surfaces.
311    ///
312    /// # Arguments
313    /// * `field_a` โ€“ first operand
314    /// * `field_b` โ€“ second operand (must have same grid as `field_a`)
315    pub fn intersection(field_a: &LevelSetField, field_b: &LevelSetField) -> LevelSetField {
316        let mut result = LevelSetField::new(
317            field_a.nx,
318            field_a.ny,
319            field_a.nz,
320            field_a.dx,
321            field_a.origin,
322        );
323        for i in 0..result.data.len() {
324            let va = field_a.data.get(i).copied().unwrap_or(f64::MAX);
325            let vb = field_b.data.get(i).copied().unwrap_or(f64::MAX);
326            result.data[i] = va.max(vb);
327        }
328        result
329    }
330    /// Difference A โˆ’ B: ฯ† = max(ฯ†_a, โˆ’ฯ†_b).
331    ///
332    /// Corresponds to removing the region of `field_b` from `field_a`.
333    ///
334    /// # Arguments
335    /// * `field_a` โ€“ base operand
336    /// * `field_b` โ€“ region to subtract
337    pub fn difference(field_a: &LevelSetField, field_b: &LevelSetField) -> LevelSetField {
338        let mut result = LevelSetField::new(
339            field_a.nx,
340            field_a.ny,
341            field_a.nz,
342            field_a.dx,
343            field_a.origin,
344        );
345        for i in 0..result.data.len() {
346            let va = field_a.data.get(i).copied().unwrap_or(f64::MAX);
347            let vb = field_b.data.get(i).copied().unwrap_or(f64::MAX);
348            result.data[i] = va.max(-vb);
349        }
350        result
351    }
352    /// Complement (negation): ฯ† = โˆ’ฯ†_a.
353    ///
354    /// Flips the inside/outside classification of the level-set.
355    ///
356    /// # Arguments
357    /// * `field_a` โ€“ the level-set field to negate
358    pub fn complement(field_a: &LevelSetField) -> LevelSetField {
359        let mut result = LevelSetField::new(
360            field_a.nx,
361            field_a.ny,
362            field_a.nz,
363            field_a.dx,
364            field_a.origin,
365        );
366        for i in 0..result.data.len() {
367            result.data[i] = -field_a.data[i];
368        }
369        result
370    }
371    /// Smooth minimum blend (Cยน union): ฯ† = smin(ฯ†_a, ฯ†_b, k).
372    ///
373    /// Produces a smooth blend at the union boundary with blend radius `k`.
374    ///
375    /// # Arguments
376    /// * `field_a` โ€“ first operand
377    /// * `field_b` โ€“ second operand
378    /// * `k`       โ€“ blend radius (larger k โ†’ smoother blend)
379    pub fn smooth_union(field_a: &LevelSetField, field_b: &LevelSetField, k: f64) -> LevelSetField {
380        let mut result = LevelSetField::new(
381            field_a.nx,
382            field_a.ny,
383            field_a.nz,
384            field_a.dx,
385            field_a.origin,
386        );
387        for i in 0..result.data.len() {
388            let va = field_a.data.get(i).copied().unwrap_or(f64::MAX);
389            let vb = field_b.data.get(i).copied().unwrap_or(f64::MAX);
390            let h = (0.5 + 0.5 * (vb - va) / k.max(1e-30)).clamp(0.0, 1.0);
391            result.data[i] = va * h + vb * (1.0 - h) - k * h * (1.0 - h);
392        }
393        result
394    }
395}
396/// Signed distance function sampled on a uniform 3-D Cartesian grid.
397///
398/// The grid has `nx ร— ny ร— nz` cells.  Values are stored in row-major order
399/// as `data[iz * ny * nx + iy * nx + ix]`.  Positive values are outside the
400/// surface; negative values are inside.
401pub struct LevelSetField {
402    /// Number of grid points along x.
403    pub nx: usize,
404    /// Number of grid points along y.
405    pub ny: usize,
406    /// Number of grid points along z.
407    pub nz: usize,
408    /// Grid spacing (uniform in all directions).
409    pub dx: f64,
410    /// Origin of the grid (coordinates of node \[0,0,0\]).
411    pub origin: [f64; 3],
412    /// Flat array of signed distance values, length `nx * ny * nz`.
413    pub data: Vec<f64>,
414}
415impl LevelSetField {
416    /// Create a new [`LevelSetField`] filled with `f64::MAX` (uninitialized).
417    ///
418    /// # Arguments
419    /// * `nx`, `ny`, `nz` โ€“ grid dimensions
420    /// * `dx`             โ€“ uniform grid spacing
421    /// * `origin`         โ€“ world-space coordinates of node (0,0,0)
422    pub fn new(nx: usize, ny: usize, nz: usize, dx: f64, origin: [f64; 3]) -> Self {
423        let data = vec![f64::MAX; nx * ny * nz];
424        Self {
425            nx,
426            ny,
427            nz,
428            dx,
429            origin,
430            data,
431        }
432    }
433    /// Linear index for grid node (ix, iy, iz).
434    #[inline]
435    pub fn idx(&self, ix: usize, iy: usize, iz: usize) -> usize {
436        iz * self.ny * self.nx + iy * self.nx + ix
437    }
438    /// World-space coordinates of grid node (ix, iy, iz).
439    #[inline]
440    pub fn node_pos(&self, ix: usize, iy: usize, iz: usize) -> [f64; 3] {
441        [
442            self.origin[0] + ix as f64 * self.dx,
443            self.origin[1] + iy as f64 * self.dx,
444            self.origin[2] + iz as f64 * self.dx,
445        ]
446    }
447    /// Get the level-set value at grid node (ix, iy, iz).
448    ///
449    /// Returns `f64::MAX` for out-of-bounds nodes.
450    pub fn get(&self, ix: usize, iy: usize, iz: usize) -> f64 {
451        if ix >= self.nx || iy >= self.ny || iz >= self.nz {
452            return f64::MAX;
453        }
454        self.data[self.idx(ix, iy, iz)]
455    }
456    /// Set the level-set value at grid node (ix, iy, iz).
457    ///
458    /// Silently ignores out-of-bounds writes.
459    pub fn set(&mut self, ix: usize, iy: usize, iz: usize, value: f64) {
460        if ix >= self.nx || iy >= self.ny || iz >= self.nz {
461            return;
462        }
463        let i = self.idx(ix, iy, iz);
464        self.data[i] = value;
465    }
466    /// Tri-linear interpolation of the level-set field at world position `p`.
467    ///
468    /// Returns `f64::MAX` for positions outside the grid.
469    pub fn interpolate(&self, p: [f64; 3]) -> f64 {
470        let fx = (p[0] - self.origin[0]) / self.dx;
471        let fy = (p[1] - self.origin[1]) / self.dx;
472        let fz = (p[2] - self.origin[2]) / self.dx;
473        if fx < 0.0 || fy < 0.0 || fz < 0.0 {
474            return f64::MAX;
475        }
476        let ix = fx as usize;
477        let iy = fy as usize;
478        let iz = fz as usize;
479        if ix + 1 >= self.nx || iy + 1 >= self.ny || iz + 1 >= self.nz {
480            return f64::MAX;
481        }
482        let tx = fx - ix as f64;
483        let ty = fy - iy as f64;
484        let tz = fz - iz as f64;
485        let c000 = self.get(ix, iy, iz);
486        let c100 = self.get(ix + 1, iy, iz);
487        let c010 = self.get(ix, iy + 1, iz);
488        let c110 = self.get(ix + 1, iy + 1, iz);
489        let c001 = self.get(ix, iy, iz + 1);
490        let c101 = self.get(ix + 1, iy, iz + 1);
491        let c011 = self.get(ix, iy + 1, iz + 1);
492        let c111 = self.get(ix + 1, iy + 1, iz + 1);
493        let c00 = c000 * (1.0 - tx) + c100 * tx;
494        let c10 = c010 * (1.0 - tx) + c110 * tx;
495        let c01 = c001 * (1.0 - tx) + c101 * tx;
496        let c11 = c011 * (1.0 - tx) + c111 * tx;
497        let c0 = c00 * (1.0 - ty) + c10 * ty;
498        let c1 = c01 * (1.0 - ty) + c11 * ty;
499        c0 * (1.0 - tz) + c1 * tz
500    }
501    /// Numerical gradient of the level-set field at grid node (ix, iy, iz).
502    ///
503    /// Uses central differences where possible, one-sided differences at boundaries.
504    pub fn gradient(&self, ix: usize, iy: usize, iz: usize) -> [f64; 3] {
505        let gx = if ix == 0 {
506            (self.get(1, iy, iz) - self.get(0, iy, iz)) / self.dx
507        } else if ix + 1 == self.nx {
508            (self.get(ix, iy, iz) - self.get(ix - 1, iy, iz)) / self.dx
509        } else {
510            (self.get(ix + 1, iy, iz) - self.get(ix - 1, iy, iz)) / (2.0 * self.dx)
511        };
512        let gy = if iy == 0 {
513            (self.get(ix, 1, iz) - self.get(ix, 0, iz)) / self.dx
514        } else if iy + 1 == self.ny {
515            (self.get(ix, iy, iz) - self.get(ix, iy - 1, iz)) / self.dx
516        } else {
517            (self.get(ix, iy + 1, iz) - self.get(ix, iy - 1, iz)) / (2.0 * self.dx)
518        };
519        let gz = if iz == 0 {
520            (self.get(ix, iy, 1) - self.get(ix, iy, 0)) / self.dx
521        } else if iz + 1 == self.nz {
522            (self.get(ix, iy, iz) - self.get(ix, iy, iz - 1)) / self.dx
523        } else {
524            (self.get(ix, iy, iz + 1) - self.get(ix, iy, iz - 1)) / (2.0 * self.dx)
525        };
526        [gx, gy, gz]
527    }
528    /// Mean curvature ฮบ = div(โˆ‡ฯ† / |โˆ‡ฯ†|) at grid node (ix, iy, iz).
529    ///
530    /// Computed via second-order finite differences.
531    pub fn mean_curvature(&self, ix: usize, iy: usize, iz: usize) -> f64 {
532        let phi = self.get(ix, iy, iz);
533        let dx2 = self.dx * self.dx;
534        let get = |x: usize, y: usize, z: usize| -> f64 {
535            let xc = x.min(self.nx - 1);
536            let yc = y.min(self.ny - 1);
537            let zc = z.min(self.nz - 1);
538            self.get(xc, yc, zc)
539        };
540        let pxp = if ix + 1 < self.nx {
541            get(ix + 1, iy, iz)
542        } else {
543            phi
544        };
545        let pxm = if ix > 0 { get(ix - 1, iy, iz) } else { phi };
546        let pyp = if iy + 1 < self.ny {
547            get(ix, iy + 1, iz)
548        } else {
549            phi
550        };
551        let pym = if iy > 0 { get(ix, iy - 1, iz) } else { phi };
552        let pzp = if iz + 1 < self.nz {
553            get(ix, iy, iz + 1)
554        } else {
555            phi
556        };
557        let pzm = if iz > 0 { get(ix, iy, iz - 1) } else { phi };
558        let phix = (pxp - pxm) / (2.0 * self.dx);
559        let phiy = (pyp - pym) / (2.0 * self.dx);
560        let phiz = (pzp - pzm) / (2.0 * self.dx);
561        let phixx = (pxp - 2.0 * phi + pxm) / dx2;
562        let phiyy = (pyp - 2.0 * phi + pym) / dx2;
563        let phizz = (pzp - 2.0 * phi + pzm) / dx2;
564        let grad2 = phix * phix + phiy * phiy + phiz * phiz;
565        let grad_mag = grad2.sqrt().max(1e-30);
566
567        (phixx + phiyy + phizz) / grad_mag
568            - (phix * phix * phixx
569                + phiy * phiy * phiyy
570                + phiz * phiz * phizz
571                + 2.0
572                    * (phix * phiy * ((pxp + pyp - pxm - pym) / (4.0 * self.dx * self.dx) - 0.0)
573                        + phix * phiz * 0.0
574                        + phiy * phiz * 0.0))
575                / (grad_mag * grad2).max(1e-30)
576    }
577    /// Initialize the level-set as a sphere SDF.
578    ///
579    /// Sets ฯ†(x) = |x โˆ’ center| โˆ’ radius for all nodes.
580    pub fn init_sphere(&mut self, center: [f64; 3], radius: f64) {
581        for iz in 0..self.nz {
582            for iy in 0..self.ny {
583                for ix in 0..self.nx {
584                    let p = self.node_pos(ix, iy, iz);
585                    let d = ((p[0] - center[0]).powi(2)
586                        + (p[1] - center[1]).powi(2)
587                        + (p[2] - center[2]).powi(2))
588                    .sqrt();
589                    self.set(ix, iy, iz, d - radius);
590                }
591            }
592        }
593    }
594    /// Initialize the level-set as an axis-aligned box SDF.
595    ///
596    /// Sets ฯ†(x) to the signed distance to the box \[lo, hi\].
597    pub fn init_box(&mut self, lo: [f64; 3], hi: [f64; 3]) {
598        for iz in 0..self.nz {
599            for iy in 0..self.ny {
600                for ix in 0..self.nx {
601                    let p = self.node_pos(ix, iy, iz);
602                    let dx = (lo[0] - p[0]).max(p[0] - hi[0]).max(0.0);
603                    let dy = (lo[1] - p[1]).max(p[1] - hi[1]).max(0.0);
604                    let dz = (lo[2] - p[2]).max(p[2] - hi[2]).max(0.0);
605                    let outside = (dx * dx + dy * dy + dz * dz).sqrt();
606                    let inside = (p[0] - lo[0])
607                        .min(hi[0] - p[0])
608                        .min(p[1] - lo[1])
609                        .min(hi[1] - p[1])
610                        .min(p[2] - lo[2])
611                        .min(hi[2] - p[2]);
612                    let sdf = if inside < 0.0 { outside } else { -inside };
613                    self.set(ix, iy, iz, sdf);
614                }
615            }
616        }
617    }
618    /// Count nodes with ฯ† โ‰ค 0 (inside or on the interface).
619    pub fn count_inside(&self) -> usize {
620        self.data.iter().filter(|&&v| v <= 0.0).count()
621    }
622    /// Minimum value of ฯ† over all nodes.
623    pub fn min_value(&self) -> f64 {
624        self.data.iter().cloned().fold(f64::MAX, f64::min)
625    }
626    /// Maximum value of ฯ† over all nodes.
627    pub fn max_value(&self) -> f64 {
628        self.data.iter().cloned().fold(f64::MIN, f64::max)
629    }
630}
631/// Level-set reinitialization via fast marching method (redistancing).
632///
633/// Converts an arbitrary level-set function into a proper signed distance
634/// function while preserving the zero-level-set location.
635pub struct LevelSetReinit {
636    /// Convergence tolerance for iterative reinitialization.
637    pub tol: f64,
638    /// Maximum number of pseudo-time iterations.
639    pub max_iter: usize,
640}
641impl LevelSetReinit {
642    /// Create a new reinitialization solver with given tolerance and iteration limit.
643    ///
644    /// # Arguments
645    /// * `tol`      โ€“ convergence tolerance (โˆž-norm of update)
646    /// * `max_iter` โ€“ maximum pseudo-time steps
647    pub fn new(tol: f64, max_iter: usize) -> Self {
648        Self { tol, max_iter }
649    }
650    /// Reinitialize a level-set field using the PDE-based Sussman scheme.
651    ///
652    /// Integrates โˆ‚ฯ†/โˆ‚ฯ„ = sign(ฯ†โ‚€)(1 โˆ’ |โˆ‡ฯ†|) until steady state.
653    /// Modifies `field.data` in place.
654    ///
655    /// # Arguments
656    /// * `field` โ€“ the level-set field to reinitialize
657    pub fn reinitialize(&self, field: &mut LevelSetField) {
658        let dx = field.dx;
659        let dt = 0.5 * dx;
660        let nx = field.nx;
661        let ny = field.ny;
662        let nz = field.nz;
663        let phi0: Vec<f64> = field.data.clone();
664        let sign0: Vec<f64> = phi0
665            .iter()
666            .map(|&v| {
667                if v > 0.0 {
668                    1.0
669                } else if v < 0.0 {
670                    -1.0
671                } else {
672                    0.0
673                }
674            })
675            .collect();
676        for _iter in 0..self.max_iter {
677            let mut new_data = field.data.clone();
678            let mut max_change = 0.0_f64;
679            for iz in 0..nz {
680                for iy in 0..ny {
681                    for ix in 0..nx {
682                        let idx = field.idx(ix, iy, iz);
683                        let s = sign0[idx];
684                        if s == 0.0 {
685                            continue;
686                        }
687                        let phi_c = field.data[idx];
688                        let grad_mag = godunov_grad_mag(field, ix, iy, iz, s);
689                        let update = s * (1.0 - grad_mag);
690                        new_data[idx] = phi_c + dt * update;
691                        max_change = max_change.max((dt * update).abs());
692                    }
693                }
694            }
695            field.data = new_data;
696            if max_change < self.tol {
697                break;
698            }
699        }
700    }
701    /// Simple iterative fast marching sweep reinitialization (alternating directions).
702    ///
703    /// Faster than the PDE approach for well-behaved fields; performs `n_sweeps`
704    /// alternating-direction passes of the first-order update.
705    ///
706    /// # Arguments
707    /// * `field`   โ€“ the level-set field to reinitialize
708    /// * `n_sweeps`โ€“ number of alternating-direction sweep passes
709    pub fn fast_sweep(&self, field: &mut LevelSetField, n_sweeps: usize) {
710        let dx = field.dx;
711        let nx = field.nx;
712        let ny = field.ny;
713        let nz = field.nz;
714        for sweep in 0..n_sweeps {
715            let ix_range: Vec<usize> = if sweep % 2 == 0 {
716                (0..nx).collect()
717            } else {
718                (0..nx).rev().collect()
719            };
720            let iy_range: Vec<usize> = if (sweep / 2) % 2 == 0 {
721                (0..ny).collect()
722            } else {
723                (0..ny).rev().collect()
724            };
725            let iz_range: Vec<usize> = if (sweep / 4) % 2 == 0 {
726                (0..nz).collect()
727            } else {
728                (0..nz).rev().collect()
729            };
730            for &iz in &iz_range {
731                for &iy in &iy_range {
732                    for &ix in &ix_range {
733                        let phi_c = field.get(ix, iy, iz);
734                        let axm = if ix > 0 {
735                            field.get(ix - 1, iy, iz)
736                        } else {
737                            phi_c
738                        };
739                        let axp = if ix + 1 < nx {
740                            field.get(ix + 1, iy, iz)
741                        } else {
742                            phi_c
743                        };
744                        let aym = if iy > 0 {
745                            field.get(ix, iy - 1, iz)
746                        } else {
747                            phi_c
748                        };
749                        let ayp = if iy + 1 < ny {
750                            field.get(ix, iy + 1, iz)
751                        } else {
752                            phi_c
753                        };
754                        let azm = if iz > 0 {
755                            field.get(ix, iy, iz - 1)
756                        } else {
757                            phi_c
758                        };
759                        let azp = if iz + 1 < nz {
760                            field.get(ix, iy, iz + 1)
761                        } else {
762                            phi_c
763                        };
764                        let ax = if phi_c >= 0.0 {
765                            axm.min(axp)
766                        } else {
767                            axm.max(axp)
768                        };
769                        let ay = if phi_c >= 0.0 {
770                            aym.min(ayp)
771                        } else {
772                            aym.max(ayp)
773                        };
774                        let az = if phi_c >= 0.0 {
775                            azm.min(azp)
776                        } else {
777                            azm.max(azp)
778                        };
779                        let new_val = if phi_c >= 0.0 {
780                            (ax.abs().min(ay.abs()).min(az.abs()) + dx).min(phi_c.abs())
781                        } else {
782                            let d = (ax.abs().min(ay.abs()).min(az.abs()) + dx).min(phi_c.abs());
783                            -d
784                        };
785                        let _ = ax;
786                        let _ = ay;
787                        let _ = az;
788                        let _ = new_val;
789                        let candidate = if phi_c >= 0.0 {
790                            axm.max(0.0).min(axp.max(0.0)).min(phi_c).max(0.0) + dx
791                        } else {
792                            (axm.min(0.0).max(axp.min(0.0)).max(phi_c).min(0.0)) - dx
793                        };
794                        if (candidate.abs() < phi_c.abs()) && (candidate * phi_c >= 0.0) {
795                            field.set(ix, iy, iz, candidate);
796                        }
797                    }
798                }
799            }
800        }
801    }
802}
803/// Output triangle from marching cubes isosurface extraction.
804#[derive(Debug, Clone)]
805pub struct McTriangle {
806    /// Vertex positions (3 vertices, each an \[x, y, z\] world-space coordinate).
807    pub vertices: [[f64; 3]; 3],
808}
809/// Isosurface extraction via the Marching Cubes algorithm.
810///
811/// Implements the full 256-case lookup table for robust triangle generation
812/// at the specified isovalue.
813pub struct IsosurfaceExtraction {
814    /// Isovalue defining the surface (default: 0.0 for signed distance fields).
815    pub isovalue: f64,
816}
817impl IsosurfaceExtraction {
818    /// Create a new [`IsosurfaceExtraction`] with the given isovalue.
819    ///
820    /// # Arguments
821    /// * `isovalue` โ€“ the scalar value defining the isosurface
822    pub fn new(isovalue: f64) -> Self {
823        Self { isovalue }
824    }
825    /// Extract the isosurface from a [`LevelSetField`] as a list of triangles.
826    ///
827    /// Iterates over all cells in the grid, classifies each cube, and uses the
828    /// Marching Cubes lookup table to generate triangles.
829    pub fn extract(&self, field: &LevelSetField) -> Vec<McTriangle> {
830        let mut triangles = Vec::new();
831        let nx = field.nx;
832        let ny = field.ny;
833        let nz = field.nz;
834        if nx < 2 || ny < 2 || nz < 2 {
835            return triangles;
836        }
837        for iz in 0..nz - 1 {
838            for iy in 0..ny - 1 {
839                for ix in 0..nx - 1 {
840                    self.process_cell(field, ix, iy, iz, &mut triangles);
841                }
842            }
843        }
844        triangles
845    }
846    /// Process a single cube cell and append any generated triangles.
847    fn process_cell(
848        &self,
849        field: &LevelSetField,
850        ix: usize,
851        iy: usize,
852        iz: usize,
853        triangles: &mut Vec<McTriangle>,
854    ) {
855        let v: [f64; 8] = [
856            field.get(ix, iy, iz),
857            field.get(ix + 1, iy, iz),
858            field.get(ix + 1, iy + 1, iz),
859            field.get(ix, iy + 1, iz),
860            field.get(ix, iy, iz + 1),
861            field.get(ix + 1, iy, iz + 1),
862            field.get(ix + 1, iy + 1, iz + 1),
863            field.get(ix, iy + 1, iz + 1),
864        ];
865        let p: [[f64; 3]; 8] = [
866            field.node_pos(ix, iy, iz),
867            field.node_pos(ix + 1, iy, iz),
868            field.node_pos(ix + 1, iy + 1, iz),
869            field.node_pos(ix, iy + 1, iz),
870            field.node_pos(ix, iy, iz + 1),
871            field.node_pos(ix + 1, iy, iz + 1),
872            field.node_pos(ix + 1, iy + 1, iz + 1),
873            field.node_pos(ix, iy + 1, iz + 1),
874        ];
875        let mut cube_idx = 0u8;
876        for (i, &vi) in v.iter().enumerate() {
877            if vi < self.isovalue {
878                cube_idx |= 1 << i;
879            }
880        }
881        if cube_idx == 0 || cube_idx == 255 {
882            return;
883        }
884        let edge_flags = MC_EDGE_TABLE[cube_idx as usize];
885        let mut edge_verts = [[0.0f64; 3]; 12];
886        for e in 0..12 {
887            if edge_flags & (1 << e) != 0 {
888                let (a, b) = MC_EDGE_CORNERS[e];
889                edge_verts[e] = interp_vertex(p[a], p[b], v[a], v[b], self.isovalue);
890            }
891        }
892        let tris = &MC_TRI_TABLE[cube_idx as usize];
893        let mut i = 0;
894        while i < tris.len() && tris[i] != -1 {
895            let tri = McTriangle {
896                vertices: [
897                    edge_verts[tris[i] as usize],
898                    edge_verts[tris[i + 1] as usize],
899                    edge_verts[tris[i + 2] as usize],
900                ],
901            };
902            triangles.push(tri);
903            i += 3;
904        }
905    }
906    /// Count the number of triangles in the isosurface.
907    pub fn count_triangles(&self, field: &LevelSetField) -> usize {
908        self.extract(field).len()
909    }
910}