Skip to main content

oxiphysics_geometry/procedural_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/// Parametric surface types: tensor product, Coons patch.
8#[derive(Debug, Clone)]
9pub struct SurfaceParametric {
10    /// Control net: `rows × cols` array of 3D control points
11    pub control_net: Vec<Vec<[f64; 3]>>,
12}
13impl SurfaceParametric {
14    /// Create a new parametric surface from a control net.
15    pub fn new(control_net: Vec<Vec<[f64; 3]>>) -> Self {
16        Self { control_net }
17    }
18    /// Evaluate a tensor-product Bézier surface at parameters (u, v).
19    pub fn tensor_bezier(&self, u: f64, v: f64) -> [f64; 3] {
20        let rows = self.control_net.len();
21        if rows == 0 {
22            return [0.0, 0.0, 0.0];
23        }
24        let row_pts: Vec<[f64; 3]> = self
25            .control_net
26            .iter()
27            .map(|row| {
28                let curve = CurveParametric::new(row.clone());
29                curve.bezier_de_casteljau(u)
30            })
31            .collect();
32        let col_curve = CurveParametric::new(row_pts);
33        col_curve.bezier_de_casteljau(v)
34    }
35    /// Evaluate a bilinear Coons patch at parameters (u, v).
36    ///
37    /// Requires exactly a 2×2 control net (four corner points).
38    pub fn coons_patch(&self, u: f64, v: f64) -> [f64; 3] {
39        if self.control_net.len() < 2 || self.control_net[0].len() < 2 {
40            return [0.0, 0.0, 0.0];
41        }
42        let p00 = self.control_net[0][0];
43        let p01 = self.control_net[0][1];
44        let p10 = self.control_net[1][0];
45        let p11 = self.control_net[1][1];
46        let w00 = (1.0 - u) * (1.0 - v);
47        let w01 = (1.0 - u) * v;
48        let w10 = u * (1.0 - v);
49        let w11 = u * v;
50        [
51            w00 * p00[0] + w01 * p01[0] + w10 * p10[0] + w11 * p11[0],
52            w00 * p00[1] + w01 * p01[1] + w10 * p10[1] + w11 * p11[1],
53            w00 * p00[2] + w01 * p01[2] + w10 * p10[2] + w11 * p11[2],
54        ]
55    }
56    /// Compute the approximate surface normal at (u, v) using finite differences.
57    pub fn normal(&self, u: f64, v: f64) -> [f64; 3] {
58        let eps = 1e-4;
59        let pu = self.tensor_bezier((u + eps).min(1.0), v);
60        let pu_neg = self.tensor_bezier((u - eps).max(0.0), v);
61        let pv = self.tensor_bezier(u, (v + eps).min(1.0));
62        let pv_neg = self.tensor_bezier(u, (v - eps).max(0.0));
63        let du = [
64            (pu[0] - pu_neg[0]) / (2.0 * eps),
65            (pu[1] - pu_neg[1]) / (2.0 * eps),
66            (pu[2] - pu_neg[2]) / (2.0 * eps),
67        ];
68        let dv = [
69            (pv[0] - pv_neg[0]) / (2.0 * eps),
70            (pv[1] - pv_neg[1]) / (2.0 * eps),
71            (pv[2] - pv_neg[2]) / (2.0 * eps),
72        ];
73        let n = [
74            du[1] * dv[2] - du[2] * dv[1],
75            du[2] * dv[0] - du[0] * dv[2],
76            du[0] * dv[1] - du[1] * dv[0],
77        ];
78        let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
79        if len < 1e-12 {
80            [0.0, 0.0, 1.0]
81        } else {
82            [n[0] / len, n[1] / len, n[2] / len]
83        }
84    }
85    /// Sample the surface at `nu × nv` grid points.
86    pub fn sample(&self, nu: usize, nv: usize) -> Vec<Vec<[f64; 3]>> {
87        (0..nu)
88            .map(|i| {
89                let u = i as f64 / (nu - 1).max(1) as f64;
90                (0..nv)
91                    .map(|j| {
92                        let v = j as f64 / (nv - 1).max(1) as f64;
93                        self.tensor_bezier(u, v)
94                    })
95                    .collect()
96            })
97            .collect()
98    }
99}
100/// Turtle graphics state for L-system interpretation.
101#[derive(Debug, Clone, Copy)]
102pub struct TurtleState {
103    /// Current x position
104    pub x: f64,
105    /// Current y position
106    pub y: f64,
107    /// Current heading angle in radians
108    pub angle: f64,
109}
110impl TurtleState {
111    /// Create a new TurtleState at the origin pointing right.
112    pub fn new() -> Self {
113        Self {
114            x: 0.0,
115            y: 0.0,
116            angle: 0.0,
117        }
118    }
119}
120/// 2D Perlin noise generator with permutation table.
121#[derive(Debug, Clone)]
122pub struct PerlinNoise2d {
123    /// Permutation table (512 entries)
124    pub(super) perm: Vec<u8>,
125}
126impl PerlinNoise2d {
127    /// Create a new Perlin noise generator with a given seed.
128    pub fn new(seed: u64) -> Self {
129        let mut perm = (0u8..=255).collect::<Vec<_>>();
130        let mut s = seed;
131        for i in (1..256).rev() {
132            s = s
133                .wrapping_mul(6_364_136_223_846_793_005)
134                .wrapping_add(1_442_695_040_888_963_407);
135            let j = (s >> 33) as usize % (i + 1);
136            perm.swap(i, j);
137        }
138        let mut full = perm.clone();
139        full.extend_from_slice(&perm);
140        Self { perm: full }
141    }
142    fn fade(t: f64) -> f64 {
143        t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
144    }
145    fn lerp(a: f64, b: f64, t: f64) -> f64 {
146        a + t * (b - a)
147    }
148    fn grad2(hash: u8, x: f64, y: f64) -> f64 {
149        match hash & 3 {
150            0 => x + y,
151            1 => -x + y,
152            2 => x - y,
153            _ => -x - y,
154        }
155    }
156    /// Evaluate 2D Perlin noise at (x, y). Returns value in approximately \[-1, 1\].
157    pub fn noise(&self, x: f64, y: f64) -> f64 {
158        let xi = x.floor() as i32;
159        let yi = y.floor() as i32;
160        let xf = x - xi as f64;
161        let yf = y - yi as f64;
162        let u = Self::fade(xf);
163        let v = Self::fade(yf);
164        let aa = self.perm[(self.perm[(xi & 255) as usize] as i32 + (yi & 255)) as usize & 255];
165        let ab =
166            self.perm[(self.perm[(xi & 255) as usize] as i32 + ((yi + 1) & 255)) as usize & 255];
167        let ba =
168            self.perm[(self.perm[((xi + 1) & 255) as usize] as i32 + (yi & 255)) as usize & 255];
169        let bb = self.perm
170            [(self.perm[((xi + 1) & 255) as usize] as i32 + ((yi + 1) & 255)) as usize & 255];
171        let x1 = Self::lerp(Self::grad2(aa, xf, yf), Self::grad2(ba, xf - 1.0, yf), u);
172        let x2 = Self::lerp(
173            Self::grad2(ab, xf, yf - 1.0),
174            Self::grad2(bb, xf - 1.0, yf - 1.0),
175            u,
176        );
177        Self::lerp(x1, x2, v)
178    }
179    /// Fractional Brownian Motion (fBm): sum of octaves of Perlin noise.
180    ///
181    /// Higher octave count produces more detail (roughness).
182    pub fn fbm(&self, x: f64, y: f64, octaves: u32, lacunarity: f64, gain: f64) -> f64 {
183        let mut value = 0.0;
184        let mut amplitude = 1.0;
185        let mut frequency = 1.0;
186        let mut max_val = 0.0;
187        for _ in 0..octaves {
188            value += amplitude * self.noise(x * frequency, y * frequency);
189            max_val += amplitude;
190            amplitude *= gain;
191            frequency *= lacunarity;
192        }
193        if max_val > 0.0 { value / max_val } else { 0.0 }
194    }
195    /// Turbulence: sum of absolute values of noise octaves.
196    pub fn turbulence(&self, x: f64, y: f64, octaves: u32, lacunarity: f64, gain: f64) -> f64 {
197        let mut value = 0.0;
198        let mut amplitude = 1.0;
199        let mut frequency = 1.0;
200        for _ in 0..octaves {
201            value += amplitude * self.noise(x * frequency, y * frequency).abs();
202            amplitude *= gain;
203            frequency *= lacunarity;
204        }
205        value
206    }
207}
208/// Worley (cellular) noise generator.
209#[derive(Debug, Clone)]
210pub struct WorleyNoise {
211    /// Feature points (2D)
212    pub(super) points: Vec<[f64; 2]>,
213}
214impl WorleyNoise {
215    /// Create Worley noise with randomly distributed feature points in \[0, 1\]^2.
216    pub fn new(num_points: usize, seed: u64) -> Self {
217        let mut s = seed;
218        let mut points = Vec::with_capacity(num_points);
219        for _ in 0..num_points {
220            s = s
221                .wrapping_mul(6_364_136_223_846_793_005)
222                .wrapping_add(1_442_695_040_888_963_407);
223            let x = (s >> 11) as f64 / (1u64 << 53) as f64;
224            s = s
225                .wrapping_mul(6_364_136_223_846_793_005)
226                .wrapping_add(1_442_695_040_888_963_407);
227            let y = (s >> 11) as f64 / (1u64 << 53) as f64;
228            points.push([x, y]);
229        }
230        Self { points }
231    }
232    /// Evaluate F1 (distance to nearest feature point) at (x, y).
233    pub fn f1(&self, x: f64, y: f64) -> f64 {
234        self.points
235            .iter()
236            .map(|&[px, py]| {
237                let dx = x - px;
238                let dy = y - py;
239                (dx * dx + dy * dy).sqrt()
240            })
241            .fold(f64::MAX, f64::min)
242    }
243    /// Evaluate F2 - F1 (distance to second nearest minus nearest).
244    pub fn f2_minus_f1(&self, x: f64, y: f64) -> f64 {
245        let mut d1 = f64::MAX;
246        let mut d2 = f64::MAX;
247        for &[px, py] in &self.points {
248            let d = ((x - px).powi(2) + (y - py).powi(2)).sqrt();
249            if d < d1 {
250                d2 = d1;
251                d1 = d;
252            } else if d < d2 {
253                d2 = d;
254            }
255        }
256        d2 - d1
257    }
258}
259/// Lindenmayer system (L-system) for generating fractal geometry strings.
260#[derive(Debug, Clone)]
261pub struct LSystem {
262    /// Axiom (initial string)
263    pub axiom: String,
264    /// Production rules
265    pub rules: Vec<LSystemRule>,
266    /// Angle increment in radians for turtle interpretation
267    pub angle_increment: f64,
268    /// Step size for turtle movement
269    pub step_size: f64,
270}
271impl LSystem {
272    /// Create a new L-system.
273    pub fn new(
274        axiom: &str,
275        rules: Vec<LSystemRule>,
276        angle_increment_deg: f64,
277        step_size: f64,
278    ) -> Self {
279        Self {
280            axiom: axiom.to_string(),
281            rules,
282            angle_increment: angle_increment_deg.to_radians(),
283            step_size,
284        }
285    }
286    /// Apply the production rules for `n` generations.
287    pub fn evolve(&self, generations: u32) -> LSystemState {
288        let mut current = self.axiom.clone();
289        for _ in 0..generations {
290            let mut next = String::new();
291            for ch in current.chars() {
292                if let Some(rule) = self.rules.iter().find(|r| r.predecessor == ch) {
293                    next.push_str(&rule.successor);
294                } else {
295                    next.push(ch);
296                }
297            }
298            current = next;
299        }
300        LSystemState {
301            string: current,
302            generation: generations,
303        }
304    }
305    /// Interpret the L-system string as turtle graphics commands.
306    ///
307    /// Returns a vector of line segments as pairs of 2D points.
308    /// Symbols: `F` = move forward (draw), `f` = move forward (no draw),
309    /// `+` = turn left, `-` = turn right, `[` = push state, `]` = pop state.
310    pub fn interpret(&self, state: &LSystemState) -> Vec<([f64; 2], [f64; 2])> {
311        let mut segments = Vec::new();
312        let mut turtle = TurtleState::new();
313        let mut stack: Vec<TurtleState> = Vec::new();
314        for ch in state.string.chars() {
315            match ch {
316                'F' | 'G' => {
317                    let nx = turtle.x + self.step_size * turtle.angle.cos();
318                    let ny = turtle.y + self.step_size * turtle.angle.sin();
319                    segments.push(([turtle.x, turtle.y], [nx, ny]));
320                    turtle.x = nx;
321                    turtle.y = ny;
322                }
323                'f' => {
324                    turtle.x += self.step_size * turtle.angle.cos();
325                    turtle.y += self.step_size * turtle.angle.sin();
326                }
327                '+' => turtle.angle += self.angle_increment,
328                '-' => turtle.angle -= self.angle_increment,
329                '[' => stack.push(turtle),
330                ']' => {
331                    if let Some(s) = stack.pop() {
332                        turtle = s;
333                    }
334                }
335                _ => {}
336            }
337        }
338        segments
339    }
340    /// Create an L-system for the Koch snowflake.
341    pub fn koch_curve() -> Self {
342        Self::new(
343            "F--F--F",
344            vec![LSystemRule::new('F', "F+F--F+F")],
345            60.0,
346            1.0,
347        )
348    }
349    /// Create an L-system for the Sierpinski triangle.
350    pub fn sierpinski() -> Self {
351        Self::new(
352            "F-G-G",
353            vec![
354                LSystemRule::new('F', "F-G+F+G-F"),
355                LSystemRule::new('G', "GG"),
356            ],
357            120.0,
358            1.0,
359        )
360    }
361    /// Create an L-system for the dragon curve.
362    pub fn dragon_curve() -> Self {
363        Self::new(
364            "FX",
365            vec![
366                LSystemRule::new('X', "X+YF+"),
367                LSystemRule::new('Y', "-FX-Y"),
368            ],
369            90.0,
370            1.0,
371        )
372    }
373    /// Create an L-system for plant branching.
374    pub fn plant_branching() -> Self {
375        Self::new(
376            "X",
377            vec![
378                LSystemRule::new('X', "F+[[X]-X]-F[-FX]+X"),
379                LSystemRule::new('F', "FF"),
380            ],
381            25.0,
382            1.0,
383        )
384    }
385}
386/// A single affine transformation for an IFS.
387#[derive(Debug, Clone, Copy)]
388pub struct IfsTransform {
389    /// 2×2 linear part: a coefficient
390    pub a: f64,
391    /// 2×2 linear part: b coefficient
392    pub b: f64,
393    /// 2×2 linear part: c coefficient
394    pub c: f64,
395    /// 2×2 linear part: d coefficient
396    pub d: f64,
397    /// Translation x
398    pub e: f64,
399    /// Translation y
400    pub f: f64,
401    /// Probability weight
402    pub prob: f64,
403}
404impl IfsTransform {
405    /// Create a new IFS affine transform.
406    pub fn new(a: f64, b: f64, c: f64, d: f64, e: f64, f_val: f64, prob: f64) -> Self {
407        Self {
408            a,
409            b,
410            c,
411            d,
412            e,
413            f: f_val,
414            prob,
415        }
416    }
417    fn apply(&self, x: f64, y: f64) -> (f64, f64) {
418        (
419            self.a * x + self.b * y + self.e,
420            self.c * x + self.d * y + self.f,
421        )
422    }
423}
424/// Reaction-diffusion simulation using the Gray-Scott model.
425///
426/// The Gray-Scott model produces Turing patterns by simulating two chemical
427/// species U and V with different diffusion rates and reaction kinetics.
428#[derive(Debug, Clone)]
429pub struct ReactionDiffusion {
430    /// Grid width
431    pub width: usize,
432    /// Grid height
433    pub height: usize,
434    /// Concentration of species U
435    pub u: Vec<f64>,
436    /// Concentration of species V
437    pub v: Vec<f64>,
438    /// Diffusion rate of U
439    pub d_u: f64,
440    /// Diffusion rate of V
441    pub d_v: f64,
442    /// Feed rate
443    pub feed: f64,
444    /// Kill rate
445    pub kill: f64,
446}
447impl ReactionDiffusion {
448    /// Create a new Gray-Scott reaction-diffusion grid.
449    ///
450    /// # Parameters
451    /// - `width`, `height`: grid dimensions
452    /// - `d_u`: diffusion rate of U (typically 0.2)
453    /// - `d_v`: diffusion rate of V (typically 0.1)
454    /// - `feed`: feed rate (controls pattern type)
455    /// - `kill`: kill rate (controls pattern type)
456    pub fn new(width: usize, height: usize, d_u: f64, d_v: f64, feed: f64, kill: f64) -> Self {
457        let size = width * height;
458        let u = vec![1.0; size];
459        let v = vec![0.0; size];
460        Self {
461            width,
462            height,
463            u,
464            v,
465            d_u,
466            d_v,
467            feed,
468            kill,
469        }
470    }
471    /// Seed with a square perturbation at the center.
472    pub fn seed_center(&mut self) {
473        let cx = self.width / 2;
474        let cy = self.height / 2;
475        let r = 5;
476        for y in cy.saturating_sub(r)..=(cy + r).min(self.height - 1) {
477            for x in cx.saturating_sub(r)..=(cx + r).min(self.width - 1) {
478                let idx = y * self.width + x;
479                self.u[idx] = 0.5;
480                self.v[idx] = 0.25;
481            }
482        }
483    }
484    fn idx(&self, x: usize, y: usize) -> usize {
485        y * self.width + x
486    }
487    fn laplacian_u(&self, x: usize, y: usize) -> f64 {
488        let i = self.idx(x, y);
489        let left = if x > 0 {
490            self.u[self.idx(x - 1, y)]
491        } else {
492            self.u[self.idx(self.width - 1, y)]
493        };
494        let right = if x + 1 < self.width {
495            self.u[self.idx(x + 1, y)]
496        } else {
497            self.u[self.idx(0, y)]
498        };
499        let up = if y > 0 {
500            self.u[self.idx(x, y - 1)]
501        } else {
502            self.u[self.idx(x, self.height - 1)]
503        };
504        let down = if y + 1 < self.height {
505            self.u[self.idx(x, y + 1)]
506        } else {
507            self.u[self.idx(x, 0)]
508        };
509        left + right + up + down - 4.0 * self.u[i]
510    }
511    fn laplacian_v(&self, x: usize, y: usize) -> f64 {
512        let i = self.idx(x, y);
513        let left = if x > 0 {
514            self.v[self.idx(x - 1, y)]
515        } else {
516            self.v[self.idx(self.width - 1, y)]
517        };
518        let right = if x + 1 < self.width {
519            self.v[self.idx(x + 1, y)]
520        } else {
521            self.v[self.idx(0, y)]
522        };
523        let up = if y > 0 {
524            self.v[self.idx(x, y - 1)]
525        } else {
526            self.v[self.idx(x, self.height - 1)]
527        };
528        let down = if y + 1 < self.height {
529            self.v[self.idx(x, y + 1)]
530        } else {
531            self.v[self.idx(x, 0)]
532        };
533        left + right + up + down - 4.0 * self.v[i]
534    }
535    /// Advance the simulation by `steps` time steps with given `dt`.
536    pub fn step(&mut self, steps: u32, dt: f64) {
537        for _ in 0..steps {
538            let mut du = vec![0.0f64; self.width * self.height];
539            let mut dv = vec![0.0f64; self.width * self.height];
540            for y in 0..self.height {
541                for x in 0..self.width {
542                    let i = self.idx(x, y);
543                    let u = self.u[i];
544                    let v = self.v[i];
545                    let uvv = u * v * v;
546                    du[i] = self.d_u * self.laplacian_u(x, y) - uvv + self.feed * (1.0 - u);
547                    dv[i] = self.d_v * self.laplacian_v(x, y) + uvv - (self.feed + self.kill) * v;
548                }
549            }
550            for i in 0..self.u.len() {
551                self.u[i] = (self.u[i] + dt * du[i]).clamp(0.0, 1.0);
552                self.v[i] = (self.v[i] + dt * dv[i]).clamp(0.0, 1.0);
553            }
554        }
555    }
556}
557/// Iterated Function System for generating fractal attractors (e.g., Barnsley fern).
558#[derive(Debug, Clone)]
559pub struct IteratedFunctionSystem {
560    /// The set of affine transformations
561    pub transforms: Vec<IfsTransform>,
562}
563impl IteratedFunctionSystem {
564    /// Create a new IFS from a set of transforms.
565    pub fn new(transforms: Vec<IfsTransform>) -> Self {
566        Self { transforms }
567    }
568    /// Create the classic Barnsley fern IFS.
569    pub fn barnsley_fern() -> Self {
570        Self::new(vec![
571            IfsTransform::new(0.0, 0.0, 0.0, 0.16, 0.0, 0.0, 0.01),
572            IfsTransform::new(0.85, 0.04, -0.04, 0.85, 0.0, 1.6, 0.85),
573            IfsTransform::new(0.2, -0.26, 0.23, 0.22, 0.0, 1.6, 0.07),
574            IfsTransform::new(-0.15, 0.28, 0.26, 0.24, 0.0, 0.44, 0.07),
575        ])
576    }
577    /// Generate `n` points of the IFS attractor using the chaos game.
578    pub fn generate(&self, n: usize, seed: u64) -> Vec<[f64; 2]> {
579        if self.transforms.is_empty() {
580            return Vec::new();
581        }
582        let total: f64 = self.transforms.iter().map(|t| t.prob).sum();
583        let mut cum = Vec::with_capacity(self.transforms.len());
584        let mut acc = 0.0;
585        for t in &self.transforms {
586            acc += t.prob / total;
587            cum.push(acc);
588        }
589        let mut x = 0.0f64;
590        let mut y = 0.0f64;
591        let mut s = seed;
592        for _ in 0..20 {
593            s = s
594                .wrapping_mul(6_364_136_223_846_793_005)
595                .wrapping_add(1_442_695_040_888_963_407);
596            let r = (s >> 11) as f64 / (1u64 << 53) as f64;
597            let idx = cum
598                .iter()
599                .position(|&c| r <= c)
600                .unwrap_or(self.transforms.len() - 1);
601            let (nx, ny) = self.transforms[idx].apply(x, y);
602            x = nx;
603            y = ny;
604        }
605        let mut points = Vec::with_capacity(n);
606        for _ in 0..n {
607            s = s
608                .wrapping_mul(6_364_136_223_846_793_005)
609                .wrapping_add(1_442_695_040_888_963_407);
610            let r = (s >> 11) as f64 / (1u64 << 53) as f64;
611            let idx = cum
612                .iter()
613                .position(|&c| r <= c)
614                .unwrap_or(self.transforms.len() - 1);
615            let (nx, ny) = self.transforms[idx].apply(x, y);
616            x = nx;
617            y = ny;
618            points.push([x, y]);
619        }
620        points
621    }
622}
623/// Aggregates noise generation methods.
624#[derive(Debug, Clone)]
625pub struct NoiseGeometry;
626impl NoiseGeometry {
627    /// Create a 2D Perlin noise instance.
628    pub fn perlin2d(seed: u64) -> PerlinNoise2d {
629        PerlinNoise2d::new(seed)
630    }
631    /// Create a 3D Perlin noise instance.
632    pub fn perlin3d(seed: u64) -> PerlinNoise3d {
633        PerlinNoise3d::new(seed)
634    }
635    /// Create a Worley noise instance.
636    pub fn worley(num_points: usize, seed: u64) -> WorleyNoise {
637        WorleyNoise::new(num_points, seed)
638    }
639    /// Generate a 2D heightmap using fBm noise.
640    ///
641    /// Returns a grid of `width × height` values in approximately \[-1, 1\].
642    pub fn heightmap_fbm(
643        width: usize,
644        height: usize,
645        scale: f64,
646        octaves: u32,
647        seed: u64,
648    ) -> Vec<Vec<f64>> {
649        let noise = PerlinNoise2d::new(seed);
650        (0..height)
651            .map(|row| {
652                (0..width)
653                    .map(|col| {
654                        let x = col as f64 / width as f64 * scale;
655                        let y = row as f64 / height as f64 * scale;
656                        noise.fbm(x, y, octaves, 2.0, 0.5)
657                    })
658                    .collect()
659            })
660            .collect()
661    }
662}
663/// Represents the current state of an L-system after string rewriting.
664#[derive(Debug, Clone)]
665pub struct LSystemState {
666    /// The current L-system string
667    pub string: String,
668    /// Current generation number
669    pub generation: u32,
670}
671/// Space colonization algorithm for tree and vascular structure generation.
672#[derive(Debug, Clone)]
673pub struct SpaceColonization {
674    /// Attractor points defining the target space
675    pub attractors: Vec<[f64; 3]>,
676    /// Current tree nodes
677    pub nodes: Vec<ColonizationNode>,
678    /// Segment length for each growth step
679    pub segment_length: f64,
680    /// Influence radius of attractors
681    pub influence_radius: f64,
682    /// Kill radius (attractor removed when node is within this distance)
683    pub kill_radius: f64,
684}
685impl SpaceColonization {
686    /// Create a new space colonization system.
687    pub fn new(
688        attractors: Vec<[f64; 3]>,
689        root: [f64; 3],
690        segment_length: f64,
691        influence_radius: f64,
692        kill_radius: f64,
693    ) -> Self {
694        let root_node = ColonizationNode {
695            position: root,
696            parent: None,
697            direction: [0.0, 1.0, 0.0],
698            num_attractors: 0,
699        };
700        Self {
701            attractors,
702            nodes: vec![root_node],
703            segment_length,
704            influence_radius,
705            kill_radius,
706        }
707    }
708    fn dist3(a: &[f64; 3], b: &[f64; 3]) -> f64 {
709        let dx = a[0] - b[0];
710        let dy = a[1] - b[1];
711        let dz = a[2] - b[2];
712        (dx * dx + dy * dy + dz * dz).sqrt()
713    }
714    fn normalize3(v: [f64; 3]) -> [f64; 3] {
715        let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
716        if len < 1e-12 {
717            [0.0, 1.0, 0.0]
718        } else {
719            [v[0] / len, v[1] / len, v[2] / len]
720        }
721    }
722    /// Perform one growth step.
723    ///
724    /// Returns the number of new nodes added.
725    pub fn grow(&mut self) -> usize {
726        if self.attractors.is_empty() {
727            return 0;
728        }
729        let n = self.nodes.len();
730        let mut directions: Vec<[f64; 3]> = vec![[0.0, 0.0, 0.0]; n];
731        let mut influenced: Vec<bool> = vec![false; n];
732        for attr in &self.attractors {
733            let mut closest_idx = None;
734            let mut closest_dist = self.influence_radius;
735            for (i, node) in self.nodes.iter().enumerate() {
736                let d = Self::dist3(attr, &node.position);
737                if d < closest_dist {
738                    closest_dist = d;
739                    closest_idx = Some(i);
740                }
741            }
742            if let Some(idx) = closest_idx {
743                let node_pos = self.nodes[idx].position;
744                let dir = [
745                    attr[0] - node_pos[0],
746                    attr[1] - node_pos[1],
747                    attr[2] - node_pos[2],
748                ];
749                let norm = Self::normalize3(dir);
750                directions[idx][0] += norm[0];
751                directions[idx][1] += norm[1];
752                directions[idx][2] += norm[2];
753                influenced[idx] = true;
754            }
755        }
756        let mut new_nodes = Vec::new();
757        for (i, &inf) in influenced.iter().enumerate() {
758            if inf {
759                let dir = Self::normalize3(directions[i]);
760                let pos = self.nodes[i].position;
761                let new_pos = [
762                    pos[0] + dir[0] * self.segment_length,
763                    pos[1] + dir[1] * self.segment_length,
764                    pos[2] + dir[2] * self.segment_length,
765                ];
766                new_nodes.push(ColonizationNode {
767                    position: new_pos,
768                    parent: Some(i),
769                    direction: dir,
770                    num_attractors: 0,
771                });
772            }
773        }
774        let added = new_nodes.len();
775        self.nodes.extend(new_nodes);
776        let kill_radius = self.kill_radius;
777        let nodes = &self.nodes;
778        self.attractors.retain(|attr| {
779            nodes
780                .iter()
781                .all(|node| Self::dist3(attr, &node.position) > kill_radius)
782        });
783        added
784    }
785    /// Grow until no more attractors remain or max_steps reached.
786    pub fn grow_until_done(&mut self, max_steps: u32) {
787        for _ in 0..max_steps {
788            if self.attractors.is_empty() {
789                break;
790            }
791            self.grow();
792        }
793    }
794}
795/// A 2D Voronoi tessellation.
796#[derive(Debug, Clone)]
797pub struct VoronoiTessellation2d {
798    /// The seed points
799    pub seeds: Vec<[f64; 2]>,
800    /// Domain bounds \[x_min, x_max, y_min, y_max\]
801    pub bounds: [f64; 4],
802}
803impl VoronoiTessellation2d {
804    /// Create a new Voronoi tessellation over the given domain.
805    pub fn new(seeds: Vec<[f64; 2]>, bounds: [f64; 4]) -> Self {
806        Self { seeds, bounds }
807    }
808    /// Find the index of the nearest seed to point (x, y).
809    pub fn nearest_seed(&self, x: f64, y: f64) -> usize {
810        self.seeds
811            .iter()
812            .enumerate()
813            .map(|(i, &[sx, sy])| (i, (x - sx).powi(2) + (y - sy).powi(2)))
814            .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
815            .map(|(i, _)| i)
816            .unwrap_or(0)
817    }
818    /// Compute the approximate area of each Voronoi cell by sampling the domain.
819    ///
820    /// Uses a raster scan with `resolution × resolution` sample points.
821    pub fn cell_areas(&self, resolution: usize) -> Vec<f64> {
822        let [x_min, x_max, y_min, y_max] = self.bounds;
823        let total_area = (x_max - x_min) * (y_max - y_min);
824        let n = self.seeds.len();
825        let mut counts = vec![0usize; n];
826        let total_samples = resolution * resolution;
827        for row in 0..resolution {
828            for col in 0..resolution {
829                let x = x_min + (col as f64 + 0.5) / resolution as f64 * (x_max - x_min);
830                let y = y_min + (row as f64 + 0.5) / resolution as f64 * (y_max - y_min);
831                let idx = self.nearest_seed(x, y);
832                counts[idx] += 1;
833            }
834        }
835        counts
836            .iter()
837            .map(|&c| c as f64 / total_samples as f64 * total_area)
838            .collect()
839    }
840    /// Find neighboring cells (cells that share an approximate boundary).
841    pub fn neighbors(&self, resolution: usize) -> Vec<Vec<usize>> {
842        let [x_min, x_max, y_min, y_max] = self.bounds;
843        let n = self.seeds.len();
844        let mut neighbor_sets: Vec<std::collections::HashSet<usize>> =
845            (0..n).map(|_| std::collections::HashSet::new()).collect();
846        for row in 0..resolution {
847            for col in 0..resolution {
848                let x = x_min + (col as f64 + 0.5) / resolution as f64 * (x_max - x_min);
849                let y = y_min + (row as f64 + 0.5) / resolution as f64 * (y_max - y_min);
850                let idx = self.nearest_seed(x, y);
851                for (dr, dc) in [(-1i32, 0), (1, 0), (0, -1i32), (0, 1)] {
852                    let nr = row as i32 + dr;
853                    let nc = col as i32 + dc;
854                    if nr >= 0 && nr < resolution as i32 && nc >= 0 && nc < resolution as i32 {
855                        let nx = x_min + (nc as f64 + 0.5) / resolution as f64 * (x_max - x_min);
856                        let ny = y_min + (nr as f64 + 0.5) / resolution as f64 * (y_max - y_min);
857                        let nidx = self.nearest_seed(nx, ny);
858                        if nidx != idx {
859                            neighbor_sets[idx].insert(nidx);
860                            neighbor_sets[nidx].insert(idx);
861                        }
862                    }
863                }
864            }
865        }
866        neighbor_sets
867            .into_iter()
868            .map(|s| s.into_iter().collect())
869            .collect()
870    }
871    /// Generate random seed points within the domain using LCG.
872    pub fn random_seeds(n: usize, bounds: [f64; 4], seed: u64) -> Self {
873        let [x_min, x_max, y_min, y_max] = bounds;
874        let mut s = seed;
875        let mut seeds = Vec::with_capacity(n);
876        for _ in 0..n {
877            s = s
878                .wrapping_mul(6_364_136_223_846_793_005)
879                .wrapping_add(1_442_695_040_888_963_407);
880            let x = x_min + (s >> 11) as f64 / (1u64 << 53) as f64 * (x_max - x_min);
881            s = s
882                .wrapping_mul(6_364_136_223_846_793_005)
883                .wrapping_add(1_442_695_040_888_963_407);
884            let y = y_min + (s >> 11) as f64 / (1u64 << 53) as f64 * (y_max - y_min);
885            seeds.push([x, y]);
886        }
887        Self::new(seeds, bounds)
888    }
889}
890/// Fractal geometry analysis: box-counting dimension and Hausdorff dimension estimation.
891#[derive(Debug, Clone)]
892pub struct FractalGeometry {
893    /// Set of 2D points forming the fractal set
894    pub points: Vec<[f64; 2]>,
895}
896impl FractalGeometry {
897    /// Create a new FractalGeometry from a set of 2D points.
898    pub fn new(points: Vec<[f64; 2]>) -> Self {
899        Self { points }
900    }
901    /// Estimate fractal dimension via box-counting method.
902    ///
903    /// Covers the point set with boxes of decreasing size and measures
904    /// the scaling of the count N(ε) with box size ε. The slope of
905    /// log(N) vs log(1/ε) gives the box-counting dimension.
906    pub fn box_counting_dimension(&self, num_scales: usize) -> f64 {
907        if self.points.is_empty() || num_scales < 2 {
908            return 0.0;
909        }
910        let (mut x_min, mut x_max) = (f64::MAX, f64::MIN);
911        let (mut y_min, mut y_max) = (f64::MAX, f64::MIN);
912        for &[x, y] in &self.points {
913            x_min = x_min.min(x);
914            x_max = x_max.max(x);
915            y_min = y_min.min(y);
916            y_max = y_max.max(y);
917        }
918        let extent = (x_max - x_min).max(y_max - y_min).max(1e-10);
919        let mut log_inv_eps = Vec::with_capacity(num_scales);
920        let mut log_count = Vec::with_capacity(num_scales);
921        for k in 1..=num_scales {
922            let eps = extent / (2_f64.powi(k as i32));
923            if eps < 1e-12 {
924                break;
925            }
926            let inv_eps = 1.0 / eps;
927            let mut occupied: std::collections::HashSet<(i64, i64)> =
928                std::collections::HashSet::new();
929            for &[x, y] in &self.points {
930                let ix = ((x - x_min) / eps).floor() as i64;
931                let iy = ((y - y_min) / eps).floor() as i64;
932                occupied.insert((ix, iy));
933            }
934            let n = occupied.len() as f64;
935            if n > 0.0 {
936                log_inv_eps.push(inv_eps.ln());
937                log_count.push(n.ln());
938            }
939        }
940        if log_inv_eps.len() < 2 {
941            return 0.0;
942        }
943        let n = log_inv_eps.len() as f64;
944        let sum_x: f64 = log_inv_eps.iter().sum();
945        let sum_y: f64 = log_count.iter().sum();
946        let sum_xy: f64 = log_inv_eps.iter().zip(&log_count).map(|(x, y)| x * y).sum();
947        let sum_x2: f64 = log_inv_eps.iter().map(|x| x * x).sum();
948        let denom = n * sum_x2 - sum_x * sum_x;
949        if denom.abs() < 1e-12 {
950            return 0.0;
951        }
952        (n * sum_xy - sum_x * sum_y) / denom
953    }
954    /// Estimate Hausdorff dimension (approximated via box-counting on fine scale).
955    ///
956    /// For self-similar fractals, the Hausdorff dimension equals the box-counting
957    /// dimension. This method uses more scales for a more accurate estimate.
958    pub fn hausdorff_dimension(&self) -> f64 {
959        self.box_counting_dimension(12)
960    }
961    /// Generate Koch snowflake points up to a given iteration depth.
962    pub fn koch_snowflake(iterations: u32, num_points_per_segment: usize) -> Self {
963        let mut segments: Vec<([f64; 2], [f64; 2])> = vec![
964            ([0.0, 0.0], [1.0, 0.0]),
965            ([1.0, 0.0], [0.5, 0.866_025_4]),
966            ([0.5, 0.866_025_4], [0.0, 0.0]),
967        ];
968        for _ in 0..iterations {
969            let mut new_segments = Vec::new();
970            for (a, b) in &segments {
971                let p1 = *a;
972                let p2 = [a[0] + (b[0] - a[0]) / 3.0, a[1] + (b[1] - a[1]) / 3.0];
973                let p4 = [
974                    a[0] + 2.0 * (b[0] - a[0]) / 3.0,
975                    a[1] + 2.0 * (b[1] - a[1]) / 3.0,
976                ];
977                let p5 = *b;
978                let mx = (p2[0] + p4[0]) / 2.0;
979                let my = (p2[1] + p4[1]) / 2.0;
980                let dx = p4[0] - p2[0];
981                let dy = p4[1] - p2[1];
982                let p3 = [mx - dy * 3_f64.sqrt() / 2.0, my + dx * 3_f64.sqrt() / 2.0];
983                new_segments.push((p1, p2));
984                new_segments.push((p2, p3));
985                new_segments.push((p3, p4));
986                new_segments.push((p4, p5));
987            }
988            segments = new_segments;
989        }
990        let n = num_points_per_segment.max(2);
991        let mut points = Vec::new();
992        for (a, b) in &segments {
993            for i in 0..n {
994                let t = i as f64 / (n - 1) as f64;
995                points.push([a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1])]);
996            }
997        }
998        Self { points }
999    }
1000    /// Generate Sierpinski triangle points up to a given iteration depth.
1001    pub fn sierpinski_triangle(iterations: u32) -> Self {
1002        let mut triangles: Vec<([f64; 2], [f64; 2], [f64; 2])> =
1003            vec![([0.0, 0.0], [1.0, 0.0], [0.5, 0.866_025_4])];
1004        for _ in 0..iterations {
1005            let mut new_triangles = Vec::new();
1006            for (a, b, c) in &triangles {
1007                let ab = [(a[0] + b[0]) / 2.0, (a[1] + b[1]) / 2.0];
1008                let bc = [(b[0] + c[0]) / 2.0, (b[1] + c[1]) / 2.0];
1009                let ca = [(c[0] + a[0]) / 2.0, (c[1] + a[1]) / 2.0];
1010                new_triangles.push((*a, ab, ca));
1011                new_triangles.push((ab, *b, bc));
1012                new_triangles.push((ca, bc, *c));
1013            }
1014            triangles = new_triangles;
1015        }
1016        let mut points = Vec::new();
1017        for (a, b, c) in &triangles {
1018            points.push(*a);
1019            points.push(*b);
1020            points.push(*c);
1021        }
1022        Self { points }
1023    }
1024}
1025/// A single production rule in an L-system: maps a predecessor character to a successor string.
1026#[derive(Debug, Clone)]
1027pub struct LSystemRule {
1028    /// The predecessor symbol
1029    pub predecessor: char,
1030    /// The successor string
1031    pub successor: String,
1032}
1033impl LSystemRule {
1034    /// Create a new L-system production rule.
1035    pub fn new(predecessor: char, successor: &str) -> Self {
1036        Self {
1037            predecessor,
1038            successor: successor.to_string(),
1039        }
1040    }
1041}
1042/// A triangle in the Delaunay triangulation.
1043#[derive(Debug, Clone, Copy)]
1044pub struct DelaunayTri {
1045    /// Indices into the point array
1046    pub indices: [usize; 3],
1047}
1048impl DelaunayTri {
1049    /// Create a new Delaunay triangle.
1050    pub fn new(a: usize, b: usize, c: usize) -> Self {
1051        Self { indices: [a, b, c] }
1052    }
1053    /// Compute the circumcircle center and radius squared of this triangle.
1054    pub fn circumcircle(&self, points: &[[f64; 2]]) -> ([f64; 2], f64) {
1055        let [ax, ay] = points[self.indices[0]];
1056        let [bx, by] = points[self.indices[1]];
1057        let [cx, cy] = points[self.indices[2]];
1058        let d = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
1059        if d.abs() < 1e-12 {
1060            return ([0.0, 0.0], f64::MAX);
1061        }
1062        let ux = ((ax * ax + ay * ay) * (by - cy)
1063            + (bx * bx + by * by) * (cy - ay)
1064            + (cx * cx + cy * cy) * (ay - by))
1065            / d;
1066        let uy = ((ax * ax + ay * ay) * (cx - bx)
1067            + (bx * bx + by * by) * (ax - cx)
1068            + (cx * cx + cy * cy) * (bx - ax))
1069            / d;
1070        let r2 = (ax - ux).powi(2) + (ay - uy).powi(2);
1071        ([ux, uy], r2)
1072    }
1073    /// Check whether point `p` lies inside the circumcircle of this triangle.
1074    pub fn in_circumcircle(&self, points: &[[f64; 2]], p: [f64; 2]) -> bool {
1075        let ([cx, cy], r2) = self.circumcircle(points);
1076        (p[0] - cx).powi(2) + (p[1] - cy).powi(2) < r2 - 1e-10
1077    }
1078}
1079/// Delaunay triangulation using the Bowyer-Watson algorithm.
1080#[derive(Debug, Clone)]
1081pub struct DelaunayTriangulation2d {
1082    /// Input points
1083    pub points: Vec<[f64; 2]>,
1084    /// Output triangles
1085    pub triangles: Vec<DelaunayTri>,
1086}
1087impl DelaunayTriangulation2d {
1088    /// Compute the Delaunay triangulation of the given points using Bowyer-Watson.
1089    pub fn new(points: Vec<[f64; 2]>) -> Self {
1090        let mut tris = Self::bowyer_watson(&points);
1091        let n = points.len();
1092        tris.retain(|t| t.indices.iter().all(|&i| i < n));
1093        Self {
1094            points,
1095            triangles: tris,
1096        }
1097    }
1098    fn bowyer_watson(points: &[[f64; 2]]) -> Vec<DelaunayTri> {
1099        if points.is_empty() {
1100            return Vec::new();
1101        }
1102        let (mut x_min, mut x_max) = (f64::MAX, f64::MIN);
1103        let (mut y_min, mut y_max) = (f64::MAX, f64::MIN);
1104        for &[x, y] in points {
1105            x_min = x_min.min(x);
1106            x_max = x_max.max(x);
1107            y_min = y_min.min(y);
1108            y_max = y_max.max(y);
1109        }
1110        let dx = x_max - x_min;
1111        let dy = y_max - y_min;
1112        let delta_max = dx.max(dy) * 10.0;
1113        let mid_x = (x_min + x_max) / 2.0;
1114        let mid_y = (y_min + y_max) / 2.0;
1115        let n = points.len();
1116        let mut all_points = points.to_vec();
1117        all_points.push([mid_x - 20.0 * delta_max, mid_y - delta_max]);
1118        all_points.push([mid_x, mid_y + 20.0 * delta_max]);
1119        all_points.push([mid_x + 20.0 * delta_max, mid_y - delta_max]);
1120        let mut triangles = vec![DelaunayTri::new(n, n + 1, n + 2)];
1121        for (pi, &point) in points.iter().enumerate() {
1122            let bad: Vec<usize> = triangles
1123                .iter()
1124                .enumerate()
1125                .filter(|(_, t)| t.in_circumcircle(&all_points, point))
1126                .map(|(i, _)| i)
1127                .collect();
1128            let mut boundary: Vec<(usize, usize)> = Vec::new();
1129            for &bi in &bad {
1130                let t = triangles[bi];
1131                let edges = [
1132                    (t.indices[0], t.indices[1]),
1133                    (t.indices[1], t.indices[2]),
1134                    (t.indices[2], t.indices[0]),
1135                ];
1136                for (e0, e1) in edges {
1137                    let shared = bad.iter().filter(|&&bj| bj != bi).any(|&bj| {
1138                        let other = triangles[bj];
1139                        let other_edges = [
1140                            (other.indices[0], other.indices[1]),
1141                            (other.indices[1], other.indices[2]),
1142                            (other.indices[2], other.indices[0]),
1143                        ];
1144                        other_edges
1145                            .iter()
1146                            .any(|&(a, b)| (a == e0 && b == e1) || (a == e1 && b == e0))
1147                    });
1148                    if !shared {
1149                        boundary.push((e0, e1));
1150                    }
1151                }
1152            }
1153            let mut keep = vec![true; triangles.len()];
1154            for &bi in &bad {
1155                keep[bi] = false;
1156            }
1157            triangles = triangles
1158                .into_iter()
1159                .zip(keep)
1160                .filter(|(_, k)| *k)
1161                .map(|(t, _)| t)
1162                .collect();
1163            for (e0, e1) in boundary {
1164                triangles.push(DelaunayTri::new(e0, e1, pi));
1165            }
1166        }
1167        triangles
1168    }
1169    /// Verify the Delaunay property: no point lies inside the circumcircle of any triangle.
1170    pub fn verify_delaunay(&self) -> bool {
1171        for tri in &self.triangles {
1172            for (i, &p) in self.points.iter().enumerate() {
1173                if tri.indices.contains(&i) {
1174                    continue;
1175                }
1176                if tri.in_circumcircle(&self.points, p) {
1177                    return false;
1178                }
1179            }
1180        }
1181        true
1182    }
1183}
1184/// Parametric curve types: Bézier, B-spline, NURBS.
1185#[derive(Debug, Clone)]
1186pub struct CurveParametric {
1187    /// Control points in 3D
1188    pub control_points: Vec<[f64; 3]>,
1189}
1190impl CurveParametric {
1191    /// Create a new parametric curve from control points.
1192    pub fn new(control_points: Vec<[f64; 3]>) -> Self {
1193        Self { control_points }
1194    }
1195    /// Evaluate a Bézier curve at parameter t ∈ \[0, 1\] using de Casteljau algorithm.
1196    ///
1197    /// Works for any degree (number of control points - 1).
1198    pub fn bezier_de_casteljau(&self, t: f64) -> [f64; 3] {
1199        let n = self.control_points.len();
1200        if n == 0 {
1201            return [0.0, 0.0, 0.0];
1202        }
1203        let mut pts = self.control_points.clone();
1204        for r in 1..n {
1205            for i in 0..(n - r) {
1206                pts[i] = [
1207                    (1.0 - t) * pts[i][0] + t * pts[i + 1][0],
1208                    (1.0 - t) * pts[i][1] + t * pts[i + 1][1],
1209                    (1.0 - t) * pts[i][2] + t * pts[i + 1][2],
1210                ];
1211            }
1212        }
1213        pts[0]
1214    }
1215    /// Evaluate a cubic Bézier curve using the Bernstein polynomial formula.
1216    ///
1217    /// Requires exactly 4 control points. Falls back to de Casteljau for other counts.
1218    pub fn bezier_bernstein(&self, t: f64) -> [f64; 3] {
1219        if self.control_points.len() != 4 {
1220            return self.bezier_de_casteljau(t);
1221        }
1222        let [p0, p1, p2, p3] = [
1223            self.control_points[0],
1224            self.control_points[1],
1225            self.control_points[2],
1226            self.control_points[3],
1227        ];
1228        let mt = 1.0 - t;
1229        let b0 = mt * mt * mt;
1230        let b1 = 3.0 * mt * mt * t;
1231        let b2 = 3.0 * mt * t * t;
1232        let b3 = t * t * t;
1233        [
1234            b0 * p0[0] + b1 * p1[0] + b2 * p2[0] + b3 * p3[0],
1235            b0 * p0[1] + b1 * p1[1] + b2 * p2[1] + b3 * p3[1],
1236            b0 * p0[2] + b1 * p1[2] + b2 * p2[2] + b3 * p3[2],
1237        ]
1238    }
1239    /// Evaluate a B-spline at parameter t using the de Boor algorithm.
1240    ///
1241    /// `knots` must have length = `control_points.len() + degree + 1`.
1242    pub fn bspline_de_boor(&self, t: f64, degree: usize, knots: &[f64]) -> [f64; 3] {
1243        let n = self.control_points.len();
1244        if n == 0 || knots.len() < n + degree + 1 {
1245            return [0.0, 0.0, 0.0];
1246        }
1247        let t = t.clamp(knots[degree], knots[n]);
1248        let mut k = degree;
1249        for i in degree..n {
1250            if t >= knots[i] && t < knots[i + 1] {
1251                k = i;
1252                break;
1253            }
1254        }
1255        if t >= knots[n] {
1256            k = n - 1;
1257        }
1258        let mut d: Vec<[f64; 3]> = (0..=degree)
1259            .map(|j| self.control_points[k - degree + j])
1260            .collect();
1261        for r in 1..=degree {
1262            for j in (r..=degree).rev() {
1263                let i = k - degree + j;
1264                let denom = knots[i + degree - r + 1] - knots[i];
1265                let alpha = if denom.abs() < 1e-12 {
1266                    0.0
1267                } else {
1268                    (t - knots[i]) / denom
1269                };
1270                d[j] = [
1271                    (1.0 - alpha) * d[j - 1][0] + alpha * d[j][0],
1272                    (1.0 - alpha) * d[j - 1][1] + alpha * d[j][1],
1273                    (1.0 - alpha) * d[j - 1][2] + alpha * d[j][2],
1274                ];
1275            }
1276        }
1277        d[degree]
1278    }
1279    /// Evaluate a NURBS curve at parameter t.
1280    ///
1281    /// `weights` must have the same length as `control_points`.
1282    pub fn nurbs(&self, t: f64, degree: usize, knots: &[f64], weights: &[f64]) -> [f64; 3] {
1283        let n = self.control_points.len();
1284        if n == 0 || weights.len() != n {
1285            return [0.0, 0.0, 0.0];
1286        }
1287        let homo_xyz: Vec<[f64; 3]> = self
1288            .control_points
1289            .iter()
1290            .zip(weights)
1291            .map(|(&[x, y, z], &w)| [x * w, y * w, z * w])
1292            .collect();
1293        let w_pts: Vec<[f64; 3]> = weights.iter().map(|&w| [w, 0.0, 0.0]).collect();
1294        let homo_curve = CurveParametric::new(homo_xyz);
1295        let w_curve = CurveParametric::new(w_pts);
1296        let p = homo_curve.bspline_de_boor(t, degree, knots);
1297        let w = w_curve.bspline_de_boor(t, degree, knots)[0];
1298        if w.abs() < 1e-12 {
1299            return [0.0, 0.0, 0.0];
1300        }
1301        [p[0] / w, p[1] / w, p[2] / w]
1302    }
1303    /// Sample the Bézier curve at `n` uniformly spaced parameter values.
1304    pub fn sample_bezier(&self, n: usize) -> Vec<[f64; 3]> {
1305        (0..n)
1306            .map(|i| {
1307                let t = i as f64 / (n - 1).max(1) as f64;
1308                self.bezier_de_casteljau(t)
1309            })
1310            .collect()
1311    }
1312    /// Compute the approximate arc length of the Bézier curve by sampling.
1313    pub fn bezier_arc_length(&self, samples: usize) -> f64 {
1314        let pts = self.sample_bezier(samples);
1315        pts.windows(2)
1316            .map(|w| {
1317                let [ax, ay, az] = w[0];
1318                let [bx, by, bz] = w[1];
1319                ((ax - bx).powi(2) + (ay - by).powi(2) + (az - bz).powi(2)).sqrt()
1320            })
1321            .sum()
1322    }
1323}
1324/// 3D Perlin noise generator.
1325#[derive(Debug, Clone)]
1326pub struct PerlinNoise3d {
1327    pub(super) perm: Vec<u8>,
1328}
1329impl PerlinNoise3d {
1330    /// Create a new 3D Perlin noise generator.
1331    pub fn new(seed: u64) -> Self {
1332        let mut perm = (0u8..=255).collect::<Vec<_>>();
1333        let mut s = seed;
1334        for i in (1..256).rev() {
1335            s = s
1336                .wrapping_mul(6_364_136_223_846_793_005)
1337                .wrapping_add(1_442_695_040_888_963_407);
1338            let j = (s >> 33) as usize % (i + 1);
1339            perm.swap(i, j);
1340        }
1341        let mut full = perm.clone();
1342        full.extend_from_slice(&perm);
1343        Self { perm: full }
1344    }
1345    fn fade(t: f64) -> f64 {
1346        t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
1347    }
1348    fn lerp(a: f64, b: f64, t: f64) -> f64 {
1349        a + t * (b - a)
1350    }
1351    fn grad3(hash: u8, x: f64, y: f64, z: f64) -> f64 {
1352        match hash & 15 {
1353            0 => x + y,
1354            1 => -x + y,
1355            2 => x - y,
1356            3 => -x - y,
1357            4 => x + z,
1358            5 => -x + z,
1359            6 => x - z,
1360            7 => -x - z,
1361            8 => y + z,
1362            9 => -y + z,
1363            10 => y - z,
1364            11 => -y - z,
1365            12 => y + x,
1366            13 => -y + z,
1367            14 => y - x,
1368            _ => -y - z,
1369        }
1370    }
1371    fn p(&self, i: usize) -> u8 {
1372        self.perm[i & 511]
1373    }
1374    /// Evaluate 3D Perlin noise at (x, y, z). Returns value in approximately \[-1, 1\].
1375    pub fn noise(&self, x: f64, y: f64, z: f64) -> f64 {
1376        let xi = x.floor() as i32 & 255;
1377        let yi = y.floor() as i32 & 255;
1378        let zi = z.floor() as i32 & 255;
1379        let xf = x - x.floor();
1380        let yf = y - y.floor();
1381        let zf = z - z.floor();
1382        let u = Self::fade(xf);
1383        let v = Self::fade(yf);
1384        let w = Self::fade(zf);
1385        let a = self.p(xi as usize).wrapping_add(yi as u8);
1386        let aa = self.p(a as usize).wrapping_add(zi as u8);
1387        let ab = self.p((a.wrapping_add(1)) as usize).wrapping_add(zi as u8);
1388        let b = self.p((xi + 1) as usize).wrapping_add(yi as u8);
1389        let ba = self.p(b as usize).wrapping_add(zi as u8);
1390        let bb = self.p((b.wrapping_add(1)) as usize).wrapping_add(zi as u8);
1391        Self::lerp(
1392            Self::lerp(
1393                Self::lerp(
1394                    Self::grad3(self.p(aa as usize), xf, yf, zf),
1395                    Self::grad3(self.p(ba as usize), xf - 1.0, yf, zf),
1396                    u,
1397                ),
1398                Self::lerp(
1399                    Self::grad3(self.p(ab as usize), xf, yf - 1.0, zf),
1400                    Self::grad3(self.p(bb as usize), xf - 1.0, yf - 1.0, zf),
1401                    u,
1402                ),
1403                v,
1404            ),
1405            Self::lerp(
1406                Self::lerp(
1407                    Self::grad3(self.p((aa.wrapping_add(1)) as usize), xf, yf, zf - 1.0),
1408                    Self::grad3(
1409                        self.p((ba.wrapping_add(1)) as usize),
1410                        xf - 1.0,
1411                        yf,
1412                        zf - 1.0,
1413                    ),
1414                    u,
1415                ),
1416                Self::lerp(
1417                    Self::grad3(
1418                        self.p((ab.wrapping_add(1)) as usize),
1419                        xf,
1420                        yf - 1.0,
1421                        zf - 1.0,
1422                    ),
1423                    Self::grad3(
1424                        self.p((bb.wrapping_add(1)) as usize),
1425                        xf - 1.0,
1426                        yf - 1.0,
1427                        zf - 1.0,
1428                    ),
1429                    u,
1430                ),
1431                v,
1432            ),
1433            w,
1434        )
1435    }
1436    /// 3D Fractional Brownian Motion.
1437    pub fn fbm(&self, x: f64, y: f64, z: f64, octaves: u32, lacunarity: f64, gain: f64) -> f64 {
1438        let mut value = 0.0;
1439        let mut amplitude = 1.0;
1440        let mut frequency = 1.0;
1441        let mut max_val = 0.0;
1442        for _ in 0..octaves {
1443            value += amplitude * self.noise(x * frequency, y * frequency, z * frequency);
1444            max_val += amplitude;
1445            amplitude *= gain;
1446            frequency *= lacunarity;
1447        }
1448        if max_val > 0.0 { value / max_val } else { 0.0 }
1449    }
1450}
1451/// A node in a space colonization tree.
1452#[derive(Debug, Clone)]
1453pub struct ColonizationNode {
1454    /// 3D position of the node
1455    pub position: [f64; 3],
1456    /// Index of the parent node (None for root)
1457    pub parent: Option<usize>,
1458    /// Accumulated growth direction
1459    pub direction: [f64; 3],
1460    /// Number of attractors influencing this node
1461    pub num_attractors: usize,
1462}