proof_engine/fractal/
fractal3d.rs1use glam::Vec3;
4
5#[derive(Debug, Clone)]
6pub struct RayMarchParams {
7 pub max_steps: u32,
8 pub max_dist: f32,
9 pub min_dist: f32,
10 pub power: f32,
11}
12impl Default for RayMarchParams {
13 fn default() -> Self { Self { max_steps: 128, max_dist: 100.0, min_dist: 0.001, power: 8.0 } }
14}
15
16pub struct RayMarchResult { pub distance: f32, pub steps: u32, pub hit: bool, pub position: Vec3 }
17
18pub struct Mandelbulb { pub power: f32, pub max_iter: u32 }
20impl Default for Mandelbulb { fn default() -> Self { Self { power: 8.0, max_iter: 15 } } }
21impl Mandelbulb {
22 pub fn sdf(&self, p: Vec3) -> f32 {
23 let mut z = p;
24 let mut dr = 1.0f32;
25 let mut r = 0.0f32;
26 for _ in 0..self.max_iter {
27 r = z.length();
28 if r > 2.0 { break; }
29 let theta = (z.z / r).acos();
30 let phi = z.y.atan2(z.x);
31 dr = r.powf(self.power - 1.0) * self.power * dr + 1.0;
32 let zr = r.powf(self.power);
33 let t = theta * self.power;
34 let ph = phi * self.power;
35 z = Vec3::new(t.sin() * ph.cos(), t.sin() * ph.sin(), t.cos()) * zr + p;
36 }
37 0.5 * r.ln() * r / dr
38 }
39
40 pub fn ray_march(&self, origin: Vec3, direction: Vec3, params: &RayMarchParams) -> RayMarchResult {
41 let mut t = 0.0f32;
42 for step in 0..params.max_steps {
43 let p = origin + direction * t;
44 let d = self.sdf(p);
45 if d < params.min_dist { return RayMarchResult { distance: t, steps: step, hit: true, position: p }; }
46 if t > params.max_dist { break; }
47 t += d;
48 }
49 RayMarchResult { distance: t, steps: params.max_steps, hit: false, position: origin + direction * t }
50 }
51}
52
53pub struct Mandelbox { pub scale: f32, pub max_iter: u32, pub fold_limit: f32 }
55impl Default for Mandelbox { fn default() -> Self { Self { scale: 2.0, max_iter: 15, fold_limit: 1.0 } } }
56impl Mandelbox {
57 pub fn sdf(&self, p: Vec3) -> f32 {
58 let mut z = p;
59 let mut dr = 1.0f32;
60 let fl = self.fold_limit;
61 for _ in 0..self.max_iter {
62 z = Vec3::new(
64 if z.x > fl { 2.0 * fl - z.x } else if z.x < -fl { -2.0 * fl - z.x } else { z.x },
65 if z.y > fl { 2.0 * fl - z.y } else if z.y < -fl { -2.0 * fl - z.y } else { z.y },
66 if z.z > fl { 2.0 * fl - z.z } else if z.z < -fl { -2.0 * fl - z.z } else { z.z },
67 );
68 let r2 = z.length_squared();
70 if r2 < 0.25 { let t = 4.0; z *= t; dr *= t; }
71 else if r2 < 1.0 { let t = 1.0 / r2; z *= t; dr *= t; }
72 z = z * self.scale + p;
73 dr = dr * self.scale.abs() + 1.0;
74 }
75 z.length() / dr.abs()
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 #[test]
83 fn mandelbulb_sdf_at_origin() {
84 let mb = Mandelbulb::default();
85 let d = mb.sdf(Vec3::ZERO);
86 assert!(d < 0.1, "Origin should be near/inside the bulb, got {d}");
87 }
88 #[test]
89 fn mandelbulb_sdf_far_away() {
90 let mb = Mandelbulb::default();
91 let d = mb.sdf(Vec3::new(10.0, 0.0, 0.0));
92 assert!(d > 1.0, "Far point should be outside, got {d}");
93 }
94 #[test]
95 fn mandelbox_evaluates() {
96 let mb = Mandelbox::default();
97 let _d = mb.sdf(Vec3::new(0.5, 0.5, 0.5));
98 }
99 #[test]
100 fn ray_march_hits_mandelbulb() {
101 let mb = Mandelbulb::default();
102 let result = mb.ray_march(Vec3::new(0.0, 0.0, 3.0), Vec3::new(0.0, 0.0, -1.0), &RayMarchParams::default());
103 assert!(result.hit, "Ray toward origin should hit the Mandelbulb");
104 }
105}