1use std::error::Error as StdError;
77use std::fmt;
78use std::fmt::Display;
79use std::io;
80use std::io::{BufRead, BufReader};
81use std::str::FromStr;
82
83#[derive(Debug)]
88pub enum ObjError {
89 Io(io::Error),
90 Parse { line: usize, kind: ParseErrorKind },
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
95pub enum ParseErrorKind {
96 EmptyPrefix,
98 UnknownPrefix(String),
101 MissingField(&'static str),
103 InvalidNumber { field: &'static str, value: String },
105 InvalidIndex { kind: &'static str, value: String },
107 InvalidSmoothingGroup(String),
109 LineElementTooShort,
111 PointElementEmpty,
113 Custom(String),
115}
116
117impl Display for ObjError {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 match self {
120 ObjError::Io(e) => write!(f, "I/O error: {e}"),
121 ObjError::Parse { line, kind } => write!(f, "line {line}: {kind}"),
122 }
123 }
124}
125
126impl Display for ParseErrorKind {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match self {
129 ParseErrorKind::EmptyPrefix => write!(f, "empty prefix"),
130 ParseErrorKind::UnknownPrefix(p) => write!(f, "Unknown line prefix: {p}"),
131 ParseErrorKind::MissingField(field) => write!(f, "missing {field}"),
132 ParseErrorKind::InvalidNumber { field, value } => {
133 write!(f, "invalid {field}: {value}")
134 }
135 ParseErrorKind::InvalidIndex { kind, value } => {
136 write!(f, "invalid {kind} index: {value}")
137 }
138 ParseErrorKind::InvalidSmoothingGroup(v) => {
139 write!(
140 f,
141 "invalid smoothing group: {v} (expected integer or 'off')"
142 )
143 }
144 ParseErrorKind::LineElementTooShort => {
145 write!(f, "line element needs at least 2 vertices")
146 }
147 ParseErrorKind::PointElementEmpty => {
148 write!(f, "point element needs at least 1 vertex")
149 }
150 ParseErrorKind::Custom(s) => write!(f, "{s}"),
151 }
152 }
153}
154
155impl StdError for ObjError {
156 fn source(&self) -> Option<&(dyn StdError + 'static)> {
157 match self {
158 ObjError::Io(e) => Some(e),
159 ObjError::Parse { .. } => None,
160 }
161 }
162}
163
164impl From<io::Error> for ObjError {
165 fn from(e: io::Error) -> Self {
166 ObjError::Io(e)
167 }
168}
169
170impl From<ObjError> for io::Error {
171 fn from(e: ObjError) -> Self {
172 match e {
173 ObjError::Io(inner) => inner,
174 parse @ ObjError::Parse { .. } => {
175 io::Error::new(io::ErrorKind::InvalidData, parse.to_string())
176 }
177 }
178 }
179}
180
181pub trait ObjFloat: Copy + Display + FromStr + PartialEq {
184 fn fract(self) -> Self;
186
187 fn is_zero_fract(self) -> bool {
189 self.fract() == Self::zero()
190 }
191
192 fn zero() -> Self;
194}
195
196impl ObjFloat for f32 {
197 fn fract(self) -> Self {
198 self.fract()
199 }
200 fn zero() -> Self {
201 0.0
202 }
203}
204
205impl ObjFloat for f64 {
206 fn fract(self) -> Self {
207 self.fract()
208 }
209 fn zero() -> Self {
210 0.0
211 }
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum SmoothingGroup {
220 Off,
221 Group(u32),
222}
223
224pub trait ObjWriter<F: ObjFloat = f64> {
229 fn write_comment<S: AsRef<str>>(&mut self, comment: S) -> io::Result<()>;
230 fn write_object_name<S: AsRef<str>>(&mut self, name: S) -> io::Result<()>;
231 fn write_vertex(&mut self, x: F, y: F, z: F, w: Option<F>) -> io::Result<()>;
232 fn write_texture_coordinate(&mut self, u: F, v: Option<F>, w: Option<F>) -> io::Result<()>;
233 fn write_normal(&mut self, nx: F, ny: F, nz: F) -> io::Result<()>;
234 fn write_face(
235 &mut self,
236 vertex_indices: &[(usize, Option<usize>, Option<usize>)],
237 ) -> io::Result<()>;
238
239 fn write_material_lib<S: AsRef<str>>(&mut self, names: &[S]) -> io::Result<()>;
241
242 fn write_use_material<S: AsRef<str>>(&mut self, name: S) -> io::Result<()>;
244
245 fn write_group<S: AsRef<str>>(&mut self, names: &[S]) -> io::Result<()>;
247
248 fn write_smoothing_group(&mut self, group: SmoothingGroup) -> io::Result<()>;
250
251 fn write_line_element(&mut self, indices: &[(usize, Option<usize>)]) -> io::Result<()>;
254
255 fn write_point_element(&mut self, indices: &[usize]) -> io::Result<()>;
257}
258
259pub trait ObjReader<F: ObjFloat = f64> {
264 fn read_comment(&mut self, comment: &str) -> ();
265 fn read_object_name(&mut self, name: &str) -> ();
266 fn read_vertex(&mut self, x: F, y: F, z: F, w: Option<F>) -> ();
267 fn read_texture_coordinate(&mut self, u: F, v: Option<F>, w: Option<F>) -> ();
268 fn read_normal(&mut self, nx: F, ny: F, nz: F) -> ();
269 fn read_face(&mut self, vertex_indices: &[(usize, Option<usize>, Option<usize>)]) -> ();
270
271 fn read_material_lib(&mut self, _names: &[&str]) {}
273
274 fn read_use_material(&mut self, _name: &str) {}
276
277 fn read_group(&mut self, _names: &[&str]) {}
279
280 fn read_smoothing_group(&mut self, _group: SmoothingGroup) {}
282
283 fn read_line_element(&mut self, _indices: &[(usize, Option<usize>)]) {}
285
286 fn read_point_element(&mut self, _indices: &[usize]) {}
288
289 fn read_unknown(&mut self, prefix: &str, _rest: &str, line: usize) -> Result<(), ObjError> {
296 Err(ObjError::Parse {
297 line,
298 kind: ParseErrorKind::UnknownPrefix(prefix.to_string()),
299 })
300 }
301}
302
303pub fn read_obj_file<R: io::Read, T: ObjReader<F>, F: ObjFloat>(
304 reader: R,
305 obj_reader: &mut T,
306) -> Result<(), ObjError>
307where
308 <F as FromStr>::Err: Display,
309{
310 let mut buf_reader = BufReader::new(reader);
311 let mut line = String::new();
312 let mut lineno: usize = 0;
313
314 while buf_reader.read_line(&mut line)? != 0 {
315 lineno += 1;
316 let trimmed = line.trim();
317 if trimmed.is_empty() {
318 line.clear();
319 continue;
320 }
321 let mut parts = trimmed.split_whitespace();
322 let prefix = parts.next().ok_or(ObjError::Parse {
323 line: lineno,
324 kind: ParseErrorKind::EmptyPrefix,
325 })?;
326
327 let parse_f = |s: &str, field: &'static str| -> Result<F, ObjError> {
328 s.parse::<F>().map_err(|_| ObjError::Parse {
329 line: lineno,
330 kind: ParseErrorKind::InvalidNumber {
331 field,
332 value: s.to_string(),
333 },
334 })
335 };
336
337 let parse_index = |s: &str, kind: &'static str| -> Result<usize, ObjError> {
338 let index = s.parse::<usize>().map_err(|_| ObjError::Parse {
339 line: lineno,
340 kind: ParseErrorKind::InvalidIndex {
341 kind,
342 value: s.to_string(),
343 },
344 })?;
345 if index == 0 {
346 return Err(ObjError::Parse {
347 line: lineno,
348 kind: ParseErrorKind::InvalidIndex {
349 kind,
350 value: s.to_string(),
351 },
352 });
353 }
354 Ok(index)
355 };
356
357 let missing = |field: &'static str| -> ObjError {
358 ObjError::Parse {
359 line: lineno,
360 kind: ParseErrorKind::MissingField(field),
361 }
362 };
363
364 match prefix {
365 "#" => {
366 let comment = parts.collect::<Vec<&str>>().join(" ");
367 obj_reader.read_comment(&comment);
368 }
369 "v" => {
370 let x = parts
371 .next()
372 .ok_or_else(|| missing("vertex x"))
373 .and_then(|s| parse_f(s, "vertex x"))?;
374 let y = parts
375 .next()
376 .ok_or_else(|| missing("vertex y"))
377 .and_then(|s| parse_f(s, "vertex y"))?;
378 let z = parts
379 .next()
380 .ok_or_else(|| missing("vertex z"))
381 .and_then(|s| parse_f(s, "vertex z"))?;
382 let w = match parts.next() {
383 Some(s) => Some(parse_f(s, "vertex w")?),
384 None => None,
385 };
386 obj_reader.read_vertex(x, y, z, w);
387 }
388 "vt" => {
389 let u = parts
390 .next()
391 .ok_or_else(|| missing("texture u"))
392 .and_then(|s| parse_f(s, "texture u"))?;
393 let v = match parts.next() {
394 Some(s) => Some(parse_f(s, "texture v")?),
395 None => None,
396 };
397 let w = match parts.next() {
398 Some(s) => Some(parse_f(s, "texture w")?),
399 None => None,
400 };
401 obj_reader.read_texture_coordinate(u, v, w);
402 }
403 "vn" => {
404 let nx = parts
405 .next()
406 .ok_or_else(|| missing("normal nx"))
407 .and_then(|s| parse_f(s, "normal nx"))?;
408 let ny = parts
409 .next()
410 .ok_or_else(|| missing("normal ny"))
411 .and_then(|s| parse_f(s, "normal ny"))?;
412 let nz = parts
413 .next()
414 .ok_or_else(|| missing("normal nz"))
415 .and_then(|s| parse_f(s, "normal nz"))?;
416 obj_reader.read_normal(nx, ny, nz);
417 }
418 "f" => {
419 let mut vertex_indices = Vec::new();
420 for part in parts {
421 let first_slash = part.find('/');
423 let (v_str, rest) = match first_slash {
424 Some(i) => (&part[..i], &part[i + 1..]),
425 None => (part, ""),
426 };
427
428 let v_idx = parse_index(v_str, "vertex")?;
429
430 let (vt_idx, vn_idx) = if rest.is_empty() {
431 (None, None)
432 } else {
433 let second_slash = rest.find('/');
434 if let Some(j) = second_slash {
435 let vt_part = &rest[..j];
436 let vn_part = &rest[j + 1..];
437 let vt = if vt_part.is_empty() {
438 None
439 } else {
440 Some(parse_index(vt_part, "texcoord")?)
441 };
442 let vn = if vn_part.is_empty() {
443 None
444 } else {
445 Some(parse_index(vn_part, "normal")?)
446 };
447 (vt, vn)
448 } else {
449 let vt = if rest.is_empty() {
451 None
452 } else {
453 Some(parse_index(rest, "texcoord")?)
454 };
455 (vt, None)
456 }
457 };
458
459 vertex_indices.push((v_idx, vt_idx, vn_idx));
460 }
461 obj_reader.read_face(&vertex_indices);
462 }
463 "o" => {
464 let name = parts.collect::<Vec<&str>>().join(" ");
465 obj_reader.read_object_name(&name);
466 }
467 "mtllib" => {
468 let names: Vec<&str> = parts.collect();
469 obj_reader.read_material_lib(&names);
470 }
471 "usemtl" => {
472 let name = parts.collect::<Vec<&str>>().join(" ");
475 obj_reader.read_use_material(&name);
476 }
477 "g" => {
478 let names: Vec<&str> = parts.collect();
479 obj_reader.read_group(&names);
480 }
481 "s" => {
482 let value = parts
483 .next()
484 .ok_or_else(|| missing("smoothing group value"))?;
485 let group = if value.eq_ignore_ascii_case("off") {
486 SmoothingGroup::Off
487 } else {
488 let n = value.parse::<u32>().map_err(|_| ObjError::Parse {
489 line: lineno,
490 kind: ParseErrorKind::InvalidSmoothingGroup(value.to_string()),
491 })?;
492 if n == 0 {
493 SmoothingGroup::Off
494 } else {
495 SmoothingGroup::Group(n)
496 }
497 };
498 obj_reader.read_smoothing_group(group);
499 }
500 "l" => {
501 let mut indices: Vec<(usize, Option<usize>)> = Vec::new();
502 for part in parts {
503 let (v_str, vt_str) = match part.find('/') {
504 Some(i) => (&part[..i], Some(&part[i + 1..])),
505 None => (part, None),
506 };
507 let v_idx = parse_index(v_str, "vertex")?;
508 let vt_idx = match vt_str {
509 Some(s) if !s.is_empty() => Some(parse_index(s, "texcoord")?),
510 _ => None,
511 };
512 indices.push((v_idx, vt_idx));
513 }
514 if indices.len() < 2 {
515 return Err(ObjError::Parse {
516 line: lineno,
517 kind: ParseErrorKind::LineElementTooShort,
518 });
519 }
520 obj_reader.read_line_element(&indices);
521 }
522 "p" => {
523 let mut indices: Vec<usize> = Vec::new();
524 for part in parts {
525 indices.push(parse_index(part, "vertex")?);
526 }
527 if indices.is_empty() {
528 return Err(ObjError::Parse {
529 line: lineno,
530 kind: ParseErrorKind::PointElementEmpty,
531 });
532 }
533 obj_reader.read_point_element(&indices);
534 }
535 other => {
536 let rest = parts.collect::<Vec<&str>>().join(" ");
537 obj_reader.read_unknown(other, &rest, lineno)?;
538 }
539 }
540
541 line.clear();
542 }
543
544 Ok(())
545}
546
547pub struct IoObjWriter<W: io::Write, F: ObjFloat = f64> {
548 out: W,
549 line_buf: Vec<u8>,
550 printf_f_format: bool,
552 _phantom: std::marker::PhantomData<F>,
553}
554impl<W: io::Write, F: ObjFloat> IoObjWriter<W, F> {
555 pub fn new(writer: W) -> Self {
557 IoObjWriter {
558 out: writer,
559 line_buf: Vec::with_capacity(256),
560 printf_f_format: false,
561 _phantom: std::marker::PhantomData,
562 }
563 }
564
565 #[cfg(test)]
568 pub fn new_with_printf_f_format(writer: W) -> Self {
569 IoObjWriter {
570 out: writer,
571 line_buf: Vec::with_capacity(256),
572 printf_f_format: true,
573 _phantom: std::marker::PhantomData,
574 }
575 }
576
577 #[cfg(test)]
579 pub fn set_printf_f_format(&mut self, enabled: bool) {
580 self.printf_f_format = enabled;
581 }
582
583 pub fn into_inner(self) -> W {
585 self.out
586 }
587
588 #[inline]
589 fn push_str(&mut self, s: &str) {
590 self.line_buf.extend_from_slice(s.as_bytes());
591 }
592
593 #[inline]
594 fn push_u<T: itoa::Integer>(&mut self, v: T) {
595 let mut buf = itoa::Buffer::new();
596 self.push_str(buf.format(v));
597 }
598
599 #[inline]
600 fn push_f(&mut self, v: F) {
601 if v.is_zero_fract() {
603 self.push_str(&format!("{}", v));
604 return;
605 }
606 if self.printf_f_format {
609 self.push_str(&format!("{:.6}", v));
610 } else {
611 self.push_str(&format!("{}", v));
612 }
613 }
614
615 #[inline]
616 fn flush_line(&mut self) -> io::Result<()> {
617 self.line_buf.push(b'\n');
618 self.out.write_all(&self.line_buf)?;
619 self.line_buf.clear();
620 Ok(())
621 }
622}
623impl<W: io::Write, F: ObjFloat> ObjWriter<F> for IoObjWriter<W, F> {
624 fn write_comment<S: AsRef<str>>(&mut self, comment: S) -> io::Result<()> {
625 self.push_str("# ");
626 self.push_str(comment.as_ref());
627 self.flush_line()
628 }
629
630 fn write_object_name<S: AsRef<str>>(&mut self, name: S) -> io::Result<()> {
631 self.push_str("o ");
632 self.push_str(name.as_ref());
633 self.flush_line()
634 }
635
636 fn write_vertex(&mut self, x: F, y: F, z: F, w: Option<F>) -> io::Result<()> {
637 self.push_str("v ");
638 self.push_f(x);
639 self.push_str(" ");
640 self.push_f(y);
641 self.push_str(" ");
642 self.push_f(z);
643 if let Some(wv) = w {
644 self.push_str(" ");
645 self.push_f(wv);
646 }
647 self.flush_line()
648 }
649
650 fn write_texture_coordinate(&mut self, u: F, v: Option<F>, w: Option<F>) -> io::Result<()> {
651 self.push_str("vt ");
652 self.push_f(u);
653 if let Some(vv) = v {
654 self.push_str(" ");
655 self.push_f(vv);
656 if let Some(wv) = w {
657 self.push_str(" ");
658 self.push_f(wv);
659 }
660 }
661 self.flush_line()
662 }
663
664 fn write_normal(&mut self, nx: F, ny: F, nz: F) -> io::Result<()> {
665 self.push_str("vn ");
666 self.push_f(nx);
667 self.push_str(" ");
668 self.push_f(ny);
669 self.push_str(" ");
670 self.push_f(nz);
671 self.flush_line()
672 }
673
674 fn write_face(
675 &mut self,
676 vertex_indices: &[(usize, Option<usize>, Option<usize>)],
677 ) -> io::Result<()> {
678 self.push_str("f");
680 for (v_idx, vt_idx, vn_idx) in vertex_indices.iter() {
681 self.push_str(" ");
682 self.push_u(*v_idx);
684 match (vt_idx, vn_idx) {
685 (None, None) => {}
686 (Some(vt), None) => {
687 self.push_str("/");
688 self.push_u(*vt);
689 }
690 (None, Some(vn)) => {
691 self.push_str("//");
692 self.push_u(*vn);
693 }
694 (Some(vt), Some(vn)) => {
695 self.push_str("/");
696 self.push_u(*vt);
697 self.push_str("/");
698 self.push_u(*vn);
699 }
700 }
701 }
702 self.flush_line()
703 }
704
705 fn write_material_lib<S: AsRef<str>>(&mut self, names: &[S]) -> io::Result<()> {
706 self.push_str("mtllib");
707 for name in names {
708 self.push_str(" ");
709 self.push_str(name.as_ref());
710 }
711 self.flush_line()
712 }
713
714 fn write_use_material<S: AsRef<str>>(&mut self, name: S) -> io::Result<()> {
715 self.push_str("usemtl ");
716 self.push_str(name.as_ref());
717 self.flush_line()
718 }
719
720 fn write_group<S: AsRef<str>>(&mut self, names: &[S]) -> io::Result<()> {
721 self.push_str("g");
722 for name in names {
723 self.push_str(" ");
724 self.push_str(name.as_ref());
725 }
726 self.flush_line()
727 }
728
729 fn write_smoothing_group(&mut self, group: SmoothingGroup) -> io::Result<()> {
730 match group {
731 SmoothingGroup::Off => self.push_str("s off"),
732 SmoothingGroup::Group(n) => {
733 self.push_str("s ");
734 self.push_u(n);
735 }
736 }
737 self.flush_line()
738 }
739
740 fn write_line_element(&mut self, indices: &[(usize, Option<usize>)]) -> io::Result<()> {
741 self.push_str("l");
742 for (v_idx, vt_idx) in indices.iter() {
743 self.push_str(" ");
744 self.push_u(*v_idx);
745 if let Some(vt) = vt_idx {
746 self.push_str("/");
747 self.push_u(*vt);
748 }
749 }
750 self.flush_line()
751 }
752
753 fn write_point_element(&mut self, indices: &[usize]) -> io::Result<()> {
754 self.push_str("p");
755 for v_idx in indices.iter() {
756 self.push_str(" ");
757 self.push_u(*v_idx);
758 }
759 self.flush_line()
760 }
761}
762
763#[derive(Debug)]
769pub enum MtlError {
770 Io(io::Error),
771 Parse {
772 line: usize,
773 kind: MtlParseErrorKind,
774 },
775}
776
777#[derive(Debug, Clone, PartialEq, Eq)]
779pub enum MtlParseErrorKind {
780 EmptyPrefix,
781 UnknownPrefix(String),
782 MissingField(&'static str),
783 InvalidNumber { field: &'static str, value: String },
784 InvalidIlluminationModel(String),
785 Custom(String),
786}
787
788impl Display for MtlError {
789 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
790 match self {
791 MtlError::Io(e) => write!(f, "I/O error: {e}"),
792 MtlError::Parse { line, kind } => write!(f, "line {line}: {kind}"),
793 }
794 }
795}
796
797impl Display for MtlParseErrorKind {
798 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799 match self {
800 MtlParseErrorKind::EmptyPrefix => write!(f, "empty prefix"),
801 MtlParseErrorKind::UnknownPrefix(p) => write!(f, "Unknown line prefix: {p}"),
802 MtlParseErrorKind::MissingField(field) => write!(f, "missing {field}"),
803 MtlParseErrorKind::InvalidNumber { field, value } => {
804 write!(f, "invalid {field}: {value}")
805 }
806 MtlParseErrorKind::InvalidIlluminationModel(v) => {
807 write!(f, "invalid illumination model: {v}")
808 }
809 MtlParseErrorKind::Custom(s) => write!(f, "{s}"),
810 }
811 }
812}
813
814impl StdError for MtlError {
815 fn source(&self) -> Option<&(dyn StdError + 'static)> {
816 match self {
817 MtlError::Io(e) => Some(e),
818 MtlError::Parse { .. } => None,
819 }
820 }
821}
822
823impl From<io::Error> for MtlError {
824 fn from(e: io::Error) -> Self {
825 MtlError::Io(e)
826 }
827}
828
829impl From<MtlError> for io::Error {
830 fn from(e: MtlError) -> Self {
831 match e {
832 MtlError::Io(inner) => inner,
833 parse @ MtlError::Parse { .. } => {
834 io::Error::new(io::ErrorKind::InvalidData, parse.to_string())
835 }
836 }
837 }
838}
839
840#[derive(Debug, Clone, Copy, PartialEq, Eq)]
846pub enum MapKind {
847 Ambient,
849 Diffuse,
851 Specular,
853 Emissive,
855 SpecularExponent,
857 Dissolve,
859 Bump,
861 MapBump,
863 Displacement,
865 Decal,
867 Reflection,
869}
870
871impl MapKind {
872 fn from_prefix(s: &str) -> Option<Self> {
873 Some(match s {
874 "map_Ka" => MapKind::Ambient,
875 "map_Kd" => MapKind::Diffuse,
876 "map_Ks" => MapKind::Specular,
877 "map_Ke" => MapKind::Emissive,
878 "map_Ns" => MapKind::SpecularExponent,
879 "map_d" => MapKind::Dissolve,
880 "bump" => MapKind::Bump,
881 "map_bump" => MapKind::MapBump,
882 "disp" => MapKind::Displacement,
883 "decal" => MapKind::Decal,
884 "refl" => MapKind::Reflection,
885 _ => return None,
886 })
887 }
888
889 fn as_prefix(self) -> &'static str {
890 match self {
891 MapKind::Ambient => "map_Ka",
892 MapKind::Diffuse => "map_Kd",
893 MapKind::Specular => "map_Ks",
894 MapKind::Emissive => "map_Ke",
895 MapKind::SpecularExponent => "map_Ns",
896 MapKind::Dissolve => "map_d",
897 MapKind::Bump => "bump",
898 MapKind::MapBump => "map_bump",
899 MapKind::Displacement => "disp",
900 MapKind::Decal => "decal",
901 MapKind::Reflection => "refl",
902 }
903 }
904}
905
906pub trait MtlReader<F: ObjFloat = f64> {
913 fn read_comment(&mut self, comment: &str);
914 fn read_new_material(&mut self, name: &str);
915 fn read_ambient(&mut self, r: F, g: Option<F>, b: Option<F>);
916 fn read_diffuse(&mut self, r: F, g: Option<F>, b: Option<F>);
917 fn read_specular(&mut self, r: F, g: Option<F>, b: Option<F>);
918 fn read_specular_exponent(&mut self, value: F);
919 fn read_dissolve(&mut self, value: F);
920 fn read_illumination_model(&mut self, value: u32);
921
922 fn read_map(&mut self, kind: MapKind, args: &str);
927
928 fn read_emissive(&mut self, _r: F, _g: Option<F>, _b: Option<F>) {}
930 fn read_optical_density(&mut self, _value: F) {}
932 fn read_transparency(&mut self, _value: F) {}
935
936 fn read_unknown(&mut self, prefix: &str, _rest: &str, line: usize) -> Result<(), MtlError> {
939 Err(MtlError::Parse {
940 line,
941 kind: MtlParseErrorKind::UnknownPrefix(prefix.to_string()),
942 })
943 }
944}
945
946pub trait MtlWriter<F: ObjFloat = f64> {
948 fn write_comment<S: AsRef<str>>(&mut self, comment: S) -> io::Result<()>;
949 fn write_new_material<S: AsRef<str>>(&mut self, name: S) -> io::Result<()>;
950 fn write_ambient(&mut self, r: F, g: Option<F>, b: Option<F>) -> io::Result<()>;
951 fn write_diffuse(&mut self, r: F, g: Option<F>, b: Option<F>) -> io::Result<()>;
952 fn write_specular(&mut self, r: F, g: Option<F>, b: Option<F>) -> io::Result<()>;
953 fn write_emissive(&mut self, r: F, g: Option<F>, b: Option<F>) -> io::Result<()>;
954 fn write_specular_exponent(&mut self, value: F) -> io::Result<()>;
955 fn write_optical_density(&mut self, value: F) -> io::Result<()>;
956 fn write_dissolve(&mut self, value: F) -> io::Result<()>;
957 fn write_transparency(&mut self, value: F) -> io::Result<()>;
958 fn write_illumination_model(&mut self, value: u32) -> io::Result<()>;
959 fn write_map<S: AsRef<str>>(&mut self, kind: MapKind, args: S) -> io::Result<()>;
960}
961
962pub fn read_mtl_file<R: io::Read, T: MtlReader<F>, F: ObjFloat>(
963 reader: R,
964 mtl_reader: &mut T,
965) -> Result<(), MtlError>
966where
967 <F as FromStr>::Err: Display,
968{
969 let mut buf_reader = BufReader::new(reader);
970 let mut line = String::new();
971 let mut lineno: usize = 0;
972
973 while buf_reader.read_line(&mut line)? != 0 {
974 lineno += 1;
975 let trimmed = line.trim();
976 if trimmed.is_empty() {
977 line.clear();
978 continue;
979 }
980 let mut parts = trimmed.split_whitespace();
981 let prefix = parts.next().ok_or(MtlError::Parse {
982 line: lineno,
983 kind: MtlParseErrorKind::EmptyPrefix,
984 })?;
985
986 let parse_f = |s: &str, field: &'static str| -> Result<F, MtlError> {
987 s.parse::<F>().map_err(|_| MtlError::Parse {
988 line: lineno,
989 kind: MtlParseErrorKind::InvalidNumber {
990 field,
991 value: s.to_string(),
992 },
993 })
994 };
995
996 let missing = |field: &'static str| -> MtlError {
997 MtlError::Parse {
998 line: lineno,
999 kind: MtlParseErrorKind::MissingField(field),
1000 }
1001 };
1002
1003 let parse_color = |parts: &mut std::str::SplitWhitespace<'_>,
1004 field_r: &'static str,
1005 field_g: &'static str,
1006 field_b: &'static str|
1007 -> Result<(F, Option<F>, Option<F>), MtlError> {
1008 let r = parts
1009 .next()
1010 .ok_or_else(|| missing(field_r))
1011 .and_then(|s| parse_f(s, field_r))?;
1012 let g = match parts.next() {
1013 Some(s) => Some(parse_f(s, field_g)?),
1014 None => None,
1015 };
1016 let b = match parts.next() {
1017 Some(s) => Some(parse_f(s, field_b)?),
1018 None => None,
1019 };
1020 Ok((r, g, b))
1021 };
1022
1023 match prefix {
1024 "#" => {
1025 let comment = parts.collect::<Vec<&str>>().join(" ");
1026 mtl_reader.read_comment(&comment);
1027 }
1028 "newmtl" => {
1029 let name = parts.collect::<Vec<&str>>().join(" ");
1030 if name.is_empty() {
1031 return Err(missing("material name"));
1032 }
1033 mtl_reader.read_new_material(&name);
1034 }
1035 "Ka" => {
1036 let (r, g, b) = parse_color(&mut parts, "Ka r", "Ka g", "Ka b")?;
1037 mtl_reader.read_ambient(r, g, b);
1038 }
1039 "Kd" => {
1040 let (r, g, b) = parse_color(&mut parts, "Kd r", "Kd g", "Kd b")?;
1041 mtl_reader.read_diffuse(r, g, b);
1042 }
1043 "Ks" => {
1044 let (r, g, b) = parse_color(&mut parts, "Ks r", "Ks g", "Ks b")?;
1045 mtl_reader.read_specular(r, g, b);
1046 }
1047 "Ke" => {
1048 let (r, g, b) = parse_color(&mut parts, "Ke r", "Ke g", "Ke b")?;
1049 mtl_reader.read_emissive(r, g, b);
1050 }
1051 "Ns" => {
1052 let v = parts
1053 .next()
1054 .ok_or_else(|| missing("Ns"))
1055 .and_then(|s| parse_f(s, "Ns"))?;
1056 mtl_reader.read_specular_exponent(v);
1057 }
1058 "Ni" => {
1059 let v = parts
1060 .next()
1061 .ok_or_else(|| missing("Ni"))
1062 .and_then(|s| parse_f(s, "Ni"))?;
1063 mtl_reader.read_optical_density(v);
1064 }
1065 "d" => {
1066 let v = parts
1067 .next()
1068 .ok_or_else(|| missing("d"))
1069 .and_then(|s| parse_f(s, "d"))?;
1070 mtl_reader.read_dissolve(v);
1071 }
1072 "Tr" => {
1073 let v = parts
1074 .next()
1075 .ok_or_else(|| missing("Tr"))
1076 .and_then(|s| parse_f(s, "Tr"))?;
1077 mtl_reader.read_transparency(v);
1078 }
1079 "illum" => {
1080 let s = parts.next().ok_or_else(|| missing("illum"))?;
1081 let v = s.parse::<u32>().map_err(|_| MtlError::Parse {
1082 line: lineno,
1083 kind: MtlParseErrorKind::InvalidIlluminationModel(s.to_string()),
1084 })?;
1085 mtl_reader.read_illumination_model(v);
1086 }
1087 other => {
1088 if let Some(kind) = MapKind::from_prefix(other) {
1089 let args = parts.collect::<Vec<&str>>().join(" ");
1090 if args.is_empty() {
1091 return Err(missing("map filename"));
1092 }
1093 mtl_reader.read_map(kind, &args);
1094 } else {
1095 let rest = parts.collect::<Vec<&str>>().join(" ");
1096 mtl_reader.read_unknown(other, &rest, lineno)?;
1097 }
1098 }
1099 }
1100
1101 line.clear();
1102 }
1103
1104 Ok(())
1105}
1106
1107pub struct IoMtlWriter<W: io::Write, F: ObjFloat = f64> {
1109 out: W,
1110 line_buf: Vec<u8>,
1111 _phantom: std::marker::PhantomData<F>,
1112}
1113
1114impl<W: io::Write, F: ObjFloat> IoMtlWriter<W, F> {
1115 pub fn new(writer: W) -> Self {
1116 IoMtlWriter {
1117 out: writer,
1118 line_buf: Vec::with_capacity(256),
1119 _phantom: std::marker::PhantomData,
1120 }
1121 }
1122
1123 pub fn into_inner(self) -> W {
1125 self.out
1126 }
1127
1128 #[inline]
1129 fn push_str(&mut self, s: &str) {
1130 self.line_buf.extend_from_slice(s.as_bytes());
1131 }
1132
1133 #[inline]
1134 fn push_u<T: itoa::Integer>(&mut self, v: T) {
1135 let mut buf = itoa::Buffer::new();
1136 self.push_str(buf.format(v));
1137 }
1138
1139 #[inline]
1140 fn push_f(&mut self, v: F) {
1141 self.push_str(&format!("{}", v));
1142 }
1143
1144 #[inline]
1145 fn flush_line(&mut self) -> io::Result<()> {
1146 self.line_buf.push(b'\n');
1147 self.out.write_all(&self.line_buf)?;
1148 self.line_buf.clear();
1149 Ok(())
1150 }
1151
1152 fn write_color(&mut self, prefix: &str, r: F, g: Option<F>, b: Option<F>) -> io::Result<()> {
1153 self.push_str(prefix);
1154 self.push_str(" ");
1155 self.push_f(r);
1156 if let Some(gv) = g {
1157 self.push_str(" ");
1158 self.push_f(gv);
1159 }
1160 if let Some(bv) = b {
1161 self.push_str(" ");
1162 self.push_f(bv);
1163 }
1164 self.flush_line()
1165 }
1166}
1167
1168impl<W: io::Write, F: ObjFloat> MtlWriter<F> for IoMtlWriter<W, F> {
1169 fn write_comment<S: AsRef<str>>(&mut self, comment: S) -> io::Result<()> {
1170 self.push_str("# ");
1171 self.push_str(comment.as_ref());
1172 self.flush_line()
1173 }
1174
1175 fn write_new_material<S: AsRef<str>>(&mut self, name: S) -> io::Result<()> {
1176 self.push_str("newmtl ");
1177 self.push_str(name.as_ref());
1178 self.flush_line()
1179 }
1180
1181 fn write_ambient(&mut self, r: F, g: Option<F>, b: Option<F>) -> io::Result<()> {
1182 self.write_color("Ka", r, g, b)
1183 }
1184
1185 fn write_diffuse(&mut self, r: F, g: Option<F>, b: Option<F>) -> io::Result<()> {
1186 self.write_color("Kd", r, g, b)
1187 }
1188
1189 fn write_specular(&mut self, r: F, g: Option<F>, b: Option<F>) -> io::Result<()> {
1190 self.write_color("Ks", r, g, b)
1191 }
1192
1193 fn write_emissive(&mut self, r: F, g: Option<F>, b: Option<F>) -> io::Result<()> {
1194 self.write_color("Ke", r, g, b)
1195 }
1196
1197 fn write_specular_exponent(&mut self, value: F) -> io::Result<()> {
1198 self.push_str("Ns ");
1199 self.push_f(value);
1200 self.flush_line()
1201 }
1202
1203 fn write_optical_density(&mut self, value: F) -> io::Result<()> {
1204 self.push_str("Ni ");
1205 self.push_f(value);
1206 self.flush_line()
1207 }
1208
1209 fn write_dissolve(&mut self, value: F) -> io::Result<()> {
1210 self.push_str("d ");
1211 self.push_f(value);
1212 self.flush_line()
1213 }
1214
1215 fn write_transparency(&mut self, value: F) -> io::Result<()> {
1216 self.push_str("Tr ");
1217 self.push_f(value);
1218 self.flush_line()
1219 }
1220
1221 fn write_illumination_model(&mut self, value: u32) -> io::Result<()> {
1222 self.push_str("illum ");
1223 self.push_u(value);
1224 self.flush_line()
1225 }
1226
1227 fn write_map<S: AsRef<str>>(&mut self, kind: MapKind, args: S) -> io::Result<()> {
1228 self.push_str(kind.as_prefix());
1229 self.push_str(" ");
1230 self.push_str(args.as_ref());
1231 self.flush_line()
1232 }
1233}
1234
1235#[cfg(test)]
1236mod tests {
1237 use super::*;
1238 use pretty_assertions::assert_eq;
1239 use std::io::Cursor;
1240
1241 type Face = Vec<(usize, Option<usize>, Option<usize>)>;
1242
1243 #[derive(Default)]
1244 struct TestObjReader64 {
1245 comments: Vec<String>,
1246 names: Vec<String>,
1247 vertices: Vec<(f64, f64, f64, Option<f64>)>,
1248 texture_coordinates: Vec<(f64, Option<f64>, Option<f64>)>,
1249 normals: Vec<(f64, f64, f64)>,
1250 faces: Vec<Face>,
1251 }
1252
1253 impl ObjReader for TestObjReader64 {
1254 fn read_comment(&mut self, comment: &str) {
1255 self.comments.push(comment.to_string());
1256 }
1257
1258 fn read_object_name(&mut self, name: &str) {
1259 self.names.push(name.to_string());
1260 }
1261
1262 fn read_vertex(&mut self, x: f64, y: f64, z: f64, w: Option<f64>) {
1263 self.vertices.push((x, y, z, w));
1264 }
1265
1266 fn read_texture_coordinate(&mut self, u: f64, v: Option<f64>, w: Option<f64>) {
1267 self.texture_coordinates.push((u, v, w));
1268 }
1269
1270 fn read_normal(&mut self, nx: f64, ny: f64, nz: f64) {
1271 self.normals.push((nx, ny, nz));
1272 }
1273
1274 fn read_face(&mut self, vertex_indices: &[(usize, Option<usize>, Option<usize>)]) {
1275 self.faces.push(vertex_indices.to_vec());
1276 }
1277 }
1278
1279 #[test]
1280 fn test_obj_reading_2() {
1281 let input = "# This is a test OBJ file
1282o TestObject
1283v 1 2 3
1284vt 0.5 0.5
1285vn 0 1 1.1
1286f 1/1/1 2/2/2 3/3/3
1287";
1288
1289 let reader = Cursor::new(input);
1290 let mut test_reader: TestObjReader64 = Default::default();
1291 read_obj_file(reader, &mut test_reader).unwrap();
1292 assert_eq!(test_reader.comments, vec!["This is a test OBJ file"]);
1293 assert_eq!(test_reader.names, vec!["TestObject"]);
1294 assert_eq!(test_reader.vertices, vec![(1.0, 2.0, 3.0, None)]);
1295 assert_eq!(
1296 test_reader.texture_coordinates,
1297 vec![(0.5, Some(0.5), None)]
1298 );
1299 assert_eq!(test_reader.normals, vec![(0.0, 1.0, 1.1)]);
1300 assert_eq!(
1301 test_reader.faces,
1302 vec![vec![
1303 (1, Some(1), Some(1)),
1304 (2, Some(2), Some(2)),
1305 (3, Some(3), Some(3))
1306 ]]
1307 );
1308 }
1309
1310 #[test]
1311 fn test_obj_writing() {
1312 let mut buffer = Vec::new();
1313 let mut writer = IoObjWriter::new(&mut buffer);
1314 writer.write_comment("This is a test OBJ file").unwrap();
1315 writer.write_object_name("TestObject").unwrap();
1316 writer.write_vertex(1.0, 2.0, 3.0, None).unwrap();
1317 writer
1318 .write_texture_coordinate(0.5, Some(0.5), None)
1319 .unwrap();
1320 writer.write_normal(0.0, 1.0, 1.1).unwrap();
1321 writer
1322 .write_face(&[
1323 (1, Some(1), Some(1)),
1324 (2, Some(2), Some(2)),
1325 (3, Some(3), Some(3)),
1326 ])
1327 .unwrap();
1328
1329 let output = String::from_utf8(buffer).unwrap();
1330 let expected_output = "# This is a test OBJ file
1331o TestObject
1332v 1 2 3
1333vt 0.5 0.5
1334vn 0 1 1.1
1335f 1/1/1 2/2/2 3/3/3
1336";
1337 assert_eq!(output, expected_output);
1338 }
1339
1340 #[derive(Default)]
1343 struct TestObjReader32 {
1344 vertices: Vec<(f32, f32, f32, Option<f32>)>,
1345 texture_coordinates: Vec<(f32, Option<f32>, Option<f32>)>,
1346 normals: Vec<(f32, f32, f32)>,
1347 }
1348
1349 impl ObjReader<f32> for TestObjReader32 {
1350 fn read_comment(&mut self, _comment: &str) {}
1351 fn read_object_name(&mut self, _name: &str) {}
1352
1353 fn read_vertex(&mut self, x: f32, y: f32, z: f32, w: Option<f32>) {
1354 self.vertices.push((x, y, z, w));
1355 }
1356
1357 fn read_texture_coordinate(&mut self, u: f32, v: Option<f32>, w: Option<f32>) {
1358 self.texture_coordinates.push((u, v, w));
1359 }
1360
1361 fn read_normal(&mut self, nx: f32, ny: f32, nz: f32) {
1362 self.normals.push((nx, ny, nz));
1363 }
1364
1365 fn read_face(&mut self, _vertex_indices: &[(usize, Option<usize>, Option<usize>)]) {}
1366 }
1367
1368 #[test]
1369 fn test_obj_f32_reading() {
1370 let input = "o TestObject
1371v 1.5 2.5 3.5
1372vt 0.25 0.75
1373vn 0.0 1.0 0.0
1374";
1375
1376 let reader = Cursor::new(input);
1377 let mut test_reader = TestObjReader32::default();
1378 read_obj_file(reader, &mut test_reader).unwrap();
1379
1380 assert_eq!(test_reader.vertices, vec![(1.5f32, 2.5f32, 3.5f32, None)]);
1381 assert_eq!(
1382 test_reader.texture_coordinates,
1383 vec![(0.25f32, Some(0.75f32), None)]
1384 );
1385 assert_eq!(test_reader.normals, vec![(0.0f32, 1.0f32, 0.0f32)]);
1386 }
1387
1388 #[test]
1389 fn test_obj_f32_writing() {
1390 let mut buffer = Vec::new();
1391 let mut writer: IoObjWriter<_, f32> = IoObjWriter::new(&mut buffer);
1392
1393 writer.write_comment("f32 test").unwrap();
1394 writer.write_object_name("F32Object").unwrap();
1395 writer.write_vertex(1.5f32, 2.5f32, 3.5f32, None).unwrap();
1396 writer
1397 .write_texture_coordinate(0.25f32, Some(0.75f32), None)
1398 .unwrap();
1399 writer.write_normal(0.0f32, 1.0f32, 0.0f32).unwrap();
1400
1401 let output = String::from_utf8(buffer).unwrap();
1402 let expected = "# f32 test
1403o F32Object
1404v 1.5 2.5 3.5
1405vt 0.25 0.75
1406vn 0 1 0
1407";
1408 assert_eq!(output, expected);
1409 }
1410
1411 #[test]
1412 fn test_obj_f32_round_trip() {
1413 let input = "o Test
1415v 0.123456789 0.987654321 1.5
1416vn 0.577 0.577 0.577
1417";
1418
1419 let reader = Cursor::new(input);
1420 let mut test_reader = TestObjReader32::default();
1421 read_obj_file(reader, &mut test_reader).unwrap();
1422
1423 let mut buffer = Vec::new();
1425 let mut writer: IoObjWriter<_, f32> = IoObjWriter::new(&mut buffer);
1426 writer.write_object_name("Test").unwrap();
1427 for (x, y, z, w) in &test_reader.vertices {
1428 writer.write_vertex(*x, *y, *z, *w).unwrap();
1429 }
1430 for (nx, ny, nz) in &test_reader.normals {
1431 writer.write_normal(*nx, *ny, *nz).unwrap();
1432 }
1433
1434 let output = String::from_utf8(buffer).unwrap();
1435
1436 assert!(output.contains("o Test"));
1438 assert!(output.contains("v "));
1439 assert!(output.contains("vn "));
1440 }
1441
1442 #[test]
1443 fn test_printf_f_formatting() {
1444 let mut buffer = Vec::new();
1445 let mut writer: IoObjWriter<_, f64> = IoObjWriter::new_with_printf_f_format(&mut buffer);
1446
1447 writer.write_object_name("PrintfFTest").unwrap();
1448 writer
1449 .write_vertex(754.4214477539063, 1753.2353515625, -91.72238159179688, None)
1450 .unwrap();
1451 writer
1452 .write_texture_coordinate(0.123456789, Some(0.987654321), None)
1453 .unwrap();
1454 writer
1455 .write_normal(0.5773502691896257, 0.5773502691896257, 0.5773502691896257)
1456 .unwrap();
1457
1458 let output = String::from_utf8(buffer).unwrap();
1459
1460 let expected = "o PrintfFTest
1462v 754.421448 1753.235352 -91.722382
1463vt 0.123457 0.987654
1464vn 0.577350 0.577350 0.577350
1465";
1466 assert_eq!(output, expected);
1467 }
1468
1469 #[test]
1470 fn test_printf_f_flag_toggle() {
1471 let mut buffer = Vec::new();
1473 let mut writer: IoObjWriter<_, f32> = IoObjWriter::new(&mut buffer);
1474
1475 writer
1477 .write_vertex(1.2345678_f32, 2.345678_f32, 3.45678_f32, None)
1478 .unwrap();
1479
1480 writer.set_printf_f_format(true);
1482 writer
1483 .write_vertex(1.2345678_f32, 2.3456789_f32, 3.456789_f32, None)
1484 .unwrap();
1485
1486 let output = String::from_utf8(buffer).unwrap();
1487
1488 let lines: Vec<&str> = output.lines().collect();
1490 assert_eq!(lines.len(), 2);
1491
1492 assert_eq!(lines[0], "v 1.2345678 2.345678 3.45678");
1494
1495 assert_eq!(lines[1], "v 1.234568 2.345679 3.456789");
1497 }
1498
1499 #[derive(Default, Debug, PartialEq)]
1504 struct ExtendedReader32 {
1505 material_libs: Vec<Vec<String>>,
1506 use_materials: Vec<String>,
1507 groups: Vec<Vec<String>>,
1508 smoothing_groups: Vec<SmoothingGroup>,
1509 line_elements: Vec<Vec<(usize, Option<usize>)>>,
1510 point_elements: Vec<Vec<usize>>,
1511 }
1512
1513 impl ObjReader<f32> for ExtendedReader32 {
1514 fn read_comment(&mut self, _: &str) {}
1515 fn read_object_name(&mut self, _: &str) {}
1516 fn read_vertex(&mut self, _: f32, _: f32, _: f32, _: Option<f32>) {}
1517 fn read_texture_coordinate(&mut self, _: f32, _: Option<f32>, _: Option<f32>) {}
1518 fn read_normal(&mut self, _: f32, _: f32, _: f32) {}
1519 fn read_face(&mut self, _: &[(usize, Option<usize>, Option<usize>)]) {}
1520
1521 fn read_material_lib(&mut self, names: &[&str]) {
1522 self.material_libs
1523 .push(names.iter().map(|s| s.to_string()).collect());
1524 }
1525 fn read_use_material(&mut self, name: &str) {
1526 self.use_materials.push(name.to_string());
1527 }
1528 fn read_group(&mut self, names: &[&str]) {
1529 self.groups
1530 .push(names.iter().map(|s| s.to_string()).collect());
1531 }
1532 fn read_smoothing_group(&mut self, group: SmoothingGroup) {
1533 self.smoothing_groups.push(group);
1534 }
1535 fn read_line_element(&mut self, indices: &[(usize, Option<usize>)]) {
1536 self.line_elements.push(indices.to_vec());
1537 }
1538 fn read_point_element(&mut self, indices: &[usize]) {
1539 self.point_elements.push(indices.to_vec());
1540 }
1541 }
1542
1543 #[test]
1544 fn read_mtllib_and_usemtl() {
1545 let input = "mtllib first.mtl second.mtl
1546usemtl SomeMaterial
1547";
1548 let mut reader = ExtendedReader32::default();
1549 read_obj_file(Cursor::new(input), &mut reader).unwrap();
1550 assert_eq!(
1551 reader.material_libs,
1552 vec![vec!["first.mtl".to_string(), "second.mtl".to_string()]]
1553 );
1554 assert_eq!(reader.use_materials, vec!["SomeMaterial".to_string()]);
1555 }
1556
1557 #[test]
1558 fn read_group_with_multiple_names() {
1559 let input = "g cube top
1560g default
1561";
1562 let mut reader = ExtendedReader32::default();
1563 read_obj_file(Cursor::new(input), &mut reader).unwrap();
1564 assert_eq!(
1565 reader.groups,
1566 vec![
1567 vec!["cube".to_string(), "top".to_string()],
1568 vec!["default".to_string()],
1569 ]
1570 );
1571 }
1572
1573 #[test]
1574 fn read_smoothing_group_off_zero_and_named() {
1575 let input = "s off
1576s 0
1577s 1
1578s 42
1579";
1580 let mut reader = ExtendedReader32::default();
1581 read_obj_file(Cursor::new(input), &mut reader).unwrap();
1582 assert_eq!(
1583 reader.smoothing_groups,
1584 vec![
1585 SmoothingGroup::Off,
1586 SmoothingGroup::Off,
1587 SmoothingGroup::Group(1),
1588 SmoothingGroup::Group(42),
1589 ]
1590 );
1591 }
1592
1593 #[test]
1594 fn read_smoothing_group_invalid_value_errors() {
1595 let input = "s notanumber\n";
1596 let mut reader = ExtendedReader32::default();
1597 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1598 assert!(
1599 err.to_string().contains("invalid smoothing group"),
1600 "got: {}",
1601 err
1602 );
1603 }
1604
1605 #[test]
1606 fn read_line_and_point_elements() {
1607 let input = "l 1 2 3
1608l 4/1 5/2 6/3
1609p 1 2 3 4
1610";
1611 let mut reader = ExtendedReader32::default();
1612 read_obj_file(Cursor::new(input), &mut reader).unwrap();
1613 assert_eq!(
1614 reader.line_elements,
1615 vec![
1616 vec![(1, None), (2, None), (3, None)],
1617 vec![(4, Some(1)), (5, Some(2)), (6, Some(3))],
1618 ]
1619 );
1620 assert_eq!(reader.point_elements, vec![vec![1, 2, 3, 4]]);
1621 }
1622
1623 #[test]
1624 fn read_line_element_with_one_vertex_errors() {
1625 let mut reader = ExtendedReader32::default();
1626 let err = read_obj_file(Cursor::new("l 1\n"), &mut reader).unwrap_err();
1627 assert!(
1628 err.to_string().contains("at least 2 vertices"),
1629 "got: {}",
1630 err
1631 );
1632 }
1633
1634 #[test]
1635 fn write_directive_round_trip() {
1636 let mut buffer = Vec::new();
1638 {
1639 let mut writer: IoObjWriter<_, f32> = IoObjWriter::new(&mut buffer);
1640 writer
1641 .write_material_lib(&["lib1.mtl", "lib2.mtl"])
1642 .unwrap();
1643 writer.write_use_material("Wood").unwrap();
1644 writer.write_group(&["cube", "top"]).unwrap();
1645 writer.write_smoothing_group(SmoothingGroup::Off).unwrap();
1646 writer
1647 .write_smoothing_group(SmoothingGroup::Group(7))
1648 .unwrap();
1649 writer
1650 .write_line_element(&[(1, None), (2, Some(2)), (3, None)])
1651 .unwrap();
1652 writer.write_point_element(&[1, 2, 3]).unwrap();
1653 }
1654 let text = String::from_utf8(buffer.clone()).unwrap();
1655 let expected = "mtllib lib1.mtl lib2.mtl
1656usemtl Wood
1657g cube top
1658s off
1659s 7
1660l 1 2/2 3
1661p 1 2 3
1662";
1663 assert_eq!(text, expected);
1664
1665 let mut reader = ExtendedReader32::default();
1666 read_obj_file(Cursor::new(buffer), &mut reader).unwrap();
1667 assert_eq!(
1668 reader.material_libs,
1669 vec![vec!["lib1.mtl".to_string(), "lib2.mtl".to_string()]]
1670 );
1671 assert_eq!(reader.use_materials, vec!["Wood".to_string()]);
1672 assert_eq!(
1673 reader.groups,
1674 vec![vec!["cube".to_string(), "top".to_string()]]
1675 );
1676 assert_eq!(
1677 reader.smoothing_groups,
1678 vec![SmoothingGroup::Off, SmoothingGroup::Group(7)]
1679 );
1680 assert_eq!(
1681 reader.line_elements,
1682 vec![vec![(1, None), (2, Some(2)), (3, None)]]
1683 );
1684 assert_eq!(reader.point_elements, vec![vec![1, 2, 3]]);
1685 }
1686
1687 #[test]
1688 fn unknown_prefix_still_errors_by_default() {
1689 let input = "vp 0.1 0.2 0.3\n";
1692 let mut reader = ExtendedReader32::default();
1693 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1694 assert!(
1695 err.to_string().contains("Unknown line prefix: vp"),
1696 "got: {}",
1697 err
1698 );
1699 }
1700
1701 #[test]
1705 fn typed_error_unknown_prefix_carries_prefix_and_line() {
1706 let input = "v 0 0 0\nvp 0.1 0.2\n";
1707 let mut reader = ExtendedReader32::default();
1708 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1709 match err {
1710 ObjError::Parse {
1711 line,
1712 kind: ParseErrorKind::UnknownPrefix(prefix),
1713 } => {
1714 assert_eq!(line, 2);
1715 assert_eq!(prefix, "vp");
1716 }
1717 other => panic!("wrong error variant: {:?}", other),
1718 }
1719 }
1720
1721 #[test]
1722 fn typed_error_invalid_number_carries_field_and_value() {
1723 let input = "v 1.0 nope 3.0\n";
1724 let mut reader = ExtendedReader32::default();
1725 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1726 match err {
1727 ObjError::Parse {
1728 line: 1,
1729 kind: ParseErrorKind::InvalidNumber { field, value },
1730 } => {
1731 assert_eq!(field, "vertex y");
1732 assert_eq!(value, "nope");
1733 }
1734 other => panic!("wrong error variant: {:?}", other),
1735 }
1736 }
1737
1738 #[test]
1739 fn typed_error_invalid_index_for_face() {
1740 let input = "v 0 0 0\nf 0/0/0 0/0/0 0/0/0\n";
1742 let mut reader = ExtendedReader32::default();
1743 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1744 match err {
1745 ObjError::Parse {
1746 line: 2,
1747 kind: ParseErrorKind::InvalidIndex { kind, value },
1748 } => {
1749 assert_eq!(kind, "vertex");
1750 assert_eq!(value, "0");
1751 }
1752 other => panic!("wrong error variant: {:?}", other),
1753 }
1754 }
1755
1756 #[test]
1757 fn typed_error_invalid_smoothing_group() {
1758 let input = "s notanumber\n";
1759 let mut reader = ExtendedReader32::default();
1760 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1761 match err {
1762 ObjError::Parse {
1763 line: 1,
1764 kind: ParseErrorKind::InvalidSmoothingGroup(value),
1765 } => assert_eq!(value, "notanumber"),
1766 other => panic!("wrong error variant: {:?}", other),
1767 }
1768 }
1769
1770 #[test]
1771 fn typed_error_missing_field() {
1772 let input = "v 1.0 2.0\n";
1773 let mut reader = ExtendedReader32::default();
1774 let err = read_obj_file(Cursor::new(input), &mut reader).unwrap_err();
1775 match err {
1776 ObjError::Parse {
1777 line: 1,
1778 kind: ParseErrorKind::MissingField(field),
1779 } => assert_eq!(field, "vertex z"),
1780 other => panic!("wrong error variant: {:?}", other),
1781 }
1782 }
1783
1784 #[test]
1785 fn typed_error_converts_to_io_error() {
1786 fn legacy_caller<R: io::Read>(input: R) -> io::Result<()> {
1789 let mut reader = ExtendedReader32::default();
1790 read_obj_file(input, &mut reader)?;
1791 Ok(())
1792 }
1793
1794 let err = legacy_caller(Cursor::new("vp 0.1 0.2\n")).unwrap_err();
1795 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
1796 assert!(err.to_string().contains("Unknown line prefix: vp"));
1797 }
1798
1799 #[test]
1800 fn typed_error_io_failure_propagates() {
1801 struct FailingRead;
1803 impl io::Read for FailingRead {
1804 fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
1805 Err(io::Error::other("disk on fire"))
1806 }
1807 }
1808
1809 let mut reader = ExtendedReader32::default();
1810 let err = read_obj_file(FailingRead, &mut reader).unwrap_err();
1811 match err {
1812 ObjError::Io(inner) => {
1813 assert_eq!(inner.to_string(), "disk on fire");
1814 }
1815 other => panic!("wrong error variant: {:?}", other),
1816 }
1817 }
1818
1819 #[test]
1820 fn custom_read_unknown_can_return_objerror() {
1821 struct StrictCustom;
1823 impl ObjReader<f32> for StrictCustom {
1824 fn read_comment(&mut self, _: &str) {}
1825 fn read_object_name(&mut self, _: &str) {}
1826 fn read_vertex(&mut self, _: f32, _: f32, _: f32, _: Option<f32>) {}
1827 fn read_texture_coordinate(&mut self, _: f32, _: Option<f32>, _: Option<f32>) {}
1828 fn read_normal(&mut self, _: f32, _: f32, _: f32) {}
1829 fn read_face(&mut self, _: &[(usize, Option<usize>, Option<usize>)]) {}
1830 fn read_unknown(&mut self, prefix: &str, _: &str, line: usize) -> Result<(), ObjError> {
1831 Err(ObjError::Parse {
1832 line,
1833 kind: ParseErrorKind::Custom(format!("nope: {prefix}")),
1834 })
1835 }
1836 }
1837
1838 let err = read_obj_file(Cursor::new("vp 0 0\n"), &mut StrictCustom).unwrap_err();
1839 match err {
1840 ObjError::Parse {
1841 line: 1,
1842 kind: ParseErrorKind::Custom(msg),
1843 } => assert_eq!(msg, "nope: vp"),
1844 other => panic!("wrong error variant: {:?}", other),
1845 }
1846 }
1847
1848 #[derive(Default, Debug, PartialEq)]
1853 struct CapturingMtlReader {
1854 comments: Vec<String>,
1855 materials: Vec<String>,
1856 ambients: Vec<(f64, Option<f64>, Option<f64>)>,
1857 diffuses: Vec<(f64, Option<f64>, Option<f64>)>,
1858 speculars: Vec<(f64, Option<f64>, Option<f64>)>,
1859 emissives: Vec<(f64, Option<f64>, Option<f64>)>,
1860 specular_exponents: Vec<f64>,
1861 optical_densities: Vec<f64>,
1862 dissolves: Vec<f64>,
1863 transparencies: Vec<f64>,
1864 illumination_models: Vec<u32>,
1865 maps: Vec<(MapKind, String)>,
1866 }
1867
1868 impl MtlReader for CapturingMtlReader {
1869 fn read_comment(&mut self, c: &str) {
1870 self.comments.push(c.to_string());
1871 }
1872 fn read_new_material(&mut self, name: &str) {
1873 self.materials.push(name.to_string());
1874 }
1875 fn read_ambient(&mut self, r: f64, g: Option<f64>, b: Option<f64>) {
1876 self.ambients.push((r, g, b));
1877 }
1878 fn read_diffuse(&mut self, r: f64, g: Option<f64>, b: Option<f64>) {
1879 self.diffuses.push((r, g, b));
1880 }
1881 fn read_specular(&mut self, r: f64, g: Option<f64>, b: Option<f64>) {
1882 self.speculars.push((r, g, b));
1883 }
1884 fn read_emissive(&mut self, r: f64, g: Option<f64>, b: Option<f64>) {
1885 self.emissives.push((r, g, b));
1886 }
1887 fn read_specular_exponent(&mut self, v: f64) {
1888 self.specular_exponents.push(v);
1889 }
1890 fn read_optical_density(&mut self, v: f64) {
1891 self.optical_densities.push(v);
1892 }
1893 fn read_dissolve(&mut self, v: f64) {
1894 self.dissolves.push(v);
1895 }
1896 fn read_transparency(&mut self, v: f64) {
1897 self.transparencies.push(v);
1898 }
1899 fn read_illumination_model(&mut self, v: u32) {
1900 self.illumination_models.push(v);
1901 }
1902 fn read_map(&mut self, kind: MapKind, args: &str) {
1903 self.maps.push((kind, args.to_string()));
1904 }
1905 }
1906
1907 #[test]
1908 fn read_mtl_simple_material() {
1909 let input = "# A simple material\n\
1910 newmtl Wood\n\
1911 Ka 0.2 0.2 0.2\n\
1912 Kd 0.8 0.6 0.4\n\
1913 Ks 1.0 1.0 1.0\n\
1914 Ns 100.0\n\
1915 d 1.0\n\
1916 illum 2\n\
1917 map_Kd wood.png\n";
1918 let mut reader = CapturingMtlReader::default();
1919 read_mtl_file(Cursor::new(input), &mut reader).unwrap();
1920 assert_eq!(reader.comments, vec!["A simple material"]);
1921 assert_eq!(reader.materials, vec!["Wood"]);
1922 assert_eq!(reader.ambients, vec![(0.2, Some(0.2), Some(0.2))]);
1923 assert_eq!(reader.diffuses, vec![(0.8, Some(0.6), Some(0.4))]);
1924 assert_eq!(reader.speculars, vec![(1.0, Some(1.0), Some(1.0))]);
1925 assert_eq!(reader.specular_exponents, vec![100.0]);
1926 assert_eq!(reader.dissolves, vec![1.0]);
1927 assert_eq!(reader.illumination_models, vec![2]);
1928 assert_eq!(
1929 reader.maps,
1930 vec![(MapKind::Diffuse, "wood.png".to_string())]
1931 );
1932 }
1933
1934 #[test]
1935 fn read_mtl_multiple_materials() {
1936 let input = "newmtl First\nKd 1 0 0\nnewmtl Second\nKd 0 1 0\n";
1937 let mut reader = CapturingMtlReader::default();
1938 read_mtl_file(Cursor::new(input), &mut reader).unwrap();
1939 assert_eq!(reader.materials, vec!["First", "Second"]);
1940 assert_eq!(
1941 reader.diffuses,
1942 vec![(1.0, Some(0.0), Some(0.0)), (0.0, Some(1.0), Some(0.0))]
1943 );
1944 }
1945
1946 #[test]
1947 fn read_mtl_ka_grey_single_value() {
1948 let input = "newmtl M\nKa 0.5\n";
1949 let mut reader = CapturingMtlReader::default();
1950 read_mtl_file(Cursor::new(input), &mut reader).unwrap();
1951 assert_eq!(reader.ambients, vec![(0.5, None, None)]);
1952 }
1953
1954 #[test]
1955 fn read_mtl_all_map_kinds() {
1956 let input = "newmtl M\n\
1957 map_Ka a.png\n\
1958 map_Kd d.png\n\
1959 map_Ks s.png\n\
1960 map_Ke e.png\n\
1961 map_Ns ns.png\n\
1962 map_d alpha.png\n\
1963 bump bump.png\n\
1964 map_bump bump2.png\n\
1965 disp disp.png\n\
1966 decal decal.png\n\
1967 refl -type sphere refl.png\n";
1968 let mut reader = CapturingMtlReader::default();
1969 read_mtl_file(Cursor::new(input), &mut reader).unwrap();
1970 assert_eq!(
1971 reader.maps,
1972 vec![
1973 (MapKind::Ambient, "a.png".to_string()),
1974 (MapKind::Diffuse, "d.png".to_string()),
1975 (MapKind::Specular, "s.png".to_string()),
1976 (MapKind::Emissive, "e.png".to_string()),
1977 (MapKind::SpecularExponent, "ns.png".to_string()),
1978 (MapKind::Dissolve, "alpha.png".to_string()),
1979 (MapKind::Bump, "bump.png".to_string()),
1980 (MapKind::MapBump, "bump2.png".to_string()),
1981 (MapKind::Displacement, "disp.png".to_string()),
1982 (MapKind::Decal, "decal.png".to_string()),
1983 (MapKind::Reflection, "-type sphere refl.png".to_string()),
1984 ]
1985 );
1986 }
1987
1988 #[test]
1989 fn read_mtl_unknown_prefix_errors() {
1990 let input = "newmtl M\nweirdo 1 2 3\n";
1991 let mut reader = CapturingMtlReader::default();
1992 let err = read_mtl_file(Cursor::new(input), &mut reader).unwrap_err();
1993 match err {
1994 MtlError::Parse {
1995 line: 2,
1996 kind: MtlParseErrorKind::UnknownPrefix(p),
1997 } => assert_eq!(p, "weirdo"),
1998 other => panic!("wrong error: {:?}", other),
1999 }
2000 }
2001
2002 #[test]
2003 fn read_mtl_invalid_illum_errors() {
2004 let input = "newmtl M\nillum nope\n";
2005 let mut reader = CapturingMtlReader::default();
2006 let err = read_mtl_file(Cursor::new(input), &mut reader).unwrap_err();
2007 match err {
2008 MtlError::Parse {
2009 line: 2,
2010 kind: MtlParseErrorKind::InvalidIlluminationModel(v),
2011 } => assert_eq!(v, "nope"),
2012 other => panic!("wrong error: {:?}", other),
2013 }
2014 }
2015
2016 #[test]
2017 fn write_mtl_simple_material() {
2018 let mut buffer = Vec::new();
2019 let mut writer: IoMtlWriter<_, f64> = IoMtlWriter::new(&mut buffer);
2020 writer.write_comment("A simple material").unwrap();
2021 writer.write_new_material("Wood").unwrap();
2022 writer.write_ambient(0.2, Some(0.2), Some(0.2)).unwrap();
2023 writer.write_diffuse(0.8, Some(0.6), Some(0.4)).unwrap();
2024 writer.write_specular(1.0, Some(1.0), Some(1.0)).unwrap();
2025 writer.write_specular_exponent(100.0).unwrap();
2026 writer.write_dissolve(1.0).unwrap();
2027 writer.write_illumination_model(2).unwrap();
2028 writer.write_map(MapKind::Diffuse, "wood.png").unwrap();
2029 let output = String::from_utf8(buffer).unwrap();
2030 let expected = "# A simple material\n\
2031 newmtl Wood\n\
2032 Ka 0.2 0.2 0.2\n\
2033 Kd 0.8 0.6 0.4\n\
2034 Ks 1 1 1\n\
2035 Ns 100\n\
2036 d 1\n\
2037 illum 2\n\
2038 map_Kd wood.png\n";
2039 assert_eq!(output, expected);
2040 }
2041
2042 struct WritingMtlReader {
2044 writer: IoMtlWriter<Vec<u8>>,
2045 }
2046 impl MtlReader for WritingMtlReader {
2047 fn read_comment(&mut self, c: &str) {
2048 self.writer.write_comment(c).unwrap();
2049 }
2050 fn read_new_material(&mut self, name: &str) {
2051 self.writer.write_new_material(name).unwrap();
2052 }
2053 fn read_ambient(&mut self, r: f64, g: Option<f64>, b: Option<f64>) {
2054 self.writer.write_ambient(r, g, b).unwrap();
2055 }
2056 fn read_diffuse(&mut self, r: f64, g: Option<f64>, b: Option<f64>) {
2057 self.writer.write_diffuse(r, g, b).unwrap();
2058 }
2059 fn read_specular(&mut self, r: f64, g: Option<f64>, b: Option<f64>) {
2060 self.writer.write_specular(r, g, b).unwrap();
2061 }
2062 fn read_emissive(&mut self, r: f64, g: Option<f64>, b: Option<f64>) {
2063 self.writer.write_emissive(r, g, b).unwrap();
2064 }
2065 fn read_specular_exponent(&mut self, v: f64) {
2066 self.writer.write_specular_exponent(v).unwrap();
2067 }
2068 fn read_optical_density(&mut self, v: f64) {
2069 self.writer.write_optical_density(v).unwrap();
2070 }
2071 fn read_dissolve(&mut self, v: f64) {
2072 self.writer.write_dissolve(v).unwrap();
2073 }
2074 fn read_transparency(&mut self, v: f64) {
2075 self.writer.write_transparency(v).unwrap();
2076 }
2077 fn read_illumination_model(&mut self, v: u32) {
2078 self.writer.write_illumination_model(v).unwrap();
2079 }
2080 fn read_map(&mut self, kind: MapKind, args: &str) {
2081 self.writer.write_map(kind, args).unwrap();
2082 }
2083 }
2084
2085 #[test]
2086 fn mtl_round_trip_byte_equal() {
2087 let input = "# library header\n\
2088 newmtl First\n\
2089 Ka 0.1 0.2 0.3\n\
2090 Kd 0.4 0.5 0.6\n\
2091 Ks 0.7 0.8 0.9\n\
2092 Ke 0 0 0\n\
2093 Ns 50\n\
2094 Ni 1.45\n\
2095 d 0.5\n\
2096 Tr 0.5\n\
2097 illum 2\n\
2098 map_Ka ambient.png\n\
2099 map_Kd diffuse.png\n\
2100 bump bump.png\n\
2101 map_bump bump2.png\n\
2102 refl -type sphere refl.png\n\
2103 newmtl Second\n\
2104 Kd 1 0 0\n\
2105 illum 1\n";
2106 let writer: IoMtlWriter<_, f64> = IoMtlWriter::new(Vec::new());
2107 let mut reader = WritingMtlReader { writer };
2108 read_mtl_file(Cursor::new(input), &mut reader).unwrap();
2109 let output = String::from_utf8(reader.writer.into_inner()).unwrap();
2110 assert_eq!(output, input);
2111 }
2112}