fj_math/
plane.rs

1use crate::{Line, Point, Scalar, Vector};
2
3/// A plane
4#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
5#[repr(C)]
6pub struct Plane {
7    origin: Point<3>,
8    u: Vector<3>,
9    v: Vector<3>,
10}
11
12impl Plane {
13    /// Create a `Plane` from a parametric description
14    pub fn from_parametric(
15        origin: impl Into<Point<3>>,
16        u: impl Into<Vector<3>>,
17        v: impl Into<Vector<3>>,
18    ) -> Self {
19        let origin = origin.into();
20        let u = u.into();
21        let v = v.into();
22
23        Self { origin, u, v }
24    }
25
26    /// Access the origin of the plane
27    pub fn origin(&self) -> Point<3> {
28        self.origin
29    }
30
31    /// Access the u-vector of the plane
32    pub fn u(&self) -> Vector<3> {
33        self.u
34    }
35
36    /// Access the v-vector of the plane
37    pub fn v(&self) -> Vector<3> {
38        self.v
39    }
40
41    /// Compute the normal of the plane
42    pub fn normal(&self) -> Vector<3> {
43        self.u().cross(&self.v()).normalize()
44    }
45
46    /// Convert the plane to three-point form
47    pub fn three_point_form(&self) -> [Point<3>; 3] {
48        let a = self.origin();
49        let b = self.origin() + self.u();
50        let c = self.origin() + self.v();
51
52        [a, b, c]
53    }
54
55    /// Convert the plane to constant-normal form
56    pub fn constant_normal_form(&self) -> (Scalar, Vector<3>) {
57        let normal = self.normal();
58        let distance = normal.dot(&self.origin().coords);
59
60        (distance, normal)
61    }
62
63    /// Determine whether the plane is parallel to the given vector
64    pub fn is_parallel_to_vector(&self, vector: &Vector<3>) -> bool {
65        self.normal().dot(vector) == Scalar::ZERO
66    }
67
68    /// Project a point into the plane
69    pub fn project_point(&self, point: impl Into<Point<3>>) -> Point<2> {
70        let origin_to_point = point.into() - self.origin();
71        let coords = self.project_vector(origin_to_point);
72        Point { coords }
73    }
74
75    /// Project a vector into the plane
76    pub fn project_vector(&self, vector: impl Into<Vector<3>>) -> Vector<2> {
77        // The vector we want to project can be expressed as a linear
78        // combination of `self.u()`, `self.v()`, and `self.normal()`:
79        // `v = a*u + b*v + c*n`
80        //
81        // All we need to do is to solve this equation. `a` and `b` are the
82        // components of the projected vector. `c` is the distance of the points
83        // that the original and projected vectors point to.
84        //
85        // To solve the equation, let's change it into the standard `Mx = b`
86        // form, then we can let nalgebra do the actual solving.
87        let m =
88            nalgebra::Matrix::<_, _, nalgebra::Const<3>, _>::from_columns(&[
89                self.u().to_na(),
90                self.v().to_na(),
91                self.normal().to_na(),
92            ]);
93        let b = vector.into();
94        let x = m
95            .lu()
96            .solve(&b.to_na())
97            .expect("Expected matrix to be invertible");
98
99        Vector::from([x.x, x.y])
100    }
101
102    /// Project a line into the plane
103    pub fn project_line(&self, line: &Line<3>) -> Line<2> {
104        let line_origin_in_plane = self.project_point(line.origin());
105        let line_direction_in_plane = self.project_vector(line.direction());
106
107        Line::from_origin_and_direction(
108            line_origin_in_plane,
109            line_direction_in_plane,
110        )
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use crate::{Plane, Point, Vector};
117
118    #[test]
119    fn project_point() {
120        let plane =
121            Plane::from_parametric([1., 1., 1.], [1., 0., 0.], [0., 1., 0.]);
122
123        assert_eq!(plane.project_point([2., 1., 2.]), Point::from([1., 0.]));
124        assert_eq!(plane.project_point([1., 2., 2.]), Point::from([0., 1.]));
125    }
126
127    #[test]
128    fn project_vector() {
129        let plane =
130            Plane::from_parametric([1., 1., 1.], [1., 0., 0.], [0., 1., 0.]);
131
132        assert_eq!(plane.project_vector([1., 0., 1.]), Vector::from([1., 0.]));
133        assert_eq!(plane.project_vector([0., 1., 1.]), Vector::from([0., 1.]));
134
135        let plane =
136            Plane::from_parametric([1., 1., 1.], [1., 0., 0.], [1., 1., 0.]);
137        assert_eq!(plane.project_vector([0., 1., 0.]), Vector::from([-1., 1.]));
138    }
139}