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