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 let mut mesh = Mesh {
102 positions,
103 normals: Vec::new(),
104 indices,
105 rtc_applied: false,
106 };
107 mesh.validate_indices();
109 Ok(mesh)
110 }
111
112 fn supported_types(&self) -> Vec<IfcType> {
113 vec![IfcType::IfcTriangulatedFaceSet]
114 }
115}
116
117impl Default for TriangulatedFaceSetProcessor {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123pub struct PolygonalFaceSetProcessor;
126
127impl PolygonalFaceSetProcessor {
128 pub fn new() -> Self {
129 Self
130 }
131
132 #[inline]
133 fn parse_index_loop(indices: &[AttributeValue], pn_index: Option<&[u32]>) -> Vec<u32> {
134 indices
135 .iter()
136 .filter_map(|value| {
137 let idx = value.as_int()?;
138 if idx <= 0 {
139 return None;
140 }
141 let idx = idx as usize;
142
143 if let Some(remap) = pn_index {
144 remap.get(idx - 1).copied().filter(|mapped| *mapped > 0)
145 } else {
146 Some(idx as u32)
147 }
148 })
149 .collect()
150 }
151
152 fn triangulate_polygon(
157 outer_indices: &[u32],
158 inner_indices: &[Vec<u32>],
159 positions: &[f32],
160 output: &mut Vec<u32>,
161 ) {
162 if outer_indices.len() < 3 {
163 return;
164 }
165
166 let get_pos = |idx: u32| -> Option<(f32, f32, f32)> {
168 if idx == 0 {
169 return None;
170 }
171 let base = ((idx - 1) * 3) as usize;
172 if base + 2 < positions.len() {
173 Some((positions[base], positions[base + 1], positions[base + 2]))
174 } else {
175 None
176 }
177 };
178
179 if outer_indices.is_empty() {
181 return;
182 }
183
184 let mut sum_x = 0.0f64;
190 let mut sum_y = 0.0f64;
191 let mut sum_z = 0.0f64;
192
193 for i in 0..outer_indices.len() {
195 let v0 = match get_pos(outer_indices[i]) {
196 Some(p) => p,
197 None => {
198 return;
201 }
202 };
203 let v1 = match get_pos(outer_indices[(i + 1) % outer_indices.len()]) {
204 Some(p) => p,
205 None => {
206 return;
207 }
208 };
209
210 sum_x += (v0.1 - v1.1) as f64 * (v0.2 + v1.2) as f64;
211 sum_y += (v0.2 - v1.2) as f64 * (v0.0 + v1.0) as f64;
212 sum_z += (v0.0 - v1.0) as f64 * (v0.1 + v1.1) as f64;
213 }
214 let expected_normal = (sum_x, sum_y, sum_z);
215
216 let mut push_oriented_triangle = |a: u32, b: u32, c: u32| {
217 if a == 0 || b == 0 || c == 0 {
218 return;
219 }
220 let i0 = a - 1;
221 let mut i1 = b - 1;
222 let mut i2 = c - 1;
223
224 if expected_normal.0.abs() + expected_normal.1.abs() + expected_normal.2.abs() > 1e-12 {
225 if let (Some(p0), Some(p1), Some(p2)) = (get_pos(a), get_pos(b), get_pos(c)) {
226 let e1 = (
227 (p1.0 - p0.0) as f64,
228 (p1.1 - p0.1) as f64,
229 (p1.2 - p0.2) as f64,
230 );
231 let e2 = (
232 (p2.0 - p0.0) as f64,
233 (p2.1 - p0.1) as f64,
234 (p2.2 - p0.2) as f64,
235 );
236 let tri_normal = (
237 e1.1 * e2.2 - e1.2 * e2.1,
238 e1.2 * e2.0 - e1.0 * e2.2,
239 e1.0 * e2.1 - e1.1 * e2.0,
240 );
241 let dot = tri_normal.0 * expected_normal.0
242 + tri_normal.1 * expected_normal.1
243 + tri_normal.2 * expected_normal.2;
244 if dot < 0.0 {
245 std::mem::swap(&mut i1, &mut i2);
246 }
247 }
248 }
249
250 output.push(i0);
251 output.push(i1);
252 output.push(i2);
253 };
254
255 if inner_indices.is_empty() && outer_indices.len() == 3 {
257 push_oriented_triangle(outer_indices[0], outer_indices[1], outer_indices[2]);
258 return;
259 }
260
261 if inner_indices.is_empty() && outer_indices.len() == 4 {
263 push_oriented_triangle(outer_indices[0], outer_indices[1], outer_indices[2]);
264 push_oriented_triangle(outer_indices[0], outer_indices[2], outer_indices[3]);
265 return;
266 }
267
268 let abs_x = sum_x.abs();
270 let abs_y = sum_y.abs();
271 let abs_z = sum_z.abs();
272
273 let valid_holes: Vec<&[u32]> = inner_indices
274 .iter()
275 .filter(|loop_indices| loop_indices.len() >= 3)
276 .map(|loop_indices| loop_indices.as_slice())
277 .collect();
278
279 let total_vertices = outer_indices.len()
281 + valid_holes
282 .iter()
283 .map(|loop_indices| loop_indices.len())
284 .sum::<usize>();
285 let mut coords_2d: Vec<f64> = Vec::with_capacity(total_vertices * 2);
286 let mut flattened_indices: Vec<u32> = Vec::with_capacity(total_vertices);
287 let mut hole_starts: Vec<usize> = Vec::with_capacity(valid_holes.len());
288
289 for &idx in outer_indices {
290 let Some(p) = get_pos(idx) else {
291 return;
293 };
294 flattened_indices.push(idx);
295
296 if abs_z >= abs_x && abs_z >= abs_y {
298 coords_2d.push(p.0 as f64);
300 coords_2d.push(p.1 as f64);
301 } else if abs_y >= abs_x {
302 coords_2d.push(p.0 as f64);
304 coords_2d.push(p.2 as f64);
305 } else {
306 coords_2d.push(p.1 as f64);
308 coords_2d.push(p.2 as f64);
309 }
310 }
311
312 for hole in valid_holes {
313 hole_starts.push(flattened_indices.len());
314 for &idx in hole {
315 let Some(p) = get_pos(idx) else {
316 return;
318 };
319 flattened_indices.push(idx);
320
321 if abs_z >= abs_x && abs_z >= abs_y {
323 coords_2d.push(p.0 as f64);
325 coords_2d.push(p.1 as f64);
326 } else if abs_y >= abs_x {
327 coords_2d.push(p.0 as f64);
329 coords_2d.push(p.2 as f64);
330 } else {
331 coords_2d.push(p.1 as f64);
333 coords_2d.push(p.2 as f64);
334 }
335 }
336 }
337
338 if flattened_indices.len() < 3 {
339 return;
340 }
341
342 match earcutr::earcut(&coords_2d, &hole_starts, 2) {
344 Ok(tri_indices) => {
345 for tri in tri_indices.chunks(3) {
346 if tri.len() != 3
347 || tri[0] >= flattened_indices.len()
348 || tri[1] >= flattened_indices.len()
349 || tri[2] >= flattened_indices.len()
350 {
351 continue;
352 }
353 push_oriented_triangle(
354 flattened_indices[tri[0]],
355 flattened_indices[tri[1]],
356 flattened_indices[tri[2]],
357 );
358 }
359 }
360 Err(_) => {
361 let first = outer_indices[0];
363 for i in 1..outer_indices.len() - 1 {
364 push_oriented_triangle(first, outer_indices[i], outer_indices[i + 1]);
365 }
366 }
367 }
368 }
369
370 #[inline]
371 fn parse_face_inner_indices(
372 face_entity: &DecodedEntity,
373 pn_index: Option<&[u32]>,
374 ) -> Vec<Vec<u32>> {
375 if face_entity.ifc_type != IfcType::IfcIndexedPolygonalFaceWithVoids {
376 return Vec::new();
377 }
378
379 let Some(inner_attr) = face_entity.get(1).and_then(|a| a.as_list()) else {
380 return Vec::new();
381 };
382
383 let mut result = Vec::with_capacity(inner_attr.len());
384 for loop_attr in inner_attr {
385 let Some(loop_values) = loop_attr.as_list() else {
386 continue;
387 };
388 let parsed = Self::parse_index_loop(loop_values, pn_index);
389 if parsed.len() >= 3 {
390 result.push(parsed);
391 }
392 }
393
394 result
395 }
396
397 #[inline]
398 fn orient_closed_shell_outward(positions: &[f32], indices: &mut [u32]) {
399 if indices.len() < 3 || positions.len() < 9 {
400 return;
401 }
402
403 let vertex_count = positions.len() / 3;
404 if vertex_count == 0 {
405 return;
406 }
407
408 let mut cx = 0.0f64;
410 let mut cy = 0.0f64;
411 let mut cz = 0.0f64;
412 for p in positions.chunks_exact(3) {
413 cx += p[0] as f64;
414 cy += p[1] as f64;
415 cz += p[2] as f64;
416 }
417 let inv_n = 1.0 / vertex_count as f64;
418 cx *= inv_n;
419 cy *= inv_n;
420 cz *= inv_n;
421
422 let mut sign_accum = 0.0f64;
423 for tri in indices.chunks_exact(3) {
424 let i0 = tri[0] as usize;
425 let i1 = tri[1] as usize;
426 let i2 = tri[2] as usize;
427 if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
428 continue;
429 }
430
431 let p0 = (
432 positions[i0 * 3] as f64,
433 positions[i0 * 3 + 1] as f64,
434 positions[i0 * 3 + 2] as f64,
435 );
436 let p1 = (
437 positions[i1 * 3] as f64,
438 positions[i1 * 3 + 1] as f64,
439 positions[i1 * 3 + 2] as f64,
440 );
441 let p2 = (
442 positions[i2 * 3] as f64,
443 positions[i2 * 3 + 1] as f64,
444 positions[i2 * 3 + 2] as f64,
445 );
446
447 let e1 = (p1.0 - p0.0, p1.1 - p0.1, p1.2 - p0.2);
448 let e2 = (p2.0 - p0.0, p2.1 - p0.1, p2.2 - p0.2);
449 let n = (
450 e1.1 * e2.2 - e1.2 * e2.1,
451 e1.2 * e2.0 - e1.0 * e2.2,
452 e1.0 * e2.1 - e1.1 * e2.0,
453 );
454
455 let tc = (
456 (p0.0 + p1.0 + p2.0) / 3.0,
457 (p0.1 + p1.1 + p2.1) / 3.0,
458 (p0.2 + p1.2 + p2.2) / 3.0,
459 );
460 let out = (tc.0 - cx, tc.1 - cy, tc.2 - cz);
461 sign_accum += n.0 * out.0 + n.1 * out.1 + n.2 * out.2;
462 }
463
464 if sign_accum < 0.0 {
466 for tri in indices.chunks_exact_mut(3) {
467 tri.swap(1, 2);
468 }
469 }
470 }
471
472 #[inline]
473 fn build_flat_shaded_mesh(positions: &[f32], indices: &[u32]) -> Mesh {
474 let mut flat_positions: Vec<f32> = Vec::with_capacity(indices.len() * 3);
475 let mut flat_normals: Vec<f32> = Vec::with_capacity(indices.len() * 3);
476 let mut flat_indices: Vec<u32> = Vec::with_capacity(indices.len());
477
478 let vertex_count = positions.len() / 3;
479 let mut next_index: u32 = 0;
480
481 for tri in indices.chunks_exact(3) {
482 let i0 = tri[0] as usize;
483 let i1 = tri[1] as usize;
484 let i2 = tri[2] as usize;
485 if i0 >= vertex_count || i1 >= vertex_count || i2 >= vertex_count {
486 continue;
487 }
488
489 let p0 = (
490 positions[i0 * 3] as f64,
491 positions[i0 * 3 + 1] as f64,
492 positions[i0 * 3 + 2] as f64,
493 );
494 let p1 = (
495 positions[i1 * 3] as f64,
496 positions[i1 * 3 + 1] as f64,
497 positions[i1 * 3 + 2] as f64,
498 );
499 let p2 = (
500 positions[i2 * 3] as f64,
501 positions[i2 * 3 + 1] as f64,
502 positions[i2 * 3 + 2] as f64,
503 );
504
505 let e1 = (p1.0 - p0.0, p1.1 - p0.1, p1.2 - p0.2);
506 let e2 = (p2.0 - p0.0, p2.1 - p0.1, p2.2 - p0.2);
507 let nx = e1.1 * e2.2 - e1.2 * e2.1;
508 let ny = e1.2 * e2.0 - e1.0 * e2.2;
509 let nz = e1.0 * e2.1 - e1.1 * e2.0;
510 let len = (nx * nx + ny * ny + nz * nz).sqrt();
511 let (nx, ny, nz) = if len > 1e-12 {
512 (nx / len, ny / len, nz / len)
513 } else {
514 (0.0, 0.0, 1.0)
515 };
516
517 for &idx in &[i0, i1, i2] {
518 flat_positions.push(positions[idx * 3]);
519 flat_positions.push(positions[idx * 3 + 1]);
520 flat_positions.push(positions[idx * 3 + 2]);
521 flat_normals.push(nx as f32);
522 flat_normals.push(ny as f32);
523 flat_normals.push(nz as f32);
524 flat_indices.push(next_index);
525 next_index += 1;
526 }
527 }
528
529 Mesh {
530 positions: flat_positions,
531 normals: flat_normals,
532 indices: flat_indices,
533 rtc_applied: false,
534 }
535 }
536}
537
538impl GeometryProcessor for PolygonalFaceSetProcessor {
539 fn process(
540 &self,
541 entity: &DecodedEntity,
542 decoder: &mut EntityDecoder,
543 _schema: &IfcSchema,
544 ) -> Result<Mesh> {
545 let coords_attr = entity
553 .get(0)
554 .ok_or_else(|| Error::geometry("PolygonalFaceSet missing Coordinates".to_string()))?;
555
556 let coord_entity_id = coords_attr.as_entity_ref().ok_or_else(|| {
557 Error::geometry("Expected entity reference for Coordinates".to_string())
558 })?;
559
560 use ifc_lite_core::extract_coordinate_list_from_entity;
562
563 let positions = if let Some(raw_bytes) = decoder.get_raw_bytes(coord_entity_id) {
564 extract_coordinate_list_from_entity(raw_bytes).unwrap_or_default()
565 } else {
566 let coords_entity = decoder.decode_by_id(coord_entity_id)?;
568 let coord_list_attr = coords_entity.get(0).ok_or_else(|| {
569 Error::geometry("CartesianPointList3D missing CoordList".to_string())
570 })?;
571 let coord_list = coord_list_attr
572 .as_list()
573 .ok_or_else(|| Error::geometry("Expected coordinate list".to_string()))?;
574 AttributeValue::parse_coordinate_list_3d(coord_list)
575 };
576
577 if positions.is_empty() {
578 return Ok(Mesh::new());
579 }
580
581 let faces_attr = entity
583 .get(2)
584 .ok_or_else(|| Error::geometry("PolygonalFaceSet missing Faces".to_string()))?;
585
586 let face_refs = faces_attr
587 .as_list()
588 .ok_or_else(|| Error::geometry("Expected faces list".to_string()))?;
589
590 let pn_index = entity.get(3).and_then(|attr| attr.as_list()).map(|list| {
593 list.iter()
594 .filter_map(|value| value.as_int())
595 .filter(|v| *v > 0)
596 .map(|v| v as u32)
597 .collect::<Vec<u32>>()
598 });
599
600 let mut indices = Vec::with_capacity(face_refs.len() * 6);
602
603 for face_ref in face_refs {
605 let face_id = face_ref
606 .as_entity_ref()
607 .ok_or_else(|| Error::geometry("Expected entity reference for face".to_string()))?;
608
609 let face_entity = decoder.decode_by_id(face_id)?;
610
611 let coord_index_attr = face_entity.get(0).ok_or_else(|| {
614 Error::geometry("IndexedPolygonalFace missing CoordIndex".to_string())
615 })?;
616
617 let coord_indices = coord_index_attr
618 .as_list()
619 .ok_or_else(|| Error::geometry("Expected coord index list".to_string()))?;
620
621 let face_indices = Self::parse_index_loop(coord_indices, pn_index.as_deref());
623 if face_indices.len() < 3 {
624 continue;
625 }
626
627 let inner_indices = Self::parse_face_inner_indices(&face_entity, pn_index.as_deref());
629
630 Self::triangulate_polygon(&face_indices, &inner_indices, &positions, &mut indices);
632 }
633
634 let is_closed = entity
637 .get(1)
638 .and_then(|a| a.as_enum())
639 .map(|v| v == "T")
640 .unwrap_or(false);
641 if is_closed {
642 Self::orient_closed_shell_outward(&positions, &mut indices);
643 }
644
645 Ok(Self::build_flat_shaded_mesh(&positions, &indices))
646 }
647
648 fn supported_types(&self) -> Vec<IfcType> {
649 vec![IfcType::IfcPolygonalFaceSet]
650 }
651}
652
653impl Default for PolygonalFaceSetProcessor {
654 fn default() -> Self {
655 Self::new()
656 }
657}