[go: up one dir, main page]

obj 0.7.0

A package for loading Wavefront .obj files
Documentation
//   Copyright 2017 GFX Developers
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.


#[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"),
        }
        // Slice pattern syntax is experimental
        //    match &gs[..] {
        //      &[g0, g1, g2] => Polygon::PolyTri(Triangle::new(g0, g1, g2)),
        //      &[g0, g1, g2, g3] => Polygon::PolyQuad(Quad::new(g0, g1, g2, g3)),
        //      _ => 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,
    /// An index is used to tell groups apart that share the
    /// same name
    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()),
                    };

                    // we found a new material that was applied to an existing
                    // object. It is treated as a new group.
                    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
    }
}