macaw 0.18.0

An opinionated game math library built on top the excellent glam
Documentation
use crate::Vec3;

/// A 3-dimensional plane primitive.
///
/// Represented by a normal and the signed distance from the origin to the plane.
///
/// This representation can specify any possible plane in 3D space using 4 parameters
/// (disregarding floating point precision). There are duplicate representations caused
/// by negating both parameters, but that's fine - you could view it as an oriented plane.
///
/// A point `point` is on the plane when `plane.normal.dot(point) + plane.d = 0`.
#[derive(Copy, Clone, PartialEq)]
#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with_speedy", derive(speedy::Writable, speedy::Readable))]
pub struct Plane3 {
    /// Normal vector
    pub normal: Vec3,
    /// Distance
    pub d: f32,
}

impl Plane3 {
    /// The X^Y plane with normal = +Z
    pub const XY: Self = Self {
        normal: Vec3::Z,
        d: 0.0,
    };
    /// The Y^Z plane with normal = +X
    pub const YZ: Self = Self {
        normal: Vec3::X,
        d: 0.0,
    };
    /// The Z^X plane with normal = +Y
    pub const ZX: Self = Self {
        normal: Vec3::Y,
        d: 0.0,
    };

    /// From the plane normal and a distance `d` so that for all points on the plane:
    /// `normal.dot(point) + d = 0`.
    #[inline]
    pub fn from_normal_dist(normal: Vec3, d: f32) -> Self {
        Self { normal, d }
    }

    /// From the plane normal and a point on the plane.
    #[inline]
    pub fn from_normal_point(normal: Vec3, point: Vec3) -> Self {
        Self {
            normal,
            d: -normal.dot(point),
        }
    }

    /// Get normalized plane
    #[inline]
    #[must_use]
    pub fn normalized(&self) -> Self {
        let inv_len = self.normal.length_recip();
        Self {
            normal: self.normal * inv_len,
            d: self.d * inv_len,
        }
    }

    /// Computes the distance between the plane and the point p.
    /// The returned distance is only correct if the plane is normalized or the distance is zero.
    #[inline]
    pub fn distance(&self, p: Vec3) -> f32 {
        self.normal.dot(p) + self.d
    }

    /// The bool is whether the plane was hit or not.
    ///
    /// If false, the ray was either perpendicular to the plane, or the ray shot away from the plane.
    ///
    /// The returned f32 is the t value so you can easily compute the
    /// intersection point through "origin + dir * t".
    pub fn intersect_ray(&self, origin: Vec3, dir: Vec3) -> (bool, f32) {
        let denom = dir.dot(self.normal);
        if denom == 0.0 {
            (false, 0.0)
        } else {
            let t = -(origin.dot(self.normal) + self.d) / denom;
            if t < 0.0 {
                (false, t)
            } else {
                (true, t)
            }
        }
    }

    /// True if every value is finite
    #[inline]
    pub fn is_finite(&self) -> bool {
        self.normal.is_finite() && self.d.is_finite()
    }

    /// The distance to a point `[x, y, z, 1]` is the dot product of the point and this.
    #[inline]
    pub fn as_vec4(&self) -> crate::Vec4 {
        self.normal.extend(self.d)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_plane3() {
        #![allow(clippy::float_cmp)]
        let point = Vec3::new(2.0, 3.0, 4.0);
        let p = Plane3::from_normal_point(Vec3::new(2.0, 0.0, 0.0), point);
        assert_eq!(p.distance(point), 0.0);
        let p = p.normalized();
        assert_eq!(p.distance(point), 0.0);
    }
}