obj/
mtl.rs

1//   Copyright 2017 GFX Developers
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15//! Parsing and writing of a .mtl file as defined in the
16//! [full spec](http://paulbourke.net/dataformats/mtl/).
17
18use std::{
19    borrow::Cow,
20    fmt,
21    io::{self, BufRead, BufReader, Error, Read, Write},
22    path::Path,
23    str::FromStr,
24    sync::Arc,
25};
26
27/// The model of an a single Material as defined in the .mtl spec.
28#[derive(Debug, Clone, PartialEq)]
29pub struct Material {
30    pub name: String,
31
32    // Material color and illumination
33    pub ka: Option<[f32; 3]>,
34    pub kd: Option<[f32; 3]>,
35    pub ks: Option<[f32; 3]>,
36    pub ke: Option<[f32; 3]>,
37    pub km: Option<f32>,
38    pub tf: Option<[f32; 3]>,
39    pub ns: Option<f32>,
40    pub ni: Option<f32>,
41    pub tr: Option<f32>,
42    pub d: Option<f32>,
43    pub illum: Option<i32>,
44
45    // Texture and reflection maps
46    pub map_ka: Option<String>,
47    pub map_kd: Option<String>,
48    pub map_ks: Option<String>,
49    pub map_ke: Option<String>,
50    pub map_ns: Option<String>,
51    pub map_d: Option<String>,
52    pub map_bump: Option<String>,
53    pub map_refl: Option<String>,
54}
55
56impl Material {
57    pub fn new(name: String) -> Self {
58        Material {
59            name,
60            ka: None,
61            kd: None,
62            ks: None,
63            ke: None,
64            km: None,
65            ns: None,
66            ni: None,
67            tr: None,
68            tf: None,
69            d: None,
70            map_ka: None,
71            map_kd: None,
72            map_ks: None,
73            map_ke: None,
74            map_ns: None,
75            map_d: None,
76            map_bump: None,
77            map_refl: None,
78            illum: None,
79        }
80    }
81}
82
83/// Indicates type of a missing value
84#[derive(Debug)]
85pub enum MtlMissingType {
86    /// i32
87    I32,
88    /// f32
89    F32,
90    /// String
91    String,
92}
93
94impl fmt::Display for MtlMissingType {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        match self {
97            MtlMissingType::I32 => write!(f, "i32"),
98            MtlMissingType::F32 => write!(f, "f32"),
99            MtlMissingType::String => write!(f, "String"),
100        }
101    }
102}
103
104/// Errors parsing or loading a .mtl file.
105#[derive(Debug)]
106pub enum MtlError {
107    Io(io::Error),
108    /// Given instruction was not in .mtl spec.
109    InvalidInstruction(String),
110    /// Attempted to parse value, but failed.
111    InvalidValue(String),
112    /// `newmtl` issued, but no name provided.
113    MissingMaterialName,
114    /// Instruction requires a value, but that value was not provided.
115    MissingValue(MtlMissingType),
116}
117
118impl std::error::Error for MtlError {
119    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
120        match self {
121            MtlError::Io(err) => Some(err),
122            _ => None,
123        }
124    }
125}
126
127impl fmt::Display for MtlError {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        match self {
130            MtlError::Io(err) => write!(f, "I/O error loading a .mtl file: {}", err),
131            MtlError::InvalidInstruction(instruction) => write!(f, "Unsupported mtl instruction: {}", instruction),
132            MtlError::InvalidValue(val) => write!(f, "Attempted to parse the value '{}' but failed.", val),
133            MtlError::MissingMaterialName => write!(f, "newmtl issued, but no name provided."),
134            MtlError::MissingValue(ty) => write!(f, "Instruction is missing a value of type '{}'", ty),
135        }
136    }
137}
138
139impl From<io::Error> for MtlError {
140    fn from(e: Error) -> Self {
141        Self::Io(e)
142    }
143}
144
145impl<'a> From<Material> for Cow<'a, Material> {
146    #[inline]
147    fn from(s: Material) -> Cow<'a, Material> {
148        Cow::Owned(s)
149    }
150}
151
152struct Parser<I>(I);
153
154impl<'a, I: Iterator<Item = &'a str>> Parser<I> {
155    fn get_vec(&mut self) -> Result<[f32; 3], MtlError> {
156        let (x, y, z) = match (self.0.next(), self.0.next(), self.0.next()) {
157            (Some(x), Some(y), Some(z)) => (x, y, z),
158            other => {
159                return Err(MtlError::InvalidValue(format!("{:?}", other)));
160            }
161        };
162
163        match (x.parse::<f32>(), y.parse::<f32>(), z.parse::<f32>()) {
164            (Ok(x), Ok(y), Ok(z)) => Ok([x, y, z]),
165            other => Err(MtlError::InvalidValue(format!("{:?}", other))),
166        }
167    }
168
169    fn get_i32(&mut self) -> Result<i32, MtlError> {
170        match self.0.next() {
171            Some(v) => FromStr::from_str(v).map_err(|_| MtlError::InvalidValue(v.to_string())),
172            None => Err(MtlError::MissingValue(MtlMissingType::I32)),
173        }
174    }
175
176    fn get_f32(&mut self) -> Result<f32, MtlError> {
177        match self.0.next() {
178            Some(v) => FromStr::from_str(v).map_err(|_| MtlError::InvalidValue(v.to_string())),
179            None => Err(MtlError::MissingValue(MtlMissingType::F32)),
180        }
181    }
182
183    fn into_string(mut self) -> Result<String, MtlError> {
184        match self.0.next() {
185            Some(v) => {
186                // See note on mtllib parsing in obj.rs for why this is needed/works
187                Ok(self.0.fold(v.to_string(), |mut existing, next| {
188                    existing.push(' ');
189                    existing.push_str(next);
190                    existing
191                }))
192            }
193            None => Err(MtlError::MissingValue(MtlMissingType::String)),
194        }
195    }
196}
197
198/// The data represented by the `mtllib` command.
199///
200/// The material name is replaced by the actual material data when the material libraries are
201/// laoded if a match is found.
202#[derive(Debug, Clone, PartialEq)]
203pub struct Mtl {
204    /// Name of the .mtl file.
205    pub filename: String,
206    /// A list of loaded materials.
207    ///
208    /// The individual materials are wrapped into an `Arc` to facilitate referencing this data
209    /// where these materials are assigned in the `.obj` file.
210    pub materials: Vec<Arc<Material>>,
211}
212
213impl Mtl {
214    /// Construct a new empty mtl lib with the given file name.
215    pub fn new(filename: String) -> Self {
216        Mtl {
217            filename,
218            materials: Vec::new(),
219        }
220    }
221
222    /// Load the mtl library from the input buffer generated by the given closure.
223    ///
224    /// This function overwrites the contents of this library if it has already been loaded.
225    pub fn reload_with<R, F>(&mut self, obj_dir: impl AsRef<Path>, mut resolve: F) -> Result<&mut Self, MtlError>
226    where
227        R: BufRead,
228        F: FnMut(&Path, &str) -> io::Result<R>,
229    {
230        self.reload(resolve(obj_dir.as_ref(), &self.filename)?)
231    }
232
233    /// Load the mtl library from the given input buffer.
234    ///
235    /// This function overwrites the contents of this library if it has already been loaded.
236    pub fn reload(&mut self, input: impl Read) -> Result<&mut Self, MtlError> {
237        self.materials.clear();
238        let input = BufReader::new(input);
239        let mut material = None;
240        for line in input.lines() {
241            let mut parser = match line {
242                Ok(ref line) => Parser(line.split_whitespace().filter(|s| !s.is_empty())),
243                Err(err) => return Err(MtlError::Io(err)),
244            };
245            match parser.0.next() {
246                Some("newmtl") => {
247                    self.materials.extend(material.take().map(Arc::new));
248                    material = Some(Material::new(
249                        parser
250                            .0
251                            .next()
252                            .ok_or_else(|| MtlError::MissingMaterialName)?
253                            .to_string(),
254                    ));
255                }
256                Some("Ka") => {
257                    if let Some(ref mut m) = material {
258                        m.ka = Some(parser.get_vec()?);
259                    }
260                }
261                Some("Kd") => {
262                    if let Some(ref mut m) = material {
263                        m.kd = Some(parser.get_vec()?);
264                    }
265                }
266                Some("Ks") => {
267                    if let Some(ref mut m) = material {
268                        m.ks = Some(parser.get_vec()?);
269                    }
270                }
271                Some("Ke") => {
272                    if let Some(ref mut m) = material {
273                        m.ke = Some(parser.get_vec()?);
274                    }
275                }
276                Some("Ns") => {
277                    if let Some(ref mut m) = material {
278                        m.ns = Some(parser.get_f32()?);
279                    }
280                }
281                Some("Ni") => {
282                    if let Some(ref mut m) = material {
283                        m.ni = Some(parser.get_f32()?);
284                    }
285                }
286                Some("Km") => {
287                    if let Some(ref mut m) = material {
288                        m.km = Some(parser.get_f32()?);
289                    }
290                }
291                Some("d") => {
292                    if let Some(ref mut m) = material {
293                        m.d = Some(parser.get_f32()?);
294                    }
295                }
296                Some("Tr") => {
297                    if let Some(ref mut m) = material {
298                        m.tr = Some(parser.get_f32()?);
299                    }
300                }
301                Some("Tf") => {
302                    if let Some(ref mut m) = material {
303                        m.tf = Some(parser.get_vec()?);
304                    }
305                }
306                Some("illum") => {
307                    if let Some(ref mut m) = material {
308                        m.illum = Some(parser.get_i32()?);
309                    }
310                }
311                Some("map_Ka") => {
312                    if let Some(ref mut m) = material {
313                        m.map_ka = Some(parser.into_string()?);
314                    }
315                }
316                Some("map_Kd") => {
317                    if let Some(ref mut m) = material {
318                        m.map_kd = Some(parser.into_string()?);
319                    }
320                }
321                Some("map_Ks") => {
322                    if let Some(ref mut m) = material {
323                        m.map_ks = Some(parser.into_string()?);
324                    }
325                }
326                Some("map_Ns") => {
327                    if let Some(ref mut m) = material {
328                        m.map_ns = Some(parser.into_string()?);
329                    }
330                }
331                Some("map_d") => {
332                    if let Some(ref mut m) = material {
333                        m.map_d = Some(parser.into_string()?);
334                    }
335                }
336                Some("map_refl") | Some("refl") => {
337                    if let Some(ref mut m) = material {
338                        m.map_refl = Some(parser.into_string()?);
339                    }
340                }
341                Some("map_bump") | Some("map_Bump") | Some("bump") => {
342                    if let Some(ref mut m) = material {
343                        m.map_bump = Some(parser.into_string()?);
344                    }
345                }
346                Some(other) => {
347                    if !other.starts_with('#') {
348                        return Err(MtlError::InvalidInstruction(other.to_string()));
349                    }
350                }
351                None => {}
352            }
353        }
354
355        if let Some(material) = material {
356            self.materials.push(Arc::new(material));
357        }
358
359        Ok(self)
360    }
361
362    pub fn write_to_buf(&self, out: &mut impl Write) -> Result<(), io::Error> {
363        for mtl in &self.materials {
364            writeln!(out, "newmtl {}", mtl.name)?;
365            if let Some([ka0, ka1, ka2]) = mtl.ka {
366                writeln!(out, "Ka {} {} {}", ka0, ka1, ka2)?;
367            }
368            if let Some([kd0, kd1, kd2]) = mtl.kd {
369                writeln!(out, "Kd {} {} {}", kd0, kd1, kd2)?;
370            }
371            if let Some([ks0, ks1, ks2]) = mtl.ks {
372                writeln!(out, "Ks {} {} {}", ks0, ks1, ks2)?;
373            }
374            if let Some([ke0, ke1, ke2]) = mtl.ke {
375                writeln!(out, "Ke {} {} {}", ke0, ke1, ke2)?;
376            }
377            if let Some(ns) = mtl.ns {
378                writeln!(out, "Ns {}", ns)?;
379            }
380            if let Some(ns) = mtl.ns {
381                writeln!(out, "Ns {}", ns)?;
382            }
383            if let Some(ni) = mtl.ni {
384                writeln!(out, "Ni {}", ni)?;
385            }
386            if let Some(km) = mtl.km {
387                writeln!(out, "Km {}", km)?;
388            }
389            if let Some(d) = mtl.d {
390                writeln!(out, "d {}", d)?;
391            }
392            if let Some(tr) = mtl.tr {
393                writeln!(out, "Tr {}", tr)?;
394            }
395            if let Some([tf0, tf1, tf2]) = mtl.tf {
396                writeln!(out, "Tf {} {} {}", tf0, tf1, tf2)?;
397            }
398            if let Some(illum) = mtl.illum {
399                writeln!(out, "illum {}", illum)?;
400            }
401            if let Some(map_ka) = &mtl.map_ka {
402                writeln!(out, "map_Ka {}", map_ka)?;
403            }
404            if let Some(map_kd) = &mtl.map_kd {
405                writeln!(out, "map_Kd {}", map_kd)?;
406            }
407            if let Some(map_ks) = &mtl.map_ks {
408                writeln!(out, "map_Ks {}", map_ks)?;
409            }
410            if let Some(map_d) = &mtl.map_d {
411                writeln!(out, "map_d {}", map_d)?;
412            }
413            if let Some(map_refl) = &mtl.map_refl {
414                writeln!(out, "refl {}", map_refl)?;
415            }
416            if let Some(map_bump) = &mtl.map_bump {
417                writeln!(out, "bump {}", map_bump)?;
418            }
419        }
420        Ok(())
421    }
422}