use axiolid_core::{Point2, Scalar, Tolerance, Vec2};
use axiolid_guarantees::Sign;
use axiolid_linear::Line2;
use axiolid_predicates::orient2d;
use crate::error::{InputSide, LinearIntersectionError};
use crate::validate::{finite_point, finite_vector, nonzero_direction};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineLineIntersection2 {
Point {
point: Point2,
left_parameter: Scalar,
right_parameter: Scalar,
},
Parallel,
Coincident,
}
pub fn line_line2(
left: Line2,
right: Line2,
tolerance: Tolerance,
) -> Result<LineLineIntersection2, LinearIntersectionError> {
validate(left, InputSide::Left)?;
validate(right, InputSide::Right)?;
let direction_origin = left.origin;
let left_tip = translate(direction_origin, left.direction)?;
let right_tip = translate(direction_origin, right.direction)?;
let directions_parallel =
sign(orient2d(direction_origin, left_tip, right_tip).sign())? == Sign::Zero;
if directions_parallel {
let left_second = translate(left.origin, left.direction)?;
let right_on_left = sign(orient2d(left.origin, left_second, right.origin).sign())?;
return Ok(if right_on_left == Sign::Zero {
LineLineIntersection2::Coincident
} else {
LineLineIntersection2::Parallel
});
}
let determinant = left.direction.x * right.direction.y - left.direction.y * right.direction.x;
if !determinant.is_finite() {
return Err(LinearIntersectionError::ArithmeticOverflow);
}
if determinant == 0.0 {
return Err(LinearIntersectionError::ArithmeticOverflow);
}
let delta_x = right.origin.x - left.origin.x;
let delta_y = right.origin.y - left.origin.y;
let left_parameter = (delta_x * right.direction.y - delta_y * right.direction.x) / determinant;
let right_parameter = (delta_x * left.direction.y - delta_y * left.direction.x) / determinant;
if !left_parameter.is_finite() || !right_parameter.is_finite() {
return Err(LinearIntersectionError::ArithmeticOverflow);
}
let point = Point2 {
x: left.origin.x + left_parameter * left.direction.x,
y: left.origin.y + left_parameter * left.direction.y,
};
if !point.x.is_finite() || !point.y.is_finite() {
return Err(LinearIntersectionError::ArithmeticOverflow);
}
let residual_x = point.x - (right.origin.x + right_parameter * right.direction.x);
let residual_y = point.y - (right.origin.y + right_parameter * right.direction.y);
let residual = residual_x.hypot(residual_y);
if !residual.is_finite() {
return Err(LinearIntersectionError::ArithmeticOverflow);
}
let scale = point.x.abs().max(point.y.abs()).max(1.0);
if residual > tolerance.linear() * scale && residual > f64::EPSILON * scale * 16.0 {
return Err(LinearIntersectionError::ArithmeticOverflow);
}
Ok(LineLineIntersection2::Point {
point,
left_parameter,
right_parameter,
})
}
fn validate(line: Line2, side: InputSide) -> Result<(), LinearIntersectionError> {
finite_point(line.origin, side)?;
finite_vector(line.direction, side)?;
nonzero_direction(line.direction, side)
}
fn translate(point: Point2, offset: Vec2) -> Result<Point2, LinearIntersectionError> {
let moved = Point2 {
x: point.x + offset.x,
y: point.y + offset.y,
};
if moved.x.is_finite() && moved.y.is_finite() {
Ok(moved)
} else {
Err(LinearIntersectionError::ArithmeticOverflow)
}
}
fn sign(sign: Option<Sign>) -> Result<Sign, LinearIntersectionError> {
sign.ok_or(LinearIntersectionError::ArithmeticOverflow)
}