rscad_csg_bsp/primitives/rhomboid.rs
1use crate::*;
2use nalgebra::{Point, Vector3};
3
4/// Create a parallelepiped (rhomboid) centered at the origin, defined by three edge vectors.
5///
6/// This is the 3D analog of a parallelogram — a box with potentially non-orthogonal
7/// faces. For an axis-aligned box, use [`cuboid`] instead.
8///
9/// The three vectors define the edges meeting at one corner. The shape is centered
10/// at the origin, so it extends from -(a+b+c)/2 to +(a+b+c)/2.
11#[doc(alias = "parallelepiped")]
12pub fn rhomboid<T: Number>(a: Vector3<T>, b: Vector3<T>, c: Vector3<T>) -> Csg<T> {
13 let two = T::one() + T::one();
14 let offset = (a + b + c) / two;
15
16 // 7---------6
17 // /| /| Edges from vertex 0:
18 // / | / | 0→1 = a
19 // 3---------2 | 0→3 = b
20 // | 4------|--5 0→4 = c
21 // | / | /
22 // |/ |/
23 // 0---------1
24
25 let corners = [
26 Vector3::zeros(), // 0
27 a, // 1: +a
28 a + b, // 2: +a+b
29 b, // 3: +b
30 c, // 4: +c
31 a + c, // 5: +a+c
32 a + b + c, // 6: +a+b+c
33 b + c, // 7: +b+c
34 ];
35 let v: [Point<T, 3>; 8] = corners.map(|corner| Point::from(corner - offset));
36
37 // Same face topology as a cuboid. We build each face from its vertices,
38 // then check whether the derived normal points outward (away from the
39 // center). If it doesn't, we flip the polygon. This handles both
40 // right-handed and left-handed input vector triples.
41 let face_indices: [[usize; 4]; 6] = [
42 [0, 3, 2, 1], // -c face
43 [4, 5, 6, 7], // +c face
44 [0, 4, 7, 3], // -a face
45 [1, 2, 6, 5], // +a face
46 [0, 1, 5, 4], // -b face
47 [3, 7, 6, 2], // +b face
48 ];
49
50 let four = T::from(4).expect("BUG: 4 is representable as T");
51
52 let polygons = face_indices.into_iter().filter_map(|idx| {
53 let verts: Vec<Point<T, 3>> = idx.iter().map(|&i| v[i]).collect();
54 let face_center =
55 (verts[0].coords + verts[1].coords + verts[2].coords + verts[3].coords) / four;
56 let poly = Polygon::from_points(verts).ok()?;
57 if poly.plane().normal().dot(&face_center) < T::zero() {
58 Some(poly.flip())
59 } else {
60 Some(poly)
61 }
62 });
63
64 Csg::from_polygons(polygons)
65}