1use nalgebra::{Affine3, Vector3};
2
3use crate::*;
4use core::iter::IntoIterator;
5
6#[derive(Clone)]
7pub struct Csg<T: Number> {
8 polygons: Vec<Polygon<T, 3>>,
9 factory: CsgFactory<T>,
11}
12
13impl<T: Number> Csg<T> {
14 pub fn new(polygons: Vec<Polygon<T, 3>>) -> Self {
15 Self {
16 polygons,
17 factory: CsgFactory::new(),
18 }
19 }
20
21 pub fn from_polygons(polygons: impl IntoIterator<Item = Polygon<T, 3>>) -> Self {
22 Self {
23 polygons: polygons.into_iter().collect(),
24 factory: CsgFactory::new(),
25 }
26 }
27
28 pub fn factory(&self) -> &CsgFactory<T> {
29 &self.factory
30 }
31
32 pub fn factory_mut(&mut self) -> &mut CsgFactory<T> {
33 &mut self.factory
34 }
35
36 pub fn with_factory(mut self, factory: CsgFactory<T>) -> Self {
38 self.factory = factory;
39 self
40 }
41
42 pub fn union(mut self, other: Csg<T>) -> Self {
43 let remap = self.factory.absorb(&other.factory);
44 let other_polygons = remap_polygons(other.polygons, &remap);
45 let mut a = BspNode::build(self.polygons);
46 let mut b = BspNode::build(other_polygons);
47 a.clip_to(&b);
48 b.clip_to(&a);
49 b.invert();
50 b.clip_to(&a);
51 b.invert();
52 a.build_into(b.polygons());
53 Self {
54 polygons: a.polygons(),
55 factory: self.factory,
56 }
57 }
58
59 pub fn intersection(mut self, other: Csg<T>) -> Self {
60 let remap = self.factory.absorb(&other.factory);
61 let other_polygons = remap_polygons(other.polygons, &remap);
62 let mut a = BspNode::build(self.polygons);
63 let mut b = BspNode::build(other_polygons);
64 a.invert();
65 b.clip_to(&a);
66 b.invert();
67 a.clip_to(&b);
68 b.clip_to(&a);
69 a.build_into(b.polygons());
70 a.invert();
71 Self {
72 polygons: a.polygons(),
73 factory: self.factory,
74 }
75 }
76
77 pub fn polygons(&self) -> &[Polygon<T, 3>] {
78 self.polygons.as_slice()
79 }
80
81 pub fn cleanup(mut self) -> Self {
85 self.polygons = crate::cleanup::cleanup_polygons(self.polygons);
86 self
87 }
88
89 pub fn mapv_inplace<F>(&mut self, f: F)
91 where
92 F: Fn(Polygon<T, 3>) -> Polygon<T, 3>,
93 {
94 let polys = core::mem::take(&mut self.polygons);
95 self.polygons = polys.into_iter().map(f).collect();
96 }
97
98 #[cfg(feature = "rayon")]
100 pub fn par_mapv_inplace<F>(&mut self, f: F)
101 where
102 F: Fn(Polygon<T, 3>) -> Polygon<T, 3> + Send + Sync,
103 {
104 use rayon::prelude::*;
105 let polys = core::mem::take(&mut self.polygons);
106 self.polygons = polys.into_par_iter().map(f).collect();
107 }
108
109 pub fn mapv_inplace_points<F>(&mut self, f: F)
111 where
112 F: Fn(nalgebra::Point3<T>) -> nalgebra::Point3<T>,
113 {
114 self.mapv_inplace(|p| p.map_vertices(&f));
115 }
116
117 #[cfg(feature = "rayon")]
119 pub fn par_mapv_inplace_points<F>(&mut self, f: F)
120 where
121 F: Fn(nalgebra::Point3<T>) -> nalgebra::Point3<T> + Send + Sync,
122 {
123 self.par_mapv_inplace(|p| p.map_vertices(&f));
124 }
125
126 pub fn translate(mut self, offset: nalgebra::Vector3<T>) -> Self {
128 self.mapv_inplace(|p| p.translate(offset));
129 self
130 }
131
132 pub fn scale(mut self, factors: nalgebra::Vector3<T>) -> Self {
138 self.mapv_inplace(|mut p| {
139 p.vertices
140 .mapv_inplace(|v| v.coords.component_mul(&factors).into());
141 p.recompute_plane();
142 p
143 });
144 self
145 }
146
147 pub fn rotate_euler(mut self, angles_deg: nalgebra::Vector3<T>) -> Self {
149 let rotation = crate::euler_rotation_matrix(angles_deg);
150 self.mapv_inplace(|p| p.rotate(rotation));
151 self
152 }
153
154 pub fn mirror(mut self, axes: nalgebra::Vector3<T>) -> Self {
158 let signs = axes.map(|a| if a != T::zero() { -T::one() } else { T::one() });
159 self.mapv_inplace(|mut p| {
160 p.vertices
161 .mapv_inplace(|v| v.coords.component_mul(&signs).into());
162 p.vertices.invert_axis(ndarray::Axis(0));
163 let normal = p.plane.normal().component_mul(&signs);
164 let d = normal.dot(&p.vertices[0].coords);
165 p.plane = Plane::new(normal, d);
166 p
167 });
168 self
169 }
170
171 pub fn xor(self, other: Csg<T>) -> Self {
173 let a2 = self.clone();
174 let b2 = other.clone();
175 self.difference(b2).union(other.difference(a2))
176 }
177
178 pub fn difference(mut self, other: Csg<T>) -> Self {
180 let remap = self.factory.absorb(&other.factory);
181 let other_polygons = remap_polygons(other.polygons, &remap);
182 let mut a = BspNode::build(self.polygons);
183 let mut b = BspNode::build(other_polygons);
184 a.invert();
185 a.clip_to(&b);
186 b.clip_to(&a);
187 b.invert();
188 b.clip_to(&a);
189 b.invert();
190 a.build_into(b.polygons());
191 a.invert();
192 Self {
193 polygons: a.polygons(),
194 factory: self.factory,
195 }
196 }
197
198 pub fn extrude(
209 self,
210 along: Vector3<T>,
211 transform: Affine3<T>,
212 slices: usize,
213 ) -> Result<Self, Error> {
214 use nalgebra::Rotation3;
215 use num_traits::Float;
216
217 if !self.coplanar() {
218 return Err(Error::NonPlanarExtrude);
219 }
220
221 let slices = slices.max(1);
222 let slices_t = T::from(slices).expect("BUG: slices must convert to T");
223
224 let m = transform.matrix().fixed_view::<3, 3>(0, 0).into_owned();
226 let scale_factor = Float::cbrt(m.determinant());
227 let rot_matrix = m / scale_factor;
228 let rotation = Rotation3::from_matrix_unchecked(rot_matrix);
229 let axis_angle = rotation.scaled_axis();
230
231 let vertex_at = |v: nalgebra::Point3<T>, t: T| {
232 let s = T::one() + (scale_factor - T::one()) * t;
233 let rot = Rotation3::from_scaled_axis(axis_angle * t);
234 let offset = along * t;
235 nalgebra::Point3::from((rot * (v.coords * s)) + offset)
236 };
237
238 use itertools::Itertools;
239
240 let polygons: Vec<Polygon<T, 3>> =
241 self.polygons
242 .iter()
243 .flat_map(|base_poly| {
244 let aligned = base_poly.plane().normal().dot(&along) > T::zero();
245
246 let base_verts: Vec<nalgebra::Point3<T>> =
247 base_poly.vertices().iter().copied().collect();
248
249 let levels: Vec<Vec<nalgebra::Point3<T>>> = (0..=slices)
250 .map(|i| {
251 let t = T::from(i).expect("BUG: slice index") / slices_t;
252 base_verts.iter().map(|&v| vertex_at(v, t)).collect()
253 })
254 .collect();
255
256 let sides = levels
257 .iter()
258 .map(|level| {
259 level
260 .iter()
261 .copied()
262 .circular_tuple_windows::<(_, _)>()
263 .collect::<Vec<_>>()
264 })
265 .tuple_windows()
266 .flat_map(move |(bot_edges, top_edges)| {
267 bot_edges.into_iter().zip(top_edges).flat_map(
268 move |((a, b), (d, c))| {
269 if aligned {
270 [
271 Polygon::from_points([a, b, c]).ok(),
272 Polygon::from_points([a, c, d]).ok(),
273 ]
274 } else {
275 [
276 Polygon::from_points([a, c, b]).ok(),
277 Polygon::from_points([a, d, c]).ok(),
278 ]
279 }
280 .into_iter()
281 .flatten()
282 },
283 )
284 });
285
286 let first_level = levels.first().expect("BUG: levels is non-empty");
287 let last_level = levels.last().expect("BUG: levels is non-empty");
288 let (bottom_cap, top_cap) = if aligned {
289 (
290 Polygon::from_points(first_level.iter().rev().copied()),
291 Polygon::from_points(last_level.iter().copied()),
292 )
293 } else {
294 (
295 Polygon::from_points(first_level.iter().copied()),
296 Polygon::from_points(last_level.iter().rev().copied()),
297 )
298 };
299
300 sides.chain(bottom_cap).chain(top_cap).collect::<Vec<_>>()
301 })
302 .collect();
303
304 Ok(Csg::from_polygons(polygons))
305 }
306
307 pub fn lathe(self, angle_degrees: T, segments: usize) -> Result<Self, Error> {
315 use itertools::Itertools;
316 use num_traits::Float;
317
318 if !self.coplanar() {
319 return Err(Error::NonPlanarExtrude);
320 }
321
322 let segments = segments.max(3);
323 let full_turn = T::from(360.0).expect("BUG: const");
324 let full_circle = angle_degrees >= full_turn;
325 let sweep = if full_circle {
326 full_turn
327 } else {
328 angle_degrees
329 };
330 let step =
331 Float::to_radians(sweep) / T::from(segments).expect("BUG: segments must convert to T");
332
333 let vertex_at = |v: nalgebra::Point3<T>, i: usize| {
334 let theta = step * T::from(i).expect("BUG: segment index");
335 nalgebra::Point3::new(v.x * Float::cos(theta), v.z, v.x * Float::sin(theta))
336 };
337
338 let level_count = if full_circle { segments } else { segments + 1 };
340
341 let polygons: Vec<Polygon<T, 3>> = self
342 .polygons
343 .iter()
344 .flat_map(|base_poly| {
345 let aligned = base_poly.plane().normal().y < T::zero();
348
349 let base_verts: Vec<nalgebra::Point3<T>> =
350 base_poly.vertices().iter().copied().collect();
351
352 let levels: Vec<Vec<nalgebra::Point3<T>>> = (0..level_count)
353 .map(|i| base_verts.iter().map(|&v| vertex_at(v, i)).collect())
354 .collect();
355
356 let edges_of = |level: &Vec<nalgebra::Point3<T>>| {
357 level
358 .iter()
359 .copied()
360 .circular_tuple_windows::<(_, _)>()
361 .collect::<Vec<_>>()
362 };
363
364 let sides =
365 (0..segments)
366 .map(|i| (i, (i + 1) % level_count))
367 .flat_map(|(bot, top)| {
368 edges_of(&levels[bot])
369 .into_iter()
370 .zip(edges_of(&levels[top]))
371 .flat_map(move |((a, b), (d, c))| {
372 if aligned {
373 [
374 Polygon::from_points([a, b, c]).ok(),
375 Polygon::from_points([a, c, d]).ok(),
376 ]
377 } else {
378 [
379 Polygon::from_points([a, c, b]).ok(),
380 Polygon::from_points([a, d, c]).ok(),
381 ]
382 }
383 .into_iter()
384 .flatten()
385 })
386 .collect::<Vec<_>>()
387 });
388
389 let caps = if full_circle {
390 [None, None]
391 } else {
392 let first_level = levels.first().expect("BUG: levels is non-empty");
393 let last_level = levels.last().expect("BUG: levels is non-empty");
394 if aligned {
395 [
396 Polygon::from_points(first_level.iter().rev().copied()).ok(),
397 Polygon::from_points(last_level.iter().copied()).ok(),
398 ]
399 } else {
400 [
401 Polygon::from_points(first_level.iter().copied()).ok(),
402 Polygon::from_points(last_level.iter().rev().copied()).ok(),
403 ]
404 }
405 };
406
407 sides.chain(caps.into_iter().flatten()).collect::<Vec<_>>()
408 })
409 .collect();
410
411 Ok(Csg::from_polygons(polygons))
412 }
413
414 pub fn coplanar(&self) -> bool {
415 use itertools::Itertools;
416 self.polygons()
417 .iter()
418 .tuple_windows()
419 .all(|(last, next)| last.plane().roughly_eq(&next.plane()))
420 }
421
422 pub fn cast<T2: Number>(self) -> Csg<T2>
427 where
428 T: num_traits::cast::AsPrimitive<T2>,
429 {
430 let (new_factory, face_remap) = self.factory.cast::<T2>();
431 let polygons = self
432 .polygons
433 .into_iter()
434 .map(|p| {
435 let mut p2 = p.cast::<T2>();
436 if let Some(fk) = p2.face_key()
437 && let Some(&new_fk) = face_remap.0.get(&fk)
438 {
439 p2.set_face_key(Some(new_fk));
440 }
441 p2
442 })
443 .collect();
444 Csg {
445 polygons,
446 factory: new_factory,
447 }
448 }
449}
450
451fn remap_polygons<T: Number>(
453 mut polygons: Vec<Polygon<T, 3>>,
454 remap: &FaceKeyRemap,
455) -> Vec<Polygon<T, 3>> {
456 if remap.0.is_empty() {
457 return polygons;
458 }
459 for p in &mut polygons {
460 if let Some(fk) = p.face_key()
461 && let Some(&new_fk) = remap.0.get(&fk)
462 {
463 p.set_face_key(Some(new_fk));
464 }
465 }
466 polygons
467}
468
469#[cfg(feature = "bevy")]
470impl Csg<f32> {
471 fn polygon_color(&self, polygon: &Polygon<f32, 3>) -> Option<[f32; 4]> {
473 let fk = polygon.face_key()?;
474 let fm = self.factory.face(fk)?;
475 let pm = self.factory.primitive(fm.primitive_key)?;
476 pm.color
477 }
478
479 pub fn to_bevy_mesh(&self) -> bevy_mesh::Mesh {
480 use bevy_asset::RenderAssetUsages;
481 use bevy_mesh::{Indices, Mesh};
482 use wgpu::PrimitiveTopology;
483
484 let has_colors = self
485 .polygons
486 .iter()
487 .any(|p| self.polygon_color(p).is_some());
488 let color_fn = |p: &Polygon<f32, 3>| self.polygon_color(p);
489
490 let (vertices, normals, indices, colors) =
491 build_mesh_buffers(&self.polygons, has_colors, &color_fn);
492
493 let mut mesh = Mesh::new(
494 PrimitiveTopology::TriangleList,
495 RenderAssetUsages::default(),
496 );
497 mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices);
498 mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
499 if let Some(colors) = colors {
500 mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
501 }
502 mesh.insert_indices(Indices::U32(indices));
503 mesh
504 }
505}
506
507#[cfg(all(feature = "bevy", feature = "rayon"))]
508#[allow(clippy::type_complexity)]
509fn build_mesh_buffers(
510 polygons: &[Polygon<f32, 3>],
511 emit_colors: bool,
512 color_fn: &dyn Fn(&Polygon<f32, 3>) -> Option<[f32; 4]>,
513) -> (
514 Vec<[f32; 3]>,
515 Vec<[f32; 3]>,
516 Vec<u32>,
517 Option<Vec<[f32; 4]>>,
518) {
519 use rayon::prelude::*;
520
521 #[allow(clippy::type_complexity)]
522 let chunks: Vec<(Vec<[f32; 3]>, [f32; 3], Vec<u32>, Option<[f32; 4]>)> = polygons
523 .par_iter()
524 .map(|polygon| {
525 let normal: [f32; 3] = bytemuck::cast(polygon.plane().normal());
526 let verts: Vec<[f32; 3]> = polygon
527 .vertices()
528 .iter()
529 .map(|v| bytemuck::cast(*v))
530 .collect();
531 let tris: Vec<u32> = if polygon.vertices().len() == 3 {
532 vec![0, 1, 2]
533 } else {
534 polygon
535 .tessellate_earcut::<u32>()
536 .triangles()
537 .iter()
538 .copied()
539 .collect()
540 };
541 (verts, normal, tris, None)
544 })
545 .collect();
546
547 let mut offset = 0u32;
548 let offsets: Vec<u32> = chunks
549 .iter()
550 .map(|(verts, _, _, _)| {
551 let o = offset;
552 offset += verts.len() as u32;
553 o
554 })
555 .collect();
556
557 let total_verts: usize = chunks.iter().map(|(v, _, _, _)| v.len()).sum();
558 let total_tris: usize = chunks.iter().map(|(_, _, t, _)| t.len()).sum();
559
560 let mut vertices = Vec::with_capacity(total_verts);
561 let mut normals = Vec::with_capacity(total_verts);
562 let mut indices = Vec::with_capacity(total_tris);
563 let mut colors = if emit_colors {
564 Some(Vec::with_capacity(total_verts))
565 } else {
566 None
567 };
568
569 for (((verts, normal, tris, _), offset), polygon) in
570 chunks.into_iter().zip(offsets).zip(polygons)
571 {
572 let n = verts.len();
573 normals.extend(std::iter::repeat_n(normal, n));
574 vertices.extend(verts);
575 indices.extend(tris.into_iter().map(|i| i + offset));
576 if let Some(ref mut color_buf) = colors {
577 let c = color_fn(polygon).unwrap_or([1.0, 1.0, 1.0, 1.0]);
578 color_buf.extend(std::iter::repeat_n(c, n));
579 }
580 }
581
582 (vertices, normals, indices, colors)
583}
584
585#[cfg(all(feature = "bevy", not(feature = "rayon")))]
586#[allow(clippy::type_complexity)]
587fn build_mesh_buffers(
588 polygons: &[Polygon<f32, 3>],
589 emit_colors: bool,
590 color_fn: &dyn Fn(&Polygon<f32, 3>) -> Option<[f32; 4]>,
591) -> (
592 Vec<[f32; 3]>,
593 Vec<[f32; 3]>,
594 Vec<u32>,
595 Option<Vec<[f32; 4]>>,
596) {
597 let mut vertices: Vec<[f32; 3]> = Vec::new();
598 let mut normals: Vec<[f32; 3]> = Vec::new();
599 let mut indices: Vec<u32> = Vec::new();
600 let mut colors: Option<Vec<[f32; 4]>> = if emit_colors { Some(Vec::new()) } else { None };
601 let mut vertex_offset: u32 = 0;
602
603 for polygon in polygons {
604 let normal: [f32; 3] = bytemuck::cast(polygon.plane().normal());
605 let n_verts = polygon.vertices().len();
606
607 for v in polygon.vertices().iter() {
608 vertices.push(bytemuck::cast(*v));
609 normals.push(normal);
610 }
611 if let Some(ref mut color_buf) = colors {
612 let c = color_fn(polygon).unwrap_or([1.0, 1.0, 1.0, 1.0]);
613 color_buf.extend(std::iter::repeat_n(c, n_verts));
614 }
615 if n_verts == 3 {
616 indices.extend_from_slice(&[vertex_offset, vertex_offset + 1, vertex_offset + 2]);
617 } else {
618 let tessellated = polygon.tessellate_earcut::<u32>();
619 for idx in tessellated.triangles().iter() {
620 indices.push(*idx + vertex_offset);
621 }
622 }
623 vertex_offset += n_verts as u32;
624 }
625
626 (vertices, normals, indices, colors)
627}
628
629#[derive(thiserror::Error, Debug)]
630pub enum Error {
631 #[error("The shape you tried to extrude is non planar")]
632 NonPlanarExtrude,
633}
634
635#[cfg(test)]
636mod tests {
637 #[test]
642 fn non_uniform_scale_keeps_polygon_planes_consistent() {
643 let sphere = crate::CsgFactory::<f64>::new().sphere(0.5, 32, 32);
644 let scaled = sphere.scale(nalgebra::Vector3::new(3.6, 3.98, 1.0));
645 for polygon in scaled.polygons() {
646 let plane = polygon.plane();
647 for v in polygon.vertices().iter() {
648 assert!(
649 plane.has_point(v),
650 "vertex {v:?} off its polygon plane {plane:?}"
651 );
652 }
653 }
654 }
655}