1use std::error::Error as StdError;
69use std::fmt;
70use std::fmt::Display;
71use std::io;
72use std::io::{BufRead, BufReader};
73use std::str::FromStr;
74
75#[derive(Debug)]
80pub enum ObjError {
81 Io(io::Error),
82 Parse { line: usize, kind: ParseErrorKind },
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum ParseErrorKind {
88 EmptyPrefix,
90 UnknownPrefix(String),
93 MissingField(&'static str),
95 InvalidNumber { field: &'static str, value: String },
97 InvalidIndex { kind: &'static str, value: String },
99 InvalidSmoothingGroup(String),
101 LineElementTooShort,
103 PointElementEmpty,
105 Custom(String),
107}
108
109impl Display for ObjError {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 ObjError::Io(e) => write!(f, "I/O error: {e}"),
113 ObjError::Parse { line, kind } => write!(f, "line {line}: {kind}"),
114 }
115 }
116}
117
118impl Display for ParseErrorKind {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 match self {
121 ParseErrorKind::EmptyPrefix => write!(f, "empty prefix"),
122 ParseErrorKind::UnknownPrefix(p) => write!(f, "Unknown line prefix: {p}"),
123 ParseErrorKind::MissingField(field) => write!(f, "missing {field}"),
124 ParseErrorKind::InvalidNumber { field, value } => {
125 write!(f, "invalid {field}: {value}")
126 }
127 ParseErrorKind::InvalidIndex { kind, value } => {
128 write!(f, "invalid {kind} index: {value}")
129 }
130 ParseErrorKind::InvalidSmoothingGroup(v) => {
131 write!(
132 f,
133 "invalid smoothing group: {v} (expected integer or 'off')"
134 )
135 }
136 ParseErrorKind::LineElementTooShort => {
137 write!(f, "line element needs at least 2 vertices")
138 }
139 ParseErrorKind::PointElementEmpty => {
140 write!(f, "point element needs at least 1 vertex")
141 }
142 ParseErrorKind::Custom(s) => write!(f, "{s}"),
143 }
144 }
145}
146
147impl StdError for ObjError {
148 fn source(&self) -> Option<&(dyn StdError + 'static)> {
149 match self {
150 ObjError::Io(e) => Some(e),
151 ObjError::Parse { .. } => None,
152 }
153 }
154}
155
156impl From<io::Error> for ObjError {
157 fn from(e: io::Error) -> Self {
158 ObjError::Io(e)
159 }
160}
161
162impl From<ObjError> for io::Error {
163 fn from(e: ObjError) -> Self {
164 match e {
165 ObjError::Io(inner) => inner,
166 parse @ ObjError::Parse { .. } => {
167 io::Error::new(io::ErrorKind::InvalidData, parse.to_string())
168 }
169 }
170 }
171}
172
173pub trait ObjFloat: Copy + Display + FromStr + PartialEq {
176 fn fract(self) -> Self;
178
179 fn is_zero_fract(self) -> bool {
181 self.fract() == Self::zero()
182 }
183
184 fn zero() -> Self;
186}
187
188impl ObjFloat for f32 {
189 fn fract(self) -> Self {
190 self.fract()
191 }
192 fn zero() -> Self {
193 0.0
194 }
195}
196
197impl ObjFloat for f64 {
198 fn fract(self) -> Self {
199 self.fract()
200 }
201 fn zero() -> Self {
202 0.0
203 }
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum SmoothingGroup {
212 Off,
213 Group(u32),
214}
215
216pub trait ObjWriter<F: ObjFloat = f64> {
221 fn write_comment<S: AsRef<str>>(&mut self, comment: S) -> io::Result<()>;
222 fn write_object_name<S: AsRef<str>>(&mut self, name: S) -> io::Result<()>;
223 fn write_vertex(&mut self, x: F, y: F, z: F, w: Option<F>) -> io::Result<()>;
224 fn write_texture_coordinate(&mut self, u: F, v: Option<F>, w: Option<F>) -> io::Result<()>;
225 fn write_normal(&mut self, nx: F, ny: F, nz: F) -> io::Result<()>;
226 fn write_face(
227 &mut self,
228 vertex_indices: &[(usize, Option<usize>, Option<usize>)],
229 ) -> io::Result<()>;
230
231 fn write_material_lib<S: AsRef<str>>(&mut self, names: &[S]) -> io::Result<()>;
233
234 fn write_use_material<S: AsRef<str>>(&mut self, name: S) -> io::Result<()>;
236
237 fn write_group<S: AsRef<str>>(&mut self, names: &[S]) -> io::Result<()>;
239
240 fn write_smoothing_group(&mut self, group: SmoothingGroup) -> io::Result<()>;
242
243 fn write_line_element(&mut self, indices: &[(usize, Option<usize>)]) -> io::Result<()>;
246
247 fn write_point_element(&mut self, indices: &[usize]) -> io::Result<()>;
249}
250
251pub trait ObjReader<F: ObjFloat = f64> {
256 fn read_comment(&mut self, comment: &str) -> ();
257 fn read_object_name(&mut self, name: &str) -> ();
258 fn read_vertex(&mut self, x: F, y: F, z: F, w: Option<F>) -> ();
259 fn read_texture_coordinate(&mut self, u: F, v: Option<F>, w: Option<F>) -> ();
260 fn read_normal(&mut self, nx: F, ny: F, nz: F) -> ();
261 fn read_face(&mut self, vertex_indices: &[(usize, Option<usize>, Option<usize>)]) -> ();
262
263 fn read_material_lib(&mut self, _names: &[&str]) {}
265
266 fn read_use_material(&mut self, _name: &str) {}
268
269 fn read_group(&mut self, _names: &[&str]) {}
271
272 fn read_smoothing_group(&mut self, _group: SmoothingGroup) {}
274
275 fn read_line_element(&mut self, _indices: &[(usize, Option<usize>)]) {}
277
278 fn read_point_element(&mut self, _indices: &[usize]) {}
280
281 fn read_unknown(&mut self, prefix: &str, _rest: &str, line: usize) -> Result<(), ObjError> {
288 Err(ObjError::Parse {
289 line,
290 kind: ParseErrorKind::UnknownPrefix(prefix.to_string()),
291 })
292 }
293}
294
295pub fn read_obj_file<R: io::Read, T: ObjReader<F>, F: ObjFloat>(
296 reader: R,
297 obj_reader: &mut T,
298) -> Result<(), ObjError>
299where
300 <F as FromStr>::Err: Display,
301{
302 let mut buf_reader = BufReader::new(reader);
303 let mut line = String::new();
304 let mut lineno: usize = 0;
305
306 while buf_reader.read_line(&mut line)? != 0 {
307 lineno += 1;
308 let trimmed = line.trim();
309 if trimmed.is_empty() {
310 line.clear();
311 continue;
312 }
313 let mut parts = trimmed.split_whitespace();
314 let prefix = parts.next().ok_or(ObjError::Parse {
315 line: lineno,
316 kind: ParseErrorKind::EmptyPrefix,
317 })?;
318
319 let parse_f = |s: &str, field: &'static str| -> Result<F, ObjError> {
320 s.parse::<F>().map_err(|_| ObjError::Parse {
321 line: lineno,
322 kind: ParseErrorKind::InvalidNumber {
323 field,
324 value: s.to_string(),
325 },
326 })
327 };
328
329 let parse_index = |s: &str, kind: &'static str| -> Result<usize, ObjError> {
330 let index = s.parse::<usize>().map_err(|_| ObjError::Parse {
331 line: lineno,
332 kind: ParseErrorKind::InvalidIndex {
333 kind,
334 value: s.to_string(),
335 },
336 })?;
337 if index == 0 {
338 return Err(ObjError::Parse {
339 line: lineno,
340 kind: ParseErrorKind::InvalidIndex {
341 kind,
342 value: s.to_string(),
343 },
344 });
345 }
346 Ok(index)
347 };
348
349 let missing = |field: &'static str| -> ObjError {
350 ObjError::Parse {
351 line: lineno,
352 kind: ParseErrorKind::MissingField(field),
353 }
354 };
355
356 match prefix {
357 "#" => {
358 let comment = parts.collect::<Vec<&str>>().join(" ");
359 obj_reader.read_comment(&comment);
360 }
361 "v" => {
362 let x = parts
363 .next()
364 .ok_or_else(|| missing("vertex x"))
365 .and_then(|s| parse_f(s, "vertex x"))?;
366 let y = parts
367 .next()
368 .ok_or_else(|| missing("vertex y"))
369 .and_then(|s| parse_f(s, "vertex y"))?;
370 let z = parts
371 .next()
372 .ok_or_else(|| missing("vertex z"))
373 .and_then(|s| parse_f(s, "vertex z"))?;
374 let w = match parts.next() {
375 Some(s) => Some(parse_f(s, "vertex w")?),
376 None => None,
377 };
378 obj_reader.read_vertex(x, y, z, w);
379 }
380 "vt" => {
381 let u = parts
382 .next()
383 .ok_or_else(|| missing("texture u"))
384 .and_then(|s| parse_f(s, "texture u"))?;
385 let v = match parts.next() {
386 Some(s) => Some(parse_f(s, "texture v")?),
387 None => None,
388 };
389 let w = match parts.next() {
390 Some(s) => Some(parse_f(s, "texture w")?),
391 None => None,
392 };
393 obj_reader.read_texture_coordinate(u, v, w);
394 }
395 "vn" => {
396 let nx = parts
397 .next()
398 .ok_or_else(|| missing("normal nx"))
399 .and_then(|s| parse_f(s, "normal nx"))?;
400 let ny = parts
401 .next()
402 .ok_or_else(|| missing("normal ny"))
403 .and_then(|s| parse_f(s, "normal ny"))?;
404 let nz = parts
405 .next()
406 .ok_or_else(|| missing("normal nz"))
407 .and_then(|s| parse_f(s, "normal nz"))?;
408 obj_reader.read_normal(nx, ny, nz);
409 }
410 "f" => {
411 let mut vertex_indices = Vec::new();
412 for part in parts {
413 let first_slash = part.find('/');
415 let (v_str, rest) = match first_slash {
416 Some(i) => (&part[..i], &part[i + 1..]),
417 None => (part, ""),
418 };
419
420 let v_idx = parse_index(v_str, "vertex")?;
421
422 let (vt_idx, vn_idx) = if rest.is_empty() {
423 (None, None)
424 } else {
425 let second_slash = rest.find('/');
426 if let Some(j) = second_slash {
427 let vt_part = &rest[..j];
428 let vn_part = &rest[j + 1..];
429 let vt = if vt_part.is_empty() {
430 None
431 } else {
432 Some(parse_index(vt_part, "texcoord")?)
433 };
434 let vn = if vn_part.is_empty() {
435 None
436 } else {
437 Some(parse_index(vn_part, "normal")?)
438 };
439 (vt, vn)
440 } else {
441 let vt = if rest.is_empty() {
443 None
444 } else {
445 Some(parse_index(rest, "texcoord")?)
446 };
447 (vt, None)
448 }
449 };
450
451 vertex_indices.push((v_idx, vt_idx, vn_idx));
452 }
453 obj_reader.read_face(&vertex_indices);
454 }
455 "o" => {
456 let name = parts.collect::<Vec<&str>>().join(" ");
457 obj_reader.read_object_name(&name);
458 }
459 "mtllib" => {
460 let names: Vec<&str> = parts.collect();
461 obj_reader.read_material_lib(&names);
462 }
463 "usemtl" => {
464 let name = parts.collect::<Vec<&str>>().join(" ");
467 obj_reader.read_use_material(&name);
468 }
469 "g" => {
470 let names: Vec<&str> = parts.collect();
471 obj_reader.read_group(&names);
472 }
473 "s" => {
474 let value = parts
475 .next()
476 .ok_or_else(|| missing("smoothing group value"))?;
477 let group = if value.eq_ignore_ascii_case("off") {
478 SmoothingGroup::Off
479 } else {
480 let n = value.parse::<u32>().map_err(|_| ObjError::Parse {
481 line: lineno,
482 kind: ParseErrorKind::InvalidSmoothingGroup(value.to_string()),
483 })?;
484 if n == 0 {
485 SmoothingGroup::Off
486 } else {
487 SmoothingGroup::Group(n)
488 }
489 };
490 obj_reader.read_smoothing_group(group);
491 }
492 "l" => {
493 let mut indices: Vec<(usize, Option<usize>)> = Vec::new();
494 for part in parts {
495 let (v_str, vt_str) = match part.find('/') {
496 Some(i) => (&part[..i], Some(&part[i + 1..])),
497 None => (part, None),
498 };
499 let v_idx = parse_index(v_str, "vertex")?;
500 let vt_idx = match vt_str {
501 Some(s) if !s.is_empty() => Some(parse_index(s, "texcoord")?),
502 _ => None,
503 };
504 indices.push((v_idx, vt_idx));
505 }
506 if indices.len() < 2 {
507 return Err(ObjError::Parse {
508 line: lineno,
509 kind: ParseErrorKind::LineElementTooShort,
510 });
511 }
512 obj_reader.read_line_element(&indices);
513 }
514 "p" => {
515 let mut indices: Vec<usize> = Vec::new();
516 for part in parts {
517 indices.push(parse_index(part, "vertex")?);
518 }
519 if indices.is_empty() {
520 return Err(ObjError::Parse {
521 line: lineno,
522 kind: ParseErrorKind::PointElementEmpty,
523 });
524 }
525 obj_reader.read_point_element(&indices);
526 }
527 other => {
528 let rest = parts.collect::<Vec<&str>>().join(" ");
529 obj_reader.read_unknown(other, &rest, lineno)?;
530 }
531 }
532
533 line.clear();
534 }
535
536 Ok(())
537}
538
539pub struct IoObjWriter<W: io::Write, F: ObjFloat = f64> {
540 out: W,
541 line_buf: Vec<u8>,
542 printf_f_format: bool,
544 _phantom: std::marker::PhantomData<F>,
545}
546impl<W: io::Write, F: ObjFloat> IoObjWriter<W, F> {
547 pub fn new(writer: W) -> Self {
549 IoObjWriter {
550 out: writer,
551 line_buf: Vec::with_capacity(256),
552 printf_f_format: false,
553 _phantom: std::marker::PhantomData,
554 }
555 }
556
557 #[cfg(test)]
560 pub fn new_with_printf_f_format(writer: W) -> Self {
561 IoObjWriter {
562 out: writer,
563 line_buf: Vec::with_capacity(256),
564 printf_f_format: true,
565 _phantom: std::marker::PhantomData,
566 }
567 }
568
569 #[cfg(test)]
571 pub fn set_printf_f_format(&mut self, enabled: bool) {
572 self.printf_f_format = enabled;
573 }
574
575 #[inline]
576 fn push_str(&mut self, s: &str) {
577 self.line_buf.extend_from_slice(s.as_bytes());
578 }
579
580 #[inline]
581 fn push_u<T: itoa::Integer>(&mut self, v: T) {
582 let mut buf = itoa::Buffer::new();
583 self.push_str(buf.format(v));
584 }
585
586 #[inline]
587 fn push_f(&mut self, v: F) {
588 if v.is_zero_fract() {
590 self.push_str(&format!("{}", v));
591 return;
592 }
593 if self.printf_f_format {
596 self.push_str(&format!("{:.6}", v));
597 } else {
598 self.push_str(&format!("{}", v));
599 }
600 }
601
602 #[inline]
603 fn flush_line(&mut self) -> io::Result<()> {
604 self.line_buf.push(b'\n');
605 self.out.write_all(&self.line_buf)?;
606 self.line_buf.clear();
607 Ok(())
608 }
609}
610impl<W: io::Write, F: ObjFloat> ObjWriter<F> for IoObjWriter<W, F> {
611 fn write_comment<S: AsRef<str>>(&mut self, comment: S) -> io::Result<()> {
612 self.push_str("# ");
613 self.push_str(comment.as_ref());
614 self.flush_line()
615 }
616
617 fn write_object_name<S: AsRef<str>>(&mut self, name: S) -> io::Result<()> {
618 self.push_str("o ");
619 self.push_str(name.as_ref());
620 self.flush_line()
621 }
622
623 fn write_vertex(&mut self, x: F, y: F, z: F, w: Option<F>) -> io::Result<()> {
624 self.push_str("v ");
625 self.push_f(x);
626 self.push_str(" ");
627 self.push_f(y);
628 self.push_str(" ");
629 self.push_f(z);
630 if let Some(wv) = w {
631 self.push_str(" ");
632 self.push_f(wv);
633 }
634 self.flush_line()
635 }
636
637 fn write_texture_coordinate(&mut self, u: F, v: Option<F>, w: Option<F>) -> io::Result<()> {
638 self.push_str("vt ");
639 self.push_f(u);
640 if let Some(vv) = v {
641 self.push_str(" ");
642 self.push_f(vv);
643 if let Some(wv) = w {
644 self.push_str(" ");
645 self.push_f(wv);
646 }
647 }
648 self.flush_line()
649 }
650
651 fn write_normal(&mut self, nx: F, ny: F, nz: F) -> io::Result<()> {
652 self.push_str("vn ");
653 self.push_f(nx);
654 self.push_str(" ");
655 self.push_f(ny);
656 self.push_str(" ");
657 self.push_f(nz);
658 self.flush_line()
659 }
660
661 fn write_face(
662 &mut self,
663 vertex_indices: &[(usize, Option<usize>, Option<usize>)],
664 ) -> io::Result<()> {
665 self.push_str("f");
667 for (v_idx, vt_idx, vn_idx) in vertex_indices.iter() {
668 self.push_str(" ");
669 self.push_u(*v_idx);
671 match (vt_idx, vn_idx) {
672 (None, None) => {}
673 (Some(vt), None) => {
674 self.push_str("/");
675 self.push_u(*vt);
676 }
677 (None, Some(vn)) => {
678 self.push_str("//");
679 self.push_u(*vn);
680 }
681 (Some(vt), Some(vn)) => {
682 self.push_str("/");
683 self.push_u(*vt);
684 self.push_str("/");
685 self.push_u(*vn);
686 }
687 }
688 }
689 self.flush_line()
690 }
691
692 fn write_material_lib<S: AsRef<str>>(&mut self, names: &[S]) -> io::Result<()> {
693 self.push_str("mtllib");
694 for name in names {
695 self.push_str(" ");
696 self.push_str(name.as_ref());
697 }
698 self.flush_line()
699 }
700
701 fn write_use_material<S: AsRef<str>>(&mut self, name: S) -> io::Result<()> {
702 self.push_str("usemtl ");
703 self.push_str(name.as_ref());
704 self.flush_line()
705 }
706
707 fn write_group<S: AsRef<str>>(&mut self, names: &[S]) -> io::Result<()> {
708 self.push_str("g");
709 for name in names {
710 self.push_str(" ");
711 self.push_str(name.as_ref());
712 }
713 self.flush_line()
714 }
715
716 fn write_smoothing_group(&mut self, group: SmoothingGroup) -> io::Result<()> {
717 match group {
718 SmoothingGroup::Off => self.push_str("s off"),
719 SmoothingGroup::Group(n) => {
720 self.push_str("s ");
721 self.push_u(n);
722 }
723 }
724 self.flush_line()
725 }
726
727 fn write_line_element(&mut self, indices: &[(usize, Option<usize>)]) -> io::Result<()> {
728 self.push_str("l");
729 for (v_idx, vt_idx) in indices.iter() {
730 self.push_str(" ");
731 self.push_u(*v_idx);
732 if let Some(vt) = vt_idx {
733 self.push_str("/");
734 self.push_u(*vt);
735 }
736 }
737 self.flush_line()
738 }
739
740 fn write_point_element(&mut self, indices: &[usize]) -> io::Result<()> {
741 self.push_str("p");
742 for v_idx in indices.iter() {
743 self.push_str(" ");
744 self.push_u(*v_idx);
745 }
746 self.flush_line()
747 }
748}
749
750#[cfg(test)]
751mod tests {
752 use super::*;
753 use pretty_assertions::assert_eq;
754 use std::io::Cursor;
755
756 type Face = Vec<(usize, Option<usize>, Option<usize>)>;
757
758 #[derive(Default)]
759 struct TestObjReader64 {
760 comments: Vec<String>,
761 names: Vec<String>,
762 vertices: Vec<(f64, f64, f64, Option<f64>)>,
763 texture_coordinates: Vec<(f64, Option<f64>, Option<f64>)>,
764 normals: Vec<(f64, f64, f64)>,
765 faces: Vec<Face>,
766 }
767
768 impl ObjReader for TestObjReader64 {
769 fn read_comment(&mut self, comment: &str) {
770 self.comments.push(comment.to_string());
771 }
772
773 fn read_object_name(&mut self, name: &str) {
774 self.names.push(name.to_string());
775 }
776
777 fn read_vertex(&mut self, x: f64, y: f64, z: f64, w: Option<f64>) {
778 self.vertices.push((x, y, z, w));
779 }
780
781 fn read_texture_coordinate(&mut self, u: f64, v: Option<f64>, w: Option<f64>) {
782 self.texture_coordinates.push((u, v, w));
783 }
784
785 fn read_normal(&mut self, nx: f64, ny: f64, nz: f64) {
786 self.normals.push((nx, ny, nz));
787 }
788
789 fn read_face(&mut self, vertex_indices: &[(usize, Option<usize>, Option<usize>)]) {
790 self.faces.push(vertex_indices.to_vec());
791 }
792 }
793
794 #[test]
795 fn test_obj_reading() {
796 let obj_data = include_str!("../testdata/screw_f64.obj");
798 let cursor = Cursor::new(obj_data);
799 let mut reader: TestObjReader64 = Default::default();
800 read_obj_file(cursor, &mut reader).unwrap();
801 assert_eq!(reader.comments.len(), 3);
803 assert_eq!(reader.names.len(), 1);
804 assert_eq!(reader.vertices.len(), 41);
805 assert_eq!(reader.texture_coordinates.len(), 41);
806 assert_eq!(reader.normals.len(), 41);
807 assert_eq!(reader.faces.len(), 48);
808 }
809
810 #[test]
811 fn test_obj_reading_2() {
812 let input = "# This is a test OBJ file
813o TestObject
814v 1 2 3
815vt 0.5 0.5
816vn 0 1 1.1
817f 1/1/1 2/2/2 3/3/3
818";
819
820 let reader = Cursor::new(input);
821 let mut test_reader: TestObjReader64 = Default::default();
822 read_obj_file(reader, &mut test_reader).unwrap();
823 assert_eq!(test_reader.comments, vec!["This is a test OBJ file"]);
824 assert_eq!(test_reader.names, vec!["TestObject"]);
825 assert_eq!(test_reader.vertices, vec![(1.0, 2.0, 3.0, None)]);
826 assert_eq!(
827 test_reader.texture_coordinates,
828 vec![(0.5, Some(0.5), None)]
829 );
830 assert_eq!(test_reader.normals, vec![(0.0, 1.0, 1.1)]);
831 assert_eq!(
832 test_reader.faces,
833 vec![vec![
834 (1, Some(1), Some(1)),
835 (2, Some(2), Some(2)),
836 (3, Some(3), Some(3))
837 ]]
838 );
839 }
840
841 #[test]
842 fn test_obj_writing() {
843 let mut buffer = Vec::new();
844 let mut writer = IoObjWriter::new(&mut buffer);
845 writer.write_comment("This is a test OBJ file").unwrap();
846 writer.write_object_name("TestObject").unwrap();
847 writer.write_vertex(1.0, 2.0, 3.0, None).unwrap();
848 writer
849 .write_texture_coordinate(0.5, Some(0.5), None)
850 .unwrap();
851 writer.write_normal(0.0, 1.0, 1.1).unwrap();
852 writer
853 .write_face(&[
854 (1, Some(1), Some(1)),
855 (2, Some(2), Some(2)),
856 (3, Some(3), Some(3)),
857 ])
858 .unwrap();
859
860 let output = String::from_utf8(buffer).unwrap();
861 let expected_output = "# This is a test OBJ file
862o TestObject
863v 1 2 3
864vt 0.5 0.5
865vn 0 1 1.1
866f 1/1/1 2/2/2 3/3/3
867";
868 assert_eq!(output, expected_output);
869 }
870
871 struct WritingReader64 {
872 writer: IoObjWriter<Vec<u8>>,
873 }
874 impl ObjReader for WritingReader64 {
875 fn read_comment(&mut self, comment: &str) {
876 self.writer.write_comment(comment).unwrap();
877 }
878
879 fn read_object_name(&mut self, name: &str) {
880 self.writer.write_object_name(name).unwrap();
881 }
882
883 fn read_vertex(&mut self, x: f64, y: f64, z: f64, w: Option<f64>) {
884 self.writer.write_vertex(x, y, z, w).unwrap();
885 }
886
887 fn read_texture_coordinate(&mut self, u: f64, v: Option<f64>, w: Option<f64>) {
888 self.writer.write_texture_coordinate(u, v, w).unwrap();
889 }
890
891 fn read_normal(&mut self, nx: f64, ny: f64, nz: f64) {
892 self.writer.write_normal(nx, ny, nz).unwrap();
893 }
894
895 fn read_face(&mut self, vertex_indices: &[(usize, Option<usize>, Option<usize>)]) {
896 self.writer.write_face(vertex_indices).unwrap();
897 }
898 }
899
900 #[test]
901 fn test_obj_read_write_compare_64() {
902 let obj_data = include_str!("../testdata/screw_f64.obj").replace("\r\n", "\n");
904 let cursor = Cursor::new(&obj_data);
905 let writer: IoObjWriter<_, f64> = IoObjWriter::new(Vec::new());
907 let mut reader = WritingReader64 { writer };
908 read_obj_file(cursor, &mut reader).unwrap();
909
910 let output = String::from_utf8(reader.writer.out).unwrap();
911 assert_eq!(output, obj_data);
912 }
913
914 #[derive(Default)]
917 struct TestObjReader32 {
918 vertices: Vec<(f32, f32, f32, Option<f32>)>,
919 texture_coordinates: Vec<(f32, Option<f32>, Option<f32>)>,
920 normals: Vec<(f32, f32, f32)>,
921 }
922
923 impl ObjReader<f32> for TestObjReader32 {
924 fn read_comment(&mut self, _comment: &str) {}
925 fn read_object_name(&mut self, _name: &str) {}
926
927 fn read_vertex(&mut self, x: f32, y: f32, z: f32, w: Option<f32>) {
928 self.vertices.push((x, y, z, w));
929 }
930
931 fn read_texture_coordinate(&mut self, u: f32, v: Option<f32>, w: Option<f32>) {
932 self.texture_coordinates.push((u, v, w));
933 }
934
935 fn read_normal(&mut self, nx: f32, ny: f32, nz: f32) {
936 self.normals.push((nx, ny, nz));
937 }
938
939 fn read_face(&mut self, _vertex_indices: &[(usize, Option<usize>, Option<usize>)]) {}
940 }
941
942 struct WritingReader32 {
943 writer: IoObjWriter<Vec<u8>, f32>,
944 }
945 impl ObjReader<f32> for WritingReader32 {
946 fn read_comment(&mut self, comment: &str) {
947 self.writer.write_comment(comment).unwrap();
948 }
949
950 fn read_object_name(&mut self, name: &str) {
951 self.writer.write_object_name(name).unwrap();
952 }
953
954 fn read_vertex(&mut self, x: f32, y: f32, z: f32, w: Option<f32>) {
955 self.writer.write_vertex(x, y, z, w).unwrap();
956 }
957
958 fn read_texture_coordinate(&mut self, u: f32, v: Option<f32>, w: Option<f32>) {
959 self.writer.write_texture_coordinate(u, v, w).unwrap();
960 }
961
962 fn read_normal(&mut self, nx: f32, ny: f32, nz: f32) {
963 self.writer.write_normal(nx, ny, nz).unwrap();
964 }
965
966 fn read_face(&mut self, vertex_indices: &[(usize, Option<usize>, Option<usize>)]) {
967 self.writer.write_face(vertex_indices).unwrap();
968 }
969 }
970
971 #[test]
972 fn test_obj_f32_reading() {
973 let input = "o TestObject
974v 1.5 2.5 3.5
975vt 0.25 0.75
976vn 0.0 1.0 0.0
977";
978
979 let reader = Cursor::new(input);
980 let mut test_reader = TestObjReader32::default();
981 read_obj_file(reader, &mut test_reader).unwrap();
982
983 assert_eq!(test_reader.vertices, vec![(1.5f32, 2.5f32, 3.5f32, None)]);
984 assert_eq!(
985 test_reader.texture_coordinates,
986 vec![(0.25f32, Some(0.75f32), None)]
987 );
988 assert_eq!(test_reader.normals, vec![(0.0f32, 1.0f32, 0.0f32)]);
989 }
990
991 #[test]
992 fn test_obj_f32_writing() {
993 let mut buffer = Vec::new();
994 let mut writer: IoObjWriter<_, f32> = IoObjWriter::new(&mut buffer);
995
996 writer.write_comment("f32 test").unwrap();
997 writer.write_object_name("F32Object").unwrap();
998 writer.write_vertex(1.5f32, 2.5f32, 3.5f32, None).unwrap();
999 writer
1000 .write_texture_coordinate(0.25f32, Some(0.75f32), None)
1001 .unwrap();
1002 writer.write_normal(0.0f32, 1.0f32, 0.0f32).unwrap();
1003
1004 let output = String::from_utf8(buffer).unwrap();
1005 let expected = "# f32 test
1006o F32Object
1007v 1.5 2.5 3.5
1008vt 0.25 0.75
1009vn 0 1 0
1010";
1011 assert_eq!(output, expected);
1012 }
1013
1014 #[test]
1015 fn test_obj_f32_round_trip() {
1016 let input = "o Test
1018v 0.123456789 0.987654321 1.5
1019vn 0.577 0.577 0.577
1020";
1021
1022 let reader = Cursor::new(input);
1023 let mut test_reader = TestObjReader32::default();
1024 read_obj_file(reader, &mut test_reader).unwrap();
1025
1026 let mut buffer = Vec::new();
1028 let mut writer: IoObjWriter<_, f32> = IoObjWriter::new(&mut buffer);
1029 writer.write_object_name("Test").unwrap();
1030 for (x, y, z, w) in &test_reader.vertices {
1031 writer.write_vertex(*x, *y, *z, *w).unwrap();
1032 }
1033 for (nx, ny, nz) in &test_reader.normals {
1034 writer.write_normal(*nx, *ny, *nz).unwrap();
1035 }
1036
1037 let output = String::from_utf8(buffer).unwrap();
1038
1039 assert!(output.contains("o Test"));
1041 assert!(output.contains("v "));
1042 assert!(output.contains("vn "));
1043 }
1044
1045 #[test]
1046 fn test_printf_f_formatting() {
1047 let mut buffer = Vec::new();
1048 let mut writer: IoObjWriter<_, f64> = IoObjWriter::new_with_printf_f_format(&mut buffer);
1049
1050 writer.write_object_name("PrintfFTest").unwrap();
1051 writer
1052 .write_vertex(754.4214477539063, 1753.2353515625, -91.72238159179688, None)
1053 .unwrap();
1054 writer
1055 .write_texture_coordinate(0.123456789, Some(0.987654321), None)
1056 .unwrap();
1057 writer
1058 .write_normal(0.5773502691896257, 0.5773502691896257, 0.5773502691896257)
1059 .unwrap();
1060
1061 let output = String::from_utf8(buffer).unwrap();
1062
1063 let expected = "o PrintfFTest
1065v 754.421448 1753.235352 -91.722382
1066vt 0.123457 0.987654
1067vn 0.577350 0.577350 0.577350
1068";
1069 assert_eq!(output, expected);
1070 }
1071
1072 #[test]
1073 fn test_printf_f_flag_toggle() {
1074 let mut buffer = Vec::new();
1076 let mut writer: IoObjWriter<_, f32> = IoObjWriter::new(&mut buffer);
1077
1078 writer
1080 .write_vertex(1.2345678_f32, 2.345678_f32, 3.45678_f32, None)
1081 .unwrap();
1082
1083 writer.set_printf_f_format(true);
1085 writer
1086 .write_vertex(1.2345678_f32, 2.3456789_f32, 3.456789_f32, None)
1087 .unwrap();
1088
1089 let output = String::from_utf8(buffer).unwrap();
1090
1091 let lines: Vec<&str> = output.lines().collect();
1093 assert_eq!(lines.len(), 2);
1094
1095 assert_eq!(lines[0], "v 1.2345678 2.345678 3.45678");
1097
1098 assert_eq!(lines[1], "v 1.234568 2.345679 3.456789");
1100 }
1101
1102 #[test]
1103 fn test_obj_read_write_compare_32() {
1104 let obj_data = include_str!("../testdata/screw_f32.obj").replace("\r\n", "\n");
1106 let cursor = Cursor::new(&obj_data);
1107 let writer: IoObjWriter<_, f32> = IoObjWriter::new(Vec::new());
1109 let mut reader = WritingReader32 { writer };
1110 read_obj_file(cursor, &mut reader).unwrap();
1111
1112 let output = String::from_utf8(reader.writer.out).unwrap();
1113 assert_eq!(output, obj_data);
1114 }
1115
1116 #[test]
1117 fn f32_reader_can_read_f64_obj() {
1118 let obj_data = include_str!("../testdata/screw_f64.obj");
1119 let cursor = Cursor::new(obj_data);
1120 let mut test_reader: TestObjReader32 = Default::default();
1121 read_obj_file(cursor, &mut test_reader).unwrap();
1122 assert_eq!(test_reader.vertices.len(), 41);
1124 assert_eq!(test_reader.texture_coordinates.len(), 41);
1125 assert_eq!(test_reader.normals.len(), 41);
1126 }
1127
1128 #[derive(Default, Debug, PartialEq)]
1133 struct ExtendedReader32 {
1134 material_libs: Vec<Vec<String>>,
1135 use_materials: Vec<String>,
1136 groups: Vec<Vec<String>>,
1137 smoothing_groups: Vec<SmoothingGroup>,
1138 line_elements: Vec<Vec<(usize, Option<usize>)>>,
1139 point_elements: Vec<Vec<usize>>,
1140 }
1141
1142 impl ObjReader<f32> for ExtendedReader32 {
1143 fn read_comment(&mut self, _: &str) {}
1144 fn read_object_name(&mut self, _: &str) {}
1145 fn read_vertex(&mut self, _: f32, _: f32, _: f32, _: Option<f32>) {}
1146 fn read_texture_coordinate(&mut self, _: f32, _: Option<f32>, _: Option<f32>) {}
1147 fn read_normal(&mut self, _: f32, _: f32, _: f32) {}
1148 fn read_face(&mut self, _: &[(usize, Option<usize>, Option<usize>)]) {}
1149
1150 fn read_material_lib(&mut self, names: &[&str]) {
1151 self.material_libs
1152 .push(names.iter().map(|s| s.to_string()).collect());
1153 }
1154 fn read_use_material(&mut self, name: &str) {
1155 self.use_materials.push(name.to_string());
1156 }
1157 fn read_group(&mut self, names: &[&str]) {
1158 self.groups
1159 .push(names.iter().map(|s| s.to_string()).collect());
1160 }
1161 fn read_smoothing_group(&mut self, group: SmoothingGroup) {
1162 self.smoothing_groups.push(group);
1163 }
1164 fn read_line_element(&mut self, indices: &[(usize, Option<usize>)]) {
1165 self.line_elements.push(indices.to_vec());
1166 }
1167 fn read_point_element(&mut self, indices: &[usize]) {
1168 self.point_elements.push(indices.to_vec());
1169 }
1170 }
1171
1172 #[test]
1173 fn read_mtllib_and_usemtl() {
1174 let input = "mtllib first.mtl second.mtl
1175usemtl SomeMaterial
1176";
1177 let mut reader = ExtendedReader32::default();
1178 read_obj_file(Cursor::new(input), &mut reader).unwrap();
1179 assert_eq!(
1180 reader.material_libs,
1181 vec![vec!["first.mtl".to_string(), "second.mtl".to_string()]]
1182 );
1183 assert_eq!(reader.use_materials, vec!["SomeMaterial".to_string()]);
1184 }
1185
1186 #[test]
1187 fn read_group_with_multiple_names() {
1188 let input = "g cube top
1189g default
1190";
1191 let mut reader = ExtendedReader32::default();
1192 read_obj_file(Cursor::new(input), &mut reader).unwrap();
1193 assert_eq!(
1194 reader.groups,
1195 vec![
1196 vec!["cube".to_string(), "top".to_string()],
1197 vec!["default".to_string()],
1198 ]
1199 );
1200 }
1201
1202 #[test]
1203 fn read_smoothing_group_off_zero_and_named() {
1204 let input = "s off
1205s 0
1206s 1
1207s 42
1208";
1209 let mut reader = ExtendedReader32::default();
1210 read_obj_file(Cursor::new(input), &mut reader).unwrap();
1211 assert_eq!(
1212 reader.smoothing_groups,
1213 vec![
1214 SmoothingGroup::Off,
1215 SmoothingGroup::Off,
1216 SmoothingGroup::Group(1),
1217 SmoothingGroup::Group(42),
1218 ]
1219 );
1220 }
1221
1222 #[test]
1223 fn read_smoothing_group_invalid_value_errors() {
1224 let input = "s notanumber\n";
1225 let mut reader = ExtendedReader32::default();
1226 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1227 assert!(
1228 err.to_string().contains("invalid smoothing group"),
1229 "got: {}",
1230 err
1231 );
1232 }
1233
1234 #[test]
1235 fn read_line_and_point_elements() {
1236 let input = "l 1 2 3
1237l 4/1 5/2 6/3
1238p 1 2 3 4
1239";
1240 let mut reader = ExtendedReader32::default();
1241 read_obj_file(Cursor::new(input), &mut reader).unwrap();
1242 assert_eq!(
1243 reader.line_elements,
1244 vec![
1245 vec![(1, None), (2, None), (3, None)],
1246 vec![(4, Some(1)), (5, Some(2)), (6, Some(3))],
1247 ]
1248 );
1249 assert_eq!(reader.point_elements, vec![vec![1, 2, 3, 4]]);
1250 }
1251
1252 #[test]
1253 fn read_line_element_with_one_vertex_errors() {
1254 let mut reader = ExtendedReader32::default();
1255 let err = read_obj_file(Cursor::new("l 1\n"), &mut reader).unwrap_err();
1256 assert!(
1257 err.to_string().contains("at least 2 vertices"),
1258 "got: {}",
1259 err
1260 );
1261 }
1262
1263 #[test]
1264 fn write_directive_round_trip() {
1265 let mut buffer = Vec::new();
1267 {
1268 let mut writer: IoObjWriter<_, f32> = IoObjWriter::new(&mut buffer);
1269 writer
1270 .write_material_lib(&["lib1.mtl", "lib2.mtl"])
1271 .unwrap();
1272 writer.write_use_material("Wood").unwrap();
1273 writer.write_group(&["cube", "top"]).unwrap();
1274 writer.write_smoothing_group(SmoothingGroup::Off).unwrap();
1275 writer
1276 .write_smoothing_group(SmoothingGroup::Group(7))
1277 .unwrap();
1278 writer
1279 .write_line_element(&[(1, None), (2, Some(2)), (3, None)])
1280 .unwrap();
1281 writer.write_point_element(&[1, 2, 3]).unwrap();
1282 }
1283 let text = String::from_utf8(buffer.clone()).unwrap();
1284 let expected = "mtllib lib1.mtl lib2.mtl
1285usemtl Wood
1286g cube top
1287s off
1288s 7
1289l 1 2/2 3
1290p 1 2 3
1291";
1292 assert_eq!(text, expected);
1293
1294 let mut reader = ExtendedReader32::default();
1295 read_obj_file(Cursor::new(buffer), &mut reader).unwrap();
1296 assert_eq!(
1297 reader.material_libs,
1298 vec![vec!["lib1.mtl".to_string(), "lib2.mtl".to_string()]]
1299 );
1300 assert_eq!(reader.use_materials, vec!["Wood".to_string()]);
1301 assert_eq!(
1302 reader.groups,
1303 vec![vec!["cube".to_string(), "top".to_string()]]
1304 );
1305 assert_eq!(
1306 reader.smoothing_groups,
1307 vec![SmoothingGroup::Off, SmoothingGroup::Group(7)]
1308 );
1309 assert_eq!(
1310 reader.line_elements,
1311 vec![vec![(1, None), (2, Some(2)), (3, None)]]
1312 );
1313 assert_eq!(reader.point_elements, vec![vec![1, 2, 3]]);
1314 }
1315
1316 #[test]
1317 fn unknown_prefix_still_errors_by_default() {
1318 let input = "vp 0.1 0.2 0.3\n";
1321 let mut reader = ExtendedReader32::default();
1322 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1323 assert!(
1324 err.to_string().contains("Unknown line prefix: vp"),
1325 "got: {}",
1326 err
1327 );
1328 }
1329
1330 #[test]
1334 fn typed_error_unknown_prefix_carries_prefix_and_line() {
1335 let input = "v 0 0 0\nvp 0.1 0.2\n";
1336 let mut reader = ExtendedReader32::default();
1337 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1338 match err {
1339 ObjError::Parse {
1340 line,
1341 kind: ParseErrorKind::UnknownPrefix(prefix),
1342 } => {
1343 assert_eq!(line, 2);
1344 assert_eq!(prefix, "vp");
1345 }
1346 other => panic!("wrong error variant: {:?}", other),
1347 }
1348 }
1349
1350 #[test]
1351 fn typed_error_invalid_number_carries_field_and_value() {
1352 let input = "v 1.0 nope 3.0\n";
1353 let mut reader = ExtendedReader32::default();
1354 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1355 match err {
1356 ObjError::Parse {
1357 line: 1,
1358 kind: ParseErrorKind::InvalidNumber { field, value },
1359 } => {
1360 assert_eq!(field, "vertex y");
1361 assert_eq!(value, "nope");
1362 }
1363 other => panic!("wrong error variant: {:?}", other),
1364 }
1365 }
1366
1367 #[test]
1368 fn typed_error_invalid_index_for_face() {
1369 let input = "v 0 0 0\nf 0/0/0 0/0/0 0/0/0\n";
1371 let mut reader = ExtendedReader32::default();
1372 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1373 match err {
1374 ObjError::Parse {
1375 line: 2,
1376 kind: ParseErrorKind::InvalidIndex { kind, value },
1377 } => {
1378 assert_eq!(kind, "vertex");
1379 assert_eq!(value, "0");
1380 }
1381 other => panic!("wrong error variant: {:?}", other),
1382 }
1383 }
1384
1385 #[test]
1386 fn typed_error_invalid_smoothing_group() {
1387 let input = "s notanumber\n";
1388 let mut reader = ExtendedReader32::default();
1389 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1390 match err {
1391 ObjError::Parse {
1392 line: 1,
1393 kind: ParseErrorKind::InvalidSmoothingGroup(value),
1394 } => assert_eq!(value, "notanumber"),
1395 other => panic!("wrong error variant: {:?}", other),
1396 }
1397 }
1398
1399 #[test]
1400 fn typed_error_missing_field() {
1401 let input = "v 1.0 2.0\n";
1402 let mut reader = ExtendedReader32::default();
1403 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1404 match err {
1405 ObjError::Parse {
1406 line: 1,
1407 kind: ParseErrorKind::MissingField(field),
1408 } => assert_eq!(field, "vertex z"),
1409 other => panic!("wrong error variant: {:?}", other),
1410 }
1411 }
1412
1413 #[test]
1414 fn typed_error_converts_to_io_error() {
1415 fn legacy_caller<R: io::Read>(input: R) -> io::Result<()> {
1418 let mut reader = ExtendedReader32::default();
1419 read_obj_file(input, &mut reader)?;
1420 Ok(())
1421 }
1422
1423 let err = legacy_caller(Cursor::new("vp 0.1 0.2\n")).unwrap_err();
1424 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
1425 assert!(err.to_string().contains("Unknown line prefix: vp"));
1426 }
1427
1428 #[test]
1429 fn typed_error_io_failure_propagates() {
1430 struct FailingRead;
1432 impl io::Read for FailingRead {
1433 fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
1434 Err(io::Error::other("disk on fire"))
1435 }
1436 }
1437
1438 let mut reader = ExtendedReader32::default();
1439 let err = read_obj_file(FailingRead, &mut reader).unwrap_err();
1440 match err {
1441 ObjError::Io(inner) => {
1442 assert_eq!(inner.to_string(), "disk on fire");
1443 }
1444 other => panic!("wrong error variant: {:?}", other),
1445 }
1446 }
1447
1448 #[test]
1449 fn custom_read_unknown_can_return_objerror() {
1450 struct StrictCustom;
1452 impl ObjReader<f32> for StrictCustom {
1453 fn read_comment(&mut self, _: &str) {}
1454 fn read_object_name(&mut self, _: &str) {}
1455 fn read_vertex(&mut self, _: f32, _: f32, _: f32, _: Option<f32>) {}
1456 fn read_texture_coordinate(&mut self, _: f32, _: Option<f32>, _: Option<f32>) {}
1457 fn read_normal(&mut self, _: f32, _: f32, _: f32) {}
1458 fn read_face(&mut self, _: &[(usize, Option<usize>, Option<usize>)]) {}
1459 fn read_unknown(&mut self, prefix: &str, _: &str, line: usize) -> Result<(), ObjError> {
1460 Err(ObjError::Parse {
1461 line,
1462 kind: ParseErrorKind::Custom(format!("nope: {prefix}")),
1463 })
1464 }
1465 }
1466
1467 let err = read_obj_file(Cursor::new("vp 0 0\n"), &mut StrictCustom).unwrap_err();
1468 match err {
1469 ObjError::Parse {
1470 line: 1,
1471 kind: ParseErrorKind::Custom(msg),
1472 } => assert_eq!(msg, "nope: vp"),
1473 other => panic!("wrong error variant: {:?}", other),
1474 }
1475 }
1476}