1#![allow(clippy::manual_strip)]
2#![allow(dead_code)]
17
18use std::collections::HashMap;
19use std::fmt::Write as FmtWrite;
20
21#[derive(Debug, Clone, Default)]
27pub struct MeshData {
28 pub vertices: Vec<[f64; 3]>,
30 pub faces: Vec<[usize; 3]>,
32 pub normals: Vec<[f64; 3]>,
34 pub uvs: Vec<[f64; 2]>,
36 pub material_ids: Vec<usize>,
38}
39
40impl MeshData {
41 pub fn new() -> Self {
43 Self::default()
44 }
45
46 pub fn add_vertex(&mut self, pos: [f64; 3]) -> usize {
48 let idx = self.vertices.len();
49 self.vertices.push(pos);
50 idx
51 }
52
53 pub fn add_face(&mut self, v0: usize, v1: usize, v2: usize) {
55 self.faces.push([v0, v1, v2]);
56 }
57
58 pub fn add_face_with_material(&mut self, v0: usize, v1: usize, v2: usize, mat: usize) {
60 self.faces.push([v0, v1, v2]);
61 self.material_ids.push(mat);
62 }
63
64 pub fn compute_flat_normals(&mut self) {
66 self.normals = vec![[0.0; 3]; self.vertices.len()];
67 for face in &self.faces {
68 let v0 = self.vertices[face[0]];
69 let v1 = self.vertices[face[1]];
70 let v2 = self.vertices[face[2]];
71 let n = triangle_normal_f64(v0, v1, v2);
72 for &vi in face {
73 self.normals[vi] = n;
74 }
75 }
76 }
77
78 pub fn compute_smooth_normals(&mut self) {
80 let mut acc = vec![[0.0f64; 3]; self.vertices.len()];
81 let mut count = vec![0u32; self.vertices.len()];
82 for face in &self.faces {
83 let v0 = self.vertices[face[0]];
84 let v1 = self.vertices[face[1]];
85 let v2 = self.vertices[face[2]];
86 let n = triangle_normal_f64(v0, v1, v2);
87 for &vi in face {
88 acc[vi][0] += n[0];
89 acc[vi][1] += n[1];
90 acc[vi][2] += n[2];
91 count[vi] += 1;
92 }
93 }
94 self.normals = acc
95 .iter()
96 .zip(count.iter())
97 .map(|(a, &c)| {
98 if c == 0 {
99 [0.0; 3]
100 } else {
101 let inv = 1.0 / c as f64;
102 normalize3_f64([a[0] * inv, a[1] * inv, a[2] * inv])
103 }
104 })
105 .collect();
106 }
107
108 pub fn bounding_box(&self) -> ([f64; 3], [f64; 3]) {
110 let mut mn = [f64::INFINITY; 3];
111 let mut mx = [f64::NEG_INFINITY; 3];
112 for &v in &self.vertices {
113 for i in 0..3 {
114 mn[i] = mn[i].min(v[i]);
115 mx[i] = mx[i].max(v[i]);
116 }
117 }
118 (mn, mx)
119 }
120
121 pub fn num_triangles(&self) -> usize {
123 self.faces.len()
124 }
125}
126
127fn triangle_normal_f64(v0: [f64; 3], v1: [f64; 3], v2: [f64; 3]) -> [f64; 3] {
133 let a = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
134 let b = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
135 let n = [
136 a[1] * b[2] - a[2] * b[1],
137 a[2] * b[0] - a[0] * b[2],
138 a[0] * b[1] - a[1] * b[0],
139 ];
140 normalize3_f64(n)
141}
142
143fn normalize3_f64(v: [f64; 3]) -> [f64; 3] {
145 let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
146 if l < 1e-12 {
147 [0.0; 3]
148 } else {
149 [v[0] / l, v[1] / l, v[2] / l]
150 }
151}
152
153#[derive(Debug, Clone)]
159pub struct Material {
160 pub name: String,
162 pub diffuse: [f64; 3],
164 pub specular: [f64; 3],
166 pub shininess: f64,
168 pub opacity: f64,
170}
171
172impl Default for Material {
173 fn default() -> Self {
174 Self {
175 name: "default".to_string(),
176 diffuse: [0.8, 0.8, 0.8],
177 specular: [0.2, 0.2, 0.2],
178 shininess: 32.0,
179 opacity: 1.0,
180 }
181 }
182}
183
184impl Material {
185 pub fn new(name: impl Into<String>) -> Self {
187 Self {
188 name: name.into(),
189 ..Default::default()
190 }
191 }
192}
193
194#[derive(Debug, Default)]
200pub struct ObjExporter {
201 pub materials: Vec<Material>,
203}
204
205impl ObjExporter {
206 pub fn new() -> Self {
208 Self::default()
209 }
210
211 pub fn add_material(&mut self, mat: Material) {
213 self.materials.push(mat);
214 }
215
216 pub fn export_obj(&self, mesh: &MeshData, mtl_name: Option<&str>) -> String {
220 let mut out = String::new();
221 let _ = writeln!(out, "# OxiPhysics OBJ export");
222 if let Some(name) = mtl_name {
223 let _ = writeln!(out, "mtllib {name}");
224 }
225
226 for &v in &mesh.vertices {
228 let _ = writeln!(out, "v {} {} {}", v[0], v[1], v[2]);
229 }
230
231 for &n in &mesh.normals {
233 let _ = writeln!(out, "vn {} {} {}", n[0], n[1], n[2]);
234 }
235
236 for &uv in &mesh.uvs {
238 let _ = writeln!(out, "vt {} {}", uv[0], uv[1]);
239 }
240
241 let has_mats = !mesh.material_ids.is_empty() && !self.materials.is_empty();
243 let mut current_mat: Option<usize> = None;
244
245 for (fi, &face) in mesh.faces.iter().enumerate() {
246 if has_mats {
247 let mat_id = *mesh.material_ids.get(fi).unwrap_or(&0);
248 if current_mat != Some(mat_id) {
249 if let Some(mat) = self.materials.get(mat_id) {
250 let _ = writeln!(out, "usemtl {}", mat.name);
251 }
252 current_mat = Some(mat_id);
253 }
254 }
255 let v0 = face[0] + 1;
257 let v1 = face[1] + 1;
258 let v2 = face[2] + 1;
259 if !mesh.normals.is_empty() && !mesh.uvs.is_empty() {
260 let _ = writeln!(out, "f {v0}/{v0}/{v0} {v1}/{v1}/{v1} {v2}/{v2}/{v2}");
261 } else if !mesh.normals.is_empty() {
262 let _ = writeln!(out, "f {v0}//{v0} {v1}//{v1} {v2}//{v2}");
263 } else if !mesh.uvs.is_empty() {
264 let _ = writeln!(out, "f {v0}/{v0} {v1}/{v1} {v2}/{v2}");
265 } else {
266 let _ = writeln!(out, "f {v0} {v1} {v2}");
267 }
268 }
269 out
270 }
271
272 pub fn export_mtl(&self) -> String {
274 let mut out = String::new();
275 let _ = writeln!(out, "# OxiPhysics MTL export");
276 for mat in &self.materials {
277 let _ = writeln!(out, "newmtl {}", mat.name);
278 writeln!(
279 out,
280 "Kd {} {} {}",
281 mat.diffuse[0], mat.diffuse[1], mat.diffuse[2]
282 )
283 .expect("operation should succeed");
284 writeln!(
285 out,
286 "Ks {} {} {}",
287 mat.specular[0], mat.specular[1], mat.specular[2]
288 )
289 .expect("operation should succeed");
290 let _ = writeln!(out, "Ns {}", mat.shininess);
291 let _ = writeln!(out, "d {}", mat.opacity);
292 }
293 out
294 }
295}
296
297#[derive(Debug, Default)]
303pub struct StlExporter;
304
305impl StlExporter {
306 pub fn new() -> Self {
308 Self
309 }
310
311 pub fn export_binary(&self, mesh: &MeshData, solid_name: &str) -> Vec<u8> {
315 let n = mesh.faces.len();
316 let mut buf = Vec::with_capacity(84 + n * 50);
317
318 let mut header = [0u8; 80];
320 let name_bytes = solid_name.as_bytes();
321 let copy_len = name_bytes.len().min(80);
322 header[..copy_len].copy_from_slice(&name_bytes[..copy_len]);
323 buf.extend_from_slice(&header);
324
325 buf.extend_from_slice(&(n as u32).to_le_bytes());
327
328 for &face in &mesh.faces {
329 let v0 = mesh.vertices[face[0]];
330 let v1 = mesh.vertices[face[1]];
331 let v2 = mesh.vertices[face[2]];
332
333 let n_vec = if !mesh.normals.is_empty() {
335 mesh.normals[face[0]]
336 } else {
337 triangle_normal_f64(v0, v1, v2)
338 };
339
340 for &c in &n_vec {
341 buf.extend_from_slice(&(c as f32).to_le_bytes());
342 }
343 for &v in &[v0, v1, v2] {
344 for &c in &v {
345 buf.extend_from_slice(&(c as f32).to_le_bytes());
346 }
347 }
348 buf.extend_from_slice(&0u16.to_le_bytes()); }
350 buf
351 }
352
353 pub fn export_ascii(&self, mesh: &MeshData, solid_name: &str) -> String {
355 let mut out = String::new();
356 let _ = writeln!(out, "solid {solid_name}");
357 for &face in &mesh.faces {
358 let v0 = mesh.vertices[face[0]];
359 let v1 = mesh.vertices[face[1]];
360 let v2 = mesh.vertices[face[2]];
361 let n_vec = triangle_normal_f64(v0, v1, v2);
362 let _ = writeln!(out, " facet normal {} {} {}", n_vec[0], n_vec[1], n_vec[2]);
363 let _ = writeln!(out, " outer loop");
364 let _ = writeln!(out, " vertex {} {} {}", v0[0], v0[1], v0[2]);
365 let _ = writeln!(out, " vertex {} {} {}", v1[0], v1[1], v1[2]);
366 let _ = writeln!(out, " vertex {} {} {}", v2[0], v2[1], v2[2]);
367 let _ = writeln!(out, " endloop");
368 let _ = writeln!(out, " endfacet");
369 }
370 let _ = writeln!(out, "endsolid {solid_name}");
371 out
372 }
373
374 pub fn crc32(data: &[u8]) -> u32 {
376 let mut crc: u32 = 0xFFFF_FFFF;
377 for &byte in data {
378 crc ^= byte as u32;
379 for _ in 0..8 {
380 if crc & 1 != 0 {
381 crc = (crc >> 1) ^ 0xEDB8_8320;
382 } else {
383 crc >>= 1;
384 }
385 }
386 }
387 crc ^ 0xFFFF_FFFF
388 }
389}
390
391#[derive(Debug, Default)]
397pub struct PlyExporter {
398 pub binary: bool,
400}
401
402impl PlyExporter {
403 pub fn new(binary: bool) -> Self {
405 Self { binary }
406 }
407
408 fn build_header(&self, mesh: &MeshData) -> String {
410 let mut h = String::new();
411 let _ = writeln!(h, "ply");
412 if self.binary {
413 let _ = writeln!(h, "format binary_little_endian 1.0");
414 } else {
415 let _ = writeln!(h, "format ascii 1.0");
416 }
417 let _ = writeln!(h, "comment OxiPhysics PLY export");
418 let _ = writeln!(h, "element vertex {}", mesh.vertices.len());
419 let _ = writeln!(h, "property float x");
420 let _ = writeln!(h, "property float y");
421 let _ = writeln!(h, "property float z");
422 if !mesh.normals.is_empty() {
423 let _ = writeln!(h, "property float nx");
424 let _ = writeln!(h, "property float ny");
425 let _ = writeln!(h, "property float nz");
426 }
427 if !mesh.uvs.is_empty() {
428 let _ = writeln!(h, "property float s");
429 let _ = writeln!(h, "property float t");
430 }
431 let _ = writeln!(h, "element face {}", mesh.faces.len());
432 let _ = writeln!(h, "property list uchar int vertex_indices");
433 let _ = writeln!(h, "end_header");
434 h
435 }
436
437 pub fn export_ascii_bytes(&self, mesh: &MeshData) -> Vec<u8> {
439 let header = self.build_header(mesh);
440 let mut out = header;
441 for (i, &v) in mesh.vertices.iter().enumerate() {
442 let mut line = format!("{} {} {}", v[0] as f32, v[1] as f32, v[2] as f32);
443 if !mesh.normals.is_empty() {
444 let n = mesh.normals[i];
445 line.push_str(&format!(" {} {} {}", n[0] as f32, n[1] as f32, n[2] as f32));
446 }
447 if !mesh.uvs.is_empty() {
448 let uv = mesh.uvs[i];
449 line.push_str(&format!(" {} {}", uv[0] as f32, uv[1] as f32));
450 }
451 let _ = writeln!(out, "{line}");
452 }
453 for &face in &mesh.faces {
454 let _ = writeln!(out, "3 {} {} {}", face[0], face[1], face[2]);
455 }
456 out.into_bytes()
457 }
458
459 pub fn export_binary_bytes(&self, mesh: &MeshData) -> Vec<u8> {
461 let header = self.build_header(mesh);
462 let mut buf = header.into_bytes();
463
464 for (i, &v) in mesh.vertices.iter().enumerate() {
465 for &c in &v {
466 buf.extend_from_slice(&(c as f32).to_le_bytes());
467 }
468 if !mesh.normals.is_empty() {
469 let n = mesh.normals[i];
470 for &c in &n {
471 buf.extend_from_slice(&(c as f32).to_le_bytes());
472 }
473 }
474 if !mesh.uvs.is_empty() {
475 let uv = mesh.uvs[i];
476 for &c in &uv {
477 buf.extend_from_slice(&(c as f32).to_le_bytes());
478 }
479 }
480 }
481 for &face in &mesh.faces {
482 buf.push(3u8); for &vi in &face {
484 buf.extend_from_slice(&(vi as i32).to_le_bytes());
485 }
486 }
487 buf
488 }
489
490 pub fn export(&self, mesh: &MeshData) -> Vec<u8> {
492 if self.binary {
493 self.export_binary_bytes(mesh)
494 } else {
495 self.export_ascii_bytes(mesh)
496 }
497 }
498}
499
500#[derive(Debug, Default)]
506pub struct GltfExporter {
507 pub scene_name: String,
509}
510
511impl GltfExporter {
512 pub fn new(scene_name: impl Into<String>) -> Self {
514 Self {
515 scene_name: scene_name.into(),
516 }
517 }
518
519 pub fn export(&self, mesh: &MeshData) -> (String, Vec<u8>) {
524 let mut bin: Vec<u8> = Vec::new();
526 for &v in &mesh.vertices {
527 for &c in &v {
528 bin.extend_from_slice(&(c as f32).to_le_bytes());
529 }
530 }
531 let indices_offset = bin.len();
533 for &face in &mesh.faces {
534 for &vi in &face {
535 bin.extend_from_slice(&(vi as u32).to_le_bytes());
536 }
537 }
538
539 let vertex_count = mesh.vertices.len();
540 let index_count = mesh.faces.len() * 3;
541 let positions_byte_len = vertex_count * 3 * 4;
542 let indices_byte_len = index_count * 4;
543 let total_byte_len = bin.len();
544
545 let (bb_min, bb_max) = mesh.bounding_box();
547
548 let json = format!(
549 r#"{{
550 "asset": {{"version": "2.0", "generator": "OxiPhysics"}},
551 "scene": 0,
552 "scenes": [{{"name": "{}", "nodes": [0]}}],
553 "nodes": [{{"mesh": 0}}],
554 "meshes": [{{
555 "name": "mesh0",
556 "primitives": [{{
557 "attributes": {{"POSITION": 0}},
558 "indices": 1,
559 "mode": 4
560 }}]
561 }}],
562 "accessors": [
563 {{
564 "bufferView": 0,
565 "byteOffset": 0,
566 "componentType": 5126,
567 "count": {vertex_count},
568 "type": "VEC3",
569 "min": [{}, {}, {}],
570 "max": [{}, {}, {}]
571 }},
572 {{
573 "bufferView": 1,
574 "byteOffset": 0,
575 "componentType": 5125,
576 "count": {index_count},
577 "type": "SCALAR"
578 }}
579 ],
580 "bufferViews": [
581 {{"buffer": 0, "byteOffset": 0, "byteLength": {positions_byte_len}, "target": 34962}},
582 {{"buffer": 0, "byteOffset": {indices_offset}, "byteLength": {indices_byte_len}, "target": 34963}}
583 ],
584 "buffers": [{{"byteLength": {total_byte_len}}}]
585}}"#,
586 self.scene_name,
587 bb_min[0] as f32,
588 bb_min[1] as f32,
589 bb_min[2] as f32,
590 bb_max[0] as f32,
591 bb_max[1] as f32,
592 bb_max[2] as f32,
593 );
594
595 (json, bin)
596 }
597}
598
599#[derive(Debug, Default)]
605pub struct MeshImporter;
606
607impl MeshImporter {
608 pub fn new() -> Self {
610 Self
611 }
612
613 pub fn parse_obj(&self, src: &str) -> MeshData {
617 let mut mesh = MeshData::new();
618 let mut obj_normals: Vec<[f64; 3]> = Vec::new();
619 let mut obj_uvs: Vec<[f64; 2]> = Vec::new();
620
621 for line in src.lines() {
622 let line = line.trim();
623 if line.starts_with("v ") {
624 let vals: Vec<f64> = line[2..]
625 .split_whitespace()
626 .filter_map(|s| s.parse().ok())
627 .collect();
628 if vals.len() >= 3 {
629 mesh.add_vertex([vals[0], vals[1], vals[2]]);
630 }
631 } else if line.starts_with("vn ") {
632 let vals: Vec<f64> = line[3..]
633 .split_whitespace()
634 .filter_map(|s| s.parse().ok())
635 .collect();
636 if vals.len() >= 3 {
637 obj_normals.push([vals[0], vals[1], vals[2]]);
638 }
639 } else if line.starts_with("vt ") {
640 let vals: Vec<f64> = line[3..]
641 .split_whitespace()
642 .filter_map(|s| s.parse().ok())
643 .collect();
644 if vals.len() >= 2 {
645 obj_uvs.push([vals[0], vals[1]]);
646 }
647 } else if line.starts_with("f ") {
648 let parts: Vec<&str> = line[2..].split_whitespace().collect();
649 if parts.len() >= 3 {
650 let parse_index = |tok: &str| -> usize {
652 let s = tok.split('/').next().unwrap_or("1");
653 s.parse::<usize>().unwrap_or(1).saturating_sub(1)
654 };
655 let i0 = parse_index(parts[0]);
656 let i1 = parse_index(parts[1]);
657 let i2 = parse_index(parts[2]);
658 mesh.add_face(i0, i1, i2);
659 for k in 3..parts.len() {
661 let ik = parse_index(parts[k]);
662 mesh.add_face(i0, parse_index(parts[k - 1]), ik);
663 }
664 }
665 }
666 }
667
668 if !obj_normals.is_empty() && obj_normals.len() == mesh.vertices.len() {
670 mesh.normals = obj_normals;
671 }
672 if !obj_uvs.is_empty() && obj_uvs.len() == mesh.vertices.len() {
673 mesh.uvs = obj_uvs;
674 }
675
676 mesh
677 }
678
679 pub fn parse_stl_binary(&self, data: &[u8]) -> Option<MeshData> {
683 if data.len() < 84 {
684 return None;
685 }
686 let count = u32::from_le_bytes(data[80..84].try_into().ok()?) as usize;
687 let expected_len = 84 + count * 50;
688 if data.len() < expected_len {
689 return None;
690 }
691
692 let mut mesh = MeshData::new();
693 let mut offset = 84usize;
694 for _ in 0..count {
695 let read_f32 = |buf: &[u8], off: usize| -> f32 {
696 f32::from_le_bytes(
697 buf[off..off + 4]
698 .try_into()
699 .expect("slice length must match"),
700 )
701 };
702 let nx = read_f32(data, offset) as f64;
703 let ny = read_f32(data, offset + 4) as f64;
704 let nz = read_f32(data, offset + 8) as f64;
705 let v0 = [
706 read_f32(data, offset + 12) as f64,
707 read_f32(data, offset + 16) as f64,
708 read_f32(data, offset + 20) as f64,
709 ];
710 let v1 = [
711 read_f32(data, offset + 24) as f64,
712 read_f32(data, offset + 28) as f64,
713 read_f32(data, offset + 32) as f64,
714 ];
715 let v2 = [
716 read_f32(data, offset + 36) as f64,
717 read_f32(data, offset + 40) as f64,
718 read_f32(data, offset + 44) as f64,
719 ];
720 let base = mesh.vertices.len();
721 mesh.add_vertex(v0);
722 mesh.add_vertex(v1);
723 mesh.add_vertex(v2);
724 mesh.add_face(base, base + 1, base + 2);
725 mesh.normals.push([nx, ny, nz]);
726 mesh.normals.push([nx, ny, nz]);
727 mesh.normals.push([nx, ny, nz]);
728 offset += 50;
729 }
730 Some(mesh)
731 }
732
733 pub fn parse_stl_ascii(&self, src: &str) -> MeshData {
735 let mut mesh = MeshData::new();
736 let mut current_normal = [0.0f64; 3];
737 let mut verts_in_loop: Vec<[f64; 3]> = Vec::new();
738
739 for line in src.lines() {
740 let line = line.trim();
741 if line.starts_with("facet normal") {
742 let vals: Vec<f64> = line
743 .split_whitespace()
744 .skip(2)
745 .filter_map(|s| s.parse().ok())
746 .collect();
747 if vals.len() >= 3 {
748 current_normal = [vals[0], vals[1], vals[2]];
749 }
750 } else if line.starts_with("vertex") {
751 let vals: Vec<f64> = line
752 .split_whitespace()
753 .skip(1)
754 .filter_map(|s| s.parse().ok())
755 .collect();
756 if vals.len() >= 3 {
757 verts_in_loop.push([vals[0], vals[1], vals[2]]);
758 }
759 } else if line.starts_with("endfacet") {
760 if verts_in_loop.len() == 3 {
761 let base = mesh.vertices.len();
762 for &v in &verts_in_loop {
763 mesh.add_vertex(v);
764 mesh.normals.push(current_normal);
765 }
766 mesh.add_face(base, base + 1, base + 2);
767 }
768 verts_in_loop.clear();
769 }
770 }
771 mesh
772 }
773
774 pub fn parse_ply_ascii(&self, src: &str) -> Option<MeshData> {
776 let mut lines = src.lines();
777 let mut vertex_count = 0usize;
778 let mut face_count = 0usize;
779 let mut has_normal = false;
780 let mut has_uv = false;
781 for line in lines.by_ref() {
783 let line = line.trim();
784 if line == "end_header" {
785 break;
786 }
787 if line.starts_with("element vertex") {
788 vertex_count = line
789 .split_whitespace()
790 .last()
791 .and_then(|s| s.parse().ok())
792 .unwrap_or(0);
793 } else if line.starts_with("element face") {
794 face_count = line
795 .split_whitespace()
796 .last()
797 .and_then(|s| s.parse().ok())
798 .unwrap_or(0);
799 } else if line.contains("property float nx") || line.contains("property float nz") {
800 has_normal = true;
801 } else if line.contains("property float s") || line.contains("property float t") {
802 has_uv = true;
803 }
804 }
805
806 let mut mesh = MeshData::new();
807
808 for _ in 0..vertex_count {
810 let line = lines.next()?.trim().to_string();
811 let vals: Vec<f64> = line
812 .split_whitespace()
813 .filter_map(|s| s.parse().ok())
814 .collect();
815 if vals.len() < 3 {
816 return None;
817 }
818 mesh.add_vertex([vals[0], vals[1], vals[2]]);
819 if has_normal && vals.len() >= 6 {
820 mesh.normals.push([vals[3], vals[4], vals[5]]);
821 }
822 let uv_offset = if has_normal { 6 } else { 3 };
823 if has_uv && vals.len() >= uv_offset + 2 {
824 mesh.uvs.push([vals[uv_offset], vals[uv_offset + 1]]);
825 }
826 }
827
828 for _ in 0..face_count {
830 let line = lines.next()?.trim().to_string();
831 let vals: Vec<usize> = line
832 .split_whitespace()
833 .filter_map(|s| s.parse().ok())
834 .collect();
835 if vals.is_empty() {
836 continue;
837 }
838 let n = vals[0];
839 if n >= 3 && vals.len() > n {
840 mesh.add_face(vals[1], vals[2], vals[3]);
841 for k in 3..n {
842 mesh.add_face(vals[1], vals[k], vals[k + 1]);
843 }
844 }
845 }
846
847 Some(mesh)
848 }
849}
850
851#[derive(Debug, Clone, Default)]
857struct Quadric {
858 q: [f64; 10],
860}
861
862impl Quadric {
863 fn add(&mut self, other: &Quadric) {
864 for i in 0..10 {
865 self.q[i] += other.q[i];
866 }
867 }
868
869 fn add_plane(&mut self, a: f64, b: f64, c: f64, d: f64) {
871 self.q[0] += a * a;
872 self.q[1] += a * b;
873 self.q[2] += a * c;
874 self.q[3] += a * d;
875 self.q[4] += b * b;
876 self.q[5] += b * c;
877 self.q[6] += b * d;
878 self.q[7] += c * c;
879 self.q[8] += c * d;
880 self.q[9] += d * d;
881 }
882
883 fn error_at(&self, v: [f64; 3]) -> f64 {
885 let [x, y, z] = v;
886 let q = &self.q;
887 x * x * q[0]
888 + 2.0 * x * y * q[1]
889 + 2.0 * x * z * q[2]
890 + 2.0 * x * q[3]
891 + y * y * q[4]
892 + 2.0 * y * z * q[5]
893 + 2.0 * y * q[6]
894 + z * z * q[7]
895 + 2.0 * z * q[8]
896 + q[9]
897 }
898}
899
900#[derive(Debug, Clone)]
902struct EdgeCollapse {
903 v0: usize,
905 v1: usize,
907 target: [f64; 3],
909 error: f64,
911}
912
913#[derive(Debug, Default)]
915pub struct MeshSimplification;
916
917impl MeshSimplification {
918 pub fn new() -> Self {
920 Self
921 }
922
923 pub fn simplify(&self, mesh: &MeshData, target_triangles: usize) -> MeshData {
928 if mesh.faces.len() <= target_triangles {
929 return mesh.clone();
930 }
931
932 let n = mesh.vertices.len();
933 let mut quadrics: Vec<Quadric> = vec![Quadric::default(); n];
934
935 for &face in &mesh.faces {
937 let v0 = mesh.vertices[face[0]];
938 let v1 = mesh.vertices[face[1]];
939 let v2 = mesh.vertices[face[2]];
940 let nrm = triangle_normal_f64(v0, v1, v2);
941 let d = -(nrm[0] * v0[0] + nrm[1] * v0[1] + nrm[2] * v0[2]);
942 for &vi in &face {
943 quadrics[vi].add_plane(nrm[0], nrm[1], nrm[2], d);
944 }
945 }
946
947 let mut edge_set: HashMap<(usize, usize), ()> = HashMap::new();
949 for &face in &mesh.faces {
950 for k in 0..3 {
951 let a = face[k];
952 let b = face[(k + 1) % 3];
953 let key = if a < b { (a, b) } else { (b, a) };
954 edge_set.insert(key, ());
955 }
956 }
957
958 let mut candidates: Vec<EdgeCollapse> = edge_set
960 .keys()
961 .map(|&(v0, v1)| {
962 let mid = [
963 (mesh.vertices[v0][0] + mesh.vertices[v1][0]) * 0.5,
964 (mesh.vertices[v0][1] + mesh.vertices[v1][1]) * 0.5,
965 (mesh.vertices[v0][2] + mesh.vertices[v1][2]) * 0.5,
966 ];
967 let mut combined = quadrics[v0].clone();
968 combined.add(&quadrics[v1]);
969 let error = combined.error_at(mid);
970 EdgeCollapse {
971 v0,
972 v1,
973 target: mid,
974 error,
975 }
976 })
977 .collect();
978
979 candidates.sort_by(|a, b| {
980 a.error
981 .partial_cmp(&b.error)
982 .unwrap_or(std::cmp::Ordering::Equal)
983 });
984
985 let mut vertices = mesh.vertices.clone();
987 let mut faces = mesh.faces.clone();
988 let mut redirect: Vec<usize> = (0..n).collect();
989 let mut removed_count = 0;
990
991 let to_remove = mesh.faces.len() - target_triangles;
992
993 for collapse in &candidates {
994 if removed_count >= to_remove {
995 break;
996 }
997 let v0 = follow_redirect(&redirect, collapse.v0);
998 let v1 = follow_redirect(&redirect, collapse.v1);
999 if v0 == v1 {
1000 continue;
1001 }
1002
1003 vertices[v0] = collapse.target;
1005 redirect[v1] = v0;
1006
1007 let prev_len = faces.len();
1009 faces.retain(|f| {
1010 let a = follow_redirect(&redirect, f[0]);
1011 let b = follow_redirect(&redirect, f[1]);
1012 let c = follow_redirect(&redirect, f[2]);
1013 a != b && b != c && a != c
1014 });
1015 for f in faces.iter_mut() {
1017 f[0] = follow_redirect(&redirect, f[0]);
1018 f[1] = follow_redirect(&redirect, f[1]);
1019 f[2] = follow_redirect(&redirect, f[2]);
1020 }
1021
1022 removed_count += prev_len - faces.len();
1023 }
1024
1025 let mut new_mesh = MeshData::new();
1027 let mut new_index: HashMap<usize, usize> = HashMap::new();
1028 for f in &faces {
1029 let mut new_face = [0usize; 3];
1030 for (k, &vi) in f.iter().enumerate() {
1031 let entry_count = new_index.len();
1032 let new_vi = *new_index.entry(vi).or_insert(entry_count);
1033 if new_vi == new_index.len() - 1 {
1034 new_mesh.add_vertex(vertices[vi]);
1035 }
1036 new_face[k] = new_vi;
1037 }
1038 new_mesh.add_face(new_face[0], new_face[1], new_face[2]);
1039 }
1040 new_mesh
1041 }
1042}
1043
1044fn follow_redirect(redirect: &[usize], mut v: usize) -> usize {
1046 while redirect[v] != v {
1047 v = redirect[v];
1048 }
1049 v
1050}
1051
1052#[cfg(test)]
1057mod tests {
1058 use super::*;
1059
1060 fn tetra() -> MeshData {
1062 let mut m = MeshData::new();
1063 m.add_vertex([0.0, 0.0, 0.0]);
1064 m.add_vertex([1.0, 0.0, 0.0]);
1065 m.add_vertex([0.0, 1.0, 0.0]);
1066 m.add_vertex([0.0, 0.0, 1.0]);
1067 m.add_face(0, 1, 2);
1068 m.add_face(0, 1, 3);
1069 m.add_face(0, 2, 3);
1070 m.add_face(1, 2, 3);
1071 m
1072 }
1073
1074 fn quad() -> MeshData {
1076 let mut m = MeshData::new();
1077 m.add_vertex([0.0, 0.0, 0.0]);
1078 m.add_vertex([1.0, 0.0, 0.0]);
1079 m.add_vertex([1.0, 1.0, 0.0]);
1080 m.add_vertex([0.0, 1.0, 0.0]);
1081 m.add_face(0, 1, 2);
1082 m.add_face(0, 2, 3);
1083 m
1084 }
1085
1086 #[test]
1089 fn test_mesh_add_vertex() {
1090 let mut m = MeshData::new();
1091 let idx = m.add_vertex([1.0, 2.0, 3.0]);
1092 assert_eq!(idx, 0);
1093 assert_eq!(m.vertices.len(), 1);
1094 }
1095
1096 #[test]
1097 fn test_mesh_add_face() {
1098 let m = tetra();
1099 assert_eq!(m.num_triangles(), 4);
1100 }
1101
1102 #[test]
1103 fn test_mesh_bounding_box() {
1104 let m = tetra();
1105 let (mn, mx) = m.bounding_box();
1106 assert!((mn[0] - 0.0).abs() < 1e-10);
1107 assert!((mx[0] - 1.0).abs() < 1e-10);
1108 }
1109
1110 #[test]
1111 fn test_mesh_compute_flat_normals() {
1112 let mut m = quad();
1113 m.compute_flat_normals();
1114 assert_eq!(m.normals.len(), m.vertices.len());
1115 for n in &m.normals {
1117 assert!((n[2].abs() - 1.0).abs() < 1e-5);
1118 }
1119 }
1120
1121 #[test]
1122 fn test_mesh_compute_smooth_normals() {
1123 let mut m = quad();
1124 m.compute_smooth_normals();
1125 assert_eq!(m.normals.len(), m.vertices.len());
1126 }
1127
1128 #[test]
1129 fn test_mesh_add_face_with_material() {
1130 let mut m = MeshData::new();
1131 m.add_vertex([0.0; 3]);
1132 m.add_vertex([1.0, 0.0, 0.0]);
1133 m.add_vertex([0.0, 1.0, 0.0]);
1134 m.add_face_with_material(0, 1, 2, 3);
1135 assert_eq!(m.material_ids[0], 3);
1136 }
1137
1138 #[test]
1141 fn test_obj_export_basic() {
1142 let exporter = ObjExporter::new();
1143 let m = quad();
1144 let obj = exporter.export_obj(&m, None);
1145 assert!(obj.contains("v 0 0 0"));
1146 assert!(obj.contains("f 1 2 3"));
1147 }
1148
1149 #[test]
1150 fn test_obj_export_with_mtllib() {
1151 let exporter = ObjExporter::new();
1152 let m = quad();
1153 let obj = exporter.export_obj(&m, Some("scene.mtl"));
1154 assert!(obj.contains("mtllib scene.mtl"));
1155 }
1156
1157 #[test]
1158 fn test_obj_export_normals() {
1159 let mut exporter = ObjExporter::new();
1160 exporter.add_material(Material::new("mat0"));
1161 let mut m = quad();
1162 m.compute_flat_normals();
1163 let obj = exporter.export_obj(&m, None);
1164 assert!(obj.contains("vn "));
1165 }
1166
1167 #[test]
1168 fn test_mtl_export() {
1169 let mut exporter = ObjExporter::new();
1170 exporter.add_material(Material::new("red"));
1171 let mtl = exporter.export_mtl();
1172 assert!(mtl.contains("newmtl red"));
1173 assert!(mtl.contains("Kd "));
1174 }
1175
1176 #[test]
1177 fn test_obj_usemtl_directive() {
1178 let mut exporter = ObjExporter::new();
1179 exporter.add_material(Material::new("mat0"));
1180 let mut m = quad();
1181 m.material_ids = vec![0, 0];
1182 let obj = exporter.export_obj(&m, None);
1183 assert!(obj.contains("usemtl mat0"));
1184 }
1185
1186 #[test]
1189 fn test_stl_binary_header() {
1190 let exp = StlExporter::new();
1191 let m = quad();
1192 let bytes = exp.export_binary(&m, "test");
1193 assert!(bytes.len() >= 84);
1194 assert_eq!(&bytes[..4], b"test");
1196 }
1197
1198 #[test]
1199 fn test_stl_binary_triangle_count() {
1200 let exp = StlExporter::new();
1201 let m = quad();
1202 let bytes = exp.export_binary(&m, "q");
1203 let count = u32::from_le_bytes(bytes[80..84].try_into().unwrap());
1204 assert_eq!(count, 2);
1205 }
1206
1207 #[test]
1208 fn test_stl_binary_size() {
1209 let exp = StlExporter::new();
1210 let m = quad();
1211 let bytes = exp.export_binary(&m, "q");
1212 assert_eq!(bytes.len(), 84 + 2 * 50);
1213 }
1214
1215 #[test]
1216 fn test_stl_ascii_contains_solid() {
1217 let exp = StlExporter::new();
1218 let m = quad();
1219 let s = exp.export_ascii(&m, "mymesh");
1220 assert!(s.starts_with("solid mymesh"));
1221 assert!(s.contains("endsolid mymesh"));
1222 }
1223
1224 #[test]
1225 fn test_stl_ascii_facet_count() {
1226 let exp = StlExporter::new();
1227 let m = quad();
1228 let s = exp.export_ascii(&m, "q");
1229 let count = s.matches("facet normal").count();
1230 assert_eq!(count, 2);
1231 }
1232
1233 #[test]
1234 fn test_stl_crc32_known() {
1235 let crc = StlExporter::crc32(&[]);
1237 assert_eq!(crc, 0x0000_0000);
1238 }
1239
1240 #[test]
1243 fn test_ply_ascii_header() {
1244 let exp = PlyExporter::new(false);
1245 let m = quad();
1246 let bytes = exp.export_ascii_bytes(&m);
1247 let s = String::from_utf8_lossy(&bytes);
1248 assert!(s.contains("ply"));
1249 assert!(s.contains("format ascii"));
1250 assert!(s.contains("element vertex 4"));
1251 assert!(s.contains("element face 2"));
1252 }
1253
1254 #[test]
1255 fn test_ply_binary_header() {
1256 let exp = PlyExporter::new(true);
1257 let m = quad();
1258 let bytes = exp.export_binary_bytes(&m);
1259 let header_end = bytes
1260 .windows(11)
1261 .position(|w| w == b"end_header\n")
1262 .unwrap()
1263 + 11;
1264 let header = String::from_utf8_lossy(&bytes[..header_end]);
1265 assert!(header.contains("format binary_little_endian"));
1266 }
1267
1268 #[test]
1269 fn test_ply_export_dispatch() {
1270 let exp_ascii = PlyExporter::new(false);
1271 let exp_bin = PlyExporter::new(true);
1272 let m = quad();
1273 let a = exp_ascii.export(&m);
1274 let b = exp_bin.export(&m);
1275 assert!(!a.is_empty());
1276 assert!(!b.is_empty());
1277 }
1278
1279 #[test]
1280 fn test_ply_ascii_with_normals() {
1281 let mut m = quad();
1282 m.compute_flat_normals();
1283 let exp = PlyExporter::new(false);
1284 let bytes = exp.export_ascii_bytes(&m);
1285 let s = String::from_utf8_lossy(&bytes);
1286 assert!(s.contains("property float nx"));
1287 }
1288
1289 #[test]
1292 fn test_gltf_json_structure() {
1293 let exp = GltfExporter::new("test_scene");
1294 let m = quad();
1295 let (json, _bin) = exp.export(&m);
1296 assert!(json.contains("\"version\": \"2.0\""));
1297 assert!(json.contains("test_scene"));
1298 assert!(json.contains("POSITION"));
1299 }
1300
1301 #[test]
1302 fn test_gltf_binary_buffer_size() {
1303 let exp = GltfExporter::new("s");
1304 let m = quad(); let (_json, bin) = exp.export(&m);
1306 let expected = 4 * 3 * 4 + 2 * 3 * 4;
1308 assert_eq!(bin.len(), expected);
1309 }
1310
1311 #[test]
1312 fn test_gltf_accessor_count() {
1313 let exp = GltfExporter::new("s");
1314 let m = quad();
1315 let (json, _) = exp.export(&m);
1316 assert!(json.contains("\"count\": 4")); assert!(json.contains("\"count\": 6")); }
1319
1320 #[test]
1323 fn test_import_obj_basic() {
1324 let src = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n";
1325 let imp = MeshImporter::new();
1326 let m = imp.parse_obj(src);
1327 assert_eq!(m.vertices.len(), 3);
1328 assert_eq!(m.faces.len(), 1);
1329 }
1330
1331 #[test]
1332 fn test_import_obj_normals() {
1333 let src = "v 0 0 0\nv 1 0 0\nv 0 1 0\nvn 0 0 1\nvn 0 0 1\nvn 0 0 1\nf 1//1 2//2 3//3\n";
1334 let imp = MeshImporter::new();
1335 let m = imp.parse_obj(src);
1336 assert_eq!(m.vertices.len(), 3);
1337 assert_eq!(m.normals.len(), 3);
1339 }
1340
1341 #[test]
1342 fn test_import_obj_quad_fan() {
1343 let src = "v 0 0 0\nv 1 0 0\nv 1 1 0\nv 0 1 0\nf 1 2 3 4\n";
1344 let imp = MeshImporter::new();
1345 let m = imp.parse_obj(src);
1346 assert_eq!(m.faces.len(), 2); }
1348
1349 #[test]
1350 fn test_import_stl_binary_roundtrip() {
1351 let exp = StlExporter::new();
1352 let m = quad();
1353 let bytes = exp.export_binary(&m, "test");
1354 let imp = MeshImporter::new();
1355 let m2 = imp.parse_stl_binary(&bytes).unwrap();
1356 assert_eq!(m2.faces.len(), 2);
1357 }
1358
1359 #[test]
1360 fn test_import_stl_ascii_roundtrip() {
1361 let exp = StlExporter::new();
1362 let m = quad();
1363 let s = exp.export_ascii(&m, "q");
1364 let imp = MeshImporter::new();
1365 let m2 = imp.parse_stl_ascii(&s);
1366 assert_eq!(m2.faces.len(), 2);
1367 }
1368
1369 #[test]
1370 fn test_import_stl_binary_too_short() {
1371 let imp = MeshImporter::new();
1372 let result = imp.parse_stl_binary(&[0u8; 10]);
1373 assert!(result.is_none());
1374 }
1375
1376 #[test]
1377 fn test_import_ply_ascii_roundtrip() {
1378 let exp = PlyExporter::new(false);
1379 let m = quad();
1380 let bytes = exp.export_ascii_bytes(&m);
1381 let s = String::from_utf8(bytes).unwrap();
1382 let imp = MeshImporter::new();
1383 let m2 = imp.parse_ply_ascii(&s).unwrap();
1384 assert_eq!(m2.vertices.len(), 4);
1385 assert_eq!(m2.faces.len(), 2);
1386 }
1387
1388 #[test]
1389 fn test_import_ply_ascii_with_normals() {
1390 let exp = PlyExporter::new(false);
1391 let mut m = quad();
1392 m.compute_flat_normals();
1393 let bytes = exp.export_ascii_bytes(&m);
1394 let s = String::from_utf8(bytes).unwrap();
1395 let imp = MeshImporter::new();
1396 let m2 = imp.parse_ply_ascii(&s).unwrap();
1397 assert_eq!(m2.normals.len(), 4);
1398 }
1399
1400 #[test]
1403 fn test_simplify_already_simple() {
1404 let s = MeshSimplification::new();
1405 let m = quad();
1406 let out = s.simplify(&m, 10);
1407 assert_eq!(out.faces.len(), m.faces.len());
1408 }
1409
1410 #[test]
1411 fn test_simplify_reduces_triangles() {
1412 let s = MeshSimplification::new();
1413 let m = tetra();
1414 let out = s.simplify(&m, 2);
1415 assert!(out.faces.len() <= 4);
1416 }
1417
1418 #[test]
1419 fn test_simplify_empty_mesh() {
1420 let s = MeshSimplification::new();
1421 let m = MeshData::new();
1422 let out = s.simplify(&m, 100);
1423 assert_eq!(out.faces.len(), 0);
1424 }
1425
1426 #[test]
1427 fn test_simplify_single_face() {
1428 let s = MeshSimplification::new();
1429 let mut m = MeshData::new();
1430 m.add_vertex([0.0; 3]);
1431 m.add_vertex([1.0, 0.0, 0.0]);
1432 m.add_vertex([0.0, 1.0, 0.0]);
1433 m.add_face(0, 1, 2);
1434 let out = s.simplify(&m, 1);
1435 assert_eq!(out.faces.len(), 1);
1436 }
1437
1438 #[test]
1441 fn test_material_default() {
1442 let mat = Material::default();
1443 assert_eq!(mat.name, "default");
1444 assert!((mat.opacity - 1.0).abs() < 1e-10);
1445 }
1446
1447 #[test]
1448 fn test_material_new() {
1449 let mat = Material::new("chrome");
1450 assert_eq!(mat.name, "chrome");
1451 }
1452
1453 #[test]
1456 fn test_triangle_normal_z() {
1457 let n = triangle_normal_f64([0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1458 assert!((n[2] - 1.0).abs() < 1e-10);
1459 }
1460
1461 #[test]
1462 fn test_normalize3_f64_unit() {
1463 let n = normalize3_f64([3.0, 0.0, 0.0]);
1464 assert!((n[0] - 1.0).abs() < 1e-10);
1465 }
1466
1467 #[test]
1468 fn test_normalize3_f64_zero() {
1469 let n = normalize3_f64([0.0; 3]);
1470 assert_eq!(n, [0.0; 3]);
1471 }
1472}