nom_obj/model/
mod.rs

1use parser::obj::{FaceIndex, ObjLine, ObjParser};
2
3use parser::mtl::{MtlLine, MtlParser};
4
5// use parser::mtl::{ MtlLine, MtlParser };
6use std::collections::HashMap;
7use std::error::Error;
8use std::fs::File;
9use std::io::BufReader;
10use std::path::Path;
11
12pub struct Obj {
13    pub comments: Vec<String>,
14    pub objects: Vec<ObjObject>,
15}
16impl Obj {
17    pub fn read_file(filename: &str) -> Result<Self, Box<dyn Error>> {
18        let file = File::open(filename)?;
19        let parser = ObjParser::new(BufReader::new(file));
20
21        let mut comments = Vec::new();
22        let mut objects = Vec::new();
23        let mut object = ObjObject::new();
24
25        for line in parser {
26            match line {
27                ObjLine::ObjectName(name) => {
28                    // new object encountered, when multiple objects exist
29                    if object.name.is_some() {
30                        objects.push(object);
31                        object = ObjObject::new();
32                    }
33                    object.name = Some(name);
34                }
35                ObjLine::MtlLib(name) => {
36                    if let Some(parent) = Path::new(filename).parent() {
37                        let mtl_path = Path::join(parent, name);
38                        let file = File::open(mtl_path)?;
39                        let reader = BufReader::new(file);
40                        let mut mtl_parser = MtlParser::new(reader);
41                        for line in mtl_parser {
42                            if let MtlLine::DiffuseMap(diffuse_map) = line {
43                                let path = Path::join(parent, diffuse_map);
44                                if let Some(diffuse) = path.to_owned().to_str() {
45                                    let diffuse_map = diffuse.to_string();
46                                    object.material = Some(ObjMaterial { diffuse_map });
47                                    continue;
48                                }
49                            }
50                        }
51                    }
52                }
53                ObjLine::Vertex(..) => object.vertices.push(line),
54                ObjLine::VertexParam(..) => object.vertex_params.push(line),
55                ObjLine::Face(..) => object.faces.push(line),
56                ObjLine::Normal(..) => object.normals.push(line),
57                ObjLine::TextureUVW(..) => object.texture_coords.push(line),
58                ObjLine::Comment(comment) => comments.push(comment),
59                _ => {}
60            }
61        }
62        objects.push(object);
63        Ok(Obj { comments, objects })
64    }
65}
66
67#[derive(Debug)]
68pub struct ObjObject {
69    pub name: Option<String>,
70    pub material: Option<ObjMaterial>,
71    pub vertices: Vec<ObjLine>,
72    pub normals: Vec<ObjLine>,
73    pub texture_coords: Vec<ObjLine>,
74    pub vertex_params: Vec<ObjLine>,
75    pub faces: Vec<ObjLine>,
76}
77
78#[derive(Debug)]
79pub struct ObjMaterial {
80    pub diffuse_map: String,
81}
82
83impl ObjObject {
84    pub fn new() -> Self {
85        ObjObject {
86            name: None,
87            material: None,
88            vertices: Vec::new(),
89            normals: Vec::new(),
90            texture_coords: Vec::new(),
91            vertex_params: Vec::new(),
92            faces: Vec::new(),
93        }
94    }
95    pub fn vertices(&self) -> &Vec<ObjLine> {
96        &self.vertices
97    }
98    pub fn vertex_params(&self) -> &Vec<ObjLine> {
99        &self.vertex_params
100    }
101    pub fn normals(&self) -> &Vec<ObjLine> {
102        &self.normals
103    }
104    pub fn texture_coords(&self) -> &Vec<ObjLine> {
105        &self.texture_coords
106    }
107
108    #[inline]
109    fn get_v_tuple(&self, face_index: &FaceIndex) -> (f32, f32, f32, f32) {
110        let &FaceIndex(v, _, _) = face_index;
111        match self.vertices[(v as usize) - 1] {
112            ObjLine::Vertex(x, y, z, w) => (x, y, z, w.unwrap_or(1.0)),
113            _ => panic!("not a vertex"),
114        }
115    }
116
117    #[inline]
118    fn get_vt_tuple(&self, face_index: &FaceIndex) -> (f32, f32, f32) {
119        let &FaceIndex(_, vt, _) = face_index;
120        if vt.is_none() {
121            (0.0, 0.0, 0.0)
122        } else {
123            match self.texture_coords[(vt.unwrap() as usize) - 1] {
124                ObjLine::TextureUVW(u, v, w) => (u, v, w.unwrap_or(0.0)),
125                _ => panic!("not a vertex"),
126            }
127        }
128    }
129
130    #[inline]
131    fn get_vn_tuple(&self, face_index: &FaceIndex) -> (f32, f32, f32) {
132        let &FaceIndex(_, _, vn) = face_index;
133        if vn.is_none() {
134            (0.0, 0.0, 0.0)
135        } else {
136            match self.normals[(vn.unwrap() as usize) - 1] {
137                ObjLine::Normal(x, y, z) => (x, y, z),
138                _ => panic!("not a vertex"),
139            }
140        }
141    }
142
143    #[inline]
144    fn interleave_tuples(
145        &self,
146        id: &FaceIndex,
147    ) -> ((f32, f32, f32, f32), (f32, f32, f32), (f32, f32, f32)) {
148        let vert = self.get_v_tuple(id);
149        let text = self.get_vt_tuple(id);
150        let norm = self.get_vn_tuple(id);
151        (vert, text, norm)
152    }
153
154    pub fn interleaved(&self) -> Interleaved {
155        let mut vertex_map = HashMap::new();
156
157        let mut data = Interleaved {
158            v_vt_vn: Vec::new(),
159            idx: Vec::new(),
160        };
161
162        for i in 0usize..self.faces.len() {
163            match self.faces[i] {
164                ObjLine::Face(ref id1, ref id2, ref id3) => {
165                    let next_idx = (id1.0 as usize) - 1;
166                    data.idx.push(next_idx);
167                    vertex_map
168                        .entry(next_idx)
169                        .or_insert_with(|| self.interleave_tuples(id1));
170
171                    let next_idx = (id2.0 as usize) - 1;
172                    data.idx.push(next_idx);
173                    vertex_map
174                        .entry(next_idx)
175                        .or_insert_with(|| self.interleave_tuples(id2));
176
177                    let next_idx = (id3.0 as usize) - 1;
178                    data.idx.push(next_idx);
179                    vertex_map
180                        .entry(next_idx)
181                        .or_insert_with(|| self.interleave_tuples(id3));
182                }
183                _ => panic!("Found something other than a ObjLine::Face in object.faces"),
184            }
185        }
186        for i in 0usize..vertex_map.len() {
187            data.v_vt_vn.push(vertex_map.remove(&i).unwrap());
188        }
189        data
190    }
191}
192
193pub struct Interleaved {
194    pub v_vt_vn: Vec<((f32, f32, f32, f32), (f32, f32, f32), (f32, f32, f32))>,
195    pub idx: Vec<usize>,
196}
197
198#[cfg(test)]
199mod tests {
200
201    use super::*;
202
203    #[test]
204    fn cube_format_interleaved() -> Result<(), Box<dyn Error>> {
205        let o = Obj::read_file("assets/cube.obj")?;
206        let interleaved = o.objects[0].interleaved();
207        println!("{:?}", o.objects[0].faces);
208        assert_eq!(o.objects[0].faces.len(), 12);
209        assert_eq!(interleaved.v_vt_vn.len(), 8);
210
211        assert!(o.objects[0].material.is_some());
212        let ObjMaterial { diffuse_map } = o.objects[0].material.as_ref().unwrap();
213        assert_eq!(diffuse_map, "assets/diffuse_map.png");
214        Ok(())
215    }
216
217    #[test]
218    fn cube_obj_has_12_faces() -> Result<(), Box<dyn Error>> {
219        // Triangulated model, 12/2 = 6 quads
220        let Obj {
221            objects: cube_objects,
222            ..
223        } = Obj::read_file("assets/cube.obj")?;
224        assert_eq!(cube_objects[0].faces.len(), 12);
225        Ok(())
226    }
227
228    #[test]
229    fn cube_obj_has_8_verts() -> Result<(), Box<dyn Error>> {
230        let o = Obj::read_file("assets/cube.obj")?;
231        assert_eq!(o.objects[0].vertices.len(), 8);
232        Ok(())
233    }
234
235    #[test]
236    fn cube_obj_has_1_object() -> Result<(), Box<dyn Error>> {
237        let o = Obj::read_file("assets/cube.obj")?;
238        assert_eq!(o.objects.len(), 1);
239        Ok(())
240    }
241
242    #[test]
243    fn parses_separate_objects() -> Result<(), Box<dyn Error>> {
244        let o = Obj::read_file("assets/four_blue_cubes.obj")?;
245        assert_eq!(o.objects.len(), 4);
246        Ok(())
247    }
248}