aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-07-18 15:41:53 +0300
committerValentin Popov <valentin@popov.link>2026-07-18 15:41:53 +0300
commit4df46865091fe12a3e04606d4710521516f35d85 (patch)
treed79eb47661b0922a5a4d040eb0d8022f221b66d5 /crates
parente0f0e4a18ef9f2b6ca34917d4436e42eeb7f1eb1 (diff)
downloadfparkan-4df46865091fe12a3e04606d4710521516f35d85.tar.xz
fparkan-4df46865091fe12a3e04606d4710521516f35d85.zip
feat(terrain): bind map base textures
Diffstat (limited to 'crates')
-rw-r--r--crates/fparkan-inspection/src/lib.rs66
1 files changed, 66 insertions, 0 deletions
diff --git a/crates/fparkan-inspection/src/lib.rs b/crates/fparkan-inspection/src/lib.rs
index 7a6a2c7..e2b9068 100644
--- a/crates/fparkan-inspection/src/lib.rs
+++ b/crates/fparkan-inspection/src/lib.rs
@@ -484,6 +484,43 @@ pub fn load_wear_material_texture_mip0_rgba8_from_root(
})
}
+/// Resolves phase-zero diffuse TEXM through a standalone map-local WEAR file.
+///
+/// Map terrain keeps its two WEAR tables adjacent to `Land.msh`, while their
+/// MAT0 and TEXM resources remain in the game root's normal archives. This
+/// preserves that split instead of treating the sidecar as an archive entry.
+///
+/// # Errors
+///
+/// Returns a string error if the standalone WEAR file, global material
+/// resources, or selected diffuse TEXM cannot be decoded.
+pub fn load_standalone_wear_material_texture_mip0_rgba8_from_root(
+ root: &Path,
+ wear_path: &Path,
+ material_index: u16,
+) -> Result<WearMaterialTexture, String> {
+ let wear_bytes =
+ fs::read(wear_path).map_err(|err| format!("{}: {err}", wear_path.display()))?;
+ let wear = decode_wear(&wear_bytes).map_err(|err| err.to_string())?;
+ let repository = CachedResourceRepository::new(Arc::new(DirectoryVfs::new(root)));
+ let material =
+ resolve_material(&repository, &wear, material_index).map_err(|err| err.to_string())?;
+ let texture = material.document.primary_texture().ok_or_else(|| {
+ "MAT0 phase zero declares an intentionally untextured material".to_string()
+ })?;
+ let texture_name = String::from_utf8_lossy(&texture.0).into_owned();
+ let image = load_texture_mip0_rgba8_from_root(root, "Textures.lib", &texture_name)?;
+ Ok(WearMaterialTexture {
+ wear_archive: "<standalone>".to_string(),
+ wear_resource: wear_path.display().to_string(),
+ material_index,
+ material_name: String::from_utf8_lossy(&material.name.0).into_owned(),
+ material_fallback: material.fallback,
+ texture_name,
+ image,
+ })
+}
+
/// Inspects a terrain land file by path.
///
/// # Errors
@@ -814,6 +851,35 @@ mod tests {
}
#[test]
+ fn standalone_wear_material_texture_loader_keeps_map_sidecar_separate() {
+ let dir = temp_dir("standalone-wear-material-texture");
+ let wear_path = dir.join("Land2.wea");
+ fs::write(&wear_path, b"1\n0 MAT\n").expect("standalone wear");
+ let mut mat0 = vec![0; 4 + 34];
+ mat0[0..2].copy_from_slice(&1_u16.to_le_bytes());
+ mat0[22..25].copy_from_slice(b"TEX");
+ fs::write(
+ dir.join("material.lib"),
+ build_single_entry_nres_with_meta(b"MAT", fparkan_material::MAT0_KIND, 0, &mat0),
+ )
+ .expect("material archive");
+ fs::write(
+ dir.join("Textures.lib"),
+ build_single_entry_nres(b"TEX", &texm_argb8888_pixel([0x40, 0x11, 0x22, 0x33])),
+ )
+ .expect("texture archive");
+
+ let selected =
+ load_standalone_wear_material_texture_mip0_rgba8_from_root(&dir, &wear_path, 0)
+ .expect("resolved material texture");
+
+ assert_eq!(selected.wear_archive, "<standalone>");
+ assert_eq!(selected.wear_resource, wear_path.display().to_string());
+ assert_eq!(selected.texture_name, "TEX");
+ assert_eq!(selected.image.rgba8, vec![0x11, 0x22, 0x33, 0x40]);
+ }
+
+ #[test]
fn land_mesh_bounds_preserve_each_source_axis() {
let mesh = LandMeshDocument {
streams: Vec::new(),