Skip to main content

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}