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#[derive(Debug, Clone, Copy, PartialEq)]
34pub struct Ray2 {
35 origin: Point2,
36 direction: Vector2,
37}
38
39impl Ray2 {
40 #[must_use]
42 pub const fn new(origin: Point2, direction: Vector2) -> Self {
43 Self { origin, direction }
44 }
45
46 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 #[must_use]
60 pub const fn origin(self) -> Point2 {
61 self.origin
62 }
63
64 #[must_use]
66 pub const fn direction(self) -> Vector2 {
67 self.direction
68 }
69
70 #[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}