axiolid_linear_intersection/
line_line.rs1use axiolid_core::{Point2, Scalar, Tolerance, Vec2};
9use axiolid_guarantees::Sign;
10use axiolid_linear::Line2;
11use axiolid_predicates::orient2d;
12
13use crate::error::{InputSide, LinearIntersectionError};
14use crate::validate::{finite_point, finite_vector, nonzero_direction};
15
16#[non_exhaustive]
18#[derive(Debug, Clone, Copy, PartialEq)]
19pub enum LineLineIntersection2 {
20 Point {
22 point: Point2,
24 left_parameter: Scalar,
26 right_parameter: Scalar,
28 },
29 Parallel,
31 Coincident,
33}
34
35pub fn line_line2(
41 left: Line2,
42 right: Line2,
43 tolerance: Tolerance,
44) -> Result<LineLineIntersection2, LinearIntersectionError> {
45 validate(left, InputSide::Left)?;
46 validate(right, InputSide::Right)?;
47
48 let direction_origin = left.origin;
52 let left_tip = translate(direction_origin, left.direction)?;
53 let right_tip = translate(direction_origin, right.direction)?;
54 let directions_parallel =
55 sign(orient2d(direction_origin, left_tip, right_tip).sign())? == Sign::Zero;
56
57 if directions_parallel {
58 let left_second = translate(left.origin, left.direction)?;
60 let right_on_left = sign(orient2d(left.origin, left_second, right.origin).sign())?;
61 return Ok(if right_on_left == Sign::Zero {
62 LineLineIntersection2::Coincident
63 } else {
64 LineLineIntersection2::Parallel
65 });
66 }
67
68 let determinant = left.direction.x * right.direction.y - left.direction.y * right.direction.x;
69 if !determinant.is_finite() {
70 return Err(LinearIntersectionError::ArithmeticOverflow);
71 }
72 if determinant == 0.0 {
73 return Err(LinearIntersectionError::ArithmeticOverflow);
76 }
77
78 let delta_x = right.origin.x - left.origin.x;
79 let delta_y = right.origin.y - left.origin.y;
80 let left_parameter = (delta_x * right.direction.y - delta_y * right.direction.x) / determinant;
81 let right_parameter = (delta_x * left.direction.y - delta_y * left.direction.x) / determinant;
82 if !left_parameter.is_finite() || !right_parameter.is_finite() {
83 return Err(LinearIntersectionError::ArithmeticOverflow);
84 }
85
86 let point = Point2 {
87 x: left.origin.x + left_parameter * left.direction.x,
88 y: left.origin.y + left_parameter * left.direction.y,
89 };
90 if !point.x.is_finite() || !point.y.is_finite() {
91 return Err(LinearIntersectionError::ArithmeticOverflow);
92 }
93
94 let residual_x = point.x - (right.origin.x + right_parameter * right.direction.x);
98 let residual_y = point.y - (right.origin.y + right_parameter * right.direction.y);
99 let residual = residual_x.hypot(residual_y);
100 if !residual.is_finite() {
101 return Err(LinearIntersectionError::ArithmeticOverflow);
102 }
103 let scale = point.x.abs().max(point.y.abs()).max(1.0);
104 if residual > tolerance.linear() * scale && residual > f64::EPSILON * scale * 16.0 {
105 return Err(LinearIntersectionError::ArithmeticOverflow);
106 }
107
108 Ok(LineLineIntersection2::Point {
109 point,
110 left_parameter,
111 right_parameter,
112 })
113}
114
115fn validate(line: Line2, side: InputSide) -> Result<(), LinearIntersectionError> {
116 finite_point(line.origin, side)?;
117 finite_vector(line.direction, side)?;
118 nonzero_direction(line.direction, side)
119}
120
121fn translate(point: Point2, offset: Vec2) -> Result<Point2, LinearIntersectionError> {
124 let moved = Point2 {
125 x: point.x + offset.x,
126 y: point.y + offset.y,
127 };
128 if moved.x.is_finite() && moved.y.is_finite() {
129 Ok(moved)
130 } else {
131 Err(LinearIntersectionError::ArithmeticOverflow)
132 }
133}
134
135fn sign(sign: Option<Sign>) -> Result<Sign, LinearIntersectionError> {
137 sign.ok_or(LinearIntersectionError::ArithmeticOverflow)
138}