1use crate::model::{DisplacementMesh, Geometry, Model, ObjectType, ResourceId, Unit};
2use crate::validation::{ValidationLevel, ValidationReport};
3
4pub fn validate_displacement(model: &Model, level: ValidationLevel, report: &mut ValidationReport) {
14 validate_displacement_resources(model, level, report);
16
17 for object in model.resources.iter_objects() {
19 if let Geometry::DisplacementMesh(dmesh) = &object.geometry {
20 validate_displacement_mesh(dmesh, object.id, level, report, &model.resources);
21 }
22 }
23}
24
25pub fn validate_displacement_mesh_geometry(
27 mesh: &DisplacementMesh,
28 oid: ResourceId,
29 _object_type: ObjectType,
30 level: ValidationLevel,
31 report: &mut ValidationReport,
32 _unit: Unit,
33) {
34 if mesh.vertices.is_empty() {
40 report.add_error(
41 5010,
42 format!("DisplacementMesh object {} has no vertices", oid.0),
43 );
44 }
45
46 if mesh.triangles.is_empty() {
47 report.add_error(
48 5011,
49 format!("DisplacementMesh object {} has no triangles", oid.0),
50 );
51 }
52
53 if mesh.normals.len() != mesh.vertices.len() {
55 report.add_error(
56 5012,
57 format!(
58 "Object {} has {} vertices but {} normals",
59 oid.0,
60 mesh.vertices.len(),
61 mesh.normals.len()
62 ),
63 );
64 }
65
66 let vertex_count = mesh.vertices.len();
68 for (i, tri) in mesh.triangles.iter().enumerate() {
69 if tri.v1 as usize >= vertex_count
70 || tri.v2 as usize >= vertex_count
71 || tri.v3 as usize >= vertex_count
72 {
73 report.add_error(
74 5013,
75 format!(
76 "Triangle {} in object {} has out-of-bounds vertex index",
77 i, oid.0
78 ),
79 );
80 }
81 }
82
83 if let Some(gradients) = &mesh.gradients
85 && gradients.len() != mesh.vertices.len()
86 {
87 report.add_error(
88 5015,
89 format!(
90 "Object {} has {} vertices but {} gradient vectors",
91 oid.0,
92 mesh.vertices.len(),
93 gradients.len()
94 ),
95 );
96 }
97
98 if level >= ValidationLevel::Paranoid {
100 for (i, normal) in mesh.normals.iter().enumerate() {
101 if !normal.nx.is_finite() || !normal.ny.is_finite() || !normal.nz.is_finite() {
102 report.add_error(
103 5020,
104 format!(
105 "Normal {} in object {} contains non-finite values",
106 i, oid.0
107 ),
108 );
109 }
110
111 let length_sq = normal.nx * normal.nx + normal.ny * normal.ny + normal.nz * normal.nz;
113 if (length_sq - 1.0).abs() > 1e-4 {
114 report.add_warning(
115 5021,
116 format!(
117 "Normal {} in object {} is not unit length (length^2 = {})",
118 i, oid.0, length_sq
119 ),
120 );
121 }
122 }
123
124 if let Some(gradients) = &mesh.gradients {
126 for (i, grad) in gradients.iter().enumerate() {
127 if !grad.gu.is_finite() || !grad.gv.is_finite() {
128 report.add_error(
129 5022,
130 format!(
131 "Gradient {} in object {} contains non-finite values",
132 i, oid.0
133 ),
134 );
135 }
136 }
137 }
138 }
139}
140
141fn validate_displacement_resources(
143 model: &Model,
144 level: ValidationLevel,
145 report: &mut ValidationReport,
146) {
147 for res in model.resources.iter_displacement_2d() {
148 if level >= ValidationLevel::Standard {
150 if res.path.is_empty() {
151 report.add_error(
152 5001,
153 format!("Displacement2D resource {} has empty path", res.id.0),
154 );
155 }
156
157 if !res.path.is_empty() && !model.attachments.contains_key(&res.path) {
159 report.add_warning(
160 5002,
161 format!(
162 "Displacement2D resource {} references non-existent attachment '{}'",
163 res.id.0, res.path
164 ),
165 );
166 }
167 }
168
169 if level >= ValidationLevel::Paranoid {
171 if !res.height.is_finite() {
172 report.add_error(
173 5003,
174 format!(
175 "Displacement2D resource {} has non-finite height: {}",
176 res.id.0, res.height
177 ),
178 );
179 }
180
181 if !res.offset.is_finite() {
182 report.add_error(
183 5004,
184 format!(
185 "Displacement2D resource {} has non-finite offset: {}",
186 res.id.0, res.offset
187 ),
188 );
189 }
190
191 let _ = model; }
195 }
196}
197
198fn validate_displacement_mesh(
203 mesh: &DisplacementMesh,
204 oid: ResourceId,
205 level: ValidationLevel,
206 report: &mut ValidationReport,
207 resources: &crate::model::ResourceCollection,
208) {
209 if mesh.vertices.is_empty() {
211 report.add_error(
212 5010,
213 format!("DisplacementMesh object {} has no vertices", oid.0),
214 );
215 }
216
217 if mesh.triangles.is_empty() {
218 report.add_error(
219 5011,
220 format!("DisplacementMesh object {} has no triangles", oid.0),
221 );
222 }
223
224 if mesh.normals.len() != mesh.vertices.len() {
226 report.add_error(
227 5012,
228 format!(
229 "Object {} has {} vertices but {} normals",
230 oid.0,
231 mesh.vertices.len(),
232 mesh.normals.len()
233 ),
234 );
235 }
236
237 let vertex_count = mesh.vertices.len();
239 for (i, tri) in mesh.triangles.iter().enumerate() {
240 if tri.v1 as usize >= vertex_count
241 || tri.v2 as usize >= vertex_count
242 || tri.v3 as usize >= vertex_count
243 {
244 report.add_error(
245 5013,
246 format!(
247 "Triangle {} in object {} has out-of-bounds vertex index",
248 i, oid.0
249 ),
250 );
251 }
252 }
253
254 if level >= ValidationLevel::Standard {
256 for (i, tri) in mesh.triangles.iter().enumerate() {
258 if let Some(d1) = tri.d1 {
259 validate_displacement_index(oid, i, d1, resources, report);
260 }
261 if let Some(d2) = tri.d2 {
262 validate_displacement_index(oid, i, d2, resources, report);
263 }
264 if let Some(d3) = tri.d3 {
265 validate_displacement_index(oid, i, d3, resources, report);
266 }
267 }
268
269 if let Some(gradients) = &mesh.gradients
271 && gradients.len() != mesh.vertices.len()
272 {
273 report.add_error(
274 5015,
275 format!(
276 "Object {} has {} vertices but {} gradient vectors",
277 oid.0,
278 mesh.vertices.len(),
279 gradients.len()
280 ),
281 );
282 }
283 }
284
285 if level >= ValidationLevel::Paranoid {
287 for (i, normal) in mesh.normals.iter().enumerate() {
289 if !normal.nx.is_finite() || !normal.ny.is_finite() || !normal.nz.is_finite() {
291 report.add_error(
292 5020,
293 format!(
294 "Normal {} in object {} contains non-finite values",
295 i, oid.0
296 ),
297 );
298 continue;
299 }
300
301 let length_sq = normal.nx * normal.nx + normal.ny * normal.ny + normal.nz * normal.nz;
303 let length = length_sq.sqrt();
304 let tolerance = 1e-4;
305 if (length - 1.0).abs() > tolerance {
306 report.add_warning(
307 5021,
308 format!(
309 "Normal {} in object {} is not unit length (length: {:.6})",
310 i, oid.0, length
311 ),
312 );
313 }
314 }
315
316 if let Some(gradients) = &mesh.gradients {
318 for (i, gradient) in gradients.iter().enumerate() {
319 if !gradient.gu.is_finite() || !gradient.gv.is_finite() {
320 report.add_error(
321 5022,
322 format!(
323 "Gradient {} in object {} contains non-finite values",
324 i, oid.0
325 ),
326 );
327 }
328 }
329
330 for (i, (normal, gradient)) in mesh.normals.iter().zip(gradients.iter()).enumerate() {
334 if normal.nx.is_finite()
335 && normal.ny.is_finite()
336 && normal.nz.is_finite()
337 && gradient.gu.is_finite()
338 && gradient.gv.is_finite()
339 {
340 if i == 0 {
345 report.add_info(
346 5023,
347 format!(
348 "Object {} has gradient vectors (orthogonality not verified)",
349 oid.0
350 ),
351 );
352 break; }
354 }
355 }
356 }
357 }
358}
359
360fn validate_displacement_index(
362 oid: ResourceId,
363 tri_idx: usize,
364 d_index: u32,
365 resources: &crate::model::ResourceCollection,
366 report: &mut ValidationReport,
367) {
368 let rid = ResourceId(d_index);
371 if resources.get_displacement_2d(rid).is_none() {
372 report.add_error(
373 5014,
374 format!(
375 "Triangle {} in object {} references non-existent displacement texture {}",
376 tri_idx, oid.0, d_index
377 ),
378 );
379 }
380}