1#![allow(clippy::manual_strip, clippy::should_implement_trait)]
6#[allow(unused_imports)]
7use super::functions::*;
8#[allow(unused_imports)]
9use super::functions_2::*;
10
11use crate::{Error, Result};
12use oxiphysics_core::math::Vec3;
13use std::fs::File;
14use std::io::{BufRead, BufReader, BufWriter, Write};
15use std::path::Path;
16
17#[allow(dead_code)]
19#[derive(Debug, Clone, Default)]
20pub struct ObjScene {
21 pub nodes: Vec<ObjSceneNode>,
23 pub meshes: Vec<ObjMesh>,
25 pub materials: Vec<ObjMaterial>,
27}
28#[allow(dead_code)]
29impl ObjScene {
30 pub fn new() -> Self {
32 Self::default()
33 }
34 pub fn add_mesh(&mut self, mesh: ObjMesh) -> usize {
36 let idx = self.meshes.len();
37 self.meshes.push(mesh);
38 idx
39 }
40 pub fn add_material(&mut self, mat: ObjMaterial) -> usize {
42 let idx = self.materials.len();
43 self.materials.push(mat);
44 idx
45 }
46 pub fn add_node(
48 &mut self,
49 name: &str,
50 mesh_index: Option<usize>,
51 transform: MeshTransform,
52 ) -> usize {
53 let idx = self.nodes.len();
54 self.nodes.push(ObjSceneNode {
55 name: name.to_string(),
56 transform,
57 mesh_index,
58 children: Vec::new(),
59 });
60 idx
61 }
62 pub fn add_child(&mut self, parent_idx: usize, child_idx: usize) {
64 if parent_idx < self.nodes.len() {
65 self.nodes[parent_idx].children.push(child_idx);
66 }
67 }
68 pub fn flatten(&self) -> ObjMesh {
72 let mut result = ObjMesh::default();
73 for node in &self.nodes {
74 if let Some(mi) = node.mesh_index
75 && let Some(mesh) = self.meshes.get(mi)
76 {
77 let inst = MeshInstance {
78 name: node.name.clone(),
79 transform: node.transform.clone(),
80 };
81 let xformed = instantiate_mesh(mesh, &inst);
82 result = merge_obj_meshes(&result, &xformed);
83 }
84 }
85 result
86 }
87 pub fn total_vertices(&self) -> usize {
89 self.meshes.iter().map(|m| m.vertices.len()).sum()
90 }
91 pub fn total_faces(&self) -> usize {
93 self.meshes.iter().map(|m| m.faces.len()).sum()
94 }
95}
96#[allow(dead_code)]
98#[derive(Debug, Clone)]
99pub struct ObjCurve {
100 pub name: String,
102 pub degree: usize,
104 pub control_points: Vec<usize>,
106 pub knots: Vec<f64>,
108}
109#[allow(dead_code)]
111pub struct ObjReader;
112#[allow(dead_code)]
113impl ObjReader {
114 pub fn from_str(data: &str) -> Result<ObjMesh> {
116 let mut mesh = ObjMesh::default();
117 let mut current_group_name: Option<String> = None;
118 let mut current_group_start: usize = 0;
119 let mut current_smoothing_group: u32 = 0;
120 let mut current_material: Option<String> = None;
121 for raw in data.lines() {
122 let line = raw.trim();
123 if line.is_empty() || line.starts_with('#') {
124 continue;
125 }
126 if line.starts_with("g ") || line.starts_with("o ") {
127 if let Some(ref name) = current_group_name {
128 let count = mesh.faces.len() - current_group_start;
129 if count > 0 {
130 mesh.groups.push(ObjGroup {
131 name: name.clone(),
132 face_start: current_group_start,
133 face_count: count,
134 });
135 }
136 }
137 let name = line[2..].trim().to_string();
138 current_group_name = Some(name);
139 current_group_start = mesh.faces.len();
140 } else if line.starts_with("s ") {
141 let val = line[2..].trim();
142 current_smoothing_group = if val == "off" || val == "0" {
143 0
144 } else {
145 val.parse::<u32>().unwrap_or(0)
146 };
147 } else if line.starts_with("usemtl ") {
148 current_material = Some(line[7..].trim().to_string());
149 } else {
150 Self::parse_line_extended(
151 line,
152 &mut mesh,
153 current_smoothing_group,
154 ¤t_material,
155 )?;
156 }
157 }
158 if let Some(ref name) = current_group_name {
159 let count = mesh.faces.len() - current_group_start;
160 if count > 0 {
161 mesh.groups.push(ObjGroup {
162 name: name.clone(),
163 face_start: current_group_start,
164 face_count: count,
165 });
166 }
167 }
168 Ok(mesh)
169 }
170 fn parse_line_extended(
171 line: &str,
172 mesh: &mut ObjMesh,
173 smoothing_group: u32,
174 material: &Option<String>,
175 ) -> Result<()> {
176 if line.starts_with("vn ") {
177 let p: Vec<&str> = line.split_whitespace().collect();
178 if p.len() >= 4 {
179 let x = p[1]
180 .parse::<f64>()
181 .map_err(|e| Error::Parse(e.to_string()))?;
182 let y = p[2]
183 .parse::<f64>()
184 .map_err(|e| Error::Parse(e.to_string()))?;
185 let z = p[3]
186 .parse::<f64>()
187 .map_err(|e| Error::Parse(e.to_string()))?;
188 mesh.normals.push([x, y, z]);
189 }
190 } else if line.starts_with("vt ") {
191 let p: Vec<&str> = line.split_whitespace().collect();
192 if p.len() >= 3 {
193 let u = p[1]
194 .parse::<f64>()
195 .map_err(|e| Error::Parse(e.to_string()))?;
196 let v = p[2]
197 .parse::<f64>()
198 .map_err(|e| Error::Parse(e.to_string()))?;
199 mesh.uvs.push([u, v]);
200 }
201 } else if line.starts_with("v ") {
202 let p: Vec<&str> = line.split_whitespace().collect();
203 if p.len() >= 4 {
204 let x = p[1]
205 .parse::<f64>()
206 .map_err(|e| Error::Parse(e.to_string()))?;
207 let y = p[2]
208 .parse::<f64>()
209 .map_err(|e| Error::Parse(e.to_string()))?;
210 let z = p[3]
211 .parse::<f64>()
212 .map_err(|e| Error::Parse(e.to_string()))?;
213 mesh.vertices.push([x, y, z]);
214 }
215 } else if line.starts_with("f ") {
216 let p: Vec<&str> = line.split_whitespace().collect();
217 let mut vis = Vec::new();
218 let mut vts = Vec::new();
219 let mut vns = Vec::new();
220 let mut has_vt = false;
221 let mut has_vn = false;
222 for tok in &p[1..] {
223 let parts: Vec<&str> = tok.split('/').collect();
224 let vi = parts[0]
225 .parse::<usize>()
226 .map_err(|e| Error::Parse(e.to_string()))?
227 - 1;
228 vis.push(vi);
229 if parts.len() >= 2 && !parts[1].is_empty() {
230 has_vt = true;
231 let vt = parts[1]
232 .parse::<usize>()
233 .map_err(|e| Error::Parse(e.to_string()))?
234 - 1;
235 vts.push(vt);
236 }
237 if parts.len() >= 3 && !parts[2].is_empty() {
238 has_vn = true;
239 let vn = parts[2]
240 .parse::<usize>()
241 .map_err(|e| Error::Parse(e.to_string()))?
242 - 1;
243 vns.push(vn);
244 }
245 }
246 mesh.faces.push(ObjFace {
247 vertex_indices: vis,
248 normal_indices: if has_vn { Some(vns) } else { None },
249 uv_indices: if has_vt { Some(vts) } else { None },
250 smoothing_group,
251 material: material.clone(),
252 });
253 }
254 Ok(())
255 }
256 pub fn from_file(path: &str) -> Result<ObjMesh> {
258 let file = File::open(Path::new(path))?;
259 let reader = BufReader::new(file);
260 let mut data = String::new();
261 for raw in reader.lines() {
262 let raw = raw?;
263 data.push_str(&raw);
264 data.push('\n');
265 }
266 Self::from_str(&data)
267 }
268 pub fn read(path: &str) -> Result<(Vec<Vec3>, Vec<[usize; 3]>)> {
272 let mesh = Self::from_file(path)?;
273 let vertices: Vec<Vec3> = mesh
274 .vertices
275 .iter()
276 .map(|v| Vec3::new(v[0], v[1], v[2]))
277 .collect();
278 let faces: Vec<[usize; 3]> = mesh
279 .faces
280 .iter()
281 .filter(|f| f.vertex_indices.len() >= 3)
282 .map(|f| {
283 [
284 f.vertex_indices[0],
285 f.vertex_indices[1],
286 f.vertex_indices[2],
287 ]
288 })
289 .collect();
290 Ok((vertices, faces))
291 }
292}
293#[allow(dead_code)]
295#[derive(Debug, Clone)]
296pub struct ObjMaterial {
297 pub name: String,
299 pub kd: [f64; 3],
301 pub ks: [f64; 3],
303 pub ns: f64,
305 pub ka: [f64; 3],
307 pub dissolve: f64,
309 pub map_kd: Option<String>,
311}
312#[allow(dead_code)]
313impl ObjMaterial {
314 pub fn basic(name: &str, kd: [f64; 3]) -> Self {
316 Self {
317 name: name.to_string(),
318 kd,
319 ks: [0.0; 3],
320 ns: 1.0,
321 ka: [0.0; 3],
322 dissolve: 1.0,
323 map_kd: None,
324 }
325 }
326}
327#[allow(dead_code)]
329#[derive(Debug, Clone, Default)]
330pub struct ObjLod {
331 pub levels: Vec<ObjMesh>,
333 pub thresholds: Vec<f64>,
336}
337#[allow(dead_code)]
338impl ObjLod {
339 pub fn new() -> Self {
341 Self::default()
342 }
343 pub fn push(&mut self, mesh: ObjMesh, threshold: f64) {
345 self.levels.push(mesh);
346 self.thresholds.push(threshold);
347 }
348 pub fn select(&self, distance: f64) -> Option<&ObjMesh> {
350 for (i, &t) in self.thresholds.iter().enumerate() {
351 if distance <= t {
352 return self.levels.get(i);
353 }
354 }
355 self.levels.last()
356 }
357 pub fn num_levels(&self) -> usize {
359 self.levels.len()
360 }
361 pub fn decimate(mesh: &ObjMesh, target_faces: usize) -> ObjMesh {
372 if mesh.faces.len() <= target_faces {
373 return mesh.clone();
374 }
375 decimate_qem_impl(mesh, target_faces)
376 }
377}
378#[allow(dead_code)]
380#[derive(Debug, Clone)]
381pub struct MeshTransform {
382 pub translation: [f64; 3],
384 pub scale: f64,
386 pub axis: [f64; 3],
388 pub angle: f64,
390}
391#[allow(dead_code)]
392impl MeshTransform {
393 pub fn identity() -> Self {
395 Self {
396 translation: [0.0; 3],
397 scale: 1.0,
398 axis: [0.0, 0.0, 1.0],
399 angle: 0.0,
400 }
401 }
402 pub fn from_translation(tx: f64, ty: f64, tz: f64) -> Self {
404 Self {
405 translation: [tx, ty, tz],
406 scale: 1.0,
407 axis: [0.0, 0.0, 1.0],
408 angle: 0.0,
409 }
410 }
411 pub fn apply(&self, p: [f64; 3]) -> [f64; 3] {
415 let s = [p[0] * self.scale, p[1] * self.scale, p[2] * self.scale];
416 let (ax, ay, az) = (self.axis[0], self.axis[1], self.axis[2]);
417 let (sin_a, cos_a) = (self.angle.sin(), self.angle.cos());
418 let dot = ax * s[0] + ay * s[1] + az * s[2];
419 let cross = [
420 ay * s[2] - az * s[1],
421 az * s[0] - ax * s[2],
422 ax * s[1] - ay * s[0],
423 ];
424 let rx = s[0] * cos_a + cross[0] * sin_a + ax * dot * (1.0 - cos_a);
425 let ry = s[1] * cos_a + cross[1] * sin_a + ay * dot * (1.0 - cos_a);
426 let rz = s[2] * cos_a + cross[2] * sin_a + az * dot * (1.0 - cos_a);
427 [
428 rx + self.translation[0],
429 ry + self.translation[1],
430 rz + self.translation[2],
431 ]
432 }
433}
434#[allow(dead_code)]
436#[derive(Debug, Clone)]
437pub struct ObjMeshStats {
438 pub vertex_count: usize,
440 pub face_count: usize,
442 pub triangle_count: usize,
444 pub material_count: usize,
446 pub group_count: usize,
448 pub faces_with_normals: usize,
450 pub faces_with_uvs: usize,
452 pub surface_area: f64,
454 pub bbox: Option<([f64; 3], [f64; 3])>,
456}
457#[allow(dead_code)]
459#[derive(Debug, Clone, Default)]
460pub struct ObjMesh {
461 pub vertices: Vec<[f64; 3]>,
463 pub normals: Vec<[f64; 3]>,
465 pub uvs: Vec<[f64; 2]>,
467 pub faces: Vec<ObjFace>,
469 pub groups: Vec<ObjGroup>,
471}
472#[allow(dead_code)]
473impl ObjMesh {
474 pub fn to_triangle_soup(&self) -> Vec<[[f64; 3]; 3]> {
479 let mut soup = Vec::new();
480 for face in &self.faces {
481 let verts = &face.vertex_indices;
482 if verts.len() < 3 {
483 continue;
484 }
485 for i in 1..(verts.len() - 1) {
486 let v0 = self.vertices[verts[0]];
487 let v1 = self.vertices[verts[i]];
488 let v2 = self.vertices[verts[i + 1]];
489 soup.push([v0, v1, v2]);
490 }
491 }
492 soup
493 }
494 pub fn faces_in_group(&self, group_name: &str) -> Vec<&ObjFace> {
496 if let Some(group) = self.groups.iter().find(|g| g.name == group_name) {
497 let end = group.face_start + group.face_count;
498 let end = end.min(self.faces.len());
499 self.faces[group.face_start..end].iter().collect()
500 } else {
501 Vec::new()
502 }
503 }
504 pub fn faces_in_smoothing_group(&self, sg: u32) -> Vec<&ObjFace> {
506 self.faces
507 .iter()
508 .filter(|f| f.smoothing_group == sg)
509 .collect()
510 }
511 pub fn faces_with_material(&self, mat_name: &str) -> Vec<&ObjFace> {
513 self.faces
514 .iter()
515 .filter(|f| f.material.as_deref() == Some(mat_name))
516 .collect()
517 }
518 pub fn triangle_count(&self) -> usize {
520 self.faces
521 .iter()
522 .map(|f| {
523 if f.vertex_indices.len() >= 3 {
524 f.vertex_indices.len() - 2
525 } else {
526 0
527 }
528 })
529 .sum()
530 }
531 pub fn face_normal(&self, face_idx: usize) -> Option<[f64; 3]> {
533 let face = self.faces.get(face_idx)?;
534 if face.vertex_indices.len() < 3 {
535 return None;
536 }
537 let v0 = self.vertices[face.vertex_indices[0]];
538 let v1 = self.vertices[face.vertex_indices[1]];
539 let v2 = self.vertices[face.vertex_indices[2]];
540 let e1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
541 let e2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
542 let n = [
543 e1[1] * e2[2] - e1[2] * e2[1],
544 e1[2] * e2[0] - e1[0] * e2[2],
545 e1[0] * e2[1] - e1[1] * e2[0],
546 ];
547 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
548 if len < 1e-30 {
549 return None;
550 }
551 Some([n[0] / len, n[1] / len, n[2] / len])
552 }
553 pub fn bounding_box(&self) -> Option<([f64; 3], [f64; 3])> {
555 if self.vertices.is_empty() {
556 return None;
557 }
558 let mut min = self.vertices[0];
559 let mut max = self.vertices[0];
560 for v in &self.vertices[1..] {
561 for k in 0..3 {
562 if v[k] < min[k] {
563 min[k] = v[k];
564 }
565 if v[k] > max[k] {
566 max[k] = v[k];
567 }
568 }
569 }
570 Some((min, max))
571 }
572}
573#[allow(dead_code)]
575#[derive(Debug, Clone)]
576pub struct MeshInstance {
577 pub name: String,
579 pub transform: MeshTransform,
581}
582#[allow(dead_code)]
584#[derive(Debug, Clone)]
585pub struct ObjGroup {
586 pub name: String,
588 pub face_start: usize,
590 pub face_count: usize,
592}
593#[allow(dead_code)]
598#[derive(Debug, Clone)]
599pub struct ObjFace {
600 pub vertex_indices: Vec<usize>,
602 pub normal_indices: Option<Vec<usize>>,
604 pub uv_indices: Option<Vec<usize>>,
606 pub smoothing_group: u32,
608 pub material: Option<String>,
610}
611#[allow(dead_code)]
613pub struct ObjWriter;
614#[allow(dead_code)]
615impl ObjWriter {
616 pub fn write(mesh: &ObjMesh) -> String {
618 Self::write_with_groups(mesh, false)
619 }
620 pub fn write_with_groups(mesh: &ObjMesh, emit_groups: bool) -> String {
622 let mut s = String::from("# OxiPhysics OBJ export\n");
623 for v in &mesh.vertices {
624 s.push_str(&format!("v {} {} {}\n", v[0], v[1], v[2]));
625 }
626 for vn in &mesh.normals {
627 s.push_str(&format!("vn {} {} {}\n", vn[0], vn[1], vn[2]));
628 }
629 for vt in &mesh.uvs {
630 s.push_str(&format!("vt {} {}\n", vt[0], vt[1]));
631 }
632 let mut current_group: Option<&str> = None;
633 let mut current_material: Option<&str> = None;
634 let mut current_sg: u32 = 0;
635 for (fi, face) in mesh.faces.iter().enumerate() {
636 if emit_groups {
637 for group in &mesh.groups {
638 if fi == group.face_start && current_group != Some(&group.name) {
639 s.push_str(&format!("g {}\n", group.name));
640 current_group = Some(&group.name);
641 }
642 }
643 }
644 if let Some(ref mat) = face.material
645 && current_material != Some(mat.as_str())
646 {
647 s.push_str(&format!("usemtl {}\n", mat));
648 current_material = Some(mat);
649 }
650 if face.smoothing_group != current_sg {
651 current_sg = face.smoothing_group;
652 if current_sg == 0 {
653 s.push_str("s off\n");
654 } else {
655 s.push_str(&format!("s {}\n", current_sg));
656 }
657 }
658 s.push('f');
659 for i in 0..face.vertex_indices.len() {
660 let vi = face.vertex_indices[i] + 1;
661 let vt_idx = face.uv_indices.as_ref().map(|uvs| uvs[i] + 1);
662 let vn_idx = face.normal_indices.as_ref().map(|ns| ns[i] + 1);
663 let token = match (vt_idx, vn_idx) {
664 (Some(vt), Some(vn)) => format!(" {}/{}/{}", vi, vt, vn),
665 (None, Some(vn)) => format!(" {}//{}", vi, vn),
666 (Some(vt), None) => format!(" {}/{}", vi, vt),
667 (None, None) => format!(" {}", vi),
668 };
669 s.push_str(&token);
670 }
671 s.push('\n');
672 }
673 s
674 }
675 pub fn write_to_file(path: &str, mesh: &ObjMesh) -> Result<()> {
677 let file = File::create(Path::new(path))?;
678 let mut w = BufWriter::new(file);
679 write!(w, "{}", Self::write(mesh))?;
680 w.flush()?;
681 Ok(())
682 }
683 pub fn write_legacy(
687 path: &str,
688 vertices: &[Vec3],
689 triangles: &[[usize; 3]],
690 normals: Option<&[Vec3]>,
691 ) -> Result<()> {
692 let file = File::create(Path::new(path))?;
693 let mut w = BufWriter::new(file);
694 writeln!(w, "# OxiPhysics OBJ export")?;
695 for v in vertices {
696 writeln!(w, "v {} {} {}", v.x, v.y, v.z)?;
697 }
698 if let Some(norms) = normals {
699 for n in norms {
700 writeln!(w, "vn {} {} {}", n.x, n.y, n.z)?;
701 }
702 for t in triangles {
703 writeln!(
704 w,
705 "f {}//{} {}//{} {}//{}",
706 t[0] + 1,
707 t[0] + 1,
708 t[1] + 1,
709 t[1] + 1,
710 t[2] + 1,
711 t[2] + 1,
712 )?;
713 }
714 } else {
715 for t in triangles {
716 writeln!(w, "f {} {} {}", t[0] + 1, t[1] + 1, t[2] + 1)?;
717 }
718 }
719 w.flush()?;
720 Ok(())
721 }
722 pub fn write_with_uvs(
724 path: &str,
725 vertices: &[Vec3],
726 uvs: &[[f64; 2]],
727 triangles: &[[usize; 3]],
728 ) -> Result<()> {
729 let file = File::create(Path::new(path))?;
730 let mut w = BufWriter::new(file);
731 writeln!(w, "# OxiPhysics OBJ export")?;
732 for v in vertices {
733 writeln!(w, "v {} {} {}", v.x, v.y, v.z)?;
734 }
735 for uv in uvs {
736 writeln!(w, "vt {} {}", uv[0], uv[1])?;
737 }
738 for t in triangles {
739 writeln!(
740 w,
741 "f {}/{} {}/{} {}/{}",
742 t[0] + 1,
743 t[0] + 1,
744 t[1] + 1,
745 t[1] + 1,
746 t[2] + 1,
747 t[2] + 1,
748 )?;
749 }
750 w.flush()?;
751 Ok(())
752 }
753}
754#[allow(dead_code)]
756#[derive(Debug, Clone)]
757pub struct ObjSceneNode {
758 pub name: String,
760 pub transform: MeshTransform,
762 pub mesh_index: Option<usize>,
764 pub children: Vec<usize>,
766}
767#[allow(dead_code)]
769#[derive(Debug, Clone, Copy, PartialEq)]
770pub struct ObjVertexColor {
771 pub r: f64,
773 pub g: f64,
775 pub b: f64,
777 pub a: f64,
779}
780#[allow(dead_code)]
781impl ObjVertexColor {
782 pub fn rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
784 Self { r, g, b, a }
785 }
786 pub fn rgb(r: f64, g: f64, b: f64) -> Self {
788 Self { r, g, b, a: 1.0 }
789 }
790 pub fn to_array(self) -> [f64; 4] {
792 [self.r, self.g, self.b, self.a]
793 }
794 pub fn lerp(self, other: Self, t: f64) -> Self {
796 Self {
797 r: self.r + (other.r - self.r) * t,
798 g: self.g + (other.g - self.g) * t,
799 b: self.b + (other.b - self.b) * t,
800 a: self.a + (other.a - self.a) * t,
801 }
802 }
803}
804#[allow(dead_code)]
806#[derive(Debug, Clone)]
807pub struct ObjSurface {
808 pub name: String,
810 pub degree_u: usize,
812 pub degree_v: usize,
814 pub control_points: Vec<usize>,
816 pub n_u: usize,
818 pub knots_u: Vec<f64>,
820 pub knots_v: Vec<f64>,
822}
823#[allow(dead_code)]
825pub struct MtlWriter;
826#[allow(dead_code)]
827impl MtlWriter {
828 pub fn write(materials: &[ObjMaterial]) -> String {
830 let mut s = String::from("# OxiPhysics MTL export\n");
831 for mat in materials {
832 s.push_str(&format!("\nnewmtl {}\n", mat.name));
833 s.push_str(&format!("Ka {} {} {}\n", mat.ka[0], mat.ka[1], mat.ka[2]));
834 s.push_str(&format!("Kd {} {} {}\n", mat.kd[0], mat.kd[1], mat.kd[2]));
835 s.push_str(&format!("Ks {} {} {}\n", mat.ks[0], mat.ks[1], mat.ks[2]));
836 s.push_str(&format!("Ns {}\n", mat.ns));
837 s.push_str(&format!("d {}\n", mat.dissolve));
838 if let Some(ref tex) = mat.map_kd {
839 s.push_str(&format!("map_Kd {}\n", tex));
840 }
841 }
842 s
843 }
844}
845#[allow(dead_code)]
850#[derive(Debug, Clone, Default)]
851pub struct ObjVertexColorMesh {
852 pub mesh: ObjMesh,
854 pub colors: Vec<ObjVertexColor>,
856}
857#[allow(dead_code)]
858impl ObjVertexColorMesh {
859 pub fn from_str(data: &str) -> std::result::Result<Self, String> {
861 let mut vcmesh = ObjVertexColorMesh::default();
862 let mut smoothing_group: u32 = 0;
863 let mut current_material: Option<String> = None;
864 let mut current_group_name: Option<String> = None;
865 let mut current_group_start: usize = 0;
866 for raw in data.lines() {
867 let line = raw.trim();
868 if line.is_empty() || line.starts_with('#') {
869 continue;
870 }
871 if line.starts_with("g ") || line.starts_with("o ") {
872 if let Some(ref name) = current_group_name {
873 let count = vcmesh.mesh.faces.len() - current_group_start;
874 if count > 0 {
875 vcmesh.mesh.groups.push(ObjGroup {
876 name: name.clone(),
877 face_start: current_group_start,
878 face_count: count,
879 });
880 }
881 }
882 current_group_name = Some(line[2..].trim().to_string());
883 current_group_start = vcmesh.mesh.faces.len();
884 } else if line.starts_with("usemtl ") {
885 current_material = Some(line[7..].trim().to_string());
886 } else if line.starts_with("s ") {
887 let val = line[2..].trim();
888 smoothing_group = if val == "off" || val == "0" {
889 0
890 } else {
891 val.parse::<u32>().unwrap_or(0)
892 };
893 } else if line.starts_with("v ") {
894 let p: Vec<&str> = line.split_whitespace().collect();
895 if p.len() < 4 {
896 return Err(format!("Vertex line too short: {}", line));
897 }
898 let x: f64 = p[1]
899 .parse()
900 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
901 let y: f64 = p[2]
902 .parse()
903 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
904 let z: f64 = p[3]
905 .parse()
906 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
907 vcmesh.mesh.vertices.push([x, y, z]);
908 let r: f64 = p.get(4).and_then(|v| v.parse().ok()).unwrap_or(1.0);
909 let g: f64 = p.get(5).and_then(|v| v.parse().ok()).unwrap_or(1.0);
910 let b: f64 = p.get(6).and_then(|v| v.parse().ok()).unwrap_or(1.0);
911 let a: f64 = p.get(7).and_then(|v| v.parse().ok()).unwrap_or(1.0);
912 vcmesh.colors.push(ObjVertexColor { r, g, b, a });
913 } else if line.starts_with("vn ") {
914 let p: Vec<&str> = line.split_whitespace().collect();
915 if p.len() >= 4 {
916 let x: f64 = p[1]
917 .parse()
918 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
919 let y: f64 = p[2]
920 .parse()
921 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
922 let z: f64 = p[3]
923 .parse()
924 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
925 vcmesh.mesh.normals.push([x, y, z]);
926 }
927 } else if line.starts_with("vt ") {
928 let p: Vec<&str> = line.split_whitespace().collect();
929 if p.len() >= 3 {
930 let u: f64 = p[1]
931 .parse()
932 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
933 let v: f64 = p[2]
934 .parse()
935 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
936 vcmesh.mesh.uvs.push([u, v]);
937 }
938 } else if line.starts_with("f ") {
939 let p: Vec<&str> = line.split_whitespace().collect();
940 let mut vis = Vec::new();
941 let mut vts = Vec::new();
942 let mut vns = Vec::new();
943 let mut has_vt = false;
944 let mut has_vn = false;
945 for tok in &p[1..] {
946 let parts: Vec<&str> = tok.split('/').collect();
947 let vi: usize = parts[0].parse::<usize>().map_err(|e| e.to_string())? - 1;
948 vis.push(vi);
949 if parts.len() >= 2 && !parts[1].is_empty() {
950 has_vt = true;
951 let vt: usize = parts[1].parse::<usize>().map_err(|e| e.to_string())? - 1;
952 vts.push(vt);
953 }
954 if parts.len() >= 3 && !parts[2].is_empty() {
955 has_vn = true;
956 let vn: usize = parts[2].parse::<usize>().map_err(|e| e.to_string())? - 1;
957 vns.push(vn);
958 }
959 }
960 vcmesh.mesh.faces.push(ObjFace {
961 vertex_indices: vis,
962 normal_indices: if has_vn { Some(vns) } else { None },
963 uv_indices: if has_vt { Some(vts) } else { None },
964 smoothing_group,
965 material: current_material.clone(),
966 });
967 }
968 }
969 if let Some(ref name) = current_group_name {
970 let count = vcmesh.mesh.faces.len() - current_group_start;
971 if count > 0 {
972 vcmesh.mesh.groups.push(ObjGroup {
973 name: name.clone(),
974 face_start: current_group_start,
975 face_count: count,
976 });
977 }
978 }
979 Ok(vcmesh)
980 }
981 pub fn to_obj_str(&self) -> String {
983 let mut s = String::from("# OxiPhysics OBJ export (vertex colours)\n");
984 for (i, v) in self.mesh.vertices.iter().enumerate() {
985 if let Some(c) = self.colors.get(i) {
986 s.push_str(&format!(
987 "v {} {} {} {} {} {}\n",
988 v[0], v[1], v[2], c.r, c.g, c.b
989 ));
990 } else {
991 s.push_str(&format!("v {} {} {}\n", v[0], v[1], v[2]));
992 }
993 }
994 for vn in &self.mesh.normals {
995 s.push_str(&format!("vn {} {} {}\n", vn[0], vn[1], vn[2]));
996 }
997 for vt in &self.mesh.uvs {
998 s.push_str(&format!("vt {} {}\n", vt[0], vt[1]));
999 }
1000 for face in &self.mesh.faces {
1001 s.push('f');
1002 for i in 0..face.vertex_indices.len() {
1003 let vi = face.vertex_indices[i] + 1;
1004 let vt_idx = face.uv_indices.as_ref().map(|uvs| uvs[i] + 1);
1005 let vn_idx = face.normal_indices.as_ref().map(|ns| ns[i] + 1);
1006 let tok = match (vt_idx, vn_idx) {
1007 (Some(vt), Some(vn)) => format!(" {}/{}/{}", vi, vt, vn),
1008 (None, Some(vn)) => format!(" {}//{}", vi, vn),
1009 (Some(vt), None) => format!(" {}/{}", vi, vt),
1010 (None, None) => format!(" {}", vi),
1011 };
1012 s.push_str(&tok);
1013 }
1014 s.push('\n');
1015 }
1016 s
1017 }
1018}
1019
1020#[derive(Clone, Copy)]
1029struct Quadric {
1030 m: [f64; 10],
1031}
1032
1033impl Quadric {
1034 const ZERO: Self = Self { m: [0.0; 10] };
1035
1036 fn from_plane(a: f64, b: f64, c: f64, d: f64) -> Self {
1038 Self {
1039 m: [
1040 a * a,
1041 a * b,
1042 a * c,
1043 a * d,
1044 b * b,
1045 b * c,
1046 b * d,
1047 c * c,
1048 c * d,
1049 d * d,
1050 ],
1051 }
1052 }
1053
1054 fn add(&self, other: &Self) -> Self {
1056 let mut m = [0.0f64; 10];
1057 for (i, val) in m.iter_mut().enumerate() {
1058 *val = self.m[i] + other.m[i];
1059 }
1060 Self { m }
1061 }
1062
1063 fn eval(&self, p: [f64; 3]) -> f64 {
1065 let [x, y, z] = p;
1066 let m = &self.m;
1067 m[0] * x * x
1068 + 2.0 * m[1] * x * y
1069 + 2.0 * m[2] * x * z
1070 + 2.0 * m[3] * x
1071 + m[4] * y * y
1072 + 2.0 * m[5] * y * z
1073 + 2.0 * m[6] * y
1074 + m[7] * z * z
1075 + 2.0 * m[8] * z
1076 + m[9]
1077 }
1078
1079 fn optimal_pos(&self, midpoint: [f64; 3]) -> [f64; 3] {
1082 let m = &self.m;
1083 let a00 = m[0];
1084 let a01 = m[1];
1085 let a02 = m[2];
1086 let a11 = m[4];
1087 let a12 = m[5];
1088 let a22 = m[7];
1089 let bx = -m[3];
1090 let by = -m[6];
1091 let bz = -m[8];
1092
1093 let det = a00 * (a11 * a22 - a12 * a12) - a01 * (a01 * a22 - a12 * a02)
1094 + a02 * (a01 * a12 - a11 * a02);
1095
1096 if det.abs() < 1.0e-12 {
1097 return midpoint;
1098 }
1099 let inv = 1.0 / det;
1100 let x = inv
1101 * (bx * (a11 * a22 - a12 * a12) - a01 * (by * a22 - a12 * bz)
1102 + a02 * (by * a12 - a11 * bz));
1103 let y = inv
1104 * (a00 * (by * a22 - a12 * bz) - bx * (a01 * a22 - a12 * a02)
1105 + a02 * (a01 * bz - by * a02));
1106 let z = inv
1107 * (a00 * (a11 * bz - by * a12) - a01 * (a01 * bz - by * a02)
1108 + bx * (a01 * a12 - a11 * a02));
1109 [x, y, z]
1110 }
1111}
1112
1113#[derive(Clone)]
1115struct EdgeEntry {
1116 cost: f64,
1117 v0: usize,
1118 v1: usize,
1119 pos: [f64; 3],
1120 epoch: u64,
1123}
1124
1125impl PartialEq for EdgeEntry {
1126 fn eq(&self, other: &Self) -> bool {
1127 self.cost == other.cost
1128 }
1129}
1130impl Eq for EdgeEntry {}
1131impl PartialOrd for EdgeEntry {
1132 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1133 Some(self.cmp(other))
1134 }
1135}
1136impl Ord for EdgeEntry {
1137 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1139 other
1140 .cost
1141 .partial_cmp(&self.cost)
1142 .unwrap_or(std::cmp::Ordering::Equal)
1143 }
1144}
1145
1146fn decimate_qem_impl(mesh: &ObjMesh, target_faces: usize) -> ObjMesh {
1148 use std::collections::{BinaryHeap, HashSet};
1149
1150 let nv = mesh.vertices.len();
1151 if nv == 0 || mesh.faces.is_empty() {
1152 return mesh.clone();
1153 }
1154
1155 let mut triangles: Vec<[usize; 3]> = Vec::new();
1157 for face in &mesh.faces {
1158 let vi = &face.vertex_indices;
1159 if vi.len() < 3 {
1160 continue;
1161 }
1162 for k in 1..(vi.len() - 1) {
1163 if vi[0] < nv && vi[k] < nv && vi[k + 1] < nv {
1164 triangles.push([vi[0], vi[k], vi[k + 1]]);
1165 }
1166 }
1167 }
1168 if triangles.is_empty() {
1169 return mesh.clone();
1170 }
1171
1172 let mut quadrics = vec![Quadric::ZERO; nv];
1174 for &[i0, i1, i2] in &triangles {
1175 let v0 = mesh.vertices[i0];
1176 let v1 = mesh.vertices[i1];
1177 let v2 = mesh.vertices[i2];
1178 let ex = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
1179 let fy = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
1180 let nx = ex[1] * fy[2] - ex[2] * fy[1];
1181 let ny = ex[2] * fy[0] - ex[0] * fy[2];
1182 let nz = ex[0] * fy[1] - ex[1] * fy[0];
1183 let len = (nx * nx + ny * ny + nz * nz).sqrt();
1184 if len < 1.0e-15 {
1185 continue;
1186 }
1187 let (a, b, c) = (nx / len, ny / len, nz / len);
1188 let d = -(a * v0[0] + b * v0[1] + c * v0[2]);
1189 let q = Quadric::from_plane(a, b, c, d);
1190 quadrics[i0] = quadrics[i0].add(&q);
1191 quadrics[i1] = quadrics[i1].add(&q);
1192 quadrics[i2] = quadrics[i2].add(&q);
1193 }
1194
1195 let mut edge_set: HashSet<(usize, usize)> = HashSet::new();
1197 for &[i0, i1, i2] in &triangles {
1198 for (a, b) in [(i0, i1), (i1, i2), (i0, i2)] {
1199 edge_set.insert((a.min(b), a.max(b)));
1200 }
1201 }
1202
1203 let mut vertices: Vec<Option<[f64; 3]>> = mesh.vertices.iter().map(|&v| Some(v)).collect();
1205 let mut vertex_epochs: Vec<u64> = vec![0u64; nv];
1206 let mut remap: Vec<usize> = (0..nv).collect();
1207 let mut heap: BinaryHeap<EdgeEntry> = BinaryHeap::new();
1208 let mut faces: Vec<Option<[usize; 3]>> = triangles.iter().map(|&t| Some(t)).collect();
1209 let mut active_face_count = faces.len();
1210
1211 fn resolve(remap: &[usize], mut v: usize) -> usize {
1213 while remap[v] != v {
1214 v = remap[v];
1215 }
1216 v
1217 }
1218
1219 let push_edge = |heap: &mut BinaryHeap<EdgeEntry>,
1221 v0: usize,
1222 v1: usize,
1223 qv: &[Quadric],
1224 verts: &[Option<[f64; 3]>],
1225 ve: &[u64]| {
1226 let Some(p0) = verts[v0] else { return };
1227 let Some(p1) = verts[v1] else { return };
1228 let combined = qv[v0].add(&qv[v1]);
1229 let mid = [
1230 (p0[0] + p1[0]) * 0.5,
1231 (p0[1] + p1[1]) * 0.5,
1232 (p0[2] + p1[2]) * 0.5,
1233 ];
1234 let pos = combined.optimal_pos(mid);
1235 let cost = combined.eval(pos);
1236 heap.push(EdgeEntry {
1237 cost,
1238 v0,
1239 v1,
1240 pos,
1241 epoch: ve[v0] ^ (ve[v1] << 32),
1242 });
1243 };
1244
1245 for &(a, b) in &edge_set {
1246 push_edge(&mut heap, a, b, &quadrics, &vertices, &vertex_epochs);
1247 }
1248
1249 while active_face_count > target_faces {
1251 let entry = match heap.pop() {
1252 Some(e) => e,
1253 None => break,
1254 };
1255 let v0 = resolve(&remap, entry.v0);
1256 let v1 = resolve(&remap, entry.v1);
1257 if v0 == v1 {
1258 continue;
1259 }
1260 if entry.epoch != vertex_epochs[v0] ^ (vertex_epochs[v1] << 32) {
1261 continue;
1262 }
1263 if vertices[v0].is_none() || vertices[v1].is_none() {
1264 continue;
1265 }
1266
1267 vertices[v0] = Some(entry.pos);
1269 vertices[v1] = None;
1270 quadrics[v0] = quadrics[v0].add(&quadrics[v1]);
1271 remap[v1] = v0;
1272 vertex_epochs[v0] += 1;
1273 vertex_epochs[v1] += 1;
1274
1275 for face in faces.iter_mut() {
1277 let tri = match face {
1278 Some(t) => t,
1279 None => continue,
1280 };
1281 let mut changed = false;
1282 for idx in tri.iter_mut() {
1283 let r = resolve(&remap, *idx);
1284 if r != *idx {
1285 *idx = r;
1286 changed = true;
1287 }
1288 }
1289 if changed && (tri[0] == tri[1] || tri[1] == tri[2] || tri[0] == tri[2]) {
1290 *face = None;
1291 active_face_count -= 1;
1292 }
1293 }
1294
1295 for &(a, b) in &edge_set {
1297 let ra = resolve(&remap, a);
1298 let rb = resolve(&remap, b);
1299 if ra == rb {
1300 continue;
1301 }
1302 if ra == v0 || rb == v0 {
1303 push_edge(&mut heap, ra, rb, &quadrics, &vertices, &vertex_epochs);
1304 }
1305 }
1306 }
1307
1308 let mut new_vertices: Vec<[f64; 3]> = Vec::new();
1310 let mut old_to_new: Vec<Option<usize>> = vec![None; nv];
1311 for (i, v) in vertices.iter().enumerate() {
1312 if let Some(pos) = v {
1313 old_to_new[i] = Some(new_vertices.len());
1314 new_vertices.push(*pos);
1315 }
1316 }
1317
1318 let mut new_faces: Vec<ObjFace> = Vec::new();
1319 for face in faces.iter().flatten() {
1320 let a = resolve(&remap, face[0]);
1321 let b = resolve(&remap, face[1]);
1322 let c = resolve(&remap, face[2]);
1323 if a == b || b == c || a == c {
1324 continue;
1325 }
1326 let Some(na) = old_to_new[a] else { continue };
1327 let Some(nb) = old_to_new[b] else { continue };
1328 let Some(nc) = old_to_new[c] else { continue };
1329 new_faces.push(ObjFace {
1330 vertex_indices: vec![na, nb, nc],
1331 normal_indices: None,
1332 uv_indices: None,
1333 smoothing_group: 0,
1334 material: None,
1335 });
1336 if new_faces.len() >= target_faces {
1337 break;
1338 }
1339 }
1340
1341 ObjMesh {
1342 vertices: new_vertices,
1343 normals: Vec::new(),
1344 uvs: Vec::new(),
1345 faces: new_faces,
1346 groups: Vec::new(),
1347 }
1348}