use crate::{Error, Mesh, Point3, Result};
use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
use crate::router::GeometryProcessor;
use super::advanced_face::process_advanced_face;
use super::helpers::{extract_loop_points_by_id, FaceData, FaceResult};
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]
fn triangulate_face(face: &FaceData) -> 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 as f32);
positions.push(point.y as f32);
positions.push(point.z 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 as f32);
positions.push(point.y as f32);
positions.push(point.z 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 as f32);
positions.push(point.y as f32);
positions.push(point.z 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 as f32);
positions.push(point.y as f32);
positions.push(point.z 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 as f32);
positions.push(point.y as f32);
positions.push(point.z 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 }
}
pub fn process_batch(
&self,
brep_ids: &[u32],
decoder: &mut EntityDecoder,
) -> Vec<(usize, Mesh)> {
use rayon::prelude::*;
let mut all_faces: Vec<(usize, FaceData)> = Vec::with_capacity(brep_ids.len() * 10);
for (brep_idx, &brep_id) in brep_ids.iter().enumerate() {
let shell_id = match decoder.get_first_entity_ref_fast(brep_id) {
Some(id) => id,
None => continue,
};
let face_ids = match decoder.get_entity_ref_list_fast(shell_id) {
Some(ids) => ids,
None => continue,
};
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 {
all_faces.push((
brep_idx,
FaceData {
outer_points,
hole_points,
},
));
}
}
}
let face_results: Vec<(usize, FaceResult)> = all_faces
.par_iter()
.map(|(brep_idx, face)| (*brep_idx, Self::triangulate_face(face)))
.collect();
let mut face_counts = vec![0usize; brep_ids.len()];
for (brep_idx, _) in &face_results {
face_counts[*brep_idx] += 1;
}
let mut mesh_builders: Vec<(Vec<f32>, Vec<u32>)> = face_counts
.iter()
.map(|&count| {
(
Vec::with_capacity(count * 100),
Vec::with_capacity(count * 50),
)
})
.collect();
for (brep_idx, result) in face_results {
let (positions, indices) = &mut mesh_builders[brep_idx];
let base_idx = (positions.len() / 3) as u32;
positions.extend(result.positions);
for idx in result.indices {
indices.push(base_idx + idx);
}
}
mesh_builders
.into_iter()
.enumerate()
.filter(|(_, (positions, _))| !positions.is_empty())
.map(|(brep_idx, (positions, indices))| {
(
brep_idx,
Mesh {
positions,
normals: Vec::new(),
indices,
},
)
})
.collect()
}
}
impl GeometryProcessor for FacetedBrepProcessor {
fn process(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
_schema: &IfcSchema,
) -> 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> = face_data_list
.par_iter()
.map(Self::triangulate_face)
.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,
})
}
fn supported_types(&self) -> Vec<IfcType> {
vec![IfcType::IfcFacetedBrep]
}
}
impl Default for FacetedBrepProcessor {
fn default() -> Self {
Self::new()
}
}
pub struct FaceBasedSurfaceModelProcessor;
impl FaceBasedSurfaceModelProcessor {
pub fn new() -> Self {
Self
}
}
impl GeometryProcessor for FaceBasedSurfaceModelProcessor {
fn process(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
_schema: &IfcSchema,
) -> Result<Mesh> {
let faces_attr = entity.get(0).ok_or_else(|| {
Error::geometry("FaceBasedSurfaceModel missing FbsmFaces".to_string())
})?;
let face_set_refs = faces_attr
.as_list()
.ok_or_else(|| Error::geometry("Expected face set list".to_string()))?;
let mut all_positions = Vec::new();
let mut all_indices = Vec::new();
for face_set_ref in face_set_refs {
let face_set_id = face_set_ref.as_entity_ref().ok_or_else(|| {
Error::geometry("Expected entity reference for face set".to_string())
})?;
let face_ids = match decoder.get_entity_ref_list_fast(face_set_id) {
Some(ids) => ids,
None => continue,
};
for face_id in face_ids {
let face = match decoder.decode_by_id(face_id) {
Ok(f) => f,
Err(_) => continue,
};
if face.ifc_type == IfcType::IfcAdvancedFace {
let (positions, indices) = match process_advanced_face(&face, decoder) {
Ok(result) => result,
Err(_) => continue,
};
if !positions.is_empty() {
let base_idx = (all_positions.len() / 3) as u32;
all_positions.extend(positions);
for idx in indices {
all_indices.push(base_idx + idx);
}
}
} else {
let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
Some(ids) => ids,
None => continue,
};
let mut outer_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 extract_loop_points_by_id(loop_id, decoder) {
Some(p) => p,
None => continue,
};
if !orientation {
points.reverse();
}
if is_outer || outer_points.is_none() {
outer_points = Some(points);
} else {
hole_points.push(points);
}
}
if let Some(outer) = outer_points {
if outer.len() >= 3 {
let base_idx = (all_positions.len() / 3) as u32;
for p in &outer {
all_positions.push(p.x as f32);
all_positions.push(p.y as f32);
all_positions.push(p.z as f32);
}
for i in 1..outer.len() - 1 {
all_indices.push(base_idx);
all_indices.push(base_idx + i as u32);
all_indices.push(base_idx + i as u32 + 1);
}
}
}
}
}
}
Ok(Mesh {
positions: all_positions,
normals: Vec::new(),
indices: all_indices,
})
}
fn supported_types(&self) -> Vec<IfcType> {
vec![IfcType::IfcFaceBasedSurfaceModel]
}
}
impl Default for FaceBasedSurfaceModelProcessor {
fn default() -> Self {
Self::new()
}
}
pub struct ShellBasedSurfaceModelProcessor;
impl ShellBasedSurfaceModelProcessor {
pub fn new() -> Self {
Self
}
}
impl GeometryProcessor for ShellBasedSurfaceModelProcessor {
fn process(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
_schema: &IfcSchema,
) -> Result<Mesh> {
let shells_attr = entity.get(0).ok_or_else(|| {
Error::geometry("ShellBasedSurfaceModel missing SbsmBoundary".to_string())
})?;
let shell_refs = shells_attr
.as_list()
.ok_or_else(|| Error::geometry("Expected shell list".to_string()))?;
let mut all_positions = Vec::new();
let mut all_indices = Vec::new();
for shell_ref in shell_refs {
let shell_id = shell_ref.as_entity_ref().ok_or_else(|| {
Error::geometry("Expected entity reference for shell".to_string())
})?;
let face_ids = match decoder.get_entity_ref_list_fast(shell_id) {
Some(ids) => ids,
None => continue,
};
for face_id in face_ids {
let face = match decoder.decode_by_id(face_id) {
Ok(f) => f,
Err(_) => continue,
};
if face.ifc_type == IfcType::IfcAdvancedFace {
let (positions, indices) = match process_advanced_face(&face, decoder) {
Ok(result) => result,
Err(_) => continue,
};
if !positions.is_empty() {
let base_idx = (all_positions.len() / 3) as u32;
all_positions.extend(positions);
for idx in indices {
all_indices.push(base_idx + idx);
}
}
} else {
let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
Some(ids) => ids,
None => continue,
};
let mut outer_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 extract_loop_points_by_id(loop_id, decoder) {
Some(p) => p,
None => continue,
};
if !orientation {
points.reverse();
}
if is_outer || outer_points.is_none() {
outer_points = Some(points);
} else {
hole_points.push(points);
}
}
if let Some(outer) = outer_points {
if outer.len() >= 3 {
let base_idx = (all_positions.len() / 3) as u32;
for p in &outer {
all_positions.push(p.x as f32);
all_positions.push(p.y as f32);
all_positions.push(p.z as f32);
}
for i in 1..outer.len() - 1 {
all_indices.push(base_idx);
all_indices.push(base_idx + i as u32);
all_indices.push(base_idx + i as u32 + 1);
}
}
}
}
}
}
Ok(Mesh {
positions: all_positions,
normals: Vec::new(),
indices: all_indices,
})
}
fn supported_types(&self) -> Vec<IfcType> {
vec![IfcType::IfcShellBasedSurfaceModel]
}
}
impl Default for ShellBasedSurfaceModelProcessor {
fn default() -> Self {
Self::new()
}
}