#[cfg(feature = "genmesh")]
pub use genmesh::{Polygon, Quad, Triangle};
use std::io::BufRead;
use std::str::FromStr;
pub type IndexTuple = (usize, Option<usize>, Option<usize>);
pub type SimplePolygon = Vec<IndexTuple>;
pub trait GenPolygon: Clone {
fn new(data: Vec<IndexTuple>) -> Self;
}
impl GenPolygon for SimplePolygon {
fn new(data: Vec<IndexTuple>) -> Self {
data
}
}
#[cfg(feature = "genmesh")]
impl GenPolygon for Polygon<IndexTuple> {
fn new(gs: Vec<IndexTuple>) -> 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<MTL, P: GenPolygon> {
pub name: String,
pub groups: Vec<Group<MTL, P>>,
}
impl<MTL, P: GenPolygon> Object<MTL, P> {
pub fn new(name: String) -> Self {
Object { name: name, groups: Vec::new() }
}
}
#[derive(Debug, Clone)]
pub struct Group<MTL, P: GenPolygon> {
pub name: String,
pub index: usize,
pub material: Option<MTL>,
pub indices: Vec<P>,
}
impl<MTL, P: GenPolygon> Group<MTL, P> {
pub fn new(name: String) -> Self {
Group {
name: name,
index: 0,
material: None,
indices: Vec::new(),
}
}
}
pub struct Obj<MTL, P: GenPolygon> {
pub position: Vec<[f32; 3]>,
pub texture: Vec<[f32; 2]>,
pub normal: Vec<[f32; 3]>,
pub objects: Vec<Object<MTL, P>>,
pub materials: Vec<String>,
}
fn normalize(idx: isize, len: usize) -> usize {
if idx < 0 {
(len as isize + idx) as usize
} else {
idx as usize - 1
}
}
impl<MTL, P: GenPolygon> Obj<MTL, P> {
fn new() -> Self {
Obj {
position: Vec::new(),
texture: Vec::new(),
normal: Vec::new(),
objects: Vec::new(),
materials: Vec::new(),
}
}
pub fn map<T, F>(self, mut f: F) -> Obj<T, P>
where
F: FnMut(Group<MTL, P>) -> Group<T, P>,
{
let objects = self.objects
.into_iter()
.map(|Object { name, groups }| {
let groups = groups.into_iter().map(|g| f(g)).collect();
Object { name: name, groups: groups }
})
.collect();
Obj {
position: self.position,
texture: self.texture,
normal: self.normal,
objects,
materials: self.materials,
}
}
}
impl<P: GenPolygon> Obj<String, P> {
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((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<'a, I>(&self, groups: &mut I) -> Result<P, String>
where
I: Iterator<Item = &'a 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<B: BufRead>(input: &mut B) -> Self {
let mut dat = Obj::new();
let mut object = Object::new("default".to_string());
let mut group: Option<Group<String, P>> = None;
for (idx, line) in input.lines().enumerate() {
let (line, mut words) = match line {
Ok(ref line) => (line, line.split_whitespace().filter(|s| !s.is_empty())),
Err(err) => panic!("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) => panic!("Could not parse line: {}\nline: {}: {}", e, idx, line),
Ok(poly) => poly,
};
group = Some(match group {
None => {
let mut obj = Group::new("default".to_string());
obj.indices.push(poly);
obj
}
Some(mut obj) => {
obj.indices.push(poly);
obj
}
});
}
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[1..].trim();
group = Some(Group::new(name.to_string()));
}
}
Some("mtllib") => {
let name = words.next().expect("Failed to find name for mtllib");
dat.materials.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.indices.clear();
}
g.material = match words.next() {
Some(w) => Some(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);
dat
}
}