use crate::scalar::Scalar;
use crate::segment::{BoundingBox, Segment};
use crate::traits::Transformation;
use crate::utils::min_max;
use crate::{point, vector, Box2D, Point, Vector};
use core::mem::swap;
use core::ops::Range;
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct LineSegment<S> {
pub from: Point<S>,
pub to: Point<S>,
}
impl<S: Scalar> LineSegment<S> {
#[inline]
pub fn sample(&self, t: S) -> Point<S> {
self.from.lerp(self.to, t)
}
#[inline]
pub fn x(&self, t: S) -> S {
self.from.x * (S::ONE - t) + self.to.x * t
}
#[inline]
pub fn y(&self, t: S) -> S {
self.from.y * (S::ONE - t) + self.to.y * t
}
#[inline]
pub fn from(&self) -> Point<S> {
self.from
}
#[inline]
pub fn to(&self) -> Point<S> {
self.to
}
pub fn solve_t_for_x(&self, x: S) -> S {
let dx = self.to.x - self.from.x;
if dx == S::ZERO {
return S::ZERO;
}
(x - self.from.x) / dx
}
pub fn solve_t_for_y(&self, y: S) -> S {
let dy = self.to.y - self.from.y;
if dy == S::ZERO {
return S::ZERO;
}
(y - self.from.y) / dy
}
pub fn solve_y_for_x(&self, x: S) -> S {
self.y(self.solve_t_for_x(x))
}
pub fn solve_x_for_y(&self, y: S) -> S {
self.x(self.solve_t_for_y(y))
}
#[inline]
pub fn flip(&self) -> Self {
LineSegment {
from: self.to,
to: self.from,
}
}
pub fn split_range(&self, t_range: Range<S>) -> Self {
LineSegment {
from: self.from.lerp(self.to, t_range.start),
to: self.from.lerp(self.to, t_range.end),
}
}
#[inline]
pub fn split(&self, t: S) -> (Self, Self) {
let split_point = self.sample(t);
(
LineSegment {
from: self.from,
to: split_point,
},
LineSegment {
from: split_point,
to: self.to,
},
)
}
#[inline]
pub fn before_split(&self, t: S) -> Self {
LineSegment {
from: self.from,
to: self.sample(t),
}
}
#[inline]
pub fn after_split(&self, t: S) -> Self {
LineSegment {
from: self.sample(t),
to: self.to,
}
}
pub fn split_at_x(&self, x: S) -> (Self, Self) {
self.split(self.solve_t_for_x(x))
}
#[inline]
pub fn bounding_box(&self) -> Box2D<S> {
let (min_x, max_x) = self.bounding_range_x();
let (min_y, max_y) = self.bounding_range_y();
Box2D {
min: point(min_x, min_y),
max: point(max_x, max_y),
}
}
#[inline]
fn bounding_range_x(&self) -> (S, S) {
min_max(self.from.x, self.to.x)
}
#[inline]
fn bounding_range_y(&self) -> (S, S) {
min_max(self.from.y, self.to.y)
}
#[inline]
pub fn to_vector(&self) -> Vector<S> {
self.to - self.from
}
#[inline]
pub fn to_line(&self) -> Line<S> {
Line {
point: self.from,
vector: self.to - self.from,
}
}
#[inline]
pub fn length(&self) -> S {
self.to_vector().length()
}
#[inline]
pub fn square_length(&self) -> S {
self.to_vector().square_length()
}
pub fn set_length(&mut self, new_length: S) {
let v = self.to_vector();
let old_length = v.length();
self.to = self.from + v * (new_length / old_length);
}
pub fn mid_point(&mut self) -> Point<S> {
(self.from + self.to.to_vector()) / S::TWO
}
#[inline]
pub fn translate(&mut self, by: Vector<S>) -> Self {
LineSegment {
from: self.from + by,
to: self.to + by,
}
}
#[inline]
pub fn transformed<T: Transformation<S>>(&self, transform: &T) -> Self {
LineSegment {
from: transform.transform_point(self.from),
to: transform.transform_point(self.to),
}
}
#[allow(clippy::suspicious_operation_groupings)]
pub fn intersection_t(&self, other: &Self) -> Option<(S, S)> {
if self.to == other.to
|| self.from == other.from
|| self.from == other.to
|| self.to == other.from
{
return None;
}
let v1 = self.to_vector();
let v2 = other.to_vector();
let v1_cross_v2 = v1.cross(v2);
if v1_cross_v2 == S::ZERO {
return None;
}
let sign_v1_cross_v2 = S::signum(v1_cross_v2);
let abs_v1_cross_v2 = S::abs(v1_cross_v2);
let v3 = other.from - self.from;
let t = v3.cross(v2) * sign_v1_cross_v2;
let u = v3.cross(v1) * sign_v1_cross_v2;
if t < S::ZERO || t > abs_v1_cross_v2 || u < S::ZERO || u > abs_v1_cross_v2 {
return None;
}
Some((t / abs_v1_cross_v2, u / abs_v1_cross_v2))
}
#[inline]
pub fn intersection(&self, other: &Self) -> Option<Point<S>> {
self.intersection_t(other).map(|(t, _)| self.sample(t))
}
pub fn line_intersection_t(&self, line: &Line<S>) -> Option<S> {
let v1 = self.to_vector();
let v2 = line.vector;
let v1_cross_v2 = v1.cross(v2);
if v1_cross_v2 == S::ZERO {
return None;
}
let sign_v1_cross_v2 = S::signum(v1_cross_v2);
let abs_v1_cross_v2 = S::abs(v1_cross_v2);
let v3 = line.point - self.from;
let t = v3.cross(v2) * sign_v1_cross_v2;
if t < S::ZERO || t > abs_v1_cross_v2 {
return None;
}
Some(t / abs_v1_cross_v2)
}
#[inline]
pub fn line_intersection(&self, line: &Line<S>) -> Option<Point<S>> {
self.line_intersection_t(line).map(|t| self.sample(t))
}
pub fn horizontal_line_intersection_t(&self, y: S) -> Option<S> {
Self::axis_aligned_intersection_1d(self.from.y, self.to.y, y)
}
pub fn vertical_line_intersection_t(&self, x: S) -> Option<S> {
Self::axis_aligned_intersection_1d(self.from.x, self.to.x, x)
}
#[inline]
pub fn horizontal_line_intersection(&self, y: S) -> Option<Point<S>> {
self.horizontal_line_intersection_t(y)
.map(|t| self.sample(t))
}
#[inline]
pub fn vertical_line_intersection(&self, x: S) -> Option<Point<S>> {
self.vertical_line_intersection_t(x).map(|t| self.sample(t))
}
fn axis_aligned_intersection_1d(mut a: S, mut b: S, v: S) -> Option<S> {
let swap = a > b;
if swap {
core::mem::swap(&mut a, &mut b);
}
let d = b - a;
if d == S::ZERO {
return None;
}
let t = (v - a) / d;
if t < S::ZERO || t > S::ONE {
return None;
}
Some(if swap { S::ONE - t } else { t })
}
#[inline]
pub fn intersects(&self, other: &Self) -> bool {
self.intersection_t(other).is_some()
}
#[inline]
pub fn intersects_line(&self, line: &Line<S>) -> bool {
self.line_intersection_t(line).is_some()
}
pub fn overlaps_line(&self, line: &Line<S>) -> bool {
let v1 = self.to_vector();
let v2 = line.vector;
let v3 = line.point - self.from;
v1.cross(v2) == S::ZERO && v1.cross(v3) == S::ZERO
}
pub fn overlaps_segment(&self, other: &LineSegment<S>) -> bool {
if !self.overlaps_line(&other.to_line()) {
return false;
}
let v1 = self.to - self.from;
let v2 = other.from - self.from;
let v3 = other.to - self.from;
let mut a = S::ZERO;
let mut b = v1.dot(v1);
let mut c = v1.dot(v2);
let mut d = v1.dot(v3);
if a > b {
swap(&mut a, &mut b);
}
if c > d {
swap(&mut d, &mut c);
}
(c > a && c < b)
|| (d > a && d < b)
|| (a > c && a < d)
|| (b > c && b < d)
|| (a == c && b == d)
}
pub fn contains_segment(&self, other: &LineSegment<S>) -> bool {
if !self.overlaps_line(&other.to_line()) {
return false;
}
let v1 = self.to - self.from;
let v2 = other.from - self.from;
let v3 = other.to - self.from;
let mut a = S::ZERO;
let mut b = v1.dot(v1);
let mut c = v1.dot(v2);
let mut d = v1.dot(v3);
if a > b {
swap(&mut a, &mut b);
}
if c > d {
swap(&mut d, &mut c);
}
c >= a && c <= b && d >= a && d <= b
}
pub fn clipped_x(&self, clip: Range<S>) -> Option<Self> {
if (self.from.x < clip.start && self.to.x < clip.start)
|| (self.from.x > clip.end && self.to.x > clip.end)
{
return None;
}
let mut flipped = false;
let mut result = *self;
if result.from.x > result.to.x {
flipped = true;
result = result.flip();
}
if result.from.x >= clip.start && result.to.x <= clip.end {
return Some(*self);
}
if result.from.x < clip.start {
let t = result
.vertical_line_intersection_t(clip.start)
.unwrap_or(S::ZERO);
result.from.x = clip.start;
result.from.y = result.y(t);
}
if result.to.x > clip.end {
let t = result
.vertical_line_intersection_t(clip.end)
.unwrap_or(S::ZERO);
result.to.x = clip.end;
result.to.y = result.y(t);
}
if flipped {
result = result.flip();
}
Some(result)
}
pub fn clipped_y(&self, clip: Range<S>) -> Option<Self> {
fn transpose<S: Copy>(r: &LineSegment<S>) -> LineSegment<S> {
LineSegment {
from: r.from.yx(),
to: r.to.yx(),
}
}
Some(transpose(&transpose(self).clipped_x(clip)?))
}
pub fn clipped(&self, clip: &Box2D<S>) -> Option<Self> {
self.clipped_x(clip.x_range())?.clipped_y(clip.y_range())
}
#[inline]
pub fn distance_to_point(&self, p: Point<S>) -> S {
self.square_distance_to_point(p).sqrt()
}
#[inline]
pub fn square_distance_to_point(&self, p: Point<S>) -> S {
(self.closest_point(p) - p).square_length()
}
#[inline]
pub fn closest_point(&self, p: Point<S>) -> Point<S> {
let v1 = self.to - self.from;
let v2 = p - self.from;
let t = S::min(S::max(v2.dot(v1) / v1.dot(v1), S::ZERO), S::ONE);
self.from + v1 * t
}
#[inline]
pub fn to_f32(&self) -> LineSegment<f32> {
LineSegment {
from: self.from.to_f32(),
to: self.to.to_f32(),
}
}
#[inline]
pub fn to_f64(&self) -> LineSegment<f64> {
LineSegment {
from: self.from.to_f64(),
to: self.to.to_f64(),
}
}
}
impl<S: Scalar> Segment for LineSegment<S> {
type Scalar = S;
fn from(&self) -> Point<S> {
self.from
}
fn to(&self) -> Point<S> {
self.to
}
fn sample(&self, t: S) -> Point<S> {
self.sample(t)
}
fn x(&self, t: S) -> S {
self.x(t)
}
fn y(&self, t: S) -> S {
self.y(t)
}
fn derivative(&self, _t: S) -> Vector<S> {
self.to_vector()
}
fn dx(&self, _t: S) -> S {
self.to.x - self.from.x
}
fn dy(&self, _t: S) -> S {
self.to.y - self.from.y
}
fn split(&self, t: S) -> (Self, Self) {
self.split(t)
}
fn before_split(&self, t: S) -> Self {
self.before_split(t)
}
fn after_split(&self, t: S) -> Self {
self.after_split(t)
}
fn split_range(&self, t_range: Range<S>) -> Self {
self.split_range(t_range)
}
fn flip(&self) -> Self {
self.flip()
}
fn approximate_length(&self, _tolerance: S) -> S {
self.length()
}
fn for_each_flattened_with_t(
&self,
_tolerance: Self::Scalar,
callback: &mut dyn FnMut(&LineSegment<S>, Range<S>),
) {
callback(self, S::ZERO..S::ONE);
}
}
impl<S: Scalar> BoundingBox for LineSegment<S> {
type Scalar = S;
fn bounding_range_x(&self) -> (S, S) {
self.bounding_range_x()
}
fn bounding_range_y(&self) -> (S, S) {
self.bounding_range_y()
}
fn fast_bounding_range_x(&self) -> (S, S) {
self.bounding_range_x()
}
fn fast_bounding_range_y(&self) -> (S, S) {
self.bounding_range_y()
}
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct Line<S> {
pub point: Point<S>,
pub vector: Vector<S>,
}
impl<S: Scalar> Line<S> {
pub fn intersection(&self, other: &Self) -> Option<Point<S>> {
let det = self.vector.cross(other.vector);
if S::abs(det) <= S::EPSILON {
return None;
}
let inv_det = S::ONE / det;
let self_p2 = self.point + self.vector;
let other_p2 = other.point + other.vector;
let a = self.point.to_vector().cross(self_p2.to_vector());
let b = other.point.to_vector().cross(other_p2.to_vector());
Some(point(
(b * self.vector.x - a * other.vector.x) * inv_det,
(b * self.vector.y - a * other.vector.y) * inv_det,
))
}
pub fn distance_to_point(&self, p: &Point<S>) -> S {
S::abs(self.signed_distance_to_point(p))
}
pub fn signed_distance_to_point(&self, p: &Point<S>) -> S {
let v = *p - self.point;
self.vector.cross(v) / self.vector.length()
}
pub fn square_distance_to_point(&self, p: Point<S>) -> S {
let v = p - self.point;
let c = self.vector.cross(v);
(c * c) / self.vector.square_length()
}
pub fn equation(&self) -> LineEquation<S> {
let a = -self.vector.y;
let b = self.vector.x;
let c = -(a * self.point.x + b * self.point.y);
LineEquation::new(a, b, c)
}
pub fn intersects_box(&self, rect: &Box2D<S>) -> bool {
let v = self.vector;
let diagonal = if (v.y >= S::ZERO) ^ (v.x >= S::ZERO) {
LineSegment {
from: rect.min,
to: rect.max,
}
} else {
LineSegment {
from: point(rect.max.x, rect.min.y),
to: point(rect.min.x, rect.max.y),
}
};
diagonal.intersects_line(self)
}
#[inline]
pub fn to_f32(&self) -> Line<f32> {
Line {
point: self.point.to_f32(),
vector: self.vector.to_f32(),
}
}
#[inline]
pub fn to_f64(&self) -> Line<f64> {
Line {
point: self.point.to_f64(),
vector: self.vector.to_f64(),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct LineEquation<S> {
a: S,
b: S,
c: S,
}
impl<S: Scalar> LineEquation<S> {
pub fn new(a: S, b: S, c: S) -> Self {
debug_assert!(a != S::ZERO || b != S::ZERO);
let div = S::ONE / S::sqrt(a * a + b * b);
LineEquation {
a: a * div,
b: b * div,
c: c * div,
}
}
#[inline]
pub fn a(&self) -> S {
self.a
}
#[inline]
pub fn b(&self) -> S {
self.b
}
#[inline]
pub fn c(&self) -> S {
self.c
}
pub fn project_point(&self, p: &Point<S>) -> Point<S> {
point(
self.b * (self.b * p.x - self.a * p.y) - self.a * self.c,
self.a * (self.a * p.y - self.b * p.x) - self.b * self.c,
)
}
#[inline]
pub fn signed_distance_to_point(&self, p: &Point<S>) -> S {
self.a * p.x + self.b * p.y + self.c
}
#[inline]
pub fn distance_to_point(&self, p: &Point<S>) -> S {
S::abs(self.signed_distance_to_point(p))
}
#[inline]
pub fn invert(&self) -> Self {
LineEquation {
a: -self.a,
b: -self.b,
c: -self.c,
}
}
#[inline]
pub fn parallel_line(&self, p: &Point<S>) -> Self {
let c = -(self.a * p.x + self.b * p.y);
LineEquation {
a: self.a,
b: self.b,
c,
}
}
#[inline]
pub fn offset(&self, d: S) -> Self {
LineEquation {
a: self.a,
b: self.b,
c: self.c - d,
}
}
#[inline]
pub fn tangent(&self) -> Vector<S> {
vector(self.b, -self.a)
}
#[inline]
pub fn normal(&self) -> Vector<S> {
vector(self.a, self.b)
}
#[inline]
pub fn solve_y_for_x(&self, x: S) -> Option<S> {
if self.b == S::ZERO {
return None;
}
Some((self.a * x + self.c) / -self.b)
}
#[inline]
pub fn solve_x_for_y(&self, y: S) -> Option<S> {
if self.a == S::ZERO {
return None;
}
Some((self.b * y + self.c) / -self.a)
}
#[inline]
pub fn is_horizontal(&self) -> bool {
self.a == S::ZERO
}
#[inline]
pub fn is_vertical(&self) -> bool {
self.b == S::ZERO
}
}
#[cfg(test)]
fn fuzzy_eq_f32(a: f32, b: f32, epsilon: f32) -> bool {
f32::abs(a - b) <= epsilon
}
#[cfg(test)]
fn fuzzy_eq_vector(a: Vector<f32>, b: Vector<f32>, epsilon: f32) -> bool {
fuzzy_eq_f32(a.x, b.x, epsilon) && fuzzy_eq_f32(a.y, b.y, epsilon)
}
#[cfg(test)]
fn fuzzy_eq_point(a: Point<f32>, b: Point<f32>, epsilon: f32) -> bool {
fuzzy_eq_vector(a.to_vector(), b.to_vector(), epsilon)
}
#[test]
fn intersection_rotated() {
use core::f32::consts::PI;
let epsilon = 0.0001;
let count: u32 = 100;
for i in 0..count {
for j in 0..count {
if i % (count / 2) == j % (count / 2) {
continue;
}
let angle1 = i as f32 / (count as f32) * 2.0 * PI;
let angle2 = j as f32 / (count as f32) * 2.0 * PI;
let l1 = LineSegment {
from: point(10.0 * angle1.cos(), 10.0 * angle1.sin()),
to: point(-10.0 * angle1.cos(), -10.0 * angle1.sin()),
};
let l2 = LineSegment {
from: point(10.0 * angle2.cos(), 10.0 * angle2.sin()),
to: point(-10.0 * angle2.cos(), -10.0 * angle2.sin()),
};
assert!(l1.intersects(&l2));
assert!(fuzzy_eq_point(
l1.sample(l1.intersection_t(&l2).unwrap().0),
point(0.0, 0.0),
epsilon
));
assert!(fuzzy_eq_point(
l2.sample(l1.intersection_t(&l2).unwrap().1),
point(0.0, 0.0),
epsilon
));
}
}
}
#[test]
fn intersection_touching() {
let l1 = LineSegment {
from: point(0.0, 0.0),
to: point(10.0, 10.0),
};
let l2 = LineSegment {
from: point(10.0, 10.0),
to: point(10.0, 0.0),
};
assert!(!l1.intersects(&l2));
assert!(l1.intersection(&l2).is_none());
}
#[test]
fn intersection_overlap() {
let l1 = LineSegment {
from: point(0.0, 0.0),
to: point(10.0, 0.0),
};
let l2 = LineSegment {
from: point(5.0, 00.0),
to: point(15.0, 0.0),
};
assert!(!l1.intersects(&l2));
assert!(l1.intersection(&l2).is_none());
}
#[cfg(test)]
use euclid::approxeq::ApproxEq;
#[test]
fn bounding_box() {
let l1 = LineSegment {
from: point(1.0, 5.0),
to: point(5.0, 7.0),
};
let r1 = Box2D {
min: point(1.0, 5.0),
max: point(5.0, 7.0),
};
let l2 = LineSegment {
from: point(5.0, 5.0),
to: point(1.0, 1.0),
};
let r2 = Box2D {
min: point(1.0, 1.0),
max: point(5.0, 5.0),
};
let l3 = LineSegment {
from: point(3.0, 3.0),
to: point(1.0, 5.0),
};
let r3 = Box2D {
min: point(1.0, 3.0),
max: point(3.0, 5.0),
};
let cases = std::vec![(l1, r1), (l2, r2), (l3, r3)];
for &(ls, r) in &cases {
assert_eq!(ls.bounding_box(), r);
}
}
#[test]
fn distance_to_point() {
use crate::vector;
let l1 = Line {
point: point(2.0f32, 3.0),
vector: vector(-1.5, 0.0),
};
let l2 = Line {
point: point(3.0f32, 3.0),
vector: vector(1.5, 1.5),
};
assert!(l1
.signed_distance_to_point(&point(1.1, 4.0))
.approx_eq(&-1.0));
assert!(l1
.signed_distance_to_point(&point(2.3, 2.0))
.approx_eq(&1.0));
assert!(l2
.signed_distance_to_point(&point(1.0, 0.0))
.approx_eq(&(-f32::sqrt(2.0) / 2.0)));
assert!(l2
.signed_distance_to_point(&point(0.0, 1.0))
.approx_eq(&(f32::sqrt(2.0) / 2.0)));
assert!(l1
.equation()
.distance_to_point(&point(1.1, 4.0))
.approx_eq(&1.0));
assert!(l1
.equation()
.distance_to_point(&point(2.3, 2.0))
.approx_eq(&1.0));
assert!(l2
.equation()
.distance_to_point(&point(1.0, 0.0))
.approx_eq(&(f32::sqrt(2.0) / 2.0)));
assert!(l2
.equation()
.distance_to_point(&point(0.0, 1.0))
.approx_eq(&(f32::sqrt(2.0) / 2.0)));
assert!(l1
.equation()
.signed_distance_to_point(&point(1.1, 4.0))
.approx_eq(&l1.signed_distance_to_point(&point(1.1, 4.0))));
assert!(l1
.equation()
.signed_distance_to_point(&point(2.3, 2.0))
.approx_eq(&l1.signed_distance_to_point(&point(2.3, 2.0))));
assert!(l2
.equation()
.signed_distance_to_point(&point(1.0, 0.0))
.approx_eq(&l2.signed_distance_to_point(&point(1.0, 0.0))));
assert!(l2
.equation()
.signed_distance_to_point(&point(0.0, 1.0))
.approx_eq(&l2.signed_distance_to_point(&point(0.0, 1.0))));
}
#[test]
fn solve_y_for_x() {
let line = Line {
point: Point::new(1.0, 1.0),
vector: Vector::new(2.0, 4.0),
};
let eqn = line.equation();
if let Some(y) = eqn.solve_y_for_x(line.point.x) {
std::println!("{y:?} != {:?}", line.point.y);
assert!(f64::abs(y - line.point.y) < 0.000001)
}
if let Some(x) = eqn.solve_x_for_y(line.point.y) {
assert!(f64::abs(x - line.point.x) < 0.000001)
}
let mut angle = 0.1;
for _ in 0..100 {
let (sin, cos) = f64::sin_cos(angle);
let line = Line {
point: Point::new(-1000.0, 600.0),
vector: Vector::new(cos * 100.0, sin * 100.0),
};
let eqn = line.equation();
if let Some(y) = eqn.solve_y_for_x(line.point.x) {
std::println!("{y:?} != {:?}", line.point.y);
assert!(f64::abs(y - line.point.y) < 0.000001)
}
if let Some(x) = eqn.solve_x_for_y(line.point.y) {
assert!(f64::abs(x - line.point.x) < 0.000001)
}
angle += 0.001;
}
}
#[test]
fn offset() {
let l1 = LineEquation::new(2.0, 3.0, 1.0);
let p = Point::new(10.0, 3.0);
let d = l1.signed_distance_to_point(&p);
let l2 = l1.offset(d);
assert!(l2.distance_to_point(&p) < 0.0000001f64);
}
#[test]
fn set_length() {
let mut a = LineSegment {
from: point(10.0, 1.0),
to: point(100.0, -15.0),
};
a.set_length(1.0);
assert!(a.length().approx_eq(&1.0));
a.set_length(1.5);
assert!(a.length().approx_eq(&1.5));
a.set_length(100.0);
assert!(a.length().approx_eq(&100.0));
a.set_length(-1.0);
assert!(a.length().approx_eq(&1.0));
}
#[test]
fn overlap() {
assert!(LineSegment {
from: point(0.0, 0.0),
to: point(-1.0, 0.0),
}
.overlaps_line(&Line {
point: point(100.0, 0.0),
vector: vector(10.0, 0.0),
}));
assert!(LineSegment {
from: point(0.0, 0.0),
to: point(1.0, 0.0),
}
.overlaps_line(&Line {
point: point(0.0, 0.0),
vector: vector(1.0, 0.0),
}));
assert!(LineSegment {
from: point(0.0, 0.0),
to: point(1.0, 0.0),
}
.overlaps_segment(&LineSegment {
from: point(0.0, 0.0),
to: point(1.0, 0.0),
}));
assert!(!LineSegment {
from: point(0.0, 0.0),
to: point(1.0, 0.0),
}
.overlaps_line(&Line {
point: point(0.0, 1.0),
vector: vector(1.0, 1.0),
}));
}
#[test]
fn contains_segment() {
assert!(LineSegment {
from: point(-1.0, 1.0),
to: point(4.0, 1.0),
}
.contains_segment(&LineSegment {
from: point(2.0, 1.0),
to: point(1.0, 1.0),
}));
}
#[test]
fn horizontal_line_intersection() {
let segment = LineSegment {
from: point(1.0, 2.0),
to: point(2.0, 3.0),
};
assert_eq!(segment.horizontal_line_intersection_t(2.0), Some(0.0));
assert_eq!(segment.horizontal_line_intersection_t(2.25), Some(0.25));
assert_eq!(segment.horizontal_line_intersection_t(2.5), Some(0.5));
assert_eq!(segment.horizontal_line_intersection_t(2.75), Some(0.75));
assert_eq!(segment.horizontal_line_intersection_t(3.0), Some(1.0));
assert_eq!(segment.horizontal_line_intersection_t(1.5), None);
assert_eq!(segment.horizontal_line_intersection_t(3.5), None);
let segment = LineSegment {
from: point(2.0, 3.0),
to: point(1.0, 2.0),
};
assert_eq!(segment.horizontal_line_intersection_t(2.0), Some(1.0));
assert_eq!(segment.horizontal_line_intersection_t(2.25), Some(0.75));
assert_eq!(segment.horizontal_line_intersection_t(2.5), Some(0.5));
assert_eq!(segment.horizontal_line_intersection_t(2.75), Some(0.25));
assert_eq!(segment.horizontal_line_intersection_t(3.0), Some(0.0));
assert_eq!(segment.horizontal_line_intersection_t(1.5), None);
assert_eq!(segment.horizontal_line_intersection_t(3.5), None);
}
#[test]
fn intersection_on_endpoint() {
let l1 = LineSegment {
from: point(0.0, 0.0),
to: point(0.0, 10.0),
};
let l2 = LineSegment {
from: point(0.0, 5.0),
to: point(10.0, 5.0),
};
assert_eq!(l1.intersection_t(&l2), Some((0.5, 0.0)));
assert_eq!(l2.intersection_t(&l1), Some((0.0, 0.5)));
let l3 = LineSegment {
from: point(10.0, 5.0),
to: point(0.0, 5.0),
};
assert_eq!(l1.intersection_t(&l3), Some((0.5, 1.0)));
assert_eq!(l3.intersection_t(&l1), Some((1.0, 0.5)));
}
#[test]
fn intersects_box() {
let b = Box2D {
min: point(1.0, 2.0),
max: point(4.0, 4.0),
};
assert!(!Line {
point: point(0.0, 0.0),
vector: vector(1.0, 0.0)
}
.intersects_box(&b));
assert!(!Line {
point: point(0.0, 0.0),
vector: vector(0.0, 1.0)
}
.intersects_box(&b));
assert!(!Line {
point: point(10.0, 0.0),
vector: vector(10.0, 10.0)
}
.intersects_box(&b));
assert!(!Line {
point: point(0.0, 10.0),
vector: vector(10.0, 10.0)
}
.intersects_box(&b));
assert!(Line {
point: point(1.5, 0.0),
vector: vector(1.0, 6.0)
}
.intersects_box(&b));
assert!(Line {
point: point(1.5, 0.0),
vector: vector(-1.0, 6.0)
}
.intersects_box(&b));
assert!(Line {
point: point(1.5, 2.5),
vector: vector(1.0, 0.5)
}
.intersects_box(&b));
assert!(Line {
point: point(1.5, 2.5),
vector: vector(-1.0, -2.0)
}
.intersects_box(&b));
}
#[test]
fn clipped() {
let b = Box2D {
min: point(1.0, 2.0),
max: point(3.0, 4.0),
};
fn approx_eq(a: LineSegment<f32>, b: LineSegment<f32>) -> bool {
let ok = a.from.approx_eq(&b.from) && a.to.approx_eq(&b.to);
if !ok {
std::println!("{a:?} != {b:?}");
}
ok
}
assert_eq!(
LineSegment {
from: point(0.0, 1.0),
to: point(4.0, 1.0)
}
.clipped(&b),
None
);
assert_eq!(
LineSegment {
from: point(0.0, 2.0),
to: point(4.0, 2.0)
}
.clipped(&b),
Some(LineSegment {
from: point(1.0, 2.0),
to: point(3.0, 2.0)
})
);
assert_eq!(
LineSegment {
from: point(0.0, 3.0),
to: point(4.0, 3.0)
}
.clipped(&b),
Some(LineSegment {
from: point(1.0, 3.0),
to: point(3.0, 3.0)
})
);
assert_eq!(
LineSegment {
from: point(0.0, 4.0),
to: point(4.0, 4.0)
}
.clipped(&b),
Some(LineSegment {
from: point(1.0, 4.0),
to: point(3.0, 4.0)
})
);
assert_eq!(
LineSegment {
from: point(0.0, 5.0),
to: point(4.0, 5.0)
}
.clipped(&b),
None
);
assert_eq!(
LineSegment {
from: point(4.0, 1.0),
to: point(0.0, 1.0)
}
.clipped(&b),
None
);
assert_eq!(
LineSegment {
from: point(4.0, 2.0),
to: point(0.0, 2.0)
}
.clipped(&b),
Some(LineSegment {
from: point(3.0, 2.0),
to: point(1.0, 2.0)
})
);
assert_eq!(
LineSegment {
from: point(4.0, 3.0),
to: point(0.0, 3.0)
}
.clipped(&b),
Some(LineSegment {
from: point(3.0, 3.0),
to: point(1.0, 3.0)
})
);
assert_eq!(
LineSegment {
from: point(4.0, 4.0),
to: point(0.0, 4.0)
}
.clipped(&b),
Some(LineSegment {
from: point(3.0, 4.0),
to: point(1.0, 4.0)
})
);
assert_eq!(
LineSegment {
from: point(4.0, 5.0),
to: point(0.0, 5.0)
}
.clipped(&b),
None
);
assert_eq!(
LineSegment {
from: point(0.0, 0.0),
to: point(0.0, 5.0)
}
.clipped(&b),
None
);
assert_eq!(
LineSegment {
from: point(1.0, 0.0),
to: point(1.0, 5.0)
}
.clipped(&b),
Some(LineSegment {
from: point(1.0, 2.0),
to: point(1.0, 4.0)
})
);
assert_eq!(
LineSegment {
from: point(2.0, 0.0),
to: point(2.0, 5.0)
}
.clipped(&b),
Some(LineSegment {
from: point(2.0, 2.0),
to: point(2.0, 4.0)
})
);
assert_eq!(
LineSegment {
from: point(3.0, 0.0),
to: point(3.0, 5.0)
}
.clipped(&b),
Some(LineSegment {
from: point(3.0, 2.0),
to: point(3.0, 4.0)
})
);
assert_eq!(
LineSegment {
from: point(4.0, 0.0),
to: point(4.0, 5.0)
}
.clipped(&b),
None
);
assert_eq!(
LineSegment {
from: point(0.0, 5.0),
to: point(0.0, 0.0)
}
.clipped(&b),
None
);
assert_eq!(
LineSegment {
from: point(1.0, 5.0),
to: point(1.0, 0.0)
}
.clipped(&b),
Some(LineSegment {
from: point(1.0, 4.0),
to: point(1.0, 2.0)
})
);
assert_eq!(
LineSegment {
from: point(2.0, 5.0),
to: point(2.0, 0.0)
}
.clipped(&b),
Some(LineSegment {
from: point(2.0, 4.0),
to: point(2.0, 2.0)
})
);
assert_eq!(
LineSegment {
from: point(3.0, 5.0),
to: point(3.0, 0.0)
}
.clipped(&b),
Some(LineSegment {
from: point(3.0, 4.0),
to: point(3.0, 2.0)
})
);
assert_eq!(
LineSegment {
from: point(4.0, 5.0),
to: point(4.0, 0.0)
}
.clipped(&b),
None
);
assert!(approx_eq(
LineSegment {
from: point(0.0, 2.0),
to: point(4.0, 4.0)
}
.clipped(&b)
.unwrap(),
LineSegment {
from: point(1.0, 2.5),
to: point(3.0, 3.5)
}
));
assert!(approx_eq(
LineSegment {
from: point(4.0, 4.0),
to: point(0.0, 2.0)
}
.clipped(&b)
.unwrap(),
LineSegment {
from: point(3.0, 3.5),
to: point(1.0, 2.5)
}
));
let inside = [
LineSegment {
from: point(1.0, 2.0),
to: point(3.0, 4.0),
},
LineSegment {
from: point(1.5, 2.0),
to: point(1.0, 4.0),
},
LineSegment {
from: point(1.0, 3.0),
to: point(2.0, 3.0),
},
];
for segment in &inside {
assert_eq!(segment.clipped(&b), Some(*segment));
assert_eq!(segment.flip().clipped(&b), Some(segment.flip()));
}
let outside = [
LineSegment {
from: point(2.0, 0.0),
to: point(5.0, 3.0),
},
LineSegment {
from: point(-20.0, 0.0),
to: point(4.0, 8.0),
},
];
for segment in &outside {
assert_eq!(segment.clipped(&b), None);
assert_eq!(segment.flip().clipped(&b), None);
}
}
#[test]
fn equation() {
let lines = [
Line {
point: point(100.0f64, 20.0),
vector: vector(-1.0, 3.0),
},
Line {
point: point(-30.0, 150.0),
vector: vector(10.0, 2.0),
},
Line {
point: point(50.0, -10.0),
vector: vector(5.0, -1.0),
},
];
for line in &lines {
let eqn = line.equation();
use euclid::approxeq::ApproxEq;
for t in [-100.0, -50.0, 0.0, 25.0, 325.0] {
let p = line.point + line.vector * t;
assert!(eqn.solve_y_for_x(p.x).unwrap().approx_eq(&p.y));
assert!(eqn.solve_x_for_y(p.y).unwrap().approx_eq(&p.x));
}
}
}