use crate::error::{DagError, DagResult};
use crate::types::{
IndexedGeometry, MESHLET_MAX_TRIANGLES_LIMIT, MESHLET_MAX_VERTICES_LIMIT, SOURCE_TRIANGLES_BUDGET,
SOURCE_VERTICES_BUDGET,
};
#[derive(Debug, Clone)]
pub struct ValidatedInput {
pub geometry: IndexedGeometry,
pub max_vertices: u32,
pub max_triangles: u32,
}
pub fn validate_input(
geometry: &IndexedGeometry,
max_vertices: Option<u32>,
max_triangles: Option<u32>,
) -> DagResult<ValidatedInput> {
if !geometry.positions.len().is_multiple_of(3) {
return Err(DagError::invalid_input(
"positions must contain tightly packed XYZ triples.",
));
}
if !geometry.indices.len().is_multiple_of(3) {
return Err(DagError::invalid_input(
"indices must contain complete triangles.",
));
}
let vertex_count = geometry.vertex_count();
let triangle_count = geometry.triangle_count();
budget(vertex_count as u64, SOURCE_VERTICES_BUDGET, "source vertices")?;
budget(triangle_count as u64, SOURCE_TRIANGLES_BUDGET, "source triangles")?;
if geometry.positions.iter().any(|v| !v.is_finite()) {
return Err(DagError::invalid_input("Position components must be finite."));
}
for (offset, &index) in geometry.indices.iter().enumerate() {
if index as usize >= vertex_count {
return Err(DagError::invalid_input(format!(
"Index {offset} ({index}) is outside the source vertex range ({vertex_count})."
)));
}
}
let max_vertices = meshlet_limit(max_vertices, MESHLET_MAX_VERTICES_LIMIT, "maxVertices")?;
let max_triangles = meshlet_limit(max_triangles, MESHLET_MAX_TRIANGLES_LIMIT, "maxTriangles")?;
Ok(ValidatedInput { geometry: geometry.clone(), max_vertices, max_triangles })
}
pub(crate) fn budget(value: u64, maximum: u64, label: &str) -> DagResult<()> {
if value > maximum {
return Err(DagError::budget_exceeded(label, maximum));
}
Ok(())
}
fn meshlet_limit(value: Option<u32>, maximum: u32, label: &str) -> DagResult<u32> {
let resolved = value.unwrap_or(maximum);
if resolved < 1 || resolved > maximum {
return Err(DagError::invalid_input(format!(
"{label} must be an integer in 1..{maximum}."
)));
}
Ok(resolved)
}
#[cfg(test)]
mod tests {
use super::*;
fn geometry(vertex_count: usize, triangle_count: usize) -> IndexedGeometry {
IndexedGeometry {
positions: vec![0.0; vertex_count * 3],
indices: vec![0; triangle_count * 3],
}
}
#[test]
fn accepts_valid_geometry() {
let mut g = geometry(4, 2);
g.indices.copy_from_slice(&[0, 1, 2, 1, 2, 3]);
assert!(validate_input(&g, None, None).is_ok());
}
#[test]
fn rejects_misaligned_arrays() {
let mut g = geometry(2, 1);
g.positions.truncate(5);
let err = validate_input(&g, None, None).unwrap_err();
assert!(err.to_string().contains("tightly packed"), "{err}");
let mut g = geometry(3, 2);
g.indices.truncate(5);
let err = validate_input(&g, None, None).unwrap_err();
assert!(err.to_string().contains("complete triangles"), "{err}");
}
#[test]
fn rejects_out_of_range_index() {
let mut g = geometry(2, 1);
g.indices.copy_from_slice(&[0, 1, 2]);
let err = validate_input(&g, None, None).unwrap_err();
assert!(err.to_string().contains("outside the source vertex range"));
}
#[test]
fn rejects_non_finite_positions() {
let mut g = geometry(1, 0);
g.positions[1] = f32::NAN;
assert!(validate_input(&g, None, None).is_err());
g.positions[1] = f32::INFINITY;
assert!(validate_input(&g, None, None).is_err());
}
#[test]
fn rejects_bad_meshlet_limits() {
let g = geometry(3, 1);
assert!(validate_input(&g, Some(0), None).is_err());
assert!(validate_input(&g, Some(65), None).is_err());
assert!(validate_input(&g, None, Some(127)).is_err());
assert!(validate_input(&g, Some(64), Some(126)).is_ok());
}
#[test]
fn rejects_empty_meshlet_limit() {
let g = geometry(0, 0);
assert!(validate_input(&g, Some(0), Some(0)).is_err());
}
}