Skip to main content

ifc_lite_geometry/processors/
advanced.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//! AdvancedBrep processor - NURBS/B-spline surfaces.
6//!
7//! Handles IfcAdvancedBrep and IfcAdvancedBrepWithVoids with support for
8//! planar faces, B-spline surface tessellation, and cylindrical surfaces.
9
10use crate::{Error, Mesh, Point3, Result};
11use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
12use nalgebra::Matrix4;
13
14use crate::router::GeometryProcessor;
15use super::helpers::get_axis2_placement_transform_by_id;
16
17/// AdvancedBrep processor
18/// Handles IfcAdvancedBrep and IfcAdvancedBrepWithVoids - NURBS/B-spline surfaces
19/// Supports planar faces and B-spline surface tessellation
20pub struct AdvancedBrepProcessor;
21
22impl AdvancedBrepProcessor {
23    pub fn new() -> Self {
24        Self
25    }
26
27    /// Evaluate a B-spline basis function (Cox-de Boor recursion)
28    #[inline]
29    fn bspline_basis(i: usize, p: usize, u: f64, knots: &[f64]) -> f64 {
30        if p == 0 {
31            if knots[i] <= u && u < knots[i + 1] {
32                1.0
33            } else {
34                0.0
35            }
36        } else {
37            let left = {
38                let denom = knots[i + p] - knots[i];
39                if denom.abs() < 1e-10 {
40                    0.0
41                } else {
42                    (u - knots[i]) / denom * Self::bspline_basis(i, p - 1, u, knots)
43                }
44            };
45            let right = {
46                let denom = knots[i + p + 1] - knots[i + 1];
47                if denom.abs() < 1e-10 {
48                    0.0
49                } else {
50                    (knots[i + p + 1] - u) / denom * Self::bspline_basis(i + 1, p - 1, u, knots)
51                }
52            };
53            left + right
54        }
55    }
56
57    /// Evaluate a B-spline surface at parameter (u, v)
58    fn evaluate_bspline_surface(
59        u: f64,
60        v: f64,
61        u_degree: usize,
62        v_degree: usize,
63        control_points: &[Vec<Point3<f64>>],
64        u_knots: &[f64],
65        v_knots: &[f64],
66    ) -> Point3<f64> {
67        let _n_u = control_points.len();
68
69        let mut result = Point3::new(0.0, 0.0, 0.0);
70
71        for (i, row) in control_points.iter().enumerate() {
72            let n_i = Self::bspline_basis(i, u_degree, u, u_knots);
73            for (j, cp) in row.iter().enumerate() {
74                let n_j = Self::bspline_basis(j, v_degree, v, v_knots);
75                let weight = n_i * n_j;
76                if weight.abs() > 1e-10 {
77                    result.x += weight * cp.x;
78                    result.y += weight * cp.y;
79                    result.z += weight * cp.z;
80                }
81            }
82        }
83
84        result
85    }
86
87    /// Tessellate a B-spline surface into triangles
88    fn tessellate_bspline_surface(
89        u_degree: usize,
90        v_degree: usize,
91        control_points: &[Vec<Point3<f64>>],
92        u_knots: &[f64],
93        v_knots: &[f64],
94        u_segments: usize,
95        v_segments: usize,
96    ) -> (Vec<f32>, Vec<u32>) {
97        let mut positions = Vec::new();
98        let mut indices = Vec::new();
99
100        // Get parameter domain
101        let u_min = u_knots[u_degree];
102        let u_max = u_knots[u_knots.len() - u_degree - 1];
103        let v_min = v_knots[v_degree];
104        let v_max = v_knots[v_knots.len() - v_degree - 1];
105
106        // Evaluate surface on a grid
107        for i in 0..=u_segments {
108            let u = u_min + (u_max - u_min) * (i as f64 / u_segments as f64);
109            // Clamp u to slightly inside the domain to avoid edge issues
110            let u = u.min(u_max - 1e-6).max(u_min);
111
112            for j in 0..=v_segments {
113                let v = v_min + (v_max - v_min) * (j as f64 / v_segments as f64);
114                let v = v.min(v_max - 1e-6).max(v_min);
115
116                let point = Self::evaluate_bspline_surface(
117                    u,
118                    v,
119                    u_degree,
120                    v_degree,
121                    control_points,
122                    u_knots,
123                    v_knots,
124                );
125
126                positions.push(point.x as f32);
127                positions.push(point.y as f32);
128                positions.push(point.z as f32);
129
130                // Create triangles
131                if i < u_segments && j < v_segments {
132                    let base = (i * (v_segments + 1) + j) as u32;
133                    let next_u = base + (v_segments + 1) as u32;
134
135                    // Two triangles per quad
136                    indices.push(base);
137                    indices.push(base + 1);
138                    indices.push(next_u + 1);
139
140                    indices.push(base);
141                    indices.push(next_u + 1);
142                    indices.push(next_u);
143                }
144            }
145        }
146
147        (positions, indices)
148    }
149
150    /// Parse control points from B-spline surface entity
151    fn parse_control_points(
152        &self,
153        bspline: &DecodedEntity,
154        decoder: &mut EntityDecoder,
155    ) -> Result<Vec<Vec<Point3<f64>>>> {
156        // Attribute 2: ControlPointsList (LIST of LIST of IfcCartesianPoint)
157        let cp_list_attr = bspline.get(2).ok_or_else(|| {
158            Error::geometry("BSplineSurface missing ControlPointsList".to_string())
159        })?;
160
161        let rows = cp_list_attr
162            .as_list()
163            .ok_or_else(|| Error::geometry("Expected control point list".to_string()))?;
164
165        let mut result = Vec::with_capacity(rows.len());
166
167        for row in rows {
168            let cols = row
169                .as_list()
170                .ok_or_else(|| Error::geometry("Expected control point row".to_string()))?;
171
172            let mut row_points = Vec::with_capacity(cols.len());
173            for col in cols {
174                if let Some(point_id) = col.as_entity_ref() {
175                    let point = decoder.decode_by_id(point_id)?;
176                    let coords = point.get(0).and_then(|v| v.as_list()).ok_or_else(|| {
177                        Error::geometry("CartesianPoint missing coordinates".to_string())
178                    })?;
179
180                    let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
181                    let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
182                    let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
183
184                    row_points.push(Point3::new(x, y, z));
185                }
186            }
187            result.push(row_points);
188        }
189
190        Ok(result)
191    }
192
193    /// Expand knot vector based on multiplicities
194    fn expand_knots(knot_values: &[f64], multiplicities: &[i64]) -> Vec<f64> {
195        let mut expanded = Vec::new();
196        for (knot, &mult) in knot_values.iter().zip(multiplicities.iter()) {
197            for _ in 0..mult {
198                expanded.push(*knot);
199            }
200        }
201        expanded
202    }
203
204    /// Parse knot vectors from B-spline surface entity
205    fn parse_knot_vectors(&self, bspline: &DecodedEntity) -> Result<(Vec<f64>, Vec<f64>)> {
206        // IFCBSPLINESURFACEWITHKNOTS attributes:
207        // 0: UDegree
208        // 1: VDegree
209        // 2: ControlPointsList (already parsed)
210        // 3: SurfaceForm
211        // 4: UClosed
212        // 5: VClosed
213        // 6: SelfIntersect
214        // 7: UMultiplicities (LIST of INTEGER)
215        // 8: VMultiplicities (LIST of INTEGER)
216        // 9: UKnots (LIST of REAL)
217        // 10: VKnots (LIST of REAL)
218        // 11: KnotSpec
219
220        // Get U multiplicities
221        let u_mult_attr = bspline
222            .get(7)
223            .ok_or_else(|| Error::geometry("BSplineSurface missing UMultiplicities".to_string()))?;
224        let u_mults: Vec<i64> = u_mult_attr
225            .as_list()
226            .ok_or_else(|| Error::geometry("Expected U multiplicities list".to_string()))?
227            .iter()
228            .filter_map(|v| v.as_int())
229            .collect();
230
231        // Get V multiplicities
232        let v_mult_attr = bspline
233            .get(8)
234            .ok_or_else(|| Error::geometry("BSplineSurface missing VMultiplicities".to_string()))?;
235        let v_mults: Vec<i64> = v_mult_attr
236            .as_list()
237            .ok_or_else(|| Error::geometry("Expected V multiplicities list".to_string()))?
238            .iter()
239            .filter_map(|v| v.as_int())
240            .collect();
241
242        // Get U knots
243        let u_knots_attr = bspline
244            .get(9)
245            .ok_or_else(|| Error::geometry("BSplineSurface missing UKnots".to_string()))?;
246        let u_knot_values: Vec<f64> = u_knots_attr
247            .as_list()
248            .ok_or_else(|| Error::geometry("Expected U knots list".to_string()))?
249            .iter()
250            .filter_map(|v| v.as_float())
251            .collect();
252
253        // Get V knots
254        let v_knots_attr = bspline
255            .get(10)
256            .ok_or_else(|| Error::geometry("BSplineSurface missing VKnots".to_string()))?;
257        let v_knot_values: Vec<f64> = v_knots_attr
258            .as_list()
259            .ok_or_else(|| Error::geometry("Expected V knots list".to_string()))?
260            .iter()
261            .filter_map(|v| v.as_float())
262            .collect();
263
264        // Expand knot vectors with multiplicities
265        let u_knots = Self::expand_knots(&u_knot_values, &u_mults);
266        let v_knots = Self::expand_knots(&v_knot_values, &v_mults);
267
268        Ok((u_knots, v_knots))
269    }
270
271    /// Process a planar face (IfcPlane surface)
272    fn process_planar_face(
273        &self,
274        face: &DecodedEntity,
275        decoder: &mut EntityDecoder,
276    ) -> Result<(Vec<f32>, Vec<u32>)> {
277        // Get bounds from face (attribute 0)
278        let bounds_attr = face
279            .get(0)
280            .ok_or_else(|| Error::geometry("AdvancedFace missing Bounds".to_string()))?;
281
282        let bounds = bounds_attr
283            .as_list()
284            .ok_or_else(|| Error::geometry("Expected bounds list".to_string()))?;
285
286        let mut positions = Vec::new();
287        let mut indices = Vec::new();
288
289        for bound in bounds {
290            if let Some(bound_id) = bound.as_entity_ref() {
291                let bound_entity = decoder.decode_by_id(bound_id)?;
292
293                // Get the loop (attribute 0: Bound)
294                let loop_attr = bound_entity
295                    .get(0)
296                    .ok_or_else(|| Error::geometry("FaceBound missing Bound".to_string()))?;
297
298                let loop_entity = decoder
299                    .resolve_ref(loop_attr)?
300                    .ok_or_else(|| Error::geometry("Failed to resolve loop".to_string()))?;
301
302                // Get oriented edges from edge loop
303                if loop_entity
304                    .ifc_type
305                    .as_str()
306                    .eq_ignore_ascii_case("IFCEDGELOOP")
307                {
308                    let edges_attr = loop_entity
309                        .get(0)
310                        .ok_or_else(|| Error::geometry("EdgeLoop missing EdgeList".to_string()))?;
311
312                    let edges = edges_attr
313                        .as_list()
314                        .ok_or_else(|| Error::geometry("Expected edge list".to_string()))?;
315
316                    let mut polygon_points = Vec::new();
317
318                    for edge_ref in edges {
319                        if let Some(edge_id) = edge_ref.as_entity_ref() {
320                            let oriented_edge = decoder.decode_by_id(edge_id)?;
321
322                            // IfcOrientedEdge: EdgeStart(0), EdgeEnd(1), EdgeElement(2), Orientation(3)
323                            // EdgeStart/EdgeEnd can be * (derived), get from EdgeElement if needed
324
325                            // Try to get start vertex from OrientedEdge first
326                            let vertex = oriented_edge.get(0)
327                                .and_then(|attr| decoder.resolve_ref(attr).ok().flatten())
328                                .or_else(|| {
329                                    // If EdgeStart is *, get from EdgeElement (IfcEdgeCurve)
330                                    oriented_edge.get(2)
331                                        .and_then(|attr| decoder.resolve_ref(attr).ok().flatten())
332                                        .and_then(|edge_curve| {
333                                            // IfcEdgeCurve: EdgeStart(0), EdgeEnd(1), EdgeGeometry(2)
334                                            edge_curve.get(0)
335                                                .and_then(|attr| decoder.resolve_ref(attr).ok().flatten())
336                                        })
337                                });
338
339                            if let Some(vertex) = vertex {
340                                // IfcVertexPoint has VertexGeometry (IfcCartesianPoint)
341                                if let Some(point_attr) = vertex.get(0) {
342                                    if let Some(point) = decoder.resolve_ref(point_attr).ok().flatten() {
343                                        if let Some(coords) = point.get(0).and_then(|v| v.as_list()) {
344                                            let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
345                                            let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
346                                            let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
347
348                                            polygon_points.push(Point3::new(x, y, z));
349                                        }
350                                    }
351                                }
352                            }
353                        }
354                    }
355
356                    // Triangulate the polygon
357                    if polygon_points.len() >= 3 {
358                        let base_idx = (positions.len() / 3) as u32;
359
360                        for point in &polygon_points {
361                            positions.push(point.x as f32);
362                            positions.push(point.y as f32);
363                            positions.push(point.z as f32);
364                        }
365
366                        // TODO: Fan triangulation assumes convex polygons. For non-convex faces,
367                        // consider using triangulate_polygon_with_holes from FacetedBrepProcessor.
368                        // Fan triangulation for simple convex polygons
369                        for i in 1..polygon_points.len() - 1 {
370                            indices.push(base_idx);
371                            indices.push(base_idx + i as u32);
372                            indices.push(base_idx + i as u32 + 1);
373                        }
374                    }
375                }
376            }
377        }
378
379        Ok((positions, indices))
380    }
381
382    /// Process a B-spline surface face
383    fn process_bspline_face(
384        &self,
385        bspline: &DecodedEntity,
386        decoder: &mut EntityDecoder,
387    ) -> Result<(Vec<f32>, Vec<u32>)> {
388        // Get degrees
389        let u_degree = bspline.get_float(0).unwrap_or(3.0) as usize;
390        let v_degree = bspline.get_float(1).unwrap_or(1.0) as usize;
391
392        // Parse control points
393        let control_points = self.parse_control_points(bspline, decoder)?;
394
395        // Parse knot vectors
396        let (u_knots, v_knots) = self.parse_knot_vectors(bspline)?;
397
398        // Determine tessellation resolution based on surface complexity
399        let u_segments = (control_points.len() * 3).clamp(8, 24);
400        let v_segments = if !control_points.is_empty() {
401            (control_points[0].len() * 3).clamp(4, 24)
402        } else {
403            4
404        };
405
406        // Tessellate the surface
407        let (positions, indices) = Self::tessellate_bspline_surface(
408            u_degree,
409            v_degree,
410            &control_points,
411            &u_knots,
412            &v_knots,
413            u_segments,
414            v_segments,
415        );
416
417        Ok((positions, indices))
418    }
419
420    /// Process a cylindrical surface face
421    fn process_cylindrical_face(
422        &self,
423        face: &DecodedEntity,
424        surface: &DecodedEntity,
425        decoder: &mut EntityDecoder,
426    ) -> Result<(Vec<f32>, Vec<u32>)> {
427        // Get the radius from IfcCylindricalSurface (attribute 1)
428        let radius = surface
429            .get(1)
430            .and_then(|v| v.as_float())
431            .ok_or_else(|| Error::geometry("CylindricalSurface missing Radius".to_string()))?;
432
433        // Get position/axis from IfcCylindricalSurface (attribute 0)
434        let position_attr = surface.get(0);
435        let axis_transform = if let Some(attr) = position_attr {
436            if let Some(pos_id) = attr.as_entity_ref() {
437                get_axis2_placement_transform_by_id(pos_id, decoder)?
438            } else {
439                Matrix4::identity()
440            }
441        } else {
442            Matrix4::identity()
443        };
444
445        // Extract boundary edges to determine angular and height extent
446        let bounds_attr = face
447            .get(0)
448            .ok_or_else(|| Error::geometry("AdvancedFace missing Bounds".to_string()))?;
449
450        let bounds = bounds_attr
451            .as_list()
452            .ok_or_else(|| Error::geometry("Expected bounds list".to_string()))?;
453
454        // Collect all boundary points to determine the extent
455        let mut boundary_points: Vec<Point3<f64>> = Vec::new();
456
457        for bound in bounds {
458            if let Some(bound_id) = bound.as_entity_ref() {
459                let bound_entity = decoder.decode_by_id(bound_id)?;
460                let loop_attr = bound_entity.get(0).ok_or_else(|| {
461                    Error::geometry("FaceBound missing Bound".to_string())
462                })?;
463
464                if let Some(loop_entity) = decoder.resolve_ref(loop_attr)? {
465                    if loop_entity.ifc_type.as_str().eq_ignore_ascii_case("IFCEDGELOOP") {
466                        if let Some(edges_attr) = loop_entity.get(0) {
467                            if let Some(edges) = edges_attr.as_list() {
468                                for edge_ref in edges {
469                                    if let Some(edge_id) = edge_ref.as_entity_ref() {
470                                        if let Ok(oriented_edge) = decoder.decode_by_id(edge_id) {
471                                            // IfcOrientedEdge: 0=EdgeStart, 1=EdgeEnd, 2=EdgeElement, 3=Orientation
472                                            // EdgeStart/EdgeEnd can be * (null), get from EdgeElement if needed
473
474                                            // Try to get start vertex from OrientedEdge first
475                                            let start_vertex = oriented_edge.get(0)
476                                                .and_then(|attr| decoder.resolve_ref(attr).ok().flatten());
477
478                                            // If null, get from EdgeElement (attribute 2)
479                                            let vertex = if start_vertex.is_some() {
480                                                start_vertex
481                                            } else if let Some(edge_elem_attr) = oriented_edge.get(2) {
482                                                // Get EdgeElement (IfcEdgeCurve)
483                                                if let Some(edge_curve) = decoder.resolve_ref(edge_elem_attr).ok().flatten() {
484                                                    // IfcEdgeCurve: 0=EdgeStart, 1=EdgeEnd, 2=EdgeGeometry
485                                                    edge_curve.get(0)
486                                                        .and_then(|attr| decoder.resolve_ref(attr).ok().flatten())
487                                                } else {
488                                                    None
489                                                }
490                                            } else {
491                                                None
492                                            };
493
494                                            if let Some(vertex) = vertex {
495                                                // IfcVertexPoint: 0=VertexGeometry (IfcCartesianPoint)
496                                                if let Some(point_attr) = vertex.get(0) {
497                                                    if let Some(point) = decoder.resolve_ref(point_attr).ok().flatten() {
498                                                        if let Some(coords) = point.get(0).and_then(|v| v.as_list()) {
499                                                            let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
500                                                            let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
501                                                            let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
502                                                            boundary_points.push(Point3::new(x, y, z));
503                                                        }
504                                                    }
505                                                }
506                                            }
507                                        }
508                                    }
509                                }
510                            }
511                        }
512                    }
513                }
514            }
515        }
516
517        if boundary_points.is_empty() {
518            return Ok((Vec::new(), Vec::new()));
519        }
520
521        // Transform boundary points to local cylinder coordinates
522        let inv_transform = axis_transform.try_inverse().unwrap_or(Matrix4::identity());
523        let local_points: Vec<Point3<f64>> = boundary_points
524            .iter()
525            .map(|p| inv_transform.transform_point(p))
526            .collect();
527
528        // Determine angular extent (from local x,y) and height extent (from local z)
529        let mut min_angle = f64::MAX;
530        let mut max_angle = f64::MIN;
531        let mut min_z = f64::MAX;
532        let mut max_z = f64::MIN;
533
534        for p in &local_points {
535            let angle = p.y.atan2(p.x);
536            min_angle = min_angle.min(angle);
537            max_angle = max_angle.max(angle);
538            min_z = min_z.min(p.z);
539            max_z = max_z.max(p.z);
540        }
541
542        // Handle angle wrapping (if angles span across -π/π boundary)
543        if max_angle - min_angle > std::f64::consts::PI * 1.5 {
544            // Likely wraps around, recalculate with positive angles
545            let positive_angles: Vec<f64> = local_points.iter()
546                .map(|p| {
547                    let a = p.y.atan2(p.x);
548                    if a < 0.0 { a + 2.0 * std::f64::consts::PI } else { a }
549                })
550                .collect();
551            min_angle = positive_angles.iter().cloned().fold(f64::MAX, f64::min);
552            max_angle = positive_angles.iter().cloned().fold(f64::MIN, f64::max);
553        }
554
555        // Tessellation parameters
556        let angle_span = max_angle - min_angle;
557        let height = max_z - min_z;
558
559        // Balance between accuracy and matching web-ifc's output
560        // Use ~15 degrees per segment (π/12) for good curvature approximation
561        let angle_segments = ((angle_span / (std::f64::consts::PI / 12.0)).ceil() as usize).clamp(3, 16);
562        // Height segments based on aspect ratio - at least 1, more for tall cylinders
563        let height_segments = ((height / (radius * 2.0)).ceil() as usize).clamp(1, 4);
564
565        let mut positions = Vec::new();
566        let mut indices = Vec::new();
567
568        // Generate cylinder patch vertices
569        for h in 0..=height_segments {
570            let z = min_z + (height * h as f64 / height_segments as f64);
571            for a in 0..=angle_segments {
572                let angle = min_angle + (angle_span * a as f64 / angle_segments as f64);
573                let x = radius * angle.cos();
574                let y = radius * angle.sin();
575
576                // Transform back to world coordinates
577                let local_point = Point3::new(x, y, z);
578                let world_point = axis_transform.transform_point(&local_point);
579
580                positions.push(world_point.x as f32);
581                positions.push(world_point.y as f32);
582                positions.push(world_point.z as f32);
583            }
584        }
585
586        // Generate indices for quad strip
587        let cols = angle_segments + 1;
588        for h in 0..height_segments {
589            for a in 0..angle_segments {
590                let base = (h * cols + a) as u32;
591                let next_row = base + cols as u32;
592
593                // Two triangles per quad
594                indices.push(base);
595                indices.push(base + 1);
596                indices.push(next_row + 1);
597
598                indices.push(base);
599                indices.push(next_row + 1);
600                indices.push(next_row);
601            }
602        }
603
604        Ok((positions, indices))
605    }
606}
607
608impl GeometryProcessor for AdvancedBrepProcessor {
609    fn process(
610        &self,
611        entity: &DecodedEntity,
612        decoder: &mut EntityDecoder,
613        _schema: &IfcSchema,
614    ) -> Result<Mesh> {
615        // IfcAdvancedBrep attributes:
616        // 0: Outer (IfcClosedShell)
617
618        // Get the outer shell
619        let shell_attr = entity
620            .get(0)
621            .ok_or_else(|| Error::geometry("AdvancedBrep missing Outer shell".to_string()))?;
622
623        let shell = decoder
624            .resolve_ref(shell_attr)?
625            .ok_or_else(|| Error::geometry("Failed to resolve Outer shell".to_string()))?;
626
627        // Get faces from the shell (IfcClosedShell.CfsFaces)
628        let faces_attr = shell
629            .get(0)
630            .ok_or_else(|| Error::geometry("ClosedShell missing CfsFaces".to_string()))?;
631
632        let faces = faces_attr
633            .as_list()
634            .ok_or_else(|| Error::geometry("Expected face list".to_string()))?;
635
636        let mut all_positions = Vec::new();
637        let mut all_indices = Vec::new();
638
639        for face_ref in faces {
640            if let Some(face_id) = face_ref.as_entity_ref() {
641                let face = decoder.decode_by_id(face_id)?;
642
643                // IfcAdvancedFace has:
644                // 0: Bounds (list of FaceBound)
645                // 1: FaceSurface (IfcSurface - Plane, BSplineSurface, etc.)
646                // 2: SameSense (boolean)
647
648                let surface_attr = face.get(1).ok_or_else(|| {
649                    Error::geometry("AdvancedFace missing FaceSurface".to_string())
650                })?;
651
652                let surface = decoder
653                    .resolve_ref(surface_attr)?
654                    .ok_or_else(|| Error::geometry("Failed to resolve FaceSurface".to_string()))?;
655
656                let surface_type = surface.ifc_type.as_str().to_uppercase();
657                let (positions, indices) = if surface_type == "IFCPLANE" {
658                    // Planar face - extract boundary vertices
659                    self.process_planar_face(&face, decoder)?
660                } else if surface_type == "IFCBSPLINESURFACEWITHKNOTS"
661                    || surface_type == "IFCRATIONALBSPLINESURFACEWITHKNOTS"
662                {
663                    // B-spline surface - tessellate
664                    self.process_bspline_face(&surface, decoder)?
665                } else if surface_type == "IFCCYLINDRICALSURFACE" {
666                    // Cylindrical surface - tessellate
667                    self.process_cylindrical_face(&face, &surface, decoder)?
668                } else {
669                    // Unsupported surface type - skip
670                    continue;
671                };
672
673                // Merge into combined mesh
674                let base_idx = (all_positions.len() / 3) as u32;
675                all_positions.extend(positions);
676                for idx in indices {
677                    all_indices.push(base_idx + idx);
678                }
679            }
680        }
681
682        Ok(Mesh {
683            positions: all_positions,
684            normals: Vec::new(),
685            indices: all_indices,
686        })
687    }
688
689    fn supported_types(&self) -> Vec<IfcType> {
690        vec![IfcType::IfcAdvancedBrep, IfcType::IfcAdvancedBrepWithVoids]
691    }
692}
693
694impl Default for AdvancedBrepProcessor {
695    fn default() -> Self {
696        Self::new()
697    }
698}