use crate::{
extrusion::{apply_transform, extrude_profile},
profiles::ProfileProcessor,
Error, Mesh, Result, TessellationQuality, Vector3,
};
use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
use nalgebra::Matrix4;
use super::helpers::parse_axis2_placement_3d;
use crate::router::GeometryProcessor;
use crate::scalar::{magnitude_squared3, GeomScalar};
#[inline]
pub(crate) fn extrusion_local_transform<S: GeomScalar>(
direction: &Vector3<S>,
depth: S,
) -> Option<Matrix4<S>> {
let zero = S::from_f64(0.0);
let one = S::from_f64(1.0);
let norm = magnitude_squared3(direction).sqrt();
let local_direction = Vector3::new(
direction.x / norm,
direction.y / norm,
direction.z / norm,
);
let is_local_z_aligned =
local_direction.x.abs().value() < 0.001 && local_direction.y.abs().value() < 0.001;
if is_local_z_aligned {
if local_direction.z.value() < 0.0 {
#[rustfmt::skip]
let m = Matrix4::new(
one, zero, zero, zero,
zero, one, zero, zero,
zero, zero, one, -depth,
zero, zero, zero, one,
);
Some(m)
} else {
None
}
} else {
#[rustfmt::skip]
let shear_mat = Matrix4::new(
one, zero, local_direction.x, zero,
zero, one, local_direction.y, zero,
zero, zero, local_direction.z, zero,
zero, zero, zero, one,
);
Some(shear_mat)
}
}
pub struct ExtrudedAreaSolidProcessor {
profile_processor: ProfileProcessor,
}
impl ExtrudedAreaSolidProcessor {
pub fn new(schema: IfcSchema) -> Self {
Self {
profile_processor: ProfileProcessor::new(schema),
}
}
}
impl GeometryProcessor for ExtrudedAreaSolidProcessor {
fn process(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
_schema: &IfcSchema,
quality: TessellationQuality,
) -> Result<Mesh> {
let profile_attr = entity
.get(0)
.ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing SweptArea".to_string()))?;
let profile_entity = decoder
.resolve_ref(profile_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve SweptArea".to_string()))?;
let profile = self
.profile_processor
.process(&profile_entity, decoder, quality)?;
if profile.outer.is_empty() {
return Ok(Mesh::new());
}
let direction_attr = entity.get(2).ok_or_else(|| {
Error::geometry("ExtrudedAreaSolid missing ExtrudedDirection".to_string())
})?;
let direction_entity = decoder
.resolve_ref(direction_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve ExtrudedDirection".to_string()))?;
if direction_entity.ifc_type != IfcType::IfcDirection {
return Err(Error::geometry(format!(
"Expected IfcDirection, got {}",
direction_entity.ifc_type
)));
}
let ratios_attr = direction_entity
.get(0)
.ok_or_else(|| Error::geometry("IfcDirection missing ratios".to_string()))?;
let ratios = ratios_attr
.as_list()
.ok_or_else(|| Error::geometry("Expected ratio list".to_string()))?;
use ifc_lite_core::AttributeValue;
let dir_x = ratios
.first()
.and_then(|v: &AttributeValue| v.as_float())
.unwrap_or(0.0);
let dir_y = ratios
.get(1)
.and_then(|v: &AttributeValue| v.as_float())
.unwrap_or(0.0);
let dir_z = ratios
.get(2)
.and_then(|v: &AttributeValue| v.as_float())
.unwrap_or(1.0);
let direction = Vector3::new(dir_x, dir_y, dir_z);
if direction.norm_squared() <= f64::EPSILON {
return Err(Error::geometry(
"ExtrudedAreaSolid has zero-length ExtrudedDirection".to_string(),
));
}
let depth = entity
.get_float(3)
.ok_or_else(|| Error::geometry("ExtrudedAreaSolid missing Depth".to_string()))?;
let pos_transform = if let Some(pos_attr) = entity.get(1) {
if !pos_attr.is_null() {
if let Some(pos_entity) = decoder.resolve_ref(pos_attr)? {
if pos_entity.ifc_type == IfcType::IfcAxis2Placement3D {
Some(parse_axis2_placement_3d(&pos_entity, decoder)?)
} else {
None
}
} else {
None
}
} else {
None
}
} else {
None
};
let transform = extrusion_local_transform(&direction, depth);
let mut mesh = extrude_profile(&profile, depth, transform)?;
if let Some(pos) = pos_transform {
apply_transform(&mut mesh, &pos);
}
Ok(mesh)
}
fn supported_types(&self) -> Vec<IfcType> {
vec![IfcType::IfcExtrudedAreaSolid]
}
}