proof_engine/fractal/
terrain.rs1#[derive(Debug, Clone)]
5pub struct TerrainParams {
6 pub size: u32, pub roughness: f32, pub amplitude: f32, pub seed: u64,
10}
11impl Default for TerrainParams {
12 fn default() -> Self { Self { size: 129, roughness: 0.5, amplitude: 1.0, seed: 42 } }
13}
14
15#[derive(Debug, Clone)]
17pub struct FractalTerrain {
18 pub heightmap: Vec<f32>,
19 pub size: u32,
20 pub min_height: f32,
21 pub max_height: f32,
22}
23
24impl FractalTerrain {
25 pub fn diamond_square(params: &TerrainParams) -> Self {
27 let size = params.size as usize;
28 let mut map = vec![0.0f32; size * size];
29 let mut rng = params.seed;
30
31 map[0] = rand(&mut rng) * params.amplitude;
33 map[size - 1] = rand(&mut rng) * params.amplitude;
34 map[(size - 1) * size] = rand(&mut rng) * params.amplitude;
35 map[(size - 1) * size + size - 1] = rand(&mut rng) * params.amplitude;
36
37 let mut step = size - 1;
38 let mut scale = params.amplitude;
39
40 while step > 1 {
41 let half = step / 2;
42
43 for y in (0..size - 1).step_by(step) {
45 for x in (0..size - 1).step_by(step) {
46 let avg = (map[y * size + x] + map[y * size + x + step]
47 + map[(y + step) * size + x] + map[(y + step) * size + x + step]) * 0.25;
48 map[(y + half) * size + x + half] = avg + rand(&mut rng) * scale;
49 }
50 }
51
52 for y in (0..size).step_by(half) {
54 let x_start = if (y / half) % 2 == 0 { half } else { 0 };
55 for x in (x_start..size).step_by(step) {
56 let mut sum = 0.0f32;
57 let mut count = 0;
58 if y >= half { sum += map[(y - half) * size + x]; count += 1; }
59 if y + half < size { sum += map[(y + half) * size + x]; count += 1; }
60 if x >= half { sum += map[y * size + x - half]; count += 1; }
61 if x + half < size { sum += map[y * size + x + half]; count += 1; }
62 map[y * size + x] = sum / count as f32 + rand(&mut rng) * scale;
63 }
64 }
65
66 step = half;
67 scale *= 2.0_f32.powf(-params.roughness);
68 }
69
70 let min_h = map.iter().copied().fold(f32::MAX, f32::min);
71 let max_h = map.iter().copied().fold(f32::MIN, f32::max);
72
73 Self { heightmap: map, size: params.size, min_height: min_h, max_height: max_h }
74 }
75
76 pub fn get(&self, x: u32, y: u32) -> f32 {
77 self.heightmap[(y * self.size + x) as usize]
78 }
79
80 pub fn normalize(&mut self) {
82 let range = (self.max_height - self.min_height).max(1e-6);
83 for v in &mut self.heightmap { *v = (*v - self.min_height) / range; }
84 self.min_height = 0.0;
85 self.max_height = 1.0;
86 }
87
88 pub fn sample(&self, u: f32, v: f32) -> f32 {
90 let fx = u * (self.size - 1) as f32;
91 let fy = v * (self.size - 1) as f32;
92 let x0 = (fx.floor() as u32).min(self.size - 2);
93 let y0 = (fy.floor() as u32).min(self.size - 2);
94 let fx = fx.fract();
95 let fy = fy.fract();
96 let h00 = self.get(x0, y0);
97 let h10 = self.get(x0 + 1, y0);
98 let h01 = self.get(x0, y0 + 1);
99 let h11 = self.get(x0 + 1, y0 + 1);
100 let h0 = h00 + (h10 - h00) * fx;
101 let h1 = h01 + (h11 - h01) * fx;
102 h0 + (h1 - h0) * fy
103 }
104}
105
106fn rand(rng: &mut u64) -> f32 {
107 *rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
108 (*rng >> 33) as f32 / (u32::MAX >> 1) as f32 * 2.0 - 1.0
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 #[test]
115 fn diamond_square_generates() {
116 let t = FractalTerrain::diamond_square(&TerrainParams { size: 33, ..Default::default() });
117 assert_eq!(t.heightmap.len(), 33 * 33);
118 assert!(t.max_height > t.min_height);
119 }
120 #[test]
121 fn terrain_sample_in_range() {
122 let mut t = FractalTerrain::diamond_square(&TerrainParams::default());
123 t.normalize();
124 let h = t.sample(0.5, 0.5);
125 assert!(h >= 0.0 && h <= 1.0, "h={h}");
126 }
127}