aboutsummaryrefslogtreecommitdiff
path: root/crates/fparkan-terrain/src
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-10-11 12:12:50 +0300
committerValentin Popov <valentin@popov.link>2026-10-11 12:12:50 +0300
commita8faba8aad441e87f8a8b94c148c01f9c075d621 (patch)
tree97aa4972990edba9991ff2e125edac3a7883f50f /crates/fparkan-terrain/src
parentaa51f3574d3d4d4e0fa59653eedd718fceb5d1bd (diff)
downloadfparkan-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')
-rw-r--r--crates/fparkan-terrain/src/lib.rs73
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]