use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportedMesh {
pub name: String,
pub positions: Vec<[f32; 3]>,
pub normals: Vec<[f32; 3]>,
pub uvs: Vec<[f32; 2]>,
pub indices: Vec<u32>,
}
impl ImportedMesh {
pub fn vertex_count(&self) -> usize {
self.positions.len()
}
pub fn triangle_count(&self) -> usize {
self.indices.len() / 3
}
pub fn validate(&self) -> Result<(), String> {
let n = self.positions.len();
if self.normals.len() != n {
return Err(format!(
"content_mesh_normals_mismatch:expected {n} normals, got {}",
self.normals.len()
));
}
if self.uvs.len() != n {
return Err(format!(
"content_mesh_uvs_mismatch:expected {n} uvs, got {}",
self.uvs.len()
));
}
if !self.indices.len().is_multiple_of(3) {
return Err(format!(
"content_mesh_indices_not_triangles:index count {} not divisible by 3",
self.indices.len()
));
}
let max_idx = n as u32;
for &idx in &self.indices {
if idx >= max_idx {
return Err(format!("content_mesh_index_oob:index {idx} >= vertex count {n}"));
}
}
Ok(())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ImportedMeshSet {
pub meshes: Vec<ImportedMesh>,
}
impl ImportedMeshSet {
pub fn total_vertices(&self) -> usize {
self.meshes.iter().map(|m| m.vertex_count()).sum()
}
pub fn total_triangles(&self) -> usize {
self.meshes.iter().map(|m| m.triangle_count()).sum()
}
pub fn validate(&self) -> Result<(), String> {
for (i, mesh) in self.meshes.iter().enumerate() {
mesh.validate().map_err(|e| format!("mesh[{i}] '{}': {e}", mesh.name))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn triangle_mesh() -> ImportedMesh {
ImportedMesh {
name: "Triangle".into(),
positions: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
normals: vec![[0.0, 0.0, 1.0]; 3],
uvs: vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]],
indices: vec![0, 1, 2],
}
}
#[test]
fn valid_triangle() {
let m = triangle_mesh();
assert!(m.validate().is_ok());
assert_eq!(m.vertex_count(), 3);
assert_eq!(m.triangle_count(), 1);
}
#[test]
fn normals_mismatch() {
let mut m = triangle_mesh();
m.normals.pop();
assert!(m.validate().unwrap_err().contains("normals_mismatch"));
}
#[test]
fn uvs_mismatch() {
let mut m = triangle_mesh();
m.uvs.pop();
assert!(m.validate().unwrap_err().contains("uvs_mismatch"));
}
#[test]
fn indices_not_triangles() {
let mut m = triangle_mesh();
m.indices.push(0);
assert!(m.validate().unwrap_err().contains("not_triangles"));
}
#[test]
fn index_out_of_bounds() {
let mut m = triangle_mesh();
m.indices[2] = 99;
assert!(m.validate().unwrap_err().contains("index_oob"));
}
#[test]
fn mesh_set_totals() {
let set = ImportedMeshSet {
meshes: vec![triangle_mesh(), triangle_mesh()],
};
assert_eq!(set.total_vertices(), 6);
assert_eq!(set.total_triangles(), 2);
assert!(set.validate().is_ok());
}
#[test]
fn empty_mesh_set() {
let set = ImportedMeshSet::default();
assert_eq!(set.total_vertices(), 0);
assert!(set.validate().is_ok());
}
}