1use crate::{Point, Scalar, Triangle, Vector};
2
3#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
8#[repr(C)]
9pub struct Line<const D: usize> {
10 origin: Point<D>,
11 direction: Vector<D>,
12}
13
14impl<const D: usize> Line<D> {
15 pub fn from_origin_and_direction(
21 origin: Point<D>,
22 direction: Vector<D>,
23 ) -> Self {
24 assert!(
25 direction.magnitude() != Scalar::ZERO,
26 "Can't construct `Line`. Direction is zero: {direction:?}"
27 );
28
29 Self { origin, direction }
30 }
31
32 pub fn from_points(
41 points: [impl Into<Point<D>>; 2],
42 ) -> (Self, [Point<1>; 2]) {
43 let [a, b] = points.map(Into::into);
44
45 let line = Self::from_origin_and_direction(a, b - a);
46 let coords = [[0.], [1.]].map(Point::from);
47
48 (line, coords)
49 }
50
51 pub fn from_points_with_line_coords(
57 points: [(impl Into<Point<1>>, impl Into<Point<D>>); 2],
58 ) -> Self {
59 let [(a_line, a_global), (b_line, b_global)] =
60 points.map(|(point_line, point_global)| {
61 (point_line.into(), point_global.into())
62 });
63
64 let direction = (b_global - a_global) / (b_line - a_line).t;
65 let origin = a_global + direction * -a_line.t;
66
67 Self::from_origin_and_direction(origin, direction)
68 }
69
70 pub fn origin(&self) -> Point<D> {
76 self.origin
77 }
78
79 pub fn direction(&self) -> Vector<D> {
85 self.direction
86 }
87
88 pub fn is_coincident_with(&self, other: &Self) -> bool {
95 let other_origin_is_not_on_self = {
96 let a = other.origin;
97 let b = self.origin;
98 let c = self.origin + self.direction;
99
100 Triangle::from_points([a, b, c]).is_ok()
103 };
104
105 if other_origin_is_not_on_self {
106 return false;
107 }
108
109 let d1 = self.direction.normalize();
110 let d2 = other.direction.normalize();
111
112 d1 == d2 || d1 == -d2
113 }
114
115 #[must_use]
117 pub fn reverse(mut self) -> Self {
118 self.origin += self.direction;
119 self.direction = -self.direction;
120 self
121 }
122
123 pub fn point_to_line_coords(&self, point: impl Into<Point<D>>) -> Point<1> {
132 Point {
133 coords: self.vector_to_line_coords(point.into() - self.origin),
134 }
135 }
136
137 pub fn vector_to_line_coords(
139 &self,
140 vector: impl Into<Vector<D>>,
141 ) -> Vector<1> {
142 let t = vector.into().scalar_projection_onto(&self.direction)
143 / self.direction.magnitude();
144 Vector::from([t])
145 }
146
147 pub fn point_from_line_coords(
149 &self,
150 point: impl Into<Point<1>>,
151 ) -> Point<D> {
152 self.origin + self.vector_from_line_coords(point.into().coords)
153 }
154
155 pub fn vector_from_line_coords(
157 &self,
158 vector: impl Into<Vector<1>>,
159 ) -> Vector<D> {
160 self.direction * vector.into().t
161 }
162}
163
164impl<const D: usize> approx::AbsDiffEq for Line<D> {
165 type Epsilon = <Scalar as approx::AbsDiffEq>::Epsilon;
166
167 fn default_epsilon() -> Self::Epsilon {
168 Scalar::default_epsilon()
169 }
170
171 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
172 self.origin.abs_diff_eq(&other.origin, epsilon)
173 && self.direction.abs_diff_eq(&other.direction, epsilon)
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use approx::assert_abs_diff_eq;
180
181 use crate::{Point, Scalar, Vector};
182
183 use super::Line;
184
185 #[test]
186 fn from_points_with_line_coords() {
187 let line = Line::from_points_with_line_coords([
188 ([0.], [0., 0.]),
189 ([1.], [1., 0.]),
190 ]);
191 assert_eq!(line.origin(), Point::from([0., 0.]));
192 assert_eq!(line.direction(), Vector::from([1., 0.]));
193
194 let line = Line::from_points_with_line_coords([
195 ([1.], [0., 1.]),
196 ([0.], [1., 1.]),
197 ]);
198 assert_eq!(line.origin(), Point::from([1., 1.]));
199 assert_eq!(line.direction(), Vector::from([-1., 0.]));
200
201 let line = Line::from_points_with_line_coords([
202 ([-1.], [0., 2.]),
203 ([0.], [1., 2.]),
204 ]);
205 assert_eq!(line.origin(), Point::from([1., 2.]));
206 assert_eq!(line.direction(), Vector::from([1., 0.]));
207 }
208
209 #[test]
210 fn is_coincident_with() {
211 let (line, _) = Line::from_points([[0., 0.], [1., 0.]]);
212
213 let (a, _) = Line::from_points([[0., 0.], [1., 0.]]);
214 let (b, _) = Line::from_points([[0., 0.], [-1., 0.]]);
215 let (c, _) = Line::from_points([[0., 1.], [1., 1.]]);
216
217 assert!(line.is_coincident_with(&a));
218 assert!(line.is_coincident_with(&b));
219 assert!(!line.is_coincident_with(&c));
220 }
221
222 #[test]
223 fn convert_point_to_line_coords() {
224 let line = Line {
225 origin: Point::from([1., 2., 3.]),
226 direction: Vector::from([2., 3., 5.]),
227 };
228
229 verify(line, -1.);
230 verify(line, 0.);
231 verify(line, 1.);
232 verify(line, 2.);
233
234 fn verify(line: Line<3>, t: f64) {
235 let point = line.point_from_line_coords([t]);
236 let t_result = line.point_to_line_coords(point);
237
238 assert_abs_diff_eq!(
239 Point::from([t]),
240 t_result,
241 epsilon = Scalar::from(1e-8)
242 );
243 }
244 }
245}