1use parser::obj::{FaceIndex, ObjLine, ObjParser};
2
3use parser::mtl::{MtlLine, MtlParser};
4
5use 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 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 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}