rustsim-geometry 0.0.1

2-D and 3-D geometric primitives and queries for rustsim (points, AABB, segments, rays, triangles, closest-point, raycast)
Documentation
//! Rays and planes in 3-D.

use crate::vec3::{self, Vec3};

/// 3-D ray with an origin and a (not necessarily unit) direction.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Ray3 {
    /// Origin.
    pub origin: Vec3,
    /// Direction (does not need to be unit length).
    pub dir: Vec3,
}

impl Ray3 {
    /// Point at parameter `t` along the ray.
    #[inline]
    pub fn point_at(&self, t: f64) -> Vec3 {
        vec3::add(self.origin, vec3::scale(self.dir, t))
    }
}

/// Infinite plane defined by a point on the plane and a (unit) normal.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Plane3 {
    /// A point on the plane.
    pub point: Vec3,
    /// Unit normal.
    pub normal: Vec3,
}

impl Plane3 {
    /// Constructs a horizontal plane at height `z`.
    #[inline]
    pub fn horizontal(z: f64) -> Self {
        Self {
            point: [0.0, 0.0, z],
            normal: [0.0, 0.0, 1.0],
        }
    }

    /// Signed distance from `p` to the plane (positive on normal side).
    #[inline]
    pub fn signed_distance(&self, p: Vec3) -> f64 {
        vec3::dot(vec3::sub(p, self.point), self.normal)
    }

    /// Intersect with a ray. Returns the non-negative `t` of intersection
    /// or `None` if parallel or behind the ray origin.
    pub fn intersect_ray(&self, ray: &Ray3) -> Option<f64> {
        let denom = vec3::dot(self.normal, ray.dir);
        if denom.abs() < 1e-12 {
            return None;
        }
        let t = vec3::dot(vec3::sub(self.point, ray.origin), self.normal) / denom;
        if t >= 0.0 {
            Some(t)
        } else {
            None
        }
    }
}

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

    #[test]
    fn ray_hits_horizontal_plane() {
        let r = Ray3 {
            origin: [0.0, 0.0, 5.0],
            dir: [0.0, 0.0, -1.0],
        };
        let p = Plane3::horizontal(0.0);
        let t = p.intersect_ray(&r).unwrap();
        assert!((t - 5.0).abs() < 1e-12);
        assert_eq!(r.point_at(t), [0.0, 0.0, 0.0]);
    }

    #[test]
    fn parallel_ray_misses_plane() {
        let r = Ray3 {
            origin: [0.0, 0.0, 5.0],
            dir: [1.0, 0.0, 0.0],
        };
        let p = Plane3::horizontal(0.0);
        assert!(p.intersect_ray(&r).is_none());
    }

    #[test]
    fn plane_signed_distance() {
        let p = Plane3::horizontal(0.0);
        assert!((p.signed_distance([0.0, 0.0, 3.0]) - 3.0).abs() < 1e-12);
        assert!((p.signed_distance([0.0, 0.0, -2.0]) + 2.0).abs() < 1e-12);
    }
}