ifc_lite_geometry/processors/tessellated/
polygonal.rs1use crate::{Error, Mesh, Result, TessellationQuality};
6use ifc_lite_core::{AttributeValue, DecodedEntity, EntityDecoder, IfcSchema, IfcType};
7
8use crate::router::GeometryProcessor;
9
10pub struct PolygonalFaceSetProcessor;
13
14impl PolygonalFaceSetProcessor {
15 pub fn new() -> Self {
16 Self
17 }
18
19 #[inline]
20 fn parse_index_loop(indices: &[AttributeValue], pn_index: Option<&[u32]>) -> Vec<u32> {
21 indices
22 .iter()
23 .filter_map(|value| {
24 let idx = value.as_int()?;
25 if idx <= 0 {
26 return None;
27 }
28 let idx = idx as usize;
29
30 if let Some(remap) = pn_index {
31 remap.get(idx - 1).copied().filter(|mapped| *mapped > 0)
32 } else {
33 Some(idx as u32)
34 }
35 })
36 .collect()
37 }
38
39 #[inline]
40 fn parse_face_inner_indices(
41 face_entity: &DecodedEntity,
42 pn_index: Option<&[u32]>,
43 ) -> Vec<Vec<u32>> {
44 if face_entity.ifc_type != IfcType::IfcIndexedPolygonalFaceWithVoids {
45 return Vec::new();
46 }
47
48 let Some(inner_attr) = face_entity.get(1).and_then(|a| a.as_list()) else {
49 return Vec::new();
50 };
51
52 let mut result = Vec::with_capacity(inner_attr.len());
53 for loop_attr in inner_attr {
54 let Some(loop_values) = loop_attr.as_list() else {
55 continue;
56 };
57 let parsed = Self::parse_index_loop(loop_values, pn_index);
58 if parsed.len() >= 3 {
59 result.push(parsed);
60 }
61 }
62
63 result
64 }
65
66 fn process_rebased(
70 entity: &DecodedEntity,
71 decoder: &mut EntityDecoder,
72 rtc_file_units: Option<(f64, f64, f64)>,
73 ) -> Result<Mesh> {
74 let coord_entity_id = entity
81 .get(0)
82 .ok_or_else(|| Error::geometry("PolygonalFaceSet missing Coordinates".to_string()))?
83 .as_entity_ref()
84 .ok_or_else(|| {
85 Error::geometry("Expected entity reference for Coordinates".to_string())
86 })?;
87 let positions = super::read_point_list(decoder, coord_entity_id, rtc_file_units)?;
88
89 if positions.is_empty() {
90 return Ok(Mesh::new());
91 }
92
93 let faces_attr = entity
95 .get(2)
96 .ok_or_else(|| Error::geometry("PolygonalFaceSet missing Faces".to_string()))?;
97
98 let face_refs = faces_attr
99 .as_list()
100 .ok_or_else(|| Error::geometry("Expected faces list".to_string()))?;
101
102 let pn_index = entity.get(3).and_then(|attr| attr.as_list()).map(|list| {
105 list.iter()
106 .filter_map(|value| value.as_int())
107 .filter(|v| *v > 0)
108 .map(|v| v as u32)
109 .collect::<Vec<u32>>()
110 });
111
112 let mut indices = Vec::with_capacity(face_refs.len() * 6);
114
115 for face_ref in face_refs {
117 let face_id = face_ref
118 .as_entity_ref()
119 .ok_or_else(|| Error::geometry("Expected entity reference for face".to_string()))?;
120
121 let face_entity = decoder.decode_by_id(face_id)?;
122
123 let coord_index_attr = face_entity.get(0).ok_or_else(|| {
126 Error::geometry("IndexedPolygonalFace missing CoordIndex".to_string())
127 })?;
128
129 let coord_indices = coord_index_attr
130 .as_list()
131 .ok_or_else(|| Error::geometry("Expected coord index list".to_string()))?;
132
133 let face_indices = Self::parse_index_loop(coord_indices, pn_index.as_deref());
135 if face_indices.len() < 3 {
136 continue;
137 }
138
139 let inner_indices = Self::parse_face_inner_indices(&face_entity, pn_index.as_deref());
141
142 Self::triangulate_polygon(&face_indices, &inner_indices, &positions, &mut indices);
144 }
145
146 let is_closed = entity
149 .get(1)
150 .and_then(|a| a.as_enum())
151 .map(|v| v == "T")
152 .unwrap_or(false);
153 if is_closed {
154 Self::orient_closed_shell_outward(&positions, &mut indices);
155 }
156
157 let mut mesh = Self::build_flat_shaded_mesh(&positions, &indices);
158 mesh.rtc_applied = rtc_file_units.is_some();
159 Ok(mesh)
160 }
161}
162
163impl GeometryProcessor for PolygonalFaceSetProcessor {
164 fn process(
165 &self,
166 entity: &DecodedEntity,
167 decoder: &mut EntityDecoder,
168 _schema: &IfcSchema,
169 _quality: TessellationQuality,
170 ) -> Result<Mesh> {
171 Self::process_rebased(entity, decoder, None)
172 }
173
174 fn process_in_rtc_frame(
175 &self,
176 entity: &DecodedEntity,
177 decoder: &mut EntityDecoder,
178 _schema: &IfcSchema,
179 _quality: TessellationQuality,
180 rtc_file_units: (f64, f64, f64),
181 ) -> Option<Result<Mesh>> {
182 Some(Self::process_rebased(entity, decoder, Some(rtc_file_units)))
183 }
184
185 fn supported_types(&self) -> Vec<IfcType> {
186 vec![IfcType::IfcPolygonalFaceSet]
187 }
188}
189
190impl Default for PolygonalFaceSetProcessor {
191 fn default() -> Self {
192 Self::new()
193 }
194}