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}
259
260impl FacetedBrepProcessor {
261 pub fn process_with_rtc(
265 &self,
266 entity: &DecodedEntity,
267 decoder: &mut EntityDecoder,
268 _schema: &IfcSchema,
269 rtc: (f64, f64, f64),
270 ) -> Result<Mesh> {
271 use rayon::prelude::*;
272
273 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 let face_ids = decoder
280 .get_entity_ref_list_fast(shell_id)
281 .ok_or_else(|| Error::geometry("Failed to get faces from ClosedShell".to_string()))?;
282
283 let mut face_data_list: Vec<FaceData> = Vec::with_capacity(face_ids.len());
284 for face_id in face_ids {
285 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
286 Some(ids) => ids,
287 None => continue,
288 };
289 let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
290 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
291 for bound_id in bound_ids {
292 let (loop_id, orientation, is_outer) = match decoder.get_face_bound_fast(bound_id) {
293 Some(data) => data,
294 None => continue,
295 };
296 let mut points = match self.extract_loop_points_fast(loop_id, decoder) {
297 Some(p) => p,
298 None => continue,
299 };
300 if !orientation {
301 points.reverse();
302 }
303 if is_outer || outer_bound_points.is_none() {
304 if outer_bound_points.is_some() && is_outer {
305 if let Some(prev_outer) = outer_bound_points.take() {
306 hole_points.push(prev_outer);
307 }
308 }
309 outer_bound_points = Some(points);
310 } else {
311 hole_points.push(points);
312 }
313 }
314 if let Some(outer_points) = outer_bound_points {
315 face_data_list.push(FaceData {
316 outer_points,
317 hole_points,
318 });
319 }
320 }
321
322 let face_results: Vec<FaceResult> = if face_data_list.len() < PAR_FACE_THRESHOLD {
324 face_data_list
325 .iter()
326 .map(|face| Self::triangulate_face(face, rtc))
327 .collect()
328 } else {
329 face_data_list
330 .par_iter()
331 .map(|face| Self::triangulate_face(face, rtc))
332 .collect()
333 };
334
335 let total_positions: usize = face_results.iter().map(|r| r.positions.len()).sum();
336 let total_indices: usize = face_results.iter().map(|r| r.indices.len()).sum();
337 let mut positions = Vec::with_capacity(total_positions);
338 let mut indices = Vec::with_capacity(total_indices);
339 for result in face_results {
340 let base_idx = (positions.len() / 3) as u32;
341 positions.extend(result.positions);
342 for idx in result.indices {
343 indices.push(base_idx + idx);
344 }
345 }
346 Ok(Mesh {
347 positions,
348 normals: Vec::new(),
349 indices,
350 rtc_applied: true, origin: [0.0; 3],
352 instance_meta: None, local_bounds: None, local_to_world: None })
353 }
354}
355
356impl GeometryProcessor for FacetedBrepProcessor {
357 fn process(
358 &self,
359 entity: &DecodedEntity,
360 decoder: &mut EntityDecoder,
361 _schema: &IfcSchema,
362 _quality: TessellationQuality,
363 ) -> Result<Mesh> {
364 use rayon::prelude::*;
365
366 let shell_attr = entity
371 .get(0)
372 .ok_or_else(|| Error::geometry("FacetedBrep missing Outer shell".to_string()))?;
373
374 let shell_id = shell_attr
375 .as_entity_ref()
376 .ok_or_else(|| Error::geometry("Expected entity ref for Outer shell".to_string()))?;
377
378 let face_ids = decoder
380 .get_entity_ref_list_fast(shell_id)
381 .ok_or_else(|| Error::geometry("Failed to get faces from ClosedShell".to_string()))?;
382
383 let mut face_data_list: Vec<FaceData> = Vec::with_capacity(face_ids.len());
385
386 for face_id in face_ids {
387 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
389 Some(ids) => ids,
390 None => continue,
391 };
392
393 let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
395 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
396
397 for bound_id in bound_ids {
398 let (loop_id, orientation, is_outer) = match decoder.get_face_bound_fast(bound_id) {
401 Some(data) => data,
402 None => continue,
403 };
404
405 let mut points = match self.extract_loop_points_fast(loop_id, decoder) {
407 Some(p) => p,
408 None => continue,
409 };
410
411 if !orientation {
412 points.reverse();
413 }
414
415 if is_outer || outer_bound_points.is_none() {
416 if outer_bound_points.is_some() && is_outer {
417 if let Some(prev_outer) = outer_bound_points.take() {
418 hole_points.push(prev_outer);
419 }
420 }
421 outer_bound_points = Some(points);
422 } else {
423 hole_points.push(points);
424 }
425 }
426
427 if let Some(outer_points) = outer_bound_points {
428 face_data_list.push(FaceData {
429 outer_points,
430 hole_points,
431 });
432 }
433 }
434
435 let face_results: Vec<FaceResult> = if face_data_list.len() < PAR_FACE_THRESHOLD {
440 face_data_list
441 .iter()
442 .map(|face| Self::triangulate_face(face, (0.0, 0.0, 0.0)))
443 .collect()
444 } else {
445 face_data_list
446 .par_iter()
447 .map(|face| Self::triangulate_face(face, (0.0, 0.0, 0.0)))
448 .collect()
449 };
450
451 let total_positions: usize = face_results.iter().map(|r| r.positions.len()).sum();
454 let total_indices: usize = face_results.iter().map(|r| r.indices.len()).sum();
455
456 let mut positions = Vec::with_capacity(total_positions);
457 let mut indices = Vec::with_capacity(total_indices);
458
459 for result in face_results {
460 let base_idx = (positions.len() / 3) as u32;
461 positions.extend(result.positions);
462
463 for idx in result.indices {
465 indices.push(base_idx + idx);
466 }
467 }
468
469 Ok(Mesh {
470 positions,
471 normals: Vec::new(),
472 indices,
473 rtc_applied: false,
474 origin: [0.0; 3], instance_meta: None, local_bounds: None, local_to_world: None })
475 }
476
477 fn supported_types(&self) -> Vec<IfcType> {
478 vec![IfcType::IfcFacetedBrep]
479 }
480}
481
482impl Default for FacetedBrepProcessor {
483 fn default() -> Self {
484 Self::new()
485 }
486}