use axiolid_core::{Plane3, Point3, Vec3};
use axiolid_model::{GeometryNode, NodeId, SolidOperation};
use axiolid_primitive::HalfSpace;
use ifc_model::EntityId;
use crate::error::{GeometryError, GeometryResult};
use crate::lower::session::LoweringSession;
use crate::resource::placement::axis_placement_transform;
use crate::solid::halfspace::{HalfSpaceSolid, PolygonalBoundedHalfSpace};
use crate::surface::elementary::Plane;
use crate::transform::Transform;
const KIND: &str = "half space";
fn flip(ifc_agreement_flag: bool) -> bool {
!ifc_agreement_flag
}
pub fn lower_half_space_node(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
let type_name = session.type_name(id)?;
if let Some(node) = session.memoized(id, KIND, frame) {
return Ok(node);
}
session.enter(id, KIND)?;
let result = if type_name.eq_ignore_ascii_case("IFCPOLYGONALBOUNDEDHALFSPACE") {
build_polygonal(session, id, frame)
} else {
build(session, id, frame)
};
session.exit(id);
let node = result?;
session.memoize(id, KIND, frame, node);
Ok(node)
}
fn build_polygonal(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
let entity = session.entity(id, id)?;
let view = PolygonalBoundedHalfSpace::new(id, entity);
let base = view.base();
let type_name = base.type_name().to_string();
let plane = plane_of(session, id, &type_name, base.base_surface()?, frame)?;
let half_space = session.node_for(
id,
GeometryNode::HalfSpace(HalfSpace {
boundary: plane,
agreement: flip(base.agreement_flag()?),
}),
)?;
let boundary = boundary::lower_boundary_curve(session, id, view.polygonal_boundary()?)?;
let position_ref = view.position()?;
let position = session.entity(id, position_ref)?;
let local = axis_placement_transform(session.model(), position_ref, position)?
.to_metres(session.units());
let placed = frame.compose(&local);
session.node_for(
id,
GeometryNode::SolidOperation(SolidOperation::BoundedHalfSpace {
half_space,
boundary,
placement: placed.to_geom(),
}),
)
}
fn build(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
let entity = session.entity(id, id)?;
let view = HalfSpaceSolid::new(id, entity);
let type_name = view.type_name().to_string();
let surface_ref = view.base_surface()?;
let agreement = view.agreement_flag()?;
let boundary = plane_of(session, id, &type_name, surface_ref, frame)?;
let half_space = HalfSpace {
boundary,
agreement: flip(agreement),
};
session.node_for(id, GeometryNode::HalfSpace(half_space))
}
fn plane_of(
session: &mut LoweringSession<'_>,
owner: EntityId,
owner_type: &str,
surface_ref: EntityId,
frame: Transform,
) -> GeometryResult<Plane3> {
let surface = session.entity(owner, surface_ref)?;
let surface_type = surface.type_name.to_string();
if !surface_type.eq_ignore_ascii_case("IFCPLANE") {
return Err(session.unsupported(
surface_ref,
&surface_type,
"half-space base surfaces other than IfcPlane need exact surface nodes",
));
}
let plane = Plane::new(surface_ref, surface);
let position_ref = plane.position_ref()?;
let position = session.entity(surface_ref, position_ref)?;
let local = axis_placement_transform(session.model(), position_ref, position)?
.to_metres(session.units());
let placed = frame.compose(&local);
let origin = placed.apply([0.0, 0.0, 0.0]);
let normal =
placed
.apply_unit_normal([0.0, 0.0, 1.0])
.ok_or_else(|| GeometryError::Degenerate {
entity: owner,
type_name: owner_type.to_string(),
detail: "base plane transform is singular or non-finite".to_string(),
})?;
Ok(Plane3 {
origin: Point3::from_array(origin),
normal: Vec3::from_array(normal),
})
}
mod boundary;
#[cfg(test)]
mod tests;