proof_engine/fractal/
flame.rs1use glam::Vec2;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum FlameVariation {
7 Linear, Sinusoidal, Spherical, Swirl, Horseshoe, Polar, Handkerchief,
8 Heart, Disc, Spiral, Hyperbolic, Diamond, Ex, Julia, Bent, Fisheye,
9}
10
11impl FlameVariation {
12 pub fn apply(&self, p: Vec2) -> Vec2 {
13 let (x, y) = (p.x, p.y);
14 let r = p.length().max(1e-10);
15 let theta = y.atan2(x);
16 match self {
17 Self::Linear => p,
18 Self::Sinusoidal => Vec2::new(x.sin(), y.sin()),
19 Self::Spherical => p / (r * r),
20 Self::Swirl => { let r2 = r * r; Vec2::new(x * r2.sin() - y * r2.cos(), x * r2.cos() + y * r2.sin()) }
21 Self::Horseshoe => Vec2::new((x - y) * (x + y) / r, 2.0 * x * y / r),
22 Self::Polar => Vec2::new(theta / std::f32::consts::PI, r - 1.0),
23 Self::Handkerchief => Vec2::new(r * (theta + r).sin(), r * (theta - r).cos()),
24 Self::Heart => Vec2::new(r * (theta * r).sin(), -r * (theta * r).cos()),
25 Self::Disc => { let tr = theta / std::f32::consts::PI; Vec2::new(tr * (std::f32::consts::PI * r).sin(), tr * (std::f32::consts::PI * r).cos()) }
26 Self::Spiral => Vec2::new((theta.cos() + r.sin()) / r, (theta.sin() - r.cos()) / r),
27 Self::Hyperbolic => Vec2::new(theta.sin() / r, r * theta.cos()),
28 Self::Diamond => Vec2::new(theta.sin() * r.cos(), theta.cos() * r.sin()),
29 Self::Ex => { let p0 = (theta + r).sin().powi(3); let p1 = (theta - r).cos().powi(3); Vec2::new(r * (p0 + p1), r * (p0 - p1)) }
30 Self::Julia => { let sr = r.sqrt(); let omega = if (theta * 1000.0) as i32 % 2 == 0 { 0.0 } else { std::f32::consts::PI }; Vec2::new(sr * (theta / 2.0 + omega).cos(), sr * (theta / 2.0 + omega).sin()) }
31 Self::Bent => Vec2::new(if x >= 0.0 { x } else { 2.0 * x }, if y >= 0.0 { y } else { y / 2.0 }),
32 Self::Fisheye => { let rr = 2.0 / (r + 1.0); Vec2::new(rr * y, rr * x) }
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
38pub struct FlameParams {
39 pub variations: Vec<(FlameVariation, f32)>,
40 pub color_speed: f32,
41 pub iterations: u32,
42 pub supersample: u32,
43}
44
45impl Default for FlameParams {
46 fn default() -> Self {
47 Self { variations: vec![(FlameVariation::Linear, 0.5), (FlameVariation::Sinusoidal, 0.5)], color_speed: 0.5, iterations: 100000, supersample: 1 }
48 }
49}
50
51pub fn render_flame(params: &FlameParams, seed: u64) -> Vec<(Vec2, f32)> {
53 let mut points = Vec::with_capacity(params.iterations as usize);
54 let mut p = Vec2::new(0.1, 0.1);
55 let mut color = 0.5f32;
56 let mut rng = seed;
57
58 for i in 0..params.iterations + 20 {
59 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
61 let total_w: f32 = params.variations.iter().map(|(_, w)| w).sum();
62 let r = (rng >> 33) as f32 / (u32::MAX >> 1) as f32 * total_w;
63 let mut cum = 0.0;
64 let mut selected = ¶ms.variations[0];
65 for v in ¶ms.variations {
66 cum += v.1;
67 if r <= cum { selected = v; break; }
68 }
69
70 p = selected.0.apply(p);
71 color = color * (1.0 - params.color_speed) + params.color_speed * (selected.0 as u8 as f32 / 16.0);
72
73 if i >= 20 { points.push((p, color)); }
74 }
75 points
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 #[test]
82 fn flame_renders() {
83 let params = FlameParams { iterations: 1000, ..Default::default() };
84 let result = render_flame(¶ms, 42);
85 assert_eq!(result.len(), 1000);
86 }
87 #[test]
88 fn variations_dont_panic() {
89 let p = Vec2::new(0.5, 0.3);
90 for v in &[FlameVariation::Linear, FlameVariation::Spherical, FlameVariation::Swirl, FlameVariation::Heart, FlameVariation::Julia] {
91 let _r = v.apply(p);
92 }
93 }
94}