1#![allow(clippy::field_reassign_with_default)]
6
7#[allow(unused_imports)]
8use super::functions::*;
9#[allow(unused_imports)]
10use crate::obj::types::*;
11#[cfg(test)]
12mod tests_expanded {
13 use super::*;
14 #[test]
15 fn test_vertex_color_lerp() {
16 let c0 = ObjVertexColor::rgb(0.0, 0.0, 0.0);
17 let c1 = ObjVertexColor::rgb(1.0, 1.0, 1.0);
18 let mid = c0.lerp(c1, 0.5);
19 assert!((mid.r - 0.5).abs() < 1e-10);
20 assert!((mid.g - 0.5).abs() < 1e-10);
21 assert!((mid.b - 0.5).abs() < 1e-10);
22 }
23 #[test]
24 fn test_vertex_color_to_array() {
25 let c = ObjVertexColor::rgba(0.1, 0.2, 0.3, 0.4);
26 let arr = c.to_array();
27 assert!((arr[0] - 0.1).abs() < 1e-10);
28 assert!((arr[3] - 0.4).abs() < 1e-10);
29 }
30 #[test]
31 fn test_transform_identity() {
32 let t = MeshTransform::identity();
33 let p = [1.0_f64, 2.0, 3.0];
34 let out = t.apply(p);
35 assert!((out[0] - 1.0).abs() < 1e-10);
36 assert!((out[1] - 2.0).abs() < 1e-10);
37 assert!((out[2] - 3.0).abs() < 1e-10);
38 }
39 #[test]
40 fn test_transform_translation() {
41 let t = MeshTransform::from_translation(1.0, 2.0, 3.0);
42 let p = [0.0_f64, 0.0, 0.0];
43 let out = t.apply(p);
44 assert!((out[0] - 1.0).abs() < 1e-10);
45 assert!((out[1] - 2.0).abs() < 1e-10);
46 assert!((out[2] - 3.0).abs() < 1e-10);
47 }
48 #[test]
49 fn test_transform_scale() {
50 let t = MeshTransform {
51 translation: [0.0; 3],
52 scale: 2.0,
53 axis: [0.0, 0.0, 1.0],
54 angle: 0.0,
55 };
56 let p = [1.0_f64, 1.0, 1.0];
57 let out = t.apply(p);
58 assert!((out[0] - 2.0).abs() < 1e-10);
59 assert!((out[1] - 2.0).abs() < 1e-10);
60 }
61 #[test]
62 fn test_transform_rotation_z_90() {
63 let t = MeshTransform {
64 translation: [0.0; 3],
65 scale: 1.0,
66 axis: [0.0, 0.0, 1.0],
67 angle: std::f64::consts::FRAC_PI_2,
68 };
69 let p = [1.0_f64, 0.0, 0.0];
70 let out = t.apply(p);
71 assert!((out[0] - 0.0).abs() < 1e-10, "x={}", out[0]);
72 assert!((out[1] - 1.0).abs() < 1e-10, "y={}", out[1]);
73 assert!(out[2].abs() < 1e-10);
74 }
75 #[test]
76 fn test_weld_vertices_exact() {
77 let mut mesh = ObjMesh::default();
78 mesh.vertices = vec![[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
79 mesh.faces.push(ObjFace {
80 vertex_indices: vec![0, 1, 2],
81 normal_indices: None,
82 uv_indices: None,
83 smoothing_group: 0,
84 material: None,
85 });
86 let welded = weld_vertices(&mesh, 1e-9);
87 assert_eq!(welded.vertices.len(), 2, "should collapse duplicate vertex");
88 }
89 #[test]
90 fn test_weld_vertices_no_duplicates() {
91 let mut mesh = ObjMesh::default();
92 mesh.vertices = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
93 mesh.faces.push(ObjFace {
94 vertex_indices: vec![0, 1, 2],
95 normal_indices: None,
96 uv_indices: None,
97 smoothing_group: 0,
98 material: None,
99 });
100 let welded = weld_vertices(&mesh, 1e-9);
101 assert_eq!(welded.vertices.len(), 3);
102 }
103 #[test]
104 fn test_weld_vertices_tolerance() {
105 let mut mesh = ObjMesh::default();
106 mesh.vertices = vec![[0.0, 0.0, 0.0], [0.0001, 0.0, 0.0], [1.0, 0.0, 0.0]];
107 mesh.faces.push(ObjFace {
108 vertex_indices: vec![0, 1, 2],
109 normal_indices: None,
110 uv_indices: None,
111 smoothing_group: 0,
112 material: None,
113 });
114 let welded = weld_vertices(&mesh, 0.001);
115 assert_eq!(welded.vertices.len(), 2);
116 }
117 #[test]
118 fn test_merge_obj_meshes_vertex_count() {
119 let mut a = ObjMesh::default();
120 a.vertices = vec![[0.0; 3]; 3];
121 a.faces.push(ObjFace {
122 vertex_indices: vec![0, 1, 2],
123 normal_indices: None,
124 uv_indices: None,
125 smoothing_group: 0,
126 material: None,
127 });
128 let mut b = ObjMesh::default();
129 b.vertices = vec![[1.0; 3]; 4];
130 b.faces.push(ObjFace {
131 vertex_indices: vec![0, 1, 2],
132 normal_indices: None,
133 uv_indices: None,
134 smoothing_group: 0,
135 material: None,
136 });
137 let merged = merge_obj_meshes(&a, &b);
138 assert_eq!(merged.vertices.len(), 7);
139 assert_eq!(merged.faces.len(), 2);
140 assert_eq!(merged.faces[1].vertex_indices[0], 3);
141 }
142 #[test]
143 fn test_merge_obj_meshes_empty_a() {
144 let a = ObjMesh::default();
145 let mut b = ObjMesh::default();
146 b.vertices = vec![[1.0, 2.0, 3.0]];
147 let merged = merge_obj_meshes(&a, &b);
148 assert_eq!(merged.vertices.len(), 1);
149 }
150 #[test]
151 fn test_recompute_normals_flat_triangle() {
152 let mut mesh = ObjMesh::default();
153 mesh.vertices = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
154 mesh.faces.push(ObjFace {
155 vertex_indices: vec![0, 1, 2],
156 normal_indices: None,
157 uv_indices: None,
158 smoothing_group: 0,
159 material: None,
160 });
161 recompute_normals(&mut mesh);
162 assert_eq!(mesh.normals.len(), 3);
163 for n in &mesh.normals {
164 assert!(n[2] > 0.9, "normal should face +z: {:?}", n);
165 }
166 }
167 #[test]
168 fn test_recompute_normals_sets_face_indices() {
169 let mut mesh = ObjMesh::default();
170 mesh.vertices = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
171 mesh.faces.push(ObjFace {
172 vertex_indices: vec![0, 1, 2],
173 normal_indices: None,
174 uv_indices: None,
175 smoothing_group: 0,
176 material: None,
177 });
178 recompute_normals(&mut mesh);
179 assert!(mesh.faces[0].normal_indices.is_some());
180 assert_eq!(mesh.faces[0].normal_indices.as_ref().unwrap(), &[0, 1, 2]);
181 }
182 #[test]
183 fn test_parse_mtl_basic() {
184 let data = "newmtl Mat1\nKd 1.0 0.0 0.0\nKs 0.5 0.5 0.5\nNs 32.0\nd 0.8\n";
185 let mats = parse_mtl(data);
186 assert_eq!(mats.len(), 1);
187 assert_eq!(mats[0].name, "Mat1");
188 assert!((mats[0].kd[0] - 1.0).abs() < 1e-10);
189 assert!((mats[0].ns - 32.0).abs() < 1e-10);
190 assert!((mats[0].dissolve - 0.8).abs() < 1e-10);
191 }
192 #[test]
193 fn test_parse_mtl_multiple_materials() {
194 let data = "newmtl A\nKd 1 0 0\nnewmtl B\nKd 0 1 0\n";
195 let mats = parse_mtl(data);
196 assert_eq!(mats.len(), 2);
197 assert_eq!(mats[0].name, "A");
198 assert_eq!(mats[1].name, "B");
199 assert!((mats[1].kd[1] - 1.0).abs() < 1e-10);
200 }
201 #[test]
202 fn test_parse_mtl_map_kd() {
203 let data = "newmtl Mat\nmap_Kd diffuse.png\n";
204 let mats = parse_mtl(data);
205 assert_eq!(mats[0].map_kd.as_deref(), Some("diffuse.png"));
206 }
207 #[test]
208 fn test_parse_mtl_transparency() {
209 let data = "newmtl Glass\nTr 0.5\n";
210 let mats = parse_mtl(data);
211 assert!((mats[0].dissolve - 0.5).abs() < 1e-10);
212 }
213 #[test]
214 fn test_parse_mtl_empty() {
215 let mats = parse_mtl("");
216 assert!(mats.is_empty());
217 }
218 #[test]
219 fn test_parse_mtl_comments() {
220 let data = "# This is a comment\nnewmtl MyMat\n# another comment\nKd 0.5 0.5 0.5\n";
221 let mats = parse_mtl(data);
222 assert_eq!(mats.len(), 1);
223 assert_eq!(mats[0].name, "MyMat");
224 }
225 #[test]
226 fn test_lod_select() {
227 let mut lod = ObjLod::new();
228 let m0 = ObjMesh::default();
229 let m1 = ObjMesh::default();
230 let m2 = ObjMesh::default();
231 lod.push(m0, 10.0);
232 lod.push(m1, 50.0);
233 lod.push(m2, 200.0);
234 assert_eq!(lod.num_levels(), 3);
235 assert!(lod.select(5.0).is_some());
236 assert!(lod.select(30.0).is_some());
237 assert!(lod.select(500.0).is_some());
238 }
239 #[test]
240 fn test_lod_decimate_keeps_target() {
241 let mut mesh = ObjMesh::default();
242 mesh.vertices = vec![[0.0; 3]; 6];
243 for i in 0..6 {
244 mesh.faces.push(ObjFace {
245 vertex_indices: vec![i % 3, (i + 1) % 3, (i + 2) % 3],
246 normal_indices: None,
247 uv_indices: None,
248 smoothing_group: 0,
249 material: None,
250 });
251 }
252 let dec = ObjLod::decimate(&mesh, 3);
253 assert!(dec.faces.len() <= 3 + 1, "decimated to roughly 3 faces");
254 }
255 #[test]
256 fn test_lod_decimate_no_change_if_under_target() {
257 let mut mesh = ObjMesh::default();
258 mesh.vertices = vec![[0.0; 3]; 3];
259 mesh.faces.push(ObjFace {
260 vertex_indices: vec![0, 1, 2],
261 normal_indices: None,
262 uv_indices: None,
263 smoothing_group: 0,
264 material: None,
265 });
266 let dec = ObjLod::decimate(&mesh, 100);
267 assert_eq!(
268 dec.faces.len(),
269 1,
270 "no decimation when already under target"
271 );
272 }
273 #[test]
274 fn test_scene_add_mesh() {
275 let mut scene = ObjScene::new();
276 let mut mesh = ObjMesh::default();
277 mesh.vertices = vec![[0.0; 3]; 3];
278 let idx = scene.add_mesh(mesh);
279 assert_eq!(idx, 0);
280 assert_eq!(scene.meshes.len(), 1);
281 }
282 #[test]
283 fn test_scene_total_vertices() {
284 let mut scene = ObjScene::new();
285 let mut m1 = ObjMesh::default();
286 m1.vertices = vec![[0.0; 3]; 4];
287 let mut m2 = ObjMesh::default();
288 m2.vertices = vec![[1.0; 3]; 6];
289 scene.add_mesh(m1);
290 scene.add_mesh(m2);
291 assert_eq!(scene.total_vertices(), 10);
292 }
293 #[test]
294 fn test_scene_flatten() {
295 let mut scene = ObjScene::new();
296 let mut mesh = ObjMesh::default();
297 mesh.vertices = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
298 mesh.faces.push(ObjFace {
299 vertex_indices: vec![0, 1, 2],
300 normal_indices: None,
301 uv_indices: None,
302 smoothing_group: 0,
303 material: None,
304 });
305 let mi = scene.add_mesh(mesh);
306 let t = MeshTransform::from_translation(10.0, 0.0, 0.0);
307 scene.add_node("node1", Some(mi), t);
308 let flat = scene.flatten();
309 assert_eq!(flat.vertices.len(), 3);
310 assert!((flat.vertices[0][0] - 10.0).abs() < 1e-10);
311 }
312 #[test]
313 fn test_scene_add_child() {
314 let mut scene = ObjScene::new();
315 let ni0 = scene.add_node("parent", None, MeshTransform::identity());
316 let ni1 = scene.add_node("child", None, MeshTransform::identity());
317 scene.add_child(ni0, ni1);
318 assert_eq!(scene.nodes[ni0].children, vec![ni1]);
319 }
320 #[test]
321 fn test_compute_mesh_stats_basic() {
322 let mut mesh = ObjMesh::default();
323 mesh.vertices = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
324 mesh.faces.push(ObjFace {
325 vertex_indices: vec![0, 1, 2],
326 normal_indices: None,
327 uv_indices: None,
328 smoothing_group: 0,
329 material: None,
330 });
331 let stats = compute_mesh_stats(&mesh);
332 assert_eq!(stats.vertex_count, 3);
333 assert_eq!(stats.face_count, 1);
334 assert_eq!(stats.triangle_count, 1);
335 assert!(stats.surface_area > 0.0);
336 }
337 #[test]
338 fn test_compute_mesh_stats_materials() {
339 let mut mesh = ObjMesh::default();
340 mesh.vertices = vec![[0.0; 3]; 4];
341 mesh.faces.push(ObjFace {
342 vertex_indices: vec![0, 1, 2],
343 normal_indices: None,
344 uv_indices: None,
345 smoothing_group: 0,
346 material: Some("Red".into()),
347 });
348 mesh.faces.push(ObjFace {
349 vertex_indices: vec![1, 3, 2],
350 normal_indices: None,
351 uv_indices: None,
352 smoothing_group: 0,
353 material: Some("Blue".into()),
354 });
355 let stats = compute_mesh_stats(&mesh);
356 assert_eq!(stats.material_count, 2);
357 }
358 #[test]
359 fn test_compute_mesh_stats_bbox() {
360 let mut mesh = ObjMesh::default();
361 mesh.vertices = vec![[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]];
362 let stats = compute_mesh_stats(&mesh);
363 let (min, max) = stats.bbox.unwrap();
364 assert!((min[0] - (-1.0)).abs() < 1e-10);
365 assert!((max[0] - 1.0).abs() < 1e-10);
366 }
367 #[test]
368 fn test_vcmesh_parse_basic() {
369 let data = "v 0 0 0 1.0 0.0 0.0\nv 1 0 0 0.0 1.0 0.0\nv 0 1 0 0.0 0.0 1.0\nf 1 2 3\n";
370 let vcm = ObjVertexColorMesh::from_str(data).unwrap();
371 assert_eq!(vcm.mesh.vertices.len(), 3);
372 assert_eq!(vcm.colors.len(), 3);
373 assert!((vcm.colors[0].r - 1.0).abs() < 1e-10);
374 assert!((vcm.colors[1].g - 1.0).abs() < 1e-10);
375 assert!((vcm.colors[2].b - 1.0).abs() < 1e-10);
376 }
377 #[test]
378 fn test_vcmesh_roundtrip() {
379 let data = "v 0.0 0.0 0.0 0.5 0.5 0.5\nv 1.0 0.0 0.0 0.5 0.5 0.5\nv 0.0 1.0 0.0 0.5 0.5 0.5\nf 1 2 3\n";
380 let vcm = ObjVertexColorMesh::from_str(data).unwrap();
381 let s = vcm.to_obj_str();
382 let reparsed = ObjVertexColorMesh::from_str(&s).unwrap();
383 assert_eq!(reparsed.mesh.vertices.len(), 3);
384 assert_eq!(reparsed.mesh.faces.len(), 1);
385 }
386 #[test]
387 fn test_vcmesh_no_color_defaults_white() {
388 let data = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n";
389 let vcm = ObjVertexColorMesh::from_str(data).unwrap();
390 assert_eq!(vcm.colors.len(), 3);
391 assert!((vcm.colors[0].r - 1.0).abs() < 1e-10);
392 assert!((vcm.colors[0].g - 1.0).abs() < 1e-10);
393 assert!((vcm.colors[0].b - 1.0).abs() < 1e-10);
394 }
395 #[test]
396 fn test_instantiate_mesh_translation() {
397 let mut mesh = ObjMesh::default();
398 mesh.vertices = vec![[0.0, 0.0, 0.0]];
399 let inst = MeshInstance {
400 name: "inst1".into(),
401 transform: MeshTransform::from_translation(5.0, 6.0, 7.0),
402 };
403 let out = instantiate_mesh(&mesh, &inst);
404 assert!((out.vertices[0][0] - 5.0).abs() < 1e-10);
405 assert!((out.vertices[0][1] - 6.0).abs() < 1e-10);
406 assert!((out.vertices[0][2] - 7.0).abs() < 1e-10);
407 }
408 #[test]
409 fn test_instantiate_mesh_preserves_face_count() {
410 let mut mesh = ObjMesh::default();
411 mesh.vertices = vec![[0.0; 3]; 3];
412 mesh.faces.push(ObjFace {
413 vertex_indices: vec![0, 1, 2],
414 normal_indices: None,
415 uv_indices: None,
416 smoothing_group: 0,
417 material: None,
418 });
419 let inst = MeshInstance {
420 name: "x".into(),
421 transform: MeshTransform::identity(),
422 };
423 let out = instantiate_mesh(&mesh, &inst);
424 assert_eq!(out.faces.len(), 1);
425 }
426}