use crate::{
profile::Profile2D, profiles::ProfileProcessor, Error, Mesh, Point3, Result,
TessellationQuality, Vector3,
};
use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
use nalgebra::Matrix4;
use super::super::helpers::parse_axis2_placement_3d;
use super::disk::build_tube_rmf;
use crate::router::GeometryProcessor;
pub struct SurfaceCurveSweptAreaSolidProcessor {
profile_processor: ProfileProcessor,
}
impl SurfaceCurveSweptAreaSolidProcessor {
pub fn new(schema: IfcSchema) -> Self {
Self {
profile_processor: ProfileProcessor::new(schema),
}
}
}
impl GeometryProcessor for SurfaceCurveSweptAreaSolidProcessor {
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("SurfaceCurveSweptAreaSolid missing SweptArea".to_string())
})?;
let profile_entity = decoder
.resolve_ref(profile_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve SweptArea".to_string()))?;
self.profile_processor.set_tessellation_quality(quality);
let profile = self
.profile_processor
.process(&profile_entity, decoder, quality)?;
if profile.outer.len() < 3 {
return Ok(Mesh::new());
}
let directrix_attr = entity.get(2).ok_or_else(|| {
Error::geometry("SurfaceCurveSweptAreaSolid missing Directrix".to_string())
})?;
let directrix = decoder
.resolve_ref(directrix_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve Directrix".to_string()))?;
let start_param = entity.get_float(3);
let end_param = entity.get_float(4);
let has_trim = start_param.is_some() || end_param.is_some();
let curve_points =
if has_trim && directrix.ifc_type.is_subtype_of(IfcType::IfcCompositeCurve) {
self.profile_processor.get_composite_curve_points_trimmed(
&directrix,
decoder,
start_param,
end_param,
)?
} else if has_trim && directrix.ifc_type == IfcType::IfcPolyline {
self.profile_processor.get_polyline_points_trimmed(
&directrix,
decoder,
start_param,
end_param,
)?
} else if has_trim && directrix.ifc_type == IfcType::IfcLine {
self.profile_processor.get_line_points_3d(
&directrix,
decoder,
start_param.unwrap_or(0.0),
end_param.unwrap_or(1.0),
)?
} else {
self.profile_processor
.get_curve_points(&directrix, decoder, quality)?
};
let mut points: Vec<Point3<f64>> = Vec::with_capacity(curve_points.len());
for p in curve_points {
if points
.last()
.is_none_or(|last: &Point3<f64>| (p - *last).norm() > 1e-9)
{
points.push(p);
}
}
if points.len() < 2 {
return Ok(Mesh::new());
}
let position: Matrix4<f64> = match entity.get(1) {
Some(pos_attr) if !pos_attr.is_null() => match decoder.resolve_ref(pos_attr)? {
Some(pos_entity) => parse_axis2_placement_3d(&pos_entity, decoder)?,
None => Matrix4::identity(),
},
_ => Matrix4::identity(),
};
for p in &mut points {
*p = position.transform_point(p);
}
let (_, perp1s, perp2s) = build_tube_rmf(&points);
sweep_profile_along_frames(&profile, &points, &perp1s, &perp2s)
}
fn supported_types(&self) -> Vec<IfcType> {
vec![
IfcType::IfcSurfaceCurveSweptAreaSolid,
IfcType::IfcFixedReferenceSweptAreaSolid,
]
}
}
impl Default for SurfaceCurveSweptAreaSolidProcessor {
fn default() -> Self {
Self::new(IfcSchema::new())
}
}
fn sweep_profile_along_frames(
profile: &Profile2D,
points: &[Point3<f64>],
perp1s: &[Vector3<f64>],
perp2s: &[Vector3<f64>],
) -> Result<Mesh> {
let n = points.len();
if n < 2 || perp1s.len() != n || perp2s.len() != n {
return Ok(Mesh::new());
}
let loops: Vec<&[nalgebra::Point2<f64>]> = std::iter::once(profile.outer.as_slice())
.chain(profile.holes.iter().map(|h| h.as_slice()))
.collect();
let ring_len: usize = loops.iter().map(|l| l.len()).sum();
if ring_len < 3 {
return Ok(Mesh::new());
}
let mut mesh = Mesh::with_capacity(n * ring_len, 0);
let placeholder = Vector3::new(0.0, 0.0, 1.0);
for i in 0..n {
let p = points[i];
let u = perp1s[i];
let v = perp2s[i];
for loop_pts in &loops {
for pt in loop_pts.iter() {
mesh.add_vertex(p + u * pt.x + v * pt.y, placeholder);
}
}
}
for i in 0..n - 1 {
let base_i = (i * ring_len) as u32;
let base_next = ((i + 1) * ring_len) as u32;
let mut loop_start = 0u32;
for loop_pts in &loops {
let m = loop_pts.len() as u32;
if m >= 2 {
for j in 0..m {
let jn = (j + 1) % m;
let a = base_i + loop_start + j;
let b = base_i + loop_start + jn;
let c = base_next + loop_start + j;
let d = base_next + loop_start + jn;
mesh.add_triangle(a, d, c);
mesh.add_triangle(a, b, d);
}
}
loop_start += m;
}
}
if let Ok(tri) = profile.triangulate() {
let base_first = 0u32;
let base_last = ((n - 1) * ring_len) as u32;
for t in tri.indices.chunks_exact(3) {
let (i0, i1, i2) = (t[0] as u32, t[1] as u32, t[2] as u32);
mesh.add_triangle(base_first + i0, base_first + i2, base_first + i1);
mesh.add_triangle(base_last + i0, base_last + i1, base_last + i2);
}
}
crate::calculate_normals(&mut mesh);
Ok(mesh)
}