use std::{
fs::{self},
path::Path,
};
use gltf::Mesh;
use crate::{Indices, Model3D, ModelError, Vertex};
pub fn load(path: &Path) -> Result<Model3D, ModelError> {
let gltf = gltf::Gltf::from_reader(
fs::File::open(path).map_err(|e| ModelError::OpenFile(e.to_string()))?,
)
.map_err(|e| ModelError::ModelParsing(e.to_string()))?;
let path = path.parent().unwrap_or_else(|| Path::new("./"));
let buffer_data = gltf::import_buffers(&gltf.document, Some(path), gltf.blob)
.expect("Failed to Load glTF Buffers");
let mut materials = Vec::new();
let len = gltf.document.materials().len();
for (i, material) in gltf.document.materials().enumerate() {
log::debug!(
"Loading Material {} {}/{}",
material.name().unwrap_or("Unknown"),
i + 1,
len,
);
materials.push(load_material(path, &material, &buffer_data));
}
let mut meshes = Vec::new();
for mesh in gltf.document.meshes() {
meshes.append(&mut load_mesh(&mesh, &buffer_data));
}
Ok(Model3D {
meshes,
materials,
format: crate::ModelFormat::GLTF,
})
}
fn load_material<'a>(
model_dir: &'a Path,
material: &gltf::Material<'a>,
buffer_data: &'a [gltf::buffer::Data],
) -> crate::Material {
let pbr = material.pbr_metallic_roughness();
let diffuse_texture = pbr.base_color_texture().map(|ref texture| {
let texture = texture.texture();
match texture.source().source() {
gltf::image::Source::View { view, mime_type } => {
let parent_buffer_data = &buffer_data[view.buffer().index()].0;
let begin = view.offset();
let end = begin + view.length();
let encoded_image = &parent_buffer_data[begin..end];
let sampler = texture.sampler();
let image = crate::Image::Memory {
data: encoded_image.to_vec(), mime_type: Some(mime_type.to_string()),
};
crate::Texture {
image,
sampler: convert_sampler(&sampler),
name: texture.name().map(std::string::ToString::to_string),
}
}
gltf::image::Source::Uri { uri, mime_type } => {
let sampler = texture.sampler();
let image = crate::Image::Path {
path: model_dir.join(uri),
mime_type: mime_type.map(std::string::ToString::to_string),
};
crate::Texture {
image,
sampler: convert_sampler(&sampler),
name: texture.name().map(std::string::ToString::to_string),
}
}
}
});
let alpha_mode = convert_alpha_mode(material.alpha_mode());
crate::Material {
diffuse_texture,
alpha_mode,
double_sided: material.double_sided(),
name: material.name().map(std::string::ToString::to_string),
base_color: Some(pbr.base_color_factor()),
alpha_cutoff: material.alpha_cutoff(),
}
}
fn convert_sampler<'a>(sampler: &'a gltf::texture::Sampler<'a>) -> crate::Sampler {
let mag_filter = sampler.mag_filter().map(|filter| match filter {
gltf::texture::MagFilter::Nearest => crate::MagFilter::Nearest,
gltf::texture::MagFilter::Linear => crate::MagFilter::Linear,
});
let min_filter = sampler.min_filter().map(|filter| match filter {
gltf::texture::MinFilter::Nearest => crate::MinFilter::Nearest,
gltf::texture::MinFilter::Linear => crate::MinFilter::Linear,
gltf::texture::MinFilter::NearestMipmapNearest => crate::MinFilter::NearestMipmapNearest,
gltf::texture::MinFilter::LinearMipmapNearest => crate::MinFilter::LinearMipmapNearest,
gltf::texture::MinFilter::NearestMipmapLinear => crate::MinFilter::NearestMipmapLinear,
gltf::texture::MinFilter::LinearMipmapLinear => crate::MinFilter::LinearMipmapLinear,
});
let wrap_s = convert_wrapping_mode(sampler.wrap_s());
let wrap_t = convert_wrapping_mode(sampler.wrap_t());
crate::Sampler {
mag_filter,
min_filter,
wrap_s,
wrap_t,
name: sampler.name().map(std::string::ToString::to_string),
}
}
#[must_use]
const fn convert_alpha_mode(mode: gltf::material::AlphaMode) -> crate::AlphaMode {
match mode {
gltf::material::AlphaMode::Opaque => crate::AlphaMode::Opaque,
gltf::material::AlphaMode::Mask => crate::AlphaMode::Mask,
gltf::material::AlphaMode::Blend => crate::AlphaMode::Blend,
}
}
#[must_use]
const fn convert_wrapping_mode(mode: gltf::texture::WrappingMode) -> crate::WrappingMode {
match mode {
gltf::texture::WrappingMode::ClampToEdge => crate::WrappingMode::ClampToEdge,
gltf::texture::WrappingMode::MirroredRepeat => crate::WrappingMode::MirroredRepeat,
gltf::texture::WrappingMode::Repeat => crate::WrappingMode::Repeat,
}
}
#[must_use]
const fn convert_mode(mode: gltf::mesh::Mode) -> crate::RenderMode {
match mode {
gltf::mesh::Mode::Points => crate::RenderMode::Points,
gltf::mesh::Mode::Lines => crate::RenderMode::Lines,
gltf::mesh::Mode::LineLoop => crate::RenderMode::LineLoop,
gltf::mesh::Mode::LineStrip => crate::RenderMode::LineStrip,
gltf::mesh::Mode::Triangles => crate::RenderMode::Triangles,
gltf::mesh::Mode::TriangleStrip => crate::RenderMode::TriangleStrip,
gltf::mesh::Mode::TriangleFan => crate::RenderMode::TriangleFan,
}
}
fn load_mesh(mesh: &Mesh, buffer_data: &[gltf::buffer::Data]) -> Vec<crate::Mesh> {
let mut meshes = Vec::new();
for (i, primitive) in mesh.primitives().enumerate() {
log::debug!(
" Loading Mesh Primtive {}/{}",
i + 1,
mesh.primitives().len()
);
let (vertices, indices) = load_primitive(buffer_data, &primitive);
meshes.push(crate::Mesh {
vertices,
indices,
mode: convert_mode(primitive.mode()),
material_index: primitive.material().index(),
name: mesh.name().map(std::string::ToString::to_string),
});
}
meshes
}
fn load_primitive<'a>(
buffer_data: &'a [gltf::buffer::Data],
primitive: &gltf::Primitive<'a>,
) -> (Vec<Vertex>, Option<Indices>) {
let reader = primitive.reader(|buffer| Some(&buffer_data[buffer.index()]));
let mut vertices: Vec<Vertex> = reader
.read_positions()
.unwrap()
.map(|position| Vertex {
position,
color: None,
tex_coord: None,
normal: None,
})
.collect();
if let Some(normal_attribute) = reader.read_normals() {
for (normal_index, normal) in normal_attribute.enumerate() {
vertices[normal_index].normal = Some(normal);
}
}
if let Some(color_attribute) = reader
.read_colors(0)
.map(gltf::mesh::util::ReadColors::into_rgba_f32)
{
for (color_index, color) in color_attribute.enumerate() {
vertices[color_index].color = Some(color);
}
}
if let Some(tex_coord_attribute) = reader
.read_tex_coords(0)
.map(gltf::mesh::util::ReadTexCoords::into_f32)
{
for (tex_coord_index, tex_coord) in tex_coord_attribute.enumerate() {
vertices[tex_coord_index].tex_coord = Some(tex_coord);
}
}
let indices = reader.read_indices().map(|indcies| match indcies {
gltf::mesh::util::ReadIndices::U8(indices) => Indices::U8(indices.collect::<Vec<_>>()),
gltf::mesh::util::ReadIndices::U16(indices) => Indices::U16(indices.collect::<Vec<_>>()),
gltf::mesh::util::ReadIndices::U32(indices) => Indices::U32(indices.collect::<Vec<_>>()),
});
(vertices, indices)
}