aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-07-18 16:01:17 +0300
committerValentin Popov <valentin@popov.link>2026-07-18 16:01:17 +0300
commita858910853da12cb08bf1d7cd4572314a2e49fe1 (patch)
tree377cb683036b90a972022a77635a4fee73ac082b /crates
parent83caf4d6abf95b47046e1b3cc8c3203323ee0eff (diff)
downloadfparkan-a858910853da12cb08bf1d7cd4572314a2e49fe1.tar.xz
fparkan-a858910853da12cb08bf1d7cd4572314a2e49fe1.zip
feat(terrain): encode material manager selectors
Diffstat (limited to 'crates')
-rw-r--r--crates/fparkan-terrain-format/src/lib.rs79
-rw-r--r--crates/fparkan-terrain/src/lib.rs6
2 files changed, 80 insertions, 5 deletions
diff --git a/crates/fparkan-terrain-format/src/lib.rs b/crates/fparkan-terrain-format/src/lib.rs
index ddb439d..6e018c0 100644
--- a/crates/fparkan-terrain-format/src/lib.rs
+++ b/crates/fparkan-terrain-format/src/lib.rs
@@ -74,6 +74,57 @@ pub struct TerrainMaterialLayers {
pub land2_selector: u8,
}
+/// One `World3D` material-manager lookup key.
+///
+/// The original manager selects a WEAR table in the upper 16 bits and its
+/// positional material row in the lower 16 bits.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct TerrainMaterialSelection {
+ /// Zero-based material-manager table index.
+ pub table_index: u16,
+ /// Positional material row in that WEAR table.
+ pub material_index: u16,
+}
+
+impl TerrainMaterialSelection {
+ /// Encodes the original material-manager phase lookup key.
+ #[must_use]
+ pub fn phase_key(self) -> u32 {
+ (u32::from(self.table_index) << 16) | u32::from(self.material_index)
+ }
+}
+
+impl TerrainMaterialLayers {
+ /// Constructs the decoded pair from its on-disk packed tag.
+ #[must_use]
+ pub const fn from_packed_tag(material_tag: u16) -> Self {
+ let [land2_selector, land1] = material_tag.to_le_bytes();
+ Self {
+ land1_selector: if land1 == u8::MAX { None } else { Some(land1) },
+ land2_selector,
+ }
+ }
+
+ /// Returns the table-zero `Land1.wea` selection when it exists.
+ #[must_use]
+ pub fn land1_selection(self) -> Option<TerrainMaterialSelection> {
+ self.land1_selector
+ .map(|material_index| TerrainMaterialSelection {
+ table_index: 0,
+ material_index: u16::from(material_index),
+ })
+ }
+
+ /// Returns the table-one `Land2.wea` selection.
+ #[must_use]
+ pub fn land2_selection(self) -> TerrainMaterialSelection {
+ TerrainMaterialSelection {
+ table_index: 1,
+ material_index: u16::from(self.land2_selector),
+ }
+ }
+}
+
/// Terrain face with 28-byte source layout.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TerrainFace28 {
@@ -97,11 +148,7 @@ impl TerrainFace28 {
/// Decodes the proven two-table selector layout of [`Self::material_tag`].
#[must_use]
pub const fn material_layers(&self) -> TerrainMaterialLayers {
- let [land2_selector, land1] = self.material_tag.to_le_bytes();
- TerrainMaterialLayers {
- land1_selector: if land1 == u8::MAX { None } else { Some(land1) },
- land2_selector,
- }
+ TerrainMaterialLayers::from_packed_tag(self.material_tag)
}
}
@@ -1427,6 +1474,20 @@ mod tests {
land2_selector: 2,
}
);
+ assert_eq!(
+ document.faces[0].material_layers().land1_selection(),
+ Some(TerrainMaterialSelection {
+ table_index: 0,
+ material_index: 1,
+ })
+ );
+ assert_eq!(
+ document.faces[0]
+ .material_layers()
+ .land2_selection()
+ .phase_key(),
+ 0x0001_0002
+ );
raw_face[4..6].copy_from_slice(&0xff03_u16.to_le_bytes());
let nres = decode_nres(&minimal_land_msh(&raw_face)).expect("nres");
@@ -1438,6 +1499,14 @@ mod tests {
land2_selector: 3,
}
);
+ assert_eq!(document.faces[0].material_layers().land1_selection(), None);
+ assert_eq!(
+ document.faces[0].material_layers().land2_selection(),
+ TerrainMaterialSelection {
+ table_index: 1,
+ material_index: 3,
+ }
+ );
}
#[test]
diff --git a/crates/fparkan-terrain/src/lib.rs b/crates/fparkan-terrain/src/lib.rs
index 65146b9..4eef871 100644
--- a/crates/fparkan-terrain/src/lib.rs
+++ b/crates/fparkan-terrain/src/lib.rs
@@ -23,6 +23,12 @@
use fparkan_terrain_format::{FullSurfaceMask, 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};
+
/// Terrain world.
#[derive(Clone, Debug, Default)]
pub struct TerrainWorld {