#[cfg(feature = "genmesh")]
pub use genmesh::{Polygon, Quad, Triangle};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::path::Path;
use std::str::FromStr;
use mtl::{Material, Mtl};
#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)]
pub struct IndexTuple(pub usize, pub Option<usize>, pub Option<usize>);
pub type SimplePolygon = Vec<IndexTuple>;
pub trait GenPolygon: Clone {
fn new(data: SimplePolygon) -> Self;
}
impl GenPolygon for SimplePolygon {
fn new(data: Self) -> Self {
data
}
}
#[cfg(feature = "genmesh")]
impl GenPolygon for Polygon<IndexTuple> {
fn new(gs: SimplePolygon) -> Self {
match gs.len() {
3 => Polygon::PolyTri(Triangle::new(gs[0], gs[1], gs[2])),
4 => Polygon::PolyQuad(Quad::new(gs[0], gs[1], gs[2], gs[3])),
_ => panic!("Unsupported"),
}
}
}
#[derive(Debug, Clone)]
pub struct Object<'a, P>
where
P: 'a + GenPolygon,
{
pub name: String,
pub groups: Vec<Group<'a, P>>,
}
impl<'a, P> Object<'a, P>
where
P: GenPolygon,
{
pub fn new(name: String) -> Self {
Object { name: name, groups: Vec::new() }
}
}
#[derive(Debug, Clone)]
pub struct Group<'a, P>
where
P: 'a + GenPolygon,
{
pub name: String,
pub index: usize,
pub material: Option<Cow<'a, Material>>,
pub polys: Vec<P>,
}
impl<'a, P> Group<'a, P>
where
P: 'a + GenPolygon,
{
pub fn new(name: String) -> Self {
Group {
name: name,
index: 0,
material: None,
polys: Vec::new(),
}
}
}
pub struct Obj<'a, P>
where
P: 'a + GenPolygon,
{
pub position: Vec<[f32; 3]>,
pub texture: Vec<[f32; 2]>,
pub normal: Vec<[f32; 3]>,
pub objects: Vec<Object<'a, P>>,
pub material_libs: Vec<String>,
}
fn normalize(idx: isize, len: usize) -> usize {
if idx < 0 {
(len as isize + idx) as usize
} else {
idx as usize - 1
}
}
impl<'a, P> Obj<'a, P>
where
P: GenPolygon,
{
fn new() -> Self {
Obj {
position: Vec::new(),
texture: Vec::new(),
normal: Vec::new(),
objects: Vec::new(),
material_libs: Vec::new(),
}
}
pub fn load(path: &Path) -> io::Result<Obj<P>> {
let f = File::open(path)?;
let obj = Obj::load_buf(&mut BufReader::new(f))?;
Ok(obj)
}
pub fn load_mtls(&mut self) -> Result<(), Vec<(String, io::Error)>> {
let mut errs = Vec::new();
let mut materials = HashMap::new();
for m in &self.material_libs {
let file = match File::open(&m) {
Ok(f) => f,
Err(err) => {
errs.push((m.clone(), err));
continue;
}
};
let mtl = Mtl::load(&mut BufReader::new(file));
for m in mtl.materials {
materials.insert(m.name.clone(), Cow::from(m));
}
}
for object in &mut self.objects {
for group in &mut object.groups {
if let Some(ref mut mat) = group.material {
match materials.get(&mat.name) {
Some(newmat) => *mat = newmat.clone(),
None => {}
};
}
}
}
if errs.is_empty() { Ok(()) } else { Err(errs) }
}
fn parse_vertex(&mut self, v0: Option<&str>, v1: Option<&str>, v2: Option<&str>) {
let (v0, v1, v2) = match (v0, v1, v2) {
(Some(v0), Some(v1), Some(v2)) => (v0, v1, v2),
_ => {
panic!("could not parse line {:?} {:?} {:?}", v0, v1, v2);
}
};
let vertex = match (FromStr::from_str(v0), FromStr::from_str(v1), FromStr::from_str(v2)) {
(Ok(v0), Ok(v1), Ok(v2)) => [v0, v1, v2],
_ => {
panic!("could not parse line {:?} {:?} {:?}", v0, v1, v2);
}
};
self.position.push(vertex);
}
fn parse_texture(&mut self, t0: Option<&str>, t1: Option<&str>) {
let (t0, t1) = match (t0, t1) {
(Some(t0), Some(t1)) => (t0, t1),
_ => {
panic!("could not parse line {:?} {:?}", t0, t1);
}
};
let texture = match (FromStr::from_str(t0), FromStr::from_str(t1)) {
(Ok(t0), Ok(t1)) => [t0, t1],
_ => {
panic!("could not parse line {:?} {:?}", t0, t1);
}
};
self.texture.push(texture);
}
fn parse_normal(&mut self, n0: Option<&str>, n1: Option<&str>, n2: Option<&str>) {
let (n0, n1, n2) = match (n0, n1, n2) {
(Some(n0), Some(n1), Some(n2)) => (n0, n1, n2),
_ => {
panic!("could not parse line {:?} {:?} {:?}", n0, n1, n2);
}
};
let normal = match (FromStr::from_str(n0), FromStr::from_str(n1), FromStr::from_str(n2)) {
(Ok(n0), Ok(n1), Ok(n2)) => [n0, n1, n2],
_ => {
panic!("could not parse line {:?} {:?} {:?}", n0, n1, n2);
}
};
self.normal.push(normal);
}
fn parse_group(&self, group: &str) -> Result<IndexTuple, String> {
let mut group_split = group.split('/');
let p: Option<isize> = group_split.next().and_then(|idx| FromStr::from_str(idx).ok());
let t: Option<isize> =
group_split.next().and_then(|idx| if idx != "" { FromStr::from_str(idx).ok() } else { None });
let n: Option<isize> = group_split.next().and_then(|idx| FromStr::from_str(idx).ok());
match (p, t, n) {
(Some(p), v, n) => {
Ok(IndexTuple(normalize(p, self.position.len()),
v.map(|v| normalize(v, self.texture.len())),
n.map(|n| normalize(n, self.normal.len()))))
}
_ => Err(format!("poorly formed group {}", group)),
}
}
fn parse_face<'b, I>(&self, groups: &mut I) -> Result<P, String>
where
I: Iterator<Item = &'b str>,
{
let mut ret = Vec::with_capacity(3);
for g in groups {
let ituple = self.parse_group(g)?;
ret.push(ituple);
}
Ok(P::new(ret))
}
pub fn load_buf<B>(input: &mut B) -> io::Result<Self>
where
B: BufRead,
{
let mut dat = Obj::new();
let mut object = Object::new("default".to_string());
let mut group: Option<Group<P>> = None;
for (idx, line) in input.lines().enumerate() {
let (line, mut words) = match line {
Ok(ref line) => (line.clone(), line.split_whitespace().filter(|s| !s.is_empty())),
Err(err) => {
return Err(io::Error::new(io::ErrorKind::InvalidData,
format!("failed to readline {}", err)))
}
};
let first = words.next();
match first {
Some("v") => {
let (v0, v1, v2) = (words.next(), words.next(), words.next());
dat.parse_vertex(v0, v1, v2);
}
Some("vt") => {
let (t0, t1) = (words.next(), words.next());
dat.parse_texture(t0, t1);
}
Some("vn") => {
let (n0, n1, n2) = (words.next(), words.next(), words.next());
dat.parse_normal(n0, n1, n2);
}
Some("f") => {
let poly = match dat.parse_face(&mut words) {
Err(e) => {
return Err(io::Error::new(io::ErrorKind::InvalidData,
format!("could not parse line: {}\nline: {}: {}", e, idx, line)))
}
Ok(poly) => poly,
};
group = Some(match group {
None => {
let mut g = Group::new("default".to_string());
g.polys.push(poly);
g
}
Some(mut g) => {
g.polys.push(poly);
g
}
});
}
Some("o") => {
group = match group {
Some(val) => {
object.groups.push(val);
dat.objects.push(object);
None
}
None => None,
};
object = if line.len() > 2 {
let name = line[1..].trim();
Object::new(name.to_string())
} else {
Object::new("default".to_string())
};
}
Some("g") => {
group = match group {
Some(val) => {
object.groups.push(val);
None
}
None => None,
};
if line.len() > 2 {
let name = line[2..].trim();
group = Some(Group::new(name.to_string()));
}
}
Some("mtllib") => {
let name = words.next().expect("Failed to find name for mtllib");
dat.material_libs.push(name.to_string());
}
Some("usemtl") => {
let mut g = match group {
Some(g) => g,
None => Group::new("default".to_string()),
};
if g.material.is_some() {
object.groups.push(g.clone());
g.index += 1;
g.polys.clear();
}
g.material = match words.next() {
Some(w) => Some(Cow::from(Material::new(w.to_string()))),
None => None,
};
group = Some(g);
}
Some("s") => (),
Some(other) => {
if !other.starts_with("#") {
panic!("Invalid token {:?} {:?}", other, words.next());
}
}
None => (),
}
}
match group {
Some(g) => object.groups.push(g),
None => (),
};
dat.objects.push(object);
Ok(dat)
}
}