feat: Enhance model and texture loading with improved error handling and new features
Some checks failed
Test / Lint (push) Failing after 1m10s
Test / Test (push) Has been skipped
Test / Render parity (push) Has been skipped

- Introduced `LoadedModel` and `LoadedTexture` structs for better encapsulation of model and texture data.
- Added functions to load models and textures from archives, including support for resolving textures based on materials and wear entries.
- Implemented error handling for missing textures, materials, and wear entries.
- Updated the rendering pipeline to support texture loading and binding, including command-line arguments for texture customization.
- Enhanced the `texm` crate with new decoding capabilities for various pixel formats, including indexed textures.
- Added tests for texture decoding and loading to ensure reliability and correctness.
- Updated documentation to reflect changes in the material and texture resolution process.
This commit is contained in:
2026-02-19 05:19:18 +04:00
parent 18d4c6cf9f
commit a281ffa32e
12 changed files with 985 additions and 28 deletions

View File

@@ -1,8 +1,14 @@
use msh_core::Model;
#[derive(Clone, Debug)]
pub struct RenderVertex {
pub position: [f32; 3],
pub uv0: [f32; 2],
}
#[derive(Clone, Debug)]
pub struct RenderMesh {
pub vertices: Vec<[f32; 3]>,
pub vertices: Vec<RenderVertex>,
pub batch_count: usize,
}
@@ -18,6 +24,7 @@ impl RenderMesh {
pub fn build_render_mesh(model: &Model, lod: usize, group: usize) -> RenderMesh {
let mut vertices = Vec::new();
let mut batch_count = 0usize;
let uv0 = model.uv0.as_ref();
for node_index in 0..model.node_count {
let Some(slot_idx) = model.slot_index(node_index, lod, group) else {
@@ -48,7 +55,15 @@ pub fn build_render_mesh(model: &Model, lod: usize, group: usize) -> RenderMesh
let Some(pos) = model.positions.get(final_idx) else {
continue;
};
vertices.push(*pos);
let uv = uv0
.and_then(|uvs| uvs.get(final_idx))
.copied()
.map(|packed| [packed[0] as f32 / 1024.0, packed[1] as f32 / 1024.0])
.unwrap_or([0.0, 0.0]);
vertices.push(RenderVertex {
position: *pos,
uv0: uv,
});
}
batch_count += 1;
}
@@ -80,5 +95,25 @@ pub fn compute_bounds(vertices: &[[f32; 3]]) -> Option<([f32; 3], [f32; 3])> {
Some((min_v, max_v))
}
pub fn compute_bounds_for_mesh(vertices: &[RenderVertex]) -> Option<([f32; 3], [f32; 3])> {
let mut iter = vertices.iter();
let first = iter.next()?;
let mut min_v = first.position;
let mut max_v = first.position;
for v in iter {
for i in 0..3 {
if v.position[i] < min_v[i] {
min_v[i] = v.position[i];
}
if v.position[i] > max_v[i] {
max_v[i] = v.position[i];
}
}
}
Some((min_v, max_v))
}
#[cfg(test)]
mod tests;

View File

@@ -74,9 +74,17 @@ fn build_render_mesh_for_real_models() {
if !mesh.vertices.is_empty() {
meshes_non_empty += 1;
}
if compute_bounds(&mesh.vertices).is_some() {
if compute_bounds_for_mesh(&mesh.vertices).is_some() {
bounds_non_empty += 1;
}
for vertex in &mesh.vertices {
assert!(
vertex.uv0[0].is_finite() && vertex.uv0[1].is_finite(),
"UV must be finite for '{}' in {}",
entry.meta.name,
archive_path.display()
);
}
}
}
@@ -99,3 +107,25 @@ fn compute_bounds_handles_empty_and_non_empty() {
assert_eq!(bounds.0, [-2.0, -1.0, 0.5]);
assert_eq!(bounds.1, [1.0, 5.0, 9.0]);
}
#[test]
fn compute_bounds_for_mesh_handles_empty_and_non_empty() {
assert!(compute_bounds_for_mesh(&[]).is_none());
let bounds = compute_bounds_for_mesh(&[
RenderVertex {
position: [1.0, 2.0, 3.0],
uv0: [0.0, 0.0],
},
RenderVertex {
position: [-2.0, 5.0, 0.5],
uv0: [0.2, 0.3],
},
RenderVertex {
position: [0.0, -1.0, 9.0],
uv0: [1.0, 1.0],
},
])
.expect("bounds expected");
assert_eq!(bounds.0, [-2.0, -1.0, 0.5]);
assert_eq!(bounds.1, [1.0, 5.0, 9.0]);
}