use crate::{Error, Mesh, Point3, Result, TessellationQuality};
use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
use super::super::helpers::{FaceData, FaceResult};
use crate::router::GeometryProcessor;
const PAR_FACE_THRESHOLD: usize = 64;
pub struct FacetedBrepProcessor;
impl FacetedBrepProcessor {
pub fn new() -> Self {
Self
}
#[allow(dead_code)]
#[inline]
fn extract_loop_points(
&self,
loop_entity: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Option<Vec<Point3<f64>>> {
let polygon_attr = loop_entity.get(0)?;
let point_refs = polygon_attr.as_list()?;
let mut polygon_points = Vec::with_capacity(point_refs.len());
for point_ref in point_refs {
let point_id = point_ref.as_entity_ref()?;
if let Some((x, y, z)) = decoder.get_cartesian_point_fast(point_id) {
polygon_points.push(Point3::new(x, y, z));
} else {
let point = decoder.decode_by_id(point_id).ok()?;
let coords_attr = point.get(0)?;
let coords = coords_attr.as_list()?;
use ifc_lite_core::AttributeValue;
let x = coords.first().and_then(|v: &AttributeValue| v.as_float())?;
let y = coords.get(1).and_then(|v: &AttributeValue| v.as_float())?;
let z = coords.get(2).and_then(|v: &AttributeValue| v.as_float())?;
polygon_points.push(Point3::new(x, y, z));
}
}
if polygon_points.len() >= 3 {
Some(polygon_points)
} else {
None
}
}
#[inline]
fn extract_loop_points_fast(
&self,
loop_entity_id: u32,
decoder: &mut EntityDecoder,
) -> Option<Vec<Point3<f64>>> {
let coords = decoder.get_polyloop_coords_cached(loop_entity_id)?;
let polygon_points: Vec<Point3<f64>> = coords
.into_iter()
.map(|(x, y, z)| Point3::new(x, y, z))
.collect();
if polygon_points.len() >= 3 {
Some(polygon_points)
} else {
None
}
}
#[inline]
pub(super) fn triangulate_face(face: &FaceData, rtc: (f64, f64, f64)) -> FaceResult {
let n = face.outer_points.len();
if n == 3 && face.hole_points.is_empty() {
let mut positions = Vec::with_capacity(9);
for point in &face.outer_points {
positions.push((point.x - rtc.0) as f32);
positions.push((point.y - rtc.1) as f32);
positions.push((point.z - rtc.2) as f32);
}
return FaceResult {
positions,
indices: vec![0, 1, 2],
};
}
if n == 4 && face.hole_points.is_empty() {
let mut positions = Vec::with_capacity(12);
for point in &face.outer_points {
positions.push((point.x - rtc.0) as f32);
positions.push((point.y - rtc.1) as f32);
positions.push((point.z - rtc.2) as f32);
}
return FaceResult {
positions,
indices: vec![0, 1, 2, 0, 2, 3],
};
}
if face.hole_points.is_empty() && n <= 8 {
let mut is_convex = true;
if n > 4 {
use crate::triangulation::calculate_polygon_normal;
let normal = calculate_polygon_normal(&face.outer_points);
let mut sign = 0i8;
for i in 0..n {
let p0 = &face.outer_points[i];
let p1 = &face.outer_points[(i + 1) % n];
let p2 = &face.outer_points[(i + 2) % n];
let v1 = p1 - p0;
let v2 = p2 - p1;
let cross = v1.cross(&v2);
let dot = cross.dot(&normal);
if dot.abs() > 1e-10 {
let current_sign = if dot > 0.0 { 1i8 } else { -1i8 };
if sign == 0 {
sign = current_sign;
} else if sign != current_sign {
is_convex = false;
break;
}
}
}
}
if is_convex {
let mut positions = Vec::with_capacity(n * 3);
for point in &face.outer_points {
positions.push((point.x - rtc.0) as f32);
positions.push((point.y - rtc.1) as f32);
positions.push((point.z - rtc.2) as f32);
}
let mut indices = Vec::with_capacity((n - 2) * 3);
for i in 1..n - 1 {
indices.push(0);
indices.push(i as u32);
indices.push(i as u32 + 1);
}
return FaceResult { positions, indices };
}
}
use crate::triangulation::{
calculate_polygon_normal, project_to_2d, project_to_2d_with_basis,
triangulate_polygon_with_holes,
};
let mut positions = Vec::new();
let mut indices = Vec::new();
let normal = calculate_polygon_normal(&face.outer_points);
let (outer_2d, u_axis, v_axis, origin) = project_to_2d(&face.outer_points, &normal);
let holes_2d: Vec<Vec<nalgebra::Point2<f64>>> = face
.hole_points
.iter()
.map(|hole| project_to_2d_with_basis(hole, &u_axis, &v_axis, &origin))
.collect();
let tri_indices = match triangulate_polygon_with_holes(&outer_2d, &holes_2d) {
Ok(idx) => idx,
Err(_) => {
for point in &face.outer_points {
positions.push((point.x - rtc.0) as f32);
positions.push((point.y - rtc.1) as f32);
positions.push((point.z - rtc.2) as f32);
}
for i in 1..face.outer_points.len() - 1 {
indices.push(0);
indices.push(i as u32);
indices.push(i as u32 + 1);
}
return FaceResult { positions, indices };
}
};
let mut all_points_3d: Vec<&Point3<f64>> = face.outer_points.iter().collect();
for hole in &face.hole_points {
all_points_3d.extend(hole.iter());
}
for point in &all_points_3d {
positions.push((point.x - rtc.0) as f32);
positions.push((point.y - rtc.1) as f32);
positions.push((point.z - rtc.2) as f32);
}
for i in (0..tri_indices.len()).step_by(3) {
if i + 2 >= tri_indices.len() {
break;
}
indices.push(tri_indices[i] as u32);
indices.push(tri_indices[i + 1] as u32);
indices.push(tri_indices[i + 2] as u32);
}
FaceResult { positions, indices }
}
}
impl FacetedBrepProcessor {
pub fn process_with_rtc(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
_schema: &IfcSchema,
rtc: (f64, f64, f64),
) -> Result<Mesh> {
use rayon::prelude::*;
let shell_attr = entity
.get(0)
.ok_or_else(|| Error::geometry("FacetedBrep missing Outer shell".to_string()))?;
let shell_id = shell_attr
.as_entity_ref()
.ok_or_else(|| Error::geometry("Expected entity ref for Outer shell".to_string()))?;
let face_ids = decoder
.get_entity_ref_list_fast(shell_id)
.ok_or_else(|| Error::geometry("Failed to get faces from ClosedShell".to_string()))?;
let mut face_data_list: Vec<FaceData> = Vec::with_capacity(face_ids.len());
for face_id in face_ids {
let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
Some(ids) => ids,
None => continue,
};
let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
for bound_id in bound_ids {
let (loop_id, orientation, is_outer) = match decoder.get_face_bound_fast(bound_id) {
Some(data) => data,
None => continue,
};
let mut points = match self.extract_loop_points_fast(loop_id, decoder) {
Some(p) => p,
None => continue,
};
if !orientation {
points.reverse();
}
if is_outer || outer_bound_points.is_none() {
if outer_bound_points.is_some() && is_outer {
if let Some(prev_outer) = outer_bound_points.take() {
hole_points.push(prev_outer);
}
}
outer_bound_points = Some(points);
} else {
hole_points.push(points);
}
}
if let Some(outer_points) = outer_bound_points {
face_data_list.push(FaceData {
outer_points,
hole_points,
});
}
}
let face_results: Vec<FaceResult> = if face_data_list.len() < PAR_FACE_THRESHOLD {
face_data_list
.iter()
.map(|face| Self::triangulate_face(face, rtc))
.collect()
} else {
face_data_list
.par_iter()
.map(|face| Self::triangulate_face(face, rtc))
.collect()
};
let total_positions: usize = face_results.iter().map(|r| r.positions.len()).sum();
let total_indices: usize = face_results.iter().map(|r| r.indices.len()).sum();
let mut positions = Vec::with_capacity(total_positions);
let mut indices = Vec::with_capacity(total_indices);
for result in face_results {
let base_idx = (positions.len() / 3) as u32;
positions.extend(result.positions);
for idx in result.indices {
indices.push(base_idx + idx);
}
}
Ok(Mesh {
positions,
normals: Vec::new(),
indices,
rtc_applied: true, origin: [0.0; 3],
instance_meta: None, local_bounds: None, local_to_world: None })
}
}
impl GeometryProcessor for FacetedBrepProcessor {
fn process(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
_schema: &IfcSchema,
_quality: TessellationQuality,
) -> Result<Mesh> {
use rayon::prelude::*;
let shell_attr = entity
.get(0)
.ok_or_else(|| Error::geometry("FacetedBrep missing Outer shell".to_string()))?;
let shell_id = shell_attr
.as_entity_ref()
.ok_or_else(|| Error::geometry("Expected entity ref for Outer shell".to_string()))?;
let face_ids = decoder
.get_entity_ref_list_fast(shell_id)
.ok_or_else(|| Error::geometry("Failed to get faces from ClosedShell".to_string()))?;
let mut face_data_list: Vec<FaceData> = Vec::with_capacity(face_ids.len());
for face_id in face_ids {
let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
Some(ids) => ids,
None => continue,
};
let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
for bound_id in bound_ids {
let (loop_id, orientation, is_outer) = match decoder.get_face_bound_fast(bound_id) {
Some(data) => data,
None => continue,
};
let mut points = match self.extract_loop_points_fast(loop_id, decoder) {
Some(p) => p,
None => continue,
};
if !orientation {
points.reverse();
}
if is_outer || outer_bound_points.is_none() {
if outer_bound_points.is_some() && is_outer {
if let Some(prev_outer) = outer_bound_points.take() {
hole_points.push(prev_outer);
}
}
outer_bound_points = Some(points);
} else {
hole_points.push(points);
}
}
if let Some(outer_points) = outer_bound_points {
face_data_list.push(FaceData {
outer_points,
hole_points,
});
}
}
let face_results: Vec<FaceResult> = if face_data_list.len() < PAR_FACE_THRESHOLD {
face_data_list
.iter()
.map(|face| Self::triangulate_face(face, (0.0, 0.0, 0.0)))
.collect()
} else {
face_data_list
.par_iter()
.map(|face| Self::triangulate_face(face, (0.0, 0.0, 0.0)))
.collect()
};
let total_positions: usize = face_results.iter().map(|r| r.positions.len()).sum();
let total_indices: usize = face_results.iter().map(|r| r.indices.len()).sum();
let mut positions = Vec::with_capacity(total_positions);
let mut indices = Vec::with_capacity(total_indices);
for result in face_results {
let base_idx = (positions.len() / 3) as u32;
positions.extend(result.positions);
for idx in result.indices {
indices.push(base_idx + idx);
}
}
Ok(Mesh {
positions,
normals: Vec::new(),
indices,
rtc_applied: false,
origin: [0.0; 3], instance_meta: None, local_bounds: None, local_to_world: None })
}
fn supported_types(&self) -> Vec<IfcType> {
vec![IfcType::IfcFacetedBrep]
}
}
impl Default for FacetedBrepProcessor {
fn default() -> Self {
Self::new()
}
}