1#[cfg(feature = "genmesh")]
19pub use genmesh::{Polygon, Quad, Triangle};
20
21use std::{
22 collections::HashMap,
23 fmt,
24 fs::File,
25 io::{self, BufRead, BufReader, Error, Read, Write},
26 path::{Path, PathBuf},
27 str::FromStr,
28 sync::Arc,
29};
30
31use crate::mtl::{Material, Mtl, MtlError};
32use std::io::BufWriter;
33
34const DEFAULT_OBJECT: &str = "default";
35const DEFAULT_GROUP: &str = "default";
36
37#[derive(Copy, Clone, Debug)]
39pub struct LoadConfig {
40 pub strict: bool,
48}
49
50impl Default for LoadConfig {
51 fn default() -> Self {
52 LoadConfig { strict: true }
53 }
54}
55
56#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)]
60pub struct IndexTuple(pub usize, pub Option<usize>, pub Option<usize>);
61
62#[derive(Debug, Clone, Hash, PartialEq)]
66pub struct SimplePolygon(pub Vec<IndexTuple>);
67
68pub trait WriteToBuf {
69 type Error: std::fmt::Display;
70 fn write_to_buf<W: Write>(&self, out: &mut W) -> Result<(), Self::Error>;
71}
72
73impl std::fmt::Display for IndexTuple {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 write!(f, "{}", self.0 + 1)?;
76 if let Some(idx) = self.1 {
77 write!(f, "/{}", idx + 1)?;
78 }
79 if let Some(idx) = self.2 {
80 write!(f, "/{}", idx + 1)?;
81 }
82 Ok(())
83 }
84}
85
86impl WriteToBuf for SimplePolygon {
87 type Error = ObjError;
88 fn write_to_buf<W: Write>(&self, out: &mut W) -> Result<(), ObjError> {
89 write!(out, "f")?;
90 for idx in &self.0 {
91 write!(out, " {}", idx)?;
92 }
93 writeln!(out)?;
94 Ok(())
95 }
96}
97
98#[cfg(feature = "genmesh")]
99impl SimplePolygon {
100 pub fn into_genmesh(self) -> Polygon<IndexTuple> {
106 std::convert::TryFrom::try_from(self).unwrap()
107 }
108}
109
110#[cfg(feature = "genmesh")]
111impl std::convert::TryFrom<SimplePolygon> for Polygon<IndexTuple> {
112 type Error = ObjError;
113 fn try_from(gs: SimplePolygon) -> Result<Polygon<IndexTuple>, ObjError> {
114 match gs.0.len() {
115 3 => Ok(Polygon::PolyTri(Triangle::new(gs.0[0], gs.0[1], gs.0[2]))),
116 4 => Ok(Polygon::PolyQuad(Quad::new(gs.0[0], gs.0[1], gs.0[2], gs.0[3]))),
117 n => Err(ObjError::GenMeshWrongNumberOfVertsInPolygon { vert_count: n }),
118 }
119 }
120}
121
122#[derive(Debug)]
124pub enum ObjError {
125 Io(io::Error),
126 MalformedFaceGroup {
128 line_number: usize,
129 group: String,
130 },
131 ArgumentListFailure {
134 line_number: usize,
135 list: String,
136 },
137 UnexpectedCommand {
139 line_number: usize,
140 command: String,
141 },
142 MissingMTLName {
144 line_number: usize,
145 },
146 ZeroVertexNumber {
150 line_number: usize,
151 },
152 #[cfg(feature = "genmesh")]
154 GenMeshWrongNumberOfVertsInPolygon {
155 vert_count: usize,
156 },
157}
158
159impl std::error::Error for ObjError {
160 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
161 match self {
162 ObjError::Io(err) => Some(err),
163 _ => None,
164 }
165 }
166}
167
168impl fmt::Display for ObjError {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 match self {
171 ObjError::Io(err) => write!(f, "I/O error loading a .obj file: {}", err),
172 ObjError::MalformedFaceGroup { line_number, group } => write!(
173 f,
174 "One of the arguments to `f` is malformed (line: {}, group: {})",
175 line_number, group
176 ),
177 ObjError::ArgumentListFailure { line_number, list } => write!(
178 f,
179 "An argument list either has unparsable arguments or is missing arguments. (line: {}, list: {})",
180 line_number, list
181 ),
182 ObjError::UnexpectedCommand { line_number, command } => write!(
183 f,
184 "Command found that is not in the .obj spec. (line: {}, command: {})",
185 line_number, command
186 ),
187 ObjError::MissingMTLName { line_number } => write!(
188 f,
189 "mtllib command issued, but no name was specified. (line: {})",
190 line_number
191 ),
192 ObjError::ZeroVertexNumber { line_number } => {
193 write!(f, "Zero vertex numbers are invalid. (line: {})", line_number)
194 }
195 #[cfg(feature = "genmesh")]
196 ObjError::GenMeshWrongNumberOfVertsInPolygon { vert_count } => write!(
197 f,
198 "[`genmesh::Polygon`] only supports triangles and squares. (vertex count: {}",
199 vert_count
200 ),
201 }
202 }
203}
204
205impl From<io::Error> for ObjError {
206 fn from(e: Error) -> Self {
207 Self::Io(e)
208 }
209}
210
211#[derive(Debug)]
216pub struct MtlLibsLoadError(pub Vec<(String, MtlError)>);
217
218impl std::error::Error for MtlLibsLoadError {}
219
220impl fmt::Display for MtlLibsLoadError {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 write!(f, "One of the material libraries failed to load: {:?}", self.0)
223 }
224}
225
226impl From<Vec<(String, MtlError)>> for MtlLibsLoadError {
227 fn from(e: Vec<(String, MtlError)>) -> Self {
228 MtlLibsLoadError(e)
229 }
230}
231
232#[derive(Debug, Clone, PartialEq)]
233pub struct Object {
234 pub name: String,
236 pub groups: Vec<Group>,
238}
239
240impl Object {
241 pub fn new(name: String) -> Self {
242 Object {
243 name,
244 groups: Vec::new(),
245 }
246 }
247}
248
249impl WriteToBuf for Object {
250 type Error = ObjError;
251 fn write_to_buf<W: Write>(&self, out: &mut W) -> Result<(), ObjError> {
253 if self.name.as_str() != DEFAULT_OBJECT {
254 writeln!(out, "o {}", self.name)?;
255 }
256
257 let mut group_iter = self.groups.iter().peekable();
258 while let Some(group) = group_iter.next() {
259 group.write_to_buf(out)?;
260
261 assert!(group_iter
264 .peek()
265 .map(|next_group| next_group.index == 0 || next_group.name == group.name)
266 .unwrap_or(true));
267 }
268
269 Ok(())
270 }
271}
272
273#[derive(Debug, Clone, PartialEq)]
278pub enum ObjMaterial {
279 Ref(String),
281 Mtl(Arc<Material>),
283}
284
285impl ObjMaterial {
286 fn name(&self) -> &str {
287 match self {
288 ObjMaterial::Ref(name) => name.as_str(),
289 ObjMaterial::Mtl(material) => material.name.as_str(),
290 }
291 }
292}
293
294#[derive(Debug, Clone, PartialEq)]
295pub struct Group {
296 pub name: String,
298 pub index: usize,
303 pub material: Option<ObjMaterial>,
307 pub polys: Vec<SimplePolygon>,
309}
310
311impl Group {
312 pub fn new(name: String) -> Self {
313 Group {
314 name,
315 index: 0,
316 material: None,
317 polys: Vec::new(),
318 }
319 }
320}
321
322impl WriteToBuf for Group {
323 type Error = ObjError;
324 fn write_to_buf<W: Write>(&self, out: &mut W) -> Result<(), ObjError> {
326 if self.index == 0 {
329 writeln!(out, "g {}", self.name)?;
330 }
331
332 match self.material {
333 Some(ObjMaterial::Ref(ref name)) => writeln!(out, "usemtl {}", name)?,
334 Some(ObjMaterial::Mtl(ref mtl)) => writeln!(out, "usemtl {}", mtl.name)?,
335 None => {}
336 }
337
338 for poly in &self.polys {
339 poly.write_to_buf(out)?;
340 }
341
342 Ok(())
343 }
344}
345
346#[derive(Clone, Debug, PartialEq)]
348pub struct ObjData {
349 pub position: Vec<[f32; 3]>,
351 pub texture: Vec<[f32; 2]>,
353 pub normal: Vec<[f32; 3]>,
355 pub objects: Vec<Object>,
358 pub material_libs: Vec<Mtl>,
360}
361
362impl Default for ObjData {
363 fn default() -> Self {
364 ObjData {
365 position: Vec::new(),
366 texture: Vec::new(),
367 normal: Vec::new(),
368 objects: Vec::new(),
369 material_libs: Vec::new(),
370 }
371 }
372}
373
374#[derive(Clone, Debug)]
377pub struct Obj {
378 pub data: ObjData,
380 pub path: PathBuf,
384}
385
386fn normalize(idx: isize, len: usize) -> Option<usize> {
390 if idx < 0 {
391 Some((len as isize + idx) as usize)
392 } else if idx > 0 {
393 Some(idx as usize - 1)
394 } else {
395 None
396 }
397}
398
399impl Obj {
400 pub fn save(&self, path: impl AsRef<Path>) -> Result<(), ObjError> {
404 self.data.save(path.as_ref())
405 }
406}
407
408impl Obj {
409 pub fn load(path: impl AsRef<Path>) -> Result<Obj, ObjError> {
411 Self::load_with_config(path, LoadConfig::default())
412 }
413
414 pub fn load_with_config(path: impl AsRef<Path>, config: LoadConfig) -> Result<Obj, ObjError> {
416 Obj::load_impl(path.as_ref(), config)
417 }
418
419 fn load_impl(path: &Path, config: LoadConfig) -> Result<Obj, ObjError> {
420 let f = File::open(path)?;
421 let data = ObjData::load_buf_with_config(&f, config)?;
422
423 let path = path.parent().unwrap().to_owned();
425
426 Ok(Obj { data, path })
427 }
428
429 pub fn load_mtls(&mut self) -> Result<(), MtlLibsLoadError> {
434 self.load_mtls_fn(|obj_dir, mtllib| File::open(&obj_dir.join(mtllib)).map(BufReader::new))
435 }
436
437 pub fn load_mtls_fn<R, F>(&mut self, mut resolve: F) -> Result<(), MtlLibsLoadError>
454 where
455 R: io::BufRead,
456 F: FnMut(&Path, &str) -> io::Result<R>,
457 {
458 let mut errs = Vec::new();
459 let mut materials = HashMap::new();
460
461 for mtl_lib in &mut self.data.material_libs {
462 match mtl_lib.reload_with(&self.path, &mut resolve) {
463 Ok(mtl_lib) => {
464 for m in &mtl_lib.materials {
465 materials.entry(m.name.clone()).or_insert_with(|| Arc::clone(m));
471 }
472 }
473 Err(err) => {
474 errs.push((mtl_lib.filename.clone(), err));
475 }
476 }
477 }
478
479 for object in &mut self.data.objects {
481 for group in &mut object.groups {
482 if let Some(ref mut mat) = group.material {
483 if let Some(newmat) = materials.get(mat.name()) {
484 *mat = ObjMaterial::Mtl(Arc::clone(newmat));
485 }
486 }
487 }
488 }
489
490 if errs.is_empty() {
491 Ok(())
492 } else {
493 Err(errs.into())
494 }
495 }
496}
497
498impl ObjData {
499 pub fn save(&self, path: impl AsRef<Path>) -> Result<(), ObjError> {
503 self.save_impl(path.as_ref())
504 }
505
506 fn save_impl(&self, path: &Path) -> Result<(), ObjError> {
507 let f = File::create(path)?;
508 self.write_to_buf(&mut BufWriter::new(f))?;
509
510 let path = path.parent().unwrap();
512 self.save_mtls(path)
513 }
514
515 pub fn save_mtls(&self, base_dir: impl AsRef<Path>) -> Result<(), ObjError> {
517 self.save_mtls_with_fn(base_dir.as_ref(), |base_dir, mtllib| {
518 File::create(base_dir.join(mtllib))
519 })
520 }
521
522 pub fn save_mtls_with_fn<W: Write>(
524 &self,
525 base_dir: &Path,
526 mut resolve: impl FnMut(&Path, &str) -> io::Result<W>,
527 ) -> Result<(), ObjError> {
528 for mtl in &self.material_libs {
529 mtl.write_to_buf(&mut resolve(base_dir, &mtl.filename)?)?;
530 }
531 Ok(())
532 }
533
534 pub fn write_to_buf(&self, out: &mut impl Write) -> Result<(), ObjError> {
536 writeln!(
537 out,
538 "# Generated by the obj Rust library (https://crates.io/crates/obj)."
539 )?;
540
541 for pos in &self.position {
542 writeln!(out, "v {} {} {}", pos[0], pos[1], pos[2])?;
543 }
544 for uv in &self.texture {
545 writeln!(out, "vt {} {}", uv[0], uv[1])?;
546 }
547 for nml in &self.normal {
548 writeln!(out, "vn {} {} {}", nml[0], nml[1], nml[2])?;
549 }
550 for object in &self.objects {
551 object.write_to_buf(out)?;
552 }
553 for mtl_lib in &self.material_libs {
554 writeln!(out, "mtllib {}", mtl_lib.filename)?;
555 }
556
557 Ok(())
558 }
559}
560
561impl ObjData {
562 fn parse_two(line_number: usize, n0: Option<&str>, n1: Option<&str>) -> Result<[f32; 2], ObjError> {
563 let (n0, n1) = match (n0, n1) {
564 (Some(n0), Some(n1)) => (n0, n1),
565 _ => {
566 return Err(ObjError::ArgumentListFailure {
567 line_number,
568 list: format!("{:?} {:?}", n0, n1),
569 });
570 }
571 };
572 let normal = match (FromStr::from_str(n0), FromStr::from_str(n1)) {
573 (Ok(n0), Ok(n1)) => [n0, n1],
574 _ => {
575 return Err(ObjError::ArgumentListFailure {
576 line_number,
577 list: format!("{:?} {:?}", n0, n1),
578 });
579 }
580 };
581 Ok(normal)
582 }
583
584 fn parse_three(
585 line_number: usize,
586 n0: Option<&str>,
587 n1: Option<&str>,
588 n2: Option<&str>,
589 ) -> Result<[f32; 3], ObjError> {
590 let (n0, n1, n2) = match (n0, n1, n2) {
591 (Some(n0), Some(n1), Some(n2)) => (n0, n1, n2),
592 _ => {
593 return Err(ObjError::ArgumentListFailure {
594 line_number,
595 list: format!("{:?} {:?} {:?}", n0, n1, n2),
596 });
597 }
598 };
599 let normal = match (FromStr::from_str(n0), FromStr::from_str(n1), FromStr::from_str(n2)) {
600 (Ok(n0), Ok(n1), Ok(n2)) => [n0, n1, n2],
601 _ => {
602 return Err(ObjError::ArgumentListFailure {
603 line_number,
604 list: format!("{:?} {:?} {:?}", n0, n1, n2),
605 });
606 }
607 };
608 Ok(normal)
609 }
610
611 fn parse_group(&self, line_number: usize, group: &str) -> Result<IndexTuple, ObjError> {
612 let mut group_split = group.split('/');
613 let p: Option<isize> = group_split.next().and_then(|idx| FromStr::from_str(idx).ok());
614 let t: Option<isize> = group_split
615 .next()
616 .and_then(|idx| if idx != "" { FromStr::from_str(idx).ok() } else { None });
617 let n: Option<isize> = group_split.next().and_then(|idx| FromStr::from_str(idx).ok());
618
619 match (p, t, n) {
620 (Some(p), t, n) => Ok(IndexTuple(
621 normalize(p, self.position.len()).ok_or(ObjError::ZeroVertexNumber { line_number })?,
622 t.map(|t| normalize(t, self.texture.len())).flatten(),
624 n.map(|n| normalize(n, self.normal.len())).flatten(),
625 )),
626 _ => Err(ObjError::MalformedFaceGroup {
627 line_number,
628 group: String::from(group),
629 }),
630 }
631 }
632
633 fn parse_face<'b, I>(&self, line_number: usize, groups: &mut I) -> Result<SimplePolygon, ObjError>
634 where
635 I: Iterator<Item = &'b str>,
636 {
637 let mut ret = Vec::with_capacity(4);
638 for g in groups {
639 let ituple = self.parse_group(line_number, g)?;
640 ret.push(ituple);
641 }
642 Ok(SimplePolygon(ret))
643 }
644
645 pub fn load_buf<R: Read>(input: R) -> Result<Self, ObjError> {
646 Self::load_buf_with_config(input, LoadConfig::default())
647 }
648
649 pub fn load_buf_with_config<R: Read>(input: R, config: LoadConfig) -> Result<Self, ObjError> {
650 let input = BufReader::new(input);
651 let mut dat = ObjData::default();
652 let mut object = Object::new(DEFAULT_OBJECT.to_string());
653 let mut group: Option<Group> = None;
654
655 for (idx, line) in input.lines().enumerate() {
656 let (line, mut words) = match line {
657 Ok(ref line) => (line.clone(), line.split_whitespace().filter(|s| !s.is_empty())),
658 Err(err) => {
659 return Err(ObjError::Io(io::Error::new(
660 io::ErrorKind::InvalidData,
661 format!("failed to readline {}", err),
662 )));
663 }
664 };
665 let first = words.next();
666
667 match first {
668 Some("v") => {
669 let (v0, v1, v2) = (words.next(), words.next(), words.next());
670 dat.position.push(Self::parse_three(idx, v0, v1, v2)?);
671 }
672 Some("vt") => {
673 let (t0, t1) = (words.next(), words.next());
674 dat.texture.push(Self::parse_two(idx, t0, t1)?);
675 }
676 Some("vn") => {
677 let (n0, n1, n2) = (words.next(), words.next(), words.next());
678 dat.normal.push(Self::parse_three(idx, n0, n1, n2)?);
679 }
680 Some("f") => {
681 let poly = dat.parse_face(idx, &mut words)?;
682 group = Some(match group {
683 None => {
684 let mut g = Group::new(DEFAULT_GROUP.to_string());
685 g.polys.push(poly);
686 g
687 }
688 Some(mut g) => {
689 g.polys.push(poly);
690 g
691 }
692 });
693 }
694 Some("o") => {
695 group = match group {
696 Some(val) => {
697 object.groups.push(val);
698 dat.objects.push(object);
699 None
700 }
701 None => None,
702 };
703 object = if line.len() > 2 {
704 let name = line[1..].trim();
705 Object::new(name.to_string())
706 } else {
707 Object::new(DEFAULT_OBJECT.to_string())
708 };
709 }
710 Some("g") => {
711 object.groups.extend(group.take());
712
713 if line.len() > 2 {
714 let name = line[2..].trim();
715 group = Some(Group::new(name.to_string()));
716 }
717 }
718 Some("mtllib") => {
719 let first_word = words
725 .next()
726 .ok_or_else(|| ObjError::MissingMTLName { line_number: idx })?
727 .to_string();
728 let name = words.fold(first_word, |mut existing, next| {
729 existing.push(' ');
730 existing.push_str(next);
731 existing
732 });
733 dat.material_libs.push(Mtl::new(name));
734 }
735 Some("usemtl") => {
736 let mut g = group.unwrap_or_else(|| Group::new(DEFAULT_GROUP.to_string()));
737 if g.material.is_some() {
740 object.groups.push(g.clone());
741 g.index += 1;
742 g.polys.clear();
743 }
744 g.material = words.next().map(|w| ObjMaterial::Ref(w.to_string()));
745 group = Some(g);
746 }
747 Some("s") => (),
748 Some("l") => (),
749 Some(other) => {
750 if config.strict && !other.starts_with('#') {
751 return Err(ObjError::UnexpectedCommand {
752 line_number: idx,
753 command: other.to_string(),
754 });
755 }
756 }
757 None => (),
758 }
759 }
760
761 if let Some(g) = group {
762 object.groups.push(g);
763 }
764
765 dat.objects.push(object);
766 Ok(dat)
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773
774 #[test]
776 fn load_error_on_zero_vertex_numbers() {
777 let test = b"v 0 1 2\nv 3 4 5\nf 0 1 2";
778 let mut reader = BufReader::new(&test[..]);
779 assert!(matches!(
780 ObjData::load_buf(&mut reader),
781 Err(ObjError::ZeroVertexNumber { line_number: 2 })
782 ));
783 }
784}