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 {
364 if mesh.faces.len() <= target_faces {
365 return mesh.clone();
366 }
367 let keep_ratio = target_faces as f64 / mesh.faces.len() as f64;
368 let mut out = ObjMesh {
369 vertices: mesh.vertices.clone(),
370 normals: mesh.normals.clone(),
371 uvs: mesh.uvs.clone(),
372 ..Default::default()
373 };
374 let _total = mesh.faces.len();
375 let step = (1.0 / keep_ratio).round() as usize;
376 let step = step.max(2);
377 for (i, face) in mesh.faces.iter().enumerate() {
378 if i % step != 0 {
379 out.faces.push(face.clone());
380 }
381 if out.faces.len() >= target_faces {
382 break;
383 }
384 }
385 out
386 }
387}
388#[allow(dead_code)]
390#[derive(Debug, Clone)]
391pub struct MeshTransform {
392 pub translation: [f64; 3],
394 pub scale: f64,
396 pub axis: [f64; 3],
398 pub angle: f64,
400}
401#[allow(dead_code)]
402impl MeshTransform {
403 pub fn identity() -> Self {
405 Self {
406 translation: [0.0; 3],
407 scale: 1.0,
408 axis: [0.0, 0.0, 1.0],
409 angle: 0.0,
410 }
411 }
412 pub fn from_translation(tx: f64, ty: f64, tz: f64) -> Self {
414 Self {
415 translation: [tx, ty, tz],
416 scale: 1.0,
417 axis: [0.0, 0.0, 1.0],
418 angle: 0.0,
419 }
420 }
421 pub fn apply(&self, p: [f64; 3]) -> [f64; 3] {
425 let s = [p[0] * self.scale, p[1] * self.scale, p[2] * self.scale];
426 let (ax, ay, az) = (self.axis[0], self.axis[1], self.axis[2]);
427 let (sin_a, cos_a) = (self.angle.sin(), self.angle.cos());
428 let dot = ax * s[0] + ay * s[1] + az * s[2];
429 let cross = [
430 ay * s[2] - az * s[1],
431 az * s[0] - ax * s[2],
432 ax * s[1] - ay * s[0],
433 ];
434 let rx = s[0] * cos_a + cross[0] * sin_a + ax * dot * (1.0 - cos_a);
435 let ry = s[1] * cos_a + cross[1] * sin_a + ay * dot * (1.0 - cos_a);
436 let rz = s[2] * cos_a + cross[2] * sin_a + az * dot * (1.0 - cos_a);
437 [
438 rx + self.translation[0],
439 ry + self.translation[1],
440 rz + self.translation[2],
441 ]
442 }
443}
444#[allow(dead_code)]
446#[derive(Debug, Clone)]
447pub struct ObjMeshStats {
448 pub vertex_count: usize,
450 pub face_count: usize,
452 pub triangle_count: usize,
454 pub material_count: usize,
456 pub group_count: usize,
458 pub faces_with_normals: usize,
460 pub faces_with_uvs: usize,
462 pub surface_area: f64,
464 pub bbox: Option<([f64; 3], [f64; 3])>,
466}
467#[allow(dead_code)]
469#[derive(Debug, Clone, Default)]
470pub struct ObjMesh {
471 pub vertices: Vec<[f64; 3]>,
473 pub normals: Vec<[f64; 3]>,
475 pub uvs: Vec<[f64; 2]>,
477 pub faces: Vec<ObjFace>,
479 pub groups: Vec<ObjGroup>,
481}
482#[allow(dead_code)]
483impl ObjMesh {
484 pub fn to_triangle_soup(&self) -> Vec<[[f64; 3]; 3]> {
489 let mut soup = Vec::new();
490 for face in &self.faces {
491 let verts = &face.vertex_indices;
492 if verts.len() < 3 {
493 continue;
494 }
495 for i in 1..(verts.len() - 1) {
496 let v0 = self.vertices[verts[0]];
497 let v1 = self.vertices[verts[i]];
498 let v2 = self.vertices[verts[i + 1]];
499 soup.push([v0, v1, v2]);
500 }
501 }
502 soup
503 }
504 pub fn faces_in_group(&self, group_name: &str) -> Vec<&ObjFace> {
506 if let Some(group) = self.groups.iter().find(|g| g.name == group_name) {
507 let end = group.face_start + group.face_count;
508 let end = end.min(self.faces.len());
509 self.faces[group.face_start..end].iter().collect()
510 } else {
511 Vec::new()
512 }
513 }
514 pub fn faces_in_smoothing_group(&self, sg: u32) -> Vec<&ObjFace> {
516 self.faces
517 .iter()
518 .filter(|f| f.smoothing_group == sg)
519 .collect()
520 }
521 pub fn faces_with_material(&self, mat_name: &str) -> Vec<&ObjFace> {
523 self.faces
524 .iter()
525 .filter(|f| f.material.as_deref() == Some(mat_name))
526 .collect()
527 }
528 pub fn triangle_count(&self) -> usize {
530 self.faces
531 .iter()
532 .map(|f| {
533 if f.vertex_indices.len() >= 3 {
534 f.vertex_indices.len() - 2
535 } else {
536 0
537 }
538 })
539 .sum()
540 }
541 pub fn face_normal(&self, face_idx: usize) -> Option<[f64; 3]> {
543 let face = self.faces.get(face_idx)?;
544 if face.vertex_indices.len() < 3 {
545 return None;
546 }
547 let v0 = self.vertices[face.vertex_indices[0]];
548 let v1 = self.vertices[face.vertex_indices[1]];
549 let v2 = self.vertices[face.vertex_indices[2]];
550 let e1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
551 let e2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
552 let n = [
553 e1[1] * e2[2] - e1[2] * e2[1],
554 e1[2] * e2[0] - e1[0] * e2[2],
555 e1[0] * e2[1] - e1[1] * e2[0],
556 ];
557 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
558 if len < 1e-30 {
559 return None;
560 }
561 Some([n[0] / len, n[1] / len, n[2] / len])
562 }
563 pub fn bounding_box(&self) -> Option<([f64; 3], [f64; 3])> {
565 if self.vertices.is_empty() {
566 return None;
567 }
568 let mut min = self.vertices[0];
569 let mut max = self.vertices[0];
570 for v in &self.vertices[1..] {
571 for k in 0..3 {
572 if v[k] < min[k] {
573 min[k] = v[k];
574 }
575 if v[k] > max[k] {
576 max[k] = v[k];
577 }
578 }
579 }
580 Some((min, max))
581 }
582}
583#[allow(dead_code)]
585#[derive(Debug, Clone)]
586pub struct MeshInstance {
587 pub name: String,
589 pub transform: MeshTransform,
591}
592#[allow(dead_code)]
594#[derive(Debug, Clone)]
595pub struct ObjGroup {
596 pub name: String,
598 pub face_start: usize,
600 pub face_count: usize,
602}
603#[allow(dead_code)]
608#[derive(Debug, Clone)]
609pub struct ObjFace {
610 pub vertex_indices: Vec<usize>,
612 pub normal_indices: Option<Vec<usize>>,
614 pub uv_indices: Option<Vec<usize>>,
616 pub smoothing_group: u32,
618 pub material: Option<String>,
620}
621#[allow(dead_code)]
623pub struct ObjWriter;
624#[allow(dead_code)]
625impl ObjWriter {
626 pub fn write(mesh: &ObjMesh) -> String {
628 Self::write_with_groups(mesh, false)
629 }
630 pub fn write_with_groups(mesh: &ObjMesh, emit_groups: bool) -> String {
632 let mut s = String::from("# OxiPhysics OBJ export\n");
633 for v in &mesh.vertices {
634 s.push_str(&format!("v {} {} {}\n", v[0], v[1], v[2]));
635 }
636 for vn in &mesh.normals {
637 s.push_str(&format!("vn {} {} {}\n", vn[0], vn[1], vn[2]));
638 }
639 for vt in &mesh.uvs {
640 s.push_str(&format!("vt {} {}\n", vt[0], vt[1]));
641 }
642 let mut current_group: Option<&str> = None;
643 let mut current_material: Option<&str> = None;
644 let mut current_sg: u32 = 0;
645 for (fi, face) in mesh.faces.iter().enumerate() {
646 if emit_groups {
647 for group in &mesh.groups {
648 if fi == group.face_start && current_group != Some(&group.name) {
649 s.push_str(&format!("g {}\n", group.name));
650 current_group = Some(&group.name);
651 }
652 }
653 }
654 if let Some(ref mat) = face.material
655 && current_material != Some(mat.as_str())
656 {
657 s.push_str(&format!("usemtl {}\n", mat));
658 current_material = Some(mat);
659 }
660 if face.smoothing_group != current_sg {
661 current_sg = face.smoothing_group;
662 if current_sg == 0 {
663 s.push_str("s off\n");
664 } else {
665 s.push_str(&format!("s {}\n", current_sg));
666 }
667 }
668 s.push('f');
669 for i in 0..face.vertex_indices.len() {
670 let vi = face.vertex_indices[i] + 1;
671 let vt_idx = face.uv_indices.as_ref().map(|uvs| uvs[i] + 1);
672 let vn_idx = face.normal_indices.as_ref().map(|ns| ns[i] + 1);
673 let token = match (vt_idx, vn_idx) {
674 (Some(vt), Some(vn)) => format!(" {}/{}/{}", vi, vt, vn),
675 (None, Some(vn)) => format!(" {}//{}", vi, vn),
676 (Some(vt), None) => format!(" {}/{}", vi, vt),
677 (None, None) => format!(" {}", vi),
678 };
679 s.push_str(&token);
680 }
681 s.push('\n');
682 }
683 s
684 }
685 pub fn write_to_file(path: &str, mesh: &ObjMesh) -> Result<()> {
687 let file = File::create(Path::new(path))?;
688 let mut w = BufWriter::new(file);
689 write!(w, "{}", Self::write(mesh))?;
690 w.flush()?;
691 Ok(())
692 }
693 pub fn write_legacy(
697 path: &str,
698 vertices: &[Vec3],
699 triangles: &[[usize; 3]],
700 normals: Option<&[Vec3]>,
701 ) -> Result<()> {
702 let file = File::create(Path::new(path))?;
703 let mut w = BufWriter::new(file);
704 writeln!(w, "# OxiPhysics OBJ export")?;
705 for v in vertices {
706 writeln!(w, "v {} {} {}", v.x, v.y, v.z)?;
707 }
708 if let Some(norms) = normals {
709 for n in norms {
710 writeln!(w, "vn {} {} {}", n.x, n.y, n.z)?;
711 }
712 for t in triangles {
713 writeln!(
714 w,
715 "f {}//{} {}//{} {}//{}",
716 t[0] + 1,
717 t[0] + 1,
718 t[1] + 1,
719 t[1] + 1,
720 t[2] + 1,
721 t[2] + 1,
722 )?;
723 }
724 } else {
725 for t in triangles {
726 writeln!(w, "f {} {} {}", t[0] + 1, t[1] + 1, t[2] + 1)?;
727 }
728 }
729 w.flush()?;
730 Ok(())
731 }
732 pub fn write_with_uvs(
734 path: &str,
735 vertices: &[Vec3],
736 uvs: &[[f64; 2]],
737 triangles: &[[usize; 3]],
738 ) -> Result<()> {
739 let file = File::create(Path::new(path))?;
740 let mut w = BufWriter::new(file);
741 writeln!(w, "# OxiPhysics OBJ export")?;
742 for v in vertices {
743 writeln!(w, "v {} {} {}", v.x, v.y, v.z)?;
744 }
745 for uv in uvs {
746 writeln!(w, "vt {} {}", uv[0], uv[1])?;
747 }
748 for t in triangles {
749 writeln!(
750 w,
751 "f {}/{} {}/{} {}/{}",
752 t[0] + 1,
753 t[0] + 1,
754 t[1] + 1,
755 t[1] + 1,
756 t[2] + 1,
757 t[2] + 1,
758 )?;
759 }
760 w.flush()?;
761 Ok(())
762 }
763}
764#[allow(dead_code)]
766#[derive(Debug, Clone)]
767pub struct ObjSceneNode {
768 pub name: String,
770 pub transform: MeshTransform,
772 pub mesh_index: Option<usize>,
774 pub children: Vec<usize>,
776}
777#[allow(dead_code)]
779#[derive(Debug, Clone, Copy, PartialEq)]
780pub struct ObjVertexColor {
781 pub r: f64,
783 pub g: f64,
785 pub b: f64,
787 pub a: f64,
789}
790#[allow(dead_code)]
791impl ObjVertexColor {
792 pub fn rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
794 Self { r, g, b, a }
795 }
796 pub fn rgb(r: f64, g: f64, b: f64) -> Self {
798 Self { r, g, b, a: 1.0 }
799 }
800 pub fn to_array(self) -> [f64; 4] {
802 [self.r, self.g, self.b, self.a]
803 }
804 pub fn lerp(self, other: Self, t: f64) -> Self {
806 Self {
807 r: self.r + (other.r - self.r) * t,
808 g: self.g + (other.g - self.g) * t,
809 b: self.b + (other.b - self.b) * t,
810 a: self.a + (other.a - self.a) * t,
811 }
812 }
813}
814#[allow(dead_code)]
816#[derive(Debug, Clone)]
817pub struct ObjSurface {
818 pub name: String,
820 pub degree_u: usize,
822 pub degree_v: usize,
824 pub control_points: Vec<usize>,
826 pub n_u: usize,
828 pub knots_u: Vec<f64>,
830 pub knots_v: Vec<f64>,
832}
833#[allow(dead_code)]
835pub struct MtlWriter;
836#[allow(dead_code)]
837impl MtlWriter {
838 pub fn write(materials: &[ObjMaterial]) -> String {
840 let mut s = String::from("# OxiPhysics MTL export\n");
841 for mat in materials {
842 s.push_str(&format!("\nnewmtl {}\n", mat.name));
843 s.push_str(&format!("Ka {} {} {}\n", mat.ka[0], mat.ka[1], mat.ka[2]));
844 s.push_str(&format!("Kd {} {} {}\n", mat.kd[0], mat.kd[1], mat.kd[2]));
845 s.push_str(&format!("Ks {} {} {}\n", mat.ks[0], mat.ks[1], mat.ks[2]));
846 s.push_str(&format!("Ns {}\n", mat.ns));
847 s.push_str(&format!("d {}\n", mat.dissolve));
848 if let Some(ref tex) = mat.map_kd {
849 s.push_str(&format!("map_Kd {}\n", tex));
850 }
851 }
852 s
853 }
854}
855#[allow(dead_code)]
860#[derive(Debug, Clone, Default)]
861pub struct ObjVertexColorMesh {
862 pub mesh: ObjMesh,
864 pub colors: Vec<ObjVertexColor>,
866}
867#[allow(dead_code)]
868impl ObjVertexColorMesh {
869 pub fn from_str(data: &str) -> std::result::Result<Self, String> {
871 let mut vcmesh = ObjVertexColorMesh::default();
872 let mut smoothing_group: u32 = 0;
873 let mut current_material: Option<String> = None;
874 let mut current_group_name: Option<String> = None;
875 let mut current_group_start: usize = 0;
876 for raw in data.lines() {
877 let line = raw.trim();
878 if line.is_empty() || line.starts_with('#') {
879 continue;
880 }
881 if line.starts_with("g ") || line.starts_with("o ") {
882 if let Some(ref name) = current_group_name {
883 let count = vcmesh.mesh.faces.len() - current_group_start;
884 if count > 0 {
885 vcmesh.mesh.groups.push(ObjGroup {
886 name: name.clone(),
887 face_start: current_group_start,
888 face_count: count,
889 });
890 }
891 }
892 current_group_name = Some(line[2..].trim().to_string());
893 current_group_start = vcmesh.mesh.faces.len();
894 } else if line.starts_with("usemtl ") {
895 current_material = Some(line[7..].trim().to_string());
896 } else if line.starts_with("s ") {
897 let val = line[2..].trim();
898 smoothing_group = if val == "off" || val == "0" {
899 0
900 } else {
901 val.parse::<u32>().unwrap_or(0)
902 };
903 } else if line.starts_with("v ") {
904 let p: Vec<&str> = line.split_whitespace().collect();
905 if p.len() < 4 {
906 return Err(format!("Vertex line too short: {}", line));
907 }
908 let x: f64 = p[1]
909 .parse()
910 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
911 let y: f64 = p[2]
912 .parse()
913 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
914 let z: f64 = p[3]
915 .parse()
916 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
917 vcmesh.mesh.vertices.push([x, y, z]);
918 let r: f64 = p.get(4).and_then(|v| v.parse().ok()).unwrap_or(1.0);
919 let g: f64 = p.get(5).and_then(|v| v.parse().ok()).unwrap_or(1.0);
920 let b: f64 = p.get(6).and_then(|v| v.parse().ok()).unwrap_or(1.0);
921 let a: f64 = p.get(7).and_then(|v| v.parse().ok()).unwrap_or(1.0);
922 vcmesh.colors.push(ObjVertexColor { r, g, b, a });
923 } else if line.starts_with("vn ") {
924 let p: Vec<&str> = line.split_whitespace().collect();
925 if p.len() >= 4 {
926 let x: f64 = p[1]
927 .parse()
928 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
929 let y: f64 = p[2]
930 .parse()
931 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
932 let z: f64 = p[3]
933 .parse()
934 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
935 vcmesh.mesh.normals.push([x, y, z]);
936 }
937 } else if line.starts_with("vt ") {
938 let p: Vec<&str> = line.split_whitespace().collect();
939 if p.len() >= 3 {
940 let u: f64 = p[1]
941 .parse()
942 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
943 let v: f64 = p[2]
944 .parse()
945 .map_err(|e: std::num::ParseFloatError| e.to_string())?;
946 vcmesh.mesh.uvs.push([u, v]);
947 }
948 } else if line.starts_with("f ") {
949 let p: Vec<&str> = line.split_whitespace().collect();
950 let mut vis = Vec::new();
951 let mut vts = Vec::new();
952 let mut vns = Vec::new();
953 let mut has_vt = false;
954 let mut has_vn = false;
955 for tok in &p[1..] {
956 let parts: Vec<&str> = tok.split('/').collect();
957 let vi: usize = parts[0].parse::<usize>().map_err(|e| e.to_string())? - 1;
958 vis.push(vi);
959 if parts.len() >= 2 && !parts[1].is_empty() {
960 has_vt = true;
961 let vt: usize = parts[1].parse::<usize>().map_err(|e| e.to_string())? - 1;
962 vts.push(vt);
963 }
964 if parts.len() >= 3 && !parts[2].is_empty() {
965 has_vn = true;
966 let vn: usize = parts[2].parse::<usize>().map_err(|e| e.to_string())? - 1;
967 vns.push(vn);
968 }
969 }
970 vcmesh.mesh.faces.push(ObjFace {
971 vertex_indices: vis,
972 normal_indices: if has_vn { Some(vns) } else { None },
973 uv_indices: if has_vt { Some(vts) } else { None },
974 smoothing_group,
975 material: current_material.clone(),
976 });
977 }
978 }
979 if let Some(ref name) = current_group_name {
980 let count = vcmesh.mesh.faces.len() - current_group_start;
981 if count > 0 {
982 vcmesh.mesh.groups.push(ObjGroup {
983 name: name.clone(),
984 face_start: current_group_start,
985 face_count: count,
986 });
987 }
988 }
989 Ok(vcmesh)
990 }
991 pub fn to_obj_str(&self) -> String {
993 let mut s = String::from("# OxiPhysics OBJ export (vertex colours)\n");
994 for (i, v) in self.mesh.vertices.iter().enumerate() {
995 if let Some(c) = self.colors.get(i) {
996 s.push_str(&format!(
997 "v {} {} {} {} {} {}\n",
998 v[0], v[1], v[2], c.r, c.g, c.b
999 ));
1000 } else {
1001 s.push_str(&format!("v {} {} {}\n", v[0], v[1], v[2]));
1002 }
1003 }
1004 for vn in &self.mesh.normals {
1005 s.push_str(&format!("vn {} {} {}\n", vn[0], vn[1], vn[2]));
1006 }
1007 for vt in &self.mesh.uvs {
1008 s.push_str(&format!("vt {} {}\n", vt[0], vt[1]));
1009 }
1010 for face in &self.mesh.faces {
1011 s.push('f');
1012 for i in 0..face.vertex_indices.len() {
1013 let vi = face.vertex_indices[i] + 1;
1014 let vt_idx = face.uv_indices.as_ref().map(|uvs| uvs[i] + 1);
1015 let vn_idx = face.normal_indices.as_ref().map(|ns| ns[i] + 1);
1016 let tok = match (vt_idx, vn_idx) {
1017 (Some(vt), Some(vn)) => format!(" {}/{}/{}", vi, vt, vn),
1018 (None, Some(vn)) => format!(" {}//{}", vi, vn),
1019 (Some(vt), None) => format!(" {}/{}", vi, vt),
1020 (None, None) => format!(" {}", vi),
1021 };
1022 s.push_str(&tok);
1023 }
1024 s.push('\n');
1025 }
1026 s
1027 }
1028}