diff options
| author | Valentin Popov <valentin@popov.link> | 2026-10-11 12:12:50 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-10-11 12:12:50 +0300 |
| commit | a8faba8aad441e87f8a8b94c148c01f9c075d621 (patch) | |
| tree | 97aa4972990edba9991ff2e125edac3a7883f50f /crates/fparkan-terrain/src/lib.rs | |
| parent | aa51f3574d3d4d4e0fa59653eedd718fceb5d1bd (diff) | |
| download | fparkan-a8faba8aad441e87f8a8b94c148c01f9c075d621.tar.xz fparkan-a8faba8aad441e87f8a8b94c148c01f9c075d621.zip | |
feat: complete map viewer scene and static CTL pose preview
Complete the interactive mission viewer with environment rendering, audio events, dynamic shadows, free-flight camera controls, and per-component CTL pose sampling.
Diffstat (limited to 'crates/fparkan-terrain/src/lib.rs')
| -rw-r--r-- | crates/fparkan-terrain/src/lib.rs | 73 |
1 files changed, 70 insertions, 3 deletions
diff --git a/crates/fparkan-terrain/src/lib.rs b/crates/fparkan-terrain/src/lib.rs index 7dced19..cd2a874 100644 --- a/crates/fparkan-terrain/src/lib.rs +++ b/crates/fparkan-terrain/src/lib.rs @@ -1,14 +1,16 @@ #![forbid(unsafe_code)] //! Validated terrain runtime queries using XY ground coordinates and Z height. -use fparkan_terrain_format::{FullSurfaceMask, LandMapDocument, LandMeshDocument}; +use fparkan_terrain_format::{LandMapDocument, LandMeshDocument}; use std::collections::VecDeque; /// Terrain material-selector contract preserved from the decoded map mesh. /// /// Applications access this semantic terrain API through this terrain crate /// rather than taking a direct dependency on the binary-format parser. -pub use fparkan_terrain_format::{TerrainMaterialLayers, TerrainMaterialSelection}; +pub use fparkan_terrain_format::{ + FullSurfaceMask, TerrainMaterialLayers, TerrainMaterialSelection, +}; /// Terrain world. #[derive(Clone, Debug, Default)] @@ -141,6 +143,21 @@ pub trait SurfaceQuery { direction: [f32; 3], mask: FullSurfaceMask, ) -> Result<Option<SurfaceHit>, TerrainError>; + + /// Raycast while requiring every bit in `include` and rejecting every + /// face carrying a bit in `exclude`. + /// + /// The native landscape query has both predicates. The legacy + /// [`Self::raycast`] shorthand keeps its historical include-only form; + /// callers that model a native receiver or visibility test should use + /// this method so exclusion flags cannot be mistaken for inclusion bits. + fn raycast_excluding( + &self, + origin: [f32; 3], + direction: [f32; 3], + include: FullSurfaceMask, + exclude: FullSurfaceMask, + ) -> Result<Option<SurfaceHit>, TerrainError>; } /// Navigation query. @@ -405,6 +422,28 @@ impl SurfaceQuery for TerrainWorld { direction: [f32; 3], mask: FullSurfaceMask, ) -> Result<Option<SurfaceHit>, TerrainError> { + self.raycast_filtered(origin, direction, mask, FullSurfaceMask(0)) + } + + fn raycast_excluding( + &self, + origin: [f32; 3], + direction: [f32; 3], + include: FullSurfaceMask, + exclude: FullSurfaceMask, + ) -> Result<Option<SurfaceHit>, TerrainError> { + self.raycast_filtered(origin, direction, include, exclude) + } +} + +impl TerrainWorld { + fn raycast_filtered( + &self, + origin: [f32; 3], + direction: [f32; 3], + include: FullSurfaceMask, + exclude: FullSurfaceMask, + ) -> Result<Option<SurfaceHit>, TerrainError> { if self.surfaces.is_empty() { return Err(TerrainError::Unsupported); } @@ -413,7 +452,10 @@ impl SurfaceQuery for TerrainWorld { let mut best: Option<SurfaceHit> = None; for index in candidates { let triangle = &self.surfaces[index]; - if mask.0 != 0 && triangle.mask.0 & mask.0 == 0 { + if include.0 != 0 && triangle.mask.0 & include.0 != include.0 { + continue; + } + if triangle.mask.0 & exclude.0 != 0 { continue; } let Some(distance) = triangle.raycast(origin, direction) else { @@ -1032,6 +1074,31 @@ mod tests { .expect("raycast"), None ); + + // Native include masks require every requested bit, while exclusion + // masks reject any matching face bit. + assert_eq!( + world + .raycast_excluding( + [0.25, 0.25, 2.0], + [0.0, 0.0, -1.0], + FullSurfaceMask(0x0000_0003), + FullSurfaceMask(0), + ) + .expect("raycast include all bits"), + None + ); + assert_eq!( + world + .raycast_excluding( + [0.25, 0.25, 2.0], + [0.0, 0.0, -1.0], + FullSurfaceMask(0), + FullSurfaceMask(0x0000_0001), + ) + .expect("raycast exclusion"), + None + ); } #[test] |
