Skip to main content

use_ray/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use use_coordinate::GeometryError;
5use use_point::Point2;
6use use_vector::Vector2;
7
8fn validate_direction(direction: Vector2) -> Result<Vector2, GeometryError> {
9    if !direction.x().is_finite() {
10        return Err(GeometryError::non_finite_component(
11            "Vector2",
12            "x",
13            direction.x(),
14        ));
15    }
16
17    if !direction.y().is_finite() {
18        return Err(GeometryError::non_finite_component(
19            "Vector2",
20            "y",
21            direction.y(),
22        ));
23    }
24
25    if direction.magnitude_squared() == 0.0 {
26        return Err(GeometryError::ZeroDirectionVector);
27    }
28
29    Ok(direction)
30}
31
32/// A half-infinite 2D ray.
33#[derive(Debug, Clone, Copy, PartialEq)]
34pub struct Ray2 {
35    origin: Point2,
36    direction: Vector2,
37}
38
39impl Ray2 {
40    /// Creates a ray without validation.
41    #[must_use]
42    pub const fn new(origin: Point2, direction: Vector2) -> Self {
43        Self { origin, direction }
44    }
45
46    /// Creates a ray with a finite origin and finite non-zero direction.
47    ///
48    /// # Errors
49    ///
50    /// Returns a [`GeometryError`] when the origin is non-finite or the direction is invalid.
51    pub fn try_new(origin: Point2, direction: Vector2) -> Result<Self, GeometryError> {
52        Ok(Self::new(
53            origin.validate()?,
54            validate_direction(direction)?,
55        ))
56    }
57
58    /// Returns the ray origin.
59    #[must_use]
60    pub const fn origin(self) -> Point2 {
61        self.origin
62    }
63
64    /// Returns the ray direction.
65    #[must_use]
66    pub const fn direction(self) -> Vector2 {
67        self.direction
68    }
69
70    /// Returns the point at parameter `t` along the ray.
71    #[must_use]
72    pub fn point_at(self, t: f64) -> Point2 {
73        self.origin + self.direction.scale(t)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::Ray2;
80    use use_coordinate::GeometryError;
81    use use_point::Point2;
82    use use_vector::Vector2;
83
84    #[test]
85    fn constructs_and_samples_rays() {
86        let ray = Ray2::try_new(Point2::new(1.0, 2.0), Vector2::new(3.0, 0.0)).expect("valid ray");
87
88        assert_eq!(ray.origin(), Point2::new(1.0, 2.0));
89        assert_eq!(ray.direction(), Vector2::new(3.0, 0.0));
90        assert_eq!(ray.point_at(2.0), Point2::new(7.0, 2.0));
91    }
92
93    #[test]
94    fn rejects_zero_direction() {
95        assert_eq!(
96            Ray2::try_new(Point2::origin(), Vector2::ZERO),
97            Err(GeometryError::ZeroDirectionVector)
98        );
99    }
100}