use crate::point::Point;
pub fn area_of_triangle(a: Point, b: Point, c: Point) -> f64 {
0.5 * matrix_determinant(b.y - a.x, b.x - a.x, c.y - a.y, c.x - a.y)
}
pub fn point_of_intersection(a: Point, b: Point, c: Point, d: Point) -> Option<Point> {
let a1 = b.y - a.y;
let b1 = a.x - b.x;
let c1 = (a1 * a.x) + (b1 * a.y);
let a2 = d.y - c.y;
let b2 = c.x - d.x;
let c2 = (a2 * c.x) + (b2 * c.y);
let determinant = a1 * b2 - a2 * b1;
if determinant == 0.0 {
let x_r = (b.x - c.x) / (b.x - a.x);
let y_r = (b.y - c.y) / (b.y - a.y);
if x_r > 0.0 && x_r < 1.0 && y_r > 0.0 && y_r < 1.0 {
return Some(c);
}
return None;
}
let x = (b2 * c1 - b1 * c2) / determinant;
let y = (a1 * c2 - a2 * c1) / determinant;
let x_r1 = (b.x - x) / (b.x - a.x);
let y_r1 = (b.y - y) / (b.y - a.y);
let x_r2 = (d.x - x) / (d.x - c.x);
let y_r2 = (d.y - y) / (d.y - c.y);
#[allow(clippy::manual_range_contains)]
if x_r1 < 0.0
|| x_r1 > 1.0
|| y_r1 < 0.0
|| y_r1 > 1.0
|| x_r2 < 0.0
|| x_r2 > 1.0
|| y_r2 < 0.0
|| y_r2 > 1.0
{
return None;
}
Some(Point::new(x, y))
}
pub fn line_intersects_others(a: (Point, Point), others: &[(Point, Point)]) -> Option<usize> {
for (i, el) in others.iter().enumerate() {
if lines_intersect(a.0, a.1, el.0, el.1) {
return Some(i);
}
}
None
}
pub fn lines_intersect(a: Point, b: Point, c: Point, d: Point) -> bool {
let o1 = orientation(a, b, c);
let o2 = orientation(a, b, d);
let o3 = orientation(c, d, a);
let o4 = orientation(c, d, b);
if o1 != o2 && o3 != o4 {
return true;
}
if o1 == Orientation::Collinear && on_segment(a, c, b) {
return true;
}
if o2 == Orientation::Collinear && on_segment(a, d, b) {
return true;
}
if o3 == Orientation::Collinear && on_segment(c, a, d) {
return true;
}
if o4 == Orientation::Collinear && on_segment(c, b, d) {
return true;
}
false
}
#[derive(PartialEq)]
enum Orientation {
Collinear,
Clockwise,
AntiClockwise,
}
fn orientation(a: Point, b: Point, c: Point) -> Orientation {
let v: f64 = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y);
if v == 0.0 {
return Orientation::Collinear;
} else if v > 0.0 {
return Orientation::Clockwise;
}
Orientation::AntiClockwise
}
fn on_segment(a: Point, b: Point, c: Point) -> bool {
b.x <= a.x.max(c.x) && b.x >= a.x.min(c.x) && b.y <= a.y.max(c.y) && b.y >= a.y.min(c.y)
}
fn matrix_determinant(a: f64, b: f64, c: f64, d: f64) -> f64 {
a * d - b * c
}
#[cfg(test)]
mod tests {
use super::area_of_triangle;
use super::lines_intersect;
use super::point_of_intersection;
use crate::point::Point;
use crate::tests::assert_f64;
macro_rules! intersection_tests {
($($name:ident: $line_a:expr,$line_b:expr,$expected:expr,)*) => {
$(
#[test]
fn $name() {
let result = lines_intersect($line_a.0, $line_a.1, $line_b.0, $line_b.1);
assert_eq!(result, $expected);
}
)*
};
}
intersection_tests!(
does_not_intersect: (Point::zero(), Point::new(1.0, 1.0)), (Point::new(1.0, 0.0), Point::new(2.0, 1.0)), false,
does_intersect: (Point::zero(), Point::new(1.0, 1.0)), (Point::new(1.0, 0.0), Point::new(0.0, 1.0)), true,
does_intersect_but_not: (Point::new(1.0, 0.0), Point::new(1.0, 2.0)), (Point::new(0.0, 3.0), Point::new(2.0, 3.0)), false,
);
macro_rules! triangle_area_test {
($($name:ident: $value:expr,$expected:expr,)*) => {
$(
#[test]
fn $name() {
let result = area_of_triangle($value.0, $value.1, $value.2);
assert_f64!(result, $expected);
}
)*
};
}
triangle_area_test!(
ta_test1: (Point::new(0.0, 0.0), Point::new(1.0, 0.0), Point::new(0.0, 0.0)), 0.0,
ta_test2: (Point::new(0.0, 0.0), Point::new(0.0, 0.0), Point::new(0.0, 1.0)), 0.0,
ta_test3: (Point::new(0.0, 1.0), Point::new(3.0, 6.0), Point::new(6.0, 2.0)), 13.5,
ta_test4: (Point::new(0.0, 0.0), Point::new(1.0, 0.0), Point::new(0.0, 0.0)), 0.0,
ta_test5: (Point::new(0.0, 0.0), Point::new(0.0, 0.0), Point::new(0.0, 1.0)), 0.0,
ta_test6: (Point::new(0.0, 0.0), Point::new(0.0, 1.0), Point::new(1.0, 1.0)), 0.5,
ta_test7: (Point::new(0.0, 0.0), Point::new(1.0, 1.0), Point::new(1.0, 0.0)), 0.5,
);
macro_rules! intersection_point_test {
($($name:ident: $line_a:expr,$line_b:expr,$expected:expr,)*) => {
$(
#[test]
fn $name() {
let result = point_of_intersection($line_a.0, $line_a.1, $line_b.0, $line_b.1);
assert_eq!(result, $expected);
}
)*
};
}
intersection_point_test!(
not_intersecting: (Point::new(0.0, 0.0), Point::new(1.0, 1.0)), (Point::new(1.0, 0.0), Point::new(2.0, 1.0)), None,
simple_90: (Point::new(1.0, 0.0), Point::new(1.0, 2.0)), (Point::new(0.0, 1.0), Point::new(2.0, 1.0)), Some(Point::new(1.0, 1.0)),
intersects_outside: (Point::new(1.0, 0.0), Point::new(1.0, 2.0)), (Point::new(0.0, 3.0), Point::new(2.0, 3.0)), None,
intersection_parallel: (Point::new(0.0, 0.0), Point::new(2.0, 2.0)), (Point::new(1.0, 1.0), Point::new(3.0, 3.0)), Some(Point::new(1.0, 1.0)),
);
}