From 0e19660eb5122c8c52d5e909927884ad5c50b813 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Thu, 19 Feb 2026 04:46:23 +0400 Subject: Refactor documentation structure and add new specifications - Updated MSH documentation to reflect changes in material, wear, and texture specifications. - Introduced new `render.md` file detailing the render pipeline process. - Removed outdated sections from `runtime-pipeline.md` and redirected to `render.md`. - Added detailed specifications for `Texm` texture format and `WEAR` wear table. - Updated navigation in `mkdocs.yml` to align with new documentation structure. --- crates/render-demo/src/lib.rs | 113 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 crates/render-demo/src/lib.rs (limited to 'crates/render-demo/src/lib.rs') diff --git a/crates/render-demo/src/lib.rs b/crates/render-demo/src/lib.rs new file mode 100644 index 0000000..4c73c09 --- /dev/null +++ b/crates/render-demo/src/lib.rs @@ -0,0 +1,113 @@ +use msh_core::{parse_model_payload, Model}; +use nres::Archive; +use std::path::Path; + +#[derive(Debug)] +pub enum Error { + Nres(nres::error::Error), + Msh(msh_core::error::Error), + NoMshEntries, + ModelNotFound(String), +} + +impl From for Error { + fn from(value: nres::error::Error) -> Self { + Self::Nres(value) + } +} + +impl From for Error { + fn from(value: msh_core::error::Error) -> Self { + Self::Msh(value) + } +} + +pub type Result = core::result::Result; + +pub fn load_model_from_archive(path: &Path, model_name: Option<&str>) -> Result { + let archive = Archive::open_path(path)?; + let mut msh_entries = Vec::new(); + for entry in archive.entries() { + if entry.meta.name.to_ascii_lowercase().ends_with(".msh") { + msh_entries.push((entry.id, entry.meta.name.clone())); + } + } + if msh_entries.is_empty() { + return Err(Error::NoMshEntries); + } + + let target_id = if let Some(name) = model_name { + msh_entries + .iter() + .find(|(_, n)| n.eq_ignore_ascii_case(name)) + .map(|(id, _)| *id) + .ok_or_else(|| Error::ModelNotFound(name.to_string()))? + } else { + msh_entries[0].0 + }; + + let payload = archive.read(target_id)?; + Ok(parse_model_payload(payload.as_slice())?) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::path::{Path, PathBuf}; + + fn collect_files_recursive(root: &Path, out: &mut Vec) { + let Ok(entries) = fs::read_dir(root) else { + return; + }; + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + collect_files_recursive(&path, out); + } else if path.is_file() { + out.push(path); + } + } + } + + fn archive_with_msh() -> Option { + let root = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join("testdata"); + let mut files = Vec::new(); + collect_files_recursive(&root, &mut files); + files.sort(); + for path in files { + let Ok(bytes) = fs::read(&path) else { + continue; + }; + if bytes.get(0..4) != Some(b"NRes") { + continue; + } + let Ok(archive) = Archive::open_path(&path) else { + continue; + }; + if archive + .entries() + .any(|entry| entry.meta.name.to_ascii_lowercase().ends_with(".msh")) + { + return Some(path); + } + } + None + } + + #[test] + fn load_model_from_real_archive() { + let Some(path) = archive_with_msh() else { + eprintln!("skipping load_model_from_real_archive: no .msh archives in testdata"); + return; + }; + let model = load_model_from_archive(&path, None) + .unwrap_or_else(|err| panic!("failed to load model from {}: {err:?}", path.display())); + assert!(model.node_count > 0); + assert!(!model.positions.is_empty()); + assert!(!model.indices.is_empty()); + } +} -- cgit v1.2.3