1use glam::Vec2;
4
5#[derive(Debug, Clone)]
7pub struct AffineTx {
8 pub a: f32, pub b: f32, pub c: f32, pub d: f32, pub e: f32, pub f: f32,
9 pub probability: f32,
10 pub color_index: u8,
11}
12
13impl AffineTx {
14 pub fn apply(&self, p: Vec2) -> Vec2 {
15 Vec2::new(self.a * p.x + self.b * p.y + self.e, self.c * p.x + self.d * p.y + self.f)
16 }
17}
18
19#[derive(Debug, Clone)]
21pub struct IfsSystem {
22 pub transforms: Vec<AffineTx>,
23 pub name: String,
24}
25
26pub struct IfsFractal {
28 pub points: Vec<Vec2>,
29 pub colors: Vec<u8>,
30}
31
32impl IfsSystem {
33 pub fn render(&self, iterations: u32, seed: u64) -> IfsFractal {
35 let mut points = Vec::with_capacity(iterations as usize);
36 let mut colors = Vec::with_capacity(iterations as usize);
37 let mut p = Vec2::ZERO;
38 let mut rng = seed;
39 let total_prob: f32 = self.transforms.iter().map(|t| t.probability).sum();
40
41 for _ in 0..iterations {
42 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
43 let r = (rng >> 33) as f32 / (u32::MAX >> 1) as f32 * total_prob;
44 let mut cumulative = 0.0;
45 let mut tx_idx = 0;
46 for (i, tx) in self.transforms.iter().enumerate() {
47 cumulative += tx.probability;
48 if r <= cumulative { tx_idx = i; break; }
49 }
50 p = self.transforms[tx_idx].apply(p);
51 points.push(p);
52 colors.push(self.transforms[tx_idx].color_index);
53 }
54 IfsFractal { points, colors }
55 }
56
57 pub fn barnsley_fern() -> Self {
59 Self {
60 name: "Barnsley Fern".to_string(),
61 transforms: vec![
62 AffineTx { a: 0.0, b: 0.0, c: 0.0, d: 0.16, e: 0.0, f: 0.0, probability: 0.01, color_index: 0 },
63 AffineTx { a: 0.85, b: 0.04, c: -0.04, d: 0.85, e: 0.0, f: 1.6, probability: 0.85, color_index: 1 },
64 AffineTx { a: 0.2, b: -0.26, c: 0.23, d: 0.22, e: 0.0, f: 1.6, probability: 0.07, color_index: 2 },
65 AffineTx { a: -0.15, b: 0.28, c: 0.26, d: 0.24, e: 0.0, f: 0.44, probability: 0.07, color_index: 3 },
66 ],
67 }
68 }
69
70 pub fn sierpinski() -> Self {
72 Self {
73 name: "Sierpinski Triangle".to_string(),
74 transforms: vec![
75 AffineTx { a: 0.5, b: 0.0, c: 0.0, d: 0.5, e: 0.0, f: 0.0, probability: 1.0, color_index: 0 },
76 AffineTx { a: 0.5, b: 0.0, c: 0.0, d: 0.5, e: 0.5, f: 0.0, probability: 1.0, color_index: 1 },
77 AffineTx { a: 0.5, b: 0.0, c: 0.0, d: 0.5, e: 0.25, f: 0.5, probability: 1.0, color_index: 2 },
78 ],
79 }
80 }
81
82 pub fn sierpinski_carpet() -> Self {
84 let mut transforms = Vec::new();
85 for i in 0..3 { for j in 0..3 {
86 if i == 1 && j == 1 { continue; }
87 transforms.push(AffineTx {
88 a: 1.0/3.0, b: 0.0, c: 0.0, d: 1.0/3.0,
89 e: i as f32 / 3.0, f: j as f32 / 3.0,
90 probability: 1.0, color_index: (i * 3 + j) as u8,
91 });
92 }}
93 Self { name: "Sierpinski Carpet".to_string(), transforms }
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 #[test]
101 fn barnsley_fern_renders() {
102 let ifs = IfsSystem::barnsley_fern();
103 let result = ifs.render(10000, 42);
104 assert_eq!(result.points.len(), 10000);
105 let max_y = result.points.iter().map(|p| p.y).fold(f32::MIN, f32::max);
107 assert!(max_y > 5.0 && max_y < 15.0, "Fern y range: {max_y}");
108 }
109 #[test]
110 fn sierpinski_bounded() {
111 let ifs = IfsSystem::sierpinski();
112 let result = ifs.render(5000, 123);
113 for p in &result.points {
114 assert!(p.x >= -0.1 && p.x <= 1.1 && p.y >= -0.1 && p.y <= 1.1);
115 }
116 }
117}