Skip to main content

cranpose_ui_graphics/space/
obj.rs

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