dreamwell_engine/content/
mesh.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ImportedMesh {
9 pub name: String,
11 pub positions: Vec<[f32; 3]>,
13 pub normals: Vec<[f32; 3]>,
15 pub uvs: Vec<[f32; 2]>,
17 pub indices: Vec<u32>,
19}
20
21impl ImportedMesh {
22 pub fn vertex_count(&self) -> usize {
24 self.positions.len()
25 }
26
27 pub fn triangle_count(&self) -> usize {
29 self.indices.len() / 3
30 }
31
32 pub fn validate(&self) -> Result<(), String> {
34 let n = self.positions.len();
35 if self.normals.len() != n {
36 return Err(format!(
37 "content_mesh_normals_mismatch:expected {n} normals, got {}",
38 self.normals.len()
39 ));
40 }
41 if self.uvs.len() != n {
42 return Err(format!(
43 "content_mesh_uvs_mismatch:expected {n} uvs, got {}",
44 self.uvs.len()
45 ));
46 }
47 if !self.indices.len().is_multiple_of(3) {
48 return Err(format!(
49 "content_mesh_indices_not_triangles:index count {} not divisible by 3",
50 self.indices.len()
51 ));
52 }
53 let max_idx = n as u32;
54 for &idx in &self.indices {
55 if idx >= max_idx {
56 return Err(format!("content_mesh_index_oob:index {idx} >= vertex count {n}"));
57 }
58 }
59 Ok(())
60 }
61}
62
63#[derive(Debug, Clone, Default, Serialize, Deserialize)]
65pub struct ImportedMeshSet {
66 pub meshes: Vec<ImportedMesh>,
67}
68
69impl ImportedMeshSet {
70 pub fn total_vertices(&self) -> usize {
72 self.meshes.iter().map(|m| m.vertex_count()).sum()
73 }
74
75 pub fn total_triangles(&self) -> usize {
77 self.meshes.iter().map(|m| m.triangle_count()).sum()
78 }
79
80 pub fn validate(&self) -> Result<(), String> {
82 for (i, mesh) in self.meshes.iter().enumerate() {
83 mesh.validate().map_err(|e| format!("mesh[{i}] '{}': {e}", mesh.name))?;
84 }
85 Ok(())
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn triangle_mesh() -> ImportedMesh {
94 ImportedMesh {
95 name: "Triangle".into(),
96 positions: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
97 normals: vec![[0.0, 0.0, 1.0]; 3],
98 uvs: vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]],
99 indices: vec![0, 1, 2],
100 }
101 }
102
103 #[test]
104 fn valid_triangle() {
105 let m = triangle_mesh();
106 assert!(m.validate().is_ok());
107 assert_eq!(m.vertex_count(), 3);
108 assert_eq!(m.triangle_count(), 1);
109 }
110
111 #[test]
112 fn normals_mismatch() {
113 let mut m = triangle_mesh();
114 m.normals.pop();
115 assert!(m.validate().unwrap_err().contains("normals_mismatch"));
116 }
117
118 #[test]
119 fn uvs_mismatch() {
120 let mut m = triangle_mesh();
121 m.uvs.pop();
122 assert!(m.validate().unwrap_err().contains("uvs_mismatch"));
123 }
124
125 #[test]
126 fn indices_not_triangles() {
127 let mut m = triangle_mesh();
128 m.indices.push(0);
129 assert!(m.validate().unwrap_err().contains("not_triangles"));
130 }
131
132 #[test]
133 fn index_out_of_bounds() {
134 let mut m = triangle_mesh();
135 m.indices[2] = 99;
136 assert!(m.validate().unwrap_err().contains("index_oob"));
137 }
138
139 #[test]
140 fn mesh_set_totals() {
141 let set = ImportedMeshSet {
142 meshes: vec![triangle_mesh(), triangle_mesh()],
143 };
144 assert_eq!(set.total_vertices(), 6);
145 assert_eq!(set.total_triangles(), 2);
146 assert!(set.validate().is_ok());
147 }
148
149 #[test]
150 fn empty_mesh_set() {
151 let set = ImportedMeshSet::default();
152 assert_eq!(set.total_vertices(), 0);
153 assert!(set.validate().is_ok());
154 }
155}