Skip to main content

ifc_lite_geometry/processors/
surface.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//! SurfaceOfLinearExtrusion processor - surface sweep geometry.
6
7use crate::{Error, Mesh, Point2, Point3, Result, TessellationQuality, Vector3};
8use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
9use nalgebra::Matrix4;
10
11use super::helpers::{get_axis2_placement_transform_by_id, get_direction_by_id};
12use crate::router::GeometryProcessor;
13
14/// SurfaceOfLinearExtrusion processor
15/// Handles IfcSurfaceOfLinearExtrusion - surface created by sweeping a curve along a direction
16pub struct SurfaceOfLinearExtrusionProcessor;
17
18impl SurfaceOfLinearExtrusionProcessor {
19    pub fn new() -> Self {
20        Self
21    }
22}
23
24impl GeometryProcessor for SurfaceOfLinearExtrusionProcessor {
25    fn process(
26        &self,
27        entity: &DecodedEntity,
28        decoder: &mut EntityDecoder,
29        _schema: &IfcSchema,
30        _quality: TessellationQuality,
31    ) -> Result<Mesh> {
32        // IfcSurfaceOfLinearExtrusion attributes:
33        // 0: SweptCurve (IfcProfileDef - usually IfcArbitraryOpenProfileDef)
34        // 1: Position (IfcAxis2Placement3D)
35        // 2: ExtrudedDirection (IfcDirection)
36        // 3: Depth (length)
37
38        // Get the swept curve (profile)
39        let curve_attr = entity.get(0).ok_or_else(|| {
40            Error::geometry("SurfaceOfLinearExtrusion missing SweptCurve".to_string())
41        })?;
42
43        let curve_id = curve_attr.as_entity_ref().ok_or_else(|| {
44            Error::geometry("Expected entity reference for SweptCurve".to_string())
45        })?;
46
47        // Get position
48        let position_attr = entity.get(1);
49        let position_transform = if let Some(attr) = position_attr {
50            if let Some(pos_id) = attr.as_entity_ref() {
51                get_axis2_placement_transform_by_id(pos_id, decoder)?
52            } else {
53                Matrix4::identity()
54            }
55        } else {
56            Matrix4::identity()
57        };
58
59        // Get extrusion direction
60        let direction_attr = entity.get(2).ok_or_else(|| {
61            Error::geometry("SurfaceOfLinearExtrusion missing ExtrudedDirection".to_string())
62        })?;
63
64        let direction = if let Some(dir_id) = direction_attr.as_entity_ref() {
65            get_direction_by_id(dir_id, decoder)
66                .ok_or_else(|| Error::geometry("Failed to get direction".to_string()))?
67        } else {
68            Vector3::new(0.0, 0.0, 1.0) // Default to Z-up
69        };
70
71        // Get depth
72        let depth = entity
73            .get(3)
74            .and_then(|v| v.as_float())
75            .ok_or_else(|| Error::geometry("SurfaceOfLinearExtrusion missing Depth".to_string()))?;
76
77        // Get curve points from the profile
78        let curve_points = Self::get_profile_curve_points(curve_id, decoder)?;
79
80        if curve_points.len() < 2 {
81            return Ok(Mesh::new());
82        }
83
84        // Extrude the curve to create a surface (quad strip)
85        let extrusion = direction.normalize() * depth;
86
87        let mut positions = Vec::with_capacity(curve_points.len() * 2 * 3);
88        let mut indices = Vec::with_capacity((curve_points.len() - 1) * 6);
89
90        // Create vertices: bottom row, then top row
91        for point in &curve_points {
92            // Transform 2D point to 3D using position
93            let p3d = position_transform.transform_point(&Point3::new(point.x, point.y, 0.0));
94            positions.push(p3d.x as f32);
95            positions.push(p3d.y as f32);
96            positions.push(p3d.z as f32);
97        }
98
99        for point in &curve_points {
100            // Extruded point
101            let p3d = position_transform.transform_point(&Point3::new(point.x, point.y, 0.0));
102            let p_extruded = p3d + extrusion;
103            positions.push(p_extruded.x as f32);
104            positions.push(p_extruded.y as f32);
105            positions.push(p_extruded.z as f32);
106        }
107
108        // Create quad strip triangles
109        let n = curve_points.len() as u32;
110        for i in 0..n - 1 {
111            // Two triangles per quad
112            // Triangle 1: bottom-left, bottom-right, top-left
113            indices.push(i);
114            indices.push(i + 1);
115            indices.push(i + n);
116
117            // Triangle 2: bottom-right, top-right, top-left
118            indices.push(i + 1);
119            indices.push(i + n + 1);
120            indices.push(i + n);
121        }
122
123        Ok(Mesh {
124            positions,
125            normals: Vec::new(),
126            indices,
127            rtc_applied: false, 
128            origin: [0.0; 3],        instance_meta: None, local_bounds: None, local_to_world: None })
129    }
130
131    fn supported_types(&self) -> Vec<IfcType> {
132        vec![IfcType::IfcSurfaceOfLinearExtrusion]
133    }
134}
135
136impl SurfaceOfLinearExtrusionProcessor {
137    /// Extract curve points from a profile definition
138    fn get_profile_curve_points(
139        profile_id: u32,
140        decoder: &mut EntityDecoder,
141    ) -> Result<Vec<Point2<f64>>> {
142        let profile = decoder.decode_by_id(profile_id)?;
143
144        // IfcArbitraryOpenProfileDef: 0=ProfileType, 1=ProfileName, 2=Curve
145        // IfcArbitraryClosedProfileDef: 0=ProfileType, 1=ProfileName, 2=OuterCurve
146        let curve_attr = profile
147            .get(2)
148            .ok_or_else(|| Error::geometry("Profile missing curve".to_string()))?;
149
150        let curve_id = curve_attr
151            .as_entity_ref()
152            .ok_or_else(|| Error::geometry("Expected entity reference for curve".to_string()))?;
153
154        // Get curve entity to determine type
155        let curve = decoder.decode_by_id(curve_id)?;
156
157        match curve.ifc_type {
158            IfcType::IfcPolyline => {
159                // IfcPolyline: attribute 0 is Points (list of IfcCartesianPoint)
160                let point_ids = decoder
161                    .get_polyloop_point_ids_fast(curve_id)
162                    .ok_or_else(|| Error::geometry("Failed to get polyline points".to_string()))?;
163
164                let mut points = Vec::with_capacity(point_ids.len());
165                for point_id in point_ids {
166                    if let Some((x, y, _z)) = decoder.get_cartesian_point_fast(point_id) {
167                        points.push(Point2::new(x, y));
168                    }
169                }
170                Ok(points)
171            }
172            IfcType::IfcCompositeCurve => {
173                // Handle composite curves by extracting segments
174                Self::extract_composite_curve_points(curve_id, decoder)
175            }
176            _ => {
177                // Fallback: try to get points directly
178                if let Some(point_ids) = decoder.get_polyloop_point_ids_fast(curve_id) {
179                    let mut points = Vec::with_capacity(point_ids.len());
180                    for point_id in point_ids {
181                        if let Some((x, y, _z)) = decoder.get_cartesian_point_fast(point_id) {
182                            points.push(Point2::new(x, y));
183                        }
184                    }
185                    Ok(points)
186                } else {
187                    Ok(Vec::new())
188                }
189            }
190        }
191    }
192
193    /// Extract points from a composite curve
194    fn extract_composite_curve_points(
195        curve_id: u32,
196        decoder: &mut EntityDecoder,
197    ) -> Result<Vec<Point2<f64>>> {
198        let curve = decoder.decode_by_id(curve_id)?;
199
200        // IfcCompositeCurve: attribute 0 is Segments (list of IfcCompositeCurveSegment)
201        let segments_attr = curve
202            .get(0)
203            .ok_or_else(|| Error::geometry("CompositeCurve missing Segments".to_string()))?;
204
205        let segment_refs = segments_attr
206            .as_list()
207            .ok_or_else(|| Error::geometry("Expected segment list".to_string()))?;
208
209        let mut all_points = Vec::new();
210
211        for seg_ref in segment_refs {
212            let seg_id = seg_ref.as_entity_ref().ok_or_else(|| {
213                Error::geometry("Expected entity reference for segment".to_string())
214            })?;
215
216            let segment = decoder.decode_by_id(seg_id)?;
217
218            // IfcCompositeCurveSegment: 0=Transition, 1=SameSense, 2=ParentCurve
219            let parent_curve_attr = segment
220                .get(2)
221                .ok_or_else(|| Error::geometry("Segment missing ParentCurve".to_string()))?;
222
223            let parent_curve_id = parent_curve_attr.as_entity_ref().ok_or_else(|| {
224                Error::geometry("Expected entity reference for parent curve".to_string())
225            })?;
226
227            // Recursively get points from the parent curve
228            if let Ok(segment_points) = Self::get_profile_curve_points(parent_curve_id, decoder) {
229                // Skip first point if we already have points (to avoid duplicates at joints)
230                let start_idx = if all_points.is_empty() { 0 } else { 1 };
231                all_points.extend(segment_points.into_iter().skip(start_idx));
232            }
233        }
234
235        Ok(all_points)
236    }
237}
238
239impl Default for SurfaceOfLinearExtrusionProcessor {
240    fn default() -> Self {
241        Self::new()
242    }
243}