Skip to main content

cranpose_ui_graphics/space/
obj.rs

1use std::{collections::HashMap, fmt};
2
3use super::mesh::{Mesh, MeshError, smooth_normals};
4
5/// Why the text given to [`Mesh::from_obj`] does not make a mesh.
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct ObjError {
8    /// The line the problem is on, counted from one; zero when it is about
9    /// the file as a whole.
10    pub line: usize,
11    /// What is wrong.
12    pub kind: ObjErrorKind,
13}
14
15/// What is wrong in an OBJ file.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum ObjErrorKind {
18    /// A number does not parse.
19    BadNumber,
20    /// A statement has too few numbers, or a face too few corners.
21    TooFewValues,
22    /// A face names a position, texture coordinate or normal that the file
23    /// has not given before it.
24    IndexOutOfRange,
25    /// The parts do not make a mesh.
26    Mesh(MeshError),
27}
28
29impl fmt::Display for ObjError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        let what = match &self.kind {
32            ObjErrorKind::BadNumber => "a number does not parse",
33            ObjErrorKind::TooFewValues => "too few values",
34            ObjErrorKind::IndexOutOfRange => "a face names a vertex that is not given before it",
35            ObjErrorKind::Mesh(error) => return write!(f, "OBJ file: {error}"),
36        };
37        write!(f, "OBJ line {}: {what}", self.line)
38    }
39}
40
41impl std::error::Error for ObjError {}
42
43#[derive(Default)]
44struct Builder {
45    positions: Vec<[f32; 3]>,
46    uvs: Vec<[f32; 2]>,
47    normals: Vec<[f32; 3]>,
48    corners: HashMap<(usize, Option<usize>, Option<usize>), u32>,
49    out_positions: Vec<[f32; 3]>,
50    out_uvs: Vec<[f32; 2]>,
51    out_normals: Vec<Option<[f32; 3]>>,
52    indices: Vec<u32>,
53}
54
55impl Mesh {
56    /// A mesh read from the text of a Wavefront OBJ file.
57    ///
58    /// It reads the positions (`v`), texture coordinates (`vt`), normals
59    /// (`vn`) and faces (`f`, split into triangles) of every object in the
60    /// file, and skips the other statements, such as materials and groups.
61    /// OBJ counts the texture's v from the bottom; the mesh flips it, so a
62    /// model textured for an image shows a composable upright. A face
63    /// without texture coordinates takes `(0, 0)`, and one without normals
64    /// takes smooth ones.
65    pub fn from_obj(source: &str) -> Result<Self, ObjError> {
66        let mut builder = Builder::default();
67        for (number, line) in source.lines().enumerate() {
68            let line = line.split('#').next().unwrap_or_default();
69            let mut words = line.split_whitespace();
70            let fail = |kind| ObjError {
71                line: number + 1,
72                kind,
73            };
74            match words.next() {
75                Some("v") => {
76                    let [x, y, z] = numbers(&mut words).map_err(fail)?;
77                    builder.positions.push([x, y, z]);
78                }
79                Some("vt") => {
80                    let u = number_of(words.next()).map_err(fail)?;
81                    let v = words
82                        .next()
83                        .map_or(Ok(0.0), |word| number_of(Some(word)))
84                        .map_err(fail)?;
85                    builder.uvs.push([u, 1.0 - v]);
86                }
87                Some("vn") => {
88                    let [x, y, z] = numbers(&mut words).map_err(fail)?;
89                    builder.normals.push([x, y, z]);
90                }
91                Some("f") => builder.face(words).map_err(fail)?,
92                _ => {}
93            }
94        }
95        builder.finish()
96    }
97}
98
99impl Builder {
100    /// Splits a face into a fan of triangles around its first corner.
101    fn face<'a>(&mut self, words: impl Iterator<Item = &'a str>) -> Result<(), ObjErrorKind> {
102        let mut corners = 0usize;
103        let (mut first, mut previous) = (0, 0);
104        for word in words {
105            let corner = self.corner(word)?;
106            match corners {
107                0 => first = corner,
108                1 => {}
109                _ => self.indices.extend([first, previous, corner]),
110            }
111            previous = corner;
112            corners += 1;
113        }
114        if corners < 3 {
115            return Err(ObjErrorKind::TooFewValues);
116        }
117        Ok(())
118    }
119
120    fn corner(&mut self, word: &str) -> Result<u32, ObjErrorKind> {
121        let mut parts = word.split('/');
122        let position =
123            resolve(parts.next(), self.positions.len())?.ok_or(ObjErrorKind::TooFewValues)?;
124        let uv = resolve(parts.next(), self.uvs.len())?;
125        let normal = resolve(parts.next(), self.normals.len())?;
126        if let Some(&index) = self.corners.get(&(position, uv, normal)) {
127            return Ok(index);
128        }
129        let index = self.out_positions.len() as u32;
130        self.out_positions.push(self.positions[position]);
131        self.out_uvs.push(uv.map_or([0.0, 0.0], |uv| self.uvs[uv]));
132        self.out_normals
133            .push(normal.map(|normal| self.normals[normal]));
134        self.corners.insert((position, uv, normal), index);
135        Ok(index)
136    }
137
138    fn finish(self) -> Result<Mesh, ObjError> {
139        let fail = |error| ObjError {
140            line: 0,
141            kind: ObjErrorKind::Mesh(error),
142        };
143        if self.indices.is_empty() {
144            return Err(fail(MeshError::Empty));
145        }
146        let smooth = self
147            .out_normals
148            .iter()
149            .any(Option::is_none)
150            .then(|| smooth_normals(&self.out_positions, &self.indices));
151        let normals = self
152            .out_normals
153            .iter()
154            .enumerate()
155            .map(|(index, normal)| match (normal, &smooth) {
156                (Some(normal), _) => unit(*normal),
157                (None, Some(smooth)) => smooth[index],
158                (None, None) => [0.0, 0.0, -1.0],
159            })
160            .collect();
161        Mesh::new(
162            self.out_positions,
163            self.out_uvs,
164            Some(normals),
165            self.indices,
166        )
167        .map_err(fail)
168    }
169}
170
171fn unit(normal: [f32; 3]) -> [f32; 3] {
172    super::math::Vec3::from_array(normal)
173        .normalized()
174        .map_or([0.0, 0.0, -1.0], super::math::Vec3::to_array)
175}
176
177/// The zero-based index an OBJ index names among `count` given so far:
178/// counted from one, or back from the last when negative. `None` for an
179/// empty part, as in `1//3`.
180fn resolve(part: Option<&str>, count: usize) -> Result<Option<usize>, ObjErrorKind> {
181    let Some(part) = part.filter(|part| !part.is_empty()) else {
182        return Ok(None);
183    };
184    let index: i64 = part.parse().map_err(|_| ObjErrorKind::BadNumber)?;
185    let resolved = match index {
186        1.. => index - 1,
187        ..0 => count as i64 + index,
188        0 => return Err(ObjErrorKind::IndexOutOfRange),
189    };
190    usize::try_from(resolved)
191        .ok()
192        .filter(|&resolved| resolved < count)
193        .map(Some)
194        .ok_or(ObjErrorKind::IndexOutOfRange)
195}
196
197fn number_of(word: Option<&str>) -> Result<f32, ObjErrorKind> {
198    word.ok_or(ObjErrorKind::TooFewValues)?
199        .parse()
200        .map_err(|_| ObjErrorKind::BadNumber)
201}
202
203fn numbers<'a>(words: &mut impl Iterator<Item = &'a str>) -> Result<[f32; 3], ObjErrorKind> {
204    Ok([
205        number_of(words.next())?,
206        number_of(words.next())?,
207        number_of(words.next())?,
208    ])
209}