use crate::model::{DisplacementMesh, Geometry, Model, ObjectType, ResourceId, Unit};
use crate::validation::{ValidationLevel, ValidationReport};
pub fn validate_displacement(model: &Model, level: ValidationLevel, report: &mut ValidationReport) {
validate_displacement_resources(model, level, report);
for object in model.resources.iter_objects() {
if let Geometry::DisplacementMesh(dmesh) = &object.geometry {
validate_displacement_mesh(dmesh, object.id, level, report, &model.resources);
}
}
}
pub fn validate_displacement_mesh_geometry(
mesh: &DisplacementMesh,
oid: ResourceId,
_object_type: ObjectType,
level: ValidationLevel,
report: &mut ValidationReport,
_unit: Unit,
) {
if mesh.vertices.is_empty() {
report.add_error(
5010,
format!("DisplacementMesh object {} has no vertices", oid.0),
);
}
if mesh.triangles.is_empty() {
report.add_error(
5011,
format!("DisplacementMesh object {} has no triangles", oid.0),
);
}
if mesh.normals.len() != mesh.vertices.len() {
report.add_error(
5012,
format!(
"Object {} has {} vertices but {} normals",
oid.0,
mesh.vertices.len(),
mesh.normals.len()
),
);
}
let vertex_count = mesh.vertices.len();
for (i, tri) in mesh.triangles.iter().enumerate() {
if tri.v1 as usize >= vertex_count
|| tri.v2 as usize >= vertex_count
|| tri.v3 as usize >= vertex_count
{
report.add_error(
5013,
format!(
"Triangle {} in object {} has out-of-bounds vertex index",
i, oid.0
),
);
}
}
if let Some(gradients) = &mesh.gradients
&& gradients.len() != mesh.vertices.len()
{
report.add_error(
5015,
format!(
"Object {} has {} vertices but {} gradient vectors",
oid.0,
mesh.vertices.len(),
gradients.len()
),
);
}
if level >= ValidationLevel::Paranoid {
for (i, normal) in mesh.normals.iter().enumerate() {
if !normal.nx.is_finite() || !normal.ny.is_finite() || !normal.nz.is_finite() {
report.add_error(
5020,
format!(
"Normal {} in object {} contains non-finite values",
i, oid.0
),
);
}
let length_sq = normal.nx * normal.nx + normal.ny * normal.ny + normal.nz * normal.nz;
if (length_sq - 1.0).abs() > 1e-4 {
report.add_warning(
5021,
format!(
"Normal {} in object {} is not unit length (length^2 = {})",
i, oid.0, length_sq
),
);
}
}
if let Some(gradients) = &mesh.gradients {
for (i, grad) in gradients.iter().enumerate() {
if !grad.gu.is_finite() || !grad.gv.is_finite() {
report.add_error(
5022,
format!(
"Gradient {} in object {} contains non-finite values",
i, oid.0
),
);
}
}
}
}
}
fn validate_displacement_resources(
model: &Model,
level: ValidationLevel,
report: &mut ValidationReport,
) {
for res in model.resources.iter_displacement_2d() {
if level >= ValidationLevel::Standard {
if res.path.is_empty() {
report.add_error(
5001,
format!("Displacement2D resource {} has empty path", res.id.0),
);
}
if !res.path.is_empty() && !model.attachments.contains_key(&res.path) {
report.add_warning(
5002,
format!(
"Displacement2D resource {} references non-existent attachment '{}'",
res.id.0, res.path
),
);
}
}
if level >= ValidationLevel::Paranoid {
if !res.height.is_finite() {
report.add_error(
5003,
format!(
"Displacement2D resource {} has non-finite height: {}",
res.id.0, res.height
),
);
}
if !res.offset.is_finite() {
report.add_error(
5004,
format!(
"Displacement2D resource {} has non-finite offset: {}",
res.id.0, res.offset
),
);
}
let _ = model; }
}
}
fn validate_displacement_mesh(
mesh: &DisplacementMesh,
oid: ResourceId,
level: ValidationLevel,
report: &mut ValidationReport,
resources: &crate::model::ResourceCollection,
) {
if mesh.vertices.is_empty() {
report.add_error(
5010,
format!("DisplacementMesh object {} has no vertices", oid.0),
);
}
if mesh.triangles.is_empty() {
report.add_error(
5011,
format!("DisplacementMesh object {} has no triangles", oid.0),
);
}
if mesh.normals.len() != mesh.vertices.len() {
report.add_error(
5012,
format!(
"Object {} has {} vertices but {} normals",
oid.0,
mesh.vertices.len(),
mesh.normals.len()
),
);
}
let vertex_count = mesh.vertices.len();
for (i, tri) in mesh.triangles.iter().enumerate() {
if tri.v1 as usize >= vertex_count
|| tri.v2 as usize >= vertex_count
|| tri.v3 as usize >= vertex_count
{
report.add_error(
5013,
format!(
"Triangle {} in object {} has out-of-bounds vertex index",
i, oid.0
),
);
}
}
if level >= ValidationLevel::Standard {
for (i, tri) in mesh.triangles.iter().enumerate() {
if let Some(d1) = tri.d1 {
validate_displacement_index(oid, i, d1, resources, report);
}
if let Some(d2) = tri.d2 {
validate_displacement_index(oid, i, d2, resources, report);
}
if let Some(d3) = tri.d3 {
validate_displacement_index(oid, i, d3, resources, report);
}
}
if let Some(gradients) = &mesh.gradients
&& gradients.len() != mesh.vertices.len()
{
report.add_error(
5015,
format!(
"Object {} has {} vertices but {} gradient vectors",
oid.0,
mesh.vertices.len(),
gradients.len()
),
);
}
}
if level >= ValidationLevel::Paranoid {
for (i, normal) in mesh.normals.iter().enumerate() {
if !normal.nx.is_finite() || !normal.ny.is_finite() || !normal.nz.is_finite() {
report.add_error(
5020,
format!(
"Normal {} in object {} contains non-finite values",
i, oid.0
),
);
continue;
}
let length_sq = normal.nx * normal.nx + normal.ny * normal.ny + normal.nz * normal.nz;
let length = length_sq.sqrt();
let tolerance = 1e-4;
if (length - 1.0).abs() > tolerance {
report.add_warning(
5021,
format!(
"Normal {} in object {} is not unit length (length: {:.6})",
i, oid.0, length
),
);
}
}
if let Some(gradients) = &mesh.gradients {
for (i, gradient) in gradients.iter().enumerate() {
if !gradient.gu.is_finite() || !gradient.gv.is_finite() {
report.add_error(
5022,
format!(
"Gradient {} in object {} contains non-finite values",
i, oid.0
),
);
}
}
for (i, (normal, gradient)) in mesh.normals.iter().zip(gradients.iter()).enumerate() {
if normal.nx.is_finite()
&& normal.ny.is_finite()
&& normal.nz.is_finite()
&& gradient.gu.is_finite()
&& gradient.gv.is_finite()
{
if i == 0 {
report.add_info(
5023,
format!(
"Object {} has gradient vectors (orthogonality not verified)",
oid.0
),
);
break; }
}
}
}
}
}
fn validate_displacement_index(
oid: ResourceId,
tri_idx: usize,
d_index: u32,
resources: &crate::model::ResourceCollection,
report: &mut ValidationReport,
) {
let rid = ResourceId(d_index);
if resources.get_displacement_2d(rid).is_none() {
report.add_error(
5014,
format!(
"Triangle {} in object {} references non-existent displacement texture {}",
tri_idx, oid.0, d_index
),
);
}
}