1use super::GeometryRouter;
8use crate::csg::{ClippingProcessor, Plane, Triangle, TriangleVec};
9use crate::{Error, Mesh, Point3, Result, Vector3};
10use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcType};
11use nalgebra::Matrix4;
12use rustc_hash::FxHashMap;
13
14const NORMALIZE_EPSILON: f64 = 1e-12;
16const MIN_OPENING_VOLUME: f64 = 0.0001;
19const CSG_TRIANGLE_RETENTION_DIVISOR: usize = 4;
23const MIN_VALID_TRIANGLES: usize = 4;
25
26fn extract_rotation_columns(m: &Matrix4<f64>) -> (Vector3<f64>, Vector3<f64>, Vector3<f64>) {
28 (
29 Vector3::new(m[(0, 0)], m[(1, 0)], m[(2, 0)]),
30 Vector3::new(m[(0, 1)], m[(1, 1)], m[(2, 1)]),
31 Vector3::new(m[(0, 2)], m[(1, 2)], m[(2, 2)]),
32 )
33}
34
35fn rotate_and_normalize(
37 rot: &(Vector3<f64>, Vector3<f64>, Vector3<f64>),
38 dir: &Vector3<f64>,
39) -> Result<Vector3<f64>> {
40 (rot.0 * dir.x + rot.1 * dir.y + rot.2 * dir.z)
41 .try_normalize(NORMALIZE_EPSILON)
42 .ok_or_else(|| Error::geometry("Zero-length direction vector".to_string()))
43}
44
45fn is_body_representation(rep_type: &str) -> bool {
47 matches!(
48 rep_type,
49 "Body" | "SweptSolid" | "Brep" | "CSG" | "Clipping" | "Tessellation"
50 | "MappedRepresentation" | "SolidModel" | "SurfaceModel"
51 | "AdvancedSweptSolid" | "AdvancedBrep"
52 )
53}
54
55enum OpeningType {
57 Rectangular(Point3<f64>, Point3<f64>, Option<Vector3<f64>>),
60 DiagonalRectangular(Mesh, Vector3<f64>),
63 NonRectangular(Mesh),
66}
67
68struct ClipBuffers {
73 result: TriangleVec,
75 remaining: TriangleVec,
77 next_remaining: TriangleVec,
79}
80
81impl ClipBuffers {
82 fn new() -> Self {
84 Self {
85 result: TriangleVec::new(),
86 remaining: TriangleVec::new(),
87 next_remaining: TriangleVec::new(),
88 }
89 }
90
91 #[inline]
93 fn clear(&mut self) {
94 self.result.clear();
95 self.remaining.clear();
96 self.next_remaining.clear();
97 }
98}
99
100impl GeometryRouter {
101 fn extract_extrusion_direction_from_solid(
108 &self,
109 solid: &DecodedEntity,
110 decoder: &mut EntityDecoder,
111 ) -> Option<(Vector3<f64>, Option<Matrix4<f64>>)> {
112 let direction_attr = solid.get(2)?;
114 let direction_entity = decoder.resolve_ref(direction_attr).ok()??;
115 let local_dir = self.parse_direction(&direction_entity).ok()?;
116
117 let position_transform = if let Some(pos_attr) = solid.get(1) {
119 if !pos_attr.is_null() {
120 if let Ok(Some(pos_entity)) = decoder.resolve_ref(pos_attr) {
121 if pos_entity.ifc_type == IfcType::IfcAxis2Placement3D {
122 self.parse_axis2_placement_3d(&pos_entity, decoder).ok()
123 } else {
124 None
125 }
126 } else {
127 None
128 }
129 } else {
130 None
131 }
132 } else {
133 None
134 };
135
136 Some((local_dir, position_transform))
137 }
138
139 fn extract_extrusion_direction_recursive(
143 &self,
144 item: &DecodedEntity,
145 decoder: &mut EntityDecoder,
146 ) -> Option<(Vector3<f64>, Option<Matrix4<f64>>)> {
147 match item.ifc_type {
148 IfcType::IfcExtrudedAreaSolid => {
149 self.extract_extrusion_direction_from_solid(item, decoder)
151 }
152 IfcType::IfcBooleanClippingResult | IfcType::IfcBooleanResult => {
153 let first_attr = item.get(1)?;
155 let first_operand = decoder.resolve_ref(first_attr).ok()??;
156 self.extract_extrusion_direction_recursive(&first_operand, decoder)
157 }
158 IfcType::IfcMappedItem => {
159 let source_attr = item.get(0)?;
161 let source = decoder.resolve_ref(source_attr).ok()??;
162 let rep_attr = source.get(1)?;
164 let rep = decoder.resolve_ref(rep_attr).ok()??;
165
166 let mapping_transform = if let Some(target_attr) = item.get(1) {
168 if !target_attr.is_null() {
169 if let Ok(Some(target)) = decoder.resolve_ref(target_attr) {
170 self.parse_cartesian_transformation_operator(&target, decoder).ok()
171 } else {
172 None
173 }
174 } else {
175 None
176 }
177 } else {
178 None
179 };
180
181 let items_attr = rep.get(3)?;
183 let items = decoder.resolve_ref_list(items_attr).ok()?;
184 items.first().and_then(|first| {
185 self.extract_extrusion_direction_recursive(first, decoder).map(|(dir, pos)| {
186 let combined = match (mapping_transform.as_ref(), pos) {
188 (Some(map), Some(pos)) => Some(map * pos),
189 (Some(map), None) => Some(map.clone()),
190 (None, Some(pos)) => Some(pos),
191 (None, None) => None,
192 };
193 (dir, combined)
194 })
195 })
196 }
197 _ => None,
198 }
199 }
200
201 pub fn get_opening_item_meshes_world(
205 &self,
206 element: &DecodedEntity,
207 decoder: &mut EntityDecoder,
208 ) -> Result<Vec<Mesh>> {
209 let representation_attr = element.get(6).ok_or_else(|| {
210 Error::geometry("Element has no representation attribute".to_string())
211 })?;
212 if representation_attr.is_null() {
213 return Ok(vec![]);
214 }
215
216 let representation = decoder.resolve_ref(representation_attr)?
217 .ok_or_else(|| Error::geometry("Failed to resolve representation".to_string()))?;
218 let representations_attr = representation.get(2).ok_or_else(|| {
219 Error::geometry("ProductDefinitionShape missing Representations".to_string())
220 })?;
221 let representations = decoder.resolve_ref_list(representations_attr)?;
222
223 let mut placement_transform = self.get_placement_transform_from_element(element, decoder)
225 .unwrap_or_else(|_| Matrix4::identity());
226 self.scale_transform(&mut placement_transform);
227
228 let mut item_meshes = Vec::new();
229
230 for shape_rep in representations {
231 if shape_rep.ifc_type != IfcType::IfcShapeRepresentation {
232 continue;
233 }
234 if let Some(rep_type_attr) = shape_rep.get(2) {
235 if let Some(rep_type) = rep_type_attr.as_string() {
236 if !is_body_representation(rep_type) {
237 continue;
238 }
239 }
240 }
241 let items_attr = match shape_rep.get(3) {
242 Some(attr) => attr,
243 None => continue,
244 };
245 let items = match decoder.resolve_ref_list(items_attr) {
246 Ok(items) => items,
247 Err(_) => continue,
248 };
249
250 for item in items {
251 let mut mesh = match self.process_representation_item(&item, decoder) {
252 Ok(m) if !m.is_empty() => m,
253 _ => continue,
254 };
255
256 self.transform_mesh(&mut mesh, &placement_transform);
259
260 item_meshes.push(mesh);
261 }
262 }
263
264 Ok(item_meshes)
265 }
266
267 pub fn get_opening_item_bounds_with_direction(
270 &self,
271 element: &DecodedEntity,
272 decoder: &mut EntityDecoder,
273 ) -> Result<Vec<(Point3<f64>, Point3<f64>, Option<Vector3<f64>>)>> {
274 let representation_attr = element.get(6).ok_or_else(|| {
276 Error::geometry("Element has no representation attribute".to_string())
277 })?;
278
279 if representation_attr.is_null() {
280 return Ok(vec![]);
281 }
282
283 let representation = decoder
284 .resolve_ref(representation_attr)?
285 .ok_or_else(|| Error::geometry("Failed to resolve representation".to_string()))?;
286
287 let representations_attr = representation.get(2).ok_or_else(|| {
289 Error::geometry("ProductDefinitionShape missing Representations".to_string())
290 })?;
291
292 let representations = decoder.resolve_ref_list(representations_attr)?;
293
294 let mut placement_transform = self.get_placement_transform_from_element(element, decoder)
296 .unwrap_or_else(|_| Matrix4::identity());
297 self.scale_transform(&mut placement_transform);
298
299 let mut bounds_list = Vec::new();
300
301 for shape_rep in representations {
302 if shape_rep.ifc_type != IfcType::IfcShapeRepresentation {
303 continue;
304 }
305
306 if let Some(rep_type_attr) = shape_rep.get(2) {
308 if let Some(rep_type) = rep_type_attr.as_string() {
309 if !is_body_representation(rep_type) {
310 continue;
311 }
312 }
313 }
314
315 let items_attr = match shape_rep.get(3) {
317 Some(attr) => attr,
318 None => continue,
319 };
320
321 let items = match decoder.resolve_ref_list(items_attr) {
322 Ok(items) => items,
323 Err(_) => continue,
324 };
325
326 for item in items {
328 let extrusion_direction = if let Some((local_dir, position_transform)) =
330 self.extract_extrusion_direction_recursive(&item, decoder)
331 {
332 if let Some(pos_transform) = position_transform {
334 let pos_rot = extract_rotation_columns(&pos_transform);
335 let world_dir = rotate_and_normalize(&pos_rot, &local_dir)?;
336
337 let element_rot = extract_rotation_columns(&placement_transform);
338 let final_dir = rotate_and_normalize(&element_rot, &world_dir)?;
339
340 Some(final_dir)
341 } else {
342 let element_rot = extract_rotation_columns(&placement_transform);
343 let final_dir = rotate_and_normalize(&element_rot, &local_dir)?;
344
345 Some(final_dir)
346 }
347 } else {
348 None
349 };
350
351 let mesh = match self.process_representation_item(&item, decoder) {
353 Ok(m) if !m.is_empty() => m,
354 _ => continue,
355 };
356
357 let (mesh_min, mesh_max) = mesh.bounds();
359
360 let corners = [
362 Point3::new(mesh_min.x as f64, mesh_min.y as f64, mesh_min.z as f64),
363 Point3::new(mesh_max.x as f64, mesh_min.y as f64, mesh_min.z as f64),
364 Point3::new(mesh_min.x as f64, mesh_max.y as f64, mesh_min.z as f64),
365 Point3::new(mesh_max.x as f64, mesh_max.y as f64, mesh_min.z as f64),
366 Point3::new(mesh_min.x as f64, mesh_min.y as f64, mesh_max.z as f64),
367 Point3::new(mesh_max.x as f64, mesh_min.y as f64, mesh_max.z as f64),
368 Point3::new(mesh_min.x as f64, mesh_max.y as f64, mesh_max.z as f64),
369 Point3::new(mesh_max.x as f64, mesh_max.y as f64, mesh_max.z as f64),
370 ];
371
372 let transformed: Vec<Point3<f64>> = corners.iter()
374 .map(|p| placement_transform.transform_point(p))
375 .collect();
376
377 let world_min = Point3::new(
378 transformed.iter().map(|p| p.x).fold(f64::INFINITY, f64::min),
379 transformed.iter().map(|p| p.y).fold(f64::INFINITY, f64::min),
380 transformed.iter().map(|p| p.z).fold(f64::INFINITY, f64::min),
381 );
382 let world_max = Point3::new(
383 transformed.iter().map(|p| p.x).fold(f64::NEG_INFINITY, f64::max),
384 transformed.iter().map(|p| p.y).fold(f64::NEG_INFINITY, f64::max),
385 transformed.iter().map(|p| p.z).fold(f64::NEG_INFINITY, f64::max),
386 );
387
388 let rtc = self.rtc_offset;
391 let rtc_min = Point3::new(
392 world_min.x - rtc.0,
393 world_min.y - rtc.1,
394 world_min.z - rtc.2,
395 );
396 let rtc_max = Point3::new(
397 world_max.x - rtc.0,
398 world_max.y - rtc.1,
399 world_max.z - rtc.2,
400 );
401
402 bounds_list.push((rtc_min, rtc_max, extrusion_direction));
403 }
404 }
405
406 Ok(bounds_list)
407 }
408
409 #[inline]
417 pub fn process_element_with_voids(
434 &self,
435 element: &DecodedEntity,
436 decoder: &mut EntityDecoder,
437 void_index: &FxHashMap<u32, Vec<u32>>,
438 ) -> Result<Mesh> {
439 let opening_ids = match void_index.get(&element.id) {
441 Some(ids) if !ids.is_empty() => ids,
442 _ => {
443 return self.process_element(element, decoder);
445 }
446 };
447
448 let wall_mesh = match self.process_element(element, decoder) {
456 Ok(m) => m,
457 Err(_) => {
458 return self.process_element(element, decoder);
459 }
460 };
461
462 use nalgebra::Vector3;
465 let world_clipping_planes: Vec<(Point3<f64>, Vector3<f64>, bool)> =
466 if self.has_clipping_planes(element, decoder) {
467 let mut object_placement_transform = match self.get_placement_transform_from_element(element, decoder) {
469 Ok(t) => t,
470 Err(_) => Matrix4::identity(),
471 };
472 self.scale_transform(&mut object_placement_transform);
473
474 let clipping_planes = match self.extract_base_profile_and_clips(element, decoder) {
476 Ok((_profile, _depth, _axis, _origin, _transform, clips)) => clips,
477 Err(_) => Vec::new(),
478 };
479
480 clipping_planes
482 .iter()
483 .map(|(point, normal, agreement)| {
484 let world_point = object_placement_transform.transform_point(point);
485 let rotation = object_placement_transform.fixed_view::<3, 3>(0, 0);
486 let world_normal = (rotation * normal).normalize();
487 (world_point, world_normal, *agreement)
488 })
489 .collect()
490 } else {
491 Vec::new()
492 };
493
494 let openings = self.classify_openings(opening_ids, decoder);
495
496 if openings.is_empty() {
497 return self.process_element(element, decoder);
498 }
499
500 use crate::csg::ClippingProcessor;
502 let clipper = ClippingProcessor::new();
503 let mut result = wall_mesh;
504
505 let (wall_min_f32, wall_max_f32) = result.bounds();
507 let wall_min = Point3::new(
508 wall_min_f32.x as f64,
509 wall_min_f32.y as f64,
510 wall_min_f32.z as f64,
511 );
512 let wall_max = Point3::new(
513 wall_max_f32.x as f64,
514 wall_max_f32.y as f64,
515 wall_max_f32.z as f64,
516 );
517
518 let wall_valid = !result.is_empty()
521 && result.positions.iter().all(|&v| v.is_finite())
522 && result.triangle_count() >= 4;
523
524 if !wall_valid {
525 return Ok(result);
527 }
528
529 let mut csg_operation_count = 0;
531 const MAX_CSG_OPERATIONS: usize = 10; self.apply_diagonal_openings(&mut result, &openings, &wall_min, &wall_max);
534
535 for opening in openings.iter() {
536 match opening {
537 OpeningType::Rectangular(open_min, open_max, extrusion_dir) => {
538 let (final_min, final_max) = if let Some(dir) = extrusion_dir {
540 self.extend_opening_along_direction(*open_min, *open_max, wall_min, wall_max, *dir)
542 } else {
543 (*open_min, *open_max)
545 };
546 result = self.cut_rectangular_opening(&result, final_min, final_max);
547 }
548 OpeningType::DiagonalRectangular(_opening_mesh, _extrusion_dir) => {
549 }
551 OpeningType::NonRectangular(opening_mesh) => {
552 if csg_operation_count >= MAX_CSG_OPERATIONS {
554 continue;
556 }
557
558 let opening_valid = !opening_mesh.is_empty()
560 && opening_mesh.positions.iter().all(|&v| v.is_finite())
561 && opening_mesh.positions.len() >= 9; if !opening_valid {
564 continue;
566 }
567
568 let (result_min, result_max) = result.bounds();
581 let (open_min_f32, open_max_f32) = opening_mesh.bounds();
582 let no_overlap =
583 open_max_f32.x < result_min.x || open_min_f32.x > result_max.x ||
584 open_max_f32.y < result_min.y || open_min_f32.y > result_max.y ||
585 open_max_f32.z < result_min.z || open_min_f32.z > result_max.z;
586 if no_overlap {
587 continue;
588 }
589
590 let open_vol = (open_max_f32.x - open_min_f32.x)
592 * (open_max_f32.y - open_min_f32.y)
593 * (open_max_f32.z - open_min_f32.z);
594 if open_vol < MIN_OPENING_VOLUME as f32 {
595 continue;
596 }
597
598 let tri_before = result.triangle_count();
601 match clipper.subtract_mesh(&result, opening_mesh) {
602 Ok(csg_result) => {
603 let min_tris = (tri_before / CSG_TRIANGLE_RETENTION_DIVISOR).max(MIN_VALID_TRIANGLES);
606 if !csg_result.is_empty() && csg_result.triangle_count() >= min_tris {
607 result = csg_result;
608 }
609 }
611 Err(_) => {
612 }
614 }
615 csg_operation_count += 1;
616 }
617 }
618 }
619
620 if !world_clipping_planes.is_empty() {
622 use crate::csg::{ClippingProcessor, Plane};
623 let clipper = ClippingProcessor::new();
624
625 for (_clip_idx, (plane_point, plane_normal, agreement)) in world_clipping_planes.iter().enumerate() {
626 let clip_normal = if *agreement {
627 *plane_normal
628 } else {
629 -*plane_normal
630 };
631
632 let plane = Plane::new(*plane_point, clip_normal);
633 if let Ok(clipped) = clipper.clip_mesh(&result, &plane) {
634 if !clipped.is_empty() {
635 result = clipped;
636 }
637 }
638 }
639 }
640
641 Ok(result)
642 }
643
644 fn classify_openings(
645 &self,
646 opening_ids: &[u32],
647 decoder: &mut EntityDecoder,
648 ) -> Vec<OpeningType> {
649 let mut openings: Vec<OpeningType> = Vec::new();
650 for &opening_id in opening_ids.iter() {
651 let opening_entity = match decoder.decode_by_id(opening_id) {
652 Ok(e) => e,
653 Err(_) => continue,
654 };
655
656 let opening_mesh = match self.process_element(&opening_entity, decoder) {
657 Ok(m) if !m.is_empty() => m,
658 _ => continue,
659 };
660
661 let vertex_count = opening_mesh.positions.len() / 3;
662
663 if vertex_count > 100 {
664 openings.push(OpeningType::NonRectangular(opening_mesh));
665 } else {
666 let item_bounds_with_dir = self.get_opening_item_bounds_with_direction(&opening_entity, decoder)
667 .unwrap_or_default();
668
669 if !item_bounds_with_dir.is_empty() {
670 let is_floor_opening = item_bounds_with_dir.iter().any(|(_, _, dir)| {
671 dir.map(|d| d.z.abs() > 0.95).unwrap_or(false)
672 });
673
674 if is_floor_opening && vertex_count > 0 {
675 openings.push(OpeningType::NonRectangular(opening_mesh.clone()));
676 } else {
677 let any_diagonal = item_bounds_with_dir.iter().any(|(_, _, dir)| {
678 dir.map(|d| {
679 const AXIS_THRESHOLD: f64 = 0.95;
680 let abs_x = d.x.abs();
681 let abs_y = d.y.abs();
682 let abs_z = d.z.abs();
683 !(abs_x > AXIS_THRESHOLD || abs_y > AXIS_THRESHOLD || abs_z > AXIS_THRESHOLD)
684 }).unwrap_or(false)
685 });
686
687 if any_diagonal {
688 if let Some(dir) = item_bounds_with_dir.iter().find_map(|(_, _, d)| *d) {
691 let item_meshes = self.get_opening_item_meshes_world(&opening_entity, decoder)
692 .unwrap_or_default();
693 if item_meshes.is_empty() {
694 openings.push(OpeningType::DiagonalRectangular(opening_mesh.clone(), dir));
695 } else {
696 for item_mesh in item_meshes {
697 openings.push(OpeningType::DiagonalRectangular(item_mesh, dir));
698 }
699 }
700 } else {
701 openings.push(OpeningType::NonRectangular(opening_mesh.clone()));
703 }
704 } else {
705 for (min_pt, max_pt, extrusion_dir) in item_bounds_with_dir {
706 openings.push(OpeningType::Rectangular(min_pt, max_pt, extrusion_dir));
707 }
708 }
709 }
710 } else {
711 let (open_min, open_max) = opening_mesh.bounds();
712 let min_f64 = Point3::new(open_min.x as f64, open_min.y as f64, open_min.z as f64);
713 let max_f64 = Point3::new(open_max.x as f64, open_max.y as f64, open_max.z as f64);
714
715 openings.push(OpeningType::Rectangular(min_f64, max_f64, None));
716 }
717 }
718 }
719 openings
720 }
721
722 fn apply_diagonal_openings(
723 &self,
724 result: &mut Mesh,
725 openings: &[OpeningType],
726 wall_min: &Point3<f64>,
727 wall_max: &Point3<f64>,
728 ) {
729 use nalgebra::Rotation3;
730
731 let diagonal_openings: Vec<(&Mesh, &Vector3<f64>)> = openings.iter()
732 .filter_map(|o| match o {
733 OpeningType::DiagonalRectangular(mesh, dir) => Some((mesh, dir)),
734 _ => None,
735 })
736 .collect();
737
738 if diagonal_openings.is_empty() {
739 return;
740 }
741
742 const DIR_DOT_THRESHOLD: f64 = 0.9998; let mut groups: Vec<(Vector3<f64>, Vec<&Mesh>)> = Vec::new();
747 for (mesh, dir) in &diagonal_openings {
748 let d = *dir;
749 if let Some(group) = groups.iter_mut().find(|(g, _)| d.dot(g).abs() > DIR_DOT_THRESHOLD) {
750 group.1.push(mesh);
751 } else {
752 groups.push((*d, vec![mesh]));
753 }
754 }
755
756 let wall_corners = [
757 Point3::new(wall_min.x, wall_min.y, wall_min.z),
758 Point3::new(wall_max.x, wall_min.y, wall_min.z),
759 Point3::new(wall_min.x, wall_max.y, wall_min.z),
760 Point3::new(wall_max.x, wall_max.y, wall_min.z),
761 Point3::new(wall_min.x, wall_min.y, wall_max.z),
762 Point3::new(wall_max.x, wall_min.y, wall_max.z),
763 Point3::new(wall_min.x, wall_max.y, wall_max.z),
764 Point3::new(wall_max.x, wall_max.y, wall_max.z),
765 ];
766
767 for (extrusion_dir, group_meshes) in &groups {
768 let target = Vector3::new(1.0, 0.0, 0.0);
769 let rotation = Rotation3::rotation_between(extrusion_dir, &target)
770 .unwrap_or(Rotation3::identity());
771 let inv_rotation = rotation.inverse();
772
773 for chunk in result.positions.chunks_exact_mut(3) {
775 let p = rotation * Point3::new(chunk[0] as f64, chunk[1] as f64, chunk[2] as f64);
776 chunk[0] = p.x as f32;
777 chunk[1] = p.y as f32;
778 chunk[2] = p.z as f32;
779 }
780 for chunk in result.normals.chunks_exact_mut(3) {
781 let n = rotation * Vector3::new(chunk[0] as f64, chunk[1] as f64, chunk[2] as f64);
782 chunk[0] = n.x as f32;
783 chunk[1] = n.y as f32;
784 chunk[2] = n.z as f32;
785 }
786
787 let mut wall_x_min = f64::INFINITY;
788 let mut wall_x_max = f64::NEG_INFINITY;
789 for wc in &wall_corners {
790 let rwc = rotation * wc;
791 wall_x_min = wall_x_min.min(rwc.x);
792 wall_x_max = wall_x_max.max(rwc.x);
793 }
794
795 for opening_mesh in group_meshes {
796 let mut rot_min = Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
797 let mut rot_max = Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
798 for chunk in opening_mesh.positions.chunks_exact(3) {
799 let p = rotation * Point3::new(chunk[0] as f64, chunk[1] as f64, chunk[2] as f64);
800 rot_min.x = rot_min.x.min(p.x);
801 rot_min.y = rot_min.y.min(p.y);
802 rot_min.z = rot_min.z.min(p.z);
803 rot_max.x = rot_max.x.max(p.x);
804 rot_max.y = rot_max.y.max(p.y);
805 rot_max.z = rot_max.z.max(p.z);
806 }
807 rot_min.x = rot_min.x.min(wall_x_min);
808 rot_max.x = rot_max.x.max(wall_x_max);
809
810 *result = self.cut_rectangular_opening_no_faces(result, rot_min, rot_max);
811 }
812
813 for chunk in result.positions.chunks_exact_mut(3) {
815 let p = inv_rotation * Point3::new(chunk[0] as f64, chunk[1] as f64, chunk[2] as f64);
816 chunk[0] = p.x as f32;
817 chunk[1] = p.y as f32;
818 chunk[2] = p.z as f32;
819 }
820 for chunk in result.normals.chunks_exact_mut(3) {
821 let n = inv_rotation * Vector3::new(chunk[0] as f64, chunk[1] as f64, chunk[2] as f64);
822 chunk[0] = n.x as f32;
823 chunk[1] = n.y as f32;
824 chunk[2] = n.z as f32;
825 }
826 }
827 }
828
829 fn extend_opening_along_direction(
840 &self,
841 open_min: Point3<f64>,
842 open_max: Point3<f64>,
843 wall_min: Point3<f64>,
844 wall_max: Point3<f64>,
845 extrusion_direction: Vector3<f64>, ) -> (Point3<f64>, Point3<f64>) {
847 let open_center = Point3::new(
849 (open_min.x + open_max.x) * 0.5,
850 (open_min.y + open_max.y) * 0.5,
851 (open_min.z + open_max.z) * 0.5,
852 );
853
854 let wall_corners = [
856 Point3::new(wall_min.x, wall_min.y, wall_min.z),
857 Point3::new(wall_max.x, wall_min.y, wall_min.z),
858 Point3::new(wall_min.x, wall_max.y, wall_min.z),
859 Point3::new(wall_max.x, wall_max.y, wall_min.z),
860 Point3::new(wall_min.x, wall_min.y, wall_max.z),
861 Point3::new(wall_max.x, wall_min.y, wall_max.z),
862 Point3::new(wall_min.x, wall_max.y, wall_max.z),
863 Point3::new(wall_max.x, wall_max.y, wall_max.z),
864 ];
865
866 let mut wall_min_proj = f64::INFINITY;
868 let mut wall_max_proj = f64::NEG_INFINITY;
869
870 for corner in &wall_corners {
871 let proj = (corner - open_center).dot(&extrusion_direction);
873 wall_min_proj = wall_min_proj.min(proj);
874 wall_max_proj = wall_max_proj.max(proj);
875 }
876
877 let open_corners = [
879 Point3::new(open_min.x, open_min.y, open_min.z),
880 Point3::new(open_max.x, open_min.y, open_min.z),
881 Point3::new(open_min.x, open_max.y, open_min.z),
882 Point3::new(open_max.x, open_max.y, open_min.z),
883 Point3::new(open_min.x, open_min.y, open_max.z),
884 Point3::new(open_max.x, open_min.y, open_max.z),
885 Point3::new(open_min.x, open_max.y, open_max.z),
886 Point3::new(open_max.x, open_max.y, open_max.z),
887 ];
888
889 let mut open_min_proj = f64::INFINITY;
890 let mut open_max_proj = f64::NEG_INFINITY;
891
892 for corner in &open_corners {
893 let proj = (corner - open_center).dot(&extrusion_direction);
894 open_min_proj = open_min_proj.min(proj);
895 open_max_proj = open_max_proj.max(proj);
896 }
897
898 let extend_backward = (open_min_proj - wall_min_proj).max(0.0); let extend_forward = (wall_max_proj - open_max_proj).max(0.0); let extended_min = open_min - extrusion_direction * extend_backward;
905 let extended_max = open_max + extrusion_direction * extend_forward;
906
907 let all_points = [
910 open_min, open_max,
911 extended_min, extended_max,
912 ];
913
914 let new_min = Point3::new(
915 all_points.iter().map(|p| p.x).fold(f64::INFINITY, f64::min),
916 all_points.iter().map(|p| p.y).fold(f64::INFINITY, f64::min),
917 all_points.iter().map(|p| p.z).fold(f64::INFINITY, f64::min),
918 );
919 let new_max = Point3::new(
920 all_points.iter().map(|p| p.x).fold(f64::NEG_INFINITY, f64::max),
921 all_points.iter().map(|p| p.y).fold(f64::NEG_INFINITY, f64::max),
922 all_points.iter().map(|p| p.z).fold(f64::NEG_INFINITY, f64::max),
923 );
924
925 (new_min, new_max)
926 }
927
928 pub(super) fn cut_rectangular_opening(
934 &self,
935 mesh: &Mesh,
936 open_min: Point3<f64>,
937 open_max: Point3<f64>,
938 ) -> Mesh {
939 self.cut_rectangular_opening_no_faces(mesh, open_min, open_max)
940 }
941
942 fn cut_rectangular_opening_no_faces(
945 &self,
946 mesh: &Mesh,
947 open_min: Point3<f64>,
948 open_max: Point3<f64>,
949 ) -> Mesh {
950 use nalgebra::Vector3;
951
952 const EPSILON: f64 = 1e-6;
953
954 let mut result = Mesh::with_capacity(
955 mesh.positions.len() / 3,
956 mesh.indices.len() / 3,
957 );
958
959 let mut clip_buffers = ClipBuffers::new();
960
961 for chunk in mesh.indices.chunks_exact(3) {
962 let i0 = chunk[0] as usize;
963 let i1 = chunk[1] as usize;
964 let i2 = chunk[2] as usize;
965
966 let v0 = Point3::new(
967 mesh.positions[i0 * 3] as f64,
968 mesh.positions[i0 * 3 + 1] as f64,
969 mesh.positions[i0 * 3 + 2] as f64,
970 );
971 let v1 = Point3::new(
972 mesh.positions[i1 * 3] as f64,
973 mesh.positions[i1 * 3 + 1] as f64,
974 mesh.positions[i1 * 3 + 2] as f64,
975 );
976 let v2 = Point3::new(
977 mesh.positions[i2 * 3] as f64,
978 mesh.positions[i2 * 3 + 1] as f64,
979 mesh.positions[i2 * 3 + 2] as f64,
980 );
981
982 let n0 = if mesh.normals.len() >= mesh.positions.len() {
983 Vector3::new(
984 mesh.normals[i0 * 3] as f64,
985 mesh.normals[i0 * 3 + 1] as f64,
986 mesh.normals[i0 * 3 + 2] as f64,
987 )
988 } else {
989 let edge1 = v1 - v0;
990 let edge2 = v2 - v0;
991 edge1.cross(&edge2).try_normalize(1e-10).unwrap_or(Vector3::new(0.0, 0.0, 1.0))
992 };
993
994 let tri_min_x = v0.x.min(v1.x).min(v2.x);
995 let tri_max_x = v0.x.max(v1.x).max(v2.x);
996 let tri_min_y = v0.y.min(v1.y).min(v2.y);
997 let tri_max_y = v0.y.max(v1.y).max(v2.y);
998 let tri_min_z = v0.z.min(v1.z).min(v2.z);
999 let tri_max_z = v0.z.max(v1.z).max(v2.z);
1000
1001 if tri_max_x <= open_min.x - EPSILON || tri_min_x >= open_max.x + EPSILON ||
1003 tri_max_y <= open_min.y - EPSILON || tri_min_y >= open_max.y + EPSILON ||
1004 tri_max_z <= open_min.z - EPSILON || tri_min_z >= open_max.z + EPSILON {
1005 let base = result.vertex_count() as u32;
1006 result.add_vertex(v0, n0);
1007 result.add_vertex(v1, n0);
1008 result.add_vertex(v2, n0);
1009 result.add_triangle(base, base + 1, base + 2);
1010 continue;
1011 }
1012
1013 if tri_min_x >= open_min.x + EPSILON && tri_max_x <= open_max.x - EPSILON &&
1015 tri_min_y >= open_min.y + EPSILON && tri_max_y <= open_max.y - EPSILON &&
1016 tri_min_z >= open_min.z + EPSILON && tri_max_z <= open_max.z - EPSILON {
1017 continue;
1018 }
1019
1020 if self.triangle_intersects_box(&v0, &v1, &v2, &open_min, &open_max) {
1022 self.clip_triangle_against_box(&mut result, &mut clip_buffers, &v0, &v1, &v2, &n0, &open_min, &open_max);
1023 } else {
1024 let base = result.vertex_count() as u32;
1025 result.add_vertex(v0, n0);
1026 result.add_vertex(v1, n0);
1027 result.add_vertex(v2, n0);
1028 result.add_triangle(base, base + 1, base + 2);
1029 }
1030 }
1031
1032 result
1034 }
1035
1036
1037 fn triangle_intersects_box(
1040 &self,
1041 v0: &Point3<f64>,
1042 v1: &Point3<f64>,
1043 v2: &Point3<f64>,
1044 box_min: &Point3<f64>,
1045 box_max: &Point3<f64>,
1046 ) -> bool {
1047 use nalgebra::Vector3;
1048
1049 let box_center = Point3::new(
1051 (box_min.x + box_max.x) * 0.5,
1052 (box_min.y + box_max.y) * 0.5,
1053 (box_min.z + box_max.z) * 0.5,
1054 );
1055 let box_half_extents = Vector3::new(
1056 (box_max.x - box_min.x) * 0.5,
1057 (box_max.y - box_min.y) * 0.5,
1058 (box_max.z - box_min.z) * 0.5,
1059 );
1060
1061 let t0 = v0 - box_center;
1063 let t1 = v1 - box_center;
1064 let t2 = v2 - box_center;
1065
1066 let e0 = t1 - t0;
1068 let e1 = t2 - t1;
1069 let e2 = t0 - t2;
1070
1071 for axis_idx in 0..3 {
1074 let axis = match axis_idx {
1075 0 => Vector3::new(1.0, 0.0, 0.0),
1076 1 => Vector3::new(0.0, 1.0, 0.0),
1077 2 => Vector3::new(0.0, 0.0, 1.0),
1078 _ => unreachable!(),
1079 };
1080
1081 let p0 = t0.dot(&axis);
1082 let p1 = t1.dot(&axis);
1083 let p2 = t2.dot(&axis);
1084
1085 let tri_min = p0.min(p1).min(p2);
1086 let tri_max = p0.max(p1).max(p2);
1087 let box_extent = box_half_extents[axis_idx];
1088
1089 if tri_max < -box_extent || tri_min > box_extent {
1090 return false; }
1092 }
1093
1094 let triangle_normal = e0.cross(&e2);
1096 let triangle_offset = t0.dot(&triangle_normal);
1097
1098 let mut box_projection = 0.0;
1100 for i in 0..3 {
1101 let axis = match i {
1102 0 => Vector3::new(1.0, 0.0, 0.0),
1103 1 => Vector3::new(0.0, 1.0, 0.0),
1104 2 => Vector3::new(0.0, 0.0, 1.0),
1105 _ => unreachable!(),
1106 };
1107 box_projection += box_half_extents[i] * triangle_normal.dot(&axis).abs();
1108 }
1109
1110 if triangle_offset.abs() > box_projection {
1111 return false; }
1113
1114 let box_axes = [
1116 Vector3::new(1.0, 0.0, 0.0),
1117 Vector3::new(0.0, 1.0, 0.0),
1118 Vector3::new(0.0, 0.0, 1.0),
1119 ];
1120 let tri_edges = [e0, e1, e2];
1121
1122 for box_axis in &box_axes {
1123 for tri_edge in &tri_edges {
1124 let axis = box_axis.cross(tri_edge);
1125
1126 if axis.norm_squared() < 1e-10 {
1128 continue;
1129 }
1130
1131 let axis_normalized = axis.normalize();
1132
1133 let p0 = t0.dot(&axis_normalized);
1135 let p1 = t1.dot(&axis_normalized);
1136 let p2 = t2.dot(&axis_normalized);
1137 let tri_min = p0.min(p1).min(p2);
1138 let tri_max = p0.max(p1).max(p2);
1139
1140 let mut box_projection = 0.0;
1142 for i in 0..3 {
1143 let box_axis_vec = box_axes[i];
1144 box_projection += box_half_extents[i] * axis_normalized.dot(&box_axis_vec).abs();
1145 }
1146
1147 if tri_max < -box_projection || tri_min > box_projection {
1148 return false; }
1150 }
1151 }
1152
1153 true
1155 }
1156
1157 fn clip_triangle_against_box(
1174 &self,
1175 result: &mut Mesh,
1176 buffers: &mut ClipBuffers,
1177 v0: &Point3<f64>,
1178 v1: &Point3<f64>,
1179 v2: &Point3<f64>,
1180 normal: &Vector3<f64>,
1181 open_min: &Point3<f64>,
1182 open_max: &Point3<f64>,
1183 ) {
1184 let clipper = ClippingProcessor::new();
1185 let epsilon = clipper.epsilon;
1186
1187 buffers.clear();
1189
1190 let planes = [
1193 Plane::new(Point3::new(open_min.x, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0)),
1195 Plane::new(Point3::new(open_max.x, 0.0, 0.0), Vector3::new(-1.0, 0.0, 0.0)),
1197 Plane::new(Point3::new(0.0, open_min.y, 0.0), Vector3::new(0.0, 1.0, 0.0)),
1199 Plane::new(Point3::new(0.0, open_max.y, 0.0), Vector3::new(0.0, -1.0, 0.0)),
1201 Plane::new(Point3::new(0.0, 0.0, open_min.z), Vector3::new(0.0, 0.0, 1.0)),
1203 Plane::new(Point3::new(0.0, 0.0, open_max.z), Vector3::new(0.0, 0.0, -1.0)),
1205 ];
1206
1207 buffers.remaining.push(Triangle::new(*v0, *v1, *v2));
1209
1210 for plane in &planes {
1212 buffers.next_remaining.clear();
1213
1214 for tri in &buffers.remaining {
1215 let d0 = plane.signed_distance(&tri.v0);
1217 let d1 = plane.signed_distance(&tri.v1);
1218 let d2 = plane.signed_distance(&tri.v2);
1219
1220 let f0 = d0 >= -epsilon;
1221 let f1 = d1 >= -epsilon;
1222 let f2 = d2 >= -epsilon;
1223 let front_count = f0 as u8 + f1 as u8 + f2 as u8;
1224
1225 match front_count {
1226 3 => {
1227 buffers.next_remaining.push(tri.clone());
1229 }
1230 0 => {
1231 buffers.result.push(tri.clone());
1233 }
1234 1 => {
1235 let (front, back1, back2, d_f, d_b1, d_b2) = if f0 {
1237 (tri.v0, tri.v1, tri.v2, d0, d1, d2)
1238 } else if f1 {
1239 (tri.v1, tri.v2, tri.v0, d1, d2, d0)
1240 } else {
1241 (tri.v2, tri.v0, tri.v1, d2, d0, d1)
1242 };
1243
1244 let denom1 = d_f - d_b1;
1245 let denom2 = d_f - d_b2;
1246 if denom1.abs() < 1e-12 || denom2.abs() < 1e-12 {
1247 buffers.next_remaining.push(tri.clone());
1249 continue;
1250 }
1251 let t1 = (d_f / denom1).clamp(0.0, 1.0);
1252 let t2 = (d_f / denom2).clamp(0.0, 1.0);
1253 let p1 = front + (back1 - front) * t1;
1254 let p2 = front + (back2 - front) * t2;
1255
1256 buffers.next_remaining.push(Triangle::new(front, p1, p2));
1258
1259 buffers.result.push(Triangle::new(p1, back1, back2));
1261 buffers.result.push(Triangle::new(p1, back2, p2));
1262 }
1263 2 => {
1264 let (front1, front2, back, d_f1, d_f2, d_b) = if !f0 {
1266 (tri.v1, tri.v2, tri.v0, d1, d2, d0)
1267 } else if !f1 {
1268 (tri.v2, tri.v0, tri.v1, d2, d0, d1)
1269 } else {
1270 (tri.v0, tri.v1, tri.v2, d0, d1, d2)
1271 };
1272
1273 let denom1 = d_f1 - d_b;
1274 let denom2 = d_f2 - d_b;
1275 if denom1.abs() < 1e-12 || denom2.abs() < 1e-12 {
1276 buffers.next_remaining.push(tri.clone());
1278 continue;
1279 }
1280 let t1 = (d_f1 / denom1).clamp(0.0, 1.0);
1281 let t2 = (d_f2 / denom2).clamp(0.0, 1.0);
1282 let p1 = front1 + (back - front1) * t1;
1283 let p2 = front2 + (back - front2) * t2;
1284
1285 buffers.next_remaining.push(Triangle::new(front1, front2, p1));
1287 buffers.next_remaining.push(Triangle::new(front2, p2, p1));
1288
1289 buffers.result.push(Triangle::new(p1, p2, back));
1291 }
1292 _ => unreachable!(),
1293 }
1294 }
1295
1296 std::mem::swap(&mut buffers.remaining, &mut buffers.next_remaining);
1298 }
1299
1300 for tri in &buffers.result {
1303 let base = result.vertex_count() as u32;
1304 result.add_vertex(tri.v0, *normal);
1305 result.add_vertex(tri.v1, *normal);
1306 result.add_vertex(tri.v2, *normal);
1307 result.add_triangle(base, base + 1, base + 2);
1308 }
1309 }
1310
1311}