proof_engine/fractal/
lsystem.rs1use glam::Vec3;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
7pub struct LSystemRule { pub from: char, pub to: String }
8
9#[derive(Debug, Clone)]
10pub struct LSystem {
11 pub axiom: String,
12 pub rules: Vec<LSystemRule>,
13 pub angle: f32,
14 pub step_length: f32,
15}
16
17#[derive(Debug, Clone)]
18pub struct TurtleState { pub position: Vec3, pub heading: Vec3, pub up: Vec3, pub right: Vec3 }
19
20impl Default for TurtleState {
21 fn default() -> Self {
22 Self { position: Vec3::ZERO, heading: Vec3::Y, up: Vec3::Z, right: Vec3::X }
23 }
24}
25
26impl LSystem {
27 pub fn new(axiom: &str, rules: Vec<(&str, &str)>, angle: f32, step: f32) -> Self {
28 Self {
29 axiom: axiom.to_string(),
30 rules: rules.into_iter().map(|(f, t)| LSystemRule { from: f.chars().next().unwrap(), to: t.to_string() }).collect(),
31 angle, step_length: step,
32 }
33 }
34
35 pub fn generate(&self, iterations: u32) -> String {
37 let mut current = self.axiom.clone();
38 let rule_map: HashMap<char, &str> = self.rules.iter().map(|r| (r.from, r.to.as_str())).collect();
39 for _ in 0..iterations {
40 let mut next = String::with_capacity(current.len() * 2);
41 for ch in current.chars() {
42 if let Some(&replacement) = rule_map.get(&ch) { next.push_str(replacement); }
43 else { next.push(ch); }
44 }
45 current = next;
46 }
47 current
48 }
49
50 pub fn interpret(&self, generated: &str) -> Vec<(Vec3, Vec3)> {
52 let mut segments = Vec::new();
53 let mut turtle = TurtleState::default();
54 let mut stack: Vec<TurtleState> = Vec::new();
55 let angle_rad = self.angle.to_radians();
56
57 for ch in generated.chars() {
58 match ch {
59 'F' | 'G' => {
60 let start = turtle.position;
61 turtle.position += turtle.heading * self.step_length;
62 segments.push((start, turtle.position));
63 }
64 'f' | 'g' => { turtle.position += turtle.heading * self.step_length; }
65 '+' => rotate_yaw(&mut turtle, angle_rad),
66 '-' => rotate_yaw(&mut turtle, -angle_rad),
67 '&' => rotate_pitch(&mut turtle, angle_rad),
68 '^' => rotate_pitch(&mut turtle, -angle_rad),
69 '\\' => rotate_roll(&mut turtle, angle_rad),
70 '/' => rotate_roll(&mut turtle, -angle_rad),
71 '|' => rotate_yaw(&mut turtle, std::f32::consts::PI),
72 '[' => stack.push(turtle.clone()),
73 ']' => { if let Some(s) = stack.pop() { turtle = s; } }
74 _ => {}
75 }
76 }
77 segments
78 }
79
80 pub fn koch_curve() -> Self { Self::new("F", vec![("F", "F+F-F-F+F")], 90.0, 1.0) }
83 pub fn sierpinski_triangle() -> Self { Self::new("F-G-G", vec![("F", "F-G+F+G-F"), ("G", "GG")], 120.0, 1.0) }
84 pub fn dragon_curve() -> Self { Self::new("FX", vec![("X", "X+YF+"), ("Y", "-FX-Y")], 90.0, 1.0) }
85 pub fn plant() -> Self { Self::new("X", vec![("X", "F+[[X]-X]-F[-FX]+X"), ("F", "FF")], 25.0, 1.0) }
86 pub fn hilbert_3d() -> Self { Self::new("A", vec![("A", "B-F+CFC+F-D&F^D-F+&&CFC+F+B//"), ("B", "A&F^CFB^F^D^^-F-D^|F^B|FC^F^A//"), ("C", "|D^|F^B-F+C^F^A&&FA&F^C+F+B^F^D//"), ("D", "|CFB-F+B|FA&F^A&&FB-F+B|FC//")], 90.0, 1.0) }
87}
88
89fn rotate_yaw(t: &mut TurtleState, angle: f32) {
90 let (s, c) = angle.sin_cos();
91 let new_h = t.heading * c + t.right * s;
92 let new_r = -t.heading * s + t.right * c;
93 t.heading = new_h.normalize_or_zero();
94 t.right = new_r.normalize_or_zero();
95}
96
97fn rotate_pitch(t: &mut TurtleState, angle: f32) {
98 let (s, c) = angle.sin_cos();
99 let new_h = t.heading * c + t.up * s;
100 let new_u = -t.heading * s + t.up * c;
101 t.heading = new_h.normalize_or_zero();
102 t.up = new_u.normalize_or_zero();
103}
104
105fn rotate_roll(t: &mut TurtleState, angle: f32) {
106 let (s, c) = angle.sin_cos();
107 let new_r = t.right * c + t.up * s;
108 let new_u = -t.right * s + t.up * c;
109 t.right = new_r.normalize_or_zero();
110 t.up = new_u.normalize_or_zero();
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 #[test]
117 fn koch_generates() { let s = LSystem::koch_curve().generate(2); assert!(s.len() > 10); }
118 #[test]
119 fn plant_produces_segments() {
120 let ls = LSystem::plant();
121 let gen = ls.generate(3);
122 let segs = ls.interpret(&gen);
123 assert!(!segs.is_empty());
124 }
125 #[test]
126 fn dragon_curve_grows() {
127 let ls = LSystem::dragon_curve();
128 let s1 = ls.generate(1); let s2 = ls.generate(2);
129 assert!(s2.len() > s1.len());
130 }
131}