#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use use_coordinate::GeometryError;
use use_vector::Vector3;
fn validate_vector3(name: &'static str, vector: Vector3) -> Result<Vector3, GeometryError> {
if !vector.x().is_finite() {
return Err(GeometryError::non_finite_component(name, "x", vector.x()));
}
if !vector.y().is_finite() {
return Err(GeometryError::non_finite_component(name, "y", vector.y()));
}
if !vector.z().is_finite() {
return Err(GeometryError::non_finite_component(name, "z", vector.z()));
}
Ok(vector)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Plane3 {
normal: Vector3,
offset: f64,
}
impl Plane3 {
pub fn try_new(normal: Vector3, offset: f64) -> Result<Self, GeometryError> {
let normal = validate_vector3("Plane3", normal)?;
if normal.magnitude_squared() == 0.0 {
return Err(GeometryError::ZeroDirectionVector);
}
if !offset.is_finite() {
return Err(GeometryError::non_finite_component(
"Plane3", "offset", offset,
));
}
Ok(Self { normal, offset })
}
#[must_use]
pub const fn normal(self) -> Vector3 {
self.normal
}
#[must_use]
pub const fn offset(self) -> f64 {
self.offset
}
#[must_use]
pub fn evaluate(self, point: Vector3) -> f64 {
self.normal.dot(point) + self.offset
}
}
#[cfg(test)]
mod tests {
use super::Plane3;
use use_coordinate::GeometryError;
use use_vector::Vector3;
#[test]
fn evaluates_plane_equation() {
let plane = Plane3::try_new(Vector3::new(0.0, 0.0, 1.0), -2.0).expect("valid plane");
assert_eq!(plane.evaluate(Vector3::new(0.0, 0.0, 2.0)), 0.0);
assert_eq!(plane.normal(), Vector3::new(0.0, 0.0, 1.0));
assert_eq!(plane.offset(), -2.0);
}
#[test]
fn rejects_zero_normals() {
assert_eq!(
Plane3::try_new(Vector3::ZERO, 0.0),
Err(GeometryError::ZeroDirectionVector)
);
}
}