1use super::{Grid2D, Rng, GridPos};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone)]
12pub struct Plate {
13 pub id: u32,
14 pub velocity: (f32, f32),
16 pub oceanic: bool,
18 pub base_elevation: f32,
20 pub area: usize,
22 pub density: f32,
24}
25
26#[derive(Debug, Clone)]
28pub struct PlateMap {
29 pub width: usize,
30 pub height: usize,
31 pub assignment: Vec<u32>,
33 pub plates: Vec<Plate>,
35}
36
37impl PlateMap {
38 pub fn plate_at(&self, x: usize, y: usize) -> u32 {
39 self.assignment[y * self.width + x]
40 }
41
42 pub fn boundary_cells(&self) -> Vec<(usize, usize, u32, u32)> {
44 let mut boundaries = Vec::new();
45 for y in 0..self.height {
46 for x in 0..self.width {
47 let pid = self.plate_at(x, y);
48 for &(nx, ny) in &[(x.wrapping_sub(1), y), (x + 1, y), (x, y.wrapping_sub(1)), (x, y + 1)] {
49 if nx < self.width && ny < self.height {
50 let npid = self.plate_at(nx, ny);
51 if npid != pid {
52 boundaries.push((x, y, pid, npid));
53 break;
54 }
55 }
56 }
57 }
58 }
59 boundaries
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq)]
65pub enum BoundaryType {
66 Convergent,
68 Divergent,
70 Transform,
72}
73
74pub fn generate(grid_size: usize, num_plates: usize, rng: &mut Rng) -> (Grid2D, PlateMap) {
76 let w = grid_size;
77 let h = grid_size;
78
79 let mut centers: Vec<(usize, usize)> = Vec::with_capacity(num_plates);
81 for _ in 0..num_plates {
82 centers.push((rng.range_usize(0, w), rng.range_usize(0, h)));
83 }
84
85 let mut assignment = vec![0u32; w * h];
87 for y in 0..h {
88 for x in 0..w {
89 let mut best_dist = i32::MAX;
90 let mut best_plate = 0u32;
91 for (i, &(cx, cy)) in centers.iter().enumerate() {
92 let dx = wrap_dist(x as i32, cx as i32, w as i32);
94 let dy = wrap_dist(y as i32, cy as i32, h as i32);
95 let d = dx * dx + dy * dy;
96 if d < best_dist {
97 best_dist = d;
98 best_plate = i as u32;
99 }
100 }
101 assignment[y * w + x] = best_plate;
102 }
103 }
104
105 let mut plates: Vec<Plate> = (0..num_plates)
107 .map(|i| {
108 let oceanic = rng.coin(0.6);
109 Plate {
110 id: i as u32,
111 velocity: (rng.range_f32(-1.0, 1.0), rng.range_f32(-1.0, 1.0)),
112 oceanic,
113 base_elevation: if oceanic { rng.range_f32(-0.3, 0.1) } else { rng.range_f32(0.2, 0.6) },
114 area: 0,
115 density: if oceanic { 3.0 } else { 2.7 },
116 }
117 })
118 .collect();
119
120 for &pid in &assignment {
122 plates[pid as usize].area += 1;
123 }
124
125 let plate_map = PlateMap { width: w, height: h, assignment, plates: plates.clone() };
126
127 let mut heightmap = Grid2D::new(w, h);
129
130 for y in 0..h {
132 for x in 0..w {
133 let pid = plate_map.plate_at(x, y) as usize;
134 heightmap.set(x, y, plates[pid].base_elevation);
135 }
136 }
137
138 let boundaries = plate_map.boundary_cells();
140 for &(x, y, pid_a, pid_b) in &boundaries {
141 let pa = &plates[pid_a as usize];
142 let pb = &plates[pid_b as usize];
143 let bt = classify_boundary(pa, pb);
144
145 let elevation_boost = match bt {
146 BoundaryType::Convergent => {
147 if pa.oceanic && !pb.oceanic {
149 0.4 } else if !pa.oceanic && !pb.oceanic {
151 0.6 } else {
153 0.2 }
155 }
156 BoundaryType::Divergent => {
157 -0.2 }
159 BoundaryType::Transform => {
160 0.05 }
162 };
163
164 let radius = 5;
166 for dy in -radius..=radius {
167 for dx in -radius..=radius {
168 let nx = (x as i32 + dx).rem_euclid(w as i32) as usize;
169 let ny = (y as i32 + dy).rem_euclid(h as i32) as usize;
170 let dist = ((dx * dx + dy * dy) as f32).sqrt();
171 let falloff = (1.0 - dist / radius as f32).max(0.0);
172 heightmap.add(nx, ny, elevation_boost * falloff * falloff);
173 }
174 }
175 }
176
177 add_fractal_noise(&mut heightmap, rng, 6, 0.3);
179
180 heightmap.normalize();
181
182 (heightmap, plate_map)
183}
184
185fn classify_boundary(a: &Plate, b: &Plate) -> BoundaryType {
187 let rel_vx = a.velocity.0 - b.velocity.0;
189 let rel_vy = a.velocity.1 - b.velocity.1;
190 let speed = (rel_vx * rel_vx + rel_vy * rel_vy).sqrt();
191
192 if speed < 0.3 {
193 BoundaryType::Transform
194 } else {
195 let convergence = -(rel_vx * rel_vx + rel_vy * rel_vy).sqrt();
197 if convergence < -0.2 {
198 BoundaryType::Convergent
199 } else {
200 BoundaryType::Divergent
201 }
202 }
203}
204
205fn wrap_dist(a: i32, b: i32, size: i32) -> i32 {
207 let d = (a - b).abs();
208 d.min(size - d)
209}
210
211fn add_fractal_noise(grid: &mut Grid2D, rng: &mut Rng, octaves: usize, amplitude: f32) {
213 let w = grid.width;
214 let h = grid.height;
215 let base_seed = rng.next_u64();
216
217 for octave in 0..octaves {
218 let freq = (1 << octave) as f32;
219 let amp = amplitude / (1 << octave) as f32;
220 let seed = base_seed.wrapping_add(octave as u64 * 1000);
221
222 for y in 0..h {
223 for x in 0..w {
224 let nx = x as f32 * freq / w as f32;
225 let ny = y as f32 * freq / h as f32;
226 let noise = value_noise_2d(nx, ny, seed);
227 grid.add(x, y, noise * amp);
228 }
229 }
230 }
231}
232
233fn value_noise_2d(x: f32, y: f32, seed: u64) -> f32 {
235 let xi = x.floor() as i64;
236 let yi = y.floor() as i64;
237 let xf = x - x.floor();
238 let yf = y - y.floor();
239 let tx = xf * xf * (3.0 - 2.0 * xf);
240 let ty = yf * yf * (3.0 - 2.0 * yf);
241
242 let hash = |ix: i64, iy: i64| -> f32 {
243 let n = (ix.wrapping_mul(374761393) + iy.wrapping_mul(668265263) + seed as i64) as u64;
244 let n = n.wrapping_mul(0x5851F42D4C957F2D);
245 let n = n ^ (n >> 32);
246 (n & 0x00FF_FFFF) as f32 / 0x0080_0000 as f32 - 1.0
247 };
248
249 let v00 = hash(xi, yi);
250 let v10 = hash(xi + 1, yi);
251 let v01 = hash(xi, yi + 1);
252 let v11 = hash(xi + 1, yi + 1);
253 let a = v00 + tx * (v10 - v00);
254 let b = v01 + tx * (v11 - v01);
255 a + ty * (b - a)
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_generate_plates() {
264 let mut rng = Rng::new(42);
265 let (hm, pm) = generate(64, 6, &mut rng);
266 assert_eq!(hm.width, 64);
267 assert_eq!(pm.plates.len(), 6);
268 assert!(pm.assignment.iter().all(|&p| (p as usize) < 6));
270 }
271
272 #[test]
273 fn test_boundaries_exist() {
274 let mut rng = Rng::new(42);
275 let (_, pm) = generate(64, 6, &mut rng);
276 let boundaries = pm.boundary_cells();
277 assert!(!boundaries.is_empty(), "should have plate boundaries");
278 }
279
280 #[test]
281 fn test_heightmap_normalized() {
282 let mut rng = Rng::new(42);
283 let (hm, _) = generate(64, 6, &mut rng);
284 let min = hm.min_value();
285 let max = hm.max_value();
286 assert!(min >= -0.01, "min should be ~0: {min}");
287 assert!(max <= 1.01, "max should be ~1: {max}");
288 }
289}