ifc_lite_processing/appearance/
captured.rs1use super::{captured_types::*,annotation_types::*,annotation::plan_textured_product};
5const MAX_ROWS:usize=200_000;
6
7fn validate(mesh:&CapturedMesh)->Result<(),String> {
8 if [mesh.positions.len(),mesh.triangles.len(),mesh.uvs.len()].iter().any(|n|*n==0 || *n>MAX_ROWS)
9 || mesh.uv_triangles.len()!=mesh.triangles.len() {
10 return Err("Captured mesh needs 1..200000 position, triangle and UV rows, with one UV triangle per face".into());
11 }
12 if mesh.positions.iter().flatten().any(|v|!v.is_finite() || v.abs()>1e9)
13 || mesh.uvs.iter().flatten().any(|v|!v.is_finite() || !(0. ..=1.).contains(v)) {
14 return Err("Captured coordinates must be finite world metres within +/-1e9 and UVs within the non-repeating image".into());
15 }
16 for (face,uv) in mesh.triangles.iter().zip(&mesh.uv_triangles) {
17 if face.iter().any(|i|*i as usize>=mesh.positions.len()) || uv.iter().any(|i|*i as usize>=mesh.uvs.len()) {
18 return Err("Captured geometry or UV triangle index is out of bounds".into());
19 }
20 let [a,b,c]=face.map(|i|mesh.positions[i as usize]);
21 let ab:[f64;3]=std::array::from_fn(|i|b[i]-a[i]);
22 let ac:[f64;3]=std::array::from_fn(|i|c[i]-a[i]);
23 let cross=[ab[1]*ac[2]-ab[2]*ac[1],ab[2]*ac[0]-ab[0]*ac[2],ab[0]*ac[1]-ab[1]*ac[0]];
24 if cross.iter().map(|v|v*v).sum::<f64>()<=1e-24 {
25 return Err("Captured mesh contains a degenerate triangle".into());
26 }
27 }
28 Ok(())
29}
30pub fn plan_captured_mesh(bytes:&[u8],request:&CapturedMeshRequest)->Result<CapturedMeshPlan,String> {
33 validate(&request.mesh)?;
34 if request.name.trim().is_empty() { return Err("Captured object requires a Name".into()); }
35 let r=AnnotationPlaneRequest {schema:request.schema.clone(),source_revision:request.source_revision.clone(),
36 next_express_id:request.next_express_id,container_id:request.container_id,global_id:request.global_id.clone(),
37 containment_global_id:request.containment_global_id.clone(),name:request.name.clone(),image_uri:request.image_uri.clone(),
38 frame:AnnotationPlaneFrame {origin:request.mesh.positions[0],axis_u:[1.,0.,0.],axis_v:[0.,1.,0.],size_metres:[1.,1.]} };
39 let result=plan_textured_product(bytes,&r,Some(&request.mesh),[request.repeat_s,request.repeat_t])?;
40 let uvs=result.mesh.uvs.as_ref().ok_or("Captured image coordinates were lost")?;
41 for (face,indices) in result.mesh.indices.chunks_exact(3).enumerate() {
44 for (corner,index) in indices.iter().enumerate() {
45 let p=request.mesh.positions[request.mesh.triangles[face][corner] as usize];
46 let uv=request.mesh.uvs[request.mesh.uv_triangles[face][corner] as usize];
47 let i=*index as usize;
48 for (axis,expected) in p.iter().enumerate() {
49 let actual=f64::from(result.mesh.positions[i*3+axis])+result.mesh.origin[axis]+result.rtc_offset[axis];
50 if (actual-expected).abs()>1e-4 { return Err("Captured mesh loses world precision in canonical geometry".into()); }
51 }
52 if (f64::from(uvs[i*2])-uv[0]).abs()>1e-6 || (f64::from(uvs[i*2+1])-(1.-uv[1])).abs()>1e-6 {
53 return Err("Captured triangle image correspondence was not preserved".into());
54 }
55 }
56 }
57 Ok(CapturedMeshPlan {plan:result.plan,object_id:result.product_id,geometry_item_id:result.geometry_item_id,
58 mesh:result.mesh,coordinate_space:"ifc-z-up",rtc_offset:result.rtc_offset})
59}
60
61#[cfg(test)]
62#[path="captured_tests.rs"]
63mod tests;