feat(terrain): encode material manager selectors
This commit is contained in:
@@ -45,6 +45,7 @@ use fparkan_runtime::{
|
|||||||
loaded_mission_assets, loaded_mission_object_drafts, loaded_terrain, EngineConfig, EngineMode,
|
loaded_mission_assets, loaded_mission_object_drafts, loaded_terrain, EngineConfig, EngineMode,
|
||||||
EngineServices, MissionAssets, MissionLoadPhase, MissionObjectDraft, MissionRequest,
|
EngineServices, MissionAssets, MissionLoadPhase, MissionObjectDraft, MissionRequest,
|
||||||
};
|
};
|
||||||
|
use fparkan_terrain::TerrainMaterialLayers;
|
||||||
use fparkan_terrain::TerrainWorld;
|
use fparkan_terrain::TerrainWorld;
|
||||||
use fparkan_vfs::DirectoryVfs;
|
use fparkan_vfs::DirectoryVfs;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -477,9 +478,11 @@ fn static_preview_terrain_base_materials(
|
|||||||
packed_tags
|
packed_tags
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|packed_tag| {
|
.map(|packed_tag| {
|
||||||
let selector = packed_tag & 0x00ff;
|
let selection = TerrainMaterialLayers::from_packed_tag(packed_tag).land2_selection();
|
||||||
let selected = load_standalone_wear_material_texture_mip0_rgba8_from_root(
|
let selected = load_standalone_wear_material_texture_mip0_rgba8_from_root(
|
||||||
root, &wear_path, selector,
|
root,
|
||||||
|
&wear_path,
|
||||||
|
selection.material_index,
|
||||||
)
|
)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
format!(
|
format!(
|
||||||
|
|||||||
@@ -74,6 +74,57 @@ pub struct TerrainMaterialLayers {
|
|||||||
pub land2_selector: u8,
|
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.
|
/// Terrain face with 28-byte source layout.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct TerrainFace28 {
|
pub struct TerrainFace28 {
|
||||||
@@ -97,11 +148,7 @@ impl TerrainFace28 {
|
|||||||
/// Decodes the proven two-table selector layout of [`Self::material_tag`].
|
/// Decodes the proven two-table selector layout of [`Self::material_tag`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn material_layers(&self) -> TerrainMaterialLayers {
|
pub const fn material_layers(&self) -> TerrainMaterialLayers {
|
||||||
let [land2_selector, land1] = self.material_tag.to_le_bytes();
|
TerrainMaterialLayers::from_packed_tag(self.material_tag)
|
||||||
TerrainMaterialLayers {
|
|
||||||
land1_selector: if land1 == u8::MAX { None } else { Some(land1) },
|
|
||||||
land2_selector,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1427,6 +1474,20 @@ mod tests {
|
|||||||
land2_selector: 2,
|
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());
|
raw_face[4..6].copy_from_slice(&0xff03_u16.to_le_bytes());
|
||||||
let nres = decode_nres(&minimal_land_msh(&raw_face)).expect("nres");
|
let nres = decode_nres(&minimal_land_msh(&raw_face)).expect("nres");
|
||||||
@@ -1438,6 +1499,14 @@ mod tests {
|
|||||||
land2_selector: 3,
|
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]
|
#[test]
|
||||||
|
|||||||
@@ -23,6 +23,12 @@
|
|||||||
use fparkan_terrain_format::{FullSurfaceMask, LandMapDocument, LandMeshDocument};
|
use fparkan_terrain_format::{FullSurfaceMask, LandMapDocument, LandMeshDocument};
|
||||||
use std::collections::VecDeque;
|
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.
|
/// Terrain world.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct TerrainWorld {
|
pub struct TerrainWorld {
|
||||||
|
|||||||
@@ -1559,7 +1559,11 @@ two WEAR selectors must not be collapsed into an invented one-layer material,
|
|||||||
and the type-18 `aux18` stream is now documented as microtexture mapping data.
|
and the type-18 `aux18` stream is now documented as microtexture mapping data.
|
||||||
`TerrainFace28::material_layers` exposes the evidenced high-byte `Land1`
|
`TerrainFace28::material_layers` exposes the evidenced high-byte `Land1`
|
||||||
selector (with `0xff` absent sentinel) and low-byte `Land2` selector. The
|
selector (with `0xff` absent sentinel) and low-byte `Land2` selector. The
|
||||||
manager's actual blend/pass operation remains unrecovered.
|
manager's actual blend/pass operation remains unrecovered. `World3D.dll`
|
||||||
|
supplies the manager ABI: its load slot appends `Land1` as table 0 and `Land2`
|
||||||
|
as table 1, while its material-phase lookup receives the exact 32-bit selector
|
||||||
|
`(table_index << 16) | material_index`. `TerrainMaterialSelection` preserves
|
||||||
|
that key for the renderer without assuming how the two phases are blended.
|
||||||
|
|
||||||
The static Vulkan bridge now carries each contiguous face run as a separate
|
The static Vulkan bridge now carries each contiguous face run as a separate
|
||||||
draw range keyed by the original packed `material_tag`; it neither reorders
|
draw range keyed by the original packed `material_tag`; it neither reorders
|
||||||
|
|||||||
Reference in New Issue
Block a user