use axiolid_core::{Aabb, Point3, Vec3};
use crate::attribute::AttributeChannel;
use crate::MeshValidationError;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct NormalAttribute {
pub values: Vec<Vec3>,
pub indices: Option<Vec<u32>>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct TriMesh {
pub positions: Vec<Point3>,
pub indices: Vec<u32>,
pub normals: Option<NormalAttribute>,
pub attributes: Vec<AttributeChannel>,
}
impl TriMesh {
pub fn new(positions: Vec<Point3>, indices: Vec<u32>) -> Self {
Self {
positions,
indices,
normals: None,
attributes: Vec::new(),
}
}
pub fn triangle_count(&self) -> usize {
self.indices.len() / 3
}
pub fn bounds(&self) -> Aabb {
let mut bounds = Aabb::default();
for &position in &self.positions {
bounds.extend(position);
}
bounds
}
pub fn validate_structure(&self) -> Result<(), MeshValidationError> {
if self.indices.len() % 3 != 0 {
return Err(MeshValidationError::IncompleteTriangle {
index_count: self.indices.len(),
});
}
if let Some(&index) = self
.indices
.iter()
.find(|&&index| index as usize >= self.positions.len())
{
return Err(MeshValidationError::PositionIndexOutOfRange {
index,
position_count: self.positions.len(),
});
}
if let Some(normals) = &self.normals {
if let Some(indices) = &normals.indices {
if indices.len() != self.indices.len() {
return Err(MeshValidationError::NormalIndexCount {
expected: self.indices.len(),
actual: indices.len(),
});
}
if let Some(&index) = indices
.iter()
.find(|&&index| index as usize >= normals.values.len())
{
return Err(MeshValidationError::NormalIndexOutOfRange {
index,
normal_count: normals.values.len(),
});
}
} else if normals.values.len() != self.positions.len() {
return Err(MeshValidationError::NormalCount {
expected: self.positions.len(),
actual: normals.values.len(),
});
}
}
for (position, channel) in self.attributes.iter().enumerate() {
if channel.width == 0 {
return Err(MeshValidationError::AttributeZeroWidth {
name: channel.name.clone(),
});
}
if self.attributes[..position]
.iter()
.any(|earlier| earlier.name == channel.name)
{
return Err(MeshValidationError::AttributeDuplicateName {
name: channel.name.clone(),
});
}
if let Some(corners) = &channel.corner_indices {
validate_corner_channel(channel, corners, self.indices.len())?;
} else if channel.vertex_count() != self.positions.len()
|| channel.values.len() % channel.width != 0
{
return Err(MeshValidationError::AttributeCount {
name: channel.name.clone(),
expected: self.positions.len(),
actual: channel.vertex_count(),
});
}
}
Ok(())
}
pub fn is_structurally_valid(&self) -> bool {
self.validate_structure().is_ok()
}
pub fn triangles(&self) -> impl ExactSizeIterator<Item = [u32; 3]> + '_ {
self.indices
.chunks_exact(3)
.map(|triangle| [triangle[0], triangle[1], triangle[2]])
}
}
fn validate_corner_channel(
channel: &AttributeChannel,
corners: &[u32],
corner_count: usize,
) -> Result<(), MeshValidationError> {
if channel.values.len() % channel.width != 0 {
return Err(MeshValidationError::AttributeRaggedValues {
name: channel.name.clone(),
values: channel.values.len(),
width: channel.width,
});
}
if corners.len() != corner_count {
return Err(MeshValidationError::AttributeCornerCount {
name: channel.name.clone(),
expected: corner_count,
actual: corners.len(),
});
}
let value_count = channel.value_count();
if let Some(&index) = corners
.iter()
.find(|&&index| index != AttributeChannel::UNMAPPED && index as usize >= value_count)
{
return Err(MeshValidationError::AttributeCornerIndexOutOfRange {
name: channel.name.clone(),
index,
value_count,
});
}
for (triangle, entries) in corners.chunks_exact(3).enumerate() {
let unmapped = entries
.iter()
.filter(|&&index| index == AttributeChannel::UNMAPPED)
.count();
if unmapped != 0 && unmapped != 3 {
return Err(MeshValidationError::AttributePartiallyMapped {
name: channel.name.clone(),
triangle,
});
}
}
Ok(())
}