1use glam::{Vec2, Vec3};
12use std::f32::consts::{PI, TAU};
13
14#[derive(Debug, Clone)]
20pub enum NoiseSource {
21 Perlin {
23 frequency: f32,
24 amplitude: f32,
25 seed: u32,
26 },
27 Fbm {
29 frequency: f32,
30 amplitude: f32,
31 octaves: u32,
32 lacunarity: f32,
33 persistence: f32,
34 seed: u32,
35 },
36 Ridged {
38 frequency: f32,
39 amplitude: f32,
40 octaves: u32,
41 lacunarity: f32,
42 gain: f32,
43 offset: f32,
44 seed: u32,
45 },
46 DomainWarped {
48 base: Box<NoiseSource>,
49 warp_frequency: f32,
50 warp_amplitude: f32,
51 warp_seed: u32,
52 },
53 Flat {
55 height: f32,
56 },
57 Sinusoidal {
59 frequency_x: f32,
60 frequency_z: f32,
61 amplitude: f32,
62 },
63 Composite {
65 sources: Vec<NoiseSource>,
66 },
67}
68
69impl Default for NoiseSource {
70 fn default() -> Self {
71 NoiseSource::Fbm {
72 frequency: 0.02,
73 amplitude: 30.0,
74 octaves: 6,
75 lacunarity: 2.0,
76 persistence: 0.5,
77 seed: 42,
78 }
79 }
80}
81
82const PERM: [u8; 256] = [
88 151,160,137, 91, 90, 15,131, 13,201, 95, 96, 53,194,233, 7,225,
89 140, 36,103, 30, 69,142, 8, 99, 37,240, 21, 10, 23,190, 6,148,
90 247,120,234, 75, 0, 26,197, 62, 94,252,219,203,117, 35, 11, 32,
91 57,177, 33, 88,237,149, 56, 87,174, 20,125,136,171,168, 68,175,
92 74,165, 71,134,139, 48, 27,166, 77,146,158,231, 83,111,229,122,
93 60,211,133,230,220,105, 92, 41, 55, 46,245, 40,244,102,143, 54,
94 65, 25, 63,161, 1,216, 80, 73,209, 76,132,187,208, 89, 18,169,
95 200,196,135,130,116,188,159, 86,164,100,109,198,173,186, 3, 64,
96 52,217,226,250,124,123, 5,202, 38,147,118,126,255, 82, 85,212,
97 207,206, 59,227, 47, 16, 58, 17,182,189, 28, 42,223,183,170,213,
98 119,248,152, 2, 44,154,163, 70,221,153,101,155,167, 43,172, 9,
99 129, 22, 39,253, 19, 98,108,110, 79,113,224,232,178,185,112,104,
100 218,246, 97,228,251, 34,242,193,238,210,144, 12,191,179,162,241,
101 81, 51,145,235,249, 14,239,107, 49,192,214, 31,181,199,106,157,
102 184, 84,204,176,115,121, 50, 45,127, 4,150,254,138,236,205, 93,
103 222,114, 67, 29, 24, 72,243,141,128,195, 78, 66,215, 61,156,180,
104];
105
106#[inline(always)]
107fn perm(i: i32) -> usize {
108 PERM[((i % 256 + 256) % 256) as usize] as usize
109}
110
111#[inline(always)]
112fn perm_seeded(i: i32, seed: u32) -> usize {
113 PERM[(((i.wrapping_add(seed as i32)) % 256 + 256) % 256) as usize] as usize
114}
115
116#[inline(always)]
117fn fade(t: f32) -> f32 {
118 t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
119}
120
121#[inline(always)]
122fn lerp(a: f32, b: f32, t: f32) -> f32 {
123 a + t * (b - a)
124}
125
126#[inline(always)]
127fn grad2(hash: usize, x: f32, y: f32) -> f32 {
128 match hash & 3 {
129 0 => x + y,
130 1 => -x + y,
131 2 => x - y,
132 _ => -x - y,
133 }
134}
135
136fn perlin2_seeded(x: f32, y: f32, seed: u32) -> f32 {
138 let xi = x.floor() as i32;
139 let yi = y.floor() as i32;
140 let xf = x - x.floor();
141 let yf = y - y.floor();
142 let u = fade(xf);
143 let v = fade(yf);
144
145 let aa = perm_seeded(perm_seeded(xi, seed) as i32 + yi, seed);
146 let ba = perm_seeded(perm_seeded(xi + 1, seed) as i32 + yi, seed);
147 let ab = perm_seeded(perm_seeded(xi, seed) as i32 + yi + 1, seed);
148 let bb = perm_seeded(perm_seeded(xi + 1, seed) as i32 + yi + 1, seed);
149
150 lerp(
151 lerp(grad2(aa, xf, yf), grad2(ba, xf - 1.0, yf), u),
152 lerp(grad2(ab, xf, yf - 1.0), grad2(bb, xf - 1.0, yf - 1.0), u),
153 v,
154 )
155}
156
157impl NoiseSource {
158 pub fn sample(&self, x: f32, z: f32) -> f32 {
160 match self {
161 NoiseSource::Perlin { frequency, amplitude, seed } => {
162 perlin2_seeded(x * frequency, z * frequency, *seed) * amplitude
163 }
164 NoiseSource::Fbm { frequency, amplitude, octaves, lacunarity, persistence, seed } => {
165 let mut value = 0.0_f32;
166 let mut freq = *frequency;
167 let mut amp = *amplitude;
168 for oct in 0..*octaves {
169 let s = seed.wrapping_add(oct * 31);
170 value += perlin2_seeded(x * freq, z * freq, s) * amp;
171 freq *= lacunarity;
172 amp *= persistence;
173 }
174 value
175 }
176 NoiseSource::Ridged { frequency, amplitude, octaves, lacunarity, gain, offset, seed } => {
177 let mut value = 0.0_f32;
178 let mut freq = *frequency;
179 let mut amp = *amplitude;
180 let mut weight = 1.0_f32;
181 for oct in 0..*octaves {
182 let s = seed.wrapping_add(oct * 31);
183 let signal = perlin2_seeded(x * freq, z * freq, s).abs();
184 let signal = offset - signal;
185 let signal = signal * signal * weight;
186 weight = (signal * gain).clamp(0.0, 1.0);
187 value += signal * amp;
188 freq *= lacunarity;
189 amp *= 0.5;
190 }
191 value
192 }
193 NoiseSource::DomainWarped { base, warp_frequency, warp_amplitude, warp_seed } => {
194 let wx = perlin2_seeded(x * warp_frequency, z * warp_frequency, *warp_seed)
195 * warp_amplitude;
196 let wz = perlin2_seeded(
197 (x + 100.0) * warp_frequency,
198 (z + 100.0) * warp_frequency,
199 warp_seed.wrapping_add(7),
200 ) * warp_amplitude;
201 base.sample(x + wx, z + wz)
202 }
203 NoiseSource::Flat { height } => *height,
204 NoiseSource::Sinusoidal { frequency_x, frequency_z, amplitude } => {
205 (x * frequency_x * TAU).sin() * (z * frequency_z * TAU).sin() * amplitude
206 }
207 NoiseSource::Composite { sources } => {
208 sources.iter().map(|s| s.sample(x, z)).sum()
209 }
210 }
211 }
212
213 pub fn gradient(&self, x: f32, z: f32) -> Vec2 {
215 let eps = 0.1;
216 let dx = (self.sample(x + eps, z) - self.sample(x - eps, z)) / (2.0 * eps);
217 let dz = (self.sample(x, z + eps) - self.sample(x, z - eps)) / (2.0 * eps);
218 Vec2::new(dx, dz)
219 }
220}
221
222#[derive(Clone)]
231pub struct HeightFieldSurface {
232 pub noise: NoiseSource,
234 pub origin: Vec2,
236 pub size: Vec2,
238 pub resolution_x: usize,
240 pub resolution_z: usize,
242 pub heights: Vec<f32>,
244 pub time: f32,
246 pub animations: Vec<HeightFieldAnimation>,
248}
249
250#[derive(Debug, Clone)]
252pub enum HeightFieldAnimation {
253 Breathe {
255 amplitude: f32,
256 frequency: f32,
257 },
258 Ripple {
260 center: Vec2,
261 speed: f32,
262 amplitude: f32,
263 wavelength: f32,
264 decay: f32,
265 },
266 Warp {
268 direction: Vec2,
269 speed: f32,
270 amplitude: f32,
271 frequency: f32,
272 },
273 Collapse {
275 center: Vec2,
276 speed: f32,
277 radius: f32,
278 depth: f32,
279 },
280 Wave {
282 direction: Vec2,
283 speed: f32,
284 amplitude: f32,
285 wavelength: f32,
286 },
287}
288
289impl HeightFieldSurface {
290 pub fn new(
292 noise: NoiseSource,
293 origin: Vec2,
294 size: Vec2,
295 resolution_x: usize,
296 resolution_z: usize,
297 ) -> Self {
298 let rx = resolution_x.max(2);
299 let rz = resolution_z.max(2);
300 let mut surface = Self {
301 noise,
302 origin,
303 size,
304 resolution_x: rx,
305 resolution_z: rz,
306 heights: vec![0.0; rx * rz],
307 time: 0.0,
308 animations: Vec::new(),
309 };
310 surface.regenerate();
311 surface
312 }
313
314 pub fn regenerate(&mut self) {
316 for iz in 0..self.resolution_z {
317 for ix in 0..self.resolution_x {
318 let wx = self.origin.x + (ix as f32 / (self.resolution_x - 1) as f32) * self.size.x;
319 let wz = self.origin.y + (iz as f32 / (self.resolution_z - 1) as f32) * self.size.y;
320 self.heights[iz * self.resolution_x + ix] = self.noise.sample(wx, wz);
321 }
322 }
323 }
324
325 #[inline]
327 pub fn height_at_index(&self, ix: usize, iz: usize) -> f32 {
328 let ix = ix.min(self.resolution_x - 1);
329 let iz = iz.min(self.resolution_z - 1);
330 self.heights[iz * self.resolution_x + ix]
331 }
332
333 #[inline]
335 pub fn set_height(&mut self, ix: usize, iz: usize, h: f32) {
336 if ix < self.resolution_x && iz < self.resolution_z {
337 self.heights[iz * self.resolution_x + ix] = h;
338 }
339 }
340
341 pub fn sample_height(&self, x: f32, z: f32) -> f32 {
343 let fx = ((x - self.origin.x) / self.size.x) * (self.resolution_x - 1) as f32;
344 let fz = ((z - self.origin.y) / self.size.y) * (self.resolution_z - 1) as f32;
345
346 let ix = fx.floor() as i32;
347 let iz = fz.floor() as i32;
348
349 if ix < 0 || iz < 0 || ix >= (self.resolution_x - 1) as i32 || iz >= (self.resolution_z - 1) as i32 {
350 return self.noise.sample(x, z);
352 }
353
354 let ix = ix as usize;
355 let iz = iz as usize;
356 let sx = fx - ix as f32;
357 let sz = fz - iz as f32;
358
359 let h00 = self.height_at_index(ix, iz);
360 let h10 = self.height_at_index(ix + 1, iz);
361 let h01 = self.height_at_index(ix, iz + 1);
362 let h11 = self.height_at_index(ix + 1, iz + 1);
363
364 let top = h00 * (1.0 - sx) + h10 * sx;
365 let bottom = h01 * (1.0 - sx) + h11 * sx;
366 top * (1.0 - sz) + bottom * sz
367 }
368
369 pub fn normal_at(&self, x: f32, z: f32) -> Vec3 {
371 let cell_x = self.size.x / (self.resolution_x - 1) as f32;
372 let cell_z = self.size.y / (self.resolution_z - 1) as f32;
373 let eps_x = cell_x * 0.5;
374 let eps_z = cell_z * 0.5;
375
376 let hx_pos = self.sample_height(x + eps_x, z);
377 let hx_neg = self.sample_height(x - eps_x, z);
378 let hz_pos = self.sample_height(x, z + eps_z);
379 let hz_neg = self.sample_height(x, z - eps_z);
380
381 let dx = (hx_pos - hx_neg) / (2.0 * eps_x);
382 let dz = (hz_pos - hz_neg) / (2.0 * eps_z);
383
384 Vec3::new(-dx, 1.0, -dz).normalize()
385 }
386
387 pub fn normal_at_index(&self, ix: usize, iz: usize) -> Vec3 {
389 let h_left = if ix > 0 { self.height_at_index(ix - 1, iz) } else { self.height_at_index(ix, iz) };
390 let h_right = if ix + 1 < self.resolution_x { self.height_at_index(ix + 1, iz) } else { self.height_at_index(ix, iz) };
391 let h_down = if iz > 0 { self.height_at_index(ix, iz - 1) } else { self.height_at_index(ix, iz) };
392 let h_up = if iz + 1 < self.resolution_z { self.height_at_index(ix, iz + 1) } else { self.height_at_index(ix, iz) };
393
394 let cell_x = self.size.x / (self.resolution_x - 1) as f32;
395 let cell_z = self.size.y / (self.resolution_z - 1) as f32;
396
397 let dx = (h_right - h_left) / (2.0 * cell_x);
398 let dz = (h_up - h_down) / (2.0 * cell_z);
399
400 Vec3::new(-dx, 1.0, -dz).normalize()
401 }
402
403 pub fn tick(&mut self, dt: f32) {
405 self.time += dt;
406 if self.animations.is_empty() {
407 return;
408 }
409
410 self.regenerate();
412
413 let t = self.time;
415 let animations = self.animations.clone();
416
417 for iz in 0..self.resolution_z {
418 for ix in 0..self.resolution_x {
419 let wx = self.origin.x + (ix as f32 / (self.resolution_x - 1) as f32) * self.size.x;
420 let wz = self.origin.y + (iz as f32 / (self.resolution_z - 1) as f32) * self.size.y;
421 let idx = iz * self.resolution_x + ix;
422 let pos = Vec2::new(wx, wz);
423
424 for anim in &animations {
425 match anim {
426 HeightFieldAnimation::Breathe { amplitude, frequency } => {
427 self.heights[idx] += (t * frequency * TAU).sin() * amplitude;
428 }
429 HeightFieldAnimation::Ripple { center, speed, amplitude, wavelength, decay } => {
430 let dist = pos.distance(*center);
431 let wave = ((dist / wavelength - t * speed) * TAU).sin();
432 let falloff = (-dist * decay).exp();
433 self.heights[idx] += wave * amplitude * falloff;
434 }
435 HeightFieldAnimation::Warp { direction, speed, amplitude, frequency } => {
436 let phase = pos.dot(*direction) * frequency - t * speed;
437 self.heights[idx] += (phase * TAU).sin() * amplitude;
438 }
439 HeightFieldAnimation::Collapse { center, speed, radius, depth } => {
440 let dist = pos.distance(*center);
441 let progress = (t * speed).min(1.0);
442 let factor = (1.0 - (dist / radius).min(1.0)).max(0.0);
443 let factor = factor * factor; self.heights[idx] -= factor * depth * progress;
445 }
446 HeightFieldAnimation::Wave { direction, speed, amplitude, wavelength } => {
447 let phase = pos.dot(*direction) / wavelength - t * speed;
448 self.heights[idx] += (phase * TAU).sin() * amplitude;
449 }
450 }
451 }
452 }
453 }
454 }
455
456 pub fn deform_brush(&mut self, cx: f32, cz: f32, radius: f32, strength: f32) {
458 for iz in 0..self.resolution_z {
459 for ix in 0..self.resolution_x {
460 let wx = self.origin.x + (ix as f32 / (self.resolution_x - 1) as f32) * self.size.x;
461 let wz = self.origin.y + (iz as f32 / (self.resolution_z - 1) as f32) * self.size.y;
462 let dist = ((wx - cx).powi(2) + (wz - cz).powi(2)).sqrt();
463 if dist < radius {
464 let falloff = 1.0 - dist / radius;
465 let falloff = falloff * falloff * (3.0 - 2.0 * falloff); self.heights[iz * self.resolution_x + ix] += strength * falloff;
467 }
468 }
469 }
470 }
471
472 pub fn flatten_brush(&mut self, cx: f32, cz: f32, radius: f32, target: f32, strength: f32) {
474 for iz in 0..self.resolution_z {
475 for ix in 0..self.resolution_x {
476 let wx = self.origin.x + (ix as f32 / (self.resolution_x - 1) as f32) * self.size.x;
477 let wz = self.origin.y + (iz as f32 / (self.resolution_z - 1) as f32) * self.size.y;
478 let dist = ((wx - cx).powi(2) + (wz - cz).powi(2)).sqrt();
479 if dist < radius {
480 let falloff = 1.0 - dist / radius;
481 let falloff = falloff * falloff * (3.0 - 2.0 * falloff);
482 let idx = iz * self.resolution_x + ix;
483 self.heights[idx] += (target - self.heights[idx]) * strength * falloff;
484 }
485 }
486 }
487 }
488
489 pub fn world_position(&self, ix: usize, iz: usize) -> Vec3 {
491 let wx = self.origin.x + (ix as f32 / (self.resolution_x - 1) as f32) * self.size.x;
492 let wz = self.origin.y + (iz as f32 / (self.resolution_z - 1) as f32) * self.size.y;
493 let h = self.height_at_index(ix, iz);
494 Vec3::new(wx, h, wz)
495 }
496
497 pub fn bounding_box(&self) -> (Vec3, Vec3) {
499 let mut min_h = f32::MAX;
500 let mut max_h = f32::MIN;
501 for &h in &self.heights {
502 min_h = min_h.min(h);
503 max_h = max_h.max(h);
504 }
505 (
506 Vec3::new(self.origin.x, min_h, self.origin.y),
507 Vec3::new(self.origin.x + self.size.x, max_h, self.origin.y + self.size.y),
508 )
509 }
510
511 pub fn to_vertex_data(&self) -> (Vec<Vec3>, Vec<Vec3>) {
513 let mut positions = Vec::with_capacity(self.resolution_x * self.resolution_z);
514 let mut normals = Vec::with_capacity(self.resolution_x * self.resolution_z);
515
516 for iz in 0..self.resolution_z {
517 for ix in 0..self.resolution_x {
518 positions.push(self.world_position(ix, iz));
519 normals.push(self.normal_at_index(ix, iz));
520 }
521 }
522
523 (positions, normals)
524 }
525
526 pub fn generate_indices(&self) -> Vec<[u32; 3]> {
528 let mut indices = Vec::with_capacity((self.resolution_x - 1) * (self.resolution_z - 1) * 2);
529 for iz in 0..self.resolution_z - 1 {
530 for ix in 0..self.resolution_x - 1 {
531 let tl = (iz * self.resolution_x + ix) as u32;
532 let tr = tl + 1;
533 let bl = tl + self.resolution_x as u32;
534 let br = bl + 1;
535 indices.push([tl, bl, tr]);
536 indices.push([tr, bl, br]);
537 }
538 }
539 indices
540 }
541
542 pub fn sample_noise(&self, x: f32, z: f32) -> f32 {
544 self.noise.sample(x, z)
545 }
546}
547
548pub struct HeightFieldCollider;
554
555#[derive(Debug, Clone, Copy)]
557pub struct HeightFieldHit {
558 pub position: Vec3,
559 pub normal: Vec3,
560 pub distance: f32,
561}
562
563impl HeightFieldCollider {
564 pub fn ray_cast(
569 surface: &HeightFieldSurface,
570 origin: Vec3,
571 direction: Vec3,
572 max_distance: f32,
573 step_size: f32,
574 ) -> Option<HeightFieldHit> {
575 let dir = direction.normalize();
576 let mut t = 0.0_f32;
577 let mut prev_above = true;
578 let mut prev_pos = origin;
579
580 while t < max_distance {
582 let pos = origin + dir * t;
583 let terrain_h = surface.sample_height(pos.x, pos.z);
584 let above = pos.y > terrain_h;
585
586 if !above && prev_above && t > 0.0 {
587 let mut lo = t - step_size;
590 let mut hi = t;
591
592 for _ in 0..16 {
593 let mid = (lo + hi) * 0.5;
594 let mid_pos = origin + dir * mid;
595 let mid_h = surface.sample_height(mid_pos.x, mid_pos.z);
596 if mid_pos.y > mid_h {
597 lo = mid;
598 } else {
599 hi = mid;
600 }
601 }
602
603 let final_t = (lo + hi) * 0.5;
604 let hit_pos = origin + dir * final_t;
605 let normal = surface.normal_at(hit_pos.x, hit_pos.z);
606
607 return Some(HeightFieldHit {
608 position: hit_pos,
609 normal,
610 distance: final_t,
611 });
612 }
613
614 prev_above = above;
615 prev_pos = pos;
616 t += step_size;
617 }
618
619 None
620 }
621
622 pub fn is_above(surface: &HeightFieldSurface, point: Vec3) -> bool {
624 point.y > surface.sample_height(point.x, point.z)
625 }
626
627 pub fn distance_to_surface(surface: &HeightFieldSurface, point: Vec3) -> f32 {
629 point.y - surface.sample_height(point.x, point.z)
630 }
631
632 pub fn project_onto(surface: &HeightFieldSurface, point: Vec3) -> Vec3 {
634 Vec3::new(point.x, surface.sample_height(point.x, point.z), point.z)
635 }
636
637 pub fn sphere_intersects(
640 surface: &HeightFieldSurface,
641 center: Vec3,
642 radius: f32,
643 ) -> bool {
644 let h = surface.sample_height(center.x, center.z);
646 if center.y - radius < h {
647 return true;
648 }
649
650 for &(dx, dz) in &[(radius, 0.0), (-radius, 0.0), (0.0, radius), (0.0, -radius)] {
652 let px = center.x + dx;
653 let pz = center.z + dz;
654 let ph = surface.sample_height(px, pz);
655 let dist_sq = (center.x - px).powi(2) + (center.y - ph).powi(2) + (center.z - pz).powi(2);
656 if dist_sq < radius * radius {
657 return true;
658 }
659 }
660
661 false
662 }
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
671pub enum LodLevel {
672 Full,
674 Half,
676 Quarter,
678 Eighth,
680}
681
682impl LodLevel {
683 pub fn stride(self) -> usize {
685 match self {
686 LodLevel::Full => 1,
687 LodLevel::Half => 2,
688 LodLevel::Quarter => 4,
689 LodLevel::Eighth => 8,
690 }
691 }
692
693 pub fn from_distance(distance: f32, thresholds: &LodThresholds) -> Self {
695 if distance < thresholds.full_distance {
696 LodLevel::Full
697 } else if distance < thresholds.half_distance {
698 LodLevel::Half
699 } else if distance < thresholds.quarter_distance {
700 LodLevel::Quarter
701 } else {
702 LodLevel::Eighth
703 }
704 }
705
706 pub fn all() -> &'static [LodLevel] {
708 &[LodLevel::Full, LodLevel::Half, LodLevel::Quarter, LodLevel::Eighth]
709 }
710}
711
712#[derive(Debug, Clone, Copy)]
714pub struct LodThresholds {
715 pub full_distance: f32,
716 pub half_distance: f32,
717 pub quarter_distance: f32,
718}
719
720impl Default for LodThresholds {
721 fn default() -> Self {
722 Self {
723 full_distance: 100.0,
724 half_distance: 200.0,
725 quarter_distance: 400.0,
726 }
727 }
728}
729
730pub fn generate_lod_indices(resolution_x: usize, resolution_z: usize, lod: LodLevel) -> Vec<[u32; 3]> {
732 let stride = lod.stride();
733 let mut indices = Vec::new();
734
735 let mut iz = 0;
736 while iz + stride < resolution_z {
737 let mut ix = 0;
738 while ix + stride < resolution_x {
739 let tl = (iz * resolution_x + ix) as u32;
740 let tr = (iz * resolution_x + ix + stride) as u32;
741 let bl = ((iz + stride) * resolution_x + ix) as u32;
742 let br = ((iz + stride) * resolution_x + ix + stride) as u32;
743 indices.push([tl, bl, tr]);
744 indices.push([tr, bl, br]);
745 ix += stride;
746 }
747 iz += stride;
748 }
749
750 indices
751}
752
753#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
759pub struct ChunkCoord {
760 pub x: i32,
761 pub z: i32,
762}
763
764impl ChunkCoord {
765 pub fn new(x: i32, z: i32) -> Self { Self { x, z } }
766
767 pub fn world_origin(self, chunk_size: f32) -> Vec2 {
769 Vec2::new(self.x as f32 * chunk_size, self.z as f32 * chunk_size)
770 }
771
772 pub fn distance_to(self, point: Vec2, chunk_size: f32) -> f32 {
774 let center = Vec2::new(
775 (self.x as f32 + 0.5) * chunk_size,
776 (self.z as f32 + 0.5) * chunk_size,
777 );
778 center.distance(point)
779 }
780
781 pub fn from_world(x: f32, z: f32, chunk_size: f32) -> Self {
783 Self {
784 x: (x / chunk_size).floor() as i32,
785 z: (z / chunk_size).floor() as i32,
786 }
787 }
788}
789
790pub struct HeightFieldChunk {
792 pub coord: ChunkCoord,
793 pub surface: HeightFieldSurface,
794 pub lod: LodLevel,
795 pub cached_indices: Vec<[u32; 3]>,
797}
798
799impl HeightFieldChunk {
800 pub fn new(
802 coord: ChunkCoord,
803 noise: &NoiseSource,
804 chunk_size: f32,
805 resolution: usize,
806 ) -> Self {
807 let origin = coord.world_origin(chunk_size);
808 let surface = HeightFieldSurface::new(
809 noise.clone(),
810 origin,
811 Vec2::splat(chunk_size),
812 resolution,
813 resolution,
814 );
815 let cached_indices = surface.generate_indices();
816 Self {
817 coord,
818 surface,
819 lod: LodLevel::Full,
820 cached_indices,
821 }
822 }
823
824 pub fn update_lod(&mut self, new_lod: LodLevel) {
826 if self.lod != new_lod {
827 self.lod = new_lod;
828 self.cached_indices = generate_lod_indices(
829 self.surface.resolution_x,
830 self.surface.resolution_z,
831 new_lod,
832 );
833 }
834 }
835
836 pub fn tick(&mut self, dt: f32) {
838 self.surface.tick(dt);
839 }
840}
841
842pub struct ChunkManager {
844 pub noise: NoiseSource,
846 pub chunk_size: f32,
848 pub chunk_resolution: usize,
850 pub view_radius: i32,
852 pub lod_thresholds: LodThresholds,
854 pub chunks: std::collections::HashMap<ChunkCoord, HeightFieldChunk>,
856 pub last_camera_pos: Vec2,
858 pub animations: Vec<HeightFieldAnimation>,
860}
861
862impl ChunkManager {
863 pub fn new(
865 noise: NoiseSource,
866 chunk_size: f32,
867 chunk_resolution: usize,
868 view_radius: i32,
869 ) -> Self {
870 Self {
871 noise,
872 chunk_size,
873 chunk_resolution,
874 view_radius,
875 lod_thresholds: LodThresholds::default(),
876 chunks: std::collections::HashMap::new(),
877 last_camera_pos: Vec2::ZERO,
878 animations: Vec::new(),
879 }
880 }
881
882 pub fn update(&mut self, camera_x: f32, camera_z: f32) {
885 let cam_pos = Vec2::new(camera_x, camera_z);
886 self.last_camera_pos = cam_pos;
887
888 let center_coord = ChunkCoord::from_world(camera_x, camera_z, self.chunk_size);
889
890 let mut needed: std::collections::HashSet<ChunkCoord> = std::collections::HashSet::new();
892 for dz in -self.view_radius..=self.view_radius {
893 for dx in -self.view_radius..=self.view_radius {
894 needed.insert(ChunkCoord::new(center_coord.x + dx, center_coord.z + dz));
895 }
896 }
897
898 let to_remove: Vec<ChunkCoord> = self.chunks.keys()
900 .filter(|c| !needed.contains(c))
901 .copied()
902 .collect();
903 for coord in to_remove {
904 self.chunks.remove(&coord);
905 }
906
907 for coord in &needed {
909 if !self.chunks.contains_key(coord) {
910 let mut chunk = HeightFieldChunk::new(
911 *coord,
912 &self.noise,
913 self.chunk_size,
914 self.chunk_resolution,
915 );
916 chunk.surface.animations = self.animations.clone();
917 self.chunks.insert(*coord, chunk);
918 }
919 }
920
921 for (coord, chunk) in &mut self.chunks {
923 let dist = coord.distance_to(cam_pos, self.chunk_size);
924 let lod = LodLevel::from_distance(dist, &self.lod_thresholds);
925 chunk.update_lod(lod);
926 }
927 }
928
929 pub fn tick(&mut self, dt: f32) {
931 for chunk in self.chunks.values_mut() {
932 chunk.tick(dt);
933 }
934 }
935
936 pub fn sample_height(&self, x: f32, z: f32) -> f32 {
938 let coord = ChunkCoord::from_world(x, z, self.chunk_size);
939 if let Some(chunk) = self.chunks.get(&coord) {
940 chunk.surface.sample_height(x, z)
941 } else {
942 self.noise.sample(x, z)
944 }
945 }
946
947 pub fn sample_normal(&self, x: f32, z: f32) -> Vec3 {
949 let coord = ChunkCoord::from_world(x, z, self.chunk_size);
950 if let Some(chunk) = self.chunks.get(&coord) {
951 chunk.surface.normal_at(x, z)
952 } else {
953 Vec3::Y
954 }
955 }
956
957 pub fn loaded_chunk_count(&self) -> usize {
959 self.chunks.len()
960 }
961
962 pub fn iter_chunks(&self) -> impl Iterator<Item = &HeightFieldChunk> {
964 self.chunks.values()
965 }
966
967 pub fn iter_chunks_mut(&mut self) -> impl Iterator<Item = &mut HeightFieldChunk> {
969 self.chunks.values_mut()
970 }
971
972 pub fn ray_cast(&self, origin: Vec3, direction: Vec3, max_distance: f32) -> Option<HeightFieldHit> {
974 let mut closest: Option<HeightFieldHit> = None;
975
976 for chunk in self.chunks.values() {
977 if let Some(hit) = HeightFieldCollider::ray_cast(
978 &chunk.surface, origin, direction, max_distance, 0.5,
979 ) {
980 if closest.as_ref().map_or(true, |c| hit.distance < c.distance) {
981 closest = Some(hit);
982 }
983 }
984 }
985
986 closest
987 }
988
989 pub fn deform(&mut self, x: f32, z: f32, radius: f32, strength: f32) {
991 let chunk_radius = (radius / self.chunk_size).ceil() as i32 + 1;
993 let center = ChunkCoord::from_world(x, z, self.chunk_size);
994
995 for dz in -chunk_radius..=chunk_radius {
996 for dx in -chunk_radius..=chunk_radius {
997 let coord = ChunkCoord::new(center.x + dx, center.z + dz);
998 if let Some(chunk) = self.chunks.get_mut(&coord) {
999 chunk.surface.deform_brush(x, z, radius, strength);
1000 }
1001 }
1002 }
1003 }
1004}
1005
1006#[derive(Debug, Clone, Copy)]
1012pub struct ErosionParams {
1013 pub iterations: usize,
1014 pub inertia: f32,
1015 pub sediment_capacity: f32,
1016 pub min_sediment_capacity: f32,
1017 pub deposit_speed: f32,
1018 pub erode_speed: f32,
1019 pub evaporate_speed: f32,
1020 pub gravity: f32,
1021 pub max_droplet_lifetime: usize,
1022 pub initial_water: f32,
1023 pub initial_speed: f32,
1024}
1025
1026impl Default for ErosionParams {
1027 fn default() -> Self {
1028 Self {
1029 iterations: 5000,
1030 inertia: 0.05,
1031 sediment_capacity: 4.0,
1032 min_sediment_capacity: 0.01,
1033 deposit_speed: 0.3,
1034 erode_speed: 0.3,
1035 evaporate_speed: 0.01,
1036 gravity: 4.0,
1037 max_droplet_lifetime: 30,
1038 initial_water: 1.0,
1039 initial_speed: 1.0,
1040 }
1041 }
1042}
1043
1044struct SimpleRng {
1046 state: u32,
1047}
1048
1049impl SimpleRng {
1050 fn new(seed: u32) -> Self {
1051 Self { state: seed.max(1) }
1052 }
1053
1054 fn next_u32(&mut self) -> u32 {
1055 self.state ^= self.state << 13;
1056 self.state ^= self.state >> 17;
1057 self.state ^= self.state << 5;
1058 self.state
1059 }
1060
1061 fn next_f32(&mut self) -> f32 {
1062 (self.next_u32() as f32) / (u32::MAX as f32)
1063 }
1064}
1065
1066pub fn erode(surface: &mut HeightFieldSurface, params: &ErosionParams, seed: u32) {
1068 let mut rng = SimpleRng::new(seed);
1069 let rx = surface.resolution_x;
1070 let rz = surface.resolution_z;
1071
1072 for _ in 0..params.iterations {
1073 let mut pos_x = rng.next_f32() * (rx - 2) as f32 + 0.5;
1075 let mut pos_z = rng.next_f32() * (rz - 2) as f32 + 0.5;
1076 let mut dir_x = 0.0_f32;
1077 let mut dir_z = 0.0_f32;
1078 let mut speed = params.initial_speed;
1079 let mut water = params.initial_water;
1080 let mut sediment = 0.0_f32;
1081
1082 for _ in 0..params.max_droplet_lifetime {
1083 let ix = pos_x as usize;
1084 let iz = pos_z as usize;
1085
1086 if ix < 1 || ix >= rx - 1 || iz < 1 || iz >= rz - 1 {
1087 break;
1088 }
1089
1090 let h_l = surface.heights[iz * rx + ix - 1];
1092 let h_r = surface.heights[iz * rx + ix + 1];
1093 let h_d = surface.heights[(iz - 1) * rx + ix];
1094 let h_u = surface.heights[(iz + 1) * rx + ix];
1095
1096 let grad_x = (h_r - h_l) * 0.5;
1097 let grad_z = (h_u - h_d) * 0.5;
1098
1099 dir_x = dir_x * params.inertia - grad_x * (1.0 - params.inertia);
1101 dir_z = dir_z * params.inertia - grad_z * (1.0 - params.inertia);
1102
1103 let len = (dir_x * dir_x + dir_z * dir_z).sqrt();
1104 if len < 1e-6 {
1105 let angle = rng.next_f32() * TAU;
1107 dir_x = angle.cos();
1108 dir_z = angle.sin();
1109 } else {
1110 dir_x /= len;
1111 dir_z /= len;
1112 }
1113
1114 let new_x = pos_x + dir_x;
1116 let new_z = pos_z + dir_z;
1117
1118 let new_ix = new_x as usize;
1119 let new_iz = new_z as usize;
1120 if new_ix < 1 || new_ix >= rx - 1 || new_iz < 1 || new_iz >= rz - 1 {
1121 break;
1122 }
1123
1124 let old_h = surface.heights[iz * rx + ix];
1125 let new_h = surface.heights[new_iz * rx + new_ix];
1126 let delta_h = new_h - old_h;
1127
1128 let capacity = (-delta_h * speed * water * params.sediment_capacity)
1130 .max(params.min_sediment_capacity);
1131
1132 if sediment > capacity || delta_h > 0.0 {
1133 let amount = if delta_h > 0.0 {
1135 delta_h.min(sediment)
1136 } else {
1137 (sediment - capacity) * params.deposit_speed
1138 };
1139 sediment -= amount;
1140 surface.heights[iz * rx + ix] += amount;
1141 } else {
1142 let amount = ((capacity - sediment) * params.erode_speed).min(-delta_h);
1144 sediment += amount;
1145 surface.heights[iz * rx + ix] -= amount;
1146 }
1147
1148 speed = (speed * speed + delta_h * params.gravity).abs().sqrt();
1150 water *= 1.0 - params.evaporate_speed;
1151
1152 pos_x = new_x;
1153 pos_z = new_z;
1154
1155 if water < 0.001 {
1156 break;
1157 }
1158 }
1159 }
1160}
1161
1162#[cfg(test)]
1167mod tests {
1168 use super::*;
1169
1170 #[test]
1171 fn flat_noise_source() {
1172 let ns = NoiseSource::Flat { height: 5.0 };
1173 assert!((ns.sample(0.0, 0.0) - 5.0).abs() < 1e-5);
1174 assert!((ns.sample(100.0, 200.0) - 5.0).abs() < 1e-5);
1175 }
1176
1177 #[test]
1178 fn sinusoidal_terrain() {
1179 let ns = NoiseSource::Sinusoidal {
1180 frequency_x: 1.0,
1181 frequency_z: 1.0,
1182 amplitude: 10.0,
1183 };
1184 let h = ns.sample(0.25, 0.25);
1185 assert!(h.abs() <= 10.0);
1186 }
1187
1188 #[test]
1189 fn heightfield_create() {
1190 let hf = HeightFieldSurface::new(
1191 NoiseSource::Flat { height: 3.0 },
1192 Vec2::ZERO,
1193 Vec2::splat(100.0),
1194 16,
1195 16,
1196 );
1197 assert!((hf.sample_height(50.0, 50.0) - 3.0).abs() < 1e-3);
1198 }
1199
1200 #[test]
1201 fn heightfield_normal_flat() {
1202 let hf = HeightFieldSurface::new(
1203 NoiseSource::Flat { height: 0.0 },
1204 Vec2::ZERO,
1205 Vec2::splat(100.0),
1206 16,
1207 16,
1208 );
1209 let n = hf.normal_at(50.0, 50.0);
1210 assert!((n.y - 1.0).abs() < 0.01);
1211 }
1212
1213 #[test]
1214 fn ray_cast_flat() {
1215 let hf = HeightFieldSurface::new(
1216 NoiseSource::Flat { height: 0.0 },
1217 Vec2::new(-50.0, -50.0),
1218 Vec2::splat(100.0),
1219 32,
1220 32,
1221 );
1222 let hit = HeightFieldCollider::ray_cast(
1223 &hf,
1224 Vec3::new(0.0, 10.0, 0.0),
1225 Vec3::new(0.0, -1.0, 0.0),
1226 100.0,
1227 0.5,
1228 );
1229 assert!(hit.is_some());
1230 let h = hit.unwrap();
1231 assert!((h.position.y).abs() < 1.0);
1232 }
1233
1234 #[test]
1235 fn chunk_manager_loading() {
1236 let mut mgr = ChunkManager::new(
1237 NoiseSource::Flat { height: 0.0 },
1238 64.0,
1239 16,
1240 1,
1241 );
1242 mgr.update(0.0, 0.0);
1243 assert_eq!(mgr.loaded_chunk_count(), 9);
1245 }
1246
1247 #[test]
1248 fn chunk_manager_move_camera() {
1249 let mut mgr = ChunkManager::new(
1250 NoiseSource::Flat { height: 0.0 },
1251 64.0,
1252 8,
1253 1,
1254 );
1255 mgr.update(0.0, 0.0);
1256 let initial = mgr.loaded_chunk_count();
1257 mgr.update(1000.0, 0.0);
1258 assert_eq!(mgr.loaded_chunk_count(), initial);
1259 }
1260
1261 #[test]
1262 fn deform_brush() {
1263 let mut hf = HeightFieldSurface::new(
1264 NoiseSource::Flat { height: 0.0 },
1265 Vec2::ZERO,
1266 Vec2::splat(100.0),
1267 32,
1268 32,
1269 );
1270 hf.deform_brush(50.0, 50.0, 10.0, 5.0);
1271 let h = hf.sample_height(50.0, 50.0);
1272 assert!(h > 4.0);
1273 }
1274
1275 #[test]
1276 fn lod_levels() {
1277 let thresholds = LodThresholds::default();
1278 assert_eq!(LodLevel::from_distance(50.0, &thresholds), LodLevel::Full);
1279 assert_eq!(LodLevel::from_distance(150.0, &thresholds), LodLevel::Half);
1280 assert_eq!(LodLevel::from_distance(300.0, &thresholds), LodLevel::Quarter);
1281 assert_eq!(LodLevel::from_distance(500.0, &thresholds), LodLevel::Eighth);
1282 }
1283
1284 #[test]
1285 fn erosion_runs() {
1286 let mut hf = HeightFieldSurface::new(
1287 NoiseSource::Sinusoidal {
1288 frequency_x: 0.1,
1289 frequency_z: 0.1,
1290 amplitude: 10.0,
1291 },
1292 Vec2::ZERO,
1293 Vec2::splat(100.0),
1294 32,
1295 32,
1296 );
1297 let params = ErosionParams { iterations: 100, ..Default::default() };
1298 erode(&mut hf, ¶ms, 12345);
1299 }
1301
1302 #[test]
1303 fn composite_noise() {
1304 let ns = NoiseSource::Composite {
1305 sources: vec![
1306 NoiseSource::Flat { height: 5.0 },
1307 NoiseSource::Flat { height: 3.0 },
1308 ],
1309 };
1310 assert!((ns.sample(0.0, 0.0) - 8.0).abs() < 1e-5);
1311 }
1312}