use anyhow::Context;
use num_traits::AsPrimitive;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::ops::AddAssign;
pub struct WavefrontObj<Index, Real> {
pub vtx2xyz: Vec<[Real; 3]>,
pub vtx2uv: Vec<[Real; 2]>,
pub vtx2nrm: Vec<[Real; 3]>,
pub elem2idx: Vec<Index>,
pub idx2vtx_xyz: Vec<Index>,
pub idx2vtx_uv: Vec<Index>,
pub idx2vtx_nrm: Vec<Index>,
pub elem2group: Vec<Index>,
pub group2name: Vec<String>,
pub elem2mtl: Vec<Index>,
pub mtl_file_name: String,
pub mtl2name: Vec<String>,
}
impl<Index, Real> WavefrontObj<Index, Real>
where
Real: std::str::FromStr + std::fmt::Display + Copy + num_traits::Zero,
Index: num_traits::PrimInt + 'static + AddAssign + AsPrimitive<usize> + Copy,
usize: AsPrimitive<Index>,
i32: AsPrimitive<Index>,
{
pub fn new() -> Self {
WavefrontObj::<Index, Real> {
vtx2xyz: Vec::new(),
vtx2uv: Vec::new(),
vtx2nrm: Vec::new(),
elem2idx: Vec::new(),
idx2vtx_uv: Vec::new(),
idx2vtx_nrm: Vec::new(),
idx2vtx_xyz: Vec::new(),
elem2group: Vec::new(),
group2name: Vec::new(),
mtl_file_name: "".to_string(),
elem2mtl: Vec::new(),
mtl2name: Vec::new(),
}
}
pub fn load<P: AsRef<std::path::Path>>(&mut self, filename: P) -> anyhow::Result<()> {
let mut elem2vtx_xyz0: Vec<i32> = vec![];
let mut elem2vtx_uv0: Vec<i32> = vec![];
let mut elem2vtx_nrm0: Vec<i32> = vec![];
self.elem2group.clear();
self.elem2mtl.clear();
self.elem2idx = vec![Index::zero()];
let mut name2group = std::collections::BTreeMap::<String, usize>::new();
let mut name2mtl = std::collections::BTreeMap::<String, usize>::new();
name2group.insert("_default".to_string(), 0);
name2mtl.insert("_default".to_string(), 0);
let mut i_group = 0_usize;
let mut i_mtl = 0_usize;
let f = File::open(filename).context("file not found")?;
let reader = BufReader::new(f);
for line in reader.lines() {
let line = line.unwrap();
if line.is_empty() {
continue;
}
let char0 = line.chars().next();
if char0.is_none() {
continue;
}
let char0 = char0.unwrap();
let char1 = line.chars().nth(1);
if char1.is_none() {
continue;
}
let char1 = char1.unwrap();
if char0 == '#' {
continue;
}
if char0 == 'v' && char1 == ' ' {
let v: Vec<&str> = line.split_whitespace().collect();
let x = v[1].parse::<Real>().ok().unwrap();
let y = v[2].parse::<Real>().ok().unwrap();
let z = v[3].parse::<Real>().ok().unwrap();
self.vtx2xyz.push([x, y, z]);
}
if char0 == 'g' && char1 == ' ' {
let v: Vec<&str> = line.split_whitespace().collect();
let name = v[1].to_string();
match name2group.get(&name) {
None => {
i_group = name2group.len();
name2group.insert(name, i_group);
}
Some(&v) => {
i_group = v;
}
};
}
if char0 == 'm' && char1 == 't' {
let v: Vec<&str> = line.split_whitespace().collect();
self.mtl_file_name = v[1].to_string();
}
if char0 == 'u' && char1 == 's' {
let v: Vec<&str> = line.split_whitespace().collect();
let name = v[1].to_string();
match name2mtl.get(&name) {
None => {
i_mtl = name2mtl.len();
name2mtl.insert(name, i_mtl);
}
Some(&v) => {
i_mtl = v;
}
};
}
if char0 == 'v' && char1 == 'n' {
let v: Vec<&str> = line.split_whitespace().collect();
let x = v[1].parse::<Real>().ok().unwrap();
let y = v[2].parse::<Real>().ok().unwrap();
let z = v[3].parse::<Real>().ok().unwrap();
self.vtx2nrm.push([x, y, z]);
}
if char0 == 'v' && char1 == 't' {
let v: Vec<&str> = line.split_whitespace().collect();
let u = v[1].parse::<Real>().ok().unwrap();
let v = v[2].parse::<Real>().ok().unwrap();
self.vtx2uv.push([u, v]);
}
if char0 == 'f' && char1 == ' ' {
let v: Vec<&str> = line.split_whitespace().collect();
for v_ in v.iter().skip(1) {
let (ipnt, itex, inrm) = parse_vertex(v_);
elem2vtx_xyz0.push(ipnt);
elem2vtx_uv0.push(itex);
elem2vtx_nrm0.push(inrm);
}
self.elem2idx.push(elem2vtx_xyz0.len().as_());
self.elem2group.push(i_group.as_());
self.elem2mtl.push(i_mtl.as_());
}
} self.group2name = vec!["".to_string(); name2group.len()];
for (name, &i_group) in name2group.iter() {
self.group2name[i_group].clone_from(name);
}
self.mtl2name = vec!["".to_string(); name2mtl.len()];
for (name, &i_mtl) in name2mtl.iter() {
self.mtl2name[i_mtl].clone_from(name);
}
{
let nvtx_xyz = self.vtx2xyz.len();
self.idx2vtx_xyz = elem2vtx_xyz0
.iter()
.map(|i| {
if *i >= 0 {
(*i).as_()
} else {
(nvtx_xyz as i32 + *i).as_()
}
})
.collect();
}
{
let nvtx_uv = self.vtx2uv.len();
self.idx2vtx_uv = elem2vtx_uv0
.iter()
.map(|i| {
if *i >= 0 {
(*i).as_()
} else {
(nvtx_uv as i32 + *i).as_()
}
})
.collect();
}
{
let nvtx_nrm = self.vtx2nrm.len();
self.idx2vtx_nrm = elem2vtx_nrm0
.iter()
.map(|i| {
if *i >= 0 {
(*i).as_()
} else {
(nvtx_nrm as i32 + *i).as_()
}
})
.collect();
}
Ok(())
}
#[allow(clippy::type_complexity)]
pub fn unified_xyz_uv_as_trimesh(&self) -> (Vec<[Index; 3]>, Vec<[Real; 3]>, Vec<[Real; 2]>) {
let (tri2uni, uni2vtx_xyz, uni2vtx_uv) =
crate::unify_index::unify_two_indices_of_triangle_mesh(
self.idx2vtx_xyz.as_chunks::<3>().0,
self.idx2vtx_uv.as_chunks::<3>().0,
);
assert_eq!(uni2vtx_xyz.len(), uni2vtx_uv.len());
let uni2xyz =
crate::map_idx::map_vertex_attibute_from(self.vtx2xyz.as_flattened(), 3, &uni2vtx_xyz);
let uni2uv =
crate::map_idx::map_vertex_attibute_from(self.vtx2uv.as_flattened(), 2, &uni2vtx_uv);
let tri2uni: Vec<[Index; 3]> = tri2uni;
let uni2xyz: Vec<[Real; 3]> = uni2xyz.as_chunks::<3>().0.to_vec();
let uni2uv: Vec<[Real; 2]> = uni2uv.as_chunks::<2>().0.to_vec();
(tri2uni, uni2xyz, uni2uv)
}
}
impl<Index, Real> Default for WavefrontObj<Index, Real>
where
Real: std::str::FromStr + std::fmt::Display + Copy + num_traits::Zero,
Index: num_traits::PrimInt + 'static + AddAssign + AsPrimitive<usize> + Copy,
usize: AsPrimitive<Index>,
i32: AsPrimitive<Index>,
{
fn default() -> Self {
Self::new()
}
}
#[allow(clippy::type_complexity)]
pub fn load_tri_mesh<P: AsRef<std::path::Path>, Index, Real>(
filepath: P,
scale: Option<Real>,
) -> anyhow::Result<(Vec<[Index; 3]>, Vec<[Real; 3]>)>
where
Real: std::str::FromStr + std::fmt::Display + num_traits::Float,
Index: num_traits::PrimInt + 'static + AddAssign + AsPrimitive<usize> + Copy,
usize: AsPrimitive<Index>,
i32: AsPrimitive<Index>,
{
let mut obj = WavefrontObj::<Index, Real>::new();
obj.load(&filepath)?;
let tri2vtx: Vec<[Index; 3]> = obj.idx2vtx_xyz.as_chunks::<3>().0.to_vec();
let mut vtx2xyz = obj.vtx2xyz;
if let Some(scale_) = scale {
crate::vtx2xyz::normalize_in_place(&mut vtx2xyz, scale_);
}
Ok((tri2vtx, vtx2xyz))
}
pub fn save_tri_mesh_texture(
filepath: &str,
tri2vtx_xyz: &[[usize; 3]],
vtx2xyz: &[[f32; 3]],
tri2vtx_uv: &[[usize; 3]],
vtx2uv: &[[f32; 2]],
) -> anyhow::Result<()> {
assert_eq!(tri2vtx_xyz.len(), tri2vtx_uv.len());
let mut file = File::create(filepath).context("file not found.")?;
for xyz in vtx2xyz.iter() {
writeln!(file, "v {} {} {}", xyz[0], xyz[1], xyz[2])?;
}
for uv in vtx2uv.iter() {
writeln!(file, "vt {} {}", uv[0], uv[1])?;
}
for i_tri in 0..tri2vtx_xyz.len() {
writeln!(
file,
"f {}/{} {}/{} {}/{}",
tri2vtx_xyz[i_tri][0] + 1,
tri2vtx_uv[i_tri][0] + 1,
tri2vtx_xyz[i_tri][1] + 1,
tri2vtx_uv[i_tri][1] + 1,
tri2vtx_xyz[i_tri][2] + 1,
tri2vtx_uv[i_tri][2] + 1
)?;
}
Ok(())
}
fn write_vtx2xyz<Real>(
file: &mut std::io::BufWriter<File>,
vtx2xyz: &[[Real; 3]],
) -> anyhow::Result<()>
where
Real: std::fmt::Display,
{
for v in vtx2xyz {
writeln!(file, "v {} {} {}", v[0], v[1], v[2])?;
}
Ok(())
}
fn write_vtx2nrm<Real>(
file: &mut std::io::BufWriter<File>,
vtx2nrm: &[[Real; 3]],
) -> anyhow::Result<()>
where
Real: std::fmt::Display,
{
for v in vtx2nrm {
writeln!(file, "vn {} {} {}", v[0], v[1], v[2])?;
}
Ok(())
}
fn write_vtx2xyz_vtx2rgb<Real>(
file: &mut std::io::BufWriter<File>,
vtx2xyz: &[[Real; 3]],
vtx2rgb: &[[f32; 3]],
) -> anyhow::Result<()>
where
Real: std::fmt::Display,
{
for (xyz, rgb) in vtx2xyz.iter().zip(vtx2rgb.iter()) {
writeln!(
file,
"v {} {} {} {} {} {}",
xyz[0], xyz[1], xyz[2], rgb[0], rgb[1], rgb[2]
)?;
}
Ok(())
}
pub fn save_tri2vtx_vtx2xyz<Path, Index, Real>(
filepath: Path,
tri2vtx: &[[Index; 3]],
vtx2xyz: &[[Real; 3]],
) -> anyhow::Result<()>
where
Path: AsRef<std::path::Path>,
Real: num_traits::Float + std::fmt::Display,
Index: num_traits::PrimInt + std::fmt::Display,
{
let file = File::create(&filepath)
.context(format!("file not found. {}", filepath.as_ref().display()))?;
let mut file = std::io::BufWriter::new(file);
write_vtx2xyz(&mut file, vtx2xyz)?;
for node2vtx in tri2vtx.iter() {
writeln!(
file,
"f {} {} {}",
node2vtx[0] + Index::one(),
node2vtx[1] + Index::one(),
node2vtx[2] + Index::one()
)?;
}
Ok(())
}
pub fn save_tri2vtx_vtx2xyz_vtx2rgb<Path, Index, Real>(
filepath: Path,
tri2vtx: &[[Index; 3]],
vtx2xyz: &[[Real; 3]],
vtx2rgb: &[[f32; 3]],
) -> anyhow::Result<()>
where
Path: AsRef<std::path::Path>,
Real: num_traits::Float + std::fmt::Display,
Index: num_traits::PrimInt + std::fmt::Display,
{
let file = File::create(filepath).context("file not found.")?;
let mut file = std::io::BufWriter::new(file);
write_vtx2xyz_vtx2rgb(&mut file, vtx2xyz, vtx2rgb)?;
for node2vtx in tri2vtx.iter() {
writeln!(
file,
"f {} {} {}",
node2vtx[0] + Index::one(),
node2vtx[1] + Index::one(),
node2vtx[2] + Index::one()
)?;
}
Ok(())
}
pub fn save_tri2vtx_vtx2xyz_vtx2nrm<Path, Index, Real>(
filepath: Path,
tri2vtx: &[[Index; 3]],
vtx2xyz: &[[Real; 3]],
vtx2nrm: &[[Real; 3]],
) -> anyhow::Result<()>
where
Path: AsRef<std::path::Path>,
Real: num_traits::Float + std::fmt::Display,
Index: num_traits::PrimInt + std::fmt::Display,
{
let file = File::create(filepath).context("file not found.")?;
let mut file = std::io::BufWriter::new(file);
write_vtx2xyz(&mut file, vtx2xyz)?;
write_vtx2nrm(&mut file, vtx2nrm)?;
for node2vtx in tri2vtx.iter() {
let i0 = node2vtx[0] + Index::one();
let i1 = node2vtx[1] + Index::one();
let i2 = node2vtx[2] + Index::one();
writeln!(file, "f {i0}//{i0} {i1}//{i1} {i2}//{i2}")?;
}
Ok(())
}
pub fn save_vtx2xyz_as_polyloop<Path, Real>(
filepath: Path,
vtx2xyz: &[[Real; 3]],
) -> anyhow::Result<()>
where
Path: AsRef<std::path::Path>,
Real: num_traits::Float + std::fmt::Display,
{
let file = File::create(filepath).context("file not found.")?;
let mut file = std::io::BufWriter::new(file);
write_vtx2xyz(&mut file, vtx2xyz)?;
let num_vtx = vtx2xyz.len();
for i_vtx in 0..num_vtx {
let i0 = i_vtx;
let i1 = (i_vtx + 1) % num_vtx;
writeln!(file, "l {} {}", i0 + 1, i1 + 1)?;
}
Ok(())
}
pub fn save_vtx2xyz_as_polyline<Path, Real>(
filepath: Path,
vtx2xyz: &[[Real; 3]],
) -> anyhow::Result<()>
where
Path: AsRef<std::path::Path>,
Real: num_traits::Float + std::fmt::Display,
{
let file = File::create(filepath).context("file not found.")?;
let mut file = std::io::BufWriter::new(file);
write_vtx2xyz(&mut file, vtx2xyz)?;
let num_vtx = vtx2xyz.len();
for i_vtx in 0..num_vtx - 1 {
let i0 = i_vtx;
let i1 = i_vtx + 1;
writeln!(file, "l {} {}", i0 + 1, i1 + 1)?;
}
Ok(())
}
pub fn save_edge2vtx_vtx2xyz<Path, Real>(
filepath: Path,
edge2vtx: &[[usize; 2]],
vtx2xyz: &[[Real; 3]],
) -> anyhow::Result<()>
where
Path: AsRef<std::path::Path>,
Real: num_traits::Float + std::fmt::Display,
{
let file = File::create(filepath).context("file not found.")?;
let mut file = std::io::BufWriter::new(file);
write_vtx2xyz(&mut file, vtx2xyz)?;
for node2vtx in edge2vtx.iter() {
writeln!(file, "l {} {}", node2vtx[0] + 1, node2vtx[1] + 1)?;
}
Ok(())
}
pub fn save_polyline2vtx_vtx2xyz<Path, Real>(
filepath: Path,
polyline2vtx: &[usize],
vtx2xyz: &[[Real; 3]],
) -> anyhow::Result<()>
where
Path: AsRef<std::path::Path>,
Real: num_traits::Float + std::fmt::Display,
{
let file = File::create(filepath).context("file not found.")?;
let mut file = std::io::BufWriter::new(file);
write_vtx2xyz(&mut file, vtx2xyz)?;
for i_poly in 0..polyline2vtx.len() - 1 {
let num_vtx_in_polyline = polyline2vtx[i_poly + 1] - polyline2vtx[i_poly];
for i_vtx in 0..num_vtx_in_polyline - 1 {
let i0 = polyline2vtx[i_poly] + i_vtx;
let i1 = polyline2vtx[i_poly] + i_vtx + 1;
writeln!(file, "l {} {}", i0 + 1, i1 + 1)?;
}
}
Ok(())
}
pub fn save_quad2vtx_vtx2xyz<Path, Real>(
filepath: Path,
quad2vtx: &[[usize; 4]],
vtx2xyz: &[[Real; 3]],
) -> anyhow::Result<()>
where
Path: AsRef<std::path::Path>,
Real: num_traits::Float + std::fmt::Display,
{
let file = File::create(filepath).context("file not found.")?;
let mut file = std::io::BufWriter::new(file);
write_vtx2xyz(&mut file, vtx2xyz)?;
for node2vtx in quad2vtx.iter() {
writeln!(
file,
"f {} {} {} {}",
node2vtx[0] + 1,
node2vtx[1] + 1,
node2vtx[2] + 1,
node2vtx[3] + 1
)?;
}
Ok(())
}
pub fn save_elem2idx_idx2vtx_vtx2xyz<Path, Real>(
filepath: Path,
elem2idx: &[usize],
idx2vtx: &[usize],
vtx2xyz: &[[Real; 3]],
) -> anyhow::Result<()>
where
Path: AsRef<std::path::Path>,
Real: num_traits::Float + std::fmt::Display,
{
let file = File::create(filepath).context("file not found.")?;
let mut file = std::io::BufWriter::new(file);
write_vtx2xyz(&mut file, vtx2xyz)?;
for i_elem in 0..elem2idx.len() - 1 {
let noel = &idx2vtx[elem2idx[i_elem]..elem2idx[i_elem + 1]];
match noel.len() {
3 => writeln!(file, "f {} {} {}", noel[0] + 1, noel[1] + 1, noel[2] + 1)?,
4 => writeln!(
file,
"f {} {} {} {}",
noel[0] + 1,
noel[1] + 1,
noel[2] + 1,
noel[3] + 1
)?,
_ => {
unreachable!()
}
}
}
Ok(())
}
pub fn save_tri2xyz<Path, Real>(filepath: Path, tri2xyz: &[[Real; 9]]) -> anyhow::Result<()>
where
Path: AsRef<std::path::Path>,
Real: num_traits::Float + std::fmt::Display,
{
let file = File::create(filepath).context("file not found.")?;
let mut file = std::io::BufWriter::new(file);
write_vtx2xyz(&mut file, tri2xyz.as_flattened().as_chunks::<3>().0)?;
for (i_tri, _) in tri2xyz.iter().enumerate() {
writeln!(
file,
"f {} {} {}",
i_tri * 3 + 1,
i_tri * 3 + 2,
i_tri * 3 + 3
)?;
}
Ok(())
}
fn parse_vertex(str_in: &str) -> (i32, i32, i32) {
let snums: Vec<&str> = str_in.split('/').collect();
let mut nums: [i32; 3] = [0, 0, 0];
for i in 0..snums.len() {
nums[i] = snums[i].parse::<i32>().unwrap_or(0);
}
(nums[0] - 1, nums[1] - 1, nums[2] - 1)
}
#[test]
fn test_parse_vertex() {
assert_eq!(parse_vertex("1/2/3"), (0, 1, 2));
assert_eq!(parse_vertex("1//3"), (0, -1, 2));
assert_eq!(parse_vertex("1/2"), (0, 1, -1));
assert_eq!(parse_vertex("1"), (0, -1, -1));
}