ifc_lite_geometry/processors/brep/
faceted.rs1use crate::{Error, Mesh, Point3, Result, TessellationQuality};
6use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
7
8use super::super::helpers::{FaceData, FaceResult};
9use crate::router::GeometryProcessor;
10
11const PAR_FACE_THRESHOLD: usize = 64;
19
20pub struct FacetedBrepProcessor;
27
28impl FacetedBrepProcessor {
29 pub fn new() -> Self {
30 Self
31 }
32
33 #[allow(dead_code)]
36 #[inline]
37 fn extract_loop_points(
38 &self,
39 loop_entity: &DecodedEntity,
40 decoder: &mut EntityDecoder,
41 ) -> Option<Vec<Point3<f64>>> {
42 let polygon_attr = loop_entity.get(0)?;
44
45 let point_refs = polygon_attr.as_list()?;
47
48 let mut polygon_points = Vec::with_capacity(point_refs.len());
50
51 for point_ref in point_refs {
52 let point_id = point_ref.as_entity_ref()?;
53
54 if let Some((x, y, z)) = decoder.get_cartesian_point_fast(point_id) {
56 polygon_points.push(Point3::new(x, y, z));
57 } else {
58 let point = decoder.decode_by_id(point_id).ok()?;
60 let coords_attr = point.get(0)?;
61 let coords = coords_attr.as_list()?;
62 use ifc_lite_core::AttributeValue;
63 let x = coords.first().and_then(|v: &AttributeValue| v.as_float())?;
64 let y = coords.get(1).and_then(|v: &AttributeValue| v.as_float())?;
65 let z = coords.get(2).and_then(|v: &AttributeValue| v.as_float())?;
66 polygon_points.push(Point3::new(x, y, z));
67 }
68 }
69
70 if polygon_points.len() >= 3 {
71 Some(polygon_points)
72 } else {
73 None
74 }
75 }
76
77 #[inline]
81 fn extract_loop_points_fast(
82 &self,
83 loop_entity_id: u32,
84 decoder: &mut EntityDecoder,
85 ) -> Option<Vec<Point3<f64>>> {
86 let coords = decoder.get_polyloop_coords_cached(loop_entity_id)?;
90
91 let polygon_points: Vec<Point3<f64>> = coords
93 .into_iter()
94 .map(|(x, y, z)| Point3::new(x, y, z))
95 .collect();
96
97 if polygon_points.len() >= 3 {
98 Some(polygon_points)
99 } else {
100 None
101 }
102 }
103
104 #[inline]
112 pub(super) fn triangulate_face(face: &FaceData, rtc: (f64, f64, f64)) -> FaceResult {
113 let n = face.outer_points.len();
114
115 if n == 3 && face.hole_points.is_empty() {
117 let mut positions = Vec::with_capacity(9);
118 for point in &face.outer_points {
119 positions.push((point.x - rtc.0) as f32);
120 positions.push((point.y - rtc.1) as f32);
121 positions.push((point.z - rtc.2) as f32);
122 }
123 return FaceResult {
124 positions,
125 indices: vec![0, 1, 2],
126 };
127 }
128
129 if n == 4 && face.hole_points.is_empty() {
131 let mut positions = Vec::with_capacity(12);
132 for point in &face.outer_points {
133 positions.push((point.x - rtc.0) as f32);
134 positions.push((point.y - rtc.1) as f32);
135 positions.push((point.z - rtc.2) as f32);
136 }
137 return FaceResult {
138 positions,
139 indices: vec![0, 1, 2, 0, 2, 3],
140 };
141 }
142
143 if face.hole_points.is_empty() && n <= 8 {
145 let mut is_convex = true;
147 if n > 4 {
148 use crate::triangulation::calculate_polygon_normal;
149 let normal = calculate_polygon_normal(&face.outer_points);
150 let mut sign = 0i8;
151
152 for i in 0..n {
153 let p0 = &face.outer_points[i];
154 let p1 = &face.outer_points[(i + 1) % n];
155 let p2 = &face.outer_points[(i + 2) % n];
156
157 let v1 = p1 - p0;
158 let v2 = p2 - p1;
159 let cross = v1.cross(&v2);
160 let dot = cross.dot(&normal);
161
162 if dot.abs() > 1e-10 {
163 let current_sign = if dot > 0.0 { 1i8 } else { -1i8 };
164 if sign == 0 {
165 sign = current_sign;
166 } else if sign != current_sign {
167 is_convex = false;
168 break;
169 }
170 }
171 }
172 }
173
174 if is_convex {
175 let mut positions = Vec::with_capacity(n * 3);
176 for point in &face.outer_points {
177 positions.push((point.x - rtc.0) as f32);
178 positions.push((point.y - rtc.1) as f32);
179 positions.push((point.z - rtc.2) as f32);
180 }
181 let mut indices = Vec::with_capacity((n - 2) * 3);
182 for i in 1..n - 1 {
183 indices.push(0);
184 indices.push(i as u32);
185 indices.push(i as u32 + 1);
186 }
187 return FaceResult { positions, indices };
188 }
189 }
190
191 use crate::triangulation::{
193 calculate_polygon_normal, project_to_2d, project_to_2d_with_basis,
194 triangulate_polygon_with_holes,
195 };
196
197 let mut positions = Vec::new();
198 let mut indices = Vec::new();
199
200 let normal = calculate_polygon_normal(&face.outer_points);
202
203 let (outer_2d, u_axis, v_axis, origin) = project_to_2d(&face.outer_points, &normal);
205
206 let holes_2d: Vec<Vec<nalgebra::Point2<f64>>> = face
208 .hole_points
209 .iter()
210 .map(|hole| project_to_2d_with_basis(hole, &u_axis, &v_axis, &origin))
211 .collect();
212
213 let tri_indices = match triangulate_polygon_with_holes(&outer_2d, &holes_2d) {
215 Ok(idx) => idx,
216 Err(_) => {
217 for point in &face.outer_points {
219 positions.push((point.x - rtc.0) as f32);
220 positions.push((point.y - rtc.1) as f32);
221 positions.push((point.z - rtc.2) as f32);
222 }
223 for i in 1..face.outer_points.len() - 1 {
224 indices.push(0);
225 indices.push(i as u32);
226 indices.push(i as u32 + 1);
227 }
228 return FaceResult { positions, indices };
229 }
230 };
231
232 let mut all_points_3d: Vec<&Point3<f64>> = face.outer_points.iter().collect();
234 for hole in &face.hole_points {
235 all_points_3d.extend(hole.iter());
236 }
237
238 for point in &all_points_3d {
240 positions.push((point.x - rtc.0) as f32);
241 positions.push((point.y - rtc.1) as f32);
242 positions.push((point.z - rtc.2) as f32);
243 }
244
245 for i in (0..tri_indices.len()).step_by(3) {
247 if i + 2 >= tri_indices.len() {
248 break;
249 }
250 indices.push(tri_indices[i] as u32);
251 indices.push(tri_indices[i + 1] as u32);
252 indices.push(tri_indices[i + 2] as u32);
253 }
254
255 FaceResult { positions, indices }
256 }
257}
258
259impl FacetedBrepProcessor {
260 fn process_rebased(
264 &self,
265 entity: &DecodedEntity,
266 decoder: &mut EntityDecoder,
267 rtc_file_units: Option<(f64, f64, f64)>,
268 ) -> Result<Mesh> {
269 use rayon::prelude::*;
270
271 let shell_attr = entity
274 .get(0)
275 .ok_or_else(|| Error::geometry("FacetedBrep missing Outer shell".to_string()))?;
276 let shell_id = shell_attr
277 .as_entity_ref()
278 .ok_or_else(|| Error::geometry("Expected entity ref for Outer shell".to_string()))?;
279
280 let face_ids = decoder
282 .get_entity_ref_list_fast(shell_id)
283 .ok_or_else(|| Error::geometry("Failed to get faces from ClosedShell".to_string()))?;
284
285 let mut face_data_list: Vec<FaceData> = Vec::with_capacity(face_ids.len());
287 for face_id in face_ids {
288 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
290 Some(ids) => ids,
291 None => continue,
292 };
293
294 let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
296 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
297 for bound_id in bound_ids {
298 let (loop_id, orientation, is_outer) = match decoder.get_face_bound_fast(bound_id) {
300 Some(data) => data,
301 None => continue,
302 };
303 let mut points = match self.extract_loop_points_fast(loop_id, decoder) {
304 Some(p) => p,
305 None => continue,
306 };
307 if !orientation {
308 points.reverse();
309 }
310 if is_outer || outer_bound_points.is_none() {
311 if outer_bound_points.is_some() && is_outer {
312 if let Some(prev_outer) = outer_bound_points.take() {
313 hole_points.push(prev_outer);
314 }
315 }
316 outer_bound_points = Some(points);
317 } else {
318 hole_points.push(points);
319 }
320 }
321 if let Some(outer_points) = outer_bound_points {
322 face_data_list.push(FaceData {
323 outer_points,
324 hole_points,
325 });
326 }
327 }
328
329 let rtc = rtc_file_units.unwrap_or((0.0, 0.0, 0.0));
332 let face_results: Vec<FaceResult> = if face_data_list.len() < PAR_FACE_THRESHOLD {
333 face_data_list
334 .iter()
335 .map(|face| Self::triangulate_face(face, rtc))
336 .collect()
337 } else {
338 face_data_list
339 .par_iter()
340 .map(|face| Self::triangulate_face(face, rtc))
341 .collect()
342 };
343
344 let total_positions: usize = face_results.iter().map(|r| r.positions.len()).sum();
346 let total_indices: usize = face_results.iter().map(|r| r.indices.len()).sum();
347 let mut positions = Vec::with_capacity(total_positions);
348 let mut indices = Vec::with_capacity(total_indices);
349 for result in face_results {
350 let base_idx = (positions.len() / 3) as u32;
351 positions.extend(result.positions);
352 for idx in result.indices {
353 indices.push(base_idx + idx);
354 }
355 }
356
357 Ok(Mesh {
358 positions,
359 normals: Vec::new(),
360 indices,
361 rtc_applied: rtc_file_units.is_some(),
362 origin: [0.0; 3],
363 instance_meta: None,
364 local_bounds: None,
365 local_to_world: None,
366 welded_in_object_frame: false,
367 plane_tags: None,
368 })
369 }
370}
371
372impl FacetedBrepProcessor {
373 pub fn process_with_rtc(
376 &self,
377 entity: &DecodedEntity,
378 decoder: &mut EntityDecoder,
379 _schema: &IfcSchema,
380 rtc: (f64, f64, f64),
381 ) -> Result<Mesh> {
382 self.process_rebased(entity, decoder, Some(rtc))
383 }
384}
385
386impl GeometryProcessor for FacetedBrepProcessor {
387 fn process(
388 &self,
389 entity: &DecodedEntity,
390 decoder: &mut EntityDecoder,
391 _schema: &IfcSchema,
392 _quality: TessellationQuality,
393 ) -> Result<Mesh> {
394 self.process_rebased(entity, decoder, None)
395 }
396
397 fn process_in_rtc_frame(
398 &self,
399 entity: &DecodedEntity,
400 decoder: &mut EntityDecoder,
401 schema: &IfcSchema,
402 _quality: TessellationQuality,
403 rtc_file_units: (f64, f64, f64),
404 ) -> Option<Result<Mesh>> {
405 Some(self.process_with_rtc(entity, decoder, schema, rtc_file_units))
406 }
407
408 fn supported_types(&self) -> Vec<IfcType> {
409 vec![IfcType::IfcFacetedBrep]
410 }
411}
412
413impl Default for FacetedBrepProcessor {
414 fn default() -> Self {
415 Self::new()
416 }
417}