ifc_lite_geometry/processors/
tessellated.rs1use crate::{Error, Mesh, Result};
11use ifc_lite_core::{AttributeValue, DecodedEntity, EntityDecoder, IfcSchema, IfcType};
12
13use crate::router::GeometryProcessor;
14
15pub struct TriangulatedFaceSetProcessor;
18
19impl TriangulatedFaceSetProcessor {
20 pub fn new() -> Self {
21 Self
22 }
23}
24
25impl GeometryProcessor for TriangulatedFaceSetProcessor {
26 #[inline]
27 fn process(
28 &self,
29 entity: &DecodedEntity,
30 decoder: &mut EntityDecoder,
31 _schema: &IfcSchema,
32 ) -> Result<Mesh> {
33 let coords_attr = entity.get(0).ok_or_else(|| {
41 Error::geometry("TriangulatedFaceSet missing Coordinates".to_string())
42 })?;
43
44 let coord_entity_id = coords_attr.as_entity_ref().ok_or_else(|| {
45 Error::geometry("Expected entity reference for Coordinates".to_string())
46 })?;
47
48 use ifc_lite_core::{extract_coordinate_list_from_entity, parse_indices_direct};
51
52 let positions = if let Some(raw_bytes) = decoder.get_raw_bytes(coord_entity_id) {
53 extract_coordinate_list_from_entity(raw_bytes).unwrap_or_default()
56 } else {
57 let coords_entity = decoder.decode_by_id(coord_entity_id)?;
59
60 let coord_list_attr = coords_entity.get(0).ok_or_else(|| {
61 Error::geometry("CartesianPointList3D missing CoordList".to_string())
62 })?;
63
64 let coord_list = coord_list_attr
65 .as_list()
66 .ok_or_else(|| Error::geometry("Expected coordinate list".to_string()))?;
67
68 use ifc_lite_core::AttributeValue;
69 AttributeValue::parse_coordinate_list_3d(coord_list)
70 };
71
72 let indices_attr = entity
74 .get(3)
75 .ok_or_else(|| Error::geometry("TriangulatedFaceSet missing CoordIndex".to_string()))?;
76
77 let indices = if let Some(raw_entity_bytes) = decoder.get_raw_bytes(entity.id) {
80 if let Some(coord_index_bytes) = super::extract_coord_index_bytes(raw_entity_bytes) {
83 parse_indices_direct(coord_index_bytes)
84 } else {
85 let face_list = indices_attr
87 .as_list()
88 .ok_or_else(|| Error::geometry("Expected face index list".to_string()))?;
89 use ifc_lite_core::AttributeValue;
90 AttributeValue::parse_index_list(face_list)
91 }
92 } else {
93 let face_list = indices_attr
94 .as_list()
95 .ok_or_else(|| Error::geometry("Expected face index list".to_string()))?;
96 use ifc_lite_core::AttributeValue;
97 AttributeValue::parse_index_list(face_list)
98 };
99
100 Ok(Mesh {
102 positions,
103 normals: Vec::new(),
104 indices,
105 })
106 }
107
108 fn supported_types(&self) -> Vec<IfcType> {
109 vec![IfcType::IfcTriangulatedFaceSet]
110 }
111}
112
113impl Default for TriangulatedFaceSetProcessor {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119pub struct PolygonalFaceSetProcessor;
122
123impl PolygonalFaceSetProcessor {
124 pub fn new() -> Self {
125 Self
126 }
127
128 #[inline]
129 fn parse_index_loop(indices: &[AttributeValue], pn_index: Option<&[u32]>) -> Vec<u32> {
130 indices
131 .iter()
132 .filter_map(|value| {
133 let idx = value.as_int()?;
134 if idx <= 0 {
135 return None;
136 }
137 let idx = idx as usize;
138
139 if let Some(remap) = pn_index {
140 remap.get(idx - 1).copied().filter(|mapped| *mapped > 0)
141 } else {
142 Some(idx as u32)
143 }
144 })
145 .collect()
146 }
147
148 fn triangulate_polygon(
153 outer_indices: &[u32],
154 inner_indices: &[Vec<u32>],
155 positions: &[f32],
156 output: &mut Vec<u32>,
157 ) {
158 if outer_indices.len() < 3 {
159 return;
160 }
161
162 let get_pos = |idx: u32| -> Option<(f32, f32, f32)> {
164 if idx == 0 {
165 return None;
166 }
167 let base = ((idx - 1) * 3) as usize;
168 if base + 2 < positions.len() {
169 Some((positions[base], positions[base + 1], positions[base + 2]))
170 } else {
171 None
172 }
173 };
174
175 let mut sum_x = 0.0f64;
181 let mut sum_y = 0.0f64;
182 let mut sum_z = 0.0f64;
183
184 for i in 0..outer_indices.len() {
186 let v0 = match get_pos(outer_indices[i]) {
187 Some(p) => p,
188 None => {
189 let first = outer_indices[0] - 1;
191 for j in 1..outer_indices.len() - 1 {
192 output.push(first);
193 output.push(outer_indices[j] - 1);
194 output.push(outer_indices[j + 1] - 1);
195 }
196 return;
197 }
198 };
199 let v1 = match get_pos(outer_indices[(i + 1) % outer_indices.len()]) {
200 Some(p) => p,
201 None => {
202 let first = outer_indices[0] - 1;
203 for j in 1..outer_indices.len() - 1 {
204 output.push(first);
205 output.push(outer_indices[j] - 1);
206 output.push(outer_indices[j + 1] - 1);
207 }
208 return;
209 }
210 };
211
212 sum_x += (v0.1 - v1.1) as f64 * (v0.2 + v1.2) as f64;
213 sum_y += (v0.2 - v1.2) as f64 * (v0.0 + v1.0) as f64;
214 sum_z += (v0.0 - v1.0) as f64 * (v0.1 + v1.1) as f64;
215 }
216 let expected_normal = (sum_x, sum_y, sum_z);
217
218 let mut push_oriented_triangle = |a: u32, b: u32, c: u32| {
219 if a == 0 || b == 0 || c == 0 {
220 return;
221 }
222 let i0 = a - 1;
223 let mut i1 = b - 1;
224 let mut i2 = c - 1;
225
226 if expected_normal.0.abs() + expected_normal.1.abs() + expected_normal.2.abs() > 1e-12 {
227 if let (Some(p0), Some(p1), Some(p2)) = (get_pos(a), get_pos(b), get_pos(c)) {
228 let e1 = (
229 (p1.0 - p0.0) as f64,
230 (p1.1 - p0.1) as f64,
231 (p1.2 - p0.2) as f64,
232 );
233 let e2 = (
234 (p2.0 - p0.0) as f64,
235 (p2.1 - p0.1) as f64,
236 (p2.2 - p0.2) as f64,
237 );
238 let tri_normal = (
239 e1.1 * e2.2 - e1.2 * e2.1,
240 e1.2 * e2.0 - e1.0 * e2.2,
241 e1.0 * e2.1 - e1.1 * e2.0,
242 );
243 let dot = tri_normal.0 * expected_normal.0
244 + tri_normal.1 * expected_normal.1
245 + tri_normal.2 * expected_normal.2;
246 if dot < 0.0 {
247 std::mem::swap(&mut i1, &mut i2);
248 }
249 }
250 }
251
252 output.push(i0);
253 output.push(i1);
254 output.push(i2);
255 };
256
257 if inner_indices.is_empty() && outer_indices.len() == 3 {
259 push_oriented_triangle(outer_indices[0], outer_indices[1], outer_indices[2]);
260 return;
261 }
262
263 if inner_indices.is_empty() && outer_indices.len() == 4 {
265 push_oriented_triangle(outer_indices[0], outer_indices[1], outer_indices[2]);
266 push_oriented_triangle(outer_indices[0], outer_indices[2], outer_indices[3]);
267 return;
268 }
269
270 let abs_x = sum_x.abs();
272 let abs_y = sum_y.abs();
273 let abs_z = sum_z.abs();
274
275 let valid_holes: Vec<&[u32]> = inner_indices
276 .iter()
277 .filter(|loop_indices| loop_indices.len() >= 3)
278 .map(|loop_indices| loop_indices.as_slice())
279 .collect();
280
281 let total_vertices =
283 outer_indices.len() + valid_holes.iter().map(|loop_indices| loop_indices.len()).sum::<usize>();
284 let mut coords_2d: Vec<f64> = Vec::with_capacity(total_vertices * 2);
285 let mut flattened_indices: Vec<u32> = Vec::with_capacity(total_vertices);
286 let mut hole_starts: Vec<usize> = Vec::with_capacity(valid_holes.len());
287
288 for &idx in outer_indices {
289 let Some(p) = get_pos(idx) else {
290 let first = outer_indices[0];
291 for i in 1..outer_indices.len() - 1 {
292 push_oriented_triangle(first, outer_indices[i], outer_indices[i + 1]);
293 }
294 return;
295 };
296 flattened_indices.push(idx);
297
298 if abs_z >= abs_x && abs_z >= abs_y {
300 coords_2d.push(p.0 as f64);
302 coords_2d.push(p.1 as f64);
303 } else if abs_y >= abs_x {
304 coords_2d.push(p.0 as f64);
306 coords_2d.push(p.2 as f64);
307 } else {
308 coords_2d.push(p.1 as f64);
310 coords_2d.push(p.2 as f64);
311 }
312 }
313
314 for hole in valid_holes {
315 hole_starts.push(flattened_indices.len());
316 for &idx in hole {
317 let Some(p) = get_pos(idx) else {
318 let first = outer_indices[0];
319 for i in 1..outer_indices.len() - 1 {
320 push_oriented_triangle(first, outer_indices[i], outer_indices[i + 1]);
321 }
322 return;
323 };
324 flattened_indices.push(idx);
325
326 if abs_z >= abs_x && abs_z >= abs_y {
328 coords_2d.push(p.0 as f64);
330 coords_2d.push(p.1 as f64);
331 } else if abs_y >= abs_x {
332 coords_2d.push(p.0 as f64);
334 coords_2d.push(p.2 as f64);
335 } else {
336 coords_2d.push(p.1 as f64);
338 coords_2d.push(p.2 as f64);
339 }
340 }
341 }
342
343 if flattened_indices.len() < 3 {
344 return;
345 }
346
347 match earcutr::earcut(&coords_2d, &hole_starts, 2) {
349 Ok(tri_indices) => {
350 for tri in tri_indices.chunks(3) {
351 if tri.len() != 3
352 || tri[0] >= flattened_indices.len()
353 || tri[1] >= flattened_indices.len()
354 || tri[2] >= flattened_indices.len()
355 {
356 continue;
357 }
358 push_oriented_triangle(
359 flattened_indices[tri[0]],
360 flattened_indices[tri[1]],
361 flattened_indices[tri[2]],
362 );
363 }
364 }
365 Err(_) => {
366 let first = outer_indices[0];
368 for i in 1..outer_indices.len() - 1 {
369 push_oriented_triangle(first, outer_indices[i], outer_indices[i + 1]);
370 }
371 }
372 }
373 }
374
375 #[inline]
376 fn parse_face_inner_indices(face_entity: &DecodedEntity, pn_index: Option<&[u32]>) -> Vec<Vec<u32>> {
377 if face_entity.ifc_type != IfcType::IfcIndexedPolygonalFaceWithVoids {
378 return Vec::new();
379 }
380
381 let Some(inner_attr) = face_entity.get(1).and_then(|a| a.as_list()) else {
382 return Vec::new();
383 };
384
385 let mut result = Vec::with_capacity(inner_attr.len());
386 for loop_attr in inner_attr {
387 let Some(loop_values) = loop_attr.as_list() else {
388 continue;
389 };
390 let parsed = Self::parse_index_loop(loop_values, pn_index);
391 if parsed.len() >= 3 {
392 result.push(parsed);
393 }
394 }
395
396 result
397 }
398
399 #[inline]
400 fn orient_closed_shell_outward(positions: &[f32], indices: &mut [u32]) {
401 if indices.len() < 3 || positions.len() < 9 {
402 return;
403 }
404
405 let vertex_count = positions.len() / 3;
406 if vertex_count == 0 {
407 return;
408 }
409
410 let mut cx = 0.0f64;
412 let mut cy = 0.0f64;
413 let mut cz = 0.0f64;
414 for p in positions.chunks_exact(3) {
415 cx += p[0] as f64;
416 cy += p[1] as f64;
417 cz += p[2] as f64;
418 }
419 let inv_n = 1.0 / vertex_count as f64;
420 cx *= inv_n;
421 cy *= inv_n;
422 cz *= inv_n;
423
424 let mut sign_accum = 0.0f64;
425 for tri in indices.chunks_exact(3) {
426 let i0 = tri[0] as usize;
427 let i1 = tri[1] as usize;
428 let i2 = tri[2] as usize;
429 if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
430 continue;
431 }
432
433 let p0 = (
434 positions[i0 * 3] as f64,
435 positions[i0 * 3 + 1] as f64,
436 positions[i0 * 3 + 2] as f64,
437 );
438 let p1 = (
439 positions[i1 * 3] as f64,
440 positions[i1 * 3 + 1] as f64,
441 positions[i1 * 3 + 2] as f64,
442 );
443 let p2 = (
444 positions[i2 * 3] as f64,
445 positions[i2 * 3 + 1] as f64,
446 positions[i2 * 3 + 2] as f64,
447 );
448
449 let e1 = (p1.0 - p0.0, p1.1 - p0.1, p1.2 - p0.2);
450 let e2 = (p2.0 - p0.0, p2.1 - p0.1, p2.2 - p0.2);
451 let n = (
452 e1.1 * e2.2 - e1.2 * e2.1,
453 e1.2 * e2.0 - e1.0 * e2.2,
454 e1.0 * e2.1 - e1.1 * e2.0,
455 );
456
457 let tc = (
458 (p0.0 + p1.0 + p2.0) / 3.0,
459 (p0.1 + p1.1 + p2.1) / 3.0,
460 (p0.2 + p1.2 + p2.2) / 3.0,
461 );
462 let out = (tc.0 - cx, tc.1 - cy, tc.2 - cz);
463 sign_accum += n.0 * out.0 + n.1 * out.1 + n.2 * out.2;
464 }
465
466 if sign_accum < 0.0 {
468 for tri in indices.chunks_exact_mut(3) {
469 tri.swap(1, 2);
470 }
471 }
472 }
473
474 #[inline]
475 fn build_flat_shaded_mesh(positions: &[f32], indices: &[u32]) -> Mesh {
476 let mut flat_positions: Vec<f32> = Vec::with_capacity(indices.len() * 3);
477 let mut flat_normals: Vec<f32> = Vec::with_capacity(indices.len() * 3);
478 let mut flat_indices: Vec<u32> = Vec::with_capacity(indices.len());
479
480 let vertex_count = positions.len() / 3;
481 let mut next_index: u32 = 0;
482
483 for tri in indices.chunks_exact(3) {
484 let i0 = tri[0] as usize;
485 let i1 = tri[1] as usize;
486 let i2 = tri[2] as usize;
487 if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
488 continue;
489 }
490
491 let p0 = (
492 positions[i0 * 3] as f64,
493 positions[i0 * 3 + 1] as f64,
494 positions[i0 * 3 + 2] as f64,
495 );
496 let p1 = (
497 positions[i1 * 3] as f64,
498 positions[i1 * 3 + 1] as f64,
499 positions[i1 * 3 + 2] as f64,
500 );
501 let p2 = (
502 positions[i2 * 3] as f64,
503 positions[i2 * 3 + 1] as f64,
504 positions[i2 * 3 + 2] as f64,
505 );
506
507 let e1 = (p1.0 - p0.0, p1.1 - p0.1, p1.2 - p0.2);
508 let e2 = (p2.0 - p0.0, p2.1 - p0.1, p2.2 - p0.2);
509 let nx = e1.1 * e2.2 - e1.2 * e2.1;
510 let ny = e1.2 * e2.0 - e1.0 * e2.2;
511 let nz = e1.0 * e2.1 - e1.1 * e2.0;
512 let len = (nx * nx + ny * ny + nz * nz).sqrt();
513 let (nx, ny, nz) = if len > 1e-12 {
514 (nx / len, ny / len, nz / len)
515 } else {
516 (0.0, 0.0, 1.0)
517 };
518
519 for &idx in &[i0, i1, i2] {
520 flat_positions.push(positions[idx * 3]);
521 flat_positions.push(positions[idx * 3 + 1]);
522 flat_positions.push(positions[idx * 3 + 2]);
523 flat_normals.push(nx as f32);
524 flat_normals.push(ny as f32);
525 flat_normals.push(nz as f32);
526 flat_indices.push(next_index);
527 next_index += 1;
528 }
529 }
530
531 Mesh {
532 positions: flat_positions,
533 normals: flat_normals,
534 indices: flat_indices,
535 }
536 }
537}
538
539impl GeometryProcessor for PolygonalFaceSetProcessor {
540 fn process(
541 &self,
542 entity: &DecodedEntity,
543 decoder: &mut EntityDecoder,
544 _schema: &IfcSchema,
545 ) -> Result<Mesh> {
546 let coords_attr = entity.get(0).ok_or_else(|| {
554 Error::geometry("PolygonalFaceSet missing Coordinates".to_string())
555 })?;
556
557 let coord_entity_id = coords_attr.as_entity_ref().ok_or_else(|| {
558 Error::geometry("Expected entity reference for Coordinates".to_string())
559 })?;
560
561 use ifc_lite_core::extract_coordinate_list_from_entity;
563
564 let positions = if let Some(raw_bytes) = decoder.get_raw_bytes(coord_entity_id) {
565 extract_coordinate_list_from_entity(raw_bytes).unwrap_or_default()
566 } else {
567 let coords_entity = decoder.decode_by_id(coord_entity_id)?;
569 let coord_list_attr = coords_entity.get(0).ok_or_else(|| {
570 Error::geometry("CartesianPointList3D missing CoordList".to_string())
571 })?;
572 let coord_list = coord_list_attr
573 .as_list()
574 .ok_or_else(|| Error::geometry("Expected coordinate list".to_string()))?;
575 AttributeValue::parse_coordinate_list_3d(coord_list)
576 };
577
578 if positions.is_empty() {
579 return Ok(Mesh::new());
580 }
581
582 let faces_attr = entity.get(2).ok_or_else(|| {
584 Error::geometry("PolygonalFaceSet missing Faces".to_string())
585 })?;
586
587 let face_refs = faces_attr
588 .as_list()
589 .ok_or_else(|| Error::geometry("Expected faces list".to_string()))?;
590
591 let pn_index = entity
594 .get(3)
595 .and_then(|attr| attr.as_list())
596 .map(|list| {
597 list.iter()
598 .filter_map(|value| value.as_int())
599 .filter(|v| *v > 0)
600 .map(|v| v as u32)
601 .collect::<Vec<u32>>()
602 });
603
604 let mut indices = Vec::with_capacity(face_refs.len() * 6);
606
607 for face_ref in face_refs {
609 let face_id = face_ref.as_entity_ref().ok_or_else(|| {
610 Error::geometry("Expected entity reference for face".to_string())
611 })?;
612
613 let face_entity = decoder.decode_by_id(face_id)?;
614
615 let coord_index_attr = face_entity.get(0).ok_or_else(|| {
618 Error::geometry("IndexedPolygonalFace missing CoordIndex".to_string())
619 })?;
620
621 let coord_indices = coord_index_attr
622 .as_list()
623 .ok_or_else(|| Error::geometry("Expected coord index list".to_string()))?;
624
625 let face_indices = Self::parse_index_loop(coord_indices, pn_index.as_deref());
627 if face_indices.len() < 3 {
628 continue;
629 }
630
631 let inner_indices = Self::parse_face_inner_indices(&face_entity, pn_index.as_deref());
633
634 Self::triangulate_polygon(&face_indices, &inner_indices, &positions, &mut indices);
636 }
637
638 let is_closed = entity
641 .get(1)
642 .and_then(|a| a.as_enum())
643 .map(|v| v == "T")
644 .unwrap_or(false);
645 if is_closed {
646 Self::orient_closed_shell_outward(&positions, &mut indices);
647 }
648
649 Ok(Self::build_flat_shaded_mesh(&positions, &indices))
650 }
651
652 fn supported_types(&self) -> Vec<IfcType> {
653 vec![IfcType::IfcPolygonalFaceSet]
654 }
655}
656
657impl Default for PolygonalFaceSetProcessor {
658 fn default() -> Self {
659 Self::new()
660 }
661}