Classify how segments `a -> b` and `c -> d` relate in the XY plane. Crossing vs not is decided by exact orientation signs; crossing vs touching, and near-miss touches, by metric distance ([`XY_TOL`]).
(a: DVec2, b: DVec2, c: DVec2, d: DVec2)
| 125 | let o1 = orient2d(c, d, a); |
| 126 | let o2 = orient2d(c, d, b); |
| 127 | let o3 = orient2d(a, b, c); |
| 128 | let o4 = orient2d(a, b, d); |
| 129 | |
| 130 | // Exact proper-crossing test: each segment's endpoints straddle the |
| 131 | // other's supporting line (allowing exactly-on-line as straddling so a |
| 132 | // vertex sitting exactly on the other segment still yields a point). |
| 133 | let straddles = (o1 >= 0.0) != (o2 >= 0.0) || (o1 > 0.0) != (o2 > 0.0); |
| 134 | let straddled = (o3 >= 0.0) != (o4 >= 0.0) || (o3 > 0.0) != (o4 > 0.0); |
| 135 | if straddles && straddled { |
| 136 | let r = b - a; |
| 137 | let s = d - c; |
| 138 | let denom = r.perp_dot(s); |
| 139 | // A true crossing guarantees denom != 0; the division is |
| 140 | // well-conditioned because the straddle test already bounded the |
| 141 | // configuration away from parallel-and-disjoint. |
| 142 | let t = ((c - a).perp_dot(s) / denom).clamp(0.0, 1.0); |
| 143 | let u = ((c - a).perp_dot(r) / denom).clamp(0.0, 1.0); |
| 144 | let point = a + r * t; |
| 145 | let near_endpoint = point.distance(a) <= XY_TOL || point.distance(b) <= XY_TOL || point.distance(c) <= XY_TOL || point.distance(d) <= XY_TOL; |
| 146 | return if near_endpoint { |
| 147 | SegSeg::Touching { point, t, u } |
| 148 | } else { |
| 149 | SegSeg::Crossing { point, t, u } |
| 150 | }; |
| 151 | } |
| 152 | |
| 153 | // No exact crossing: the segments may still pass within XY_TOL of each |
| 154 | // other at an endpoint (a touch that drifted apart by float noise). |
| 155 | let candidates = [(a, c, d, 0.0, None), (b, c, d, 1.0, None), (c, a, b, 0.0, Some(())), (d, a, b, 1.0, Some(()))]; |
| 156 | let mut best: Option<(f64, DVec2, f64, f64)> = None; |
| 157 | for (p, s0, s1, fixed_param, swapped) in candidates { |
| 158 | let (proj, param) = project_onto_segment(p, s0, s1); |
| 159 | let dist = p.distance(proj); |
| 160 | if dist <= XY_TOL && best.is_none_or(|(bd, ..)| dist < bd) { |
| 161 | let (t, u) = if swapped.is_some() { (param, fixed_param) } else { (fixed_param, param) }; |
| 162 | best = Some((dist, proj, t, u)); |
| 163 | } |
| 164 | } |
| 165 | if let Some((_, point, t, u)) = best { |
| 166 | return SegSeg::Touching { point, t, u }; |
| 167 | } |
| 168 | SegSeg::Disjoint |
| 169 | } |
| 170 | |
| 171 | /// Signed perpendicular distance in metres from `point` to the directed line |
| 172 | /// `a -> b`. Positive on the left of the line; sign is exact (via |
| 173 | /// [`orient2d`]), magnitude is approximate. Returns `0.0` for a degenerate |
| 174 | /// (zero-length) edge. |
| 175 | pub(crate) fn signed_distance_to_line(point: DVec2, a: DVec2, b: DVec2) -> f64 { |
| 176 | let len = a.distance(b); |
| 177 | if len == 0.0 { |
| 178 | return 0.0; |
| 179 | } |
| 180 | orient2d(a, b, point) / len |
| 181 | } |
| 182 | |
| 183 | /// Closest point on segment `a -> b` to `p`, with its clamped parameter. |
| 184 | pub(crate) fn project_onto_segment(p: DVec2, a: DVec2, b: DVec2) -> (DVec2, f64) { |