diff options
| author | Valentin Popov <valentin@popov.link> | 2026-02-19 13:09:18 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-02-19 13:09:18 +0300 |
| commit | bb827c3928ee6fc56c04e503be9f39ae70efee67 (patch) | |
| tree | 9d1af6595567517bcee3bddbcf7fefedce5dc5fe /crates/render-core | |
| parent | efab61a45c8837d3c2aaec464d8f6243fecb7a38 (diff) | |
| download | fparkan-bb827c3928ee6fc56c04e503be9f39ae70efee67.tar.xz fparkan-bb827c3928ee6fc56c04e503be9f39ae70efee67.zip | |
feat: Refactor code structure and enhance functionality across multiple crates
Diffstat (limited to 'crates/render-core')
| -rw-r--r-- | crates/render-core/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/render-core/src/lib.rs | 70 | ||||
| -rw-r--r-- | crates/render-core/src/tests.rs | 53 |
3 files changed, 89 insertions, 35 deletions
diff --git a/crates/render-core/Cargo.toml b/crates/render-core/Cargo.toml index b856d12..c93d624 100644 --- a/crates/render-core/Cargo.toml +++ b/crates/render-core/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" msh-core = { path = "../msh-core" } [dev-dependencies] +common = { path = "../common" } nres = { path = "../nres" } diff --git a/crates/render-core/src/lib.rs b/crates/render-core/src/lib.rs index d06761a..c7a69d6 100644 --- a/crates/render-core/src/lib.rs +++ b/crates/render-core/src/lib.rs @@ -1,4 +1,5 @@ use msh_core::Model; +use std::collections::HashMap; pub const DEFAULT_UV_SCALE: f32 = 1024.0; @@ -11,21 +12,24 @@ pub struct RenderVertex { #[derive(Clone, Debug)] pub struct RenderMesh { pub vertices: Vec<RenderVertex>, + pub indices: Vec<u16>, pub batch_count: usize, + pub index_overflow: bool, } impl RenderMesh { pub fn triangle_count(&self) -> usize { - self.vertices.len() / 3 + self.indices.len() / 3 } } -/// Builds an expanded triangle list for a specific LOD/group pair. -/// -/// The output is suitable for simple `glDrawArrays(GL_TRIANGLES, ...)` paths. +/// Builds an indexed triangle mesh for a specific LOD/group pair. pub fn build_render_mesh(model: &Model, lod: usize, group: usize) -> RenderMesh { let mut vertices = Vec::new(); + let mut indices = Vec::new(); + let mut index_remap: HashMap<usize, u16> = HashMap::new(); let mut batch_count = 0usize; + let mut index_overflow = false; let uv0 = model.uv0.as_ref(); for node_index in 0..model.node_count { @@ -49,36 +53,62 @@ pub fn build_render_mesh(model: &Model, lod: usize, group: usize) -> RenderMesh continue; } + let batch_out_start = indices.len(); + let mut batch_valid = true; for &idx in &model.indices[index_start..index_end] { let final_idx_u64 = u64::from(batch.base_vertex).saturating_add(u64::from(idx)); let Ok(final_idx) = usize::try_from(final_idx_u64) else { - continue; + batch_valid = false; + break; }; let Some(pos) = model.positions.get(final_idx) else { - continue; + batch_valid = false; + break; }; - let uv = uv0 - .and_then(|uvs| uvs.get(final_idx)) - .copied() - .map(|packed| { - [ - packed[0] as f32 / DEFAULT_UV_SCALE, - packed[1] as f32 / DEFAULT_UV_SCALE, - ] - }) - .unwrap_or([0.0, 0.0]); - vertices.push(RenderVertex { - position: *pos, - uv0: uv, - }); + + let local_index = if let Some(&mapped) = index_remap.get(&final_idx) { + mapped + } else { + let Ok(mapped) = u16::try_from(vertices.len()) else { + index_overflow = true; + batch_valid = false; + break; + }; + let uv = uv0 + .and_then(|uvs| uvs.get(final_idx)) + .copied() + .map(|packed| { + [ + packed[0] as f32 / DEFAULT_UV_SCALE, + packed[1] as f32 / DEFAULT_UV_SCALE, + ] + }) + .unwrap_or([0.0, 0.0]); + vertices.push(RenderVertex { + position: *pos, + uv0: uv, + }); + index_remap.insert(final_idx, mapped); + mapped + }; + + indices.push(local_index); } + + if !batch_valid { + indices.truncate(batch_out_start); + continue; + } + batch_count += 1; } } RenderMesh { vertices, + indices, batch_count, + index_overflow, } } diff --git a/crates/render-core/src/tests.rs b/crates/render-core/src/tests.rs index c9b55a0..1c5285e 100644 --- a/crates/render-core/src/tests.rs +++ b/crates/render-core/src/tests.rs @@ -1,23 +1,10 @@ use super::*; +use common::collect_files_recursive; use msh_core::parse_model_payload; use nres::Archive; 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 nres_test_files() -> Vec<PathBuf> { let root = Path::new(env!("CARGO_MANIFEST_DIR")) .join("..") @@ -71,12 +58,20 @@ fn build_render_mesh_for_real_models() { ) }); let mesh = build_render_mesh(&model, 0, 0); - if !mesh.vertices.is_empty() { + if !mesh.indices.is_empty() { meshes_non_empty += 1; } if compute_bounds_for_mesh(&mesh.vertices).is_some() { bounds_non_empty += 1; } + for &index in &mesh.indices { + assert!( + usize::from(index) < mesh.vertices.len(), + "index out of bounds for '{}' in {}", + entry.meta.name, + archive_path.display() + ); + } for vertex in &mesh.vertices { assert!( vertex.uv0[0].is_finite() && vertex.uv0[1].is_finite(), @@ -189,6 +184,7 @@ fn build_render_mesh_handles_empty_slot_model() { let mesh = build_render_mesh(&model, 0, 0); assert!(mesh.vertices.is_empty()); + assert!(mesh.indices.is_empty()); assert_eq!(mesh.batch_count, 0); assert_eq!(mesh.triangle_count(), 0); } @@ -225,9 +221,36 @@ fn build_render_mesh_supports_multi_node_and_uv_scaling() { let mesh = build_render_mesh(&model, 0, 0); assert_eq!(mesh.batch_count, 2); assert_eq!(mesh.vertices.len(), 6); + assert_eq!(mesh.indices, vec![0, 1, 2, 3, 4, 5]); assert_eq!(mesh.triangle_count(), 2); assert_eq!(mesh.vertices[0].uv0, [1.0, -1.0]); assert_eq!(mesh.vertices[1].uv0, [0.5, 0.25]); assert_eq!(mesh.vertices[2].uv0, [0.0, 0.0]); assert_eq!(mesh.vertices[3].uv0, [1.0, 1.0]); } + +#[test] +fn build_render_mesh_deduplicates_shared_vertices() { + let model = msh_core::Model { + node_stride: 38, + node_count: 1, + nodes_raw: nodes_with_slot_refs(&[Some(0)]), + slots: vec![slot(0, 1)], + positions: vec![ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [1.0, 1.0, 0.0], + ], + normals: None, + uv0: None, + indices: vec![0, 1, 2, 2, 1, 3], + batches: vec![batch(0, 6, 0)], + node_names: None, + }; + + let mesh = build_render_mesh(&model, 0, 0); + assert_eq!(mesh.vertices.len(), 4); + assert_eq!(mesh.indices, vec![0, 1, 2, 2, 1, 3]); + assert_eq!(mesh.triangle_count(), 2); +} |
