aboutsummaryrefslogtreecommitdiff
path: root/crates/render-demo/src/lib.rs
blob: 4c73c094470a9d6c777bee5a4fb7993c3b64a8fd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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<nres::error::Error> for Error {
    fn from(value: nres::error::Error) -> Self {
        Self::Nres(value)
    }
}

impl From<msh_core::error::Error> for Error {
    fn from(value: msh_core::error::Error) -> Self {
        Self::Msh(value)
    }
}

pub type Result<T> = core::result::Result<T, Error>;

pub fn load_model_from_archive(path: &Path, model_name: Option<&str>) -> Result<Model> {
    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<PathBuf>) {
        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<PathBuf> {
        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());
    }
}