Skip to main content

ifc_lite_geometry/processors/
brep.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! BRep/surface model processors.
6//!
7//! Handles IfcFacetedBrep, IfcFaceBasedSurfaceModel, and IfcShellBasedSurfaceModel.
8//! All deal with boundary representations composed of face loops.
9
10use 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
16// ---------- FacetedBrepProcessor ----------
17
18/// FacetedBrep processor
19/// Handles IfcFacetedBrep - explicit mesh with faces
20/// Supports faces with inner bounds (holes)
21/// Uses parallel triangulation for large BREPs
22pub struct FacetedBrepProcessor;
23
24impl FacetedBrepProcessor {
25    pub fn new() -> Self {
26        Self
27    }
28
29    /// Extract polygon points from a loop entity
30    /// Uses fast path for CartesianPoint extraction to avoid decode overhead
31    #[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        // Try to get Polygon attribute (attribute 0) - IfcPolyLoop has this
39        let polygon_attr = loop_entity.get(0)?;
40
41        // Get the list of point references directly
42        let point_refs = polygon_attr.as_list()?;
43
44        // Pre-allocate with known size
45        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            // Try fast path first
51            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                // Fallback to standard path if fast extraction fails
55                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    /// Extract polygon points using ultra-fast path from loop entity ID
74    /// Uses cached coordinate extraction - points are cached across faces
75    /// This is the fastest path for files with shared cartesian points
76    #[inline]
77    fn extract_loop_points_fast(
78        &self,
79        loop_entity_id: u32,
80        decoder: &mut EntityDecoder,
81    ) -> Option<Vec<Point3<f64>>> {
82        // ULTRA-FAST PATH with CACHING: Get coordinates with point cache
83        // Many faces share the same cartesian points, so caching avoids
84        // re-parsing the same point data multiple times
85        let coords = decoder.get_polyloop_coords_cached(loop_entity_id)?;
86
87        // Convert to Point3 - pre-allocated in get_polyloop_coords_cached
88        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    /// Triangulate a single face (can be called in parallel)
101    /// Optimized with fast paths for simple faces
102    #[inline]
103    fn triangulate_face(face: &FaceData) -> FaceResult {
104        let n = face.outer_points.len();
105
106        // FAST PATH: Triangle without holes - no triangulation needed
107        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        // FAST PATH: Quad without holes - simple fan
121        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        // FAST PATH: Simple convex polygon without holes
135        if face.hole_points.is_empty() && n <= 8 {
136            // Check if convex by testing cross products in 3D
137            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        // SLOW PATH: Complex polygon or polygon with holes
183        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        // Calculate face normal from outer boundary
192        let normal = calculate_polygon_normal(&face.outer_points);
193
194        // Project outer boundary to 2D and get the coordinate system
195        let (outer_2d, u_axis, v_axis, origin) = project_to_2d(&face.outer_points, &normal);
196
197        // Project holes to 2D using the SAME coordinate system as the outer boundary
198        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        // Triangulate with holes
205        let tri_indices = match triangulate_polygon_with_holes(&outer_2d, &holes_2d) {
206            Ok(idx) => idx,
207            Err(_) => {
208                // Fallback to simple fan triangulation without holes
209                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        // Combine all 3D points (outer + holes) in the same order as 2D
224        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        // Add vertices
230        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        // Add triangle indices
237        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    /// Batch process multiple FacetedBrep entities for maximum parallelism
250    /// Extracts all face data sequentially, then triangulates ALL faces in one parallel batch
251    /// Returns Vec of (brep_index, Mesh) pairs
252    pub fn process_batch(
253        &self,
254        brep_ids: &[u32],
255        decoder: &mut EntityDecoder,
256    ) -> Vec<(usize, Mesh)> {
257        use rayon::prelude::*;
258
259        // PHASE 1: Sequential - Extract all face data from all BREPs
260        // Each entry: (brep_index, face_data)
261        let mut all_faces: Vec<(usize, FaceData)> = Vec::with_capacity(brep_ids.len() * 10);
262
263        for (brep_idx, &brep_id) in brep_ids.iter().enumerate() {
264            // FAST PATH: Get shell ID directly from raw bytes (avoids full entity decode)
265            let shell_id = match decoder.get_first_entity_ref_fast(brep_id) {
266                Some(id) => id,
267                None => continue,
268            };
269
270            // FAST PATH: Get face IDs from shell using raw bytes
271            let face_ids = match decoder.get_entity_ref_list_fast(shell_id) {
272                Some(ids) => ids,
273                None => continue,
274            };
275
276            // Extract face data for each face
277            for face_id in face_ids {
278                let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
279                    Some(ids) => ids,
280                    None => continue,
281                };
282
283                let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
284                let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
285
286                for bound_id in bound_ids {
287                    // FAST PATH: Extract loop_id, orientation, is_outer from raw bytes
288                    // get_face_bound_fast returns (loop_id, orientation, is_outer)
289                    let (loop_id, orientation, is_outer) =
290                        match decoder.get_face_bound_fast(bound_id) {
291                            Some(data) => data,
292                            None => continue,
293                        };
294
295                    // FAST PATH: Get loop points directly from entity ID
296                    let mut points = match self.extract_loop_points_fast(loop_id, decoder) {
297                        Some(p) => p,
298                        None => continue,
299                    };
300
301                    if !orientation {
302                        points.reverse();
303                    }
304
305                    if is_outer || outer_bound_points.is_none() {
306                        if outer_bound_points.is_some() && is_outer {
307                            if let Some(prev_outer) = outer_bound_points.take() {
308                                hole_points.push(prev_outer);
309                            }
310                        }
311                        outer_bound_points = Some(points);
312                    } else {
313                        hole_points.push(points);
314                    }
315                }
316
317                if let Some(outer_points) = outer_bound_points {
318                    all_faces.push((
319                        brep_idx,
320                        FaceData {
321                            outer_points,
322                            hole_points,
323                        },
324                    ));
325                }
326            }
327        }
328
329        // PHASE 2: Triangulate ALL faces from ALL BREPs in one parallel batch
330        // Uses rayon thread pool on both native and WASM (via wasm-bindgen-rayon)
331        let face_results: Vec<(usize, FaceResult)> = all_faces
332            .par_iter()
333            .map(|(brep_idx, face)| (*brep_idx, Self::triangulate_face(face)))
334            .collect();
335
336        // PHASE 3: Group results back by BREP index
337        // First, count faces per BREP to pre-allocate
338        let mut face_counts = vec![0usize; brep_ids.len()];
339        for (brep_idx, _) in &face_results {
340            face_counts[*brep_idx] += 1;
341        }
342
343        // Initialize mesh builders for each BREP
344        let mut mesh_builders: Vec<(Vec<f32>, Vec<u32>)> = face_counts
345            .iter()
346            .map(|&count| {
347                (
348                    Vec::with_capacity(count * 100),
349                    Vec::with_capacity(count * 50),
350                )
351            })
352            .collect();
353
354        // Merge face results into their respective meshes
355        for (brep_idx, result) in face_results {
356            let (positions, indices) = &mut mesh_builders[brep_idx];
357            let base_idx = (positions.len() / 3) as u32;
358            positions.extend(result.positions);
359            for idx in result.indices {
360                indices.push(base_idx + idx);
361            }
362        }
363
364        // Convert to final meshes
365        mesh_builders
366            .into_iter()
367            .enumerate()
368            .filter(|(_, (positions, _))| !positions.is_empty())
369            .map(|(brep_idx, (positions, indices))| {
370                (
371                    brep_idx,
372                    Mesh {
373                        positions,
374                        normals: Vec::new(),
375                        indices,
376                    },
377                )
378            })
379            .collect()
380    }
381}
382
383impl GeometryProcessor for FacetedBrepProcessor {
384    fn process(
385        &self,
386        entity: &DecodedEntity,
387        decoder: &mut EntityDecoder,
388        _schema: &IfcSchema,
389    ) -> Result<Mesh> {
390        use rayon::prelude::*;
391
392        // IfcFacetedBrep attributes:
393        // 0: Outer (IfcClosedShell)
394
395        // Get closed shell ID
396        let shell_attr = entity
397            .get(0)
398            .ok_or_else(|| Error::geometry("FacetedBrep missing Outer shell".to_string()))?;
399
400        let shell_id = shell_attr
401            .as_entity_ref()
402            .ok_or_else(|| Error::geometry("Expected entity ref for Outer shell".to_string()))?;
403
404        // FAST PATH: Get face IDs directly from ClosedShell raw bytes
405        let face_ids = decoder
406            .get_entity_ref_list_fast(shell_id)
407            .ok_or_else(|| Error::geometry("Failed to get faces from ClosedShell".to_string()))?;
408
409        // PHASE 1: Sequential - Extract all face data from IFC entities
410        let mut face_data_list: Vec<FaceData> = Vec::with_capacity(face_ids.len());
411
412        for face_id in face_ids {
413            // FAST PATH: Get bound IDs directly from Face raw bytes
414            let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
415                Some(ids) => ids,
416                None => continue,
417            };
418
419            // Separate outer bound from inner bounds (holes)
420            let mut outer_bound_points: Option<Vec<Point3<f64>>> = None;
421            let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
422
423            for bound_id in bound_ids {
424                // FAST PATH: Extract loop_id, orientation, is_outer from raw bytes
425                // get_face_bound_fast returns (loop_id, orientation, is_outer)
426                let (loop_id, orientation, is_outer) =
427                    match decoder.get_face_bound_fast(bound_id) {
428                        Some(data) => data,
429                        None => continue,
430                    };
431
432                // FAST PATH: Get loop points directly from entity ID
433                let mut points = match self.extract_loop_points_fast(loop_id, decoder) {
434                    Some(p) => p,
435                    None => continue,
436                };
437
438                if !orientation {
439                    points.reverse();
440                }
441
442                if is_outer || outer_bound_points.is_none() {
443                    if outer_bound_points.is_some() && is_outer {
444                        if let Some(prev_outer) = outer_bound_points.take() {
445                            hole_points.push(prev_outer);
446                        }
447                    }
448                    outer_bound_points = Some(points);
449                } else {
450                    hole_points.push(points);
451                }
452            }
453
454            if let Some(outer_points) = outer_bound_points {
455                face_data_list.push(FaceData {
456                    outer_points,
457                    hole_points,
458                });
459            }
460        }
461
462        // PHASE 2: Triangulate all faces in parallel (via rayon on both native and WASM)
463        let face_results: Vec<FaceResult> = face_data_list
464            .par_iter()
465            .map(Self::triangulate_face)
466            .collect();
467
468        // PHASE 3: Sequential - Merge all face results into final mesh
469        // Pre-calculate total sizes for efficient allocation
470        let total_positions: usize = face_results.iter().map(|r| r.positions.len()).sum();
471        let total_indices: usize = face_results.iter().map(|r| r.indices.len()).sum();
472
473        let mut positions = Vec::with_capacity(total_positions);
474        let mut indices = Vec::with_capacity(total_indices);
475
476        for result in face_results {
477            let base_idx = (positions.len() / 3) as u32;
478            positions.extend(result.positions);
479
480            // Offset indices by base
481            for idx in result.indices {
482                indices.push(base_idx + idx);
483            }
484        }
485
486        Ok(Mesh {
487            positions,
488            normals: Vec::new(),
489            indices,
490        })
491    }
492
493    fn supported_types(&self) -> Vec<IfcType> {
494        vec![IfcType::IfcFacetedBrep]
495    }
496}
497
498impl Default for FacetedBrepProcessor {
499    fn default() -> Self {
500        Self::new()
501    }
502}
503
504// ---------- FaceBasedSurfaceModelProcessor ----------
505
506/// FaceBasedSurfaceModel processor
507/// Handles IfcFaceBasedSurfaceModel - surface model made of connected face sets
508/// Structure: FaceBasedSurfaceModel -> ConnectedFaceSet[] -> Face[] -> FaceBound -> PolyLoop
509pub struct FaceBasedSurfaceModelProcessor;
510
511impl FaceBasedSurfaceModelProcessor {
512    pub fn new() -> Self {
513        Self
514    }
515}
516
517impl GeometryProcessor for FaceBasedSurfaceModelProcessor {
518    fn process(
519        &self,
520        entity: &DecodedEntity,
521        decoder: &mut EntityDecoder,
522        _schema: &IfcSchema,
523    ) -> Result<Mesh> {
524        // IfcFaceBasedSurfaceModel attributes:
525        // 0: FbsmFaces (SET of IfcConnectedFaceSet)
526
527        let faces_attr = entity
528            .get(0)
529            .ok_or_else(|| Error::geometry("FaceBasedSurfaceModel missing FbsmFaces".to_string()))?;
530
531        let face_set_refs = faces_attr
532            .as_list()
533            .ok_or_else(|| Error::geometry("Expected face set list".to_string()))?;
534
535        let mut all_positions = Vec::new();
536        let mut all_indices = Vec::new();
537
538        // Process each connected face set
539        for face_set_ref in face_set_refs {
540            let face_set_id = face_set_ref.as_entity_ref().ok_or_else(|| {
541                Error::geometry("Expected entity reference for face set".to_string())
542            })?;
543
544            // Get face IDs from ConnectedFaceSet
545            let face_ids = match decoder.get_entity_ref_list_fast(face_set_id) {
546                Some(ids) => ids,
547                None => continue,
548            };
549
550            // Process each face in the set
551            for face_id in face_ids {
552                // Get bound IDs from Face
553                let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
554                    Some(ids) => ids,
555                    None => continue,
556                };
557
558                let mut outer_points: Option<Vec<Point3<f64>>> = None;
559                let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
560
561                for bound_id in bound_ids {
562                    // FAST PATH: Extract loop_id, orientation, is_outer from raw bytes
563                    // get_face_bound_fast returns (loop_id, orientation, is_outer)
564                    let (loop_id, orientation, is_outer) =
565                        match decoder.get_face_bound_fast(bound_id) {
566                            Some(data) => data,
567                            None => continue,
568                        };
569
570                    // Get loop points using shared helper
571                    let mut points = match extract_loop_points_by_id(loop_id, decoder) {
572                        Some(p) => p,
573                        None => continue,
574                    };
575
576                    if !orientation {
577                        points.reverse();
578                    }
579
580                    if is_outer || outer_points.is_none() {
581                        outer_points = Some(points);
582                    } else {
583                        hole_points.push(points);
584                    }
585                }
586
587                // Triangulate the face
588                if let Some(outer) = outer_points {
589                    if outer.len() >= 3 {
590                        let base_idx = (all_positions.len() / 3) as u32;
591
592                        // Add positions
593                        for p in &outer {
594                            all_positions.push(p.x as f32);
595                            all_positions.push(p.y as f32);
596                            all_positions.push(p.z as f32);
597                        }
598
599                        // Simple fan triangulation (works for convex faces)
600                        for i in 1..outer.len() - 1 {
601                            all_indices.push(base_idx);
602                            all_indices.push(base_idx + i as u32);
603                            all_indices.push(base_idx + i as u32 + 1);
604                        }
605                    }
606                }
607            }
608        }
609
610        Ok(Mesh {
611            positions: all_positions,
612            normals: Vec::new(),
613            indices: all_indices,
614        })
615    }
616
617    fn supported_types(&self) -> Vec<IfcType> {
618        vec![IfcType::IfcFaceBasedSurfaceModel]
619    }
620}
621
622impl Default for FaceBasedSurfaceModelProcessor {
623    fn default() -> Self {
624        Self::new()
625    }
626}
627
628// ---------- ShellBasedSurfaceModelProcessor ----------
629
630/// ShellBasedSurfaceModel processor
631/// Handles IfcShellBasedSurfaceModel - surface model made of shells
632/// Structure: ShellBasedSurfaceModel -> Shell[] -> Face[] -> FaceBound -> PolyLoop
633pub struct ShellBasedSurfaceModelProcessor;
634
635impl ShellBasedSurfaceModelProcessor {
636    pub fn new() -> Self {
637        Self
638    }
639}
640
641impl GeometryProcessor for ShellBasedSurfaceModelProcessor {
642    fn process(
643        &self,
644        entity: &DecodedEntity,
645        decoder: &mut EntityDecoder,
646        _schema: &IfcSchema,
647    ) -> Result<Mesh> {
648        // IfcShellBasedSurfaceModel attributes:
649        // 0: SbsmBoundary (SET of IfcShell - either IfcOpenShell or IfcClosedShell)
650
651        let shells_attr = entity
652            .get(0)
653            .ok_or_else(|| Error::geometry("ShellBasedSurfaceModel missing SbsmBoundary".to_string()))?;
654
655        let shell_refs = shells_attr
656            .as_list()
657            .ok_or_else(|| Error::geometry("Expected shell list".to_string()))?;
658
659        let mut all_positions = Vec::new();
660        let mut all_indices = Vec::new();
661
662        // Process each shell
663        for shell_ref in shell_refs {
664            let shell_id = shell_ref.as_entity_ref().ok_or_else(|| {
665                Error::geometry("Expected entity reference for shell".to_string())
666            })?;
667
668            // Get face IDs from Shell (IfcOpenShell or IfcClosedShell)
669            // Both have CfsFaces as attribute 0
670            let face_ids = match decoder.get_entity_ref_list_fast(shell_id) {
671                Some(ids) => ids,
672                None => continue,
673            };
674
675            // Process each face in the shell
676            for face_id in face_ids {
677                // Get bound IDs from Face
678                let bound_ids = match decoder.get_entity_ref_list_fast(face_id) {
679                    Some(ids) => ids,
680                    None => continue,
681                };
682
683                let mut outer_points: Option<Vec<Point3<f64>>> = None;
684                let mut hole_points: Vec<Vec<Point3<f64>>> = Vec::new();
685
686                for bound_id in bound_ids {
687                    // FAST PATH: Extract loop_id, orientation, is_outer from raw bytes
688                    let (loop_id, orientation, is_outer) =
689                        match decoder.get_face_bound_fast(bound_id) {
690                            Some(data) => data,
691                            None => continue,
692                        };
693
694                    // Get loop points using shared helper
695                    let mut points = match extract_loop_points_by_id(loop_id, decoder) {
696                        Some(p) => p,
697                        None => continue,
698                    };
699
700                    if !orientation {
701                        points.reverse();
702                    }
703
704                    if is_outer || outer_points.is_none() {
705                        outer_points = Some(points);
706                    } else {
707                        hole_points.push(points);
708                    }
709                }
710
711                // Triangulate the face
712                if let Some(outer) = outer_points {
713                    if outer.len() >= 3 {
714                        let base_idx = (all_positions.len() / 3) as u32;
715
716                        // Add positions
717                        for p in &outer {
718                            all_positions.push(p.x as f32);
719                            all_positions.push(p.y as f32);
720                            all_positions.push(p.z as f32);
721                        }
722
723                        // Simple fan triangulation (works for convex faces)
724                        for i in 1..outer.len() - 1 {
725                            all_indices.push(base_idx);
726                            all_indices.push(base_idx + i as u32);
727                            all_indices.push(base_idx + i as u32 + 1);
728                        }
729                    }
730                }
731            }
732        }
733
734        Ok(Mesh {
735            positions: all_positions,
736            normals: Vec::new(),
737            indices: all_indices,
738        })
739    }
740
741    fn supported_types(&self) -> Vec<IfcType> {
742        vec![IfcType::IfcShellBasedSurfaceModel]
743    }
744}
745
746impl Default for ShellBasedSurfaceModelProcessor {
747    fn default() -> Self {
748        Self::new()
749    }
750}