1use oxiphysics_core::math::{Real, Vec3};
6
7use super::functions::{heightfield_tessellate, ray_aabb_xz, ray_triangle, tri_area};
8
9#[derive(Debug, Clone)]
14pub struct HeightField {
15 pub heights: Vec<Real>,
17 pub rows: usize,
19 pub cols: usize,
21 pub scale_x: Real,
23 pub scale_z: Real,
25}
26impl HeightField {
27 pub fn new(heights: Vec<Real>, rows: usize, cols: usize, scale_x: Real, scale_z: Real) -> Self {
29 assert_eq!(heights.len(), rows * cols);
30 Self {
31 heights,
32 rows,
33 cols,
34 scale_x,
35 scale_z,
36 }
37 }
38 pub fn height_at(&self, row: usize, col: usize) -> Real {
40 self.heights[row * self.cols + col]
41 }
42 pub fn from_fn(
46 cols: usize,
47 rows: usize,
48 cell_size: Real,
49 f: impl Fn(usize, usize) -> Real,
50 ) -> Self {
51 let mut heights = Vec::with_capacity(rows * cols);
52 for row in 0..rows {
53 for col in 0..cols {
54 heights.push(f(col, row));
55 }
56 }
57 Self {
58 heights,
59 rows,
60 cols,
61 scale_x: cell_size,
62 scale_z: cell_size,
63 }
64 }
65 pub fn height_at_uv(&self, u: Real, v: Real) -> Real {
69 let u = u.clamp(0.0, 1.0);
70 let v = v.clamp(0.0, 1.0);
71 let fx = u * (self.cols - 1) as Real;
72 let fz = v * (self.rows - 1) as Real;
73 let col0 = (fx as usize).min(self.cols - 2);
74 let row0 = (fz as usize).min(self.rows - 2);
75 let col1 = col0 + 1;
76 let row1 = row0 + 1;
77 let tx = fx - col0 as Real;
78 let tz = fz - row0 as Real;
79 let h00 = self.height_at(row0, col0);
80 let h10 = self.height_at(row0, col1);
81 let h01 = self.height_at(row1, col0);
82 let h11 = self.height_at(row1, col1);
83 let h0 = h00 * (1.0 - tx) + h10 * tx;
84 let h1 = h01 * (1.0 - tx) + h11 * tx;
85 h0 * (1.0 - tz) + h1 * tz
86 }
87 pub fn normal_at_grid(&self, col: usize, row: usize) -> [Real; 3] {
89 let dh_dx = if col == 0 {
90 (self.height_at(row, col + 1) - self.height_at(row, col)) / self.scale_x
91 } else if col == self.cols - 1 {
92 (self.height_at(row, col) - self.height_at(row, col - 1)) / self.scale_x
93 } else {
94 (self.height_at(row, col + 1) - self.height_at(row, col - 1)) / (2.0 * self.scale_x)
95 };
96 let dh_dz = if row == 0 {
97 (self.height_at(row + 1, col) - self.height_at(row, col)) / self.scale_z
98 } else if row == self.rows - 1 {
99 (self.height_at(row, col) - self.height_at(row - 1, col)) / self.scale_z
100 } else {
101 (self.height_at(row + 1, col) - self.height_at(row - 1, col)) / (2.0 * self.scale_z)
102 };
103 let nx = -dh_dx;
104 let ny = 1.0;
105 let nz = -dh_dz;
106 let len = (nx * nx + ny * ny + nz * nz).sqrt();
107 [nx / len, ny / len, nz / len]
108 }
109 pub fn to_triangle_mesh(&self) -> (Vec<[Real; 3]>, Vec<[usize; 3]>) {
114 let mut vertices = Vec::with_capacity(self.rows * self.cols);
115 for row in 0..self.rows {
116 for col in 0..self.cols {
117 vertices.push([
118 col as Real * self.scale_x,
119 self.height_at(row, col),
120 row as Real * self.scale_z,
121 ]);
122 }
123 }
124 let mut triangles = Vec::with_capacity((self.rows - 1) * (self.cols - 1) * 2);
125 for row in 0..(self.rows - 1) {
126 for col in 0..(self.cols - 1) {
127 let i00 = row * self.cols + col;
128 let i10 = row * self.cols + col + 1;
129 let i01 = (row + 1) * self.cols + col;
130 let i11 = (row + 1) * self.cols + col + 1;
131 triangles.push([i00, i10, i11]);
132 triangles.push([i00, i11, i01]);
133 }
134 }
135 (vertices, triangles)
136 }
137 pub fn min_height(&self) -> Real {
139 self.height_bounds().0
140 }
141 pub fn max_height(&self) -> Real {
143 self.height_bounds().1
144 }
145 pub fn surface_area(&self) -> Real {
147 if self.rows < 2 || self.cols < 2 {
148 return 0.0;
149 }
150 let mut area = 0.0;
151 for row in 0..(self.rows - 1) {
152 for col in 0..(self.cols - 1) {
153 let v00 = [
154 col as Real * self.scale_x,
155 self.height_at(row, col),
156 row as Real * self.scale_z,
157 ];
158 let v10 = [
159 (col + 1) as Real * self.scale_x,
160 self.height_at(row, col + 1),
161 row as Real * self.scale_z,
162 ];
163 let v01 = [
164 col as Real * self.scale_x,
165 self.height_at(row + 1, col),
166 (row + 1) as Real * self.scale_z,
167 ];
168 let v11 = [
169 (col + 1) as Real * self.scale_x,
170 self.height_at(row + 1, col + 1),
171 (row + 1) as Real * self.scale_z,
172 ];
173 area += tri_area(&v00, &v10, &v11);
174 area += tri_area(&v00, &v11, &v01);
175 }
176 }
177 area
178 }
179 pub fn smooth(&mut self, iterations: usize) {
184 for _ in 0..iterations {
185 let mut new_heights = self.heights.clone();
186 for row in 1..(self.rows - 1) {
187 for col in 1..(self.cols - 1) {
188 let up = self.height_at(row - 1, col);
189 let down = self.height_at(row + 1, col);
190 let left = self.height_at(row, col - 1);
191 let right = self.height_at(row, col + 1);
192 new_heights[row * self.cols + col] = (up + down + left + right) / 4.0;
193 }
194 }
195 self.heights = new_heights;
196 }
197 }
198 pub fn ray_cast_grid(
203 &self,
204 origin: [Real; 3],
205 dir: [Real; 3],
206 max_toi: Real,
207 ) -> Option<(Real, [Real; 3])> {
208 if self.rows < 2 || self.cols < 2 {
209 return None;
210 }
211 let grid_w = (self.cols - 1) as Real * self.scale_x;
212 let grid_h = (self.rows - 1) as Real * self.scale_z;
213 let (t_enter, t_exit) = ray_aabb_xz(
214 origin[0], origin[2], dir[0], dir[2], grid_w, grid_h, max_toi,
215 )?;
216 let eps = 1e-6;
217 let mut t = t_enter.max(0.0);
218 let step = self.scale_x.min(self.scale_z) * 0.5;
219 let mut best: Option<(Real, [Real; 3])> = None;
220 while t <= t_exit.min(max_toi) {
221 let px = origin[0] + dir[0] * t;
222 let pz = origin[2] + dir[2] * t;
223 let col = ((px / self.scale_x).floor() as isize)
224 .max(0)
225 .min((self.cols - 2) as isize) as usize;
226 let row = ((pz / self.scale_z).floor() as isize)
227 .max(0)
228 .min((self.rows - 2) as isize) as usize;
229 let v00 = Vec3::new(
230 col as Real * self.scale_x,
231 self.height_at(row, col),
232 row as Real * self.scale_z,
233 );
234 let v10 = Vec3::new(
235 (col + 1) as Real * self.scale_x,
236 self.height_at(row, col + 1),
237 row as Real * self.scale_z,
238 );
239 let v01 = Vec3::new(
240 col as Real * self.scale_x,
241 self.height_at(row + 1, col),
242 (row + 1) as Real * self.scale_z,
243 );
244 let v11 = Vec3::new(
245 (col + 1) as Real * self.scale_x,
246 self.height_at(row + 1, col + 1),
247 (row + 1) as Real * self.scale_z,
248 );
249 let o = Vec3::new(origin[0], origin[1], origin[2]);
250 let d = Vec3::new(dir[0], dir[1], dir[2]);
251 for (a, b, c) in [(&v00, &v10, &v11), (&v00, &v11, &v01)] {
252 if let Some(hit) = ray_triangle(&o, &d, max_toi, a, b, c)
253 && best.as_ref().is_none_or(|(bt, _)| hit.toi < *bt)
254 {
255 let dot = hit.normal.dot(&d);
256 let n = if dot > 0.0 {
257 [-hit.normal.x, -hit.normal.y, -hit.normal.z]
258 } else {
259 [hit.normal.x, hit.normal.y, hit.normal.z]
260 };
261 best = Some((hit.toi, n));
262 }
263 }
264 if best.is_some() {
265 return best;
266 }
267 t += step + eps;
268 }
269 best
270 }
271 pub(super) fn height_bounds(&self) -> (Real, Real) {
273 let mut min = Real::INFINITY;
274 let mut max = Real::NEG_INFINITY;
275 for &h in &self.heights {
276 if h < min {
277 min = h;
278 }
279 if h > max {
280 max = h;
281 }
282 }
283 (min, max)
284 }
285}
286impl HeightField {
287 pub fn lod_downsample(&self, factor: usize) -> HeightField {
292 if factor <= 1 {
293 return self.clone();
294 }
295 let new_cols = self.cols.div_ceil(factor);
296 let new_rows = self.rows.div_ceil(factor);
297 if new_cols < 2 || new_rows < 2 {
298 return self.clone();
299 }
300 let mut heights = Vec::with_capacity(new_rows * new_cols);
301 for r in 0..new_rows {
302 for c in 0..new_cols {
303 let r0 = r * factor;
304 let c0 = c * factor;
305 let r1 = (r0 + factor).min(self.rows);
306 let c1 = (c0 + factor).min(self.cols);
307 let mut sum = 0.0;
308 let mut count = 0usize;
309 for rr in r0..r1 {
310 for cc in c0..c1 {
311 sum += self.height_at(rr, cc);
312 count += 1;
313 }
314 }
315 heights.push(if count > 0 { sum / count as Real } else { 0.0 });
316 }
317 }
318 HeightField::new(
319 heights,
320 new_rows,
321 new_cols,
322 self.scale_x * factor as Real,
323 self.scale_z * factor as Real,
324 )
325 }
326 pub fn lod_pyramid(&self, levels: usize) -> Vec<HeightField> {
331 let mut result = vec![self.clone()];
332 let mut factor = 2;
333 for _ in 1..levels {
334 let prev = result.last().expect("collection should not be empty");
335 if prev.rows < 4 || prev.cols < 4 {
336 break;
337 }
338 result.push(prev.lod_downsample(2));
339 factor *= 2;
340 }
341 let _ = factor;
342 result
343 }
344 pub fn normal_at_world(&self, x: Real, z: Real) -> [Real; 3] {
347 let fx = (x / self.scale_x).clamp(0.0, (self.cols - 1) as Real);
348 let fz = (z / self.scale_z).clamp(0.0, (self.rows - 1) as Real);
349 let c0 = (fx as usize).min(self.cols - 1);
350 let r0 = (fz as usize).min(self.rows - 1);
351 let c1 = (c0 + 1).min(self.cols - 1);
352 let r1 = (r0 + 1).min(self.rows - 1);
353 let tx = fx - c0 as Real;
354 let tz = fz - r0 as Real;
355 let n00 = self.normal_at_grid(c0, r0);
356 let n10 = self.normal_at_grid(c1, r0);
357 let n01 = self.normal_at_grid(c0, r1);
358 let n11 = self.normal_at_grid(c1, r1);
359 let mut n = [0.0f64; 3];
360 for i in 0..3 {
361 let a = n00[i] * (1.0 - tx) + n10[i] * tx;
362 let b = n01[i] * (1.0 - tx) + n11[i] * tx;
363 n[i] = a * (1.0 - tz) + b * tz;
364 }
365 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
366 if len > 1e-10 {
367 [n[0] / len, n[1] / len, n[2] / len]
368 } else {
369 [0.0, 1.0, 0.0]
370 }
371 }
372 pub fn serialize(&self) -> Vec<Real> {
376 let mut data = Vec::with_capacity(4 + self.heights.len());
377 data.push(self.rows as Real);
378 data.push(self.cols as Real);
379 data.push(self.scale_x);
380 data.push(self.scale_z);
381 data.extend_from_slice(&self.heights);
382 data
383 }
384 pub fn deserialize(data: &[Real]) -> Option<HeightField> {
386 if data.len() < 4 {
387 return None;
388 }
389 let rows = data[0] as usize;
390 let cols = data[1] as usize;
391 let scale_x = data[2];
392 let scale_z = data[3];
393 if data.len() != 4 + rows * cols {
394 return None;
395 }
396 Some(HeightField::new(
397 data[4..].to_vec(),
398 rows,
399 cols,
400 scale_x,
401 scale_z,
402 ))
403 }
404 pub fn compute_all_normals(&self) -> Vec<[Real; 3]> {
408 let mut normals = Vec::with_capacity(self.rows * self.cols);
409 for row in 0..self.rows {
410 for col in 0..self.cols {
411 normals.push(self.normal_at_grid(col, row));
412 }
413 }
414 normals
415 }
416 pub fn height_at_world(&self, x: Real, z: Real) -> Real {
420 let u = (x / (self.scale_x * (self.cols - 1) as Real)).clamp(0.0, 1.0);
421 let v = (z / (self.scale_z * (self.rows - 1) as Real)).clamp(0.0, 1.0);
422 self.height_at_uv(u, v)
423 }
424 pub fn ray_cast_bounded(
428 &self,
429 origin: [Real; 3],
430 dir: [Real; 3],
431 max_toi: Real,
432 max_steps: usize,
433 ) -> Option<(Real, [Real; 3])> {
434 let step = self.scale_x.min(self.scale_z) * 0.25;
435 let mut t = 0.0;
436 let mut best: Option<(Real, [Real; 3])> = None;
437 for _ in 0..max_steps {
438 if t > max_toi {
439 break;
440 }
441 let px = origin[0] + dir[0] * t;
442 let pz = origin[2] + dir[2] * t;
443 if px < 0.0
444 || px > (self.cols - 1) as Real * self.scale_x
445 || pz < 0.0
446 || pz > (self.rows - 1) as Real * self.scale_z
447 {
448 t += step;
449 continue;
450 }
451 let py = origin[1] + dir[1] * t;
452 let terrain_y = self.height_at_world(px, pz);
453 if py <= terrain_y {
454 let refined_t = if t > step { t - step * 0.5 } else { 0.0 };
455 let col = ((px / self.scale_x) as usize).min(self.cols.saturating_sub(2));
456 let row = ((pz / self.scale_z) as usize).min(self.rows.saturating_sub(2));
457 let normal = self.normal_at_grid(col, row);
458 best = Some((refined_t.max(0.0), normal));
459 break;
460 }
461 t += step;
462 }
463 best
464 }
465}
466impl HeightField {
467 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
471 let (min_h, max_h) = self.height_bounds();
472 let max_x = (self.cols - 1) as f64 * self.scale_x;
473 let max_z = (self.rows - 1) as f64 * self.scale_z;
474 ([0.0, min_h, 0.0], [max_x, max_h, max_z])
475 }
476 pub fn height_at_xz(&self, x: f64, z: f64) -> f64 {
482 self.height_at_world(x, z)
483 }
484 pub fn normals(&self) -> Vec<[f64; 3]> {
489 self.compute_all_normals()
490 }
491 pub fn tessellate(&self) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
498 let vertices: Vec<[f64; 3]> = (0..self.rows)
499 .flat_map(|row| {
500 (0..self.cols).map(move |col| {
501 [
502 col as f64 * self.scale_x,
503 self.height_at(row, col),
504 row as f64 * self.scale_z,
505 ]
506 })
507 })
508 .collect();
509 let indices = heightfield_tessellate(self);
510 (vertices, indices)
511 }
512 pub fn ray_intersect(&self, origin: [f64; 3], dir: [f64; 3]) -> Option<(f64, [f64; 3])> {
520 let (_, max_h) = self.height_bounds();
521 if dir[1] > 0.0 && origin[1] >= max_h {
522 return None;
523 }
524 let grid_w = (self.cols - 1) as f64 * self.scale_x;
525 let grid_h = (self.rows - 1) as f64 * self.scale_z;
526 let diag = (grid_w * grid_w + grid_h * grid_h + (max_h - 0.0).abs().powi(2)).sqrt();
527 let max_toi = (origin[1] - max_h).abs() / dir[1].abs().max(1e-12) + diag * 2.0;
528 let max_toi = max_toi.min(1e9);
529 self.ray_cast_grid(origin, dir, max_toi)
530 }
531}
532impl HeightField {
533 pub fn slope_at(&self, col: usize, row: usize) -> f64 {
538 let dh_dx = if col == 0 {
539 (self.height_at(row, col + 1) - self.height_at(row, col)) / self.scale_x
540 } else if col == self.cols - 1 {
541 (self.height_at(row, col) - self.height_at(row, col - 1)) / self.scale_x
542 } else {
543 (self.height_at(row, col + 1) - self.height_at(row, col - 1)) / (2.0 * self.scale_x)
544 };
545 let dh_dz = if row == 0 {
546 (self.height_at(row + 1, col) - self.height_at(row, col)) / self.scale_z
547 } else if row == self.rows - 1 {
548 (self.height_at(row, col) - self.height_at(row - 1, col)) / self.scale_z
549 } else {
550 (self.height_at(row + 1, col) - self.height_at(row - 1, col)) / (2.0 * self.scale_z)
551 };
552 (dh_dx * dh_dx + dh_dz * dh_dz).sqrt()
553 }
554 pub fn curvature_at(&self, col: usize, row: usize) -> f64 {
559 let h = self.height_at(row, col);
560 let d2h_dx2 = if col == 0 || col == self.cols - 1 {
561 0.0
562 } else {
563 let hl = self.height_at(row, col - 1);
564 let hr = self.height_at(row, col + 1);
565 (hl - 2.0 * h + hr) / (self.scale_x * self.scale_x)
566 };
567 let d2h_dz2 = if row == 0 || row == self.rows - 1 {
568 0.0
569 } else {
570 let hd = self.height_at(row - 1, col);
571 let hu = self.height_at(row + 1, col);
572 (hd - 2.0 * h + hu) / (self.scale_z * self.scale_z)
573 };
574 (d2h_dx2 + d2h_dz2) * 0.5
575 }
576 pub fn slope_map(&self) -> Vec<f64> {
578 let mut out = Vec::with_capacity(self.rows * self.cols);
579 for row in 0..self.rows {
580 for col in 0..self.cols {
581 out.push(self.slope_at(col, row));
582 }
583 }
584 out
585 }
586 pub fn curvature_map(&self) -> Vec<f64> {
588 let mut out = Vec::with_capacity(self.rows * self.cols);
589 for row in 0..self.rows {
590 for col in 0..self.cols {
591 out.push(self.curvature_at(col, row));
592 }
593 }
594 out
595 }
596 pub fn closest_vertex(&self, q: [f64; 3]) -> ([f64; 3], usize, usize) {
601 let mut best_dist2 = f64::INFINITY;
602 let mut best_pt = [0.0f64; 3];
603 let mut best_row = 0usize;
604 let mut best_col = 0usize;
605 for row in 0..self.rows {
606 for col in 0..self.cols {
607 let vx = col as f64 * self.scale_x;
608 let vy = self.height_at(row, col);
609 let vz = row as f64 * self.scale_z;
610 let dx = q[0] - vx;
611 let dy = q[1] - vy;
612 let dz = q[2] - vz;
613 let d2 = dx * dx + dy * dy + dz * dz;
614 if d2 < best_dist2 {
615 best_dist2 = d2;
616 best_pt = [vx, vy, vz];
617 best_row = row;
618 best_col = col;
619 }
620 }
621 }
622 (best_pt, best_row, best_col)
623 }
624 pub fn resample(&self, new_cols: usize, new_rows: usize) -> HeightField {
629 assert!(new_cols >= 2 && new_rows >= 2);
630 let new_scale_x = (self.scale_x * (self.cols - 1) as f64) / (new_cols - 1) as f64;
631 let new_scale_z = (self.scale_z * (self.rows - 1) as f64) / (new_rows - 1) as f64;
632 let mut heights = Vec::with_capacity(new_rows * new_cols);
633 for nr in 0..new_rows {
634 for nc in 0..new_cols {
635 let x = nc as f64 * new_scale_x;
636 let z = nr as f64 * new_scale_z;
637 heights.push(self.height_at_world(x, z));
638 }
639 }
640 HeightField::new(heights, new_rows, new_cols, new_scale_x, new_scale_z)
641 }
642 pub fn hydraulic_erode(&mut self, sediment_rate: f64, iterations: usize) {
649 for _ in 0..iterations {
650 let mut delta = vec![0.0f64; self.rows * self.cols];
651 for row in 0..self.rows {
652 for col in 0..self.cols {
653 let h = self.height_at(row, col);
654 let neighbors: [(isize, isize); 4] = [
655 (row as isize - 1, col as isize),
656 (row as isize + 1, col as isize),
657 (row as isize, col as isize - 1),
658 (row as isize, col as isize + 1),
659 ];
660 let mut steepest_diff = 0.0f64;
661 let mut steepest_nr = -1isize;
662 let mut steepest_nc = -1isize;
663 for (nr, nc) in neighbors {
664 if nr >= 0 && nr < self.rows as isize && nc >= 0 && nc < self.cols as isize
665 {
666 let nh = self.height_at(nr as usize, nc as usize);
667 let diff = h - nh;
668 if diff > steepest_diff {
669 steepest_diff = diff;
670 steepest_nr = nr;
671 steepest_nc = nc;
672 }
673 }
674 }
675 if steepest_nr >= 0 && steepest_diff > 0.0 {
676 let transfer = sediment_rate * steepest_diff;
677 delta[row * self.cols + col] -= transfer;
678 delta[steepest_nr as usize * self.cols + steepest_nc as usize] += transfer;
679 }
680 }
681 }
682 for (h, d) in self.heights.iter_mut().zip(delta.iter()) {
683 *h += d;
684 }
685 }
686 }
687 pub fn flow_accumulation(&self) -> Vec<f64> {
693 let n = self.rows * self.cols;
694 let mut drain: Vec<Option<usize>> = vec![None; n];
695 for row in 0..self.rows {
696 for col in 0..self.cols {
697 let h = self.height_at(row, col);
698 let neighbors: [(isize, isize); 4] = [
699 (row as isize - 1, col as isize),
700 (row as isize + 1, col as isize),
701 (row as isize, col as isize - 1),
702 (row as isize, col as isize + 1),
703 ];
704 let mut best_diff = 0.0f64;
705 let mut best_idx: Option<usize> = None;
706 for (nr, nc) in neighbors {
707 if nr >= 0 && nr < self.rows as isize && nc >= 0 && nc < self.cols as isize {
708 let nh = self.height_at(nr as usize, nc as usize);
709 let diff = h - nh;
710 if diff > best_diff {
711 best_diff = diff;
712 best_idx = Some(nr as usize * self.cols + nc as usize);
713 }
714 }
715 }
716 drain[row * self.cols + col] = best_idx;
717 }
718 }
719 let mut order: Vec<usize> = (0..n).collect();
720 order.sort_by(|&a, &b| {
721 let ha = self.heights[a];
722 let hb = self.heights[b];
723 hb.partial_cmp(&ha).unwrap_or(std::cmp::Ordering::Equal)
724 });
725 let mut accum = vec![1.0f64; n];
726 for &idx in &order {
727 if let Some(target) = drain[idx] {
728 let val = accum[idx];
729 accum[target] += val;
730 }
731 }
732 accum
733 }
734 pub fn clamp_heights(&mut self, min_h: f64, max_h: f64) {
736 for h in &mut self.heights {
737 *h = h.clamp(min_h, max_h);
738 }
739 }
740 pub fn scale_heights(&mut self, factor: f64) {
742 for h in &mut self.heights {
743 *h *= factor;
744 }
745 }
746 pub fn offset_heights(&mut self, offset: f64) {
748 for h in &mut self.heights {
749 *h += offset;
750 }
751 }
752 pub fn normalize_heights(&mut self) {
755 let (min_h, max_h) = self.height_bounds();
756 let range = max_h - min_h;
757 if range < 1e-15 {
758 for h in &mut self.heights {
759 *h = 0.0;
760 }
761 } else {
762 for h in &mut self.heights {
763 *h = (*h - min_h) / range;
764 }
765 }
766 }
767 pub fn invert_heights(&mut self) {
769 let (min_h, max_h) = self.height_bounds();
770 for h in &mut self.heights {
771 *h = max_h - (*h - min_h);
772 }
773 }
774 pub fn mean_height(&self) -> f64 {
776 if self.heights.is_empty() {
777 return 0.0;
778 }
779 self.heights.iter().sum::<f64>() / self.heights.len() as f64
780 }
781 pub fn height_variance(&self) -> f64 {
783 if self.heights.is_empty() {
784 return 0.0;
785 }
786 let mean = self.mean_height();
787 self.heights
788 .iter()
789 .map(|&h| (h - mean) * (h - mean))
790 .sum::<f64>()
791 / self.heights.len() as f64
792 }
793 pub fn count_peaks(&self) -> usize {
797 let mut count = 0usize;
798 for row in 0..self.rows {
799 for col in 0..self.cols {
800 let h = self.height_at(row, col);
801 let neighbors: [(isize, isize); 4] = [
802 (row as isize - 1, col as isize),
803 (row as isize + 1, col as isize),
804 (row as isize, col as isize - 1),
805 (row as isize, col as isize + 1),
806 ];
807 let is_peak = neighbors.iter().all(|&(nr, nc)| {
808 if nr < 0 || nr >= self.rows as isize || nc < 0 || nc >= self.cols as isize {
809 true
810 } else {
811 self.height_at(nr as usize, nc as usize) < h
812 }
813 });
814 if is_peak {
815 count += 1;
816 }
817 }
818 }
819 count
820 }
821 pub fn volume(&self) -> f64 {
825 if self.rows < 2 || self.cols < 2 {
826 return 0.0;
827 }
828 let mut vol = 0.0_f64;
829 let cell_area = self.scale_x * self.scale_z;
830 for row in 0..self.rows - 1 {
831 for col in 0..self.cols - 1 {
832 let h00 = self.height_at(row, col);
833 let h10 = self.height_at(row, col + 1);
834 let h01 = self.height_at(row + 1, col);
835 let h11 = self.height_at(row + 1, col + 1);
836 let avg = (h00 + h10 + h01 + h11) * 0.25;
837 vol += avg * cell_area;
838 }
839 }
840 vol
841 }
842 pub fn ray_cast(
846 &self,
847 ray_origin: &oxiphysics_core::math::Vec3,
848 ray_direction: &oxiphysics_core::math::Vec3,
849 max_toi: f64,
850 ) -> Option<HeightfieldRayHit> {
851 let (verts, tris) = self.to_triangle_mesh();
852 let mut closest_toi = max_toi;
853 let mut hit = None;
854 for tri_idx in &tris {
855 let v0 = oxiphysics_core::math::Vec3::new(
856 verts[tri_idx[0]][0],
857 verts[tri_idx[0]][1],
858 verts[tri_idx[0]][2],
859 );
860 let v1 = oxiphysics_core::math::Vec3::new(
861 verts[tri_idx[1]][0],
862 verts[tri_idx[1]][1],
863 verts[tri_idx[1]][2],
864 );
865 let v2 = oxiphysics_core::math::Vec3::new(
866 verts[tri_idx[2]][0],
867 verts[tri_idx[2]][1],
868 verts[tri_idx[2]][2],
869 );
870 if let Some(ray_hit) =
871 ray_triangle(ray_origin, ray_direction, closest_toi, &v0, &v1, &v2)
872 && ray_hit.toi >= 0.0
873 && ray_hit.toi < closest_toi
874 {
875 closest_toi = ray_hit.toi;
876 let point = oxiphysics_core::math::Vec3::new(
877 ray_origin.x + ray_direction.x * ray_hit.toi,
878 ray_origin.y + ray_direction.y * ray_hit.toi,
879 ray_origin.z + ray_direction.z * ray_hit.toi,
880 );
881 hit = Some(HeightfieldRayHit {
882 toi: ray_hit.toi,
883 point,
884 });
885 }
886 }
887 hit
888 }
889}
890#[derive(Debug, Clone)]
892pub struct HeightfieldRayHit {
893 pub toi: f64,
895 pub point: oxiphysics_core::math::Vec3,
897}
898#[derive(Debug, Clone)]
900pub struct HeightfieldRaycast {
901 pub cell_ix: usize,
903 pub cell_iz: usize,
905 pub t: f64,
907 pub normal: [f64; 3],
909}
910#[derive(Debug, Clone)]
916pub struct HeightfieldRayTraversal {
917 pub cell_col: isize,
919 pub cell_row: isize,
921 pub(super) step_col: isize,
923 pub(super) step_row: isize,
925 pub(super) t_max_x: f64,
927 pub(super) t_max_z: f64,
929 pub(super) t_delta_x: f64,
931 pub(super) t_delta_z: f64,
933 pub(super) t_max: f64,
935 pub done: bool,
937}
938impl HeightfieldRayTraversal {
939 pub fn new(
944 hf: &HeightField,
945 ray_origin: [f64; 3],
946 ray_dir: [f64; 3],
947 max_t: f64,
948 ) -> Option<Self> {
949 if hf.rows < 2 || hf.cols < 2 {
950 return None;
951 }
952 let grid_w = (hf.cols - 1) as f64 * hf.scale_x;
953 let grid_h = (hf.rows - 1) as f64 * hf.scale_z;
954 let (t_enter, t_exit) = ray_aabb_xz(
955 ray_origin[0],
956 ray_origin[2],
957 ray_dir[0],
958 ray_dir[2],
959 grid_w,
960 grid_h,
961 max_t,
962 )?;
963 let t_start = t_enter.max(0.0);
964 if t_start > t_exit {
965 return None;
966 }
967 let px = ray_origin[0] + ray_dir[0] * t_start;
968 let pz = ray_origin[2] + ray_dir[2] * t_start;
969 let cell_col = ((px / hf.scale_x).floor() as isize).clamp(0, (hf.cols - 2) as isize);
970 let cell_row = ((pz / hf.scale_z).floor() as isize).clamp(0, (hf.rows - 2) as isize);
971 let step_col = if ray_dir[0] >= 0.0 { 1_isize } else { -1 };
972 let step_row = if ray_dir[2] >= 0.0 { 1_isize } else { -1 };
973 let t_delta_x = if ray_dir[0].abs() < 1e-12 {
974 f64::INFINITY
975 } else {
976 hf.scale_x / ray_dir[0].abs()
977 };
978 let t_delta_z = if ray_dir[2].abs() < 1e-12 {
979 f64::INFINITY
980 } else {
981 hf.scale_z / ray_dir[2].abs()
982 };
983 let x_boundary = if step_col > 0 {
984 (cell_col + 1) as f64 * hf.scale_x
985 } else {
986 cell_col as f64 * hf.scale_x
987 };
988 let z_boundary = if step_row > 0 {
989 (cell_row + 1) as f64 * hf.scale_z
990 } else {
991 cell_row as f64 * hf.scale_z
992 };
993 let t_max_x = if ray_dir[0].abs() < 1e-12 {
994 f64::INFINITY
995 } else {
996 t_start + (x_boundary - px) / ray_dir[0]
997 };
998 let t_max_z = if ray_dir[2].abs() < 1e-12 {
999 f64::INFINITY
1000 } else {
1001 t_start + (z_boundary - pz) / ray_dir[2]
1002 };
1003 Some(Self {
1004 cell_col,
1005 cell_row,
1006 step_col,
1007 step_row,
1008 t_max_x,
1009 t_max_z,
1010 t_delta_x,
1011 t_delta_z,
1012 t_max: t_exit.min(max_t),
1013 done: false,
1014 })
1015 }
1016 pub fn next_cell(&mut self) -> Option<(usize, usize, f64)> {
1021 if self.done {
1022 return None;
1023 }
1024 let col = self.cell_col;
1025 let row = self.cell_row;
1026 let t_entry = self.t_max_x.min(self.t_max_z);
1027 if t_entry > self.t_max {
1028 self.done = true;
1029 return None;
1030 }
1031 if self.t_max_x < self.t_max_z {
1032 self.cell_col += self.step_col;
1033 self.t_max_x += self.t_delta_x;
1034 } else {
1035 self.cell_row += self.step_row;
1036 self.t_max_z += self.t_delta_z;
1037 }
1038 if col < 0 || row < 0 {
1039 self.done = true;
1040 return None;
1041 }
1042 Some((col as usize, row as usize, t_entry))
1043 }
1044}