fyrox_math/
plane.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use nalgebra::Vector3;
22
23#[derive(Copy, Clone, Debug, PartialEq)]
24pub struct Plane {
25    pub normal: Vector3<f32>,
26    pub d: f32,
27}
28
29impl Default for Plane {
30    #[inline]
31    fn default() -> Self {
32        Plane {
33            normal: Vector3::new(0.0, 1.0, 0.0),
34            d: 0.0,
35        }
36    }
37}
38
39impl Plane {
40    /// Creates plane from a point and normal vector at that point.
41    /// May fail if normal is degenerated vector.
42    #[inline]
43    pub fn from_normal_and_point(normal: &Vector3<f32>, point: &Vector3<f32>) -> Option<Self> {
44        normal
45            .try_normalize(f32::EPSILON)
46            .map(|normalized_normal| Self {
47                normal: normalized_normal,
48                d: -point.dot(&normalized_normal),
49            })
50    }
51
52    /// Tries to create a plane from three points (triangle). May fail if the triangle is degenerated
53    /// (collapsed into a point or a line).
54    #[inline]
55    pub fn from_triangle(a: &Vector3<f32>, b: &Vector3<f32>, c: &Vector3<f32>) -> Option<Self> {
56        let normal = (b - a).cross(&(c - a));
57        Self::from_normal_and_point(&normal, a)
58    }
59
60    /// Creates plane using coefficients of plane equation Ax + By + Cz + D = 0
61    /// May fail if length of normal vector is zero (normal is degenerated vector).
62    #[inline]
63    pub fn from_abcd(a: f32, b: f32, c: f32, d: f32) -> Option<Self> {
64        let normal = Vector3::new(a, b, c);
65        let len = normal.norm();
66        if len == 0.0 {
67            None
68        } else {
69            let coeff = 1.0 / len;
70            Some(Self {
71                normal: normal.scale(coeff),
72                d: d * coeff,
73            })
74        }
75    }
76
77    #[inline]
78    pub fn dot(&self, point: &Vector3<f32>) -> f32 {
79        self.normal.dot(point) + self.d
80    }
81
82    #[inline]
83    pub fn distance(&self, point: &Vector3<f32>) -> f32 {
84        self.dot(point).abs()
85    }
86
87    /// Projects the given point onto the plane along the normal vector of the plane.
88    #[inline]
89    pub fn project(&self, point: &Vector3<f32>) -> Vector3<f32> {
90        point - self.normal.scale(self.normal.dot(point) + self.d)
91    }
92
93    /// <http://geomalgorithms.com/a05-_intersect-1.html>
94    pub fn intersection_point(&self, b: &Plane, c: &Plane) -> Vector3<f32> {
95        let f = -1.0 / self.normal.dot(&b.normal.cross(&c.normal));
96
97        let v1 = b.normal.cross(&c.normal).scale(self.d);
98        let v2 = c.normal.cross(&self.normal).scale(b.d);
99        let v3 = self.normal.cross(&b.normal).scale(c.d);
100
101        (v1 + v2 + v3).scale(f)
102    }
103}
104
105#[cfg(test)]
106mod test {
107    use crate::plane::Plane;
108    use nalgebra::Vector3;
109
110    #[test]
111    fn plane_sanity_tests() {
112        // Computation test
113        let plane = Plane::from_normal_and_point(
114            &Vector3::new(0.0, 10.0, 0.0),
115            &Vector3::new(0.0, 3.0, 0.0),
116        );
117        assert!(plane.is_some());
118        let plane = plane.unwrap();
119        assert_eq!(plane.normal.x, 0.0);
120        assert_eq!(plane.normal.y, 1.0);
121        assert_eq!(plane.normal.z, 0.0);
122        assert_eq!(plane.d, -3.0);
123
124        // Degenerated normal case
125        let plane = Plane::from_normal_and_point(
126            &Vector3::new(0.0, 0.0, 0.0),
127            &Vector3::new(0.0, 0.0, 0.0),
128        );
129        assert!(plane.is_none());
130
131        let plane = Plane::from_abcd(0.0, 0.0, 0.0, 0.0);
132        assert!(plane.is_none())
133    }
134
135    #[test]
136    fn test_default_for_plane() {
137        assert_eq!(
138            Plane::default(),
139            Plane {
140                normal: Vector3::new(0.0, 1.0, 0.0),
141                d: 0.0,
142            }
143        );
144    }
145
146    #[test]
147    fn test_plane_from_abcd() {
148        assert_eq!(Plane::from_abcd(0.0, 0.0, 0.0, 0.0), None);
149        assert_eq!(
150            Plane::from_abcd(1.0, 1.0, 1.0, 0.0),
151            Some(Plane {
152                normal: Vector3::new(0.57735026, 0.57735026, 0.57735026),
153                d: 0.0
154            })
155        );
156    }
157
158    #[test]
159    fn test_plane_dot() {
160        let plane = Plane::from_normal_and_point(
161            &Vector3::new(0.0, 0.0, 1.0),
162            &Vector3::new(0.0, 0.0, 0.0),
163        );
164        assert!(plane.is_some());
165        assert_eq!(plane.unwrap().dot(&Vector3::new(1.0, 1.0, 1.0)), 1.0);
166    }
167
168    #[test]
169    fn test_plane_distance() {
170        let plane = Plane::from_normal_and_point(
171            &Vector3::new(0.0, 0.0, 1.0),
172            &Vector3::new(0.0, 0.0, 0.0),
173        );
174        assert!(plane.is_some());
175        assert_eq!(plane.unwrap().distance(&Vector3::new(0.0, 0.0, 0.0)), 0.0);
176        assert_eq!(plane.unwrap().distance(&Vector3::new(1.0, 0.0, 0.0)), 0.0);
177        assert_eq!(plane.unwrap().distance(&Vector3::new(0.0, 1.0, 0.0)), 0.0);
178        assert_eq!(plane.unwrap().distance(&Vector3::new(0.0, 0.0, 1.0)), 1.0);
179    }
180
181    #[test]
182    fn test_plane_intersection_point() {
183        let plane = Plane::from_normal_and_point(
184            &Vector3::new(0.0, 0.0, 1.0),
185            &Vector3::new(0.0, 0.0, 0.0),
186        );
187        let plane2 = Plane::from_normal_and_point(
188            &Vector3::new(0.0, 1.0, 0.0),
189            &Vector3::new(0.0, 0.0, 0.0),
190        );
191        let plane3 = Plane::from_normal_and_point(
192            &Vector3::new(1.0, 0.0, 0.0),
193            &Vector3::new(0.0, 0.0, 0.0),
194        );
195        assert!(plane.is_some());
196        assert!(plane2.is_some());
197        assert!(plane3.is_some());
198
199        assert_eq!(
200            plane
201                .unwrap()
202                .intersection_point(&plane2.unwrap(), &plane3.unwrap()),
203            Vector3::new(0.0, 0.0, 0.0)
204        );
205    }
206}