1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
//! `Mesh` struct and implementations of the `CSGOps` trait for `Mesh`
use crate::float_types::{
parry3d::{
bounding_volume::{Aabb, BoundingVolume},
query::RayCast,
shape::Shape,
},
rapier3d::prelude::{
ColliderBuilder, ColliderSet, Ray, RigidBodyBuilder, RigidBodyHandle, RigidBodySet,
SharedShape, TriMesh, Triangle,
},
{EPSILON, Real},
};
use crate::mesh::{bsp::Node, plane::Plane, polygon::Polygon, vertex::Vertex};
use crate::sketch::Sketch;
use crate::traits::CSG;
use geo::{CoordsIter, Geometry, Polygon as GeoPolygon};
use nalgebra::{
Isometry3, Matrix4, Point3, Quaternion, Unit, Vector3, partial_max, partial_min,
};
use std::{cmp::PartialEq, fmt::Debug, num::NonZeroU32, sync::OnceLock};
#[cfg(feature = "parallel")]
use rayon::{iter::IntoParallelRefIterator, prelude::*};
pub mod bsp;
pub mod bsp_parallel;
#[cfg(feature = "chull")]
pub mod convex_hull;
pub mod flatten_slice;
#[cfg(feature = "metaballs")]
pub mod metaballs;
pub mod plane;
pub mod polygon;
pub mod connectivity;
pub mod manifold;
pub mod quality;
#[cfg(feature = "sdf")]
pub mod sdf;
pub mod shapes;
pub mod smoothing;
#[cfg(feature = "sdf")]
pub mod tpms;
pub mod vertex;
#[derive(Clone, Debug)]
pub struct Mesh<S: Clone + Send + Sync + Debug> {
/// 3D polygons for volumetric shapes
pub polygons: Vec<Polygon<S>>,
/// Lazily calculated AABB that spans `polygons`.
pub bounding_box: OnceLock<Aabb>,
/// Metadata
pub metadata: Option<S>,
}
impl<S: Clone + Send + Sync + Debug + PartialEq> Mesh<S> {
/// Compare just the `metadata` fields of two meshes
#[inline]
pub fn same_metadata(&self, other: &Self) -> bool {
self.metadata == other.metadata
}
/// Example: retain only polygons whose metadata matches `needle`
#[inline]
pub fn filter_polygons_by_metadata(&self, needle: &S) -> Mesh<S> {
let polys = self
.polygons
.iter()
.filter(|&p| p.metadata.as_ref() == Some(needle))
.cloned()
.collect();
Mesh {
polygons: polys,
bounding_box: std::sync::OnceLock::new(),
metadata: self.metadata.clone(),
}
}
}
impl<S: Clone + Send + Sync + Debug> Mesh<S> {
/// Build a Mesh from an existing polygon list
pub fn from_polygons(polygons: &[Polygon<S>], metadata: Option<S>) -> Self {
let mut mesh = Mesh::new();
mesh.polygons = polygons.to_vec();
mesh.metadata = metadata;
mesh
}
/// Split polygons into (may_touch, cannot_touch) using bounding‑box tests
fn partition_polys(
polys: &[Polygon<S>],
other_bb: &Aabb,
) -> (Vec<Polygon<S>>, Vec<Polygon<S>>) {
polys
.iter()
.cloned()
.partition(|p| p.bounding_box().intersects(other_bb))
}
/// Helper to collect all vertices from the CSG.
#[cfg(not(feature = "parallel"))]
pub fn vertices(&self) -> Vec<Vertex> {
self.polygons
.iter()
.flat_map(|p| p.vertices.clone())
.collect()
}
/// Parallel helper to collect all vertices from the CSG.
#[cfg(feature = "parallel")]
pub fn vertices(&self) -> Vec<Vertex> {
self.polygons
.par_iter()
.flat_map(|p| p.vertices.clone())
.collect()
}
/// Triangulate each polygon in the Mesh returning a Mesh containing triangles
pub fn triangulate(&self) -> Mesh<S> {
let triangles = self
.polygons
.iter()
.flat_map(|poly| {
poly.triangulate().into_iter().map(move |triangle| {
Polygon::new(triangle.to_vec(), poly.metadata.clone())
})
})
.collect::<Vec<_>>();
Mesh::from_polygons(&triangles, self.metadata.clone())
}
/// Subdivide all polygons in this Mesh 'levels' times, returning a new Mesh.
/// This results in a triangular mesh with more detail.
pub fn subdivide_triangles(&self, levels: NonZeroU32) -> Mesh<S> {
#[cfg(feature = "parallel")]
let new_polygons: Vec<Polygon<S>> = self
.polygons
.par_iter()
.flat_map(|poly| {
let sub_tris = poly.subdivide_triangles(levels);
// Convert each small tri back to a Polygon
sub_tris.into_par_iter().map(move |tri| {
Polygon::new(
vec![tri[0].clone(), tri[1].clone(), tri[2].clone()],
poly.metadata.clone(),
)
})
})
.collect();
#[cfg(not(feature = "parallel"))]
let new_polygons: Vec<Polygon<S>> = self
.polygons
.iter()
.flat_map(|poly| {
let sub_tris = poly.subdivide_triangles(levels);
sub_tris.into_iter().map(move |tri| {
Polygon::new(
vec![tri[0].clone(), tri[1].clone(), tri[2].clone()],
poly.metadata.clone(),
)
})
})
.collect();
Mesh::from_polygons(&new_polygons, self.metadata.clone())
}
/// Subdivide all polygons in this Mesh 'levels' times, in place.
/// This results in a triangular mesh with more detail.
///
/// ## Example
/// ```
/// use csgrs::mesh::Mesh;
/// use core::num::NonZeroU32;
/// let mut cube: Mesh<()> = Mesh::cube(2.0, None);
/// // subdivide_triangles(1) => each polygon (quad) is triangulated => 2 triangles => each tri subdivides => 4
/// // So each face with 4 vertices => 2 triangles => each becomes 4 => total 8 per face => 6 faces => 48
/// cube.subdivide_triangles_mut(1.try_into().expect("not zero"));
/// assert_eq!(cube.polygons.len(), 48);
///
/// let mut cube: Mesh<()> = Mesh::cube(2.0, None);
/// cube.subdivide_triangles_mut(2.try_into().expect("not zero"));
/// assert_eq!(cube.polygons.len(), 192);
/// ```
pub fn subdivide_triangles_mut(&mut self, levels: NonZeroU32) {
#[cfg(feature = "parallel")]
{
self.polygons = self
.polygons
.par_iter_mut()
.flat_map(|poly| {
let sub_tris = poly.subdivide_triangles(levels);
// Convert each small tri back to a Polygon
sub_tris
.into_par_iter()
.map(move |tri| Polygon::new(tri.to_vec(), poly.metadata.clone()))
})
.collect();
}
#[cfg(not(feature = "parallel"))]
{
self.polygons = self
.polygons
.iter()
.flat_map(|poly| {
let polytri = poly.subdivide_triangles(levels);
polytri
.into_iter()
.map(move |tri| Polygon::new(tri.to_vec(), poly.metadata.clone()))
})
.collect();
}
}
/// Renormalize all polygons in this Mesh by re-computing each polygon’s plane
/// and assigning that plane’s normal to all vertices.
pub fn renormalize(&mut self) {
for poly in &mut self.polygons {
poly.set_new_normal();
}
}
/// **Mathematical Foundation: Dihedral Angle Calculation**
///
/// Computes the dihedral angle between two polygons sharing an edge.
/// The angle is computed as the angle between the normal vectors of the two polygons.
///
/// Returns the angle in radians.
fn dihedral_angle(p1: &Polygon<S>, p2: &Polygon<S>) -> Real {
let n1 = p1.plane.normal();
let n2 = p2.plane.normal();
let dot = n1.dot(&n2).clamp(-1.0, 1.0);
dot.acos()
}
/// Extracts vertices and indices from the Mesh's tessellated polygons.
fn get_vertices_and_indices(&self) -> (Vec<Point3<Real>>, Vec<[u32; 3]>) {
let tri_csg = self.triangulate();
let vertices = tri_csg
.polygons
.iter()
.flat_map(|p| [p.vertices[0].pos, p.vertices[1].pos, p.vertices[2].pos])
.collect();
let indices = (0..tri_csg.polygons.len())
.map(|i| {
let offset = i as u32 * 3;
[offset, offset + 1, offset + 2]
})
.collect();
(vertices, indices)
}
/// Casts a ray defined by `origin` + t * `direction` against all triangles
/// of this Mesh and returns a list of (intersection_point, distance),
/// sorted by ascending distance.
///
/// # Parameters
/// - `origin`: The ray’s start point.
/// - `direction`: The ray’s direction vector.
///
/// # Returns
/// A `Vec` of `(Point3<Real>, Real)` where:
/// - `Point3<Real>` is the intersection coordinate in 3D,
/// - `Real` is the distance (the ray parameter t) from `origin`.
pub fn ray_intersections(
&self,
origin: &Point3<Real>,
direction: &Vector3<Real>,
) -> Vec<(Point3<Real>, Real)> {
let ray = Ray::new(*origin, *direction);
let iso = Isometry3::identity(); // No transformation on the triangles themselves.
let mut hits: Vec<_> = self
.polygons
.iter()
.flat_map(|poly| poly.triangulate())
.filter_map(|tri| {
let a = tri[0].pos;
let b = tri[1].pos;
let c = tri[2].pos;
let triangle = Triangle::new(a, b, c);
triangle
.cast_ray_and_get_normal(&iso, &ray, Real::MAX, true)
.map(|hit| {
let point_on_ray = ray.point_at(hit.time_of_impact);
(Point3::from(point_on_ray.coords), hit.time_of_impact)
})
})
.collect();
// 4) Sort hits by ascending distance (toi):
hits.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
// 5) remove duplicate hits if they fall within tolerance
hits.dedup_by(|a, b| (a.1 - b.1).abs() < EPSILON);
hits
}
/// Convert the polygons in this Mesh to a Parry `TriMesh`, wrapped in a `SharedShape` to be used in Rapier.\
/// Useful for collision detection or physics simulations.
///
/// ## Errors
/// If any 3d polygon has fewer than 3 vertices, or Parry returns a `TriMeshBuilderError`
pub fn to_rapier_shape(&self) -> SharedShape {
let (vertices, indices) = self.get_vertices_and_indices();
let trimesh = TriMesh::new(vertices, indices).unwrap();
SharedShape::new(trimesh)
}
/// Convert the polygons in this Mesh to a Parry `TriMesh`.\
/// Useful for collision detection.
///
/// ## Errors
/// If any 3d polygon has fewer than 3 vertices, or Parry returns a `TriMeshBuilderError`
pub fn to_trimesh(&self) -> Option<TriMesh> {
let (vertices, indices) = self.get_vertices_and_indices();
TriMesh::new(vertices, indices).ok()
}
/// Uses Parry to check if a point is inside a `Mesh`'s as a `TriMesh`.\
/// Note: this only use the 3d geometry of `CSG`
///
/// ## Errors
/// If any 3d polygon has fewer than 3 vertices
///
/// ## Example
/// ```
/// # use csgrs::mesh::Mesh;
/// # use nalgebra::Point3;
/// # use nalgebra::Vector3;
/// let csg_cube = Mesh::<()>::cube(6.0, None);
///
/// assert!(csg_cube.contains_vertex(&Point3::new(3.0, 3.0, 3.0)));
/// assert!(csg_cube.contains_vertex(&Point3::new(1.0, 2.0, 5.9)));
///
/// assert!(!csg_cube.contains_vertex(&Point3::new(3.0, 3.0, 6.0)));
/// assert!(!csg_cube.contains_vertex(&Point3::new(3.0, 3.0, -6.0)));
/// ```
pub fn contains_vertex(&self, point: &Point3<Real>) -> bool {
self.ray_intersections(point, &Vector3::new(1.0, 1.0, 1.0))
.len()
% 2
== 1
}
/// Approximate mass properties using Rapier.
pub fn mass_properties(
&self,
density: Real,
) -> (Real, Point3<Real>, Unit<Quaternion<Real>>) {
let trimesh = self.to_trimesh().unwrap();
let mp = trimesh.mass_properties(density);
(
mp.mass(),
mp.local_com, // a Point3<Real>
mp.principal_inertia_local_frame, // a Unit<Quaternion<Real>>
)
}
/// Create a Rapier rigid body + collider from this Mesh, using
/// an axis-angle `rotation` in 3D (the vector’s length is the
/// rotation in radians, and its direction is the axis).
pub fn to_rigid_body(
&self,
rb_set: &mut RigidBodySet,
co_set: &mut ColliderSet,
translation: Vector3<Real>,
rotation: Vector3<Real>, // rotation axis scaled by angle (radians)
density: Real,
) -> RigidBodyHandle {
let shape = self.to_rapier_shape();
// Build a Rapier RigidBody
let rb = RigidBodyBuilder::dynamic()
.translation(translation)
// Now `rotation(...)` expects an axis-angle Vector3.
.rotation(rotation)
.build();
let rb_handle = rb_set.insert(rb);
// Build the collider
let coll = ColliderBuilder::new(shape).density(density).build();
co_set.insert_with_parent(coll, rb_handle, rb_set);
rb_handle
}
/// Convert a Mesh into a Bevy `Mesh`.
#[cfg(feature = "bevymesh")]
pub fn to_bevy_mesh(&self) -> bevy_mesh::Mesh {
use bevy_asset::RenderAssetUsages;
use bevy_mesh::{Indices, Mesh};
use wgpu_types::PrimitiveTopology;
let triangulated_mesh = &self.triangulate();
let polygons = &triangulated_mesh.polygons;
// Prepare buffers
let mut positions_32 = Vec::new();
let mut normals_32 = Vec::new();
let mut indices = Vec::with_capacity(polygons.len() * 3);
let mut index_start = 0u32;
// Each polygon is assumed to have exactly 3 vertices after tessellation.
for poly in polygons {
// skip any degenerate polygons
if poly.vertices.len() != 3 {
continue;
}
// push 3 positions/normals
for v in &poly.vertices {
positions_32.push([v.pos.x as f32, v.pos.y as f32, v.pos.z as f32]);
normals_32.push([v.normal.x as f32, v.normal.y as f32, v.normal.z as f32]);
}
// triangle indices
indices.push(index_start);
indices.push(index_start + 1);
indices.push(index_start + 2);
index_start += 3;
}
// Create the mesh with the new 2-argument constructor
let mut mesh =
Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
// Insert attributes. Note the `<Vec<[f32; 3]>>` usage.
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions_32);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals_32);
// Insert triangle indices
mesh.insert_indices(Indices::U32(indices));
mesh
}
}
impl<S: Clone + Send + Sync + Debug> CSG for Mesh<S> {
/// Returns a new empty Mesh
fn new() -> Self {
Mesh {
polygons: Vec::new(),
bounding_box: OnceLock::new(),
metadata: None,
}
}
/// Return a new Mesh representing union of the two Meshes.
///
/// ```text
/// let c = a.union(b);
/// +-------+ +-------+
/// | | | |
/// | a | | c |
/// | +--+----+ = | +----+
/// +----+--+ | +----+ |
/// | b | | c |
/// | | | |
/// +-------+ +-------+
/// ```
fn union(&self, other: &Mesh<S>) -> Mesh<S> {
// avoid splitting obvious non‑intersecting faces
let (a_clip, a_passthru) =
Self::partition_polys(&self.polygons, &other.bounding_box());
let (b_clip, b_passthru) =
Self::partition_polys(&other.polygons, &self.bounding_box());
let mut a = Node::from_polygons(&a_clip);
let mut b = Node::from_polygons(&b_clip);
a.clip_to(&b);
b.clip_to(&a);
b.invert();
b.clip_to(&a);
b.invert();
a.build(&b.all_polygons());
// combine results and untouched faces
let mut final_polys = a.all_polygons();
final_polys.extend(a_passthru);
final_polys.extend(b_passthru);
Mesh {
polygons: final_polys,
bounding_box: OnceLock::new(),
metadata: self.metadata.clone(),
}
}
/// Return a new Mesh representing diffarence of the two Meshes.
///
/// ```text
/// let c = a.difference(b);
/// +-------+ +-------+
/// | | | |
/// | a | | c |
/// | +--+----+ = | +--+
/// +----+--+ | +----+
/// | b |
/// | |
/// +-------+
/// ```
fn difference(&self, other: &Mesh<S>) -> Mesh<S> {
// avoid splitting obvious non‑intersecting faces
let (a_clip, a_passthru) =
Self::partition_polys(&self.polygons, &other.bounding_box());
let (b_clip, _b_passthru) =
Self::partition_polys(&other.polygons, &self.bounding_box());
// propagate self.metadata to new polygons by overwriting intersecting
// polygon.metadata in other.
let b_clip_retagged: Vec<Polygon<S>> = b_clip
.iter()
.map(|poly| {
let mut p = poly.clone();
p.metadata = self.metadata.clone();
p
})
.collect();
let mut a = Node::from_polygons(&a_clip);
let mut b = Node::from_polygons(&b_clip_retagged);
a.invert();
a.clip_to(&b);
b.clip_to(&a);
b.invert();
b.clip_to(&a);
b.invert();
a.build(&b.all_polygons());
a.invert();
// combine results and untouched faces
let mut final_polys = a.all_polygons();
final_polys.extend(a_passthru);
Mesh {
polygons: final_polys,
bounding_box: OnceLock::new(),
metadata: self.metadata.clone(),
}
}
/// Return a new CSG representing intersection of the two CSG's.
///
/// ```text
/// let c = a.intersect(b);
/// +-------+
/// | |
/// | a |
/// | +--+----+ = +--+
/// +----+--+ | +--+
/// | b |
/// | |
/// +-------+
/// ```
fn intersection(&self, other: &Mesh<S>) -> Mesh<S> {
let mut a = Node::from_polygons(&self.polygons);
let mut b = Node::from_polygons(&other.polygons);
a.invert();
b.clip_to(&a);
b.invert();
a.clip_to(&b);
b.clip_to(&a);
a.build(&b.all_polygons());
a.invert();
Mesh {
polygons: a.all_polygons(),
bounding_box: OnceLock::new(),
metadata: self.metadata.clone(),
}
}
/// Return a new CSG representing space in this CSG excluding the space in the
/// other CSG plus the space in the other CSG excluding the space in this CSG.
///
/// ```text
/// let c = a.xor(b);
/// +-------+ +-------+
/// | | | |
/// | a | | a |
/// | +--+----+ = | +--+----+
/// +----+--+ | +----+--+ |
/// | b | | |
/// | | | |
/// +-------+ +-------+
/// ```
fn xor(&self, other: &Mesh<S>) -> Mesh<S> {
// 3D and 2D xor:
// A \ B
let a_sub_b = self.difference(other);
// B \ A
let b_sub_a = other.difference(self);
// Union those two
a_sub_b.union(&b_sub_a)
}
/// **Mathematical Foundation: General 3D Transformations**
///
/// Apply an arbitrary 3D transform (as a 4x4 matrix) to Mesh.
/// This implements the complete theory of affine transformations in homogeneous coordinates.
///
/// ## **Transformation Mathematics**
///
/// ### **Homogeneous Coordinates**
/// Points and vectors are represented in 4D homogeneous coordinates:
/// - **Point**: (x, y, z, 1)ᵀ → transforms as p' = Mp
/// - **Vector**: (x, y, z, 0)ᵀ → transforms as v' = Mv
/// - **Normal**: n'ᵀ = nᵀM⁻¹ (inverse transpose rule)
///
/// ### **Normal Vector Transformation**
/// Normals require special handling to remain perpendicular to surfaces:
/// ```text
/// If: T(p)·n = 0 (tangent perpendicular to normal)
/// Then: T(p)·T(n) ≠ 0 in general
/// But: T(p)·(M⁻¹)ᵀn = 0 ✓
/// ```
/// **Proof**: (Mp)ᵀ(M⁻¹)ᵀn = pᵀMᵀ(M⁻¹)ᵀn = pᵀ(M⁻¹M)ᵀn = pᵀn = 0
///
/// ### **Numerical Stability**
/// - **Degeneracy Detection**: Check determinant before inversion
/// - **Homogeneous Division**: Validate w-coordinate after transformation
/// - **Precision**: Maintain accuracy through matrix decomposition
///
/// ## **Algorithm Complexity**
/// - **Vertices**: O(n) matrix-vector multiplications
/// - **Matrix Inversion**: O(1) for 4×4 matrices
/// - **Plane Updates**: O(n) plane reconstructions from transformed vertices
///
/// The polygon z-coordinates and normal vectors are fully transformed in 3D
fn transform(&self, mat: &Matrix4<Real>) -> Mesh<S> {
// Compute inverse transpose for normal transformation
let mat_inv_transpose = match mat.try_inverse() {
Some(inv) => inv.transpose(),
None => {
eprintln!(
"Warning: Transformation matrix is not invertible, using identity for normals"
);
Matrix4::identity()
},
};
let mut mesh = self.clone();
for poly in &mut mesh.polygons {
for vert in &mut poly.vertices {
// Transform position using homogeneous coordinates
let hom_pos = mat * vert.pos.to_homogeneous();
match Point3::from_homogeneous(hom_pos) {
Some(transformed_pos) => vert.pos = transformed_pos,
None => {
eprintln!(
"Warning: Invalid homogeneous coordinates after transformation, skipping vertex"
);
continue;
},
}
// Transform normal using inverse transpose rule
vert.normal = mat_inv_transpose.transform_vector(&vert.normal).normalize();
}
// Reconstruct plane from transformed vertices for consistency
poly.plane = Plane::from_vertices(poly.vertices.clone());
// Invalidate the polygon's bounding box
poly.bounding_box = OnceLock::new();
}
// invalidate the old cached bounding box
mesh.bounding_box = OnceLock::new();
mesh
}
/// Returns a [`parry3d::bounding_volume::Aabb`] indicating the 3D bounds of all `polygons`.
fn bounding_box(&self) -> Aabb {
*self.bounding_box.get_or_init(|| {
// Track overall min/max in x, y, z among all 3D polygons
let mut min_x = Real::MAX;
let mut min_y = Real::MAX;
let mut min_z = Real::MAX;
let mut max_x = -Real::MAX;
let mut max_y = -Real::MAX;
let mut max_z = -Real::MAX;
// 1) Gather from the 3D polygons
for poly in &self.polygons {
for v in &poly.vertices {
min_x = *partial_min(&min_x, &v.pos.x).unwrap();
min_y = *partial_min(&min_y, &v.pos.y).unwrap();
min_z = *partial_min(&min_z, &v.pos.z).unwrap();
max_x = *partial_max(&max_x, &v.pos.x).unwrap();
max_y = *partial_max(&max_y, &v.pos.y).unwrap();
max_z = *partial_max(&max_z, &v.pos.z).unwrap();
}
}
// If still uninitialized (e.g., no polygons), return a trivial AABB at origin
if min_x > max_x {
return Aabb::new(Point3::origin(), Point3::origin());
}
// Build a parry3d Aabb from these min/max corners
let mins = Point3::new(min_x, min_y, min_z);
let maxs = Point3::new(max_x, max_y, max_z);
Aabb::new(mins, maxs)
})
}
/// Invalidates object's cached bounding box.
fn invalidate_bounding_box(&mut self) {
self.bounding_box = OnceLock::new();
}
/// Invert this Mesh (flip inside vs. outside)
fn inverse(&self) -> Mesh<S> {
let mut mesh = self.clone();
for p in &mut mesh.polygons {
p.flip();
}
mesh
}
}
impl<S: Clone + Send + Sync + Debug> From<Sketch<S>> for Mesh<S> {
/// Convert a Sketch into a Mesh.
fn from(sketch: Sketch<S>) -> Self {
/// Helper function to convert a geo::Polygon to a Vec<crate::mesh::polygon::Polygon>
fn geo_poly_to_csg_polys<S: Clone + Debug + Send + Sync>(
poly2d: &GeoPolygon<Real>,
metadata: &Option<S>,
) -> Vec<Polygon<S>> {
let mut all_polygons = Vec::new();
// Handle the exterior ring
let outer_vertices_3d: Vec<_> = poly2d
.exterior()
.coords_iter()
.map(|c| Vertex::new(Point3::new(c.x, c.y, 0.0), Vector3::z()))
.collect();
if outer_vertices_3d.len() >= 3 {
all_polygons.push(Polygon::new(outer_vertices_3d, metadata.clone()));
}
// Handle interior rings (holes)
for ring in poly2d.interiors() {
let hole_vertices_3d: Vec<_> = ring
.coords_iter()
.map(|c| Vertex::new(Point3::new(c.x, c.y, 0.0), Vector3::z()))
.collect();
if hole_vertices_3d.len() >= 3 {
all_polygons.push(Polygon::new(hole_vertices_3d, metadata.clone()));
}
}
all_polygons
}
let final_polygons = sketch
.geometry
.iter()
.flat_map(|geom| -> Vec<Polygon<S>> {
match geom {
Geometry::Polygon(poly2d) => {
geo_poly_to_csg_polys(poly2d, &sketch.metadata)
},
Geometry::MultiPolygon(multipoly) => multipoly
.iter()
.flat_map(|poly2d| geo_poly_to_csg_polys(poly2d, &sketch.metadata))
.collect(),
_ => vec![],
}
})
.collect();
Mesh {
polygons: final_polygons,
bounding_box: OnceLock::new(),
metadata: None,
}
}
}