#![cfg_attr(feature = "merging", allow(incomplete_features))]
#![cfg_attr(feature = "merging", feature(generic_const_exprs))]
#[cfg(test)]
mod tests;
use std::{
error::Error,
fmt,
fs::File,
io::{prelude::*, BufReader},
path::Path,
str::{FromStr, SplitWhitespace},
};
#[cfg(feature = "use_f64")]
type Float = f64;
#[cfg(not(feature = "use_f64"))]
type Float = f32;
#[cfg(feature = "async")]
use std::future::Future;
#[cfg(feature = "merging")]
use std::mem::size_of;
#[cfg(feature = "ahash")]
type HashMap<K, V> = ahash::AHashMap<K, V>;
#[cfg(not(feature = "ahash"))]
type HashMap<K, V> = std::collections::HashMap<K, V>;
pub const GPU_LOAD_OPTIONS: LoadOptions = LoadOptions {
#[cfg(feature = "merging")]
merge_identical_points: false,
#[cfg(feature = "reordering")]
reorder_data: false,
single_index: true,
triangulate: true,
ignore_points: true,
ignore_lines: true,
};
pub const OFFLINE_RENDERING_LOAD_OPTIONS: LoadOptions = LoadOptions {
#[cfg(feature = "merging")]
merge_identical_points: true,
#[cfg(feature = "reordering")]
reorder_data: true,
single_index: false,
triangulate: false,
ignore_points: true,
ignore_lines: true,
};
#[derive(Debug, Clone, Default)]
pub struct Mesh {
pub positions: Vec<Float>,
pub vertex_color: Vec<Float>,
pub normals: Vec<Float>,
pub texcoords: Vec<Float>,
pub indices: Vec<u32>,
pub face_arities: Vec<u32>,
#[cfg(feature = "merging")]
pub vertex_color_indices: Vec<u32>,
pub texcoord_indices: Vec<u32>,
pub normal_indices: Vec<u32>,
pub material_id: Option<usize>,
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Default, Clone, Copy)]
pub struct LoadOptions {
#[cfg(feature = "merging")]
pub merge_identical_points: bool,
#[cfg(feature = "reordering")]
pub reorder_data: bool,
pub single_index: bool,
pub triangulate: bool,
pub ignore_points: bool,
pub ignore_lines: bool,
}
impl LoadOptions {
pub fn is_valid(&self) -> bool {
#[allow(unused_mut)]
let mut other_flags = false;
#[cfg(feature = "merging")]
{
other_flags = other_flags || self.merge_identical_points;
}
#[cfg(feature = "reordering")]
{
other_flags = other_flags || self.reorder_data;
}
(self.single_index != other_flags) || (!self.single_index && !other_flags)
}
}
#[derive(Clone, Debug)]
pub struct Model {
pub mesh: Mesh,
pub name: String,
}
impl Model {
pub fn new(mesh: Mesh, name: String) -> Model {
Model { mesh, name }
}
}
#[derive(Clone, Debug, Default)]
pub struct Material {
pub name: String,
pub ambient: Option<[Float; 3]>,
pub diffuse: Option<[Float; 3]>,
pub specular: Option<[Float; 3]>,
pub shininess: Option<Float>,
pub dissolve: Option<Float>,
pub optical_density: Option<Float>,
pub ambient_texture: Option<String>,
pub diffuse_texture: Option<String>,
pub specular_texture: Option<String>,
pub normal_texture: Option<String>,
pub shininess_texture: Option<String>,
pub dissolve_texture: Option<String>,
pub illumination_model: Option<u8>,
pub unknown_param: HashMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LoadError {
OpenFileFailed,
ReadError,
UnrecognizedCharacter,
PositionParseError,
NormalParseError,
TexcoordParseError,
FaceParseError,
MaterialParseError,
InvalidObjectName,
InvalidPolygon,
FaceVertexOutOfBounds,
FaceTexCoordOutOfBounds,
FaceNormalOutOfBounds,
FaceColorOutOfBounds,
InvalidLoadOptionConfig,
GenericFailure,
}
impl fmt::Display for LoadError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let msg = match *self {
LoadError::OpenFileFailed => "open file failed",
LoadError::ReadError => "read error",
LoadError::UnrecognizedCharacter => "unrecognized character",
LoadError::PositionParseError => "position parse error",
LoadError::NormalParseError => "normal parse error",
LoadError::TexcoordParseError => "texcoord parse error",
LoadError::FaceParseError => "face parse error",
LoadError::MaterialParseError => "material parse error",
LoadError::InvalidObjectName => "invalid object name",
LoadError::InvalidPolygon => "invalid polygon",
LoadError::FaceVertexOutOfBounds => "face vertex index out of bounds",
LoadError::FaceTexCoordOutOfBounds => "face texcoord index out of bounds",
LoadError::FaceNormalOutOfBounds => "face normal index out of bounds",
LoadError::FaceColorOutOfBounds => "face vertex color index out of bounds",
LoadError::InvalidLoadOptionConfig => "mutually exclusive load options",
LoadError::GenericFailure => "generic failure",
};
f.write_str(msg)
}
}
impl Error for LoadError {}
pub type LoadResult = Result<(Vec<Model>, Result<Vec<Material>, LoadError>), LoadError>;
pub type MTLLoadResult = Result<(Vec<Material>, HashMap<String, usize>), LoadError>;
#[derive(Hash, Eq, PartialEq, PartialOrd, Ord, Debug, Copy, Clone)]
struct VertexIndices {
pub v: usize,
pub vt: usize,
pub vn: usize,
}
static MISSING_INDEX: usize = usize::MAX;
impl VertexIndices {
fn parse(
face_str: &str,
pos_sz: usize,
tex_sz: usize,
norm_sz: usize,
) -> Option<VertexIndices> {
let mut indices = [MISSING_INDEX; 3];
for i in face_str.split('/').enumerate() {
if !i.1.is_empty() {
match isize::from_str(i.1) {
Ok(x) => {
*indices.get_mut(i.0)? = if x < 0 {
match i.0 {
0 => (pos_sz as isize + x) as _,
1 => (tex_sz as isize + x) as _,
2 => (norm_sz as isize + x) as _,
_ => return None, }
} else {
(x - 1) as _
};
}
Err(_) => return None,
}
}
}
Some(VertexIndices {
v: indices[0],
vt: indices[1],
vn: indices[2],
})
}
}
#[derive(Debug)]
enum Face {
Point(VertexIndices),
Line(VertexIndices, VertexIndices),
Triangle(VertexIndices, VertexIndices, VertexIndices),
Quad(VertexIndices, VertexIndices, VertexIndices, VertexIndices),
Polygon(Vec<VertexIndices>),
}
fn parse_floatn(val_str: &mut SplitWhitespace, vals: &mut Vec<Float>, n: usize) -> bool {
let sz = vals.len();
for p in val_str.take(n) {
match FromStr::from_str(p) {
Ok(x) => vals.push(x),
Err(_) => return false,
}
}
sz + n == vals.len()
}
fn parse_float3(val_str: SplitWhitespace) -> Result<[Float; 3], LoadError> {
let arr: [Float; 3] = val_str
.take(3)
.map(FromStr::from_str)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| LoadError::MaterialParseError)?
.try_into()
.unwrap();
Ok(arr)
}
fn parse_float(val_str: Option<&str>) -> Result<Float, LoadError> {
val_str
.map(FromStr::from_str)
.map_or(Err(LoadError::MaterialParseError), |v| {
v.map_err(|_| LoadError::MaterialParseError)
})
}
fn parse_face(
face_str: SplitWhitespace,
faces: &mut Vec<Face>,
pos_sz: usize,
tex_sz: usize,
norm_sz: usize,
) -> bool {
let mut indices = Vec::new();
for f in face_str {
match VertexIndices::parse(f, pos_sz, tex_sz, norm_sz) {
Some(v) => indices.push(v),
None => return false,
}
}
match indices.len() {
1 => faces.push(Face::Point(indices[0])),
2 => faces.push(Face::Line(indices[0], indices[1])),
3 => faces.push(Face::Triangle(indices[0], indices[1], indices[2])),
4 => faces.push(Face::Quad(indices[0], indices[1], indices[2], indices[3])),
_ => faces.push(Face::Polygon(indices)),
}
true
}
fn add_vertex(
mesh: &mut Mesh,
index_map: &mut HashMap<VertexIndices, u32>,
vert: &VertexIndices,
pos: &[Float],
v_color: &[Float],
texcoord: &[Float],
normal: &[Float],
) -> Result<(), LoadError> {
match index_map.get(vert) {
Some(&i) => mesh.indices.push(i),
None => {
let v = vert.v;
if v.saturating_mul(3).saturating_add(2) >= pos.len() {
return Err(LoadError::FaceVertexOutOfBounds);
}
mesh.positions.push(pos[v * 3]);
mesh.positions.push(pos[v * 3 + 1]);
mesh.positions.push(pos[v * 3 + 2]);
if !texcoord.is_empty() && vert.vt != MISSING_INDEX {
let vt = vert.vt;
if vt * 2 + 1 >= texcoord.len() {
return Err(LoadError::FaceTexCoordOutOfBounds);
}
mesh.texcoords.push(texcoord[vt * 2]);
mesh.texcoords.push(texcoord[vt * 2 + 1]);
}
if !normal.is_empty() && vert.vn != MISSING_INDEX {
let vn = vert.vn;
if vn * 3 + 2 >= normal.len() {
return Err(LoadError::FaceNormalOutOfBounds);
}
mesh.normals.push(normal[vn * 3]);
mesh.normals.push(normal[vn * 3 + 1]);
mesh.normals.push(normal[vn * 3 + 2]);
}
if !v_color.is_empty() {
if v * 3 + 2 >= v_color.len() {
return Err(LoadError::FaceColorOutOfBounds);
}
mesh.vertex_color.push(v_color[v * 3]);
mesh.vertex_color.push(v_color[v * 3 + 1]);
mesh.vertex_color.push(v_color[v * 3 + 2]);
}
let next = index_map.len() as u32;
mesh.indices.push(next);
index_map.insert(*vert, next);
}
}
Ok(())
}
fn export_faces(
pos: &[Float],
v_color: &[Float],
texcoord: &[Float],
normal: &[Float],
faces: &[Face],
mat_id: Option<usize>,
load_options: &LoadOptions,
) -> Result<Mesh, LoadError> {
let mut index_map = HashMap::new();
let mut mesh = Mesh {
material_id: mat_id,
..Default::default()
};
let mut is_all_triangles = true;
for f in faces {
match *f {
Face::Point(ref a) => {
if !load_options.ignore_points {
add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
if load_options.triangulate {
add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
} else {
is_all_triangles = false;
mesh.face_arities.push(1);
}
}
}
Face::Line(ref a, ref b) => {
if !load_options.ignore_lines {
add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
add_vertex(&mut mesh, &mut index_map, b, pos, v_color, texcoord, normal)?;
if load_options.triangulate {
add_vertex(&mut mesh, &mut index_map, b, pos, v_color, texcoord, normal)?;
} else {
is_all_triangles = false;
mesh.face_arities.push(2);
}
}
}
Face::Triangle(ref a, ref b, ref c) => {
add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
add_vertex(&mut mesh, &mut index_map, b, pos, v_color, texcoord, normal)?;
add_vertex(&mut mesh, &mut index_map, c, pos, v_color, texcoord, normal)?;
if !load_options.triangulate {
mesh.face_arities.push(3);
}
}
Face::Quad(ref a, ref b, ref c, ref d) => {
add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
add_vertex(&mut mesh, &mut index_map, b, pos, v_color, texcoord, normal)?;
add_vertex(&mut mesh, &mut index_map, c, pos, v_color, texcoord, normal)?;
if load_options.triangulate {
add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
add_vertex(&mut mesh, &mut index_map, c, pos, v_color, texcoord, normal)?;
add_vertex(&mut mesh, &mut index_map, d, pos, v_color, texcoord, normal)?;
} else {
add_vertex(&mut mesh, &mut index_map, d, pos, v_color, texcoord, normal)?;
is_all_triangles = false;
mesh.face_arities.push(4);
}
}
Face::Polygon(ref indices) => {
if load_options.triangulate {
let a = indices.first().ok_or(LoadError::InvalidPolygon)?;
let mut b = indices.get(1).ok_or(LoadError::InvalidPolygon)?;
for c in indices.iter().skip(2) {
add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
add_vertex(&mut mesh, &mut index_map, b, pos, v_color, texcoord, normal)?;
add_vertex(&mut mesh, &mut index_map, c, pos, v_color, texcoord, normal)?;
b = c;
}
} else {
for i in indices.iter() {
add_vertex(&mut mesh, &mut index_map, i, pos, v_color, texcoord, normal)?;
}
is_all_triangles = false;
mesh.face_arities.push(indices.len() as u32);
}
}
}
}
if is_all_triangles {
mesh.face_arities = Vec::new();
}
Ok(mesh)
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn add_vertex_multi_index(
mesh: &mut Mesh,
index_map: &mut HashMap<usize, u32>,
normal_index_map: &mut HashMap<usize, u32>,
texcoord_index_map: &mut HashMap<usize, u32>,
vert: &VertexIndices,
pos: &[Float],
v_color: &[Float],
texcoord: &[Float],
normal: &[Float],
) -> Result<(), LoadError> {
match index_map.get(&vert.v) {
Some(&i) => mesh.indices.push(i),
None => {
let vertex = vert.v;
if vertex.saturating_mul(3).saturating_add(2) >= pos.len() {
return Err(LoadError::FaceVertexOutOfBounds);
}
mesh.positions.push(pos[vertex * 3]);
mesh.positions.push(pos[vertex * 3 + 1]);
mesh.positions.push(pos[vertex * 3 + 2]);
let next = index_map.len() as u32;
mesh.indices.push(next);
index_map.insert(vertex, next);
if !v_color.is_empty() {
let vertex = vert.v;
if vertex * 3 + 2 >= v_color.len() {
return Err(LoadError::FaceColorOutOfBounds);
}
mesh.vertex_color.push(v_color[vertex * 3]);
mesh.vertex_color.push(v_color[vertex * 3 + 1]);
mesh.vertex_color.push(v_color[vertex * 3 + 2]);
}
}
}
if !texcoord.is_empty() {
let texcoord_indices = &mut mesh.texcoord_indices;
if MISSING_INDEX == vert.vt {
if texcoord_indices.is_empty() {
mesh.texcoords.push(texcoord[0]);
mesh.texcoords.push(texcoord[1]);
texcoord_indices.push(0);
texcoord_index_map.insert(0, 0);
} else {
texcoord_indices.push(*texcoord_indices.last().unwrap());
}
} else {
match texcoord_index_map.get(&vert.vt) {
Some(&index) => mesh.texcoord_indices.push(index as _),
None => {
let vt = vert.vt;
if vt * 2 + 1 >= texcoord.len() {
return Err(LoadError::FaceTexCoordOutOfBounds);
}
mesh.texcoords.push(texcoord[vt * 2]);
mesh.texcoords.push(texcoord[vt * 2 + 1]);
let next = texcoord_index_map.len() as u32;
mesh.texcoord_indices.push(next);
texcoord_index_map.insert(vt, next);
}
}
}
}
if !normal.is_empty() {
let normal_indices = &mut mesh.normal_indices;
if MISSING_INDEX == vert.vn {
if normal_indices.is_empty() {
mesh.normals.push(normal[0]);
mesh.normals.push(normal[1]);
mesh.normals.push(normal[2]);
normal_indices.push(0);
normal_index_map.insert(0, 0);
} else {
normal_indices.push(*normal_indices.last().unwrap());
}
} else {
match normal_index_map.get(&vert.vn) {
Some(&index) => normal_indices.push(index as _),
None => {
let vn = vert.vn;
if vn * 3 + 2 >= normal.len() {
return Err(LoadError::FaceNormalOutOfBounds);
}
mesh.normals.push(normal[vn * 3]);
mesh.normals.push(normal[vn * 3 + 1]);
mesh.normals.push(normal[vn * 3 + 2]);
let next = normal_index_map.len() as u32;
normal_indices.push(next);
normal_index_map.insert(vn, next);
}
}
}
}
Ok(())
}
fn export_faces_multi_index(
pos: &[Float],
v_color: &[Float],
texcoord: &[Float],
normal: &[Float],
faces: &[Face],
mat_id: Option<usize>,
load_options: &LoadOptions,
) -> Result<Mesh, LoadError> {
let mut index_map = HashMap::new();
let mut normal_index_map = HashMap::new();
let mut texcoord_index_map = HashMap::new();
let mut mesh = Mesh {
material_id: mat_id,
..Default::default()
};
let mut is_all_triangles = true;
for f in faces {
match *f {
Face::Point(ref a) => {
if !load_options.ignore_points {
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
a,
pos,
v_color,
texcoord,
normal,
)?;
if load_options.triangulate {
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
a,
pos,
v_color,
texcoord,
normal,
)?;
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
a,
pos,
v_color,
texcoord,
normal,
)?;
} else {
is_all_triangles = false;
mesh.face_arities.push(1);
}
}
}
Face::Line(ref a, ref b) => {
if !load_options.ignore_lines {
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
a,
pos,
v_color,
texcoord,
normal,
)?;
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
b,
pos,
v_color,
texcoord,
normal,
)?;
if load_options.triangulate {
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
b,
pos,
v_color,
texcoord,
normal,
)?;
} else {
is_all_triangles = false;
mesh.face_arities.push(2);
}
}
}
Face::Triangle(ref a, ref b, ref c) => {
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
a,
pos,
v_color,
texcoord,
normal,
)?;
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
b,
pos,
v_color,
texcoord,
normal,
)?;
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
c,
pos,
v_color,
texcoord,
normal,
)?;
if !load_options.triangulate {
mesh.face_arities.push(3);
}
}
Face::Quad(ref a, ref b, ref c, ref d) => {
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
a,
pos,
v_color,
texcoord,
normal,
)?;
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
b,
pos,
v_color,
texcoord,
normal,
)?;
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
c,
pos,
v_color,
texcoord,
normal,
)?;
if load_options.triangulate {
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
a,
pos,
v_color,
texcoord,
normal,
)?;
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
c,
pos,
v_color,
texcoord,
normal,
)?;
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
d,
pos,
v_color,
texcoord,
normal,
)?;
} else {
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
d,
pos,
v_color,
texcoord,
normal,
)?;
is_all_triangles = false;
mesh.face_arities.push(4);
}
}
Face::Polygon(ref indices) => {
if load_options.triangulate {
let a = indices.first().ok_or(LoadError::InvalidPolygon)?;
let mut b = indices.get(1).ok_or(LoadError::InvalidPolygon)?;
for c in indices.iter().skip(2) {
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
a,
pos,
v_color,
texcoord,
normal,
)?;
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
b,
pos,
v_color,
texcoord,
normal,
)?;
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
c,
pos,
v_color,
texcoord,
normal,
)?;
b = c;
}
} else {
for i in indices.iter() {
add_vertex_multi_index(
&mut mesh,
&mut index_map,
&mut normal_index_map,
&mut texcoord_index_map,
i,
pos,
v_color,
texcoord,
normal,
)?;
}
is_all_triangles = false;
mesh.face_arities.push(indices.len() as u32);
}
}
}
}
if is_all_triangles {
mesh.face_arities = Vec::new();
}
#[cfg(feature = "merging")]
if load_options.merge_identical_points {
if !mesh.vertex_color.is_empty() {
mesh.vertex_color_indices = mesh.indices.clone();
merge_identical_points::<3>(&mut mesh.vertex_color, &mut mesh.vertex_color_indices);
}
merge_identical_points::<3>(&mut mesh.positions, &mut mesh.indices);
merge_identical_points::<3>(&mut mesh.normals, &mut mesh.normal_indices);
merge_identical_points::<2>(&mut mesh.texcoords, &mut mesh.texcoord_indices);
}
#[cfg(feature = "reordering")]
if load_options.reorder_data {
reorder_data(&mut mesh);
}
Ok(mesh)
}
#[cfg(feature = "reordering")]
#[inline]
fn reorder_data(mesh: &mut Mesh) {
if mesh.positions.len() < mesh.texcoords.len() {
mesh.texcoords = mesh
.texcoord_indices
.iter()
.flat_map(|&index| {
let index = index as usize * 2;
IntoIterator::into_iter([mesh.texcoords[index], mesh.texcoords[index + 1]])
})
.collect::<Vec<_>>();
} else {
assert!(mesh.texcoords.len() == mesh.positions.len());
let mut new_texcoords = vec![0.0; mesh.positions.len()];
mesh.texcoord_indices
.iter()
.zip(&mesh.indices)
.for_each(|(&texcoord_index, &index)| {
let texcoord_index = texcoord_index as usize * 2;
let index = index as usize * 2;
new_texcoords[index] = mesh.texcoords[texcoord_index];
new_texcoords[index + 1] = mesh.texcoords[texcoord_index + 1];
});
mesh.texcoords = new_texcoords;
}
mesh.texcoord_indices = Vec::new();
if mesh.positions.len() < mesh.normals.len() {
mesh.normals = mesh
.normal_indices
.iter()
.flat_map(|&index| {
let index = index as usize * 2;
IntoIterator::into_iter([
mesh.normals[index],
mesh.normals[index + 1],
mesh.normals[index + 2],
])
})
.collect::<Vec<_>>();
} else {
assert!(mesh.normals.len() == mesh.positions.len());
let mut new_normals = vec![0.0; mesh.positions.len()];
mesh.normal_indices
.iter()
.zip(&mesh.indices)
.for_each(|(&normal_index, &index)| {
let normal_index = normal_index as usize * 3;
let index = index as usize * 3;
new_normals[index] = mesh.normals[normal_index];
new_normals[index + 1] = mesh.normals[normal_index + 1];
new_normals[index + 2] = mesh.normals[normal_index + 2];
});
mesh.normals = new_normals;
}
mesh.normal_indices = Vec::new();
}
#[cfg(feature = "merging")]
#[inline]
fn merge_identical_points<const N: usize>(points: &mut Vec<Float>, indices: &mut Vec<u32>)
where
[(); size_of::<[Float; N]>()]:,
{
if indices.is_empty() {
return;
}
let mut compressed_indices = Vec::new();
let mut canonical_indices = HashMap::<[u8; size_of::<[Float; N]>()], u32>::new();
let mut index = 0;
*points = points
.chunks(N)
.filter_map(|position| {
let position: &[Float; N] = &unsafe { *(position.as_ptr() as *const [Float; N]) };
let bitpattern = unsafe {
std::mem::transmute::<&[Float; N], &[u8; size_of::<[Float; N]>()]>(position)
};
match canonical_indices.get(bitpattern) {
Some(&other_index) => {
compressed_indices.push(other_index);
None
}
None => {
canonical_indices.insert(*bitpattern, index);
compressed_indices.push(index);
index += 1;
Some(IntoIterator::into_iter(*position))
}
}
})
.flatten()
.collect();
indices
.iter_mut()
.for_each(|vertex| *vertex = compressed_indices[*vertex as usize]);
}
pub fn load_obj<P>(file_name: P, load_options: &LoadOptions) -> LoadResult
where
P: AsRef<Path> + fmt::Debug,
{
let file = match File::open(file_name.as_ref()) {
Ok(f) => f,
Err(_e) => {
#[cfg(feature = "log")]
log::error!("load_obj - failed to open {:?} due to {}", file_name, _e);
return Err(LoadError::OpenFileFailed);
}
};
let mut reader = BufReader::new(file);
load_obj_buf(&mut reader, load_options, |mat_path| {
let full_path = if let Some(parent) = file_name.as_ref().parent() {
parent.join(mat_path)
} else {
mat_path.to_owned()
};
self::load_mtl(full_path)
})
}
pub fn load_mtl<P>(file_name: P) -> MTLLoadResult
where
P: AsRef<Path> + fmt::Debug,
{
let file = match File::open(file_name.as_ref()) {
Ok(f) => f,
Err(_e) => {
#[cfg(feature = "log")]
log::error!("load_mtl - failed to open {:?} due to {}", file_name, _e);
return Err(LoadError::OpenFileFailed);
}
};
let mut reader = BufReader::new(file);
load_mtl_buf(&mut reader)
}
pub fn load_obj_buf<B, ML>(
reader: &mut B,
load_options: &LoadOptions,
material_loader: ML,
) -> LoadResult
where
B: BufRead,
ML: Fn(&Path) -> MTLLoadResult,
{
if !load_options.is_valid() {
return Err(LoadError::InvalidLoadOptionConfig);
}
let mut models = Vec::new();
let mut materials = Vec::new();
let mut mat_map = HashMap::new();
let mut tmp_pos = Vec::new();
let mut tmp_v_color = Vec::new();
let mut tmp_texcoord = Vec::new();
let mut tmp_normal = Vec::new();
let mut tmp_faces: Vec<Face> = Vec::new();
let mut name = "unnamed_object".to_owned();
let mut mat_id = None;
let mut mtlresult = Ok(Vec::new());
for line in reader.lines() {
let (line, mut words) = match line {
Ok(ref line) => (&line[..], line[..].split_whitespace()),
Err(_e) => {
#[cfg(feature = "log")]
log::error!("load_obj - failed to read line due to {}", _e);
return Err(LoadError::ReadError);
}
};
match words.next() {
Some("#") | None => continue,
Some("v") => {
if !parse_floatn(&mut words, &mut tmp_pos, 3) {
return Err(LoadError::PositionParseError);
}
parse_floatn(&mut words, &mut tmp_v_color, 3);
}
Some("vt") => {
if !parse_floatn(&mut words, &mut tmp_texcoord, 2) {
return Err(LoadError::TexcoordParseError);
}
}
Some("vn") => {
if !parse_floatn(&mut words, &mut tmp_normal, 3) {
return Err(LoadError::NormalParseError);
}
}
Some("f") | Some("l") => {
if !parse_face(
words,
&mut tmp_faces,
tmp_pos.len() / 3,
tmp_texcoord.len() / 2,
tmp_normal.len() / 3,
) {
return Err(LoadError::FaceParseError);
}
}
Some("o") | Some("g") => {
if !tmp_faces.is_empty() {
models.push(Model::new(
if load_options.single_index {
export_faces(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
} else {
export_faces_multi_index(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
},
name,
));
tmp_faces.clear();
}
let size = line.chars().next().unwrap().len_utf8();
name = line[size..].trim().to_owned();
if name.is_empty() {
name = "unnamed_object".to_owned();
}
}
Some("mtllib") => {
let mtllib = line.split_once(' ').unwrap_or_default().1.trim();
let mat_file = Path::new(mtllib).to_path_buf();
match material_loader(mat_file.as_path()) {
Ok((mut mats, map)) => {
let mat_offset = materials.len();
materials.append(&mut mats);
for m in map {
mat_map.insert(m.0, m.1 + mat_offset);
}
}
Err(e) => {
mtlresult = Err(e);
}
}
}
Some("usemtl") => {
let mat_name = line.split_once(' ').unwrap_or_default().1.trim().to_owned();
if !mat_name.is_empty() {
let new_mat = mat_map.get(&mat_name).cloned();
if mat_id != new_mat && !tmp_faces.is_empty() {
models.push(Model::new(
if load_options.single_index {
export_faces(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
} else {
export_faces_multi_index(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
},
name.clone(),
));
tmp_faces.clear();
}
if new_mat.is_none() {
#[cfg(feature = "log")]
log::warn!("Object {} refers to unfound material: {}", name, mat_name);
}
mat_id = new_mat;
} else {
return Err(LoadError::MaterialParseError);
}
}
Some(_) => {}
}
}
models.push(Model::new(
if load_options.single_index {
export_faces(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
} else {
export_faces_multi_index(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
},
name,
));
if !materials.is_empty() {
mtlresult = Ok(materials);
}
Ok((models, mtlresult))
}
pub fn load_mtl_buf<B: BufRead>(reader: &mut B) -> MTLLoadResult {
let mut materials = Vec::new();
let mut mat_map = HashMap::new();
let mut cur_mat = Material::default();
for line in reader.lines() {
let (line, mut words) = match line {
Ok(ref line) => (line.trim(), line[..].split_whitespace()),
Err(_e) => {
#[cfg(feature = "log")]
log::error!("load_obj - failed to read line due to {}", _e);
return Err(LoadError::ReadError);
}
};
match words.next() {
Some("#") | None => continue,
Some("newmtl") => {
if !cur_mat.name.is_empty() {
mat_map.insert(cur_mat.name.clone(), materials.len());
materials.push(cur_mat);
}
cur_mat = Material::default();
cur_mat.name = line[6..].trim().to_owned();
if cur_mat.name.is_empty() {
return Err(LoadError::InvalidObjectName);
}
}
Some("Ka") => cur_mat.ambient = Some(parse_float3(words)?),
Some("Kd") => cur_mat.diffuse = Some(parse_float3(words)?),
Some("Ks") => cur_mat.specular = Some(parse_float3(words)?),
Some("Ns") => cur_mat.shininess = Some(parse_float(words.next())?),
Some("Ni") => cur_mat.optical_density = Some(parse_float(words.next())?),
Some("d") => cur_mat.dissolve = Some(parse_float(words.next())?),
Some("map_Ka") => match line.get(6..).map(str::trim) {
Some("") | None => return Err(LoadError::MaterialParseError),
Some(tex) => cur_mat.ambient_texture = Some(tex.to_owned()),
},
Some("map_Kd") => match line.get(6..).map(str::trim) {
Some("") | None => return Err(LoadError::MaterialParseError),
Some(tex) => cur_mat.diffuse_texture = Some(tex.to_owned()),
},
Some("map_Ks") => match line.get(6..).map(str::trim) {
Some("") | None => return Err(LoadError::MaterialParseError),
Some(tex) => cur_mat.specular_texture = Some(tex.to_owned()),
},
Some("map_Bump") | Some("map_bump") => match line.get(8..).map(str::trim) {
Some("") | None => return Err(LoadError::MaterialParseError),
Some(tex) => cur_mat.normal_texture = Some(tex.to_owned()),
},
Some("map_Ns") | Some("map_ns") | Some("map_NS") => {
match line.get(6..).map(str::trim) {
Some("") | None => return Err(LoadError::MaterialParseError),
Some(tex) => cur_mat.shininess_texture = Some(tex.to_owned()),
}
}
Some("bump") => match line.get(4..).map(str::trim) {
Some("") | None => return Err(LoadError::MaterialParseError),
Some(tex) => cur_mat.normal_texture = Some(tex.to_owned()),
},
Some("map_d") => match line.get(5..).map(str::trim) {
Some("") | None => return Err(LoadError::MaterialParseError),
Some(tex) => cur_mat.dissolve_texture = Some(tex.to_owned()),
},
Some("illum") => {
if let Some(p) = words.next() {
match FromStr::from_str(p) {
Ok(x) => cur_mat.illumination_model = Some(x),
Err(_) => return Err(LoadError::MaterialParseError),
}
} else {
return Err(LoadError::MaterialParseError);
}
}
Some(unknown) => {
if !unknown.is_empty() {
let param = line[unknown.len()..].trim().to_owned();
cur_mat.unknown_param.insert(unknown.to_owned(), param);
}
}
}
}
if !cur_mat.name.is_empty() {
mat_map.insert(cur_mat.name.clone(), materials.len());
materials.push(cur_mat);
}
Ok((materials, mat_map))
}
#[cfg(feature = "async")]
pub async fn load_obj_buf_async<B, ML, MLFut>(
reader: &mut B,
load_options: &LoadOptions,
material_loader: ML,
) -> LoadResult
where
B: BufRead,
ML: Fn(String) -> MLFut,
MLFut: Future<Output = MTLLoadResult>,
{
if !load_options.is_valid() {
return Err(LoadError::InvalidLoadOptionConfig);
}
let mut models = Vec::new();
let mut materials = Vec::new();
let mut mat_map = HashMap::new();
let mut tmp_pos = Vec::new();
let mut tmp_v_color = Vec::new();
let mut tmp_texcoord = Vec::new();
let mut tmp_normal = Vec::new();
let mut tmp_faces: Vec<Face> = Vec::new();
let mut name = "unnamed_object".to_owned();
let mut mat_id = None;
let mut mtlresult = Ok(Vec::new());
for line in reader.lines() {
let (line, mut words) = match line {
Ok(ref line) => (&line[..], line[..].split_whitespace()),
Err(_e) => {
#[cfg(feature = "log")]
log::error!("load_obj - failed to read line due to {}", _e);
return Err(LoadError::ReadError);
}
};
match words.next() {
Some("#") | None => continue,
Some("v") => {
if !parse_floatn(&mut words, &mut tmp_pos, 3) {
return Err(LoadError::PositionParseError);
}
parse_floatn(&mut words, &mut tmp_v_color, 3);
}
Some("vt") => {
if !parse_floatn(&mut words, &mut tmp_texcoord, 2) {
return Err(LoadError::TexcoordParseError);
}
}
Some("vn") => {
if !parse_floatn(&mut words, &mut tmp_normal, 3) {
return Err(LoadError::NormalParseError);
}
}
Some("f") | Some("l") => {
if !parse_face(
words,
&mut tmp_faces,
tmp_pos.len() / 3,
tmp_texcoord.len() / 2,
tmp_normal.len() / 3,
) {
return Err(LoadError::FaceParseError);
}
}
Some("o") | Some("g") => {
if !tmp_faces.is_empty() {
models.push(Model::new(
if load_options.single_index {
export_faces(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
} else {
export_faces_multi_index(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
},
name,
));
tmp_faces.clear();
}
name = line[1..].trim().to_owned();
if name.is_empty() {
name = "unnamed_object".to_owned();
}
}
Some("mtllib") => {
if let Some(mtllib) = words.next() {
let mat_file = String::from(mtllib);
match material_loader(mat_file).await {
Ok((mut mats, map)) => {
let mat_offset = materials.len();
materials.append(&mut mats);
for m in map {
mat_map.insert(m.0, m.1 + mat_offset);
}
}
Err(e) => {
mtlresult = Err(e);
}
}
} else {
return Err(LoadError::MaterialParseError);
}
}
Some("usemtl") => {
let mat_name = line[7..].trim().to_owned();
if !mat_name.is_empty() {
let new_mat = mat_map.get(&mat_name).cloned();
if mat_id != new_mat && !tmp_faces.is_empty() {
models.push(Model::new(
if load_options.single_index {
export_faces(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
} else {
export_faces_multi_index(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
},
name.clone(),
));
tmp_faces.clear();
}
if new_mat.is_none() {
#[cfg(feature = "log")]
log::warn!("Object {} refers to unfound material: {}", name, mat_name);
}
mat_id = new_mat;
} else {
return Err(LoadError::MaterialParseError);
}
}
Some(_) => {}
}
}
models.push(Model::new(
if load_options.single_index {
export_faces(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
} else {
export_faces_multi_index(
&tmp_pos,
&tmp_v_color,
&tmp_texcoord,
&tmp_normal,
&tmp_faces,
mat_id,
load_options,
)?
},
name,
));
if !materials.is_empty() {
mtlresult = Ok(materials);
}
Ok((models, mtlresult))
}