use nalgebra::{RealField, SVector, Scalar, Vector2};
use num_traits::{One, Signed, Zero};
use rectutils::{Number, Rect};
pub type LineSegment2<T> = LineSegment<T, 2>;
pub type LineSegment3<T> = LineSegment<T, 3>;
#[derive(Clone, Debug)]
pub struct LineSegment<T, const D: usize> {
pub start: SVector<T, D>,
pub end: SVector<T, D>,
}
impl<T, const D: usize> LineSegment<T, D>
where
T: Zero + One + Scalar + RealField,
{
pub fn new(start: &SVector<T, D>, end: &SVector<T, D>) -> Self {
Self {
start: start.clone_owned(),
end: end.clone_owned(),
}
}
pub fn swapped(&self) -> Self {
Self::new(&self.end, &self.start)
}
pub fn is_degenerate(&self) -> bool {
self.start == self.end
}
pub fn interpolate(&self, t: T) -> SVector<T, D> {
self.start.lerp(&self.end, t)
}
pub fn interpolate_clamped(&self, t: T) -> SVector<T, D> {
self.interpolate(t.clamp(<T as Zero>::zero(), <T as One>::one()))
}
pub fn vector(&self) -> SVector<T, D> {
self.end.clone() - self.start.clone()
}
pub fn length(&self) -> T {
self.vector().norm()
}
pub fn length_squared(&self) -> T {
self.vector().norm_squared()
}
pub fn nearest_t(&self, point: &SVector<T, D>) -> T {
let v = self.vector();
let u = self.start.clone() - point;
let n2 = v.norm_squared();
if n2.is_zero() {
return T::zero();
}
-v.dot(&u) / n2
}
pub fn nearest_point(&self, point: &SVector<T, D>) -> SVector<T, D> {
self.interpolate_clamped(self.nearest_t(point))
}
pub fn distance_squared(&self, point: &SVector<T, D>) -> T {
(point - self.nearest_point(point)).norm_squared()
}
pub fn distance(&self, point: &SVector<T, D>) -> T {
(point - self.nearest_point(point)).norm()
}
}
impl<T> LineSegment2<T>
where
T: Zero + One + Scalar + RealField,
{
pub fn bounds(&self) -> Rect<T>
where
T: Number,
{
Rect::from_points(self.start, self.end)
}
pub fn collinearity(&self, point: &Vector2<T>) -> T {
let v = self.vector();
let u = self.start.clone() - point;
v.x.clone() * u.y.clone() - u.x.clone() * v.y.clone()
}
pub fn intersects(&self, other: &LineSegment2<T>) -> bool {
fn pos<T>(t: &T) -> bool
where
T: Zero + Signed,
{
t.is_positive() && !t.is_zero()
}
fn neg<T>(t: &T) -> bool
where
T: Zero + Signed,
{
t.is_negative() && !t.is_zero()
}
let o1 = self.collinearity(&other.start);
let o2 = self.collinearity(&other.end);
let s1 = other.collinearity(&self.start);
let s2 = other.collinearity(&self.end);
if neg(&s1) && neg(&s2) || pos(&s1) && pos(&s2) {
return false;
}
if neg(&o1) && neg(&o2) || pos(&o1) && pos(&o2) {
return false;
}
true
}
}
#[cfg(test)]
mod test {
use super::*;
use nalgebra::Vector2;
#[test]
fn nearest_at_start() {
let segment = LineSegment2::new(&Vector2::new(0.0, 0.0), &Vector2::new(1.0, 2.0));
assert_eq!(segment.nearest_t(&Vector2::new(-1.0, -1.0)).max(0.0), 0.0);
assert_eq!(
segment.nearest_point(&Vector2::new(-1.0, -1.0)),
Vector2::new(0.0, 0.0)
);
assert_eq!(segment.distance_squared(&Vector2::new(-1.0, -1.0)), 2.0);
assert_eq!(segment.distance(&Vector2::new(-1.0, 0.0)), 1.0);
}
#[test]
fn nearest_at_end() {
let segment = LineSegment2::new(&Vector2::new(0.0, 0.0), &Vector2::new(1.0, 2.0));
assert_eq!(segment.nearest_t(&Vector2::new(2.0, 2.0)).min(1.0), 1.0);
assert_eq!(
segment.nearest_point(&Vector2::new(2.0, 2.0)),
Vector2::new(1.0, 2.0)
);
assert_eq!(segment.distance_squared(&Vector2::new(3.0, 2.0)), 4.0);
assert_eq!(segment.distance(&Vector2::new(3.0, 2.0)), 2.0);
}
#[test]
fn nearest_in_middle() {
let segment = LineSegment2::new(&Vector2::new(0.0, 0.0), &Vector2::new(1.0, 2.0));
assert_eq!(segment.nearest_t(&Vector2::new(2.5, 0.0)), 0.5);
assert_eq!(
segment.nearest_point(&Vector2::new(2.5, 0.0)),
Vector2::new(0.5, 1.0)
);
assert_eq!(segment.distance_squared(&Vector2::new(2.5, 0.0)), 5.0);
}
#[test]
fn length() {
let segment = LineSegment2::new(&Vector2::new(0.0, 0.0), &Vector2::new(4.0, 3.0));
assert_eq!(segment.length_squared(), 25.0);
assert_eq!(segment.length(), 5.0);
}
#[test]
fn degenerate() {
let segment = LineSegment2::new(&Vector2::new(1.0, 2.0), &Vector2::new(1.0, 2.0));
assert!(segment.is_degenerate());
assert_eq!(segment.length_squared(), 0.0);
assert_eq!(segment.length(), 0.0);
}
#[test]
fn collinear() {
let segment = LineSegment2::new(&Vector2::new(0.0, 0.0), &Vector2::new(1.0, 2.0));
assert_eq!(segment.collinearity(&Vector2::new(2.0, 4.0)), 0.0);
assert_eq!(segment.collinearity(&Vector2::new(0.0, 0.0)), 0.0);
assert_eq!(segment.collinearity(&Vector2::new(1.0, 2.0)), 0.0);
assert!(
segment.collinearity(&Vector2::new(1.0, 5.0)) < 0.0,
"{} >= 0.0",
segment.collinearity(&Vector2::new(1.0, 5.0))
);
assert!(
segment.collinearity(&Vector2::new(1.0, 3.0)) < 0.0,
"{} >= 0.0",
segment.collinearity(&Vector2::new(1.0, 3.0))
);
assert!(
segment.collinearity(&Vector2::new(1.0, 1.0)) > 0.0,
"{} <= 0.0",
segment.collinearity(&Vector2::new(1.0, 1.0))
);
assert!(
segment.collinearity(&Vector2::new(-1.0, -5.0)) > 0.0,
"{} <= 0.0",
segment.collinearity(&Vector2::new(-1.0, -5.0))
);
}
#[test]
fn intersects() {
let a = LineSegment::new(&Vector2::new(1.0, 2.0), &Vector2::new(3.0, 1.0));
let b = LineSegment::new(&Vector2::new(2.0, 0.0), &Vector2::new(2.5, 3.0));
let c = LineSegment::new(&Vector2::new(1.0, 2.0), &Vector2::new(-3.0, 1.0));
assert!(a.intersects(&b));
assert!(a.intersects(&c));
assert!(b.intersects(&a));
assert!(c.intersects(&a));
assert!(a.swapped().intersects(&b));
assert!(a.swapped().intersects(&c));
}
#[test]
fn not_intersects() {
let a = LineSegment::new(&Vector2::new(1.0, 2.0), &Vector2::new(3.0, 1.0));
let b = LineSegment::new(&Vector2::new(0.0, 0.0), &Vector2::new(-1.0, 6.0));
let c = LineSegment::new(&Vector2::new(2.0, 0.0), &Vector2::new(2.0, -1.0));
assert!(!a.intersects(&b));
assert!(!b.intersects(&c));
assert!(!c.intersects(&a));
assert!(!b.intersects(&a));
assert!(!c.intersects(&b));
assert!(!a.intersects(&c));
assert!(!a.swapped().intersects(&b));
assert!(!b.swapped().intersects(&c));
assert!(!c.swapped().intersects(&a));
}
}