1#![allow(clippy::needless_range_loop)]
6#[allow(unused_imports)]
7use super::functions_2::*;
8use oxiphysics_core::math::{Real, Vec3};
9
10#[allow(unused_imports)]
11use super::functions::*;
12use super::functions::{heightfield_tessellate, ray_aabb_xz, ray_triangle, tri_area};
13
14#[derive(Debug, Clone)]
19pub struct HeightField {
20 pub heights: Vec<Real>,
22 pub rows: usize,
24 pub cols: usize,
26 pub scale_x: Real,
28 pub scale_z: Real,
30}
31impl HeightField {
32 pub fn new(heights: Vec<Real>, rows: usize, cols: usize, scale_x: Real, scale_z: Real) -> Self {
34 assert_eq!(heights.len(), rows * cols);
35 Self {
36 heights,
37 rows,
38 cols,
39 scale_x,
40 scale_z,
41 }
42 }
43 pub fn height_at(&self, row: usize, col: usize) -> Real {
45 self.heights[row * self.cols + col]
46 }
47 #[allow(dead_code)]
51 pub fn from_fn(
52 cols: usize,
53 rows: usize,
54 cell_size: Real,
55 f: impl Fn(usize, usize) -> Real,
56 ) -> Self {
57 let mut heights = Vec::with_capacity(rows * cols);
58 for row in 0..rows {
59 for col in 0..cols {
60 heights.push(f(col, row));
61 }
62 }
63 Self {
64 heights,
65 rows,
66 cols,
67 scale_x: cell_size,
68 scale_z: cell_size,
69 }
70 }
71 #[allow(dead_code)]
75 pub fn height_at_uv(&self, u: Real, v: Real) -> Real {
76 let u = u.clamp(0.0, 1.0);
77 let v = v.clamp(0.0, 1.0);
78 let fx = u * (self.cols - 1) as Real;
79 let fz = v * (self.rows - 1) as Real;
80 let col0 = (fx as usize).min(self.cols - 2);
81 let row0 = (fz as usize).min(self.rows - 2);
82 let col1 = col0 + 1;
83 let row1 = row0 + 1;
84 let tx = fx - col0 as Real;
85 let tz = fz - row0 as Real;
86 let h00 = self.height_at(row0, col0);
87 let h10 = self.height_at(row0, col1);
88 let h01 = self.height_at(row1, col0);
89 let h11 = self.height_at(row1, col1);
90 let h0 = h00 * (1.0 - tx) + h10 * tx;
91 let h1 = h01 * (1.0 - tx) + h11 * tx;
92 h0 * (1.0 - tz) + h1 * tz
93 }
94 #[allow(dead_code)]
96 pub fn normal_at_grid(&self, col: usize, row: usize) -> [Real; 3] {
97 let dh_dx = if col == 0 {
98 (self.height_at(row, col + 1) - self.height_at(row, col)) / self.scale_x
99 } else if col == self.cols - 1 {
100 (self.height_at(row, col) - self.height_at(row, col - 1)) / self.scale_x
101 } else {
102 (self.height_at(row, col + 1) - self.height_at(row, col - 1)) / (2.0 * self.scale_x)
103 };
104 let dh_dz = if row == 0 {
105 (self.height_at(row + 1, col) - self.height_at(row, col)) / self.scale_z
106 } else if row == self.rows - 1 {
107 (self.height_at(row, col) - self.height_at(row - 1, col)) / self.scale_z
108 } else {
109 (self.height_at(row + 1, col) - self.height_at(row - 1, col)) / (2.0 * self.scale_z)
110 };
111 let nx = -dh_dx;
112 let ny = 1.0;
113 let nz = -dh_dz;
114 let len = (nx * nx + ny * ny + nz * nz).sqrt();
115 [nx / len, ny / len, nz / len]
116 }
117 #[allow(dead_code)]
122 pub fn to_triangle_mesh(&self) -> (Vec<[Real; 3]>, Vec<[usize; 3]>) {
123 let mut vertices = Vec::with_capacity(self.rows * self.cols);
124 for row in 0..self.rows {
125 for col in 0..self.cols {
126 vertices.push([
127 col as Real * self.scale_x,
128 self.height_at(row, col),
129 row as Real * self.scale_z,
130 ]);
131 }
132 }
133 let mut triangles = Vec::with_capacity((self.rows - 1) * (self.cols - 1) * 2);
134 for row in 0..(self.rows - 1) {
135 for col in 0..(self.cols - 1) {
136 let i00 = row * self.cols + col;
137 let i10 = row * self.cols + col + 1;
138 let i01 = (row + 1) * self.cols + col;
139 let i11 = (row + 1) * self.cols + col + 1;
140 triangles.push([i00, i10, i11]);
141 triangles.push([i00, i11, i01]);
142 }
143 }
144 (vertices, triangles)
145 }
146 #[allow(dead_code)]
148 pub fn min_height(&self) -> Real {
149 self.height_bounds().0
150 }
151 #[allow(dead_code)]
153 pub fn max_height(&self) -> Real {
154 self.height_bounds().1
155 }
156 #[allow(dead_code)]
158 pub fn surface_area(&self) -> Real {
159 if self.rows < 2 || self.cols < 2 {
160 return 0.0;
161 }
162 let mut area = 0.0;
163 for row in 0..(self.rows - 1) {
164 for col in 0..(self.cols - 1) {
165 let v00 = [
166 col as Real * self.scale_x,
167 self.height_at(row, col),
168 row as Real * self.scale_z,
169 ];
170 let v10 = [
171 (col + 1) as Real * self.scale_x,
172 self.height_at(row, col + 1),
173 row as Real * self.scale_z,
174 ];
175 let v01 = [
176 col as Real * self.scale_x,
177 self.height_at(row + 1, col),
178 (row + 1) as Real * self.scale_z,
179 ];
180 let v11 = [
181 (col + 1) as Real * self.scale_x,
182 self.height_at(row + 1, col + 1),
183 (row + 1) as Real * self.scale_z,
184 ];
185 area += tri_area(&v00, &v10, &v11);
186 area += tri_area(&v00, &v11, &v01);
187 }
188 }
189 area
190 }
191 #[allow(dead_code)]
196 pub fn smooth(&mut self, iterations: usize) {
197 for _ in 0..iterations {
198 let mut new_heights = self.heights.clone();
199 for row in 1..(self.rows - 1) {
200 for col in 1..(self.cols - 1) {
201 let up = self.height_at(row - 1, col);
202 let down = self.height_at(row + 1, col);
203 let left = self.height_at(row, col - 1);
204 let right = self.height_at(row, col + 1);
205 new_heights[row * self.cols + col] = (up + down + left + right) / 4.0;
206 }
207 }
208 self.heights = new_heights;
209 }
210 }
211 #[allow(dead_code)]
216 pub fn ray_cast_grid(
217 &self,
218 origin: [Real; 3],
219 dir: [Real; 3],
220 max_toi: Real,
221 ) -> Option<(Real, [Real; 3])> {
222 if self.rows < 2 || self.cols < 2 {
223 return None;
224 }
225 let grid_w = (self.cols - 1) as Real * self.scale_x;
226 let grid_h = (self.rows - 1) as Real * self.scale_z;
227 let (t_enter, t_exit) = ray_aabb_xz(
228 origin[0], origin[2], dir[0], dir[2], grid_w, grid_h, max_toi,
229 )?;
230 let eps = 1e-6;
231 let mut t = t_enter.max(0.0);
232 let step = self.scale_x.min(self.scale_z) * 0.5;
233 let mut best: Option<(Real, [Real; 3])> = None;
234 while t <= t_exit.min(max_toi) {
235 let px = origin[0] + dir[0] * t;
236 let pz = origin[2] + dir[2] * t;
237 let col = ((px / self.scale_x).floor() as isize)
238 .max(0)
239 .min((self.cols - 2) as isize) as usize;
240 let row = ((pz / self.scale_z).floor() as isize)
241 .max(0)
242 .min((self.rows - 2) as isize) as usize;
243 let v00 = Vec3::new(
244 col as Real * self.scale_x,
245 self.height_at(row, col),
246 row as Real * self.scale_z,
247 );
248 let v10 = Vec3::new(
249 (col + 1) as Real * self.scale_x,
250 self.height_at(row, col + 1),
251 row as Real * self.scale_z,
252 );
253 let v01 = Vec3::new(
254 col as Real * self.scale_x,
255 self.height_at(row + 1, col),
256 (row + 1) as Real * self.scale_z,
257 );
258 let v11 = Vec3::new(
259 (col + 1) as Real * self.scale_x,
260 self.height_at(row + 1, col + 1),
261 (row + 1) as Real * self.scale_z,
262 );
263 let o = Vec3::new(origin[0], origin[1], origin[2]);
264 let d = Vec3::new(dir[0], dir[1], dir[2]);
265 for (a, b, c) in [(&v00, &v10, &v11), (&v00, &v11, &v01)] {
266 if let Some(hit) = ray_triangle(&o, &d, max_toi, a, b, c)
267 && best.as_ref().is_none_or(|(bt, _)| hit.toi < *bt)
268 {
269 let dot = hit.normal.dot(&d);
270 let n = if dot > 0.0 {
271 [-hit.normal.x, -hit.normal.y, -hit.normal.z]
272 } else {
273 [hit.normal.x, hit.normal.y, hit.normal.z]
274 };
275 best = Some((hit.toi, n));
276 }
277 }
278 if best.is_some() {
279 return best;
280 }
281 t += step + eps;
282 }
283 best
284 }
285 pub(super) fn height_bounds(&self) -> (Real, Real) {
287 let mut min = Real::INFINITY;
288 let mut max = Real::NEG_INFINITY;
289 for &h in &self.heights {
290 if h < min {
291 min = h;
292 }
293 if h > max {
294 max = h;
295 }
296 }
297 (min, max)
298 }
299}
300impl HeightField {
301 #[allow(dead_code)]
306 pub fn lod_downsample(&self, factor: usize) -> HeightField {
307 if factor <= 1 {
308 return self.clone();
309 }
310 let new_cols = self.cols.div_ceil(factor);
311 let new_rows = self.rows.div_ceil(factor);
312 if new_cols < 2 || new_rows < 2 {
313 return self.clone();
314 }
315 let mut heights = Vec::with_capacity(new_rows * new_cols);
316 for r in 0..new_rows {
317 for c in 0..new_cols {
318 let r0 = r * factor;
319 let c0 = c * factor;
320 let r1 = (r0 + factor).min(self.rows);
321 let c1 = (c0 + factor).min(self.cols);
322 let mut sum = 0.0;
323 let mut count = 0usize;
324 for rr in r0..r1 {
325 for cc in c0..c1 {
326 sum += self.height_at(rr, cc);
327 count += 1;
328 }
329 }
330 heights.push(if count > 0 { sum / count as Real } else { 0.0 });
331 }
332 }
333 HeightField::new(
334 heights,
335 new_rows,
336 new_cols,
337 self.scale_x * factor as Real,
338 self.scale_z * factor as Real,
339 )
340 }
341 #[allow(dead_code)]
346 pub fn lod_pyramid(&self, levels: usize) -> Vec<HeightField> {
347 let mut result = vec![self.clone()];
348 let mut factor = 2;
349 for _ in 1..levels {
350 let prev = result.last().expect("collection should not be empty");
351 if prev.rows < 4 || prev.cols < 4 {
352 break;
353 }
354 result.push(prev.lod_downsample(2));
355 factor *= 2;
356 }
357 let _ = factor;
358 result
359 }
360 #[allow(dead_code)]
363 pub fn normal_at_world(&self, x: Real, z: Real) -> [Real; 3] {
364 let fx = (x / self.scale_x).clamp(0.0, (self.cols - 1) as Real);
365 let fz = (z / self.scale_z).clamp(0.0, (self.rows - 1) as Real);
366 let c0 = (fx as usize).min(self.cols - 1);
367 let r0 = (fz as usize).min(self.rows - 1);
368 let c1 = (c0 + 1).min(self.cols - 1);
369 let r1 = (r0 + 1).min(self.rows - 1);
370 let tx = fx - c0 as Real;
371 let tz = fz - r0 as Real;
372 let n00 = self.normal_at_grid(c0, r0);
373 let n10 = self.normal_at_grid(c1, r0);
374 let n01 = self.normal_at_grid(c0, r1);
375 let n11 = self.normal_at_grid(c1, r1);
376 let mut n = [0.0f64; 3];
377 for i in 0..3 {
378 let a = n00[i] * (1.0 - tx) + n10[i] * tx;
379 let b = n01[i] * (1.0 - tx) + n11[i] * tx;
380 n[i] = a * (1.0 - tz) + b * tz;
381 }
382 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
383 if len > 1e-10 {
384 [n[0] / len, n[1] / len, n[2] / len]
385 } else {
386 [0.0, 1.0, 0.0]
387 }
388 }
389 #[allow(dead_code)]
393 pub fn serialize(&self) -> Vec<Real> {
394 let mut data = Vec::with_capacity(4 + self.heights.len());
395 data.push(self.rows as Real);
396 data.push(self.cols as Real);
397 data.push(self.scale_x);
398 data.push(self.scale_z);
399 data.extend_from_slice(&self.heights);
400 data
401 }
402 #[allow(dead_code)]
404 pub fn deserialize(data: &[Real]) -> Option<HeightField> {
405 if data.len() < 4 {
406 return None;
407 }
408 let rows = data[0] as usize;
409 let cols = data[1] as usize;
410 let scale_x = data[2];
411 let scale_z = data[3];
412 if data.len() != 4 + rows * cols {
413 return None;
414 }
415 Some(HeightField::new(
416 data[4..].to_vec(),
417 rows,
418 cols,
419 scale_x,
420 scale_z,
421 ))
422 }
423 #[allow(dead_code)]
427 pub fn compute_all_normals(&self) -> Vec<[Real; 3]> {
428 let mut normals = Vec::with_capacity(self.rows * self.cols);
429 for row in 0..self.rows {
430 for col in 0..self.cols {
431 normals.push(self.normal_at_grid(col, row));
432 }
433 }
434 normals
435 }
436 #[allow(dead_code)]
440 pub fn height_at_world(&self, x: Real, z: Real) -> Real {
441 let u = (x / (self.scale_x * (self.cols - 1) as Real)).clamp(0.0, 1.0);
442 let v = (z / (self.scale_z * (self.rows - 1) as Real)).clamp(0.0, 1.0);
443 self.height_at_uv(u, v)
444 }
445 #[allow(dead_code)]
449 pub fn ray_cast_bounded(
450 &self,
451 origin: [Real; 3],
452 dir: [Real; 3],
453 max_toi: Real,
454 max_steps: usize,
455 ) -> Option<(Real, [Real; 3])> {
456 let step = self.scale_x.min(self.scale_z) * 0.25;
457 let mut t = 0.0;
458 let mut best: Option<(Real, [Real; 3])> = None;
459 for _ in 0..max_steps {
460 if t > max_toi {
461 break;
462 }
463 let px = origin[0] + dir[0] * t;
464 let pz = origin[2] + dir[2] * t;
465 if px < 0.0
466 || px > (self.cols - 1) as Real * self.scale_x
467 || pz < 0.0
468 || pz > (self.rows - 1) as Real * self.scale_z
469 {
470 t += step;
471 continue;
472 }
473 let py = origin[1] + dir[1] * t;
474 let terrain_y = self.height_at_world(px, pz);
475 if py <= terrain_y {
476 let refined_t = if t > step { t - step * 0.5 } else { 0.0 };
477 let col = ((px / self.scale_x) as usize).min(self.cols.saturating_sub(2));
478 let row = ((pz / self.scale_z) as usize).min(self.rows.saturating_sub(2));
479 let normal = self.normal_at_grid(col, row);
480 best = Some((refined_t.max(0.0), normal));
481 break;
482 }
483 t += step;
484 }
485 best
486 }
487}
488impl HeightField {
489 #[allow(dead_code)]
493 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
494 let (min_h, max_h) = self.height_bounds();
495 let max_x = (self.cols - 1) as f64 * self.scale_x;
496 let max_z = (self.rows - 1) as f64 * self.scale_z;
497 ([0.0, min_h, 0.0], [max_x, max_h, max_z])
498 }
499 #[allow(dead_code)]
505 pub fn height_at_xz(&self, x: f64, z: f64) -> f64 {
506 self.height_at_world(x, z)
507 }
508 #[allow(dead_code)]
513 pub fn normals(&self) -> Vec<[f64; 3]> {
514 self.compute_all_normals()
515 }
516 #[allow(dead_code)]
523 pub fn tessellate(&self) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
524 let vertices: Vec<[f64; 3]> = (0..self.rows)
525 .flat_map(|row| {
526 (0..self.cols).map(move |col| {
527 [
528 col as f64 * self.scale_x,
529 self.height_at(row, col),
530 row as f64 * self.scale_z,
531 ]
532 })
533 })
534 .collect();
535 let indices = heightfield_tessellate(self);
536 (vertices, indices)
537 }
538 #[allow(dead_code)]
546 pub fn ray_intersect(&self, origin: [f64; 3], dir: [f64; 3]) -> Option<(f64, [f64; 3])> {
547 let (_, max_h) = self.height_bounds();
548 if dir[1] > 0.0 && origin[1] >= max_h {
549 return None;
550 }
551 let grid_w = (self.cols - 1) as f64 * self.scale_x;
552 let grid_h = (self.rows - 1) as f64 * self.scale_z;
553 let diag = (grid_w * grid_w + grid_h * grid_h + (max_h - 0.0).abs().powi(2)).sqrt();
554 let max_toi = (origin[1] - max_h).abs() / dir[1].abs().max(1e-12) + diag * 2.0;
555 let max_toi = max_toi.min(1e9);
556 self.ray_cast_grid(origin, dir, max_toi)
557 }
558}
559impl HeightField {
560 #[allow(dead_code)]
565 pub fn slope_at(&self, col: usize, row: usize) -> f64 {
566 let dh_dx = if col == 0 {
567 (self.height_at(row, col + 1) - self.height_at(row, col)) / self.scale_x
568 } else if col == self.cols - 1 {
569 (self.height_at(row, col) - self.height_at(row, col - 1)) / self.scale_x
570 } else {
571 (self.height_at(row, col + 1) - self.height_at(row, col - 1)) / (2.0 * self.scale_x)
572 };
573 let dh_dz = if row == 0 {
574 (self.height_at(row + 1, col) - self.height_at(row, col)) / self.scale_z
575 } else if row == self.rows - 1 {
576 (self.height_at(row, col) - self.height_at(row - 1, col)) / self.scale_z
577 } else {
578 (self.height_at(row + 1, col) - self.height_at(row - 1, col)) / (2.0 * self.scale_z)
579 };
580 (dh_dx * dh_dx + dh_dz * dh_dz).sqrt()
581 }
582 #[allow(dead_code)]
587 pub fn curvature_at(&self, col: usize, row: usize) -> f64 {
588 let h = self.height_at(row, col);
589 let d2h_dx2 = if col == 0 || col == self.cols - 1 {
590 0.0
591 } else {
592 let hl = self.height_at(row, col - 1);
593 let hr = self.height_at(row, col + 1);
594 (hl - 2.0 * h + hr) / (self.scale_x * self.scale_x)
595 };
596 let d2h_dz2 = if row == 0 || row == self.rows - 1 {
597 0.0
598 } else {
599 let hd = self.height_at(row - 1, col);
600 let hu = self.height_at(row + 1, col);
601 (hd - 2.0 * h + hu) / (self.scale_z * self.scale_z)
602 };
603 (d2h_dx2 + d2h_dz2) * 0.5
604 }
605 #[allow(dead_code)]
607 pub fn slope_map(&self) -> Vec<f64> {
608 let mut out = Vec::with_capacity(self.rows * self.cols);
609 for row in 0..self.rows {
610 for col in 0..self.cols {
611 out.push(self.slope_at(col, row));
612 }
613 }
614 out
615 }
616 #[allow(dead_code)]
618 pub fn curvature_map(&self) -> Vec<f64> {
619 let mut out = Vec::with_capacity(self.rows * self.cols);
620 for row in 0..self.rows {
621 for col in 0..self.cols {
622 out.push(self.curvature_at(col, row));
623 }
624 }
625 out
626 }
627 #[allow(dead_code)]
632 pub fn closest_vertex(&self, q: [f64; 3]) -> ([f64; 3], usize, usize) {
633 let mut best_dist2 = f64::INFINITY;
634 let mut best_pt = [0.0f64; 3];
635 let mut best_row = 0usize;
636 let mut best_col = 0usize;
637 for row in 0..self.rows {
638 for col in 0..self.cols {
639 let vx = col as f64 * self.scale_x;
640 let vy = self.height_at(row, col);
641 let vz = row as f64 * self.scale_z;
642 let dx = q[0] - vx;
643 let dy = q[1] - vy;
644 let dz = q[2] - vz;
645 let d2 = dx * dx + dy * dy + dz * dz;
646 if d2 < best_dist2 {
647 best_dist2 = d2;
648 best_pt = [vx, vy, vz];
649 best_row = row;
650 best_col = col;
651 }
652 }
653 }
654 (best_pt, best_row, best_col)
655 }
656 #[allow(dead_code)]
661 pub fn resample(&self, new_cols: usize, new_rows: usize) -> HeightField {
662 assert!(new_cols >= 2 && new_rows >= 2);
663 let new_scale_x = (self.scale_x * (self.cols - 1) as f64) / (new_cols - 1) as f64;
664 let new_scale_z = (self.scale_z * (self.rows - 1) as f64) / (new_rows - 1) as f64;
665 let mut heights = Vec::with_capacity(new_rows * new_cols);
666 for nr in 0..new_rows {
667 for nc in 0..new_cols {
668 let x = nc as f64 * new_scale_x;
669 let z = nr as f64 * new_scale_z;
670 heights.push(self.height_at_world(x, z));
671 }
672 }
673 HeightField::new(heights, new_rows, new_cols, new_scale_x, new_scale_z)
674 }
675 #[allow(dead_code)]
682 pub fn hydraulic_erode(&mut self, sediment_rate: f64, iterations: usize) {
683 for _ in 0..iterations {
684 let mut delta = vec![0.0f64; self.rows * self.cols];
685 for row in 0..self.rows {
686 for col in 0..self.cols {
687 let h = self.height_at(row, col);
688 let neighbors: [(isize, isize); 4] = [
689 (row as isize - 1, col as isize),
690 (row as isize + 1, col as isize),
691 (row as isize, col as isize - 1),
692 (row as isize, col as isize + 1),
693 ];
694 let mut steepest_diff = 0.0f64;
695 let mut steepest_nr = -1isize;
696 let mut steepest_nc = -1isize;
697 for (nr, nc) in neighbors {
698 if nr >= 0 && nr < self.rows as isize && nc >= 0 && nc < self.cols as isize
699 {
700 let nh = self.height_at(nr as usize, nc as usize);
701 let diff = h - nh;
702 if diff > steepest_diff {
703 steepest_diff = diff;
704 steepest_nr = nr;
705 steepest_nc = nc;
706 }
707 }
708 }
709 if steepest_nr >= 0 && steepest_diff > 0.0 {
710 let transfer = sediment_rate * steepest_diff;
711 delta[row * self.cols + col] -= transfer;
712 delta[steepest_nr as usize * self.cols + steepest_nc as usize] += transfer;
713 }
714 }
715 }
716 for i in 0..self.heights.len() {
717 self.heights[i] += delta[i];
718 }
719 }
720 }
721 #[allow(dead_code)]
727 pub fn flow_accumulation(&self) -> Vec<f64> {
728 let n = self.rows * self.cols;
729 let mut drain: Vec<Option<usize>> = vec![None; n];
730 for row in 0..self.rows {
731 for col in 0..self.cols {
732 let h = self.height_at(row, col);
733 let neighbors: [(isize, isize); 4] = [
734 (row as isize - 1, col as isize),
735 (row as isize + 1, col as isize),
736 (row as isize, col as isize - 1),
737 (row as isize, col as isize + 1),
738 ];
739 let mut best_diff = 0.0f64;
740 let mut best_idx: Option<usize> = None;
741 for (nr, nc) in neighbors {
742 if nr >= 0 && nr < self.rows as isize && nc >= 0 && nc < self.cols as isize {
743 let nh = self.height_at(nr as usize, nc as usize);
744 let diff = h - nh;
745 if diff > best_diff {
746 best_diff = diff;
747 best_idx = Some(nr as usize * self.cols + nc as usize);
748 }
749 }
750 }
751 drain[row * self.cols + col] = best_idx;
752 }
753 }
754 let mut order: Vec<usize> = (0..n).collect();
755 order.sort_by(|&a, &b| {
756 let ha = self.heights[a];
757 let hb = self.heights[b];
758 hb.partial_cmp(&ha).unwrap_or(std::cmp::Ordering::Equal)
759 });
760 let mut accum = vec![1.0f64; n];
761 for &idx in &order {
762 if let Some(target) = drain[idx] {
763 let val = accum[idx];
764 accum[target] += val;
765 }
766 }
767 accum
768 }
769 #[allow(dead_code)]
771 pub fn clamp_heights(&mut self, min_h: f64, max_h: f64) {
772 for h in &mut self.heights {
773 *h = h.clamp(min_h, max_h);
774 }
775 }
776 #[allow(dead_code)]
778 pub fn scale_heights(&mut self, factor: f64) {
779 for h in &mut self.heights {
780 *h *= factor;
781 }
782 }
783 #[allow(dead_code)]
785 pub fn offset_heights(&mut self, offset: f64) {
786 for h in &mut self.heights {
787 *h += offset;
788 }
789 }
790 #[allow(dead_code)]
793 pub fn normalize_heights(&mut self) {
794 let (min_h, max_h) = self.height_bounds();
795 let range = max_h - min_h;
796 if range < 1e-15 {
797 for h in &mut self.heights {
798 *h = 0.0;
799 }
800 } else {
801 for h in &mut self.heights {
802 *h = (*h - min_h) / range;
803 }
804 }
805 }
806 #[allow(dead_code)]
808 pub fn invert_heights(&mut self) {
809 let (min_h, max_h) = self.height_bounds();
810 for h in &mut self.heights {
811 *h = max_h - (*h - min_h);
812 }
813 }
814 #[allow(dead_code)]
816 pub fn mean_height(&self) -> f64 {
817 if self.heights.is_empty() {
818 return 0.0;
819 }
820 self.heights.iter().sum::<f64>() / self.heights.len() as f64
821 }
822 #[allow(dead_code)]
824 pub fn height_variance(&self) -> f64 {
825 if self.heights.is_empty() {
826 return 0.0;
827 }
828 let mean = self.mean_height();
829 self.heights
830 .iter()
831 .map(|&h| (h - mean) * (h - mean))
832 .sum::<f64>()
833 / self.heights.len() as f64
834 }
835 #[allow(dead_code)]
839 pub fn count_peaks(&self) -> usize {
840 let mut count = 0usize;
841 for row in 0..self.rows {
842 for col in 0..self.cols {
843 let h = self.height_at(row, col);
844 let neighbors: [(isize, isize); 4] = [
845 (row as isize - 1, col as isize),
846 (row as isize + 1, col as isize),
847 (row as isize, col as isize - 1),
848 (row as isize, col as isize + 1),
849 ];
850 let is_peak = neighbors.iter().all(|&(nr, nc)| {
851 if nr < 0 || nr >= self.rows as isize || nc < 0 || nc >= self.cols as isize {
852 true
853 } else {
854 self.height_at(nr as usize, nc as usize) < h
855 }
856 });
857 if is_peak {
858 count += 1;
859 }
860 }
861 }
862 count
863 }
864 #[allow(dead_code)]
868 pub fn volume(&self) -> f64 {
869 if self.rows < 2 || self.cols < 2 {
870 return 0.0;
871 }
872 let mut vol = 0.0_f64;
873 let cell_area = self.scale_x * self.scale_z;
874 for row in 0..self.rows - 1 {
875 for col in 0..self.cols - 1 {
876 let h00 = self.height_at(row, col);
877 let h10 = self.height_at(row, col + 1);
878 let h01 = self.height_at(row + 1, col);
879 let h11 = self.height_at(row + 1, col + 1);
880 let avg = (h00 + h10 + h01 + h11) * 0.25;
881 vol += avg * cell_area;
882 }
883 }
884 vol
885 }
886 #[allow(dead_code)]
890 pub fn ray_cast(
891 &self,
892 ray_origin: &oxiphysics_core::math::Vec3,
893 ray_direction: &oxiphysics_core::math::Vec3,
894 max_toi: f64,
895 ) -> Option<HeightfieldRayHit> {
896 let (verts, tris) = self.to_triangle_mesh();
897 let mut closest_toi = max_toi;
898 let mut hit = None;
899 for tri_idx in &tris {
900 let v0 = oxiphysics_core::math::Vec3::new(
901 verts[tri_idx[0]][0],
902 verts[tri_idx[0]][1],
903 verts[tri_idx[0]][2],
904 );
905 let v1 = oxiphysics_core::math::Vec3::new(
906 verts[tri_idx[1]][0],
907 verts[tri_idx[1]][1],
908 verts[tri_idx[1]][2],
909 );
910 let v2 = oxiphysics_core::math::Vec3::new(
911 verts[tri_idx[2]][0],
912 verts[tri_idx[2]][1],
913 verts[tri_idx[2]][2],
914 );
915 if let Some(ray_hit) =
916 ray_triangle(ray_origin, ray_direction, closest_toi, &v0, &v1, &v2)
917 && ray_hit.toi >= 0.0
918 && ray_hit.toi < closest_toi
919 {
920 closest_toi = ray_hit.toi;
921 let point = oxiphysics_core::math::Vec3::new(
922 ray_origin.x + ray_direction.x * ray_hit.toi,
923 ray_origin.y + ray_direction.y * ray_hit.toi,
924 ray_origin.z + ray_direction.z * ray_hit.toi,
925 );
926 hit = Some(HeightfieldRayHit {
927 toi: ray_hit.toi,
928 point,
929 });
930 }
931 }
932 hit
933 }
934}
935#[derive(Debug, Clone)]
937pub struct HeightfieldRayHit {
938 pub toi: f64,
940 pub point: oxiphysics_core::math::Vec3,
942}
943#[derive(Debug, Clone)]
945#[allow(dead_code)]
946pub struct HeightfieldRaycast {
947 pub cell_ix: usize,
949 pub cell_iz: usize,
951 pub t: f64,
953 pub normal: [f64; 3],
955}
956#[derive(Debug, Clone)]
962#[allow(dead_code)]
963pub struct HeightfieldRayTraversal {
964 pub cell_col: isize,
966 pub cell_row: isize,
968 pub(super) step_col: isize,
970 pub(super) step_row: isize,
972 pub(super) t_max_x: f64,
974 pub(super) t_max_z: f64,
976 pub(super) t_delta_x: f64,
978 pub(super) t_delta_z: f64,
980 pub(super) t_max: f64,
982 pub done: bool,
984}
985#[allow(dead_code)]
986impl HeightfieldRayTraversal {
987 pub fn new(
992 hf: &HeightField,
993 ray_origin: [f64; 3],
994 ray_dir: [f64; 3],
995 max_t: f64,
996 ) -> Option<Self> {
997 if hf.rows < 2 || hf.cols < 2 {
998 return None;
999 }
1000 let grid_w = (hf.cols - 1) as f64 * hf.scale_x;
1001 let grid_h = (hf.rows - 1) as f64 * hf.scale_z;
1002 let (t_enter, t_exit) = ray_aabb_xz(
1003 ray_origin[0],
1004 ray_origin[2],
1005 ray_dir[0],
1006 ray_dir[2],
1007 grid_w,
1008 grid_h,
1009 max_t,
1010 )?;
1011 let t_start = t_enter.max(0.0);
1012 if t_start > t_exit {
1013 return None;
1014 }
1015 let px = ray_origin[0] + ray_dir[0] * t_start;
1016 let pz = ray_origin[2] + ray_dir[2] * t_start;
1017 let cell_col = ((px / hf.scale_x).floor() as isize).clamp(0, (hf.cols - 2) as isize);
1018 let cell_row = ((pz / hf.scale_z).floor() as isize).clamp(0, (hf.rows - 2) as isize);
1019 let step_col = if ray_dir[0] >= 0.0 { 1_isize } else { -1 };
1020 let step_row = if ray_dir[2] >= 0.0 { 1_isize } else { -1 };
1021 let t_delta_x = if ray_dir[0].abs() < 1e-12 {
1022 f64::INFINITY
1023 } else {
1024 hf.scale_x / ray_dir[0].abs()
1025 };
1026 let t_delta_z = if ray_dir[2].abs() < 1e-12 {
1027 f64::INFINITY
1028 } else {
1029 hf.scale_z / ray_dir[2].abs()
1030 };
1031 let x_boundary = if step_col > 0 {
1032 (cell_col + 1) as f64 * hf.scale_x
1033 } else {
1034 cell_col as f64 * hf.scale_x
1035 };
1036 let z_boundary = if step_row > 0 {
1037 (cell_row + 1) as f64 * hf.scale_z
1038 } else {
1039 cell_row as f64 * hf.scale_z
1040 };
1041 let t_max_x = if ray_dir[0].abs() < 1e-12 {
1042 f64::INFINITY
1043 } else {
1044 t_start + (x_boundary - px) / ray_dir[0]
1045 };
1046 let t_max_z = if ray_dir[2].abs() < 1e-12 {
1047 f64::INFINITY
1048 } else {
1049 t_start + (z_boundary - pz) / ray_dir[2]
1050 };
1051 Some(Self {
1052 cell_col,
1053 cell_row,
1054 step_col,
1055 step_row,
1056 t_max_x,
1057 t_max_z,
1058 t_delta_x,
1059 t_delta_z,
1060 t_max: t_exit.min(max_t),
1061 done: false,
1062 })
1063 }
1064 pub fn next_cell(&mut self) -> Option<(usize, usize, f64)> {
1069 if self.done {
1070 return None;
1071 }
1072 let col = self.cell_col;
1073 let row = self.cell_row;
1074 let t_entry = self.t_max_x.min(self.t_max_z);
1075 if t_entry > self.t_max {
1076 self.done = true;
1077 return None;
1078 }
1079 if self.t_max_x < self.t_max_z {
1080 self.cell_col += self.step_col;
1081 self.t_max_x += self.t_delta_x;
1082 } else {
1083 self.cell_row += self.step_row;
1084 self.t_max_z += self.t_delta_z;
1085 }
1086 if col < 0 || row < 0 {
1087 self.done = true;
1088 return None;
1089 }
1090 Some((col as usize, row as usize, t_entry))
1091 }
1092}