#![allow(clippy::manual_strip, clippy::should_implement_trait)]
#[allow(unused_imports)]
use super::functions::*;
#[allow(unused_imports)]
use super::functions_2::*;
use crate::{Error, Result};
use oxiphysics_core::math::Vec3;
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::Path;
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct ObjScene {
pub nodes: Vec<ObjSceneNode>,
pub meshes: Vec<ObjMesh>,
pub materials: Vec<ObjMaterial>,
}
#[allow(dead_code)]
impl ObjScene {
pub fn new() -> Self {
Self::default()
}
pub fn add_mesh(&mut self, mesh: ObjMesh) -> usize {
let idx = self.meshes.len();
self.meshes.push(mesh);
idx
}
pub fn add_material(&mut self, mat: ObjMaterial) -> usize {
let idx = self.materials.len();
self.materials.push(mat);
idx
}
pub fn add_node(
&mut self,
name: &str,
mesh_index: Option<usize>,
transform: MeshTransform,
) -> usize {
let idx = self.nodes.len();
self.nodes.push(ObjSceneNode {
name: name.to_string(),
transform,
mesh_index,
children: Vec::new(),
});
idx
}
pub fn add_child(&mut self, parent_idx: usize, child_idx: usize) {
if parent_idx < self.nodes.len() {
self.nodes[parent_idx].children.push(child_idx);
}
}
pub fn flatten(&self) -> ObjMesh {
let mut result = ObjMesh::default();
for node in &self.nodes {
if let Some(mi) = node.mesh_index
&& let Some(mesh) = self.meshes.get(mi)
{
let inst = MeshInstance {
name: node.name.clone(),
transform: node.transform.clone(),
};
let xformed = instantiate_mesh(mesh, &inst);
result = merge_obj_meshes(&result, &xformed);
}
}
result
}
pub fn total_vertices(&self) -> usize {
self.meshes.iter().map(|m| m.vertices.len()).sum()
}
pub fn total_faces(&self) -> usize {
self.meshes.iter().map(|m| m.faces.len()).sum()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ObjCurve {
pub name: String,
pub degree: usize,
pub control_points: Vec<usize>,
pub knots: Vec<f64>,
}
#[allow(dead_code)]
pub struct ObjReader;
#[allow(dead_code)]
impl ObjReader {
pub fn from_str(data: &str) -> Result<ObjMesh> {
let mut mesh = ObjMesh::default();
let mut current_group_name: Option<String> = None;
let mut current_group_start: usize = 0;
let mut current_smoothing_group: u32 = 0;
let mut current_material: Option<String> = None;
for raw in data.lines() {
let line = raw.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if line.starts_with("g ") || line.starts_with("o ") {
if let Some(ref name) = current_group_name {
let count = mesh.faces.len() - current_group_start;
if count > 0 {
mesh.groups.push(ObjGroup {
name: name.clone(),
face_start: current_group_start,
face_count: count,
});
}
}
let name = line[2..].trim().to_string();
current_group_name = Some(name);
current_group_start = mesh.faces.len();
} else if line.starts_with("s ") {
let val = line[2..].trim();
current_smoothing_group = if val == "off" || val == "0" {
0
} else {
val.parse::<u32>().unwrap_or(0)
};
} else if line.starts_with("usemtl ") {
current_material = Some(line[7..].trim().to_string());
} else {
Self::parse_line_extended(
line,
&mut mesh,
current_smoothing_group,
¤t_material,
)?;
}
}
if let Some(ref name) = current_group_name {
let count = mesh.faces.len() - current_group_start;
if count > 0 {
mesh.groups.push(ObjGroup {
name: name.clone(),
face_start: current_group_start,
face_count: count,
});
}
}
Ok(mesh)
}
fn parse_line_extended(
line: &str,
mesh: &mut ObjMesh,
smoothing_group: u32,
material: &Option<String>,
) -> Result<()> {
if line.starts_with("vn ") {
let p: Vec<&str> = line.split_whitespace().collect();
if p.len() >= 4 {
let x = p[1]
.parse::<f64>()
.map_err(|e| Error::Parse(e.to_string()))?;
let y = p[2]
.parse::<f64>()
.map_err(|e| Error::Parse(e.to_string()))?;
let z = p[3]
.parse::<f64>()
.map_err(|e| Error::Parse(e.to_string()))?;
mesh.normals.push([x, y, z]);
}
} else if line.starts_with("vt ") {
let p: Vec<&str> = line.split_whitespace().collect();
if p.len() >= 3 {
let u = p[1]
.parse::<f64>()
.map_err(|e| Error::Parse(e.to_string()))?;
let v = p[2]
.parse::<f64>()
.map_err(|e| Error::Parse(e.to_string()))?;
mesh.uvs.push([u, v]);
}
} else if line.starts_with("v ") {
let p: Vec<&str> = line.split_whitespace().collect();
if p.len() >= 4 {
let x = p[1]
.parse::<f64>()
.map_err(|e| Error::Parse(e.to_string()))?;
let y = p[2]
.parse::<f64>()
.map_err(|e| Error::Parse(e.to_string()))?;
let z = p[3]
.parse::<f64>()
.map_err(|e| Error::Parse(e.to_string()))?;
mesh.vertices.push([x, y, z]);
}
} else if line.starts_with("f ") {
let p: Vec<&str> = line.split_whitespace().collect();
let mut vis = Vec::new();
let mut vts = Vec::new();
let mut vns = Vec::new();
let mut has_vt = false;
let mut has_vn = false;
for tok in &p[1..] {
let parts: Vec<&str> = tok.split('/').collect();
let vi = parts[0]
.parse::<usize>()
.map_err(|e| Error::Parse(e.to_string()))?
- 1;
vis.push(vi);
if parts.len() >= 2 && !parts[1].is_empty() {
has_vt = true;
let vt = parts[1]
.parse::<usize>()
.map_err(|e| Error::Parse(e.to_string()))?
- 1;
vts.push(vt);
}
if parts.len() >= 3 && !parts[2].is_empty() {
has_vn = true;
let vn = parts[2]
.parse::<usize>()
.map_err(|e| Error::Parse(e.to_string()))?
- 1;
vns.push(vn);
}
}
mesh.faces.push(ObjFace {
vertex_indices: vis,
normal_indices: if has_vn { Some(vns) } else { None },
uv_indices: if has_vt { Some(vts) } else { None },
smoothing_group,
material: material.clone(),
});
}
Ok(())
}
pub fn from_file(path: &str) -> Result<ObjMesh> {
let file = File::open(Path::new(path))?;
let reader = BufReader::new(file);
let mut data = String::new();
for raw in reader.lines() {
let raw = raw?;
data.push_str(&raw);
data.push('\n');
}
Self::from_str(&data)
}
pub fn read(path: &str) -> Result<(Vec<Vec3>, Vec<[usize; 3]>)> {
let mesh = Self::from_file(path)?;
let vertices: Vec<Vec3> = mesh
.vertices
.iter()
.map(|v| Vec3::new(v[0], v[1], v[2]))
.collect();
let faces: Vec<[usize; 3]> = mesh
.faces
.iter()
.filter(|f| f.vertex_indices.len() >= 3)
.map(|f| {
[
f.vertex_indices[0],
f.vertex_indices[1],
f.vertex_indices[2],
]
})
.collect();
Ok((vertices, faces))
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ObjMaterial {
pub name: String,
pub kd: [f64; 3],
pub ks: [f64; 3],
pub ns: f64,
pub ka: [f64; 3],
pub dissolve: f64,
pub map_kd: Option<String>,
}
#[allow(dead_code)]
impl ObjMaterial {
pub fn basic(name: &str, kd: [f64; 3]) -> Self {
Self {
name: name.to_string(),
kd,
ks: [0.0; 3],
ns: 1.0,
ka: [0.0; 3],
dissolve: 1.0,
map_kd: None,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct ObjLod {
pub levels: Vec<ObjMesh>,
pub thresholds: Vec<f64>,
}
#[allow(dead_code)]
impl ObjLod {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, mesh: ObjMesh, threshold: f64) {
self.levels.push(mesh);
self.thresholds.push(threshold);
}
pub fn select(&self, distance: f64) -> Option<&ObjMesh> {
for (i, &t) in self.thresholds.iter().enumerate() {
if distance <= t {
return self.levels.get(i);
}
}
self.levels.last()
}
pub fn num_levels(&self) -> usize {
self.levels.len()
}
pub fn decimate(mesh: &ObjMesh, target_faces: usize) -> ObjMesh {
if mesh.faces.len() <= target_faces {
return mesh.clone();
}
let keep_ratio = target_faces as f64 / mesh.faces.len() as f64;
let mut out = ObjMesh {
vertices: mesh.vertices.clone(),
normals: mesh.normals.clone(),
uvs: mesh.uvs.clone(),
..Default::default()
};
let _total = mesh.faces.len();
let step = (1.0 / keep_ratio).round() as usize;
let step = step.max(2);
for (i, face) in mesh.faces.iter().enumerate() {
if i % step != 0 {
out.faces.push(face.clone());
}
if out.faces.len() >= target_faces {
break;
}
}
out
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MeshTransform {
pub translation: [f64; 3],
pub scale: f64,
pub axis: [f64; 3],
pub angle: f64,
}
#[allow(dead_code)]
impl MeshTransform {
pub fn identity() -> Self {
Self {
translation: [0.0; 3],
scale: 1.0,
axis: [0.0, 0.0, 1.0],
angle: 0.0,
}
}
pub fn from_translation(tx: f64, ty: f64, tz: f64) -> Self {
Self {
translation: [tx, ty, tz],
scale: 1.0,
axis: [0.0, 0.0, 1.0],
angle: 0.0,
}
}
pub fn apply(&self, p: [f64; 3]) -> [f64; 3] {
let s = [p[0] * self.scale, p[1] * self.scale, p[2] * self.scale];
let (ax, ay, az) = (self.axis[0], self.axis[1], self.axis[2]);
let (sin_a, cos_a) = (self.angle.sin(), self.angle.cos());
let dot = ax * s[0] + ay * s[1] + az * s[2];
let cross = [
ay * s[2] - az * s[1],
az * s[0] - ax * s[2],
ax * s[1] - ay * s[0],
];
let rx = s[0] * cos_a + cross[0] * sin_a + ax * dot * (1.0 - cos_a);
let ry = s[1] * cos_a + cross[1] * sin_a + ay * dot * (1.0 - cos_a);
let rz = s[2] * cos_a + cross[2] * sin_a + az * dot * (1.0 - cos_a);
[
rx + self.translation[0],
ry + self.translation[1],
rz + self.translation[2],
]
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ObjMeshStats {
pub vertex_count: usize,
pub face_count: usize,
pub triangle_count: usize,
pub material_count: usize,
pub group_count: usize,
pub faces_with_normals: usize,
pub faces_with_uvs: usize,
pub surface_area: f64,
pub bbox: Option<([f64; 3], [f64; 3])>,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct ObjMesh {
pub vertices: Vec<[f64; 3]>,
pub normals: Vec<[f64; 3]>,
pub uvs: Vec<[f64; 2]>,
pub faces: Vec<ObjFace>,
pub groups: Vec<ObjGroup>,
}
#[allow(dead_code)]
impl ObjMesh {
pub fn to_triangle_soup(&self) -> Vec<[[f64; 3]; 3]> {
let mut soup = Vec::new();
for face in &self.faces {
let verts = &face.vertex_indices;
if verts.len() < 3 {
continue;
}
for i in 1..(verts.len() - 1) {
let v0 = self.vertices[verts[0]];
let v1 = self.vertices[verts[i]];
let v2 = self.vertices[verts[i + 1]];
soup.push([v0, v1, v2]);
}
}
soup
}
pub fn faces_in_group(&self, group_name: &str) -> Vec<&ObjFace> {
if let Some(group) = self.groups.iter().find(|g| g.name == group_name) {
let end = group.face_start + group.face_count;
let end = end.min(self.faces.len());
self.faces[group.face_start..end].iter().collect()
} else {
Vec::new()
}
}
pub fn faces_in_smoothing_group(&self, sg: u32) -> Vec<&ObjFace> {
self.faces
.iter()
.filter(|f| f.smoothing_group == sg)
.collect()
}
pub fn faces_with_material(&self, mat_name: &str) -> Vec<&ObjFace> {
self.faces
.iter()
.filter(|f| f.material.as_deref() == Some(mat_name))
.collect()
}
pub fn triangle_count(&self) -> usize {
self.faces
.iter()
.map(|f| {
if f.vertex_indices.len() >= 3 {
f.vertex_indices.len() - 2
} else {
0
}
})
.sum()
}
pub fn face_normal(&self, face_idx: usize) -> Option<[f64; 3]> {
let face = self.faces.get(face_idx)?;
if face.vertex_indices.len() < 3 {
return None;
}
let v0 = self.vertices[face.vertex_indices[0]];
let v1 = self.vertices[face.vertex_indices[1]];
let v2 = self.vertices[face.vertex_indices[2]];
let e1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
let e2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
let n = [
e1[1] * e2[2] - e1[2] * e2[1],
e1[2] * e2[0] - e1[0] * e2[2],
e1[0] * e2[1] - e1[1] * e2[0],
];
let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
if len < 1e-30 {
return None;
}
Some([n[0] / len, n[1] / len, n[2] / len])
}
pub fn bounding_box(&self) -> Option<([f64; 3], [f64; 3])> {
if self.vertices.is_empty() {
return None;
}
let mut min = self.vertices[0];
let mut max = self.vertices[0];
for v in &self.vertices[1..] {
for k in 0..3 {
if v[k] < min[k] {
min[k] = v[k];
}
if v[k] > max[k] {
max[k] = v[k];
}
}
}
Some((min, max))
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MeshInstance {
pub name: String,
pub transform: MeshTransform,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ObjGroup {
pub name: String,
pub face_start: usize,
pub face_count: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ObjFace {
pub vertex_indices: Vec<usize>,
pub normal_indices: Option<Vec<usize>>,
pub uv_indices: Option<Vec<usize>>,
pub smoothing_group: u32,
pub material: Option<String>,
}
#[allow(dead_code)]
pub struct ObjWriter;
#[allow(dead_code)]
impl ObjWriter {
pub fn write(mesh: &ObjMesh) -> String {
Self::write_with_groups(mesh, false)
}
pub fn write_with_groups(mesh: &ObjMesh, emit_groups: bool) -> String {
let mut s = String::from("# OxiPhysics OBJ export\n");
for v in &mesh.vertices {
s.push_str(&format!("v {} {} {}\n", v[0], v[1], v[2]));
}
for vn in &mesh.normals {
s.push_str(&format!("vn {} {} {}\n", vn[0], vn[1], vn[2]));
}
for vt in &mesh.uvs {
s.push_str(&format!("vt {} {}\n", vt[0], vt[1]));
}
let mut current_group: Option<&str> = None;
let mut current_material: Option<&str> = None;
let mut current_sg: u32 = 0;
for (fi, face) in mesh.faces.iter().enumerate() {
if emit_groups {
for group in &mesh.groups {
if fi == group.face_start && current_group != Some(&group.name) {
s.push_str(&format!("g {}\n", group.name));
current_group = Some(&group.name);
}
}
}
if let Some(ref mat) = face.material
&& current_material != Some(mat.as_str())
{
s.push_str(&format!("usemtl {}\n", mat));
current_material = Some(mat);
}
if face.smoothing_group != current_sg {
current_sg = face.smoothing_group;
if current_sg == 0 {
s.push_str("s off\n");
} else {
s.push_str(&format!("s {}\n", current_sg));
}
}
s.push('f');
for i in 0..face.vertex_indices.len() {
let vi = face.vertex_indices[i] + 1;
let vt_idx = face.uv_indices.as_ref().map(|uvs| uvs[i] + 1);
let vn_idx = face.normal_indices.as_ref().map(|ns| ns[i] + 1);
let token = match (vt_idx, vn_idx) {
(Some(vt), Some(vn)) => format!(" {}/{}/{}", vi, vt, vn),
(None, Some(vn)) => format!(" {}//{}", vi, vn),
(Some(vt), None) => format!(" {}/{}", vi, vt),
(None, None) => format!(" {}", vi),
};
s.push_str(&token);
}
s.push('\n');
}
s
}
pub fn write_to_file(path: &str, mesh: &ObjMesh) -> Result<()> {
let file = File::create(Path::new(path))?;
let mut w = BufWriter::new(file);
write!(w, "{}", Self::write(mesh))?;
w.flush()?;
Ok(())
}
pub fn write_legacy(
path: &str,
vertices: &[Vec3],
triangles: &[[usize; 3]],
normals: Option<&[Vec3]>,
) -> Result<()> {
let file = File::create(Path::new(path))?;
let mut w = BufWriter::new(file);
writeln!(w, "# OxiPhysics OBJ export")?;
for v in vertices {
writeln!(w, "v {} {} {}", v.x, v.y, v.z)?;
}
if let Some(norms) = normals {
for n in norms {
writeln!(w, "vn {} {} {}", n.x, n.y, n.z)?;
}
for t in triangles {
writeln!(
w,
"f {}//{} {}//{} {}//{}",
t[0] + 1,
t[0] + 1,
t[1] + 1,
t[1] + 1,
t[2] + 1,
t[2] + 1,
)?;
}
} else {
for t in triangles {
writeln!(w, "f {} {} {}", t[0] + 1, t[1] + 1, t[2] + 1)?;
}
}
w.flush()?;
Ok(())
}
pub fn write_with_uvs(
path: &str,
vertices: &[Vec3],
uvs: &[[f64; 2]],
triangles: &[[usize; 3]],
) -> Result<()> {
let file = File::create(Path::new(path))?;
let mut w = BufWriter::new(file);
writeln!(w, "# OxiPhysics OBJ export")?;
for v in vertices {
writeln!(w, "v {} {} {}", v.x, v.y, v.z)?;
}
for uv in uvs {
writeln!(w, "vt {} {}", uv[0], uv[1])?;
}
for t in triangles {
writeln!(
w,
"f {}/{} {}/{} {}/{}",
t[0] + 1,
t[0] + 1,
t[1] + 1,
t[1] + 1,
t[2] + 1,
t[2] + 1,
)?;
}
w.flush()?;
Ok(())
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ObjSceneNode {
pub name: String,
pub transform: MeshTransform,
pub mesh_index: Option<usize>,
pub children: Vec<usize>,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ObjVertexColor {
pub r: f64,
pub g: f64,
pub b: f64,
pub a: f64,
}
#[allow(dead_code)]
impl ObjVertexColor {
pub fn rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
Self { r, g, b, a }
}
pub fn rgb(r: f64, g: f64, b: f64) -> Self {
Self { r, g, b, a: 1.0 }
}
pub fn to_array(self) -> [f64; 4] {
[self.r, self.g, self.b, self.a]
}
pub fn lerp(self, other: Self, t: f64) -> Self {
Self {
r: self.r + (other.r - self.r) * t,
g: self.g + (other.g - self.g) * t,
b: self.b + (other.b - self.b) * t,
a: self.a + (other.a - self.a) * t,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ObjSurface {
pub name: String,
pub degree_u: usize,
pub degree_v: usize,
pub control_points: Vec<usize>,
pub n_u: usize,
pub knots_u: Vec<f64>,
pub knots_v: Vec<f64>,
}
#[allow(dead_code)]
pub struct MtlWriter;
#[allow(dead_code)]
impl MtlWriter {
pub fn write(materials: &[ObjMaterial]) -> String {
let mut s = String::from("# OxiPhysics MTL export\n");
for mat in materials {
s.push_str(&format!("\nnewmtl {}\n", mat.name));
s.push_str(&format!("Ka {} {} {}\n", mat.ka[0], mat.ka[1], mat.ka[2]));
s.push_str(&format!("Kd {} {} {}\n", mat.kd[0], mat.kd[1], mat.kd[2]));
s.push_str(&format!("Ks {} {} {}\n", mat.ks[0], mat.ks[1], mat.ks[2]));
s.push_str(&format!("Ns {}\n", mat.ns));
s.push_str(&format!("d {}\n", mat.dissolve));
if let Some(ref tex) = mat.map_kd {
s.push_str(&format!("map_Kd {}\n", tex));
}
}
s
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct ObjVertexColorMesh {
pub mesh: ObjMesh,
pub colors: Vec<ObjVertexColor>,
}
#[allow(dead_code)]
impl ObjVertexColorMesh {
pub fn from_str(data: &str) -> std::result::Result<Self, String> {
let mut vcmesh = ObjVertexColorMesh::default();
let mut smoothing_group: u32 = 0;
let mut current_material: Option<String> = None;
let mut current_group_name: Option<String> = None;
let mut current_group_start: usize = 0;
for raw in data.lines() {
let line = raw.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if line.starts_with("g ") || line.starts_with("o ") {
if let Some(ref name) = current_group_name {
let count = vcmesh.mesh.faces.len() - current_group_start;
if count > 0 {
vcmesh.mesh.groups.push(ObjGroup {
name: name.clone(),
face_start: current_group_start,
face_count: count,
});
}
}
current_group_name = Some(line[2..].trim().to_string());
current_group_start = vcmesh.mesh.faces.len();
} else if line.starts_with("usemtl ") {
current_material = Some(line[7..].trim().to_string());
} else if line.starts_with("s ") {
let val = line[2..].trim();
smoothing_group = if val == "off" || val == "0" {
0
} else {
val.parse::<u32>().unwrap_or(0)
};
} else if line.starts_with("v ") {
let p: Vec<&str> = line.split_whitespace().collect();
if p.len() < 4 {
return Err(format!("Vertex line too short: {}", line));
}
let x: f64 = p[1]
.parse()
.map_err(|e: std::num::ParseFloatError| e.to_string())?;
let y: f64 = p[2]
.parse()
.map_err(|e: std::num::ParseFloatError| e.to_string())?;
let z: f64 = p[3]
.parse()
.map_err(|e: std::num::ParseFloatError| e.to_string())?;
vcmesh.mesh.vertices.push([x, y, z]);
let r: f64 = p.get(4).and_then(|v| v.parse().ok()).unwrap_or(1.0);
let g: f64 = p.get(5).and_then(|v| v.parse().ok()).unwrap_or(1.0);
let b: f64 = p.get(6).and_then(|v| v.parse().ok()).unwrap_or(1.0);
let a: f64 = p.get(7).and_then(|v| v.parse().ok()).unwrap_or(1.0);
vcmesh.colors.push(ObjVertexColor { r, g, b, a });
} else if line.starts_with("vn ") {
let p: Vec<&str> = line.split_whitespace().collect();
if p.len() >= 4 {
let x: f64 = p[1]
.parse()
.map_err(|e: std::num::ParseFloatError| e.to_string())?;
let y: f64 = p[2]
.parse()
.map_err(|e: std::num::ParseFloatError| e.to_string())?;
let z: f64 = p[3]
.parse()
.map_err(|e: std::num::ParseFloatError| e.to_string())?;
vcmesh.mesh.normals.push([x, y, z]);
}
} else if line.starts_with("vt ") {
let p: Vec<&str> = line.split_whitespace().collect();
if p.len() >= 3 {
let u: f64 = p[1]
.parse()
.map_err(|e: std::num::ParseFloatError| e.to_string())?;
let v: f64 = p[2]
.parse()
.map_err(|e: std::num::ParseFloatError| e.to_string())?;
vcmesh.mesh.uvs.push([u, v]);
}
} else if line.starts_with("f ") {
let p: Vec<&str> = line.split_whitespace().collect();
let mut vis = Vec::new();
let mut vts = Vec::new();
let mut vns = Vec::new();
let mut has_vt = false;
let mut has_vn = false;
for tok in &p[1..] {
let parts: Vec<&str> = tok.split('/').collect();
let vi: usize = parts[0].parse::<usize>().map_err(|e| e.to_string())? - 1;
vis.push(vi);
if parts.len() >= 2 && !parts[1].is_empty() {
has_vt = true;
let vt: usize = parts[1].parse::<usize>().map_err(|e| e.to_string())? - 1;
vts.push(vt);
}
if parts.len() >= 3 && !parts[2].is_empty() {
has_vn = true;
let vn: usize = parts[2].parse::<usize>().map_err(|e| e.to_string())? - 1;
vns.push(vn);
}
}
vcmesh.mesh.faces.push(ObjFace {
vertex_indices: vis,
normal_indices: if has_vn { Some(vns) } else { None },
uv_indices: if has_vt { Some(vts) } else { None },
smoothing_group,
material: current_material.clone(),
});
}
}
if let Some(ref name) = current_group_name {
let count = vcmesh.mesh.faces.len() - current_group_start;
if count > 0 {
vcmesh.mesh.groups.push(ObjGroup {
name: name.clone(),
face_start: current_group_start,
face_count: count,
});
}
}
Ok(vcmesh)
}
pub fn to_obj_str(&self) -> String {
let mut s = String::from("# OxiPhysics OBJ export (vertex colours)\n");
for (i, v) in self.mesh.vertices.iter().enumerate() {
if let Some(c) = self.colors.get(i) {
s.push_str(&format!(
"v {} {} {} {} {} {}\n",
v[0], v[1], v[2], c.r, c.g, c.b
));
} else {
s.push_str(&format!("v {} {} {}\n", v[0], v[1], v[2]));
}
}
for vn in &self.mesh.normals {
s.push_str(&format!("vn {} {} {}\n", vn[0], vn[1], vn[2]));
}
for vt in &self.mesh.uvs {
s.push_str(&format!("vt {} {}\n", vt[0], vt[1]));
}
for face in &self.mesh.faces {
s.push('f');
for i in 0..face.vertex_indices.len() {
let vi = face.vertex_indices[i] + 1;
let vt_idx = face.uv_indices.as_ref().map(|uvs| uvs[i] + 1);
let vn_idx = face.normal_indices.as_ref().map(|ns| ns[i] + 1);
let tok = match (vt_idx, vn_idx) {
(Some(vt), Some(vn)) => format!(" {}/{}/{}", vi, vt, vn),
(None, Some(vn)) => format!(" {}//{}", vi, vn),
(Some(vt), None) => format!(" {}/{}", vi, vt),
(None, None) => format!(" {}", vi),
};
s.push_str(&tok);
}
s.push('\n');
}
s
}
}