1use std::fs;
6use std::io::{self, BufRead, BufReader, Cursor};
7use std::path::Path;
8
9use draco_core::draco_types::DataType;
10use draco_core::geometry_attribute::{GeometryAttributeType, PointAttribute};
11use draco_core::mesh::Mesh;
12use std::collections::HashMap;
13
14use crate::traits::{PointCloudReader, ReadFromBytes, Reader};
15
16#[derive(Debug)]
20pub struct ObjReader {
21 source: ObjReaderSource,
22}
23
24#[derive(Debug, Clone)]
25enum ObjReaderSource {
26 Path(std::path::PathBuf),
27 Bytes(Vec<u8>),
28}
29
30impl ObjReader {
31 pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
33 let path = path.as_ref().to_path_buf();
34 if !path.exists() {
35 return Err(io::Error::new(
36 io::ErrorKind::NotFound,
37 format!("File not found: {}", path.display()),
38 ));
39 }
40 Ok(Self {
41 source: ObjReaderSource::Path(path),
42 })
43 }
44
45 pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
47 Self {
48 source: ObjReaderSource::Bytes(bytes.into()),
49 }
50 }
51
52 pub fn read_from_bytes(bytes: &[u8]) -> io::Result<Mesh> {
54 let mut reader = Self::from_bytes(bytes.to_vec());
55 reader.read_mesh()
56 }
57
58 pub fn read_positions(&mut self) -> io::Result<Vec<[f32; 3]>> {
60 match &self.source {
61 ObjReaderSource::Path(path) => read_obj_positions(path),
62 ObjReaderSource::Bytes(bytes) => {
63 read_obj_positions_from_reader(BufReader::new(Cursor::new(bytes.as_slice())))
64 }
65 }
66 }
67
68 fn read_mesh_data(&self) -> io::Result<ParsedObjMesh> {
70 match &self.source {
71 ObjReaderSource::Path(path) => {
72 let file = fs::File::open(path)?;
73 read_obj_mesh_from_reader(BufReader::new(file))
74 }
75 ObjReaderSource::Bytes(bytes) => {
76 read_obj_mesh_from_reader(BufReader::new(Cursor::new(bytes.as_slice())))
77 }
78 }
79 }
80
81 pub fn read_mesh(&mut self) -> io::Result<Mesh> {
87 let parsed = self.read_mesh_data()?;
88 let mut mesh = Mesh::new();
89
90 if parsed.positions.is_empty() {
91 return Ok(mesh);
92 }
93
94 mesh.set_num_points(parsed.positions.len());
95 mesh.set_num_faces(parsed.faces.len());
96
97 mesh.add_attribute(make_f32x3_attribute(
98 GeometryAttributeType::Position,
99 &parsed.positions,
100 ));
101
102 if let Some(normals) = parsed.normals.as_ref() {
103 mesh.add_attribute(make_f32x3_attribute(GeometryAttributeType::Normal, normals));
104 }
105
106 if let Some(texcoords) = parsed.texcoords.as_ref() {
107 mesh.add_attribute(make_f32x2_attribute(
108 GeometryAttributeType::TexCoord,
109 texcoords,
110 ));
111 }
112
113 use draco_core::geometry_indices::{FaceIndex, PointIndex};
115 for (i, face) in parsed.faces.iter().enumerate() {
116 mesh.set_face(
117 FaceIndex(i as u32),
118 [
119 PointIndex(face[0]),
120 PointIndex(face[1]),
121 PointIndex(face[2]),
122 ],
123 );
124 }
125
126 mesh.deduplicate_point_ids();
130
131 Ok(mesh)
132 }
133}
134
135impl Reader for ObjReader {
136 fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
137 ObjReader::open(path)
138 }
139
140 fn read_meshes(&mut self) -> io::Result<Vec<Mesh>> {
141 let m = self.read_mesh()?;
142 Ok(vec![m])
143 }
144}
145
146impl ReadFromBytes for ObjReader {
147 fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
148 Ok(Self::from_bytes(bytes.to_vec()))
149 }
150}
151
152impl PointCloudReader for ObjReader {
153 fn read_points(&mut self) -> io::Result<Vec<[f32; 3]>> {
154 self.read_positions()
155 }
156}
157
158pub fn read_obj_positions<P: AsRef<Path>>(path: P) -> io::Result<Vec<[f32; 3]>> {
165 let file = fs::File::open(path)?;
166 let reader = BufReader::new(file);
167 read_obj_positions_from_reader(reader)
168}
169
170fn read_obj_positions_from_reader<R: BufRead>(reader: R) -> io::Result<Vec<[f32; 3]>> {
171 let mut positions = Vec::new();
172
173 for line in reader.lines() {
174 let line = line?;
175 let trimmed = strip_obj_comment(&line);
176
177 if trimmed.starts_with("vn ") || trimmed.starts_with("vt ") {
178 continue;
179 }
180
181 if !trimmed.starts_with('v') {
182 continue;
183 }
184
185 let mut parts = trimmed.split_whitespace();
186 if parts.next() != Some("v") {
187 continue;
188 }
189
190 let x = parts.next().and_then(|s| s.parse().ok());
191 let y = parts.next().and_then(|s| s.parse().ok());
192 let z = parts.next().and_then(|s| s.parse().ok());
193
194 if let (Some(x), Some(y), Some(z)) = (x, y, z) {
195 positions.push([x, y, z]);
196 }
197 }
198
199 Ok(positions)
200}
201
202#[derive(Debug)]
203struct ParsedObjMesh {
204 positions: Vec<[f32; 3]>,
205 texcoords: Option<Vec<[f32; 2]>>,
206 normals: Option<Vec<[f32; 3]>>,
207 faces: Vec<[u32; 3]>,
208}
209
210#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
211struct ObjVertexRef {
212 position: usize,
213 texcoord: Option<usize>,
214 normal: Option<usize>,
215}
216
217fn strip_obj_comment(line: &str) -> &str {
218 line.split('#').next().unwrap_or("").trim()
219}
220
221fn make_f32x3_attribute(
222 attribute_type: GeometryAttributeType,
223 values: &[[f32; 3]],
224) -> PointAttribute {
225 let mut attribute = PointAttribute::new();
226 attribute.init(attribute_type, 3, DataType::Float32, false, values.len());
227
228 let buffer = attribute.buffer_mut();
229 for (i, value) in values.iter().enumerate() {
230 let bytes: Vec<u8> = value
231 .iter()
232 .flat_map(|component| component.to_le_bytes())
233 .collect();
234 buffer.write(i * 12, &bytes);
235 }
236
237 attribute
238}
239
240fn make_f32x2_attribute(
241 attribute_type: GeometryAttributeType,
242 values: &[[f32; 2]],
243) -> PointAttribute {
244 let mut attribute = PointAttribute::new();
245 attribute.init(attribute_type, 2, DataType::Float32, false, values.len());
246
247 let buffer = attribute.buffer_mut();
248 for (i, value) in values.iter().enumerate() {
249 let bytes: Vec<u8> = value
250 .iter()
251 .flat_map(|component| component.to_le_bytes())
252 .collect();
253 buffer.write(i * 8, &bytes);
254 }
255
256 attribute
257}
258
259fn parse_obj_index(token: &str, len: usize, label: &str) -> io::Result<usize> {
260 let raw = token
261 .parse::<i32>()
262 .map_err(|_| invalid_obj(format!("Bad {label} index: {token}")))?;
263 if raw == 0 {
264 return Err(invalid_obj(format!("OBJ {label} indices are 1-based")));
265 }
266
267 let index = if raw > 0 { raw - 1 } else { len as i32 + raw };
268
269 if index < 0 || index as usize >= len {
270 return Err(invalid_obj(format!(
271 "OBJ {label} index {raw} is out of range for {len} values"
272 )));
273 }
274
275 Ok(index as usize)
276}
277
278fn parse_face_vertex(
279 token: &str,
280 position_count: usize,
281 texcoord_count: usize,
282 normal_count: usize,
283) -> io::Result<ObjVertexRef> {
284 let parts: Vec<&str> = token.split('/').collect();
285 if parts.is_empty() || parts.len() > 3 || parts[0].is_empty() {
286 return Err(invalid_obj(format!("Bad OBJ face vertex: {token}")));
287 }
288
289 let position = parse_obj_index(parts[0], position_count, "position")?;
290 let texcoord = if parts.get(1).is_some_and(|part| !part.is_empty()) {
291 Some(parse_obj_index(parts[1], texcoord_count, "texcoord")?)
292 } else {
293 None
294 };
295 let normal = if parts.get(2).is_some_and(|part| !part.is_empty()) {
296 Some(parse_obj_index(parts[2], normal_count, "normal")?)
297 } else {
298 None
299 };
300
301 Ok(ObjVertexRef {
302 position,
303 texcoord,
304 normal,
305 })
306}
307
308fn push_obj_vertex(
309 vertex_ref: ObjVertexRef,
310 vertex_map: &mut HashMap<ObjVertexRef, u32>,
311 vertices: &mut Vec<ObjVertexRef>,
312) -> u32 {
313 if let Some(&point_id) = vertex_map.get(&vertex_ref) {
314 return point_id;
315 }
316
317 let point_id = vertices.len() as u32;
318 vertices.push(vertex_ref);
319 vertex_map.insert(vertex_ref, point_id);
320 point_id
321}
322
323fn invalid_obj(message: impl Into<String>) -> io::Error {
324 io::Error::new(io::ErrorKind::InvalidData, message.into())
325}
326
327fn read_obj_mesh_from_reader<R: BufRead>(reader: R) -> io::Result<ParsedObjMesh> {
328 let mut source_positions = Vec::new();
329 let mut source_texcoords = Vec::new();
330 let mut source_normals = Vec::new();
331 let mut faces = Vec::new();
332 let mut vertices = Vec::new();
333 let mut vertex_map = HashMap::new();
334
335 for line in reader.lines() {
336 let line = line?;
337 let trimmed = strip_obj_comment(&line);
338
339 if trimmed.starts_with("v ") {
340 let mut parts = trimmed.split_whitespace();
341 parts.next(); let x = parts.next().and_then(|s| s.parse().ok());
344 let y = parts.next().and_then(|s| s.parse().ok());
345 let z = parts.next().and_then(|s| s.parse().ok());
346
347 if let (Some(x), Some(y), Some(z)) = (x, y, z) {
348 source_positions.push([x, y, z]);
349 }
350 } else if trimmed.starts_with("vt ") {
351 let mut parts = trimmed.split_whitespace();
352 parts.next(); let u = parts.next().and_then(|s| s.parse().ok());
355 let v = parts.next().and_then(|s| s.parse().ok());
356
357 if let (Some(u), Some(v)) = (u, v) {
358 source_texcoords.push([u, v]);
359 }
360 } else if trimmed.starts_with("vn ") {
361 let mut parts = trimmed.split_whitespace();
362 parts.next(); let x = parts.next().and_then(|s| s.parse().ok());
365 let y = parts.next().and_then(|s| s.parse().ok());
366 let z = parts.next().and_then(|s| s.parse().ok());
367
368 if let (Some(x), Some(y), Some(z)) = (x, y, z) {
369 source_normals.push([x, y, z]);
370 }
371 } else if trimmed.starts_with("f ") {
372 let mut parts = trimmed.split_whitespace();
373 parts.next(); let face_vertices = parts
376 .map(|part| {
377 parse_face_vertex(
378 part,
379 source_positions.len(),
380 source_texcoords.len(),
381 source_normals.len(),
382 )
383 })
384 .collect::<io::Result<Vec<_>>>()?;
385 if face_vertices.len() < 3 {
386 continue;
387 }
388
389 for i in 1..face_vertices.len() - 1 {
390 let triangle = [face_vertices[0], face_vertices[i], face_vertices[i + 1]];
391 faces.push([
392 push_obj_vertex(triangle[0], &mut vertex_map, &mut vertices),
393 push_obj_vertex(triangle[1], &mut vertex_map, &mut vertices),
394 push_obj_vertex(triangle[2], &mut vertex_map, &mut vertices),
395 ]);
396 }
397 }
398 }
399
400 if faces.is_empty() {
401 return Ok(ParsedObjMesh {
402 positions: source_positions,
403 texcoords: None,
404 normals: None,
405 faces,
406 });
407 }
408
409 let uses_texcoords = vertices.iter().any(|vertex| vertex.texcoord.is_some());
410 let uses_normals = vertices.iter().any(|vertex| vertex.normal.is_some());
411 if uses_texcoords && vertices.iter().any(|vertex| vertex.texcoord.is_none()) {
412 return Err(invalid_obj(
413 "OBJ texture coordinate indices must be present on every face vertex",
414 ));
415 }
416 if uses_normals && vertices.iter().any(|vertex| vertex.normal.is_none()) {
417 return Err(invalid_obj(
418 "OBJ normal indices must be present on every face vertex",
419 ));
420 }
421
422 let positions = vertices
423 .iter()
424 .map(|vertex| source_positions[vertex.position])
425 .collect();
426 let texcoords = uses_texcoords.then(|| {
427 vertices
428 .iter()
429 .map(|vertex| source_texcoords[vertex.texcoord.unwrap()])
430 .collect()
431 });
432 let normals = uses_normals.then(|| {
433 vertices
434 .iter()
435 .map(|vertex| source_normals[vertex.normal.unwrap()])
436 .collect()
437 });
438
439 Ok(ParsedObjMesh {
440 positions,
441 texcoords,
442 normals,
443 faces,
444 })
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450 use std::io::Write;
451 use tempfile::NamedTempFile;
452
453 #[test]
454 fn test_read_obj_positions() {
455 let mut file = NamedTempFile::new().unwrap();
456 writeln!(file, "# comment").unwrap();
457 writeln!(file, "v 1.0 2.0 3.0").unwrap();
458 writeln!(file, "v 4.5 5.5 6.5").unwrap();
459 writeln!(file, "vn 0 1 0").unwrap();
460 writeln!(file, "vt 0.5 0.5").unwrap();
461 writeln!(file, "v -1.0 -2.0 -3.0").unwrap();
462 file.flush().unwrap();
463
464 let positions = read_obj_positions(file.path()).unwrap();
465 assert_eq!(positions.len(), 3);
466 assert_eq!(positions[0], [1.0, 2.0, 3.0]);
467 assert_eq!(positions[1], [4.5, 5.5, 6.5]);
468 assert_eq!(positions[2], [-1.0, -2.0, -3.0]);
469 }
470
471 #[test]
472 fn test_read_obj_mesh_preserves_normals_and_texcoords() {
473 let obj = br#"
474v 0.0 0.0 0.0
475v 1.0 0.0 0.0
476v 1.0 1.0 0.0
477v 0.0 1.0 0.0
478vt 0.0 0.0
479vt 1.0 0.0
480vt 1.0 1.0
481vt 0.0 1.0
482vn 0.0 0.0 1.0
483f 1/1/1 2/2/1 3/3/1 4/4/1
484"#;
485
486 let mut reader = ObjReader::from_bytes(obj.as_slice());
487 let mesh = reader.read_mesh().unwrap();
488
489 assert_eq!(mesh.num_points(), 4);
490 assert_eq!(mesh.num_faces(), 2);
491
492 let normal_att = mesh.named_attribute(GeometryAttributeType::Normal).unwrap();
493 assert_eq!(normal_att.num_components(), 3);
494 assert_eq!(normal_att.data_type(), DataType::Float32);
495
496 let texcoord_att = mesh
497 .named_attribute(GeometryAttributeType::TexCoord)
498 .unwrap();
499 assert_eq!(texcoord_att.num_components(), 2);
500 assert_eq!(texcoord_att.data_type(), DataType::Float32);
501 assert_eq!(texcoord_att.buffer().data().len(), 4 * 2 * 4);
502 }
503}