use std::{
borrow::Cow,
fmt,
io::{self, BufRead, BufReader, Error, Read, Write},
path::Path,
str::FromStr,
sync::Arc,
};
#[derive(Debug, Clone, PartialEq)]
pub struct Material {
pub name: String,
pub ka: Option<[f32; 3]>,
pub kd: Option<[f32; 3]>,
pub ks: Option<[f32; 3]>,
pub ke: Option<[f32; 3]>,
pub km: Option<f32>,
pub tf: Option<[f32; 3]>,
pub ns: Option<f32>,
pub ni: Option<f32>,
pub tr: Option<f32>,
pub d: Option<f32>,
pub illum: Option<i32>,
pub map_ka: Option<String>,
pub map_kd: Option<String>,
pub map_ks: Option<String>,
pub map_ke: Option<String>,
pub map_ns: Option<String>,
pub map_d: Option<String>,
pub map_bump: Option<String>,
pub map_refl: Option<String>,
}
impl Material {
pub fn new(name: String) -> Self {
Material {
name,
ka: None,
kd: None,
ks: None,
ke: None,
km: None,
ns: None,
ni: None,
tr: None,
tf: None,
d: None,
map_ka: None,
map_kd: None,
map_ks: None,
map_ke: None,
map_ns: None,
map_d: None,
map_bump: None,
map_refl: None,
illum: None,
}
}
}
#[derive(Debug)]
pub enum MtlMissingType {
I32,
F32,
String,
}
impl fmt::Display for MtlMissingType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MtlMissingType::I32 => write!(f, "i32"),
MtlMissingType::F32 => write!(f, "f32"),
MtlMissingType::String => write!(f, "String"),
}
}
}
#[derive(Debug)]
pub enum MtlError {
Io(io::Error),
InvalidInstruction(String),
InvalidValue(String),
MissingMaterialName,
MissingValue(MtlMissingType),
}
impl std::error::Error for MtlError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
MtlError::Io(err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for MtlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MtlError::Io(err) => write!(f, "I/O error loading a .mtl file: {}", err),
MtlError::InvalidInstruction(instruction) => write!(f, "Unsupported mtl instruction: {}", instruction),
MtlError::InvalidValue(val) => write!(f, "Attempted to parse the value '{}' but failed.", val),
MtlError::MissingMaterialName => write!(f, "newmtl issued, but no name provided."),
MtlError::MissingValue(ty) => write!(f, "Instruction is missing a value of type '{}'", ty),
}
}
}
impl From<io::Error> for MtlError {
fn from(e: Error) -> Self {
Self::Io(e)
}
}
impl<'a> From<Material> for Cow<'a, Material> {
#[inline]
fn from(s: Material) -> Cow<'a, Material> {
Cow::Owned(s)
}
}
struct Parser<I>(I);
impl<'a, I: Iterator<Item = &'a str>> Parser<I> {
fn get_vec(&mut self) -> Result<[f32; 3], MtlError> {
let (x, y, z) = match (self.0.next(), self.0.next(), self.0.next()) {
(Some(x), Some(y), Some(z)) => (x, y, z),
other => {
return Err(MtlError::InvalidValue(format!("{:?}", other)));
}
};
match (x.parse::<f32>(), y.parse::<f32>(), z.parse::<f32>()) {
(Ok(x), Ok(y), Ok(z)) => Ok([x, y, z]),
other => Err(MtlError::InvalidValue(format!("{:?}", other))),
}
}
fn get_i32(&mut self) -> Result<i32, MtlError> {
match self.0.next() {
Some(v) => FromStr::from_str(v).map_err(|_| MtlError::InvalidValue(v.to_string())),
None => Err(MtlError::MissingValue(MtlMissingType::I32)),
}
}
fn get_f32(&mut self) -> Result<f32, MtlError> {
match self.0.next() {
Some(v) => FromStr::from_str(v).map_err(|_| MtlError::InvalidValue(v.to_string())),
None => Err(MtlError::MissingValue(MtlMissingType::F32)),
}
}
fn into_string(mut self) -> Result<String, MtlError> {
match self.0.next() {
Some(v) => {
Ok(self.0.fold(v.to_string(), |mut existing, next| {
existing.push(' ');
existing.push_str(next);
existing
}))
}
None => Err(MtlError::MissingValue(MtlMissingType::String)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Mtl {
pub filename: String,
pub materials: Vec<Arc<Material>>,
}
impl Mtl {
pub fn new(filename: String) -> Self {
Mtl {
filename,
materials: Vec::new(),
}
}
pub fn reload_with<R, F>(&mut self, obj_dir: impl AsRef<Path>, mut resolve: F) -> Result<&mut Self, MtlError>
where
R: BufRead,
F: FnMut(&Path, &str) -> io::Result<R>,
{
self.reload(resolve(obj_dir.as_ref(), &self.filename)?)
}
pub fn reload(&mut self, input: impl Read) -> Result<&mut Self, MtlError> {
self.materials.clear();
let input = BufReader::new(input);
let mut material = None;
for line in input.lines() {
let mut parser = match line {
Ok(ref line) => Parser(line.split_whitespace().filter(|s| !s.is_empty())),
Err(err) => return Err(MtlError::Io(err)),
};
match parser.0.next() {
Some("newmtl") => {
self.materials.extend(material.take().map(Arc::new));
material = Some(Material::new(
parser
.0
.next()
.ok_or_else(|| MtlError::MissingMaterialName)?
.to_string(),
));
}
Some("Ka") => {
if let Some(ref mut m) = material {
m.ka = Some(parser.get_vec()?);
}
}
Some("Kd") => {
if let Some(ref mut m) = material {
m.kd = Some(parser.get_vec()?);
}
}
Some("Ks") => {
if let Some(ref mut m) = material {
m.ks = Some(parser.get_vec()?);
}
}
Some("Ke") => {
if let Some(ref mut m) = material {
m.ke = Some(parser.get_vec()?);
}
}
Some("Ns") => {
if let Some(ref mut m) = material {
m.ns = Some(parser.get_f32()?);
}
}
Some("Ni") => {
if let Some(ref mut m) = material {
m.ni = Some(parser.get_f32()?);
}
}
Some("Km") => {
if let Some(ref mut m) = material {
m.km = Some(parser.get_f32()?);
}
}
Some("d") => {
if let Some(ref mut m) = material {
m.d = Some(parser.get_f32()?);
}
}
Some("Tr") => {
if let Some(ref mut m) = material {
m.tr = Some(parser.get_f32()?);
}
}
Some("Tf") => {
if let Some(ref mut m) = material {
m.tf = Some(parser.get_vec()?);
}
}
Some("illum") => {
if let Some(ref mut m) = material {
m.illum = Some(parser.get_i32()?);
}
}
Some("map_Ka") => {
if let Some(ref mut m) = material {
m.map_ka = Some(parser.into_string()?);
}
}
Some("map_Kd") => {
if let Some(ref mut m) = material {
m.map_kd = Some(parser.into_string()?);
}
}
Some("map_Ks") => {
if let Some(ref mut m) = material {
m.map_ks = Some(parser.into_string()?);
}
}
Some("map_Ns") => {
if let Some(ref mut m) = material {
m.map_ns = Some(parser.into_string()?);
}
}
Some("map_d") => {
if let Some(ref mut m) = material {
m.map_d = Some(parser.into_string()?);
}
}
Some("map_refl") | Some("refl") => {
if let Some(ref mut m) = material {
m.map_refl = Some(parser.into_string()?);
}
}
Some("map_bump") | Some("map_Bump") | Some("bump") => {
if let Some(ref mut m) = material {
m.map_bump = Some(parser.into_string()?);
}
}
Some(other) => {
if !other.starts_with('#') {
return Err(MtlError::InvalidInstruction(other.to_string()));
}
}
None => {}
}
}
if let Some(material) = material {
self.materials.push(Arc::new(material));
}
Ok(self)
}
pub fn write_to_buf(&self, out: &mut impl Write) -> Result<(), io::Error> {
for mtl in &self.materials {
writeln!(out, "newmtl {}", mtl.name)?;
if let Some([ka0, ka1, ka2]) = mtl.ka {
writeln!(out, "Ka {} {} {}", ka0, ka1, ka2)?;
}
if let Some([kd0, kd1, kd2]) = mtl.kd {
writeln!(out, "Kd {} {} {}", kd0, kd1, kd2)?;
}
if let Some([ks0, ks1, ks2]) = mtl.ks {
writeln!(out, "Ks {} {} {}", ks0, ks1, ks2)?;
}
if let Some([ke0, ke1, ke2]) = mtl.ke {
writeln!(out, "Ke {} {} {}", ke0, ke1, ke2)?;
}
if let Some(ns) = mtl.ns {
writeln!(out, "Ns {}", ns)?;
}
if let Some(ns) = mtl.ns {
writeln!(out, "Ns {}", ns)?;
}
if let Some(ni) = mtl.ni {
writeln!(out, "Ni {}", ni)?;
}
if let Some(km) = mtl.km {
writeln!(out, "Km {}", km)?;
}
if let Some(d) = mtl.d {
writeln!(out, "d {}", d)?;
}
if let Some(tr) = mtl.tr {
writeln!(out, "Tr {}", tr)?;
}
if let Some([tf0, tf1, tf2]) = mtl.tf {
writeln!(out, "Tf {} {} {}", tf0, tf1, tf2)?;
}
if let Some(illum) = mtl.illum {
writeln!(out, "illum {}", illum)?;
}
if let Some(map_ka) = &mtl.map_ka {
writeln!(out, "map_Ka {}", map_ka)?;
}
if let Some(map_kd) = &mtl.map_kd {
writeln!(out, "map_Kd {}", map_kd)?;
}
if let Some(map_ks) = &mtl.map_ks {
writeln!(out, "map_Ks {}", map_ks)?;
}
if let Some(map_d) = &mtl.map_d {
writeln!(out, "map_d {}", map_d)?;
}
if let Some(map_refl) = &mtl.map_refl {
writeln!(out, "refl {}", map_refl)?;
}
if let Some(map_bump) = &mtl.map_bump {
writeln!(out, "bump {}", map_bump)?;
}
}
Ok(())
}
}