Skip to main content

ifc_lite_processing/appearance/
captured.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4use 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}
30/// Author a bounded existing captured surface as IfcBuildingElementProxy.
31/// Decoding, reconstruction, classification and image ownership stay with callers.
32pub 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    // Check the actual native triangle corners, including seams and image V.
42    // This is a refusal boundary for float precision loss, never a silent repair.
43    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;