From a281ffa32ea615670d369503692f057b2dc60e6f Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Thu, 19 Feb 2026 05:19:18 +0400 Subject: feat: Enhance model and texture loading with improved error handling and new features - 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. --- crates/render-core/README.md | 2 +- crates/render-core/src/lib.rs | 39 +++++++++++++++++++++++++++++++++++++-- crates/render-core/src/tests.rs | 32 +++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 4 deletions(-) (limited to 'crates/render-core') diff --git a/crates/render-core/README.md b/crates/render-core/README.md index 1b58aec..a58f64f 100644 --- a/crates/render-core/README.md +++ b/crates/render-core/README.md @@ -5,7 +5,7 @@ CPU-подготовка draw-данных для моделей `MSH`. Покрывает: - обход `node -> slot -> batch`; -- раскрытие индексов в triangle-list (`Vec<[f32;3]>`); +- раскрытие индексов в triangle-list (`position + uv0`); - расчёт bounds по вершинам. Тесты: diff --git a/crates/render-core/src/lib.rs b/crates/render-core/src/lib.rs index 8e0b5e8..ddb93fb 100644 --- a/crates/render-core/src/lib.rs +++ b/crates/render-core/src/lib.rs @@ -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, 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; diff --git a/crates/render-core/src/tests.rs b/crates/render-core/src/tests.rs index 9c5eb5d..22103c6 100644 --- a/crates/render-core/src/tests.rs +++ b/crates/render-core/src/tests.rs @@ -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]); +} -- cgit v1.2.3