use crate::{Point, Scalar, Triangle, Vector};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[repr(C)]
pub struct Line<const D: usize> {
origin: Point<D>,
direction: Vector<D>,
}
impl<const D: usize> Line<D> {
pub fn from_origin_and_direction(
origin: Point<D>,
direction: Vector<D>,
) -> Self {
assert!(
direction.magnitude() != Scalar::ZERO,
"Can't construct `Line`. Direction is zero: {direction:?}"
);
Self { origin, direction }
}
pub fn from_points(
points: [impl Into<Point<D>>; 2],
) -> (Self, [Point<1>; 2]) {
let [a, b] = points.map(Into::into);
let line = Self::from_origin_and_direction(a, b - a);
let coords = [[0.], [1.]].map(Point::from);
(line, coords)
}
pub fn from_points_with_line_coords(
points: [(impl Into<Point<1>>, impl Into<Point<D>>); 2],
) -> Self {
let [(a_line, a_global), (b_line, b_global)] =
points.map(|(point_line, point_global)| {
(point_line.into(), point_global.into())
});
let direction = (b_global - a_global) / (b_line - a_line).t;
let origin = a_global + direction * -a_line.t;
Self::from_origin_and_direction(origin, direction)
}
pub fn origin(&self) -> Point<D> {
self.origin
}
pub fn direction(&self) -> Vector<D> {
self.direction
}
pub fn is_coincident_with(&self, other: &Self) -> bool {
let other_origin_is_not_on_self = {
let a = other.origin;
let b = self.origin;
let c = self.origin + self.direction;
Triangle::from_points([a, b, c]).is_ok()
};
if other_origin_is_not_on_self {
return false;
}
let d1 = self.direction.normalize();
let d2 = other.direction.normalize();
d1 == d2 || d1 == -d2
}
#[must_use]
pub fn reverse(mut self) -> Self {
self.origin += self.direction;
self.direction = -self.direction;
self
}
pub fn point_to_line_coords(&self, point: impl Into<Point<D>>) -> Point<1> {
Point {
coords: self.vector_to_line_coords(point.into() - self.origin),
}
}
pub fn vector_to_line_coords(
&self,
vector: impl Into<Vector<D>>,
) -> Vector<1> {
let t = vector.into().scalar_projection_onto(&self.direction)
/ self.direction.magnitude();
Vector::from([t])
}
pub fn point_from_line_coords(
&self,
point: impl Into<Point<1>>,
) -> Point<D> {
self.origin + self.vector_from_line_coords(point.into().coords)
}
pub fn vector_from_line_coords(
&self,
vector: impl Into<Vector<1>>,
) -> Vector<D> {
self.direction * vector.into().t
}
}
impl<const D: usize> approx::AbsDiffEq for Line<D> {
type Epsilon = <Scalar as approx::AbsDiffEq>::Epsilon;
fn default_epsilon() -> Self::Epsilon {
Scalar::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.origin.abs_diff_eq(&other.origin, epsilon)
&& self.direction.abs_diff_eq(&other.direction, epsilon)
}
}
#[cfg(test)]
mod tests {
use approx::assert_abs_diff_eq;
use crate::{Point, Scalar, Vector};
use super::Line;
#[test]
fn from_points_with_line_coords() {
let line = Line::from_points_with_line_coords([
([0.], [0., 0.]),
([1.], [1., 0.]),
]);
assert_eq!(line.origin(), Point::from([0., 0.]));
assert_eq!(line.direction(), Vector::from([1., 0.]));
let line = Line::from_points_with_line_coords([
([1.], [0., 1.]),
([0.], [1., 1.]),
]);
assert_eq!(line.origin(), Point::from([1., 1.]));
assert_eq!(line.direction(), Vector::from([-1., 0.]));
let line = Line::from_points_with_line_coords([
([-1.], [0., 2.]),
([0.], [1., 2.]),
]);
assert_eq!(line.origin(), Point::from([1., 2.]));
assert_eq!(line.direction(), Vector::from([1., 0.]));
}
#[test]
fn is_coincident_with() {
let (line, _) = Line::from_points([[0., 0.], [1., 0.]]);
let (a, _) = Line::from_points([[0., 0.], [1., 0.]]);
let (b, _) = Line::from_points([[0., 0.], [-1., 0.]]);
let (c, _) = Line::from_points([[0., 1.], [1., 1.]]);
assert!(line.is_coincident_with(&a));
assert!(line.is_coincident_with(&b));
assert!(!line.is_coincident_with(&c));
}
#[test]
fn convert_point_to_line_coords() {
let line = Line {
origin: Point::from([1., 2., 3.]),
direction: Vector::from([2., 3., 5.]),
};
verify(line, -1.);
verify(line, 0.);
verify(line, 1.);
verify(line, 2.);
fn verify(line: Line<3>, t: f64) {
let point = line.point_from_line_coords([t]);
let t_result = line.point_to_line_coords(point);
assert_abs_diff_eq!(
Point::from([t]),
t_result,
epsilon = Scalar::from(1e-8)
);
}
}
}