ifc_lite_geometry/processors/
brep.rs1use crate::{Error, Mesh, Point3, Result};
11use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
12
13use crate::router::GeometryProcessor;
14use super::helpers::{extract_loop_points_by_id, FaceData, FaceResult};
15
16pub struct FacetedBrepProcessor;
23
24impl FacetedBrepProcessor {
25 pub fn new() -> Self {
26 Self
27 }
28
29 #[allow(dead_code)]
32 #[inline]
33 fn extract_loop_points(
34 &self,
35 loop_entity: &DecodedEntity,
36 decoder: &mut EntityDecoder,
37 ) -> Option<Vec<Point3<f64>>> {
38 let polygon_attr = loop_entity.get(0)?;
40
41 let point_refs = polygon_attr.as_list()?;
43
44 let mut polygon_points = Vec::with_capacity(point_refs.len());
46
47 for point_ref in point_refs {
48 let point_id = point_ref.as_entity_ref()?;
49
50 if let Some((x, y, z)) = decoder.get_cartesian_point_fast(point_id) {
52 polygon_points.push(Point3::new(x, y, z));
53 } else {
54 let point = decoder.decode_by_id(point_id).ok()?;
56 let coords_attr = point.get(0)?;
57 let coords = coords_attr.as_list()?;
58 use ifc_lite_core::AttributeValue;
59 let x = coords.first().and_then(|v: &AttributeValue| v.as_float())?;
60 let y = coords.get(1).and_then(|v: &AttributeValue| v.as_float())?;
61 let z = coords.get(2).and_then(|v: &AttributeValue| v.as_float())?;
62 polygon_points.push(Point3::new(x, y, z));
63 }
64 }
65
66 if polygon_points.len() >= 3 {
67 Some(polygon_points)
68 } else {
69 None
70 }
71 }
72
73 #[inline]
77 fn extract_loop_points_fast(
78 &self,
79 loop_entity_id: u32,
80 decoder: &mut EntityDecoder,
81 ) -> Option<Vec<Point3<f64>>> {
82 let coords = decoder.get_polyloop_coords_cached(loop_entity_id)?;
86
87 let polygon_points: Vec<Point3<f64>> = coords
89 .into_iter()
90 .map(|(x, y, z)| Point3::new(x, y, z))
91 .collect();
92
93 if polygon_points.len() >= 3 {
94 Some(polygon_points)
95 } else {
96 None
97 }
98 }
99
100 #[inline]
103 fn triangulate_face(face: &FaceData) -> FaceResult {
104 let n = face.outer_points.len();
105
106 if n == 3 && face.hole_points.is_empty() {
108 let mut positions = Vec::with_capacity(9);
109 for point in &face.outer_points {
110 positions.push(point.x as f32);
111 positions.push(point.y as f32);
112 positions.push(point.z as f32);
113 }
114 return FaceResult {
115 positions,
116 indices: vec![0, 1, 2],
117 };
118 }
119
120 if n == 4 && face.hole_points.is_empty() {
122 let mut positions = Vec::with_capacity(12);
123 for point in &face.outer_points {
124 positions.push(point.x as f32);
125 positions.push(point.y as f32);
126 positions.push(point.z as f32);
127 }
128 return FaceResult {
129 positions,
130 indices: vec![0, 1, 2, 0, 2, 3],
131 };
132 }
133
134 if face.hole_points.is_empty() && n <= 8 {
136 let mut is_convex = true;
138 if n > 4 {
139 use crate::triangulation::calculate_polygon_normal;
140 let normal = calculate_polygon_normal(&face.outer_points);
141 let mut sign = 0i8;
142
143 for i in 0..n {
144 let p0 = &face.outer_points[i];
145 let p1 = &face.outer_points[(i + 1) % n];
146 let p2 = &face.outer_points[(i + 2) % n];
147
148 let v1 = p1 - p0;
149 let v2 = p2 - p1;
150 let cross = v1.cross(&v2);
151 let dot = cross.dot(&normal);
152
153 if dot.abs() > 1e-10 {
154 let current_sign = if dot > 0.0 { 1i8 } else { -1i8 };
155 if sign == 0 {
156 sign = current_sign;
157 } else if sign != current_sign {
158 is_convex = false;
159 break;
160 }
161 }
162 }
163 }
164
165 if is_convex {
166 let mut positions = Vec::with_capacity(n * 3);
167 for point in &face.outer_points {
168 positions.push(point.x as f32);
169 positions.push(point.y as f32);
170 positions.push(point.z as f32);
171 }
172 let mut indices = Vec::with_capacity((n - 2) * 3);
173 for i in 1..n - 1 {
174 indices.push(0);
175 indices.push(i as u32);
176 indices.push(i as u32 + 1);
177 }
178 return FaceResult { positions, indices };
179 }
180 }
181
182 use crate::triangulation::{
184 calculate_polygon_normal, project_to_2d, project_to_2d_with_basis,
185 triangulate_polygon_with_holes,
186 };
187
188 let mut positions = Vec::new();
189 let mut indices = Vec::new();
190
191 let normal = calculate_polygon_normal(&face.outer_points);
193
194 let (outer_2d, u_axis, v_axis, origin) = project_to_2d(&face.outer_points, &normal);
196
197 let holes_2d: Vec<Vec<nalgebra::Point2<f64>>> = face
199 .hole_points
200 .iter()
201 .map(|hole| project_to_2d_with_basis(hole, &u_axis, &v_axis, &origin))
202 .collect();
203
204 let tri_indices = match triangulate_polygon_with_holes(&outer_2d, &holes_2d) {
206 Ok(idx) => idx,
207 Err(_) => {
208 for point in &face.outer_points {
210 positions.push(point.x as f32);
211 positions.push(point.y as f32);
212 positions.push(point.z as f32);
213 }
214 for i in 1..face.outer_points.len() - 1 {
215 indices.push(0);
216 indices.push(i as u32);
217 indices.push(i as u32 + 1);
218 }
219 return FaceResult { positions, indices };
220 }
221 };
222
223 let mut all_points_3d: Vec<&Point3<f64>> = face.outer_points.iter().collect();
225 for hole in &face.hole_points {
226 all_points_3d.extend(hole.iter());
227 }
228
229 for point in &all_points_3d {
231 positions.push(point.x as f32);
232 positions.push(point.y as f32);
233 positions.push(point.z as f32);
234 }
235
236 for i in (0..tri_indices.len()).step_by(3) {
238 if i + 2 >= tri_indices.len() {
239 break;
240 }
241 indices.push(tri_indices[i] as u32);
242 indices.push(tri_indices[i + 1] as u32);
243 indices.push(tri_indices[i + 2] as u32);
244 }
245
246 FaceResult { positions, indices }
247 }
248
249 pub fn process_batch(
253 &self,
254 brep_ids: &[u32],
255 decoder: &mut EntityDecoder,
256 ) -> Vec<(usize, Mesh)> {
257 #[cfg(not(target_arch = "wasm32"))]
258 use rayon::prelude::*;
259
260 let mut all_faces: Vec<(usize, FaceData)> = Vec::with_capacity(brep_ids.len() * 10);
263
264 for (brep_idx, &brep_id) in brep_ids.iter().enumerate() {
265 let shell_id = match decoder.get_first_entity_ref_fast(brep_id) {
267 Some(id) => id,
268 None => continue,
269 };
270
271 let face_ids = match decoder.get_entity_ref_list_fast(shell_id) {
273 Some(ids) => ids,
274 None => continue,
275 };
276
277 for face_id in face_ids {
279 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
280 Some(ids) => ids,
281 None => continue,
282 };
283
284 let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
285 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
286
287 for bound_id in bound_ids {
288 let (loop_id, orientation, is_outer) =
291 match decoder.get_face_bound_fast(bound_id) {
292 Some(data) => data,
293 None => continue,
294 };
295
296 let mut points = match self.extract_loop_points_fast(loop_id, decoder) {
298 Some(p) => p,
299 None => continue,
300 };
301
302 if !orientation {
303 points.reverse();
304 }
305
306 if is_outer || outer_bound_points.is_none() {
307 if outer_bound_points.is_some() && is_outer {
308 if let Some(prev_outer) = outer_bound_points.take() {
309 hole_points.push(prev_outer);
310 }
311 }
312 outer_bound_points = Some(points);
313 } else {
314 hole_points.push(points);
315 }
316 }
317
318 if let Some(outer_points) = outer_bound_points {
319 all_faces.push((
320 brep_idx,
321 FaceData {
322 outer_points,
323 hole_points,
324 },
325 ));
326 }
327 }
328 }
329
330 #[cfg(not(target_arch = "wasm32"))]
334 let face_results: Vec<(usize, FaceResult)> = all_faces
335 .par_iter()
336 .map(|(brep_idx, face)| (*brep_idx, Self::triangulate_face(face)))
337 .collect();
338
339 #[cfg(target_arch = "wasm32")]
340 let face_results: Vec<(usize, FaceResult)> = all_faces
341 .iter()
342 .map(|(brep_idx, face)| (*brep_idx, Self::triangulate_face(face)))
343 .collect();
344
345 let mut face_counts = vec![0usize; brep_ids.len()];
348 for (brep_idx, _) in &face_results {
349 face_counts[*brep_idx] += 1;
350 }
351
352 let mut mesh_builders: Vec<(Vec<f32>, Vec<u32>)> = face_counts
354 .iter()
355 .map(|&count| {
356 (
357 Vec::with_capacity(count * 100),
358 Vec::with_capacity(count * 50),
359 )
360 })
361 .collect();
362
363 for (brep_idx, result) in face_results {
365 let (positions, indices) = &mut mesh_builders[brep_idx];
366 let base_idx = (positions.len() / 3) as u32;
367 positions.extend(result.positions);
368 for idx in result.indices {
369 indices.push(base_idx + idx);
370 }
371 }
372
373 mesh_builders
375 .into_iter()
376 .enumerate()
377 .filter(|(_, (positions, _))| !positions.is_empty())
378 .map(|(brep_idx, (positions, indices))| {
379 (
380 brep_idx,
381 Mesh {
382 positions,
383 normals: Vec::new(),
384 indices,
385 },
386 )
387 })
388 .collect()
389 }
390}
391
392impl GeometryProcessor for FacetedBrepProcessor {
393 fn process(
394 &self,
395 entity: &DecodedEntity,
396 decoder: &mut EntityDecoder,
397 _schema: &IfcSchema,
398 ) -> Result<Mesh> {
399 #[cfg(not(target_arch = "wasm32"))]
400 use rayon::prelude::*;
401
402 let shell_attr = entity
407 .get(0)
408 .ok_or_else(|| Error::geometry("FacetedBrep missing Outer shell".to_string()))?;
409
410 let shell_id = shell_attr
411 .as_entity_ref()
412 .ok_or_else(|| Error::geometry("Expected entity ref for Outer shell".to_string()))?;
413
414 let face_ids = decoder
416 .get_entity_ref_list_fast(shell_id)
417 .ok_or_else(|| Error::geometry("Failed to get faces from ClosedShell".to_string()))?;
418
419 let mut face_data_list: Vec<FaceData> = Vec::with_capacity(face_ids.len());
421
422 for face_id in face_ids {
423 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
425 Some(ids) => ids,
426 None => continue,
427 };
428
429 let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
431 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
432
433 for bound_id in bound_ids {
434 let (loop_id, orientation, is_outer) =
437 match decoder.get_face_bound_fast(bound_id) {
438 Some(data) => data,
439 None => continue,
440 };
441
442 let mut points = match self.extract_loop_points_fast(loop_id, decoder) {
444 Some(p) => p,
445 None => continue,
446 };
447
448 if !orientation {
449 points.reverse();
450 }
451
452 if is_outer || outer_bound_points.is_none() {
453 if outer_bound_points.is_some() && is_outer {
454 if let Some(prev_outer) = outer_bound_points.take() {
455 hole_points.push(prev_outer);
456 }
457 }
458 outer_bound_points = Some(points);
459 } else {
460 hole_points.push(points);
461 }
462 }
463
464 if let Some(outer_points) = outer_bound_points {
465 face_data_list.push(FaceData {
466 outer_points,
467 hole_points,
468 });
469 }
470 }
471
472 #[cfg(not(target_arch = "wasm32"))]
476 let face_results: Vec<FaceResult> = face_data_list
477 .par_iter()
478 .map(Self::triangulate_face)
479 .collect();
480
481 #[cfg(target_arch = "wasm32")]
482 let face_results: Vec<FaceResult> = face_data_list
483 .iter()
484 .map(Self::triangulate_face)
485 .collect();
486
487 let total_positions: usize = face_results.iter().map(|r| r.positions.len()).sum();
490 let total_indices: usize = face_results.iter().map(|r| r.indices.len()).sum();
491
492 let mut positions = Vec::with_capacity(total_positions);
493 let mut indices = Vec::with_capacity(total_indices);
494
495 for result in face_results {
496 let base_idx = (positions.len() / 3) as u32;
497 positions.extend(result.positions);
498
499 for idx in result.indices {
501 indices.push(base_idx + idx);
502 }
503 }
504
505 Ok(Mesh {
506 positions,
507 normals: Vec::new(),
508 indices,
509 })
510 }
511
512 fn supported_types(&self) -> Vec<IfcType> {
513 vec![IfcType::IfcFacetedBrep]
514 }
515}
516
517impl Default for FacetedBrepProcessor {
518 fn default() -> Self {
519 Self::new()
520 }
521}
522
523pub struct FaceBasedSurfaceModelProcessor;
529
530impl FaceBasedSurfaceModelProcessor {
531 pub fn new() -> Self {
532 Self
533 }
534}
535
536impl GeometryProcessor for FaceBasedSurfaceModelProcessor {
537 fn process(
538 &self,
539 entity: &DecodedEntity,
540 decoder: &mut EntityDecoder,
541 _schema: &IfcSchema,
542 ) -> Result<Mesh> {
543 let faces_attr = entity
547 .get(0)
548 .ok_or_else(|| Error::geometry("FaceBasedSurfaceModel missing FbsmFaces".to_string()))?;
549
550 let face_set_refs = faces_attr
551 .as_list()
552 .ok_or_else(|| Error::geometry("Expected face set list".to_string()))?;
553
554 let mut all_positions = Vec::new();
555 let mut all_indices = Vec::new();
556
557 for face_set_ref in face_set_refs {
559 let face_set_id = face_set_ref.as_entity_ref().ok_or_else(|| {
560 Error::geometry("Expected entity reference for face set".to_string())
561 })?;
562
563 let face_ids = match decoder.get_entity_ref_list_fast(face_set_id) {
565 Some(ids) => ids,
566 None => continue,
567 };
568
569 for face_id in face_ids {
571 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
573 Some(ids) => ids,
574 None => continue,
575 };
576
577 let mut outer_points: Option<Vec<Point3<f64>>> = None;
578 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
579
580 for bound_id in bound_ids {
581 let (loop_id, orientation, is_outer) =
584 match decoder.get_face_bound_fast(bound_id) {
585 Some(data) => data,
586 None => continue,
587 };
588
589 let mut points = match extract_loop_points_by_id(loop_id, decoder) {
591 Some(p) => p,
592 None => continue,
593 };
594
595 if !orientation {
596 points.reverse();
597 }
598
599 if is_outer || outer_points.is_none() {
600 outer_points = Some(points);
601 } else {
602 hole_points.push(points);
603 }
604 }
605
606 if let Some(outer) = outer_points {
608 if outer.len() >= 3 {
609 let base_idx = (all_positions.len() / 3) as u32;
610
611 for p in &outer {
613 all_positions.push(p.x as f32);
614 all_positions.push(p.y as f32);
615 all_positions.push(p.z as f32);
616 }
617
618 for i in 1..outer.len() - 1 {
620 all_indices.push(base_idx);
621 all_indices.push(base_idx + i as u32);
622 all_indices.push(base_idx + i as u32 + 1);
623 }
624 }
625 }
626 }
627 }
628
629 Ok(Mesh {
630 positions: all_positions,
631 normals: Vec::new(),
632 indices: all_indices,
633 })
634 }
635
636 fn supported_types(&self) -> Vec<IfcType> {
637 vec![IfcType::IfcFaceBasedSurfaceModel]
638 }
639}
640
641impl Default for FaceBasedSurfaceModelProcessor {
642 fn default() -> Self {
643 Self::new()
644 }
645}
646
647pub struct ShellBasedSurfaceModelProcessor;
653
654impl ShellBasedSurfaceModelProcessor {
655 pub fn new() -> Self {
656 Self
657 }
658}
659
660impl GeometryProcessor for ShellBasedSurfaceModelProcessor {
661 fn process(
662 &self,
663 entity: &DecodedEntity,
664 decoder: &mut EntityDecoder,
665 _schema: &IfcSchema,
666 ) -> Result<Mesh> {
667 let shells_attr = entity
671 .get(0)
672 .ok_or_else(|| Error::geometry("ShellBasedSurfaceModel missing SbsmBoundary".to_string()))?;
673
674 let shell_refs = shells_attr
675 .as_list()
676 .ok_or_else(|| Error::geometry("Expected shell list".to_string()))?;
677
678 let mut all_positions = Vec::new();
679 let mut all_indices = Vec::new();
680
681 for shell_ref in shell_refs {
683 let shell_id = shell_ref.as_entity_ref().ok_or_else(|| {
684 Error::geometry("Expected entity reference for shell".to_string())
685 })?;
686
687 let face_ids = match decoder.get_entity_ref_list_fast(shell_id) {
690 Some(ids) => ids,
691 None => continue,
692 };
693
694 for face_id in face_ids {
696 let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
698 Some(ids) => ids,
699 None => continue,
700 };
701
702 let mut outer_points: Option<Vec<Point3<f64>>> = None;
703 let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
704
705 for bound_id in bound_ids {
706 let (loop_id, orientation, is_outer) =
708 match decoder.get_face_bound_fast(bound_id) {
709 Some(data) => data,
710 None => continue,
711 };
712
713 let mut points = match extract_loop_points_by_id(loop_id, decoder) {
715 Some(p) => p,
716 None => continue,
717 };
718
719 if !orientation {
720 points.reverse();
721 }
722
723 if is_outer || outer_points.is_none() {
724 outer_points = Some(points);
725 } else {
726 hole_points.push(points);
727 }
728 }
729
730 if let Some(outer) = outer_points {
732 if outer.len() >= 3 {
733 let base_idx = (all_positions.len() / 3) as u32;
734
735 for p in &outer {
737 all_positions.push(p.x as f32);
738 all_positions.push(p.y as f32);
739 all_positions.push(p.z as f32);
740 }
741
742 for i in 1..outer.len() - 1 {
744 all_indices.push(base_idx);
745 all_indices.push(base_idx + i as u32);
746 all_indices.push(base_idx + i as u32 + 1);
747 }
748 }
749 }
750 }
751 }
752
753 Ok(Mesh {
754 positions: all_positions,
755 normals: Vec::new(),
756 indices: all_indices,
757 })
758 }
759
760 fn supported_types(&self) -> Vec<IfcType> {
761 vec![IfcType::IfcShellBasedSurfaceModel]
762 }
763}
764
765impl Default for ShellBasedSurfaceModelProcessor {
766 fn default() -> Self {
767 Self::new()
768 }
769}