obj/
lib.rs

1/*!
2
3[Wavefront OBJ][obj] parser for Rust. It handles both `.obj` and `.mtl` formats. [GitHub][]
4
5```rust
6use std::fs::File;
7use std::io::BufReader;
8use obj::{load_obj, Obj};
9
10let input = BufReader::new(File::open("tests/fixtures/normal-cone.obj")?);
11let dome: Obj = load_obj(input)?;
12
13// Do whatever you want
14dome.vertices;
15dome.indices;
16# Ok::<(), obj::ObjError>(())
17```
18
19<img alt="Rendered image of cute Rilakkuma" src="https://i.hyeon.me/obj-rs/bear.png" style="max-width:100%">
20
21[obj]: https://en.wikipedia.org/wiki/Wavefront_.obj_file
22[GitHub]: https://github.com/simnalamburt/obj-rs
23
24*/
25
26#![deny(missing_docs)]
27
28mod error;
29pub mod raw;
30
31pub use crate::error::{LoadError, LoadErrorKind, ObjError, ObjResult};
32
33use crate::error::{index_out_of_range, make_error};
34use crate::raw::object::Polygon;
35use num_traits::FromPrimitive;
36use std::collections::hash_map::{Entry, HashMap};
37use std::io::BufRead;
38
39#[cfg(feature = "glium")]
40use glium::implement_vertex;
41#[cfg(feature = "serde")]
42use serde::{Deserialize, Serialize};
43
44#[cfg(feature = "vulkano")]
45use bytemuck::{Pod, Zeroable};
46#[cfg(feature = "vulkano")]
47use vulkano::impl_vertex;
48
49/// Load a wavefront OBJ file into Rust & OpenGL friendly format.
50pub fn load_obj<V: FromRawVertex<I>, T: BufRead, I>(input: T) -> ObjResult<Obj<V, I>> {
51    let raw = raw::parse_obj(input)?;
52    Obj::new(raw)
53}
54
55/// 3D model object loaded from wavefront OBJ.
56#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
57#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
58pub struct Obj<V = Vertex, I = u16> {
59    /// Object's name.
60    pub name: Option<String>,
61    /// Vertex buffer.
62    pub vertices: Vec<V>,
63    /// Index buffer.
64    pub indices: Vec<I>,
65}
66
67impl<V: FromRawVertex<I>, I> Obj<V, I> {
68    /// Create `Obj` from `RawObj` object.
69    pub fn new(raw: raw::RawObj) -> ObjResult<Self> {
70        let (vertices, indices) =
71            FromRawVertex::process(raw.positions, raw.normals, raw.tex_coords, raw.polygons)?;
72
73        Ok(Obj {
74            name: raw.name,
75            vertices,
76            indices,
77        })
78    }
79}
80
81/// Conversion from `RawObj`'s raw data.
82pub trait FromRawVertex<I>: Sized {
83    /// Build vertex and index buffer from raw object data.
84    fn process(
85        vertices: Vec<(f32, f32, f32, f32)>,
86        normals: Vec<(f32, f32, f32)>,
87        tex_coords: Vec<(f32, f32, f32)>,
88        polygons: Vec<Polygon>,
89    ) -> ObjResult<(Vec<Self>, Vec<I>)>;
90}
91
92/// Vertex data type of `Obj` which contains position and normal data of a vertex.
93#[derive(Default, Copy, PartialEq, Clone, Debug)]
94#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
95#[cfg_attr(feature = "vulkano", repr(C))]
96#[cfg_attr(feature = "vulkano", derive(Zeroable, Pod))]
97pub struct Vertex {
98    /// Position vector of a vertex.
99    pub position: [f32; 3],
100    /// Normal vertor of a vertex.
101    pub normal: [f32; 3],
102}
103
104#[cfg(feature = "glium")]
105implement_vertex!(Vertex, position, normal);
106#[cfg(feature = "vulkano")]
107impl_vertex!(Vertex, position, normal);
108
109impl<I: FromPrimitive + Copy> FromRawVertex<I> for Vertex {
110    fn process(
111        positions: Vec<(f32, f32, f32, f32)>,
112        normals: Vec<(f32, f32, f32)>,
113        _: Vec<(f32, f32, f32)>,
114        polygons: Vec<Polygon>,
115    ) -> ObjResult<(Vec<Self>, Vec<I>)> {
116        let mut vb = Vec::with_capacity(polygons.len() * 3);
117        let mut ib = Vec::with_capacity(polygons.len() * 3);
118        {
119            let mut cache = HashMap::new();
120            let mut map = |pi: usize, ni: usize| -> ObjResult<()> {
121                // Look up cache
122                let index = match cache.entry((pi, ni)) {
123                    // Cache miss -> make new, store it on cache
124                    Entry::Vacant(entry) => {
125                        let p = positions[pi];
126                        let n = normals[ni];
127                        let vertex = Vertex {
128                            position: [p.0, p.1, p.2],
129                            normal: [n.0, n.1, n.2],
130                        };
131                        let index = match I::from_usize(vb.len()) {
132                            Some(val) => val,
133                            None => return index_out_of_range::<_, I>(vb.len()),
134                        };
135                        vb.push(vertex);
136                        entry.insert(index);
137                        index
138                    }
139                    // Cache hit -> use it
140                    Entry::Occupied(entry) => *entry.get(),
141                };
142                ib.push(index);
143                Ok(())
144            };
145
146            for polygon in polygons {
147                match polygon {
148                    Polygon::P(_) | Polygon::PT(_) => make_error!(
149                        InsufficientData,
150                        "Tried to extract normal data which are not contained in the model"
151                    ),
152                    Polygon::PN(ref vec) if vec.len() == 3 => {
153                        for &(pi, ni) in vec {
154                            map(pi, ni)?;
155                        }
156                    }
157                    Polygon::PTN(ref vec) if vec.len() == 3 => {
158                        for &(pi, _, ni) in vec {
159                            map(pi, ni)?;
160                        }
161                    }
162                    _ => make_error!(
163                        UntriangulatedModel,
164                        "Model should be triangulated first to be loaded properly"
165                    ),
166                }
167            }
168        }
169        vb.shrink_to_fit();
170        Ok((vb, ib))
171    }
172}
173
174/// Vertex data type of `Obj` which contains only position data of a vertex.
175#[derive(Default, Copy, PartialEq, Clone, Debug)]
176#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
177#[cfg_attr(feature = "vulkano", repr(C))]
178#[cfg_attr(feature = "vulkano", derive(Zeroable, Pod))]
179pub struct Position {
180    /// Position vector of a vertex.
181    pub position: [f32; 3],
182}
183
184#[cfg(feature = "glium")]
185implement_vertex!(Position, position);
186#[cfg(feature = "vulkano")]
187impl_vertex!(Position, position);
188
189impl<I: FromPrimitive> FromRawVertex<I> for Position {
190    fn process(
191        vertices: Vec<(f32, f32, f32, f32)>,
192        _: Vec<(f32, f32, f32)>,
193        _: Vec<(f32, f32, f32)>,
194        polygons: Vec<Polygon>,
195    ) -> ObjResult<(Vec<Self>, Vec<I>)> {
196        let vb = vertices
197            .into_iter()
198            .map(|v| Position {
199                position: [v.0, v.1, v.2],
200            })
201            .collect();
202        let mut ib = Vec::with_capacity(polygons.len() * 3);
203        {
204            let mut map = |pi: usize| -> ObjResult<()> {
205                ib.push(match I::from_usize(pi) {
206                    Some(val) => val,
207                    None => return index_out_of_range::<_, I>(pi),
208                });
209                Ok(())
210            };
211
212            for polygon in polygons {
213                match polygon {
214                    Polygon::P(ref vec) if vec.len() == 3 => {
215                        for &pi in vec {
216                            map(pi)?
217                        }
218                    }
219                    Polygon::PT(ref vec) | Polygon::PN(ref vec) if vec.len() == 3 => {
220                        for &(pi, _) in vec {
221                            map(pi)?
222                        }
223                    }
224                    Polygon::PTN(ref vec) if vec.len() == 3 => {
225                        for &(pi, _, _) in vec {
226                            map(pi)?
227                        }
228                    }
229                    _ => make_error!(
230                        UntriangulatedModel,
231                        "Model should be triangulated first to be loaded properly"
232                    ),
233                }
234            }
235        }
236        Ok((vb, ib))
237    }
238}
239
240/// Vertex data type of `Obj` which contains position, normal and texture data of a vertex.
241#[derive(Default, Copy, PartialEq, Clone, Debug)]
242#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
243#[cfg_attr(feature = "vulkano", repr(C))]
244#[cfg_attr(feature = "vulkano", derive(Zeroable, Pod))]
245pub struct TexturedVertex {
246    /// Position vector of a vertex.
247    pub position: [f32; 3],
248    /// Normal vertor of a vertex.
249    pub normal: [f32; 3],
250    /// Texture of a vertex.
251    pub texture: [f32; 3],
252}
253
254#[cfg(feature = "glium")]
255implement_vertex!(TexturedVertex, position, normal, texture);
256#[cfg(feature = "vulkano")]
257impl_vertex!(TexturedVertex, position, normal, texture);
258
259impl<I: FromPrimitive + Copy> FromRawVertex<I> for TexturedVertex {
260    fn process(
261        positions: Vec<(f32, f32, f32, f32)>,
262        normals: Vec<(f32, f32, f32)>,
263        tex_coords: Vec<(f32, f32, f32)>,
264        polygons: Vec<Polygon>,
265    ) -> ObjResult<(Vec<Self>, Vec<I>)> {
266        let mut vb = Vec::with_capacity(polygons.len() * 3);
267        let mut ib = Vec::with_capacity(polygons.len() * 3);
268        {
269            let mut cache = HashMap::new();
270            let mut map = |pi: usize, ni: usize, ti: usize| -> ObjResult<()> {
271                // Look up cache
272                let index = match cache.entry((pi, ni, ti)) {
273                    // Cache miss -> make new, store it on cache
274                    Entry::Vacant(entry) => {
275                        let p = positions[pi];
276                        let n = normals[ni];
277                        let t = tex_coords[ti];
278                        let vertex = TexturedVertex {
279                            position: [p.0, p.1, p.2],
280                            normal: [n.0, n.1, n.2],
281                            texture: [t.0, t.1, t.2],
282                        };
283                        let index = match I::from_usize(vb.len()) {
284                            Some(val) => val,
285                            None => return index_out_of_range::<_, I>(vb.len()),
286                        };
287                        vb.push(vertex);
288                        entry.insert(index);
289                        index
290                    }
291                    // Cache hit -> use it
292                    Entry::Occupied(entry) => *entry.get(),
293                };
294                ib.push(index);
295                Ok(())
296            };
297
298            for polygon in polygons {
299                match polygon {
300                    Polygon::P(_) => make_error!(InsufficientData, "Tried to extract normal and texture data which are not contained in the model"),
301                    Polygon::PT(_) => make_error!(InsufficientData, "Tried to extract normal data which are not contained in the model"),
302                    Polygon::PN(_) => make_error!(InsufficientData, "Tried to extract texture data which are not contained in the model"),
303                    Polygon::PTN(ref vec) if vec.len() == 3 => {
304                        for &(pi, ti, ni) in vec { map(pi, ni, ti)? }
305                    }
306                    _ => make_error!(UntriangulatedModel, "Model should be triangulated first to be loaded properly")
307                }
308            }
309        }
310        vb.shrink_to_fit();
311        Ok((vb, ib))
312    }
313}
314
315#[cfg(feature = "glium")]
316mod glium_support {
317    use super::Obj;
318    use glium::backend::Facade;
319    use glium::{index, vertex, IndexBuffer, VertexBuffer};
320
321    impl<V: vertex::Vertex, I: glium::index::Index> Obj<V, I> {
322        /// Retrieve glium-compatible vertex buffer from Obj
323        pub fn vertex_buffer<F: Facade>(
324            &self,
325            facade: &F,
326        ) -> Result<VertexBuffer<V>, vertex::BufferCreationError> {
327            VertexBuffer::new(facade, &self.vertices)
328        }
329
330        /// Retrieve glium-compatible index buffer from Obj
331        pub fn index_buffer<F: Facade>(
332            &self,
333            facade: &F,
334        ) -> Result<IndexBuffer<I>, index::BufferCreationError> {
335            IndexBuffer::new(facade, index::PrimitiveType::TrianglesList, &self.indices)
336        }
337    }
338}