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