use super::math::Vector3;
use anyhow::{Context, Result};
#[derive(Debug, Clone)]
pub struct MeshData {
pub positions: Vec<Vector3>,
pub normals: Option<Vec<Vector3>>,
pub indices: Vec<u32>,
}
impl MeshData {
pub fn triangle_count(&self) -> usize {
self.indices.len() / 3
}
pub fn vertex_count(&self) -> usize {
self.positions.len()
}
pub fn has_normals(&self) -> bool {
self.normals.is_some()
}
pub fn bounds(&self) -> (Vector3, Vector3) {
if self.positions.is_empty() {
return (Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0));
}
let mut min = self.positions[0];
let mut max = self.positions[0];
for pos in &self.positions {
min.x = min.x.min(pos.x);
min.y = min.y.min(pos.y);
min.z = min.z.min(pos.z);
max.x = max.x.max(pos.x);
max.y = max.y.max(pos.y);
max.z = max.z.max(pos.z);
}
(min, max)
}
pub fn center(&self) -> Vector3 {
let (min, max) = self.bounds();
Vector3::new(
(min.x + max.x) * 0.5,
(min.y + max.y) * 0.5,
(min.z + max.z) * 0.5,
)
}
pub fn size(&self) -> Vector3 {
let (min, max) = self.bounds();
Vector3::new(max.x - min.x, max.y - min.y, max.z - min.z)
}
pub fn max_dimension(&self) -> f32 {
let size = self.size();
size.x.max(size.y).max(size.z)
}
#[must_use]
pub fn normalize(&self) -> Self {
let center = self.center();
let max_dim = self.max_dimension();
if max_dim == 0.0 {
return self.clone();
}
let scale = 2.0 / max_dim;
let positions: Vec<Vector3> = self
.positions
.iter()
.map(|p| {
Vector3::new(
(p.x - center.x) * scale,
(p.y - center.y) * scale,
(p.z - center.z) * scale,
)
})
.collect();
MeshData {
positions,
normals: self.normals.clone(),
indices: self.indices.clone(),
}
}
}
pub fn load_gltf(path: &str) -> Result<MeshData> {
let (document, buffers, _images) =
gltf::import(path).with_context(|| format!("Failed to load glTF file: {}", path))?;
let mesh = document
.meshes()
.next()
.context("No meshes found in glTF file")?;
let primitive = mesh
.primitives()
.next()
.context("No primitives found in mesh")?;
let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
let positions: Vec<Vector3> = reader
.read_positions()
.context("No position data in primitive - positions are required")?
.map(|p| Vector3::new(p[0], p[1], p[2]))
.collect();
if positions.is_empty() {
anyhow::bail!("Mesh has no vertices");
}
let normals: Option<Vec<Vector3>> = reader
.read_normals()
.map(|iter| iter.map(|n| Vector3::new(n[0], n[1], n[2])).collect());
if let Some(ref normals_vec) = normals {
if normals_vec.len() != positions.len() {
anyhow::bail!(
"Normal count ({}) doesn't match position count ({})",
normals_vec.len(),
positions.len()
);
}
}
let indices: Vec<u32> = reader
.read_indices()
.context("No index data in primitive - indexed geometry required")?
.into_u32()
.collect();
if indices.is_empty() {
anyhow::bail!("Mesh has no indices");
}
if indices.len() % 3 != 0 {
anyhow::bail!(
"Index count ({}) is not a multiple of 3 - invalid triangle data",
indices.len()
);
}
let max_index = indices.iter().copied().max().unwrap_or(0);
if max_index >= positions.len() as u32 {
anyhow::bail!(
"Index out of bounds: {} >= {} vertices",
max_index,
positions.len()
);
}
Ok(MeshData {
positions,
normals,
indices,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mesh_data_triangle_count() {
let data = MeshData {
positions: vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
],
normals: None,
indices: vec![0, 1, 2],
};
assert_eq!(data.triangle_count(), 1);
assert_eq!(data.vertex_count(), 3);
assert!(!data.has_normals());
}
#[test]
fn test_mesh_data_with_normals() {
let data = MeshData {
positions: vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
],
normals: Some(vec![
Vector3::new(0.0, 0.0, 1.0),
Vector3::new(0.0, 0.0, 1.0),
Vector3::new(0.0, 0.0, 1.0),
]),
indices: vec![0, 1, 2],
};
assert!(data.has_normals());
}
#[test]
fn test_mesh_data_multiple_triangles() {
let data = MeshData {
positions: vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
Vector3::new(1.0, 1.0, 0.0),
],
normals: None,
indices: vec![0, 1, 2, 1, 3, 2], };
assert_eq!(data.triangle_count(), 2);
assert_eq!(data.vertex_count(), 4);
}
}