1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use use_coordinate::GeometryError;
5use use_vector::Vector3;
6
7fn validate_vector3(name: &'static str, vector: Vector3) -> Result<Vector3, GeometryError> {
8 if !vector.x().is_finite() {
9 return Err(GeometryError::non_finite_component(name, "x", vector.x()));
10 }
11 if !vector.y().is_finite() {
12 return Err(GeometryError::non_finite_component(name, "y", vector.y()));
13 }
14 if !vector.z().is_finite() {
15 return Err(GeometryError::non_finite_component(name, "z", vector.z()));
16 }
17 Ok(vector)
18}
19
20#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct Plane3 {
23 normal: Vector3,
24 offset: f64,
25}
26
27impl Plane3 {
28 pub fn try_new(normal: Vector3, offset: f64) -> Result<Self, GeometryError> {
34 let normal = validate_vector3("Plane3", normal)?;
35 if normal.magnitude_squared() == 0.0 {
36 return Err(GeometryError::ZeroDirectionVector);
37 }
38 if !offset.is_finite() {
39 return Err(GeometryError::non_finite_component(
40 "Plane3", "offset", offset,
41 ));
42 }
43 Ok(Self { normal, offset })
44 }
45
46 #[must_use]
48 pub const fn normal(self) -> Vector3 {
49 self.normal
50 }
51
52 #[must_use]
54 pub const fn offset(self) -> f64 {
55 self.offset
56 }
57
58 #[must_use]
60 pub fn evaluate(self, point: Vector3) -> f64 {
61 self.normal.dot(point) + self.offset
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::Plane3;
68 use use_coordinate::GeometryError;
69 use use_vector::Vector3;
70
71 #[test]
72 fn evaluates_plane_equation() {
73 let plane = Plane3::try_new(Vector3::new(0.0, 0.0, 1.0), -2.0).expect("valid plane");
74
75 assert_eq!(plane.evaluate(Vector3::new(0.0, 0.0, 2.0)), 0.0);
76 assert_eq!(plane.normal(), Vector3::new(0.0, 0.0, 1.0));
77 assert_eq!(plane.offset(), -2.0);
78 }
79
80 #[test]
81 fn rejects_zero_normals() {
82 assert_eq!(
83 Plane3::try_new(Vector3::ZERO, 0.0),
84 Err(GeometryError::ZeroDirectionVector)
85 );
86 }
87}