diff options
| author | Valentin Popov <valentin@popov.link> | 2026-10-30 17:01:37 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-10-30 17:01:37 +0300 |
| commit | 437e8d63073c1a5a360e48e7796eb42720cf64c2 (patch) | |
| tree | 05598afab62f88d258524f238a8a3ca5e498681c /crates/fparkan-terrain | |
| parent | 146db05e12a7ef40e1e313307127cb73919772eb (diff) | |
| download | fparkan-437e8d63073c1a5a360e48e7796eb42720cf64c2.tar.xz fparkan-437e8d63073c1a5a360e48e7796eb42720cf64c2.zip | |
fix(camera): constrain free flight to terrain surface bounds
Diffstat (limited to 'crates/fparkan-terrain')
| -rw-r--r-- | crates/fparkan-terrain/src/lib.rs | 16 |
1 files changed, 16 insertions, 0 deletions
diff --git a/crates/fparkan-terrain/src/lib.rs b/crates/fparkan-terrain/src/lib.rs index 54946e7..5e17a23 100644 --- a/crates/fparkan-terrain/src/lib.rs +++ b/crates/fparkan-terrain/src/lib.rs @@ -409,6 +409,13 @@ impl TerrainWorld { self.surfaces.len() } + /// Bounds of the indexed surface geometry, excluding unused source vertices. + #[must_use] + pub fn surface_bounds(&self) -> Option<[[f32; 3]; 2]> { + let root = self.surface_bvh.nodes.get(self.surface_bvh.root?)?; + Some([root.min, root.max]) + } + /// Returns the validated source mesh retained for renderer integration. /// /// Runtime collision queries use their own compact triangle representation; @@ -1215,6 +1222,15 @@ mod tests { } #[test] + fn surface_bounds_cover_query_geometry_and_ignore_unused_positions() { + let mut mesh = synthetic_land_mesh(); + mesh.positions.push([1000.0, -1000.0, 1000.0]); + let world = TerrainWorld::from_land_msh(&mesh).expect("world"); + assert_eq!(world.surface_bounds(), Some([[0.0; 3], [1.0, 1.0, 2.0]])); + assert_eq!(TerrainWorld::default().surface_bounds(), None); + } + + #[test] fn synthetic_surface_height_and_raycast_work() { let world = TerrainWorld::from_land_msh(&synthetic_land_mesh()).expect("world"); |
