use ifc_lite_processing::{
process_geometry_streaming_filtered_with_options, MeshData, OpeningFilterMode,
ProcessingResult, StreamingOptions,
};
fn fixture_bytes(name: &str) -> Vec<u8> {
let path = format!(
"{}/../geometry/tests/fixtures/{name}",
env!("CARGO_MANIFEST_DIR")
);
std::fs::read(&path).unwrap_or_else(|e| panic!("read {path}: {e}"))
}
fn run(content: &[u8], instancing: bool) -> ProcessingResult {
process_geometry_streaming_filtered_with_options(
content,
OpeningFilterMode::Default,
StreamingOptions {
enable_instancing: instancing,
..StreamingOptions::default()
},
|_, _, _| {},
|_| {},
|_| {},
)
}
fn world_bounds(m: &MeshData) -> ([f64; 3], [f64; 3]) {
let mut min = [f64::INFINITY; 3];
let mut max = [f64::NEG_INFINITY; 3];
for v in m.positions.chunks_exact(3) {
for k in 0..3 {
let w = m.origin[k] + v[k] as f64;
min[k] = min[k].min(w);
max[k] = max[k].max(w);
}
}
(min, max)
}
fn mesh_by_id(res: &ProcessingResult, id: u32) -> &MeshData {
res.meshes
.iter()
.find(|m| m.express_id == id)
.unwrap_or_else(|| panic!("no mesh for #{id}"))
}
const TOL: f64 = 1e-4;
fn assert_close(actual: [f64; 3], expected: [f64; 3], what: &str) {
for k in 0..3 {
assert!(
(actual[k] - expected[k]).abs() < TOL,
"{what}: axis {k} was {}, expected {} (full {actual:?} vs {expected:?})",
actual[k],
expected[k]
);
}
}
#[test]
fn uniform_scale_1000_matches_direct_millimetre_authoring() {
let res = run(&fixture_bytes("issue_1985_scaled_kinds.ifc"), false);
for (mapped, direct, what) in [
(74u32, 99u32, "extruded circle"),
(81, 109, "swept disk"),
] {
let (mmin, mmax) = world_bounds(mesh_by_id(&res, mapped));
let (dmin, dmax) = world_bounds(mesh_by_id(&res, direct));
let msize = [mmax[0] - mmin[0], mmax[1] - mmin[1], mmax[2] - mmin[2]];
let dsize = [dmax[0] - dmin[0], dmax[1] - dmin[1], dmax[2] - dmin[2]];
assert_close(msize, dsize, &format!("{what}: mapped size vs direct size"));
assert_close(msize, [0.1, 0.1, 2.0], &format!("{what}: absolute size"));
assert_eq!(
mesh_by_id(&res, mapped).positions.len(),
mesh_by_id(&res, direct).positions.len(),
"{what}: mapped and direct must tessellate identically"
);
}
let (bmin, bmax) = world_bounds(mesh_by_id(&res, 88));
assert_close(
[bmax[0] - bmin[0], bmax[1] - bmin[1], bmax[2] - bmin[2]],
[1.0, 1.0, 1.0],
"faceted brep size",
);
}
#[test]
fn mapping_origin_is_composed_inside_the_mapping_target() {
for instancing in [false, true] {
let res = run(&fixture_bytes("issue_1985_mapping_origin.ifc"), instancing);
let (min, max) = world_bounds(mesh_by_id(&res, 40));
assert_close(min, [-0.05, -0.05, 1.0], "occurrence #40 min (instancing)");
assert_close(max, [0.05, 0.05, 3.0], "occurrence #40 max (instancing)");
if !instancing {
let (min, max) = world_bounds(mesh_by_id(&res, 47));
assert_close(min, [0.95, -0.05, 1.0], "occurrence #47 min");
assert_close(max, [1.05, 0.05, 3.0], "occurrence #47 max");
continue;
}
let record = res
.instances
.iter()
.find(|i| i.express_id == 47)
.expect("occurrence #47 should ride as an instance record");
assert_eq!(record.template_express_id, 40, "template for #47");
let template = mesh_by_id(&res, 40);
let (mut min, mut max) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
for v in template.positions.chunks_exact(3) {
let (x, y, z) = (
template.origin[0] + v[0] as f64,
template.origin[1] + v[1] as f64,
template.origin[2] + v[2] as f64,
);
let t = record.transform.map(|c| c as f64);
let w = [
t[0] * x + t[1] * y + t[2] * z + t[3],
t[4] * x + t[5] * y + t[6] * z + t[7],
t[8] * x + t[9] * y + t[10] * z + t[11],
];
let h = t[12] * x + t[13] * y + t[14] * z + t[15];
for k in 0..3 {
min[k] = min[k].min(w[k] / h);
max[k] = max[k].max(w[k] / h);
}
}
assert_close(min, [0.95, -0.05, 1.0], "instanced #47 reconstructed min");
assert_close(max, [1.05, 0.05, 3.0], "instanced #47 reconstructed max");
}
}
#[test]
fn sub_unit_scales_nonuniform_and_nested_maps_match_direct_metre_authoring() {
for instancing in [false, true] {
let res = run(&fixture_bytes("issue_1985_metre_submm_scale.ifc"), instancing);
let size = |id: u32| {
let (min, max) = world_bounds(mesh_by_id(&res, id));
[max[0] - min[0], max[1] - min[1], max[2] - min[2]]
};
assert_close(size(99), [0.1, 0.1, 1.7345], "direct metre pipe");
assert_close(size(74), size(99), "uniform 0.001 vs direct");
assert_close(size(88), size(99), "nested map vs direct");
if !instancing {
assert_close(size(81), [0.1, 0.1, 3.469], "non-uniform 3D operator");
continue;
}
let record = res
.instances
.iter()
.find(|i| i.express_id == 81)
.expect("occurrence #81 (non-uniform) should ride as an instance record");
assert_eq!(record.template_express_id, 74, "template for #81");
let template = mesh_by_id(&res, 74);
let (mut min, mut max) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
for v in template.positions.chunks_exact(3) {
let (x, y, z) = (
template.origin[0] + v[0] as f64,
template.origin[1] + v[1] as f64,
template.origin[2] + v[2] as f64,
);
let t = record.transform.map(|c| c as f64);
let w = [
t[0] * x + t[1] * y + t[2] * z + t[3],
t[4] * x + t[5] * y + t[6] * z + t[7],
t[8] * x + t[9] * y + t[10] * z + t[11],
];
let h = t[12] * x + t[13] * y + t[14] * z + t[15];
for k in 0..3 {
min[k] = min[k].min(w[k] / h);
max[k] = max[k].max(w[k] / h);
}
}
let reconstructed = [max[0] - min[0], max[1] - min[1], max[2] - min[2]];
assert_close(
reconstructed,
[0.1, 0.1, 3.469],
"instanced #81 reconstructed non-uniform size",
);
}
}