#![allow(clippy::collapsible_if, clippy::many_single_char_names)]
use std::{
collections::HashMap,
io, mem,
path::{Path, PathBuf},
str,
};
use self::error::ErrorKind;
use crate::{
common,
utils::{
bytes::{from_utf8_lossy, memchr_naive, memchr_naive_table, path_from_bytes, starts_with},
float, int,
utf16::decode_bytes,
},
Color4, Mesh, Scene, Vec2, Vec3,
};
pub fn from_slice<B: AsRef<[u8]>, F: FnMut(&Path) -> io::Result<B>>(
bytes: &[u8],
path: Option<&Path>,
mut reader: F,
) -> io::Result<Scene> {
let bytes = &decode_bytes(bytes)?;
match read_obj(bytes, path, &mut |path, materials, material_map| {
match reader(path) {
Ok(bytes) => read_mtl(bytes.as_ref(), Some(path), materials, material_map),
Err(_e) => Ok(()),
}
}) {
Ok((meshes, materials)) => {
let materials = meshes
.iter()
.map(|m| {
materials
.get(m.material_index as usize)
.cloned()
.unwrap_or_default()
})
.collect();
Ok(Scene { materials, meshes })
}
Err(e) => Err(e.into_io_error(bytes, path)),
}
}
fn read_obj(
mut s: &[u8],
obj_path: Option<&Path>,
reader: &mut dyn FnMut(
&Path,
&mut Vec<common::Material>,
&mut HashMap<Vec<u8>, u32>,
) -> io::Result<()>,
) -> Result<(Vec<Mesh>, Vec<common::Material>), ErrorKind> {
let mut meshes = Vec::with_capacity(1); let mut vertices = vec![];
let mut normals = vec![];
let mut texcoords = vec![];
let mut colors = vec![];
let mut face = Vec::with_capacity(3);
let mut faces: Vec<Face> = vec![];
let mut current_group: &[u8] = b"default";
let mut current_material: &[u8] = &[];
let mut materials = vec![];
let mut material_map = HashMap::new();
while let Some((&c, s_next)) = s.split_first() {
match c {
b'v' => {
s = s_next;
match s.first() {
Some(b' ' | b'\t') => {
skip_spaces(&mut s);
read_v(&mut s, &mut vertices, &mut colors)?;
if !colors.is_empty() && colors.len() < vertices.len() {
colors.resize(vertices.len(), [0.; 3]);
}
continue;
}
Some(b'n') => {
s = &s[1..];
if skip_spaces(&mut s) {
read_vn(&mut s, &mut normals)?;
continue;
}
}
Some(b't') => {
s = &s[1..];
if skip_spaces(&mut s) {
read_vt(&mut s, &mut texcoords)?;
continue;
}
}
_ => {}
}
}
b'f' => {
s = s_next;
if skip_spaces(&mut s) {
read_f(
&mut s, &mut faces, &mut face, &vertices, &texcoords, &normals,
)?;
continue;
}
}
b'u' => {
s = s_next;
if token(&mut s, &"usemtl".as_bytes()[1..]) {
if skip_spaces(&mut s) {
let (name, s_next) = name(s);
if name != current_material {
let material_index = material_map.get(current_material).copied();
push_mesh(
&mut meshes,
&mut faces,
&vertices,
&texcoords,
&normals,
&colors,
current_group,
material_index,
)?;
current_material = name;
}
s = s_next;
continue;
}
}
}
b'm' => {
s = s_next;
if token(&mut s, &"mtllib".as_bytes()[1..]) {
if skip_spaces(&mut s) {
let (path, s_next) = name(s);
let path = if path.is_empty() {
None
} else {
path_from_bytes(path).ok()
};
if let Some(path) = path {
match obj_path.and_then(Path::parent) {
Some(parent) => {
reader(&parent.join(path), &mut materials, &mut material_map)
.map_err(ErrorKind::Io)?;
}
None => {} }
}
s = s_next;
continue;
}
}
}
b'g' => {
s = s_next;
if skip_spaces(&mut s) {
let (mut name, s_next) = name(s);
if name.is_empty() {
name = b"default";
}
if name != current_group {
let material_index = material_map.get(current_material).copied();
push_mesh(
&mut meshes,
&mut faces,
&vertices,
&texcoords,
&normals,
&colors,
current_group,
material_index,
)?;
current_material = &[];
current_group = name;
}
s = s_next;
continue;
}
}
_ => {}
}
skip_any_until_line(&mut s);
}
let material_index = material_map.get(current_material).copied();
push_mesh(
&mut meshes,
&mut faces,
&vertices,
&texcoords,
&normals,
&colors,
current_group,
material_index,
)?;
Ok((meshes, materials))
}
#[inline(always)]
fn read_v(
s: &mut &[u8],
vertices: &mut Vec<Vec3>,
colors: &mut Vec<Vec3>,
) -> Result<(), ErrorKind> {
let vertex = read_float3(s, "v")?;
let has_space = skip_spaces(s);
match s.first() {
Some(b'\n' | b'\r') | None => {
vertices.push(vertex);
*s = s.get(1..).unwrap_or_default();
return Ok(());
}
_ if !has_space => return Err(ErrorKind::ExpectedSpace("v", s.len())),
_ => {}
}
let w = match float::parse_partial::<f32>(s) {
Some((f, n)) => {
*s = &s[n..];
f
}
None => return Err(ErrorKind::Float(s.len())),
};
let has_space = skip_spaces(s);
match s.first() {
Some(b'\n' | b'\r') | None => {
if w == 0. {
return Err(ErrorKind::InvalidW(s.len()));
}
vertices.push([vertex[0] / w, vertex[1] / w, vertex[2] / w]);
*s = s.get(1..).unwrap_or_default();
return Ok(());
}
_ if !has_space => return Err(ErrorKind::ExpectedSpace("v", s.len())),
_ => {}
}
vertices.push(vertex);
let r = w;
let g = match float::parse_partial::<f32>(s) {
Some((f, n)) => {
*s = &s[n..];
f
}
None => return Err(ErrorKind::Float(s.len())),
};
if !skip_spaces(s) {
return Err(ErrorKind::ExpectedSpace("v", s.len()));
}
let b = match float::parse_partial::<f32>(s) {
Some((f, n)) => {
*s = &s[n..];
f
}
None => return Err(ErrorKind::Float(s.len())),
};
colors.push([r, g, b]);
if !skip_spaces_until_line(s) {
return Err(ErrorKind::ExpectedNewline("v", s.len()));
}
Ok(())
}
fn read_vn(s: &mut &[u8], normals: &mut Vec<Vec3>) -> Result<(), ErrorKind> {
let normal = read_float3(s, "vn")?;
normals.push(normal);
if !skip_spaces_until_line(s) {
return Err(ErrorKind::ExpectedNewline("vn", s.len()));
}
Ok(())
}
fn read_vt(s: &mut &[u8], texcoords: &mut Vec<Vec2>) -> Result<(), ErrorKind> {
let mut texcoord = [0.; 2];
match float::parse_partial::<f32>(s) {
Some((f, n)) => {
texcoord[0] = f;
*s = &s[n..];
}
None => return Err(ErrorKind::Float(s.len())),
}
let has_space = skip_spaces(s);
match s.first() {
Some(b'\n' | b'\r') | None => {
texcoords.push(texcoord);
*s = s.get(1..).unwrap_or_default();
return Ok(());
}
_ if !has_space => return Err(ErrorKind::ExpectedSpace("vt", s.len())),
_ => {}
}
match float::parse_partial::<f32>(s) {
Some((f, n)) => {
texcoord[1] = f;
*s = &s[n..];
}
None => return Err(ErrorKind::Float(s.len())),
}
texcoords.push(texcoord);
let has_space = skip_spaces(s);
match s.first() {
Some(b'\n' | b'\r') | None => {
*s = s.get(1..).unwrap_or_default();
return Ok(());
}
_ if !has_space => return Err(ErrorKind::ExpectedSpace("vt", s.len())),
_ => {}
}
match float::parse_partial::<f32>(s) {
Some((_f, n)) => {
*s = &s[n..];
}
None => return Err(ErrorKind::Float(s.len())),
}
if !skip_spaces_until_line(s) {
return Err(ErrorKind::ExpectedNewline("vt", s.len()));
}
Ok(())
}
fn read_f(
s: &mut &[u8],
faces: &mut Vec<Face>,
face: &mut Vec<[u32; 3]>,
vertices: &[Vec3],
texcoords: &[Vec2],
normals: &[Vec3],
) -> Result<(), ErrorKind> {
let mut f;
match memchr_naive_table(LINE, &TABLE, s) {
Some(n) => {
f = &s[..n];
*s = &s[n + 1..];
}
None => {
f = s;
*s = &[];
}
};
while !f.is_empty() {
let mut w;
let f_next = match memchr_naive_table(WS_NO_LINE, &TABLE, f) {
Some(n) => {
w = &f[..n];
&f[n + 1..]
}
None => {
w = f;
&[]
}
};
let mut idx = [u32::MAX; 3];
let mut i;
match memchr_naive(b'/', w) {
Some(n) => {
i = &w[..n];
w = &w[n + 1..];
}
None => {
i = w;
w = &[];
}
};
match int::parse::<i32>(i) {
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
Some(i) => {
idx[0] = if i < 0 {
(vertices.len() as isize + i as isize) as _
} else {
(i - 1) as _
}
}
None => return Err(ErrorKind::Int(s.len() + !s.is_empty() as usize + f.len())),
}
match memchr_naive(b'/', w) {
Some(n) => {
i = &w[..n];
w = &w[n + 1..];
}
None => {
i = w;
w = &[];
}
};
if !i.is_empty() {
match int::parse::<i32>(i) {
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
Some(i) => {
idx[1] = if i < 0 {
(texcoords.len() as isize + i as isize) as _
} else {
(i - 1) as _
}
}
None => return Err(ErrorKind::Int(s.len() + !s.is_empty() as usize + f.len())),
}
}
i = w;
if !i.is_empty() {
match int::parse::<i32>(i) {
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
Some(i) => {
idx[2] = if i < 0 {
(normals.len() as isize + i as isize) as _
} else {
(i - 1) as _
}
}
None => return Err(ErrorKind::Int(s.len() + !s.is_empty() as usize + f.len())),
}
}
f = f_next;
skip_spaces(&mut f);
face.push(idx);
}
match face.len() {
1 => {
faces.push(Face::Point([face[0]]));
face.clear();
}
2 => {
faces.push(Face::Line([face[0], face[1]]));
face.clear();
}
3 => {
faces.push(Face::Triangle([face[0], face[1], face[2]]));
face.clear();
}
0 => return Err(ErrorKind::Expected("f", s.len())),
_ => faces.push(Face::Polygon(mem::take(face))),
}
Ok(())
}
fn read_float3(s: &mut &[u8], expected: &'static str) -> Result<[f32; 3], ErrorKind> {
let mut floats = [0.; 3];
match float::parse_partial::<f32>(s) {
Some((f, n)) => {
floats[0] = f;
*s = &s[n..];
}
None => return Err(ErrorKind::Float(s.len())),
}
if !skip_spaces(s) {
return Err(ErrorKind::ExpectedSpace(expected, s.len()));
}
match float::parse_partial::<f32>(s) {
Some((f, n)) => {
floats[1] = f;
*s = &s[n..];
}
None => return Err(ErrorKind::Float(s.len())),
}
if !skip_spaces(s) {
return Err(ErrorKind::ExpectedSpace(expected, s.len()));
}
match float::parse_partial::<f32>(s) {
Some((f, n)) => {
floats[2] = f;
*s = &s[n..];
}
None => return Err(ErrorKind::Float(s.len())),
}
Ok(floats)
}
fn read_color(s: &mut &[u8], expected: &'static str) -> Result<[f32; 3], ErrorKind> {
let mut floats = [0.; 3];
match float::parse_partial::<f32>(s) {
Some((f, n)) => {
floats[0] = f;
*s = &s[n..];
}
None => return Err(ErrorKind::Float(s.len())),
}
let has_space = skip_spaces(s);
match s.first() {
Some(b'\n' | b'\r') | None => {
*s = s.get(1..).unwrap_or_default();
return Ok(floats);
}
_ if !has_space => return Err(ErrorKind::ExpectedSpace(expected, s.len())),
_ => {}
}
match float::parse_partial::<f32>(s) {
Some((f, n)) => {
floats[1] = f;
*s = &s[n..];
}
None => return Err(ErrorKind::Float(s.len())),
}
if !skip_spaces(s) {
return Err(ErrorKind::ExpectedSpace(expected, s.len()));
}
match float::parse_partial::<f32>(s) {
Some((f, n)) => {
floats[2] = f;
*s = &s[n..];
}
None => return Err(ErrorKind::Float(s.len())),
}
if !skip_spaces_until_line(s) {
return Err(ErrorKind::ExpectedNewline(expected, s.len()));
}
Ok(floats)
}
fn read_float1(s: &mut &[u8], expected: &'static str) -> Result<f32, ErrorKind> {
match float::parse_partial::<f32>(s) {
Some((f, n)) => {
*s = &s[n..];
if !skip_spaces_until_line(s) {
return Err(ErrorKind::ExpectedNewline(expected, s.len()));
}
Ok(f)
}
None => Err(ErrorKind::Float(s.len())),
}
}
#[inline(always)]
fn push_vertex(
mesh: &mut Mesh,
vert: [u32; 3],
vertices: &[Vec3],
colors: &[Vec3],
texcoords: &[Vec2],
normals: &[Vec3],
) -> Result<(), ErrorKind> {
let v = vert[0] as usize;
mesh.vertices
.push(*vertices.get(v).ok_or(ErrorKind::Oob(v, 0))?);
if !texcoords.is_empty() && vert[1] != u32::MAX {
let vt = vert[1] as usize;
mesh.texcoords[0].push(*texcoords.get(vt).ok_or(ErrorKind::Oob(vt, 0))?);
}
if !normals.is_empty() && vert[2] != u32::MAX {
let vn = vert[2] as usize;
mesh.normals
.push(*normals.get(vn).ok_or(ErrorKind::Oob(vn, 0))?);
}
if !colors.is_empty() {
let color = colors.get(v).ok_or(ErrorKind::Oob(v, 0))?;
mesh.colors[0].push([color[0], color[1], color[2], 1.0]);
}
Ok(())
}
#[inline(always)]
fn push_mesh(
meshes: &mut Vec<Mesh>,
faces: &mut Vec<Face>,
vertices: &[Vec3],
texcoords: &[Vec2],
normals: &[Vec3],
colors: &[Vec3],
current_group: &[u8],
material_index: Option<u32>,
) -> Result<(), ErrorKind> {
if !faces.is_empty() {
let mut mesh = Mesh {
name: from_utf8_lossy(current_group).into_owned(),
material_index: material_index.unwrap_or(u32::MAX),
..Default::default()
};
for face in &*faces {
match face {
Face::Point(_) | Face::Line(_) => {} Face::Triangle(face) => {
#[allow(clippy::cast_possible_truncation)]
let vertices_indices = [
mesh.vertices.len() as u32,
(mesh.vertices.len() + 1) as u32,
(mesh.vertices.len() + 2) as u32,
];
push_vertex(&mut mesh, face[0], vertices, colors, texcoords, normals)?;
push_vertex(&mut mesh, face[1], vertices, colors, texcoords, normals)?;
push_vertex(&mut mesh, face[2], vertices, colors, texcoords, normals)?;
mesh.faces.push(vertices_indices);
}
Face::Polygon(face) => {
let a = face[0];
let mut b = face[1];
for &c in &face[2..] {
#[allow(clippy::cast_possible_truncation)]
let vertices_indices = [
mesh.vertices.len() as u32,
(mesh.vertices.len() + 1) as u32,
(mesh.vertices.len() + 2) as u32,
];
push_vertex(&mut mesh, a, vertices, colors, texcoords, normals)?;
push_vertex(&mut mesh, b, vertices, colors, texcoords, normals)?;
push_vertex(&mut mesh, c, vertices, colors, texcoords, normals)?;
mesh.faces.push(vertices_indices);
b = c;
}
}
}
}
if !mesh.colors[0].is_empty() && mesh.vertices.len() != mesh.colors[0].len() {
return Err(ErrorKind::InvalidFaceIndex(0));
}
if !mesh.texcoords[0].is_empty() && mesh.vertices.len() != mesh.texcoords[0].len() {
return Err(ErrorKind::InvalidFaceIndex(0));
}
if !mesh.normals.is_empty() && mesh.vertices.len() != mesh.normals.len() {
return Err(ErrorKind::InvalidFaceIndex(0));
}
meshes.push(mesh);
faces.clear();
}
Ok(())
}
#[doc(hidden)]
#[allow(clippy::implicit_hasher)]
pub fn read_mtl(
bytes: &[u8],
path: Option<&Path>,
materials: &mut Vec<common::Material>,
material_map: &mut HashMap<Vec<u8>, u32>,
) -> io::Result<()> {
let bytes = &decode_bytes(bytes)?;
match read_mtl_internal(bytes, path.and_then(Path::parent), materials, material_map) {
Ok(()) => Ok(()),
Err(e) => Err(e.into_io_error(bytes, path)),
}
}
fn read_mtl_internal(
mut s: &[u8],
mtl_dir: Option<&Path>,
materials: &mut Vec<common::Material>,
material_map: &mut HashMap<Vec<u8>, u32>,
) -> Result<(), ErrorKind> {
let mut mat: Option<Material<'_>> = None;
let mut current_name: &[u8] = b"";
while let Some((&c, s_next)) = s.split_first() {
match c {
b'K' | b'k' => {
s = s_next;
match s.first() {
Some(b'a') => {
s = &s[1..];
if skip_spaces(&mut s) {
let color = read_color(&mut s, "Ka")?;
if let Some(mat) = &mut mat {
mat.ambient = Some(color);
}
continue;
}
}
Some(b'd') => {
s = &s[1..];
if skip_spaces(&mut s) {
let color = read_color(&mut s, "Kd")?;
if let Some(mat) = &mut mat {
mat.diffuse = Some(color);
}
continue;
}
}
Some(b's') => {
s = &s[1..];
if skip_spaces(&mut s) {
let color = read_color(&mut s, "Ks")?;
if let Some(mat) = &mut mat {
mat.specular = Some(color);
}
continue;
}
}
Some(b'e') => {
s = &s[1..];
if skip_spaces(&mut s) {
let color = read_color(&mut s, "Ke")?;
if let Some(mat) = &mut mat {
mat.emissive = Some(color);
}
continue;
}
}
_ => {}
}
}
b'T' => {
s = s_next;
match s.first() {
Some(b'f') => {
s = &s[1..];
if skip_spaces(&mut s) {
let color = read_color(&mut s, "Tf")?;
if let Some(mat) = &mut mat {
mat.transparent = Some(color);
}
continue;
}
}
Some(b'r') => {
s = &s[1..];
if skip_spaces(&mut s) {
let f = read_float1(&mut s, "Tr")?;
if let Some(mat) = &mut mat {
mat.alpha = Some(1.0 - f);
}
continue;
}
}
_ => {}
}
}
b'd' => {
match s.get(1) {
Some(b' ' | b'\t') => {
s = &s[2..];
skip_spaces(&mut s);
let f = read_float1(&mut s, "d")?;
if let Some(mat) = &mut mat {
mat.alpha = Some(f);
}
continue;
}
Some(b'i') => {
if read_texture(&mut s, &mut mat) {
continue;
}
}
_ => {}
}
s = s_next;
}
b'N' | b'n' => match s.get(1) {
Some(b's') => {
s = &s[2..];
if skip_spaces(&mut s) {
let f = read_float1(&mut s, "Ns")?;
if let Some(mat) = &mut mat {
mat.shininess = Some(f);
}
continue;
}
}
Some(b'i') => {
s = &s[2..];
if skip_spaces(&mut s) {
let f = read_float1(&mut s, "Ni")?;
if let Some(mat) = &mut mat {
mat.ior = Some(f);
}
continue;
}
}
Some(b'e') => {
s = &s[2..];
if token(&mut s, &b"newmtl"[2..]) {
if skip_spaces(&mut s) {
let (name, s_next) = name(s);
if let Some(mat) = mat.replace(Material::default()) {
fn color4(color3: Option<[f32; 3]>) -> Option<Color4> {
let c = color3?;
Some([c[0], c[1], c[2], 1.0])
}
fn texture_path(
texture: Option<&[u8]>,
mtl_dir: Option<&Path>,
) -> Option<PathBuf> {
let mut p = texture?;
if p.is_empty()
|| p.len() == 2
&& (p.starts_with(b".\\") || p.starts_with(b"./"))
{
return None;
}
match mtl_dir {
Some(mtl_dir) => {
p = p.strip_prefix(b".\\").unwrap_or(p);
p = p.strip_prefix(b"./").unwrap_or(p);
let p = path_from_bytes(p).ok()?;
Some(mtl_dir.join(p))
}
None => {
let p = path_from_bytes(p).ok()?;
Some(p.to_owned())
}
}
}
#[allow(clippy::cast_possible_truncation)]
let material_index = materials.len() as u32;
materials.push(common::Material {
name: from_utf8_lossy(current_name).into_owned(),
color: crate::Colors {
ambient: color4(mat.ambient),
diffuse: color4(mat.diffuse),
specular: color4(mat.specular),
emissive: color4(mat.emissive),
},
texture: crate::Textures {
ambient: texture_path(mat.ambient_texture, mtl_dir),
diffuse: texture_path(mat.diffuse_texture, mtl_dir),
specular: texture_path(mat.specular_texture, mtl_dir),
emissive: texture_path(mat.emissive_texture, mtl_dir),
normal: texture_path(mat.normal_texture, mtl_dir),
},
});
material_map.insert(current_name.to_owned(), material_index);
}
current_name = name;
s = s_next;
continue;
}
}
}
Some(b'o') => {
if read_texture(&mut s, &mut mat) {
continue;
}
}
_ => {}
},
b'P' => {
s = s_next;
match s.first() {
Some(b'r') => {
s = &s[1..];
if skip_spaces(&mut s) {
let f = read_float1(&mut s, "Pr")?;
if let Some(mat) = &mut mat {
mat.roughness = Some(f);
}
continue;
}
}
Some(b'm') => {
s = &s[1..];
if skip_spaces(&mut s) {
let f = read_float1(&mut s, "Pm")?;
if let Some(mat) = &mut mat {
mat.metallic = Some(f);
}
continue;
}
}
Some(b's') => {
s = &s[1..];
if skip_spaces(&mut s) {
let color = read_color(&mut s, "Ps")?;
if let Some(mat) = &mut mat {
mat.sheen = Some(color);
}
continue;
}
}
Some(b'c') => {
s = &s[1..];
if s.first() == Some(&b'r') {
if skip_spaces(&mut s) {
let f = read_float1(&mut s, "Pcr")?;
if let Some(mat) = &mut mat {
mat.clearcoat_roughness = Some(f);
}
continue;
}
} else if skip_spaces(&mut s) {
let f = read_float1(&mut s, "Pc")?;
if let Some(mat) = &mut mat {
mat.clearcoat_thickness = Some(f);
}
continue;
}
}
_ => {}
}
}
b'm' | b'b' | b'r' => {
if read_texture(&mut s, &mut mat) {
continue;
}
}
b'i' => {
s = s_next;
if token(&mut s, &b"illum"[1..]) {
if skip_spaces(&mut s) {
match int::parse_partial::<u8>(s) {
Some((i, n)) => {
s = &s[n..];
if !skip_spaces_until_line(&mut s) {
return Err(ErrorKind::ExpectedNewline("illum", s.len()));
}
if let Some(mat) = &mut mat {
mat.illumination_model = Some(i);
}
}
None => return Err(ErrorKind::Int(s.len())),
}
continue;
}
}
}
b'a' => {
s = s_next;
if skip_spaces(&mut s) {
let f = read_float1(&mut s, "a")?;
if let Some(mat) = &mut mat {
mat.anisotropy = Some(f);
}
continue;
}
}
_ => {}
}
skip_any_until_line(&mut s);
}
Ok(())
}
fn read_texture<'a>(s: &mut &'a [u8], mat: &mut Option<Material<'a>>) -> bool {
if token(s, b"map_Kd") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.diffuse_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"map_Ka") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.ambient_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"map_Ks") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.specular_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"map_disp") || token(s, b"disp") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.displacement_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"map_d") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.opacity_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"map_emissive") || token(s, b"map_Ke") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.emissive_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"map_Bump") || token(s, b"map_bump") || token(s, b"bump") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.bump_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"map_Kn") || token(s, b"norm") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.normal_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"refl") {
if skip_spaces(s) {
let (_name, s_next) = name(s);
*s = s_next;
return true;
}
} else if token(s, b"map_Ns") || token(s, b"map_ns") || token(s, b"map_NS") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.specularity_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"map_Pr") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.roughness_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"map_Pm") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.metallic_texture = Some(name);
}
*s = s_next;
return true;
}
} else if token(s, b"map_Ps") {
if skip_spaces(s) {
let (name, s_next) = name(s);
if let Some(mat) = mat {
mat.sheen_texture = Some(name);
}
*s = s_next;
return true;
}
}
false
}
enum Face {
Point(#[allow(dead_code)] [[u32; 3]; 1]),
Line(#[allow(dead_code)] [[u32; 3]; 2]),
Triangle([[u32; 3]; 3]),
Polygon(Vec<[u32; 3]>),
}
#[derive(Default)]
struct Material<'a> {
diffuse_texture: Option<&'a [u8]>,
specular_texture: Option<&'a [u8]>,
ambient_texture: Option<&'a [u8]>,
emissive_texture: Option<&'a [u8]>,
bump_texture: Option<&'a [u8]>,
normal_texture: Option<&'a [u8]>,
specularity_texture: Option<&'a [u8]>,
opacity_texture: Option<&'a [u8]>,
displacement_texture: Option<&'a [u8]>,
roughness_texture: Option<&'a [u8]>,
metallic_texture: Option<&'a [u8]>,
sheen_texture: Option<&'a [u8]>,
ambient: Option<[f32; 3]>,
diffuse: Option<[f32; 3]>,
specular: Option<[f32; 3]>,
emissive: Option<[f32; 3]>,
alpha: Option<f32>,
shininess: Option<f32>,
illumination_model: Option<u8>,
ior: Option<f32>,
transparent: Option<[f32; 3]>,
roughness: Option<f32>,
metallic: Option<f32>,
sheen: Option<[f32; 3]>,
clearcoat_thickness: Option<f32>,
clearcoat_roughness: Option<f32>,
anisotropy: Option<f32>,
}
const __: u8 = 0;
const WS: u8 = 1 << 0;
const WS_NO_LINE: u8 = 1 << 1;
const LINE: u8 = 1 << 2;
const LN: u8 = WS | LINE;
const NL: u8 = WS | WS_NO_LINE;
static TABLE: [u8; 256] = [
__, __, __, __, __, __, __, __, __, NL, LN, __, __, LN, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, NL, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, ];
#[inline]
fn skip_whitespace_until_byte_or_eof(s: &mut &[u8], byte_mask: u8, whitespace_mask: u8) -> bool {
while let Some((&b, s_next)) = s.split_first() {
let b = TABLE[b as usize];
if b & byte_mask != 0 {
*s = s_next;
break;
}
if b & whitespace_mask != 0 {
*s = s_next;
continue;
}
if b == b'\\' && matches!(s_next.first(), Some(b'\n' | b'\r')) {
if s_next.starts_with(b"\r\n") {
*s = &s_next[2..];
} else {
*s = &s_next[1..];
}
continue;
}
return false;
}
true
}
#[inline]
fn skip_spaces_until_line(s: &mut &[u8]) -> bool {
skip_whitespace_until_byte_or_eof(s, LINE, WS_NO_LINE)
}
#[inline]
fn skip_spaces(s: &mut &[u8]) -> bool {
let start = *s;
while let Some((&b, s_next)) = s.split_first() {
let b = TABLE[b as usize];
if b & WS_NO_LINE != 0 {
*s = s_next;
continue;
}
if b == b'\\' && matches!(s_next.first(), Some(b'\n' | b'\r')) {
if s_next.starts_with(b"\r\n") {
*s = &s_next[2..];
} else {
*s = &s_next[1..];
}
continue;
}
break;
}
start.len() != s.len()
}
#[inline]
fn skip_any_until_line(s: &mut &[u8]) {
loop {
match memchr_naive_table(LINE, &TABLE, s) {
Some(n) => {
if s.get(n.wrapping_sub(1)) == Some(&b'\\') {
if s[n..].starts_with(b"\r\n") {
*s = &s[n + 2..];
} else {
*s = &s[n + 1..];
}
continue;
}
*s = &s[n + 1..];
}
None => *s = &[],
}
break;
}
}
#[inline]
fn token(s: &mut &[u8], token: &'static [u8]) -> bool {
if starts_with(s, token) {
*s = &s[token.len()..];
true
} else {
false
}
}
fn name(s: &[u8]) -> (&[u8], &[u8]) {
let mut name;
let s_next = match memchr_naive_table(LINE, &TABLE, s) {
Some(n) => {
name = &s[..n];
&s[n + 1..]
}
None => {
name = s;
&[]
}
};
while let Some((&b, name_next)) = name.split_last() {
if TABLE[b as usize] & WS != 0 {
name = name_next;
continue;
}
break;
}
(name, s_next)
}
mod error {
use std::{fmt, io, path::Path, str};
#[cfg_attr(test, derive(Debug))]
pub(super) enum ErrorKind {
ExpectedSpace(&'static str, usize),
ExpectedNewline(&'static str, usize),
Expected(&'static str, usize),
Float(usize),
Int(usize),
InvalidW(usize),
InvalidFaceIndex(usize),
Oob(usize, usize),
Io(io::Error),
}
impl ErrorKind {
#[cold]
#[inline(never)]
pub(super) fn into_io_error(self, start: &[u8], path: Option<&Path>) -> io::Error {
let remaining = match self {
Self::Expected(.., n)
| Self::ExpectedNewline(.., n)
| Self::ExpectedSpace(.., n)
| Self::Float(n)
| Self::Int(n)
| Self::InvalidW(n)
| Self::InvalidFaceIndex(n)
| Self::Oob(.., n) => n,
Self::Io(e) => return e,
};
crate::error::with_location(
&crate::error::invalid_data(self.to_string()),
&crate::error::Location::find(remaining, start, path),
)
}
}
impl fmt::Display for ErrorKind {
#[cold]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::ExpectedSpace(msg, ..) => write!(f, "expected space after {msg}"),
Self::ExpectedNewline(msg, ..) => write!(f, "expected newline after {msg}"),
Self::Expected(msg, ..) => write!(f, "expected {msg}"),
Self::InvalidW(..) => write!(f, "w in homogeneous vector must not zero"),
Self::InvalidFaceIndex(..) => write!(f, "invalid face index"),
Self::Float(..) => write!(f, "error while parsing a float"),
Self::Int(..) => write!(f, "error while parsing an integer"),
Self::Oob(i, ..) => write!(f, "face index out of bounds ({i})"),
Self::Io(ref e) => write!(f, "{e}"),
}
}
}
}