use super::super::GeometryRouter;
use crate::profiles::ProfileProcessor;
use crate::{Point2, Point3, Result, TessellationQuality, Vector2, Vector3};
use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
use nalgebra::Matrix4;
impl GeometryRouter {
pub(super) fn resolve_grid_placement_with_depth(
&self,
placement: &DecodedEntity,
decoder: &mut EntityDecoder,
depth: usize,
) -> Result<Matrix4<f64>> {
let parent_transform = match placement.get(0) {
Some(attr) if !attr.is_null() => match decoder.resolve_ref(attr)? {
Some(parent) => {
self.get_placement_transform_with_depth(&parent, decoder, depth + 1)?
}
None => Matrix4::identity(),
},
_ => Matrix4::identity(),
};
let local = self
.try_resolve_grid_intersection(placement, decoder)
.unwrap_or_else(Matrix4::identity);
Ok(parent_transform * local)
}
fn try_resolve_grid_intersection(
&self,
placement: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Option<Matrix4<f64>> {
let loc_attr = placement.get(1)?;
if loc_attr.is_null() {
return None;
}
let location = decoder.resolve_ref(loc_attr).ok().flatten()?;
if location.ifc_type != IfcType::IfcVirtualGridIntersection {
return None;
}
let p = self.grid_intersection_point(&location, decoder)?;
let mut m = match self.grid_ref_direction_vector(placement, &p, decoder) {
Some(x_dir) => orient_x_in_plane(x_dir),
None => Matrix4::identity(),
};
m[(0, 3)] = p.x;
m[(1, 3)] = p.y;
m[(2, 3)] = p.z; Some(m)
}
fn grid_intersection_point(
&self,
intersection: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Option<Point3<f64>> {
let axes_attr = intersection.get(0)?;
let axes = decoder.resolve_ref_list(axes_attr).ok()?;
if axes.len() < 2 {
return None;
}
let (a0, a_dir) = self.grid_axis_line(&axes[0], decoder)?;
let (b0, b_dir) = self.grid_axis_line(&axes[1], decoder)?;
let offsets = intersection.get_list(1);
let off_u = offsets.and_then(|o| o.first()).and_then(|v| v.as_float()).unwrap_or(0.0);
let off_v = offsets.and_then(|o| o.get(1)).and_then(|v| v.as_float()).unwrap_or(0.0);
let off_z = offsets.and_then(|o| o.get(2)).and_then(|v| v.as_float()).unwrap_or(0.0);
let n_a = left_normal(a_dir);
let n_b = left_normal(b_dir);
let pa = Point2::new(a0.x + n_a.x * off_u, a0.y + n_a.y * off_u);
let pb = Point2::new(b0.x + n_b.x * off_v, b0.y + n_b.y * off_v);
let p = line_intersection_2d(pa, a_dir, pb, b_dir)?;
Some(Point3::new(p.x, p.y, off_z))
}
fn grid_axis_line(
&self,
axis: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Option<(Point2<f64>, Vector2<f64>)> {
let curve_attr = axis.get(1)?;
if curve_attr.is_null() {
return None;
}
let curve = decoder.resolve_ref(curve_attr).ok().flatten()?;
let processor = ProfileProcessor::new(IfcSchema::new());
let pts = processor
.get_curve_points(&curve, decoder, TessellationQuality::Medium)
.ok()?;
if pts.len() < 2 {
return None;
}
let start = pts.first()?;
let end = pts.last()?;
let dir = Vector2::new(end.x - start.x, end.y - start.y);
if dir.norm() < 1e-9 {
return None;
}
Some((Point2::new(start.x, start.y), dir))
}
fn grid_ref_direction_vector(
&self,
placement: &DecodedEntity,
origin: &Point3<f64>,
decoder: &mut EntityDecoder,
) -> Option<Vector2<f64>> {
let dir_attr = placement.get(2)?;
if dir_attr.is_null() {
return None;
}
let entity = decoder.resolve_ref(dir_attr).ok().flatten()?;
let x = match entity.ifc_type {
IfcType::IfcDirection => {
let d = self.parse_direction(&entity).ok()?;
Vector2::new(d.x, d.y)
}
IfcType::IfcVirtualGridIntersection => {
let q = self.grid_intersection_point(&entity, decoder)?;
Vector2::new(q.x - origin.x, q.y - origin.y)
}
_ => return None,
};
if x.norm() < 1e-9 {
return None;
}
Some(x)
}
}
fn orient_x_in_plane(x_dir: Vector2<f64>) -> Matrix4<f64> {
let z = Vector3::new(0.0, 0.0, 1.0);
let x = Vector3::new(x_dir.x, x_dir.y, 0.0).normalize();
let y = z.cross(&x).normalize();
let mut m = Matrix4::<f64>::identity();
m.fixed_view_mut::<3, 1>(0, 0).copy_from(&x);
m.fixed_view_mut::<3, 1>(0, 1).copy_from(&y);
m.fixed_view_mut::<3, 1>(0, 2).copy_from(&z);
m
}
fn left_normal(dir: Vector2<f64>) -> Vector2<f64> {
let n = Vector2::new(-dir.y, dir.x);
let len = n.norm();
if len < 1e-9 {
Vector2::new(0.0, 0.0)
} else {
n / len
}
}
fn line_intersection_2d(
p1: Point2<f64>,
d1: Vector2<f64>,
p2: Point2<f64>,
d2: Vector2<f64>,
) -> Option<Point2<f64>> {
let denom = d1.x * d2.y - d1.y * d2.x;
if denom.abs() < 1e-9 {
return None;
}
let dp = p2 - p1;
let t = (dp.x * d2.y - dp.y * d2.x) / denom;
Some(p1 + d1 * t)
}
#[cfg(test)]
mod grid_placement_tests {
use super::*;
use ifc_lite_core::build_entity_index;
const CONTENT: &str = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('','',(''),(''),'','','');
FILE_SCHEMA(('IFC4X3_ADD2'));
ENDSEC;
DATA;
#1=IFCCARTESIANPOINT((0.,0.));
#2=IFCCARTESIANPOINT((10.,0.));
#3=IFCPOLYLINE((#1,#2));
#4=IFCGRIDAXIS('P',#3,.T.);
#5=IFCCARTESIANPOINT((0.,10.));
#6=IFCPOLYLINE((#1,#5));
#7=IFCGRIDAXIS('Q',#6,.T.);
#8=IFCVIRTUALGRIDINTERSECTION((#4,#7),(0.,0.,0.));
#9=IFCCARTESIANPOINT((0.,5.));
#10=IFCCARTESIANPOINT((10.,5.));
#11=IFCPOLYLINE((#9,#10));
#12=IFCGRIDAXIS('S',#11,.T.);
#13=IFCVIRTUALGRIDINTERSECTION((#7,#12),(0.,0.,0.));
#20=IFCGRIDPLACEMENT($,#8,#13);
#21=IFCDIRECTION((0.,1.,0.));
#22=IFCGRIDPLACEMENT($,#8,#21);
#23=IFCGRIDPLACEMENT($,#8,$);
#30=IFCVIRTUALGRIDINTERSECTION((#4,#7),(2.,3.,4.));
#31=IFCGRIDPLACEMENT($,#30,$);
#40=IFCDIRECTION((0.,0.,1.));
#41=IFCDIRECTION((1.,0.,0.));
#42=IFCCARTESIANPOINT((100.,200.,300.));
#43=IFCAXIS2PLACEMENT3D(#42,#40,#41);
#44=IFCLOCALPLACEMENT($,#43);
#45=IFCGRIDPLACEMENT(#44,#8,$);
ENDSEC;
END-ISO-10303-21;
"#;
fn transform_of(id: u32) -> Matrix4<f64> {
let content = CONTENT.to_string();
let ei = build_entity_index(&content);
let mut decoder = EntityDecoder::with_index(&content, ei);
let router = GeometryRouter::new();
let placement = decoder
.decode_by_id(id)
.unwrap_or_else(|e| panic!("decode #{id}: {e:?}"));
router
.get_placement_transform(&placement, &mut decoder)
.unwrap_or_else(|e| panic!("transform #{id}: {e:?}"))
}
fn x_axis(m: &Matrix4<f64>) -> Vector3<f64> {
Vector3::new(m[(0, 0)], m[(1, 0)], m[(2, 0)])
}
fn origin(m: &Matrix4<f64>) -> Point3<f64> {
Point3::new(m[(0, 3)], m[(1, 3)], m[(2, 3)])
}
#[test]
fn ref_direction_as_ifc_direction_sets_local_x() {
let m = transform_of(22);
assert!((x_axis(&m) - Vector3::new(0.0, 1.0, 0.0)).norm() < 1e-9);
assert!((origin(&m) - Point3::new(0.0, 0.0, 0.0)).norm() < 1e-9);
}
#[test]
fn ref_direction_as_virtual_intersection_points_x_toward_it() {
let m = transform_of(20);
assert!((x_axis(&m) - Vector3::new(0.0, 1.0, 0.0)).norm() < 1e-9);
assert!((origin(&m) - Point3::new(0.0, 0.0, 0.0)).norm() < 1e-9);
}
#[test]
fn null_ref_direction_stays_axis_aligned() {
let m = transform_of(23);
assert!((x_axis(&m) - Vector3::new(1.0, 0.0, 0.0)).norm() < 1e-9);
assert!((origin(&m) - Point3::new(0.0, 0.0, 0.0)).norm() < 1e-9);
}
#[test]
fn offset_distances_shift_the_intersection() {
let m = transform_of(31);
assert!((origin(&m) - Point3::new(-3.0, 2.0, 4.0)).norm() < 1e-9, "origin={:?}", origin(&m));
}
#[test]
fn placement_rel_to_composes_with_the_grid_placement() {
let m = transform_of(45);
assert!(
(origin(&m) - Point3::new(100.0, 200.0, 300.0)).norm() < 1e-9,
"origin={:?}",
origin(&m)
);
assert!((x_axis(&m) - Vector3::new(1.0, 0.0, 0.0)).norm() < 1e-9);
}
}