use nalgebra::{Point3, Vector3};
pub const PLANE_EPSILON: f32 = 1e-4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlaneSide {
Front,
Back,
OnPlane,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Classification {
Front,
Back,
Coplanar,
Spanning,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Plane3D {
normal: Vector3<f32>,
offset: f32,
}
impl Plane3D {
pub fn new(normal: Vector3<f32>, offset: f32) -> Self {
let norm = normal.norm();
assert!(norm > f32::EPSILON, "Plane normal cannot be zero");
Self {
normal: normal / norm,
offset: offset / norm,
}
}
pub fn from_point_and_normal(point: Point3<f32>, normal: Vector3<f32>) -> Self {
let norm = normal.norm();
assert!(norm > f32::EPSILON, "Plane normal cannot be zero");
let unit_normal = normal / norm;
let offset = unit_normal.dot(&point.coords);
Self {
normal: unit_normal,
offset,
}
}
pub fn from_three_points(a: Point3<f32>, b: Point3<f32>, c: Point3<f32>) -> Self {
let ab = b - a;
let ac = c - a;
let normal = ab.cross(&ac);
Self::from_point_and_normal(a, normal)
}
#[inline]
pub fn normal(&self) -> Vector3<f32> {
self.normal
}
#[inline]
pub fn offset(&self) -> f32 {
self.offset
}
#[inline]
pub fn signed_distance(&self, point: Point3<f32>) -> f32 {
self.normal.dot(&point.coords) - self.offset
}
#[inline]
pub fn classify_point(&self, point: Point3<f32>) -> PlaneSide {
self.classify_point_with_epsilon(point, PLANE_EPSILON)
}
pub fn classify_point_with_epsilon(&self, point: Point3<f32>, epsilon: f32) -> PlaneSide {
let dist = self.signed_distance(point);
if dist > epsilon {
PlaneSide::Front
} else if dist < -epsilon {
PlaneSide::Back
} else {
PlaneSide::OnPlane
}
}
#[inline]
pub fn flipped(&self) -> Self {
Self {
normal: -self.normal,
offset: -self.offset,
}
}
#[inline]
pub fn project_point(&self, point: Point3<f32>) -> Point3<f32> {
point - self.normal * self.signed_distance(point)
}
pub fn intersect_segment(
&self,
start: Point3<f32>,
end: Point3<f32>,
) -> Option<(f32, Point3<f32>)> {
let direction = end - start;
let denom = self.normal.dot(&direction);
if denom.abs() < f32::EPSILON {
return None;
}
let t = (self.offset - self.normal.dot(&start.coords)) / denom;
if t < 0.0 || t > 1.0 {
return None;
}
let point = start + direction * t;
Some((t, point))
}
}