1use crate::{Color, Mesh, Vec3, Vertex, vec3};
2
3#[derive(Clone, Copy, Debug)]
4pub struct ObjLimits {
5 pub max_bytes: usize,
6 pub max_positions: usize,
7 pub max_faces: usize,
8 pub max_face_vertices: usize,
9 pub max_output_vertices: usize,
10}
11impl Default for ObjLimits {
12 fn default() -> Self {
13 Self {
14 max_bytes: 8 * 1024 * 1024,
15 max_positions: 100_000,
16 max_faces: 100_000,
17 max_face_vertices: 32,
18 max_output_vertices: 750_000,
19 }
20 }
21}
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum ObjErrorKind {
24 ResourceLimit,
25 InvalidRecord,
26 InvalidNumber,
27 InvalidIndex,
28 DegenerateFace,
29 NonPlanarFace,
30 ConcaveFace,
31 Empty,
32}
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct ObjError {
35 pub line: usize,
36 pub kind: ObjErrorKind,
37}
38impl std::fmt::Display for ObjError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 write!(f, "OBJ line {}: {:?}", self.line, self.kind)
41 }
42}
43impl std::error::Error for ObjError {}
44impl Mesh {
45 pub fn from_obj(source: &str, color: Color) -> Result<Self, ObjError> {
48 Self::from_obj_with_limits(source, color, ObjLimits::default())
49 }
50 pub fn from_obj_with_limits(
51 source: &str,
52 color: Color,
53 limits: ObjLimits,
54 ) -> Result<Self, ObjError> {
55 if source.len() > limits.max_bytes || !color.to_vec().is_finite() {
56 return Err(ObjError {
57 line: 0,
58 kind: ObjErrorKind::ResourceLimit,
59 });
60 }
61 let mut positions = Vec::new();
62 let mut normals = 0;
63 let mut uvs = 0;
64 let mut faces = 0;
65 let mut output = Self {
66 vertices: Vec::new(),
67 indices: Vec::new(),
68 };
69 for (number, line) in source.lines().enumerate() {
70 let number = number + 1;
71 let line = line.split('#').next().unwrap_or("");
72 let mut words = line.split_whitespace();
73 let Some(record) = words.next() else {
74 continue;
75 };
76 let fail = |kind| ObjError { line: number, kind };
77 match record {
78 "v" => {
79 if positions.len() >= limits.max_positions {
80 return Err(fail(ObjErrorKind::ResourceLimit));
81 }
82 let values = parse_values(words, 3, 3).map_err(fail)?;
83 positions.push(vec3(values[0], values[1], values[2]));
84 }
85 "vn" => {
86 parse_values(words, 3, 3).map_err(fail)?;
87 normals += 1;
88 if normals > limits.max_positions {
89 return Err(fail(ObjErrorKind::ResourceLimit));
90 }
91 }
92 "vt" => {
93 parse_values(words, 1, 3).map_err(fail)?;
94 uvs += 1;
95 if uvs > limits.max_positions {
96 return Err(fail(ObjErrorKind::ResourceLimit));
97 }
98 }
99 "f" => {
100 faces += 1;
101 if faces > limits.max_faces {
102 return Err(fail(ObjErrorKind::ResourceLimit));
103 }
104 let mut points = Vec::new();
105 for word in words {
106 if points.len() >= limits.max_face_vertices.min(1024) {
107 return Err(fail(ObjErrorKind::ResourceLimit));
108 }
109 let mut fields = word.split('/');
110 let position = parse_index(fields.next().unwrap_or(""), positions.len())
111 .map_err(fail)?;
112 if let Some(uv) = fields.next() {
113 if !uv.is_empty() {
114 parse_index(uv, uvs).map_err(fail)?;
115 }
116 if let Some(normal) = fields.next() {
117 if normal.is_empty() {
118 return Err(fail(ObjErrorKind::InvalidIndex));
119 }
120 parse_index(normal, normals).map_err(fail)?;
121 } else if uv.is_empty() {
122 return Err(fail(ObjErrorKind::InvalidIndex));
123 }
124 }
125 if fields.next().is_some() {
126 return Err(fail(ObjErrorKind::InvalidIndex));
127 }
128 points.push(positions[position]);
129 }
130 if points.len() < 3 {
131 return Err(fail(ObjErrorKind::InvalidRecord));
132 }
133 let required = (points.len() - 2) * 3;
134 if output.vertices.len().saturating_add(required)
135 > limits.max_output_vertices.min(1_000_000)
136 {
137 return Err(fail(ObjErrorKind::ResourceLimit));
138 }
139 let normal = validate_polygon(&points).map_err(fail)?;
140 for i in 1..points.len() - 1 {
141 let a = points[0];
142 let b = points[i];
143 let c = points[i + 1];
144 if (b - a).cross(c - a).length_squared() <= 1.0e-20 {
145 return Err(fail(ObjErrorKind::DegenerateFace));
146 }
147 let start = output.vertices.len() as u32;
148 output.vertices.extend([
149 Vertex::new(a, normal, color),
150 Vertex::new(b, normal, color),
151 Vertex::new(c, normal, color),
152 ]);
153 output.indices.extend([start, start + 1, start + 2]);
154 }
155 }
156 "o" | "g" | "s" | "usemtl" | "mtllib" => {}
158 _ => return Err(fail(ObjErrorKind::InvalidRecord)),
159 }
160 }
161 if output.vertices.is_empty() {
162 return Err(ObjError {
163 line: 0,
164 kind: ObjErrorKind::Empty,
165 });
166 }
167 Ok(output)
168 }
169}
170fn parse_values<'a>(
171 words: impl Iterator<Item = &'a str>,
172 min: usize,
173 max: usize,
174) -> Result<Vec<f32>, ObjErrorKind> {
175 let mut result = Vec::with_capacity(max);
176 for word in words {
177 if result.len() >= max {
178 return Err(ObjErrorKind::InvalidRecord);
179 }
180 let value = word
181 .parse::<f32>()
182 .map_err(|_| ObjErrorKind::InvalidNumber)?;
183 if !value.is_finite() {
184 return Err(ObjErrorKind::InvalidNumber);
185 }
186 result.push(value);
187 }
188 if result.len() < min {
189 return Err(ObjErrorKind::InvalidRecord);
190 }
191 Ok(result)
192}
193fn parse_index(word: &str, count: usize) -> Result<usize, ObjErrorKind> {
194 let raw = word
195 .parse::<i64>()
196 .map_err(|_| ObjErrorKind::InvalidIndex)?;
197 let index = if raw > 0 {
198 raw - 1
199 } else if raw < 0 {
200 (count as i64)
201 .checked_add(raw)
202 .ok_or(ObjErrorKind::InvalidIndex)?
203 } else {
204 return Err(ObjErrorKind::InvalidIndex);
205 };
206 if index < 0 || index as usize >= count {
207 Err(ObjErrorKind::InvalidIndex)
208 } else {
209 Ok(index as usize)
210 }
211}
212fn validate_polygon(points: &[Vec3]) -> Result<Vec3, ObjErrorKind> {
213 let origin = points[0];
214 let normal = (1..points.len() - 1)
215 .find_map(|i| {
216 (points[i] - origin)
217 .cross(points[i + 1] - origin)
218 .try_normalize()
219 })
220 .ok_or(ObjErrorKind::DegenerateFace)?;
221 let scale = points
222 .iter()
223 .map(|p| p.distance(origin))
224 .fold(1.0_f32, f32::max);
225 let epsilon = scale * 1.0e-5;
226 if !scale.is_finite() {
227 return Err(ObjErrorKind::InvalidNumber);
228 }
229 if points
230 .iter()
231 .any(|&p| (p - origin).dot(normal).abs() > epsilon)
232 {
233 return Err(ObjErrorKind::NonPlanarFace);
234 }
235 for i in 0..points.len() {
236 let a = points[i];
237 let b = points[(i + 1) % points.len()];
238 if points
241 .iter()
242 .any(|&p| (b - a).cross(p - a).dot(normal) < -epsilon * epsilon)
243 {
244 return Err(ObjErrorKind::ConcaveFace);
245 }
246 }
247 Ok(normal)
248}