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 if r < 1e-6 {
30 z = p;
33 dr = 1.0;
34 continue;
35 }
36 let theta = (z.z / r).acos();
37 let phi = z.y.atan2(z.x);
38 dr = r.powf(self.power - 1.0) * self.power * dr + 1.0;
39 let zr = r.powf(self.power);
40 let t = theta * self.power;
41 let ph = phi * self.power;
42 z = Vec3::new(t.sin() * ph.cos(), t.sin() * ph.sin(), t.cos()) * zr + p;
43 }
44 if r < 1e-6 {
45 return 0.0; }
47 0.5 * r.ln() * r / dr
48 }
49
50 pub fn ray_march(&self, origin: Vec3, direction: Vec3, params: &RayMarchParams) -> RayMarchResult {
51 let mut t = 0.0f32;
52 for step in 0..params.max_steps {
53 let p = origin + direction * t;
54 let d = self.sdf(p);
55 if d < params.min_dist { return RayMarchResult { distance: t, steps: step, hit: true, position: p }; }
56 if t > params.max_dist { break; }
57 t += d;
58 }
59 RayMarchResult { distance: t, steps: params.max_steps, hit: false, position: origin + direction * t }
60 }
61}
62
63pub struct Mandelbox { pub scale: f32, pub max_iter: u32, pub fold_limit: f32 }
65impl Default for Mandelbox { fn default() -> Self { Self { scale: 2.0, max_iter: 15, fold_limit: 1.0 } } }
66impl Mandelbox {
67 pub fn sdf(&self, p: Vec3) -> f32 {
68 let mut z = p;
69 let mut dr = 1.0f32;
70 let fl = self.fold_limit;
71 for _ in 0..self.max_iter {
72 z = Vec3::new(
74 if z.x > fl { 2.0 * fl - z.x } else if z.x < -fl { -2.0 * fl - z.x } else { z.x },
75 if z.y > fl { 2.0 * fl - z.y } else if z.y < -fl { -2.0 * fl - z.y } else { z.y },
76 if z.z > fl { 2.0 * fl - z.z } else if z.z < -fl { -2.0 * fl - z.z } else { z.z },
77 );
78 let r2 = z.length_squared();
80 if r2 < 0.25 { let t = 4.0; z *= t; dr *= t; }
81 else if r2 < 1.0 { let t = 1.0 / r2; z *= t; dr *= t; }
82 z = z * self.scale + p;
83 dr = dr * self.scale.abs() + 1.0;
84 }
85 z.length() / dr.abs()
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 #[test]
93 fn mandelbulb_sdf_at_origin() {
94 let mb = Mandelbulb::default();
95 let d = mb.sdf(Vec3::ZERO);
96 assert!(d < 0.1, "Origin should be near/inside the bulb, got {d}");
97 }
98 #[test]
99 fn mandelbulb_sdf_far_away() {
100 let mb = Mandelbulb::default();
101 let d = mb.sdf(Vec3::new(10.0, 0.0, 0.0));
102 assert!(d > 1.0, "Far point should be outside, got {d}");
103 }
104 #[test]
105 fn mandelbox_evaluates() {
106 let mb = Mandelbox::default();
107 let _d = mb.sdf(Vec3::new(0.5, 0.5, 0.5));
108 }
109 #[test]
110 fn ray_march_hits_mandelbulb() {
111 let mb = Mandelbulb::default();
112 let result = mb.ray_march(Vec3::new(0.0, 0.0, 3.0), Vec3::new(0.0, 0.0, -1.0), &RayMarchParams::default());
113 assert!(result.hit, "Ray toward origin should hit the Mandelbulb");
114 }
115}