1use glam::{Vec2, Vec3};
8
9#[derive(Clone, Debug)]
12pub struct Rng {
13 state: [u64; 4],
14}
15
16impl Rng {
17 pub fn new(seed: u64) -> Self {
18 let mut s = seed;
19 let mut next = || {
20 s = s.wrapping_add(0x9e3779b97f4a7c15);
21 let mut z = s;
22 z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
23 z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
24 z ^ (z >> 31)
25 };
26 Self { state: [next(), next(), next(), next()] }
27 }
28
29 fn rol64(x: u64, k: u32) -> u64 { (x << k) | (x >> (64 - k)) }
30
31 fn next_u64(&mut self) -> u64 {
32 let result = Self::rol64(self.state[1].wrapping_mul(5), 7).wrapping_mul(9);
33 let t = self.state[1] << 17;
34 self.state[2] ^= self.state[0];
35 self.state[3] ^= self.state[1];
36 self.state[1] ^= self.state[2];
37 self.state[0] ^= self.state[3];
38 self.state[2] ^= t;
39 self.state[3] = Self::rol64(self.state[3], 45);
40 result
41 }
42
43 fn next_f32(&mut self) -> f32 {
44 (self.next_u64() >> 11) as f32 / (1u64 << 53) as f32
45 }
46
47 fn next_f32_range(&mut self, lo: f32, hi: f32) -> f32 {
48 lo + self.next_f32() * (hi - lo)
49 }
50
51 fn next_usize(&mut self, n: usize) -> usize {
52 (self.next_u64() % n as u64) as usize
53 }
54
55 pub fn next_i32_range(&mut self, lo: i32, hi: i32) -> i32 {
56 if hi <= lo { return lo; }
57 lo + (self.next_u64() % (hi - lo) as u64) as i32
58 }
59}
60
61fn fade(t: f32) -> f32 { t * t * t * (t * (t * 6.0 - 15.0) + 10.0) }
64fn lerp(a: f32, b: f32, t: f32) -> f32 { a + t * (b - a) }
65
66fn grad2(hash: u32, x: f32, y: f32) -> f32 {
67 match hash & 7 {
68 0 => x + y, 1 => -x + y, 2 => x - y, 3 => -x - y,
69 4 => x, 5 => -x, 6 => y, _ => -y,
70 }
71}
72
73pub struct GradientNoisePublic {
75 perm: [u8; 512],
76}
77
78impl GradientNoisePublic {
79 pub fn new(seed: u64) -> Self {
80 let inner = GradientNoise::new(seed);
81 Self { perm: inner.perm }
82 }
83 pub fn noise2d(&self, x: f32, y: f32) -> f32 {
84 let inner = GradientNoise { perm: self.perm };
85 inner.noise2d(x, y)
86 }
87}
88
89struct GradientNoise {
91 perm: [u8; 512],
92}
93
94impl GradientNoise {
95 fn new(seed: u64) -> Self {
96 let mut rng = Rng::new(seed);
97 let mut p: [u8; 256] = [0u8; 256];
98 for (i, v) in p.iter_mut().enumerate() { *v = i as u8; }
99 for i in (1..256).rev() {
101 let j = rng.next_usize(i + 1);
102 p.swap(i, j);
103 }
104 let mut perm = [0u8; 512];
105 for i in 0..512 { perm[i] = p[i & 255]; }
106 Self { perm }
107 }
108
109 fn noise2d(&self, x: f32, y: f32) -> f32 {
110 let xi = x.floor() as i32;
111 let yi = y.floor() as i32;
112 let xf = x - x.floor();
113 let yf = y - y.floor();
114 let u = fade(xf);
115 let v = fade(yf);
116 let aa = self.perm[(self.perm[(xi & 255) as usize] as i32 + (yi & 255)) as usize & 511] as u32;
117 let ab = self.perm[(self.perm[(xi & 255) as usize] as i32 + (yi & 255) + 1) as usize & 511] as u32;
118 let ba = self.perm[(self.perm[((xi + 1) & 255) as usize] as i32 + (yi & 255)) as usize & 511] as u32;
119 let bb = self.perm[(self.perm[((xi + 1) & 255) as usize] as i32 + (yi & 255) + 1) as usize & 511] as u32;
120 let x1 = lerp(grad2(aa, xf, yf), grad2(ba, xf - 1.0, yf), u);
121 let x2 = lerp(grad2(ab, xf, yf - 1.0), grad2(bb, xf - 1.0, yf - 1.0), u);
122 (lerp(x1, x2, v) + 1.0) * 0.5
123 }
124}
125
126#[derive(Clone, Debug)]
133pub struct HeightMap {
134 pub width: usize,
135 pub height: usize,
136 pub data: Vec<f32>,
137}
138
139impl HeightMap {
140 pub fn new(width: usize, height: usize) -> Self {
142 Self { width, height, data: vec![0.0; width * height] }
143 }
144
145 pub fn from_data(width: usize, height: usize, data: Vec<f32>) -> Self {
147 assert_eq!(data.len(), width * height, "data length mismatch");
148 Self { width, height, data }
149 }
150
151 pub fn get(&self, x: usize, y: usize) -> f32 {
153 if x < self.width && y < self.height {
154 self.data[y * self.width + x]
155 } else {
156 0.0
157 }
158 }
159
160 pub fn slope_at(&self, x: usize, y: usize) -> f32 {
162 let xl = if x > 0 { x - 1 } else { x };
163 let xr = if x + 1 < self.width { x + 1 } else { x };
164 let yd = if y > 0 { y - 1 } else { y };
165 let yu = if y + 1 < self.height { y + 1 } else { y };
166 let dx = self.get(xr, y) - self.get(xl, y);
167 let dy = self.get(x, yu) - self.get(x, yd);
168 (dx * dx + dy * dy).sqrt()
169 }
170
171 pub fn set(&mut self, x: usize, y: usize, v: f32) {
173 if x < self.width && y < self.height {
174 self.data[y * self.width + x] = v;
175 }
176 }
177
178 pub fn sample_bilinear(&self, x: f32, y: f32) -> f32 {
181 let cx = x.clamp(0.0, (self.width - 1) as f32);
182 let cy = y.clamp(0.0, (self.height - 1) as f32);
183 let x0 = cx.floor() as usize;
184 let y0 = cy.floor() as usize;
185 let x1 = (x0 + 1).min(self.width - 1);
186 let y1 = (y0 + 1).min(self.height - 1);
187 let tx = cx - x0 as f32;
188 let ty = cy - y0 as f32;
189 let h00 = self.get(x0, y0);
190 let h10 = self.get(x1, y0);
191 let h01 = self.get(x0, y1);
192 let h11 = self.get(x1, y1);
193 lerp(lerp(h00, h10, tx), lerp(h01, h11, tx), ty)
194 }
195
196 pub fn sample_cubic(&self, x: f32, y: f32) -> f32 {
198 let cx = x.clamp(1.0, (self.width - 2) as f32);
199 let cy = y.clamp(1.0, (self.height - 2) as f32);
200 let x1 = cx.floor() as usize;
201 let y1 = cy.floor() as usize;
202 let tx = cx - x1 as f32;
203 let ty = cy - y1 as f32;
204
205 let catmull = |p0: f32, p1: f32, p2: f32, p3: f32, t: f32| -> f32 {
206 let a = -0.5 * p0 + 1.5 * p1 - 1.5 * p2 + 0.5 * p3;
207 let b = p0 - 2.5 * p1 + 2.0 * p2 - 0.5 * p3;
208 let c = -0.5 * p0 + 0.5 * p2;
209 let d = p1;
210 a * t * t * t + b * t * t + c * t + d
211 };
212
213 let row = |yr: usize| {
214 let x0 = if x1 > 0 { x1 - 1 } else { 0 };
215 let x2 = (x1 + 1).min(self.width - 1);
216 let x3 = (x1 + 2).min(self.width - 1);
217 catmull(self.get(x0, yr), self.get(x1, yr), self.get(x2, yr), self.get(x3, yr), tx)
218 };
219
220 let y0 = if y1 > 0 { y1 - 1 } else { 0 };
221 let y2 = (y1 + 1).min(self.height - 1);
222 let y3 = (y1 + 2).min(self.height - 1);
223 catmull(row(y0), row(y1), row(y2), row(y3), ty)
224 }
225
226 pub fn normal_at(&self, x: usize, y: usize) -> Vec3 {
229 let x0 = if x > 0 { x - 1 } else { 0 };
230 let x2 = (x + 1).min(self.width - 1);
231 let y0 = if y > 0 { y - 1 } else { 0 };
232 let y2 = (y + 1).min(self.height - 1);
233 let dzdx = (self.get(x2, y) - self.get(x0, y)) / 2.0;
234 let dzdy = (self.get(x, y2) - self.get(x, y0)) / 2.0;
235 Vec3::new(-dzdx, 1.0, -dzdy).normalize()
236 }
237
238 pub fn min_value(&self) -> f32 {
240 self.data.iter().cloned().fold(f32::INFINITY, f32::min)
241 }
242
243 pub fn max_value(&self) -> f32 {
245 self.data.iter().cloned().fold(f32::NEG_INFINITY, f32::max)
246 }
247
248 pub fn normalize(&mut self) {
250 let mn = self.min_value();
251 let mx = self.max_value();
252 let range = mx - mn;
253 if range < 1e-9 { return; }
254 for v in self.data.iter_mut() { *v = (*v - mn) / range; }
255 }
256
257 pub fn clamp_range(&mut self, min: f32, max: f32) {
259 for v in self.data.iter_mut() { *v = v.clamp(min, max); }
260 }
261
262 pub fn blur(&mut self, radius: usize) {
264 if radius == 0 { return; }
265 let w = self.width;
266 let h = self.height;
267 let mut tmp = vec![0.0f32; w * h];
268 let r = radius as i32;
269 for y in 0..h {
271 for x in 0..w {
272 let mut sum = 0.0f32;
273 let mut count = 0;
274 for dx in -r..=r {
275 let nx = x as i32 + dx;
276 if nx >= 0 && (nx as usize) < w {
277 sum += self.data[y * w + nx as usize];
278 count += 1;
279 }
280 }
281 tmp[y * w + x] = sum / count as f32;
282 }
283 }
284 let mut out = vec![0.0f32; w * h];
286 for y in 0..h {
287 for x in 0..w {
288 let mut sum = 0.0f32;
289 let mut count = 0;
290 for dy in -r..=r {
291 let ny = y as i32 + dy;
292 if ny >= 0 && (ny as usize) < h {
293 sum += tmp[ny as usize * w + x];
294 count += 1;
295 }
296 }
297 out[y * w + x] = sum / count as f32;
298 }
299 }
300 self.data = out;
301 }
302
303 pub fn sharpen(&mut self, amount: f32) {
305 let mut blurred = self.clone();
306 blurred.blur(2);
307 for (v, b) in self.data.iter_mut().zip(blurred.data.iter()) {
308 *v = (*v + amount * (*v - b)).clamp(0.0, 1.0);
309 }
310 }
311
312 pub fn terrace(&mut self, levels: usize) {
314 if levels < 2 { return; }
315 let levels_f = levels as f32;
316 for v in self.data.iter_mut() {
317 *v = (*v * levels_f).floor() / (levels_f - 1.0);
318 *v = v.clamp(0.0, 1.0);
319 }
320 }
321
322 pub fn island_mask(&mut self, falloff: f32) {
325 let cx = (self.width as f32 - 1.0) * 0.5;
326 let cy = (self.height as f32 - 1.0) * 0.5;
327 let max_dist = cx.min(cy);
328 for y in 0..self.height {
329 for x in 0..self.width {
330 let dx = (x as f32 - cx) / max_dist;
331 let dy = (y as f32 - cy) / max_dist;
332 let dist = (dx * dx + dy * dy).sqrt().clamp(0.0, 1.0);
333 let mask = (1.0 - dist.powf(falloff)).clamp(0.0, 1.0);
334 self.data[y * self.width + x] *= mask;
335 }
336 }
337 }
338
339 pub fn ridge_noise(&mut self, octaves: usize) {
342 let noise = GradientNoise::new(42);
343 let w = self.width;
344 let h = self.height;
345 for y in 0..h {
346 for x in 0..w {
347 let mut amplitude = 1.0f32;
348 let mut frequency = 1.0f32;
349 let mut value = 0.0f32;
350 let mut max_amplitude = 0.0f32;
351 for _oct in 0..octaves {
352 let nx = x as f32 / w as f32 * frequency;
353 let ny = y as f32 / h as f32 * frequency;
354 let n = noise.noise2d(nx * 8.0, ny * 8.0);
355 let ridge = 1.0 - (2.0 * n - 1.0).abs();
357 value += ridge * ridge * amplitude;
358 max_amplitude += amplitude;
359 amplitude *= 0.5;
360 frequency *= 2.0;
361 }
362 let existing = self.data[y * w + x];
363 self.data[y * w + x] = (existing + value / max_amplitude).clamp(0.0, 1.0) * 0.5;
364 }
365 }
366 }
367
368 pub fn slope_map(&self) -> HeightMap {
372 let mut out = HeightMap::new(self.width, self.height);
373 for y in 0..self.height {
374 for x in 0..self.width {
375 let n = self.normal_at(x, y);
376 let slope = 1.0 - n.y.clamp(0.0, 1.0);
378 out.set(x, y, slope);
379 }
380 }
381 out
382 }
383
384 pub fn curvature_map(&self) -> HeightMap {
387 let mut out = HeightMap::new(self.width, self.height);
388 for y in 1..(self.height - 1) {
389 for x in 1..(self.width - 1) {
390 let center = self.get(x, y);
391 let laplacian = self.get(x - 1, y) + self.get(x + 1, y)
392 + self.get(x, y - 1) + self.get(x, y + 1)
393 - 4.0 * center;
394 out.set(x, y, (laplacian * 0.5 + 0.5).clamp(0.0, 1.0));
396 }
397 }
398 out
399 }
400
401 pub fn flow_map(&self) -> HeightMap {
404 let mut out = HeightMap::new(self.width, self.height);
405 let dirs: [(i32, i32); 8] = [
406 (-1, -1), (0, -1), (1, -1),
407 (-1, 0), (1, 0),
408 (-1, 1), (0, 1), (1, 1),
409 ];
410 for y in 0..self.height {
411 for x in 0..self.width {
412 let h = self.get(x, y);
413 let mut min_h = h;
414 let mut best_dir = 8usize;
415 for (d, (dx, dy)) in dirs.iter().enumerate() {
416 let nx = x as i32 + dx;
417 let ny = y as i32 + dy;
418 if nx >= 0 && nx < self.width as i32 && ny >= 0 && ny < self.height as i32 {
419 let nh = self.get(nx as usize, ny as usize);
420 if nh < min_h {
421 min_h = nh;
422 best_dir = d;
423 }
424 }
425 }
426 out.set(x, y, best_dir as f32 / 8.0);
427 }
428 }
429 out
430 }
431
432 pub fn shadow_map(&self, sun_dir: Vec3) -> HeightMap {
435 let mut out = HeightMap::new(self.width, self.height);
436 for v in out.data.iter_mut() { *v = 1.0; }
438
439 let step_x = -sun_dir.x / sun_dir.y.abs().max(0.001);
441 let step_z = -sun_dir.z / sun_dir.y.abs().max(0.001);
442 let step_h = 1.0f32; for y in 0..self.height {
445 for x in 0..self.width {
446 let h0 = self.get(x, y);
447 let mut cx = x as f32;
448 let mut cy = y as f32;
449 let mut horizon_h = h0;
450 for _step in 0..256 {
451 cx += step_x;
452 cy += step_z;
453 horizon_h += step_h * 0.01;
454 if cx < 0.0 || cx >= self.width as f32 || cy < 0.0 || cy >= self.height as f32 {
455 break;
456 }
457 let terrain_h = self.sample_bilinear(cx, cy);
458 if terrain_h > horizon_h {
459 out.set(x, y, 0.0);
460 break;
461 }
462 }
463 }
464 }
465 out
466 }
467
468 pub fn visibility_map(&self, observer_height: f32) -> HeightMap {
471 let mut out = HeightMap::new(self.width, self.height);
472 let w = self.width;
473 let h = self.height;
474 for y in 0..h {
476 for x in 0..w {
477 let eye_h = self.get(x, y) + observer_height;
478 let mut visible_count = 0usize;
479 let total = 64usize; for i in 0..total {
481 let angle = i as f32 * std::f32::consts::TAU / total as f32;
482 let dir_x = angle.cos();
483 let dir_y = angle.sin();
484 let max_dist = (w.min(h) as f32) * 0.5;
485 let steps = max_dist as usize;
486 let mut visible = true;
487 let mut max_slope = f32::NEG_INFINITY;
488 for s in 1..steps {
489 let sx = x as f32 + dir_x * s as f32;
490 let sy = y as f32 + dir_y * s as f32;
491 if sx < 0.0 || sx >= w as f32 || sy < 0.0 || sy >= h as f32 { break; }
492 let th = self.sample_bilinear(sx, sy);
493 let dist = (s as f32).max(1.0);
494 let slope = (th - eye_h) / dist;
495 if slope > max_slope {
496 max_slope = slope;
497 } else if slope < max_slope - 0.05 {
498 visible = false;
499 break;
500 }
501 }
502 if visible { visible_count += 1; }
503 }
504 out.set(x, y, visible_count as f32 / total as f32);
505 }
506 }
507 out
508 }
509
510 pub fn to_raw_bytes(&self) -> Vec<u8> {
515 let mut out = Vec::with_capacity(8 + self.data.len() * 4);
516 out.extend_from_slice(&(self.width as u32).to_le_bytes());
517 out.extend_from_slice(&(self.height as u32).to_le_bytes());
518 for &v in &self.data {
519 out.extend_from_slice(&v.to_le_bytes());
520 }
521 out
522 }
523
524 pub fn from_raw_bytes(bytes: &[u8]) -> Option<Self> {
526 if bytes.len() < 8 { return None; }
527 let width = u32::from_le_bytes(bytes[0..4].try_into().ok()?) as usize;
528 let height = u32::from_le_bytes(bytes[4..8].try_into().ok()?) as usize;
529 if bytes.len() < 8 + width * height * 4 { return None; }
530 let mut data = Vec::with_capacity(width * height);
531 for i in 0..(width * height) {
532 let off = 8 + i * 4;
533 let v = f32::from_le_bytes(bytes[off..off + 4].try_into().ok()?);
534 data.push(v);
535 }
536 Some(Self { width, height, data })
537 }
538
539 pub fn to_png_8bit(&self) -> Vec<u8> {
542 let pixels: Vec<u8> = self.data.iter()
543 .map(|&v| (v.clamp(0.0, 1.0) * 255.0) as u8)
544 .collect();
545 encode_png_grayscale(self.width as u32, self.height as u32, &pixels, 8)
546 }
547
548 pub fn to_png_16bit(&self) -> Vec<u8> {
550 let pixels: Vec<u8> = self.data.iter().flat_map(|&v| {
551 let val = (v.clamp(0.0, 1.0) * 65535.0) as u16;
552 val.to_be_bytes()
553 }).collect();
554 encode_png_grayscale(self.width as u32, self.height as u32, &pixels, 16)
555 }
556
557 pub fn from_png_8bit(width: usize, height: usize, pixels: &[u8]) -> Self {
559 let data = pixels.iter().map(|&p| p as f32 / 255.0).collect();
560 Self { width, height, data }
561 }
562
563 pub fn from_png_16bit(width: usize, height: usize, pixels: &[u8]) -> Self {
565 let mut data = Vec::with_capacity(width * height);
566 for i in 0..(width * height) {
567 let hi = pixels[i * 2] as u16;
568 let lo = pixels[i * 2 + 1] as u16;
569 data.push(((hi << 8) | lo) as f32 / 65535.0);
570 }
571 Self { width, height, data }
572 }
573}
574
575fn encode_png_grayscale(width: u32, height: u32, pixels: &[u8], bit_depth: u8) -> Vec<u8> {
578 use std::io::Write;
579
580 let mut out: Vec<u8> = vec![137, 80, 78, 71, 13, 10, 26, 10];
582
583 let write_chunk = |out: &mut Vec<u8>, tag: &[u8; 4], data: &[u8]| {
584 let len = data.len() as u32;
585 out.extend_from_slice(&len.to_be_bytes());
586 out.extend_from_slice(tag);
587 out.extend_from_slice(data);
588 let crc = png_crc(tag, data);
589 out.extend_from_slice(&crc.to_be_bytes());
590 };
591
592 let mut ihdr = Vec::new();
594 let _ = ihdr.write_all(&width.to_be_bytes());
595 let _ = ihdr.write_all(&height.to_be_bytes());
596 ihdr.push(bit_depth); ihdr.push(0); ihdr.push(0); ihdr.push(0); ihdr.push(0); write_chunk(&mut out, b"IHDR", &ihdr);
602
603 let bytes_per_pixel = if bit_depth == 16 { 2 } else { 1 };
605 let row_bytes = width as usize * bytes_per_pixel;
606 let mut raw = Vec::with_capacity((row_bytes + 1) * height as usize);
607 for row in 0..height as usize {
608 raw.push(0); raw.extend_from_slice(&pixels[row * row_bytes..(row + 1) * row_bytes]);
610 }
611 let compressed = deflate_no_compress(&raw);
612 write_chunk(&mut out, b"IDAT", &compressed);
613
614 write_chunk(&mut out, b"IEND", &[]);
616
617 out
618}
619
620fn png_crc(tag: &[u8], data: &[u8]) -> u32 {
622 let table = crc32_table();
623 let mut crc = 0xFFFF_FFFFu32;
624 for &b in tag.iter().chain(data.iter()) {
625 crc = (crc >> 8) ^ table[((crc ^ b as u32) & 0xFF) as usize];
626 }
627 !crc
628}
629
630fn crc32_table() -> [u32; 256] {
631 let mut table = [0u32; 256];
632 for n in 0..256u32 {
633 let mut c = n;
634 for _ in 0..8 { c = if c & 1 != 0 { 0xEDB88320 ^ (c >> 1) } else { c >> 1 }; }
635 table[n as usize] = c;
636 }
637 table
638}
639
640fn deflate_no_compress(data: &[u8]) -> Vec<u8> {
642 let mut out: Vec<u8> = vec![0x78, 0x01];
644 const BLOCK_SIZE: usize = 65535;
645 let mut pos = 0;
646 while pos < data.len() {
647 let end = (pos + BLOCK_SIZE).min(data.len());
648 let is_last = end == data.len();
649 out.push(if is_last { 1 } else { 0 }); let len = (end - pos) as u16;
651 let nlen = !len;
652 out.extend_from_slice(&len.to_le_bytes());
653 out.extend_from_slice(&nlen.to_le_bytes());
654 out.extend_from_slice(&data[pos..end]);
655 pos = end;
656 }
657 if data.is_empty() {
658 out.push(1); out.extend_from_slice(&[0, 0, 0xFF, 0xFF]); }
661 let (s1, s2) = data.iter().fold((1u32, 0u32), |(s1, s2), &b| {
663 let s1 = (s1 + b as u32) % 65521;
664 ((s1 + s2) % 65521, (s1 + s2) % 65521)
665 });
666 let adler = (s2 << 16) | s1;
667 out.extend_from_slice(&adler.to_be_bytes());
668 out
669}
670
671pub struct DiamondSquare;
675
676impl DiamondSquare {
677 pub fn generate(size: usize, roughness: f32, seed: u64) -> HeightMap {
680 let n = size + 1;
681 let mut rng = Rng::new(seed);
682 let mut map = HeightMap::new(n, n);
683
684 map.set(0, 0, rng.next_f32());
686 map.set(size, 0, rng.next_f32());
687 map.set(0, size, rng.next_f32());
688 map.set(size, size, rng.next_f32());
689
690 let mut step = size;
691 let mut scale = roughness;
692
693 while step > 1 {
694 let half = step / 2;
695
696 let mut y = 0;
698 while y < size {
699 let mut x = 0;
700 while x < size {
701 let avg = (map.get(x, y)
702 + map.get(x + step, y)
703 + map.get(x, y + step)
704 + map.get(x + step, y + step)) * 0.25;
705 let rand = rng.next_f32_range(-scale, scale);
706 map.set(x + half, y + half, (avg + rand).clamp(0.0, 1.0));
707 x += step;
708 }
709 y += step;
710 }
711
712 let mut y = 0;
714 while y <= size {
715 let mut x = (y / half % 2) * half;
716 while x <= size {
717 let mut sum = 0.0f32;
718 let mut count = 0;
719 if x >= half {
720 sum += map.get(x - half, y);
721 count += 1;
722 }
723 if x + half <= size {
724 sum += map.get(x + half, y);
725 count += 1;
726 }
727 if y >= half {
728 sum += map.get(x, y - half);
729 count += 1;
730 }
731 if y + half <= size {
732 sum += map.get(x, y + half);
733 count += 1;
734 }
735 let avg = if count > 0 { sum / count as f32 } else { 0.5 };
736 let rand = rng.next_f32_range(-scale, scale);
737 map.set(x, y, (avg + rand).clamp(0.0, 1.0));
738 x += step;
739 }
740 y += half;
741 }
742
743 step = half;
744 scale *= 2.0f32.powf(-roughness);
745 }
746 map
747 }
748}
749
750pub struct FractalNoise;
754
755impl FractalNoise {
756 pub fn generate(
763 width: usize,
764 height: usize,
765 octaves: usize,
766 lacunarity: f32,
767 persistence: f32,
768 scale: f32,
769 seed: u64,
770 ) -> HeightMap {
771 let noise = GradientNoise::new(seed);
772 let mut map = HeightMap::new(width, height);
773 for y in 0..height {
774 for x in 0..width {
775 let mut value = 0.0f32;
776 let mut amplitude = 1.0f32;
777 let mut frequency = scale;
778 let mut max_val = 0.0f32;
779 for _oct in 0..octaves {
780 let nx = x as f32 / width as f32 * frequency;
781 let ny = y as f32 / height as f32 * frequency;
782 value += noise.noise2d(nx, ny) * amplitude;
783 max_val += amplitude;
784 amplitude *= persistence;
785 frequency *= lacunarity;
786 }
787 map.set(x, y, (value / max_val).clamp(0.0, 1.0));
788 }
789 }
790 map
791 }
792
793 pub fn generate_warped(
795 width: usize,
796 height: usize,
797 octaves: usize,
798 seed: u64,
799 ) -> HeightMap {
800 let noise1 = GradientNoise::new(seed);
801 let noise2 = GradientNoise::new(seed.wrapping_add(137));
802 let noise3 = GradientNoise::new(seed.wrapping_add(271));
803 let mut map = HeightMap::new(width, height);
804 for y in 0..height {
805 for x in 0..width {
806 let px = x as f32 / width as f32 * 4.0;
807 let py = y as f32 / height as f32 * 4.0;
808 let wx = noise1.noise2d(px, py) * 2.0 - 1.0;
810 let wy = noise2.noise2d(px + 5.2, py + 1.3) * 2.0 - 1.0;
811 let wx2 = noise2.noise2d(px + wx, py + wy);
813 let wy2 = noise3.noise2d(px + wx + 1.7, py + wy + 9.2);
814 let mut value = 0.0f32;
816 let mut amp = 1.0f32;
817 let mut freq = 1.0f32;
818 let mut max_amp = 0.0f32;
819 for _oct in 0..octaves {
820 let sx = (px + wx2) * freq;
821 let sy = (py + wy2) * freq;
822 value += noise3.noise2d(sx, sy) * amp;
823 max_amp += amp;
824 amp *= 0.5;
825 freq *= 2.0;
826 }
827 map.set(x, y, (value / max_amp).clamp(0.0, 1.0));
828 }
829 }
830 map
831 }
832}
833
834pub struct VoronoiPlates;
838
839impl VoronoiPlates {
840 pub fn generate(width: usize, height: usize, num_plates: usize, seed: u64) -> HeightMap {
845 let mut rng = Rng::new(seed);
846
847 let mut centers: Vec<(f32, f32)> = (0..num_plates)
849 .map(|_| (rng.next_f32() * width as f32, rng.next_f32() * height as f32))
850 .collect();
851
852 let plate_elevations: Vec<f32> = (0..num_plates)
854 .map(|_| if rng.next_f32() < 0.4 { rng.next_f32_range(0.0, 0.35) } else { rng.next_f32_range(0.4, 0.7) })
855 .collect();
856
857 let plate_velocities: Vec<(f32, f32)> = (0..num_plates)
859 .map(|_| {
860 let angle = rng.next_f32() * std::f32::consts::TAU;
861 (angle.cos() * 0.5, angle.sin() * 0.5)
862 })
863 .collect();
864
865 let mut map = HeightMap::new(width, height);
867 for y in 0..height {
868 for x in 0..width {
869 let px = x as f32;
870 let py = y as f32;
871 let mut dists: Vec<(f32, usize)> = centers.iter().enumerate()
872 .map(|(i, &(cx, cy))| {
873 let dx = px - cx;
874 let dy = py - cy;
875 (dx * dx + dy * dy, i)
876 })
877 .collect();
878 dists.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
879 let (d0, p0) = dists[0];
880 let (d1, _p1) = dists[1];
881 let base_h = plate_elevations[p0];
882 let boundary_t = 1.0 - ((d1.sqrt() - d0.sqrt()) / (width.min(height) as f32 * 0.05)).clamp(0.0, 1.0);
884 let v0 = plate_velocities[p0];
886 let dir = ((px - centers[p0].0).signum(), (py - centers[p0].1).signum());
887 let convergence = -(v0.0 * dir.0 + v0.1 * dir.1).clamp(-1.0, 1.0);
888 let mountain_bonus = boundary_t * convergence.max(0.0) * 0.4;
889 let rift_penalty = boundary_t * (-convergence).max(0.0) * 0.15;
890 let h = (base_h + mountain_bonus - rift_penalty).clamp(0.0, 1.0);
891 map.set(x, y, h);
892 }
893 }
894
895 map.blur(3);
897 map.normalize();
898 map
899 }
900}
901
902pub struct PerlinTerrain;
906
907impl PerlinTerrain {
908 pub fn generate(
910 width: usize,
911 height: usize,
912 octaves: usize,
913 scale: f32,
914 seed: u64,
915 ) -> HeightMap {
916 let base = FractalNoise::generate(width, height, 2, 2.0, 0.5, scale * 0.2, seed);
918 let detail = FractalNoise::generate(width, height, octaves, 2.1, 0.48, scale, seed.wrapping_add(9999));
920 let mut map = HeightMap::new(width, height);
921 for i in 0..(width * height) {
922 map.data[i] = (base.data[i] * 0.6 + detail.data[i] * 0.4).clamp(0.0, 1.0);
924 }
925 map.normalize();
926 map
927 }
928
929 pub fn generate_with_ridges(width: usize, height: usize, seed: u64) -> HeightMap {
931 let mut base = FractalNoise::generate_warped(width, height, 6, seed);
932 base.ridge_noise(4);
933 base.normalize();
934 base
935 }
936}
937
938pub struct HydraulicErosion;
942
943impl HydraulicErosion {
944 pub fn erode(
949 map: &mut HeightMap,
950 iterations: usize,
951 rain_amount: f32,
952 sediment_capacity: f32,
953 evaporation: f32,
954 seed: u64,
955 ) {
956 let mut rng = Rng::new(seed);
957 let w = map.width;
958 let h = map.height;
959
960 for _ in 0..iterations {
961 let mut px = rng.next_f32() * (w - 2) as f32 + 1.0;
963 let mut py = rng.next_f32() * (h - 2) as f32 + 1.0;
964 let mut water = rain_amount;
965 let mut sediment = 0.0f32;
966 let mut vel_x = 0.0f32;
967 let mut vel_y = 0.0f32;
968 let inertia = 0.3f32;
969 let gravity = 4.0f32;
970 let erosion_speed = 0.3f32;
971 let deposit_speed = 0.3f32;
972
973 for _step in 0..128 {
974 let xi = px as usize;
975 let yi = py as usize;
976 if xi + 1 >= w || yi + 1 >= h { break; }
977
978 let tx = px - xi as f32;
980 let ty = py - yi as f32;
981 let h00 = map.get(xi, yi);
982 let h10 = map.get(xi + 1, yi);
983 let h01 = map.get(xi, yi + 1);
984 let h11 = map.get(xi + 1, yi + 1);
985 let gx = (h10 - h00) * (1.0 - ty) + (h11 - h01) * ty;
986 let gy = (h01 - h00) * (1.0 - tx) + (h11 - h10) * tx;
987 let height_at = h00 * (1.0 - tx) * (1.0 - ty)
988 + h10 * tx * (1.0 - ty)
989 + h01 * (1.0 - tx) * ty
990 + h11 * tx * ty;
991
992 vel_x = vel_x * inertia - gx * (1.0 - inertia) * gravity;
994 vel_y = vel_y * inertia - gy * (1.0 - inertia) * gravity;
995 let speed = (vel_x * vel_x + vel_y * vel_y).sqrt().max(0.001);
996 vel_x /= speed;
997 vel_y /= speed;
998
999 let new_px = px + vel_x;
1000 let new_py = py + vel_y;
1001 if new_px < 1.0 || new_px >= (w - 1) as f32 || new_py < 1.0 || new_py >= (h - 1) as f32 { break; }
1002
1003 let new_h = map.sample_bilinear(new_px, new_py);
1004 let delta_h = new_h - height_at;
1005
1006 let capacity = sediment_capacity * speed * water * (-delta_h).max(0.001);
1007 if sediment > capacity || delta_h > 0.0 {
1008 let deposit = if delta_h > 0.0 {
1010 delta_h.min(sediment)
1011 } else {
1012 (sediment - capacity) * deposit_speed
1013 };
1014 sediment -= deposit;
1015 let w0 = (1.0 - tx) * (1.0 - ty);
1017 let w1 = tx * (1.0 - ty);
1018 let w2 = (1.0 - tx) * ty;
1019 let w3 = tx * ty;
1020 let cur00 = map.get(xi, yi);
1021 let cur10 = map.get(xi + 1, yi);
1022 let cur01 = map.get(xi, yi + 1);
1023 let cur11 = map.get(xi + 1, yi + 1);
1024 map.set(xi, yi, (cur00 + deposit * w0).clamp(0.0, 1.0));
1025 map.set(xi + 1, yi, (cur10 + deposit * w1).clamp(0.0, 1.0));
1026 map.set(xi, yi + 1, (cur01 + deposit * w2).clamp(0.0, 1.0));
1027 map.set(xi + 1, yi + 1, (cur11 + deposit * w3).clamp(0.0, 1.0));
1028 } else {
1029 let erode_amount = (capacity - sediment) * erosion_speed;
1031 let erode_radius = 2usize;
1032 let mut total_weight = 0.0f32;
1033 let mut weights = [[0.0f32; 5]; 5];
1034 for dy in 0..=erode_radius * 2 {
1035 for dx in 0..=erode_radius * 2 {
1036 let ddx = dx as i32 - erode_radius as i32;
1037 let ddy = dy as i32 - erode_radius as i32;
1038 let w = (1.0 - (ddx * ddx + ddy * ddy) as f32 / (erode_radius * erode_radius + 1) as f32).max(0.0);
1039 weights[dy][dx] = w;
1040 total_weight += w;
1041 }
1042 }
1043 for dy in 0..=erode_radius * 2 {
1044 for dx in 0..=erode_radius * 2 {
1045 let ddx = dx as i32 - erode_radius as i32;
1046 let ddy = dy as i32 - erode_radius as i32;
1047 let nx = (xi as i32 + ddx) as usize;
1048 let ny = (yi as i32 + ddy) as usize;
1049 if nx < w && ny < h {
1050 let w = weights[dy][dx] / total_weight;
1051 let cur = map.get(nx, ny);
1052 map.set(nx, ny, (cur - erode_amount * w).clamp(0.0, 1.0));
1053 }
1054 }
1055 }
1056 sediment += erode_amount;
1057 }
1058
1059 water *= 1.0 - evaporation;
1060 if water < 0.001 { break; }
1061 px = new_px;
1062 py = new_py;
1063 }
1064 }
1065 }
1066}
1067
1068pub struct ThermalErosion;
1072
1073impl ThermalErosion {
1074 pub fn erode(map: &mut HeightMap, iterations: usize, talus_angle: f32) {
1076 let w = map.width;
1077 let h = map.height;
1078 let dirs: [(i32, i32); 4] = [(1, 0), (-1, 0), (0, 1), (0, -1)];
1079 for _ in 0..iterations {
1080 for y in 0..h {
1081 for x in 0..w {
1082 let h0 = map.get(x, y);
1083 let mut total_diff = 0.0f32;
1084 let mut max_diff = 0.0f32;
1085 for (dx, dy) in &dirs {
1086 let nx = x as i32 + dx;
1087 let ny = y as i32 + dy;
1088 if nx >= 0 && nx < w as i32 && ny >= 0 && ny < h as i32 {
1089 let nh = map.get(nx as usize, ny as usize);
1090 let diff = h0 - nh;
1091 if diff > talus_angle {
1092 total_diff += diff - talus_angle;
1093 if diff > max_diff { max_diff = diff; }
1094 }
1095 }
1096 }
1097 if total_diff <= 0.0 { continue; }
1098 for (dx, dy) in &dirs {
1099 let nx = x as i32 + dx;
1100 let ny = y as i32 + dy;
1101 if nx >= 0 && nx < w as i32 && ny >= 0 && ny < h as i32 {
1102 let nh = map.get(nx as usize, ny as usize);
1103 let diff = h0 - nh;
1104 if diff > talus_angle {
1105 let frac = (diff - talus_angle) / total_diff;
1106 let transfer = frac * (diff - talus_angle) * 0.5;
1107 let cur0 = map.get(x, y);
1108 let cur_n = map.get(nx as usize, ny as usize);
1109 map.set(x, y, (cur0 - transfer).clamp(0.0, 1.0));
1110 map.set(nx as usize, ny as usize, (cur_n + transfer).clamp(0.0, 1.0));
1111 }
1112 }
1113 }
1114 }
1115 }
1116 }
1117 }
1118}
1119
1120pub struct WindErosion;
1124
1125impl WindErosion {
1126 pub fn erode(map: &mut HeightMap, wind_dir: Vec2, iterations: usize) {
1129 let w = map.width;
1130 let h = map.height;
1131 let wind = wind_dir.normalize_or_zero();
1132 let step_x = wind.x;
1133 let step_y = wind.y;
1134 let saltation_dist = 3usize;
1135 let erosion_rate = 0.002f32;
1136 let deposition_rate = 0.003f32;
1137
1138 for _ in 0..iterations {
1139 let mut delta = vec![0.0f32; w * h];
1140 for y in 0..h {
1141 for x in 0..w {
1142 let h0 = map.get(x, y);
1143 let ux = x as f32 - step_x * saltation_dist as f32;
1145 let uy = y as f32 - step_y * saltation_dist as f32;
1146 if ux >= 0.0 && ux < w as f32 && uy >= 0.0 && uy < h as f32 {
1147 let upwind_h = map.sample_bilinear(ux, uy);
1148 if upwind_h > h0 {
1149 let source_xi = ux as usize;
1151 let source_yi = uy as usize;
1152 let erode = erosion_rate * (upwind_h - h0);
1153 if source_xi < w && source_yi < h {
1154 delta[source_yi * w + source_xi] -= erode;
1155 delta[y * w + x] += erode * (1.0 - deposition_rate);
1156 }
1157 }
1158 }
1159 }
1160 }
1161 for i in 0..(w * h) {
1162 map.data[i] = (map.data[i] + delta[i]).clamp(0.0, 1.0);
1163 }
1164 }
1165 map.blur(1);
1166 }
1167}
1168
1169#[cfg(test)]
1172mod tests {
1173 use super::*;
1174
1175 #[test]
1176 fn test_heightmap_new() {
1177 let m = HeightMap::new(64, 64);
1178 assert_eq!(m.data.len(), 64 * 64);
1179 assert!(m.data.iter().all(|&v| v == 0.0));
1180 }
1181
1182 #[test]
1183 fn test_heightmap_get_set() {
1184 let mut m = HeightMap::new(16, 16);
1185 m.set(3, 7, 0.75);
1186 assert_eq!(m.get(3, 7), 0.75);
1187 assert_eq!(m.get(100, 100), 0.0); }
1189
1190 #[test]
1191 fn test_bilinear_sampling() {
1192 let mut m = HeightMap::new(4, 4);
1193 m.set(1, 1, 1.0);
1194 let v = m.sample_bilinear(1.5, 1.5);
1195 assert!(v > 0.0 && v <= 1.0);
1196 }
1197
1198 #[test]
1199 fn test_cubic_sampling() {
1200 let mut m = HeightMap::new(8, 8);
1201 m.set(3, 3, 0.8);
1202 let v = m.sample_cubic(3.2, 3.2);
1203 assert!(v >= -0.5 && v <= 1.5);
1205 }
1206
1207 #[test]
1208 fn test_normalize() {
1209 let mut m = HeightMap::new(4, 4);
1210 for (i, v) in m.data.iter_mut().enumerate() { *v = i as f32; }
1211 m.normalize();
1212 let mn = m.min_value();
1213 let mx = m.max_value();
1214 assert!((mn - 0.0).abs() < 1e-5);
1215 assert!((mx - 1.0).abs() < 1e-5);
1216 }
1217
1218 #[test]
1219 fn test_blur() {
1220 let mut m = HeightMap::new(32, 32);
1221 m.data[16 * 32 + 16] = 1.0;
1222 m.blur(3);
1223 assert!(m.get(16, 16) < 1.0);
1225 assert!(m.get(15, 16) > 0.0);
1226 }
1227
1228 #[test]
1229 fn test_terrace() {
1230 let mut m = HeightMap::new(16, 16);
1231 for (i, v) in m.data.iter_mut().enumerate() { *v = i as f32 / (16 * 16) as f32; }
1232 m.terrace(4);
1233 let unique: Vec<f32> = {
1234 let mut vals: Vec<f32> = m.data.clone();
1235 vals.sort_by(|a, b| a.partial_cmp(b).unwrap());
1236 vals.dedup();
1237 vals
1238 };
1239 assert!(unique.len() <= 5, "terrace should produce at most levels+1 unique values");
1240 }
1241
1242 #[test]
1243 fn test_island_mask() {
1244 let mut m = HeightMap::new(64, 64);
1245 for v in m.data.iter_mut() { *v = 1.0; }
1246 m.island_mask(2.0);
1247 assert!(m.get(0, 0) < 0.1);
1248 assert!(m.get(32, 32) > 0.5);
1250 }
1251
1252 #[test]
1253 fn test_diamond_square() {
1254 let m = DiamondSquare::generate(64, 0.7, 42);
1255 assert_eq!(m.width, 65);
1256 assert_eq!(m.height, 65);
1257 let mn = m.min_value();
1258 let mx = m.max_value();
1259 assert!(mn >= 0.0 && mx <= 1.0);
1260 }
1261
1262 #[test]
1263 fn test_fractal_noise() {
1264 let m = FractalNoise::generate(64, 64, 6, 2.0, 0.5, 4.0, 12345);
1265 assert_eq!(m.data.len(), 64 * 64);
1266 assert!(m.min_value() >= 0.0);
1267 assert!(m.max_value() <= 1.0);
1268 }
1269
1270 #[test]
1271 fn test_voronoi_plates() {
1272 let m = VoronoiPlates::generate(64, 64, 8, 99);
1273 assert_eq!(m.data.len(), 64 * 64);
1274 let mn = m.min_value();
1275 let mx = m.max_value();
1276 assert!(mn >= 0.0 && mx <= 1.0);
1277 }
1278
1279 #[test]
1280 fn test_perlin_terrain() {
1281 let m = PerlinTerrain::generate(64, 64, 6, 4.0, 7);
1282 assert_eq!(m.data.len(), 64 * 64);
1283 let mn = m.min_value();
1284 let mx = m.max_value();
1285 assert!((mn - 0.0).abs() < 1e-4);
1286 assert!((mx - 1.0).abs() < 1e-4);
1287 }
1288
1289 #[test]
1290 fn test_hydraulic_erosion() {
1291 let mut m = DiamondSquare::generate(32, 0.8, 1);
1292 let before: f32 = m.data.iter().sum();
1293 HydraulicErosion::erode(&mut m, 500, 1.0, 8.0, 0.05, 77);
1294 let after: f32 = m.data.iter().sum();
1295 assert!(m.min_value() >= 0.0);
1298 assert!(m.max_value() <= 1.0);
1299 let _ = (before, after);
1300 }
1301
1302 #[test]
1303 fn test_thermal_erosion() {
1304 let mut m = DiamondSquare::generate(32, 0.9, 5);
1305 ThermalErosion::erode(&mut m, 20, 0.05);
1306 assert!(m.min_value() >= 0.0);
1307 assert!(m.max_value() <= 1.0);
1308 }
1309
1310 #[test]
1311 fn test_wind_erosion() {
1312 let mut m = DiamondSquare::generate(32, 0.7, 3);
1313 WindErosion::erode(&mut m, Vec2::new(1.0, 0.3), 10);
1314 assert!(m.min_value() >= 0.0);
1315 assert!(m.max_value() <= 1.0);
1316 }
1317
1318 #[test]
1319 fn test_slope_map() {
1320 let m = DiamondSquare::generate(32, 0.7, 42);
1321 let s = m.slope_map();
1322 assert_eq!(s.data.len(), m.data.len());
1323 assert!(s.min_value() >= 0.0);
1324 assert!(s.max_value() <= 1.0);
1325 }
1326
1327 #[test]
1328 fn test_raw_bytes_roundtrip() {
1329 let m = FractalNoise::generate(16, 16, 4, 2.0, 0.5, 3.0, 42);
1330 let bytes = m.to_raw_bytes();
1331 let m2 = HeightMap::from_raw_bytes(&bytes).unwrap();
1332 assert_eq!(m.width, m2.width);
1333 assert_eq!(m.height, m2.height);
1334 for (a, b) in m.data.iter().zip(m2.data.iter()) {
1335 assert!((a - b).abs() < 1e-6);
1336 }
1337 }
1338
1339 #[test]
1340 fn test_png_8bit() {
1341 let m = FractalNoise::generate(16, 16, 4, 2.0, 0.5, 3.0, 42);
1342 let png = m.to_png_8bit();
1343 assert!(png.len() > 8);
1345 assert_eq!(&png[..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
1346 }
1347
1348 #[test]
1349 fn test_normal_at() {
1350 let m = DiamondSquare::generate(32, 0.7, 42);
1351 let n = m.normal_at(16, 16);
1352 assert!((n.length() - 1.0).abs() < 1e-4);
1353 }
1354}
1355
1356impl HeightMap {
1359 pub fn mean(&self) -> f32 {
1361 if self.data.is_empty() { return 0.0; }
1362 self.data.iter().sum::<f32>() / self.data.len() as f32
1363 }
1364
1365 pub fn variance(&self) -> f32 {
1367 if self.data.is_empty() { return 0.0; }
1368 let mean = self.mean();
1369 self.data.iter().map(|&v| (v - mean).powi(2)).sum::<f32>() / self.data.len() as f32
1370 }
1371
1372 pub fn std_dev(&self) -> f32 { self.variance().sqrt() }
1374
1375 pub fn invert(&mut self) {
1377 for v in self.data.iter_mut() { *v = 1.0 - *v; }
1378 }
1379
1380 pub fn offset(&mut self, delta: f32) {
1382 for v in self.data.iter_mut() { *v = (*v + delta).clamp(0.0, 1.0); }
1383 }
1384
1385 pub fn scale(&mut self, factor: f32) {
1387 for v in self.data.iter_mut() { *v = (*v * factor).clamp(0.0, 1.0); }
1388 }
1389
1390 pub fn add(&mut self, other: &HeightMap, weight: f32) {
1392 assert_eq!(self.data.len(), other.data.len(), "HeightMap::add size mismatch");
1393 for (a, &b) in self.data.iter_mut().zip(other.data.iter()) {
1394 *a = (*a + b * weight).clamp(0.0, 1.0);
1395 }
1396 }
1397
1398 pub fn multiply(&mut self, other: &HeightMap) {
1400 assert_eq!(self.data.len(), other.data.len(), "HeightMap::multiply size mismatch");
1401 for (a, &b) in self.data.iter_mut().zip(other.data.iter()) {
1402 *a = (*a * b).clamp(0.0, 1.0);
1403 }
1404 }
1405
1406 pub fn percentile(&self, p: f32) -> f32 {
1408 if self.data.is_empty() { return 0.0; }
1409 let mut sorted = self.data.clone();
1410 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1411 let idx = ((p.clamp(0.0, 1.0)) * (sorted.len() - 1) as f32) as usize;
1412 sorted[idx]
1413 }
1414
1415 pub fn equalize(&mut self) {
1417 let n = self.data.len();
1418 if n == 0 { return; }
1419 let mut indexed: Vec<(f32, usize)> = self.data.iter().copied().enumerate().map(|(i, v)| (v, i)).collect();
1420 indexed.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
1421 for (rank, (_, idx)) in indexed.iter().enumerate() {
1422 self.data[*idx] = rank as f32 / (n - 1) as f32;
1423 }
1424 }
1425
1426 pub fn histogram(&self, bins: usize) -> Vec<usize> {
1428 let mut counts = vec![0usize; bins];
1429 for &v in &self.data {
1430 let bin = ((v.clamp(0.0, 1.0)) * (bins - 1) as f32) as usize;
1431 counts[bin] += 1;
1432 }
1433 counts
1434 }
1435
1436 pub fn simple_erode(&mut self, iterations: usize, threshold: f32, rate: f32) {
1439 let w = self.width;
1440 let h = self.height;
1441 let dirs: [(i32, i32); 4] = [(1,0),(-1,0),(0,1),(0,-1)];
1442 for _ in 0..iterations {
1443 for y in 0..h {
1444 for x in 0..w {
1445 let cur = self.get(x, y);
1446 let mut lowest_h = cur;
1447 let mut lowest_nx = x as i32;
1448 let mut lowest_ny = y as i32;
1449 for (dx, dy) in &dirs {
1450 let nx = x as i32 + dx;
1451 let ny = y as i32 + dy;
1452 if nx >= 0 && nx < w as i32 && ny >= 0 && ny < h as i32 {
1453 let nh = self.get(nx as usize, ny as usize);
1454 if nh < lowest_h {
1455 lowest_h = nh;
1456 lowest_nx = nx;
1457 lowest_ny = ny;
1458 }
1459 }
1460 }
1461 let diff = cur - lowest_h;
1462 if diff > threshold {
1463 let transfer = diff * rate;
1464 self.set(x, y, (cur - transfer).clamp(0.0, 1.0));
1465 self.set(lowest_nx as usize, lowest_ny as usize,
1466 (lowest_h + transfer).clamp(0.0, 1.0));
1467 }
1468 }
1469 }
1470 }
1471 }
1472
1473 pub fn to_color_bytes(&self) -> Vec<u8> {
1476 let ramp: &[(f32, (u8, u8, u8))] = &[
1477 (0.00, (0, 0, 80)),
1478 (0.10, (0, 50, 170)),
1479 (0.15, (60, 120, 200)),
1480 (0.20, (240, 220, 130)),
1481 (0.35, (80, 160, 40)),
1482 (0.55, (60, 130, 30)),
1483 (0.70, (120, 100, 80)),
1484 (0.85, (140, 140, 140)),
1485 (1.00, (250, 255, 255)),
1486 ];
1487 let mut pixels = Vec::with_capacity(self.data.len() * 3);
1488 for &v in &self.data {
1489 let (r, g, b) = sample_color_ramp(ramp, v.clamp(0.0, 1.0));
1490 pixels.push(r);
1491 pixels.push(g);
1492 pixels.push(b);
1493 }
1494 pixels
1495 }
1496
1497 pub fn ambient_occlusion(&self, radius: usize, samples: usize) -> HeightMap {
1500 let mut ao = HeightMap::new(self.width, self.height);
1501 let r = radius as f32;
1502 let angle_step = std::f32::consts::TAU / samples as f32;
1503 for y in 0..self.height {
1504 for x in 0..self.width {
1505 let h0 = self.get(x, y);
1506 let mut occluded = 0.0f32;
1507 for s in 0..samples {
1508 let angle = s as f32 * angle_step;
1509 let dx = angle.cos();
1510 let dy = angle.sin();
1511 let mut max_elev_angle = 0.0f32;
1512 for step in 1..=radius {
1513 let sx = x as f32 + dx * step as f32;
1514 let sy = y as f32 + dy * step as f32;
1515 if sx < 0.0 || sx >= self.width as f32 || sy < 0.0 || sy >= self.height as f32 { break; }
1516 let sh = self.sample_bilinear(sx, sy);
1517 let dist = step as f32;
1518 let elev = (sh - h0) / dist;
1519 if elev > max_elev_angle { max_elev_angle = elev; }
1520 }
1521 let horizon_angle = (max_elev_angle * r).atan() / std::f32::consts::FRAC_PI_2;
1522 occluded += horizon_angle.clamp(0.0, 1.0);
1523 }
1524 ao.set(x, y, 1.0 - (occluded / samples as f32).clamp(0.0, 1.0));
1525 }
1526 }
1527 ao
1528 }
1529
1530 pub fn ridge_mask(&self, threshold: f32) -> HeightMap {
1532 let mut out = HeightMap::new(self.width, self.height);
1533 for y in 1..(self.height - 1) {
1534 for x in 1..(self.width - 1) {
1535 let c = self.get(x, y);
1536 let ridge_x = self.get(x-1, y) < c && self.get(x+1, y) < c;
1537 let ridge_z = self.get(x, y-1) < c && self.get(x, y+1) < c;
1538 if (ridge_x || ridge_z) && c > threshold {
1539 out.set(x, y, c);
1540 }
1541 }
1542 }
1543 out
1544 }
1545
1546 pub fn valley_mask(&self, threshold: f32) -> HeightMap {
1548 let mut out = HeightMap::new(self.width, self.height);
1549 for y in 1..(self.height - 1) {
1550 for x in 1..(self.width - 1) {
1551 let c = self.get(x, y);
1552 let valley_x = self.get(x-1, y) > c && self.get(x+1, y) > c;
1553 let valley_z = self.get(x, y-1) > c && self.get(x, y+1) > c;
1554 if (valley_x || valley_z) && c < threshold {
1555 out.set(x, y, 1.0 - c);
1556 }
1557 }
1558 }
1559 out
1560 }
1561
1562 pub fn distance_field(&self, threshold: f32) -> HeightMap {
1565 let w = self.width;
1566 let h = self.height;
1567 let mut dist = vec![f32::INFINITY; w * h];
1568 let mut queue: VecDeque<(usize, usize)> = VecDeque::new();
1570 for y in 0..h {
1571 for x in 0..w {
1572 if self.get(x, y) >= threshold {
1573 dist[y * w + x] = 0.0;
1574 queue.push_back((x, y));
1575 }
1576 }
1577 }
1578 let dirs: [(i32, i32); 4] = [(1,0),(-1,0),(0,1),(0,-1)];
1579 while let Some((cx, cy)) = queue.pop_front() {
1580 let d = dist[cy * w + cx];
1581 for (dx, dy) in &dirs {
1582 let nx = cx as i32 + dx;
1583 let ny = cy as i32 + dy;
1584 if nx >= 0 && nx < w as i32 && ny >= 0 && ny < h as i32 {
1585 let ni = ny as usize * w + nx as usize;
1586 if dist[ni] > d + 1.0 {
1587 dist[ni] = d + 1.0;
1588 queue.push_back((nx as usize, ny as usize));
1589 }
1590 }
1591 }
1592 }
1593 let max_d = dist.iter().cloned().fold(0.0f32, f32::max);
1594 let data = if max_d > 0.0 {
1595 dist.iter().map(|&d| if d.is_infinite() { 1.0 } else { d / max_d }).collect()
1596 } else {
1597 vec![0.0; w * h]
1598 };
1599 HeightMap { width: w, height: h, data }
1600 }
1601
1602 pub fn paint_circle(&mut self, cx: f32, cy: f32, radius: f32, strength: f32, add: bool) {
1604 let r2 = radius * radius;
1605 let x0 = ((cx - radius).floor() as i32).max(0) as usize;
1606 let y0 = ((cy - radius).floor() as i32).max(0) as usize;
1607 let x1 = ((cx + radius).ceil() as i32).min(self.width as i32 - 1) as usize;
1608 let y1 = ((cy + radius).ceil() as i32).min(self.height as i32 - 1) as usize;
1609 for y in y0..=y1 {
1610 for x in x0..=x1 {
1611 let dx = x as f32 - cx;
1612 let dy = y as f32 - cy;
1613 let d2 = dx * dx + dy * dy;
1614 if d2 < r2 {
1615 let t = 1.0 - (d2 / r2).sqrt();
1616 let falloff = t * t * (3.0 - 2.0 * t); let delta = strength * falloff;
1618 let cur = self.get(x, y);
1619 self.set(x, y, if add { (cur + delta).clamp(0.0, 1.0) } else { (cur - delta).clamp(0.0, 1.0) });
1620 }
1621 }
1622 }
1623 }
1624
1625 pub fn flatten_circle(&mut self, cx: f32, cy: f32, radius: f32, target: f32, strength: f32) {
1627 let r2 = radius * radius;
1628 let x0 = ((cx - radius).floor() as i32).max(0) as usize;
1629 let y0 = ((cy - radius).floor() as i32).max(0) as usize;
1630 let x1 = ((cx + radius).ceil() as i32).min(self.width as i32 - 1) as usize;
1631 let y1 = ((cy + radius).ceil() as i32).min(self.height as i32 - 1) as usize;
1632 for y in y0..=y1 {
1633 for x in x0..=x1 {
1634 let dx = x as f32 - cx;
1635 let dy = y as f32 - cy;
1636 if dx * dx + dy * dy < r2 {
1637 let cur = self.get(x, y);
1638 self.set(x, y, (cur + (target - cur) * strength).clamp(0.0, 1.0));
1639 }
1640 }
1641 }
1642 }
1643
1644 pub fn resample(&self, new_width: usize, new_height: usize) -> HeightMap {
1646 let mut out = HeightMap::new(new_width, new_height);
1647 let sx = (self.width - 1) as f32 / (new_width - 1).max(1) as f32;
1648 let sy = (self.height - 1) as f32 / (new_height - 1).max(1) as f32;
1649 for y in 0..new_height {
1650 for x in 0..new_width {
1651 out.set(x, y, self.sample_bilinear(x as f32 * sx, y as f32 * sy));
1652 }
1653 }
1654 out
1655 }
1656
1657 pub fn crop(&self, x0: usize, y0: usize, x1: usize, y1: usize) -> HeightMap {
1659 let nw = x1.saturating_sub(x0).min(self.width - x0);
1660 let nh = y1.saturating_sub(y0).min(self.height - y0);
1661 let mut out = HeightMap::new(nw, nh);
1662 for y in 0..nh {
1663 for x in 0..nw {
1664 out.set(x, y, self.get(x0 + x, y0 + y));
1665 }
1666 }
1667 out
1668 }
1669
1670 pub fn tile_horizontal(&self, other: &HeightMap) -> HeightMap {
1672 assert_eq!(self.height, other.height, "heights must match for horizontal tiling");
1673 let nw = self.width + other.width;
1674 let mut out = HeightMap::new(nw, self.height);
1675 for y in 0..self.height {
1676 for x in 0..self.width {
1677 out.set(x, y, self.get(x, y));
1678 }
1679 for x in 0..other.width {
1680 out.set(self.width + x, y, other.get(x, y));
1681 }
1682 }
1683 out
1684 }
1685}
1686
1687fn sample_color_ramp(ramp: &[(f32, (u8, u8, u8))], t: f32) -> (u8, u8, u8) {
1688 if ramp.is_empty() { return (128, 128, 128); }
1689 if t <= ramp[0].0 { return ramp[0].1; }
1690 if t >= ramp[ramp.len()-1].0 { return ramp[ramp.len()-1].1; }
1691 for i in 0..ramp.len()-1 {
1692 let (t0, c0) = ramp[i];
1693 let (t1, c1) = ramp[i+1];
1694 if t >= t0 && t <= t1 {
1695 let f = (t - t0) / (t1 - t0);
1696 let lerp_c = |a: u8, b: u8| -> u8 { (a as f32 + (b as f32 - a as f32) * f) as u8 };
1697 return (lerp_c(c0.0, c1.0), lerp_c(c0.1, c1.1), lerp_c(c0.2, c1.2));
1698 }
1699 }
1700 ramp[ramp.len()-1].1
1701}
1702
1703use std::collections::VecDeque;
1704
1705pub struct WorleyTerrain;
1710
1711impl WorleyTerrain {
1712 pub fn generate(width: usize, height: usize, num_points: usize, seed: u64) -> HeightMap {
1715 let mut rng = Rng::new(seed);
1716 let points: Vec<(f32, f32)> = (0..num_points)
1717 .map(|_| (rng.next_f32() * width as f32, rng.next_f32() * height as f32))
1718 .collect();
1719 let mut map = HeightMap::new(width, height);
1720 let max_dist = (width * width + height * height) as f32;
1721 for y in 0..height {
1722 for x in 0..width {
1723 let px = x as f32;
1724 let py = y as f32;
1725 let mut d1 = f32::INFINITY;
1726 let mut d2 = f32::INFINITY;
1727 for &(qx, qy) in &points {
1728 let dx = px - qx;
1729 let dy = py - qy;
1730 let d = dx * dx + dy * dy;
1731 if d < d1 { d2 = d1; d1 = d; }
1732 else if d < d2 { d2 = d; }
1733 }
1734 let val = ((d2.sqrt() - d1.sqrt()) / width.min(height) as f32).clamp(0.0, 1.0);
1736 map.set(x, y, val);
1737 }
1738 }
1739 map.normalize();
1740 map
1741 }
1742
1743 pub fn generate_inverted(width: usize, height: usize, num_points: usize, seed: u64) -> HeightMap {
1745 let mut m = Self::generate(width, height, num_points, seed);
1746 m.invert();
1747 m
1748 }
1749}
1750
1751pub struct HeightMapFilter;
1755
1756impl HeightMapFilter {
1757 pub fn high_pass(map: &HeightMap, radius: usize) -> HeightMap {
1759 let mut low = map.clone();
1760 low.blur(radius);
1761 let mut out = map.clone();
1762 for i in 0..map.data.len() {
1763 out.data[i] = (map.data[i] - low.data[i] + 0.5).clamp(0.0, 1.0);
1764 }
1765 out
1766 }
1767
1768 pub fn low_pass(map: &HeightMap, radius: usize) -> HeightMap {
1770 let mut out = map.clone();
1771 out.blur(radius);
1772 out
1773 }
1774
1775 pub fn band_pass(map: &HeightMap, low_radius: usize, high_radius: usize) -> HeightMap {
1777 let lp = Self::low_pass(map, low_radius);
1778 let hp = Self::high_pass(map, high_radius);
1779 let mut out = HeightMap::new(map.width, map.height);
1780 for i in 0..map.data.len() {
1781 out.data[i] = ((lp.data[i] + hp.data[i]) * 0.5).clamp(0.0, 1.0);
1782 }
1783 out
1784 }
1785
1786 pub fn emboss(map: &HeightMap) -> HeightMap {
1788 let w = map.width;
1789 let h = map.height;
1790 let mut out = HeightMap::new(w, h);
1791 for y in 1..(h-1) {
1792 for x in 1..(w-1) {
1793 let tl = map.get(x-1, y-1);
1794 let br = map.get(x+1, y+1);
1795 let emboss = (br - tl + 1.0) * 0.5;
1796 out.set(x, y, emboss.clamp(0.0, 1.0));
1797 }
1798 }
1799 out
1800 }
1801
1802 pub fn morphological_erosion(map: &HeightMap, radius: usize) -> HeightMap {
1804 let mut out = HeightMap::new(map.width, map.height);
1805 for y in 0..map.height {
1806 for x in 0..map.width {
1807 let mut mn = 1.0f32;
1808 for dy in -(radius as i32)..=(radius as i32) {
1809 for dx in -(radius as i32)..=(radius as i32) {
1810 let nx = (x as i32 + dx).clamp(0, map.width as i32 - 1) as usize;
1811 let ny = (y as i32 + dy).clamp(0, map.height as i32 - 1) as usize;
1812 mn = mn.min(map.get(nx, ny));
1813 }
1814 }
1815 out.set(x, y, mn);
1816 }
1817 }
1818 out
1819 }
1820
1821 pub fn morphological_dilation(map: &HeightMap, radius: usize) -> HeightMap {
1823 let mut out = HeightMap::new(map.width, map.height);
1824 for y in 0..map.height {
1825 for x in 0..map.width {
1826 let mut mx = 0.0f32;
1827 for dy in -(radius as i32)..=(radius as i32) {
1828 for dx in -(radius as i32)..=(radius as i32) {
1829 let nx = (x as i32 + dx).clamp(0, map.width as i32 - 1) as usize;
1830 let ny = (y as i32 + dy).clamp(0, map.height as i32 - 1) as usize;
1831 mx = mx.max(map.get(nx, ny));
1832 }
1833 }
1834 out.set(x, y, mx);
1835 }
1836 }
1837 out
1838 }
1839
1840 pub fn morphological_open(map: &HeightMap, radius: usize) -> HeightMap {
1842 let eroded = Self::morphological_erosion(map, radius);
1843 Self::morphological_dilation(&eroded, radius)
1844 }
1845
1846 pub fn morphological_close(map: &HeightMap, radius: usize) -> HeightMap {
1848 let dilated = Self::morphological_dilation(map, radius);
1849 Self::morphological_erosion(&dilated, radius)
1850 }
1851}
1852
1853pub struct TectonicSimulation {
1857 pub width: usize,
1858 pub height: usize,
1859 pub num_plates: usize,
1860 pub seed: u64,
1861 pub heightmap: HeightMap,
1863 plate_ids: Vec<usize>,
1865 plate_elevations: Vec<f32>,
1867 plate_velocities: Vec<(f32, f32)>,
1869 stress: Vec<f32>,
1871}
1872
1873impl TectonicSimulation {
1874 pub fn new(width: usize, height: usize, num_plates: usize, seed: u64) -> Self {
1876 let mut rng = Rng::new(seed);
1877 let centers: Vec<(f32, f32)> = (0..num_plates)
1878 .map(|_| (rng.next_f32() * width as f32, rng.next_f32() * height as f32))
1879 .collect();
1880 let plate_elevations: Vec<f32> = (0..num_plates)
1881 .map(|_| if rng.next_f32() < 0.45 { rng.next_f32_range(0.0, 0.3) }
1882 else { rng.next_f32_range(0.4, 0.65) })
1883 .collect();
1884 let plate_velocities: Vec<(f32, f32)> = (0..num_plates)
1885 .map(|_| {
1886 let a = rng.next_f32() * std::f32::consts::TAU;
1887 (a.cos() * 0.3, a.sin() * 0.3)
1888 })
1889 .collect();
1890 let mut plate_ids = vec![0usize; width * height];
1892 for y in 0..height {
1893 for x in 0..width {
1894 let mut best = 0;
1895 let mut best_d = f32::INFINITY;
1896 for (i, &(cx, cy)) in centers.iter().enumerate() {
1897 let d = (x as f32 - cx).powi(2) + (y as f32 - cy).powi(2);
1898 if d < best_d { best_d = d; best = i; }
1899 }
1900 plate_ids[y * width + x] = best;
1901 }
1902 }
1903 let stress = vec![0.0f32; width * height];
1904 let heightmap = HeightMap::new(width, height);
1905 Self { width, height, num_plates, seed, heightmap, plate_ids, plate_elevations, plate_velocities, stress }
1906 }
1907
1908 pub fn step(&mut self) {
1910 let w = self.width;
1911 let h = self.height;
1912 for y in 0..h {
1914 for x in 0..w {
1915 let pid = self.plate_ids[y * w + x];
1916 let base = self.plate_elevations[pid];
1917 let stress = self.stress[y * w + x];
1918 self.heightmap.data[y * w + x] = (base + stress * 0.3).clamp(0.0, 1.0);
1919 }
1920 }
1921 let dirs: [(i32, i32); 4] = [(1,0),(-1,0),(0,1),(0,-1)];
1923 for y in 0..h {
1924 for x in 0..w {
1925 let pid = self.plate_ids[y * w + x];
1926 let pv = self.plate_velocities[pid];
1927 let mut new_stress = self.stress[y * w + x] * 0.95;
1928 for (dx, dy) in &dirs {
1929 let nx = x as i32 + dx;
1930 let ny = y as i32 + dy;
1931 if nx >= 0 && nx < w as i32 && ny >= 0 && ny < h as i32 {
1932 let npid = self.plate_ids[ny as usize * w + nx as usize];
1933 if npid != pid {
1934 let nv = self.plate_velocities[npid];
1935 let rel_vx = pv.0 - nv.0;
1937 let rel_vy = pv.1 - nv.1;
1938 let boundary_nx = *dx as f32;
1939 let boundary_ny = *dy as f32;
1940 let convergence = rel_vx * boundary_nx + rel_vy * boundary_ny;
1941 if convergence < 0.0 {
1942 new_stress += (-convergence) * 0.05;
1944 }
1945 }
1946 }
1947 }
1948 self.stress[y * w + x] = new_stress.clamp(0.0, 1.0);
1949 }
1950 }
1951 }
1952
1953 pub fn simulate(&mut self, steps: usize) -> &HeightMap {
1955 for _ in 0..steps {
1956 self.step();
1957 }
1958 self.heightmap.normalize();
1959 &self.heightmap
1960 }
1961}
1962
1963pub struct AdvancedHydraulicErosion;
1967
1968impl AdvancedHydraulicErosion {
1969 pub fn erode_full(
1971 map: &mut HeightMap,
1972 iterations: usize,
1973 rain_per_cell: f32,
1974 evaporation_rate: f32,
1975 erosion_rate: f32,
1976 deposition_rate: f32,
1977 seed: u64,
1978 ) {
1979 let w = map.width;
1980 let h = map.height;
1981 let mut water = vec![0.0f32; w * h];
1982 let mut sediment = vec![0.0f32; w * h];
1983 let mut rng = Rng::new(seed);
1984 let dirs: [(i32, i32); 4] = [(1,0),(-1,0),(0,1),(0,-1)];
1985
1986 for _iter in 0..iterations {
1987 for cell in water.iter_mut() {
1989 *cell += rain_per_cell * rng.next_f32_range(0.5, 1.5);
1990 }
1991
1992 let terrain_plus_water: Vec<f32> = (0..w*h)
1994 .map(|i| map.data[i] + water[i])
1995 .collect();
1996
1997 let mut d_water = vec![0.0f32; w * h];
1998 let mut d_sediment = vec![0.0f32; w * h];
1999
2000 for y in 0..h {
2001 for x in 0..w {
2002 let idx = y * w + x;
2003 let h_cur = terrain_plus_water[idx];
2004 let w_cur = water[idx];
2005 if w_cur < 0.0001 { continue; }
2006
2007 let mut total_flow = 0.0f32;
2008 let mut flows = [(0.0f32, 0i32, 0i32); 4];
2009 for (k, (dx, dy)) in dirs.iter().enumerate() {
2010 let nx = x as i32 + dx;
2011 let ny = y as i32 + dy;
2012 if nx < 0 || nx >= w as i32 || ny < 0 || ny >= h as i32 { continue; }
2013 let nidx = ny as usize * w + nx as usize;
2014 let h_n = terrain_plus_water[nidx];
2015 let diff = h_cur - h_n;
2016 if diff > 0.0 {
2017 flows[k] = (diff, nx, ny);
2018 total_flow += diff;
2019 }
2020 }
2021 if total_flow < 0.0001 { continue; }
2022
2023 for (diff, nx, ny) in flows.iter() {
2024 if *diff <= 0.0 { continue; }
2025 let frac = diff / total_flow;
2026 let flow_w = (w_cur * frac * 0.5).min(w_cur);
2027 let flow_s = sediment[idx] * frac * 0.5;
2028 let nidx = *ny as usize * w + *nx as usize;
2029 d_water[idx] -= flow_w;
2030 d_water[nidx] += flow_w;
2031 d_sediment[idx] -= flow_s;
2032 d_sediment[nidx] += flow_s;
2033
2034 let erode = erosion_rate * flow_w * diff;
2036 map.data[idx] = (map.data[idx] - erode).clamp(0.0, 1.0);
2037 d_sediment[idx] += erode;
2038 }
2039 }
2040 }
2041
2042 for i in 0..(w*h) {
2044 water[i] = (water[i] + d_water[i]).max(0.0);
2045 sediment[i] = (sediment[i] + d_sediment[i]).max(0.0);
2046 }
2047
2048 for i in 0..(w*h) {
2050 water[i] *= 1.0 - evaporation_rate;
2051 let deposit = sediment[i] * deposition_rate;
2052 sediment[i] -= deposit;
2053 map.data[i] = (map.data[i] + deposit).clamp(0.0, 1.0);
2054 }
2055 }
2056 }
2057}
2058
2059pub struct HeightMapCompositor {
2063 layers: Vec<CompositeLayer>,
2064}
2065
2066pub struct CompositeLayer {
2068 pub map: HeightMap,
2069 pub blend: CompositeBlendMode,
2070 pub weight: f32,
2071 pub mask: Option<HeightMap>,
2072}
2073
2074#[derive(Clone, Copy, Debug)]
2076pub enum CompositeBlendMode {
2077 Normal,
2078 Add,
2079 Subtract,
2080 Multiply,
2081 Screen,
2082 Overlay,
2083 Max,
2084 Min,
2085}
2086
2087impl HeightMapCompositor {
2088 pub fn new() -> Self { Self { layers: Vec::new() } }
2089
2090 pub fn add_layer(&mut self, map: HeightMap, blend: CompositeBlendMode, weight: f32) {
2091 self.layers.push(CompositeLayer { map, blend, weight, mask: None });
2092 }
2093
2094 pub fn add_layer_with_mask(&mut self, map: HeightMap, mask: HeightMap, blend: CompositeBlendMode, weight: f32) {
2095 self.layers.push(CompositeLayer { map, blend, weight, mask: Some(mask) });
2096 }
2097
2098 pub fn flatten(&self) -> Option<HeightMap> {
2100 if self.layers.is_empty() { return None; }
2101 let w = self.layers[0].map.width;
2102 let h = self.layers[0].map.height;
2103 let mut result = HeightMap::new(w, h);
2104
2105 for layer in &self.layers {
2106 let lm = &layer.map;
2107 for y in 0..h {
2108 for x in 0..w {
2109 let src = lm.get(x.min(lm.width-1), y.min(lm.height-1));
2110 let dst = result.get(x, y);
2111 let mask_w = layer.mask.as_ref()
2112 .map(|m| m.get(x.min(m.width-1), y.min(m.height-1)))
2113 .unwrap_or(1.0);
2114 let blended = Self::blend(dst, src, layer.blend);
2115 let final_v = dst + (blended - dst) * layer.weight * mask_w;
2116 result.set(x, y, final_v.clamp(0.0, 1.0));
2117 }
2118 }
2119 }
2120 Some(result)
2121 }
2122
2123 fn blend(dst: f32, src: f32, mode: CompositeBlendMode) -> f32 {
2124 match mode {
2125 CompositeBlendMode::Normal => src,
2126 CompositeBlendMode::Add => (dst + src).clamp(0.0, 1.0),
2127 CompositeBlendMode::Subtract => (dst - src).clamp(0.0, 1.0),
2128 CompositeBlendMode::Multiply => dst * src,
2129 CompositeBlendMode::Screen => 1.0 - (1.0 - dst) * (1.0 - src),
2130 CompositeBlendMode::Overlay => {
2131 if dst < 0.5 { 2.0 * dst * src }
2132 else { 1.0 - 2.0 * (1.0 - dst) * (1.0 - src) }
2133 }
2134 CompositeBlendMode::Max => dst.max(src),
2135 CompositeBlendMode::Min => dst.min(src),
2136 }
2137 }
2138}
2139
2140pub struct DisplacementWarp {
2144 pub warp_x: HeightMap,
2146 pub warp_y: HeightMap,
2148 pub strength: f32,
2150}
2151
2152impl DisplacementWarp {
2153 pub fn from_noise(width: usize, height: usize, strength: f32, seed: u64) -> Self {
2155 let warp_x = FractalNoise::generate(width, height, 4, 2.0, 0.5, 3.0, seed);
2156 let warp_y = FractalNoise::generate(width, height, 4, 2.0, 0.5, 3.0, seed.wrapping_add(137));
2157 Self { warp_x, warp_y, strength }
2158 }
2159
2160 pub fn apply(&self, map: &HeightMap) -> HeightMap {
2162 let w = map.width;
2163 let h = map.height;
2164 let mut out = HeightMap::new(w, h);
2165 for y in 0..h {
2166 for x in 0..w {
2167 let wx = (self.warp_x.get(x, y) * 2.0 - 1.0) * self.strength;
2168 let wy = (self.warp_y.get(x, y) * 2.0 - 1.0) * self.strength;
2169 let sx = (x as f32 + wx).clamp(0.0, w as f32 - 1.0);
2170 let sy = (y as f32 + wy).clamp(0.0, h as f32 - 1.0);
2171 out.set(x, y, map.sample_bilinear(sx, sy));
2172 }
2173 }
2174 out
2175 }
2176}
2177
2178#[cfg(test)]
2181mod extended_tests {
2182 use super::*;
2183
2184 #[test]
2185 fn test_heightmap_mean_variance() {
2186 let mut m = HeightMap::new(4, 4);
2187 for v in m.data.iter_mut() { *v = 0.5; }
2188 assert!((m.mean() - 0.5).abs() < 1e-5);
2189 assert!(m.variance().abs() < 1e-5);
2190 }
2191
2192 #[test]
2193 fn test_heightmap_invert() {
2194 let mut m = HeightMap::new(4, 4);
2195 for v in m.data.iter_mut() { *v = 0.3; }
2196 m.invert();
2197 assert!((m.data[0] - 0.7).abs() < 1e-5);
2198 }
2199
2200 #[test]
2201 fn test_heightmap_add_multiply() {
2202 let mut a = HeightMap::new(4, 4);
2203 let mut b = HeightMap::new(4, 4);
2204 for v in a.data.iter_mut() { *v = 0.4; }
2205 for v in b.data.iter_mut() { *v = 0.3; }
2206 a.add(&b, 1.0);
2207 assert!((a.data[0] - 0.7).abs() < 0.01);
2208 let mut c = HeightMap::new(4, 4);
2209 let mut d = HeightMap::new(4, 4);
2210 for v in c.data.iter_mut() { *v = 0.5; }
2211 for v in d.data.iter_mut() { *v = 0.4; }
2212 c.multiply(&d);
2213 assert!((c.data[0] - 0.2).abs() < 0.01);
2214 }
2215
2216 #[test]
2217 fn test_heightmap_equalize() {
2218 let mut m = HeightMap::new(8, 8);
2219 for (i, v) in m.data.iter_mut().enumerate() { *v = (i % 4) as f32 * 0.1; }
2220 m.equalize();
2221 let mn = m.min_value();
2222 let mx = m.max_value();
2223 assert!((mn - 0.0).abs() < 1e-4);
2224 assert!((mx - 1.0).abs() < 1e-4);
2225 }
2226
2227 #[test]
2228 fn test_heightmap_histogram() {
2229 let mut m = HeightMap::new(16, 16);
2230 m.normalize(); for v in m.data.iter_mut() { *v = 0.0; }
2232 let hist = m.histogram(10);
2233 assert_eq!(hist[0], 256); assert_eq!(hist.iter().sum::<usize>(), 256);
2235 }
2236
2237 #[test]
2238 fn test_worley_terrain() {
2239 let m = WorleyTerrain::generate(32, 32, 20, 42);
2240 assert_eq!(m.data.len(), 32 * 32);
2241 let mn = m.min_value();
2242 let mx = m.max_value();
2243 assert!((mn - 0.0).abs() < 1e-4);
2244 assert!((mx - 1.0).abs() < 1e-4);
2245 }
2246
2247 #[test]
2248 fn test_heightmap_filter_high_pass() {
2249 let m = DiamondSquare::generate(32, 0.7, 42);
2250 let hp = HeightMapFilter::high_pass(&m, 3);
2251 assert_eq!(hp.data.len(), m.data.len());
2252 }
2253
2254 #[test]
2255 fn test_heightmap_filter_morphological() {
2256 let m = DiamondSquare::generate(16, 0.7, 42);
2257 let eroded = HeightMapFilter::morphological_erosion(&m, 1);
2258 assert!(eroded.max_value() <= m.max_value() + 1e-5);
2260 }
2261
2262 #[test]
2263 fn test_tectonic_simulation() {
2264 let mut sim = TectonicSimulation::new(32, 32, 6, 42);
2265 sim.simulate(5);
2266 assert_eq!(sim.heightmap.data.len(), 32 * 32);
2267 let mn = sim.heightmap.min_value();
2268 let mx = sim.heightmap.max_value();
2269 assert!(mn >= 0.0 && mx <= 1.0);
2270 }
2271
2272 #[test]
2273 fn test_heightmap_resample() {
2274 let m = DiamondSquare::generate(32, 0.7, 42);
2275 let r = m.resample(16, 16);
2276 assert_eq!(r.width, 16);
2277 assert_eq!(r.height, 16);
2278 }
2279
2280 #[test]
2281 fn test_heightmap_crop() {
2282 let m = DiamondSquare::generate(32, 0.7, 42);
2283 let c = m.crop(8, 8, 24, 24);
2284 assert_eq!(c.width, 16);
2285 assert_eq!(c.height, 16);
2286 }
2287
2288 #[test]
2289 fn test_heightmap_distance_field() {
2290 let mut m = HeightMap::new(16, 16);
2291 m.set(8, 8, 1.0); let df = m.distance_field(0.5);
2293 assert_eq!(df.get(8, 8), 0.0);
2294 assert!(df.get(0, 0) > 0.0);
2295 }
2296
2297 #[test]
2298 fn test_heightmap_paint_circle() {
2299 let mut m = HeightMap::new(64, 64);
2300 m.paint_circle(32.0, 32.0, 10.0, 0.5, true);
2301 assert!(m.get(32, 32) > 0.0);
2302 assert_eq!(m.get(0, 0), 0.0);
2303 }
2304
2305 #[test]
2306 fn test_compositor_flatten() {
2307 let a = FractalNoise::generate(16, 16, 4, 2.0, 0.5, 3.0, 42);
2308 let b = FractalNoise::generate(16, 16, 4, 2.0, 0.5, 3.0, 99);
2309 let mut comp = HeightMapCompositor::new();
2310 comp.add_layer(a, CompositeBlendMode::Normal, 0.5);
2311 comp.add_layer(b, CompositeBlendMode::Add, 0.3);
2312 let result = comp.flatten().expect("compositor should produce output");
2313 assert_eq!(result.data.len(), 16 * 16);
2314 }
2315
2316 #[test]
2317 fn test_displacement_warp() {
2318 let m = FractalNoise::generate(32, 32, 4, 2.0, 0.5, 3.0, 42);
2319 let warp = DisplacementWarp::from_noise(32, 32, 5.0, 77);
2320 let warped = warp.apply(&m);
2321 assert_eq!(warped.data.len(), m.data.len());
2322 }
2323
2324 #[test]
2325 fn test_advanced_hydraulic_erosion() {
2326 let mut m = DiamondSquare::generate(32, 0.8, 1);
2327 AdvancedHydraulicErosion::erode_full(&mut m, 50, 0.01, 0.02, 0.01, 0.05, 42);
2328 assert!(m.min_value() >= 0.0);
2329 assert!(m.max_value() <= 1.0);
2330 }
2331
2332 #[test]
2333 fn test_color_bytes() {
2334 let m = FractalNoise::generate(16, 16, 4, 2.0, 0.5, 3.0, 42);
2335 let bytes = m.to_color_bytes();
2336 assert_eq!(bytes.len(), 16 * 16 * 3);
2337 }
2338
2339 #[test]
2340 fn test_ambient_occlusion() {
2341 let m = DiamondSquare::generate(16, 0.7, 42);
2342 let ao = m.ambient_occlusion(4, 8);
2343 assert_eq!(ao.data.len(), m.data.len());
2344 assert!(ao.min_value() >= 0.0 && ao.max_value() <= 1.0);
2345 }
2346}