use bytemuck::{Pod, Zeroable};
use glam::{Mat4, Vec3};
use crate::ui::Color;
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct Vertex {
pub position: [f32; 3],
pub normal: [f32; 3],
}
pub struct MeshData {
pub name: String,
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
pub base_color: Option<Color>,
pub min: Vec3,
pub max: Vec3,
}
#[derive(Debug)]
pub enum MeshError {
Parse(gltf::Error),
MissingBinaryChunk,
MissingPositions(String),
}
impl std::fmt::Display for MeshError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Parse(e) => write!(f, "could not parse glTF: {e}"),
Self::MissingBinaryChunk => write!(f, "glb has no binary chunk"),
Self::MissingPositions(name) => write!(f, "mesh {name:?} has no positions"),
}
}
}
impl std::error::Error for MeshError {}
pub fn load_glb(bytes: &[u8]) -> Result<Vec<MeshData>, MeshError> {
let gltf = gltf::Gltf::from_slice(bytes).map_err(MeshError::Parse)?;
let blob = gltf.blob.as_deref().ok_or(MeshError::MissingBinaryChunk)?;
let mut meshes = Vec::new();
for node in gltf.nodes() {
let (Some(name), Some(mesh)) = (node.name(), node.mesh()) else {
continue;
};
let transform = Mat4::from_cols_array_2d(&node.transform().matrix());
meshes.push(load_node(name, &mesh, transform, blob)?);
}
Ok(meshes)
}
fn load_node(
name: &str,
mesh: &gltf::Mesh<'_>,
transform: Mat4,
blob: &[u8],
) -> Result<MeshData, MeshError> {
let normal_transform = Mat4::from_mat3(glam::Mat3::from_mat4(transform).inverse().transpose());
let mut vertices: Vec<Vertex> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let mut base_color = None;
for primitive in mesh.primitives() {
let reader = primitive.reader(|_| Some(blob));
let positions = reader
.read_positions()
.ok_or_else(|| MeshError::MissingPositions(name.to_string()))?;
let base_vertex = vertices.len() as u32;
let normals: Option<Vec<[f32; 3]>> = reader.read_normals().map(|n| n.collect());
for (i, position) in positions.enumerate() {
let position = transform.transform_point3(Vec3::from(position));
let normal = normals
.as_ref()
.and_then(|normals| normals.get(i).copied())
.unwrap_or([0.0, 1.0, 0.0]);
let normal = normal_transform
.transform_vector3(Vec3::from(normal))
.normalize_or_zero();
vertices.push(Vertex {
position: position.to_array(),
normal: normal.to_array(),
});
}
match reader.read_indices() {
Some(read) => indices.extend(read.into_u32().map(|i| i + base_vertex)),
None => indices.extend(base_vertex..vertices.len() as u32),
}
if base_color.is_none() {
let pbr = primitive.material().pbr_metallic_roughness();
if primitive.material().index().is_some() {
let [r, g, b, a] = pbr.base_color_factor();
base_color = Some(Color::linear_rgba(r, g, b, a));
}
}
}
let mut min = Vec3::splat(f32::MAX);
let mut max = Vec3::splat(f32::MIN);
for vertex in &vertices {
min = min.min(Vec3::from(vertex.position));
max = max.max(Vec3::from(vertex.position));
}
Ok(MeshData {
name: name.to_string(),
vertices,
indices,
base_color,
min,
max,
})
}