From 437e8d63073c1a5a360e48e7796eb42720cf64c2 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Fri, 30 Oct 2026 18:01:37 +0400 Subject: fix(camera): constrain free flight to terrain surface bounds --- crates/fparkan-terrain/src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'crates/fparkan-terrain/src') 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; @@ -1214,6 +1221,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"); -- cgit v1.2.3