1#![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
49pub 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#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
57#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
58pub struct Obj<V = Vertex, I = u16> {
59 pub name: Option<String>,
61 pub vertices: Vec<V>,
63 pub indices: Vec<I>,
65}
66
67impl<V: FromRawVertex<I>, I> Obj<V, I> {
68 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
81pub trait FromRawVertex<I>: Sized {
83 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#[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 pub position: [f32; 3],
100 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 let index = match cache.entry((pi, ni)) {
123 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 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#[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 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#[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 pub position: [f32; 3],
248 pub normal: [f32; 3],
250 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 let index = match cache.entry((pi, ni, ti)) {
273 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 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 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 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}