|
| 1 | +/// Definitions used in documentation for this module; |
| 2 | +/// |
| 3 | +/// - **line**: |
| 4 | +/// - The straight path on a plane that |
| 5 | +/// - extends infinitely in both directions. |
| 6 | +/// - defined by two distinct points `a` and `b` and |
| 7 | +/// - has the direction of the vector from `a` to `b` |
| 8 | +/// |
| 9 | +/// - **segment**: |
| 10 | +/// - A finite portion of a `line` which |
| 11 | +/// - lies between the points `a` and `b` |
| 12 | +/// - has the direction of the vector from `a` to `b` |
| 13 | +/// |
| 14 | +/// - **ray**: |
| 15 | +/// - A segment which extends infinitely in the forward direction |
| 16 | +/// |
| 17 | +/// - `Line`: the type [crate::Line] which is actually a **segment**. |
| 18 | +
|
| 19 | +use super::vector_extensions::VectorExtensions; |
| 20 | +use crate::{ |
| 21 | + Coord, |
| 22 | + CoordFloat, |
| 23 | + CoordNum, |
| 24 | + // algorithm::kernels::Kernel, |
| 25 | + // algorithm::kernels::RobustKernel, |
| 26 | + // Orientation |
| 27 | +}; |
| 28 | + |
| 29 | +/// Used to encode the relationship between a **segment** and an intersection |
| 30 | +/// point. See documentation for [LineIntersectionResultWithRelationships] |
| 31 | +#[derive(PartialEq, Eq, Debug, Clone, Copy)] |
| 32 | +pub(super) enum LineSegmentIntersectionType { |
| 33 | + /// The intersection point lies between the start and end of the **segment** |
| 34 | + /// |
| 35 | + /// Abbreviated to `TIP` in original paper |
| 36 | + TrueIntersectionPoint, |
| 37 | + /// The intersection point is 'false' or 'virtual': it lies on the same |
| 38 | + /// **line** as the **segment**, but not between the start and end points of |
| 39 | + /// the **segment**. |
| 40 | + /// |
| 41 | + /// Abbreviated to `FIP` in original paper |
| 42 | + |
| 43 | + // Note: Rust does not permit nested enum declaration, so |
| 44 | + // FalseIntersectionPointType has to be declared below. |
| 45 | + FalseIntersectionPoint(FalseIntersectionPointType), |
| 46 | +} |
| 47 | + |
| 48 | +/// These are the variants of [LineSegmentIntersectionType::FalseIntersectionPoint] |
| 49 | +#[derive(PartialEq, Eq, Debug, Clone, Copy)] |
| 50 | +pub(super) enum FalseIntersectionPointType { |
| 51 | + /// The intersection point is 'false' or 'virtual': it lies on the same |
| 52 | + /// **line** as the **segment**, and before the start of the **segment**. |
| 53 | + /// |
| 54 | + /// Abbreviated to `NFIP` in original paper (Negative) |
| 55 | + /// (also referred to as `FFIP` in Figure 6, but i think this is an |
| 56 | + /// error?) |
| 57 | + BeforeStart, |
| 58 | + /// The intersection point is 'false' or 'virtual': it lies on the same |
| 59 | + /// **line** as the **segment**, and after the end of the **segment**. |
| 60 | + /// |
| 61 | + /// Abbreviated to `PFIP` in original paper (Positive) |
| 62 | + AfterEnd, |
| 63 | +} |
| 64 | + |
| 65 | + |
| 66 | +/// Struct to contain the result for [line_segment_intersection_with_relationships] |
| 67 | +#[derive(Clone, Debug)] |
| 68 | +pub(super) struct LineIntersectionResultWithRelationships<T> |
| 69 | +where |
| 70 | + T: CoordNum, |
| 71 | +{ |
| 72 | + pub ab: LineSegmentIntersectionType, |
| 73 | + pub cd: LineSegmentIntersectionType, |
| 74 | + pub intersection: Coord<T>, |
| 75 | +} |
| 76 | + |
| 77 | +/// Computes the intersection between two line segments; |
| 78 | +/// a to b (`ab`), and c to d (`cd`) |
| 79 | +/// |
| 80 | +/// > note: looks like there is already `cartesian_intersect` as a private |
| 81 | +/// > method in simplifyvw.rs. It uses the orient2d method of [Kernel], |
| 82 | +/// > however it only gives a true/false answer and does not return the |
| 83 | +/// > intersection point or parameters needed. |
| 84 | +/// |
| 85 | +/// We already have LineIntersection trait BUT we need a function that also |
| 86 | +/// returns the parameters for both lines described below. The LineIntersection |
| 87 | +/// trait uses some fancy unrolled code it seems unlikely it could be adapted |
| 88 | +/// for this purpose. |
| 89 | +/// |
| 90 | +/// Returns the intersection point **and** parameters `t_ab` and `t_cd` |
| 91 | +/// described below |
| 92 | +/// |
| 93 | +/// The intersection of segments can be expressed as a parametric equation |
| 94 | +/// where `t_ab` and `t_cd` are unknown scalars : |
| 95 | +/// |
| 96 | +/// ```text |
| 97 | +/// a + ab · t_ab = c + cd · t_cd |
| 98 | +/// ``` |
| 99 | +/// |
| 100 | +/// > note: a real intersection can only happen when `0 <= t_ab <= 1` and |
| 101 | +/// > `0 <= t_cd <= 1` but this function will find intersections anyway |
| 102 | +/// > which may lay outside of the line segments |
| 103 | +/// |
| 104 | +/// This can be rearranged as follows: |
| 105 | +/// |
| 106 | +/// ```text |
| 107 | +/// ab · t_ab - cd · t_cd = c - a |
| 108 | +/// ``` |
| 109 | +/// |
| 110 | +/// Collecting the scalars `t_ab` and `-t_cd` into the column vector `T`, |
| 111 | +/// and by collecting the vectors `ab` and `cd` into matrix `M`: |
| 112 | +/// we get the matrix form: |
| 113 | +/// |
| 114 | +/// ```text |
| 115 | +/// [ab_x cd_x][ t_ab] = [ac_x] |
| 116 | +/// [ab_y cd_y][-t_cd] [ac_y] |
| 117 | +/// ``` |
| 118 | +/// |
| 119 | +/// or |
| 120 | +/// |
| 121 | +/// ```text |
| 122 | +/// M·T=ac |
| 123 | +/// ``` |
| 124 | +/// |
| 125 | +/// Inverting the matrix `M` involves taking the reciprocal of the determinant |
| 126 | +/// (the determinant is same as the of the [cross_product()] of `ab` and `cd`) |
| 127 | +/// |
| 128 | +/// ```text |
| 129 | +/// 1/(ab×cd) |
| 130 | +/// ``` |
| 131 | +/// |
| 132 | +/// Therefore if `ab×cd = 0` the determinant is undefined and the matrix cannot |
| 133 | +/// be inverted. The lines are either |
| 134 | +/// a) parallel or |
| 135 | +/// b) collinear |
| 136 | +/// |
| 137 | +/// Pre-multiplying both sides by the inverted 2x2 matrix we get: |
| 138 | +/// |
| 139 | +/// ```text |
| 140 | +/// [ t_ab] = 1/(ab×cd) · [ cd_y -cd_x][ac_x] |
| 141 | +/// [-t_cd] [-ab_y ab_x][ac_y] |
| 142 | +/// ``` |
| 143 | +/// |
| 144 | +/// or |
| 145 | +/// |
| 146 | +/// ```text |
| 147 | +/// T = M⁻¹·ac |
| 148 | +/// ``` |
| 149 | +/// |
| 150 | +/// Expands to: |
| 151 | +/// |
| 152 | +/// ```text |
| 153 | +/// [ t_ab] = 1/(ab_x·cd_y - ab_y·cd_x)·[ cd_y·ac_x - cd_x·ac_y] |
| 154 | +/// [-t_cd] [-ab_y·ac_x + ab_x·ac_y] |
| 155 | +/// ``` |
| 156 | +/// |
| 157 | +/// Since it is tidier to write cross products, observe that the above is |
| 158 | +/// equivalent to: |
| 159 | +/// |
| 160 | +/// ```text |
| 161 | +/// [t_ab] = [ ac×cd / ab×cd ] |
| 162 | +/// [t_cd] = [ - ab×ac / ab×cd ] |
| 163 | +/// ``` |
| 164 | +
|
| 165 | +fn line_segment_intersection_with_parameters<T>( |
| 166 | + a: Coord<T>, |
| 167 | + b: Coord<T>, |
| 168 | + c: Coord<T>, |
| 169 | + d: Coord<T>, |
| 170 | +) -> Option<(T, T, Coord<T>)> |
| 171 | +where |
| 172 | + T: CoordFloat, |
| 173 | +{ |
| 174 | + let ab = b - a; |
| 175 | + let cd = d - c; |
| 176 | + let ac = c - a; |
| 177 | + |
| 178 | + let ab_cross_cd = ab.cross_product_2d(cd); |
| 179 | + if T::is_zero(&ab_cross_cd) { |
| 180 | + // Segments are exactly parallel or colinear |
| 181 | + None |
| 182 | + } else { |
| 183 | + // Division by zero is prevented, but testing is needed to see what |
| 184 | + // happens for near-parallel sections of line. |
| 185 | + let t_ab = ac.cross_product_2d(cd) / ab_cross_cd; |
| 186 | + let t_cd = -ab.cross_product_2d(ac) / ab_cross_cd; |
| 187 | + let intersection = a + ab * t_ab; |
| 188 | + |
| 189 | + Some((t_ab, t_cd, intersection)) |
| 190 | + } |
| 191 | + |
| 192 | + // TODO: |
| 193 | + // The above could be replaced with the following, but at the cost of |
| 194 | + // repeating some computation. |
| 195 | + |
| 196 | + // match RobustKernel::orient2d(*a, *b, *d) { |
| 197 | + // Orientation::Collinear => None, |
| 198 | + // _ => { |
| 199 | + // let t_ab = cross_product_2d(ac, cd) / ab_cross_cd; |
| 200 | + // let t_cd = -cross_product_2d(ab, ac) / ab_cross_cd; |
| 201 | + // let intersection = *a + ab * t_ab; |
| 202 | + // Some((t_ab, t_cd, intersection)) |
| 203 | + // } |
| 204 | + // } |
| 205 | +} |
| 206 | + |
| 207 | +/// This is a simple wrapper for [line_segment_intersection_with_parameters]; |
| 208 | +/// Returns the intersection point as well as the relationship between the point |
| 209 | +/// and each of the input line segments. See [LineSegmentIntersectionType] |
| 210 | +pub(super) fn line_segment_intersection_with_relationships<T>( |
| 211 | + a: Coord<T>, |
| 212 | + b: Coord<T>, |
| 213 | + c: Coord<T>, |
| 214 | + d: Coord<T>, |
| 215 | +) -> Option<LineIntersectionResultWithRelationships<T>> |
| 216 | +where |
| 217 | + T: CoordFloat, |
| 218 | +{ |
| 219 | + line_segment_intersection_with_parameters(a, b, c, d).map(|(t_ab, t_cd, intersection)| { |
| 220 | + use FalseIntersectionPointType::{AfterEnd, BeforeStart}; |
| 221 | + use LineSegmentIntersectionType::{FalseIntersectionPoint, TrueIntersectionPoint}; |
| 222 | + LineIntersectionResultWithRelationships { |
| 223 | + ab: if T::zero() <= t_ab && t_ab <= T::one() { |
| 224 | + TrueIntersectionPoint |
| 225 | + } else if t_ab < T::zero() { |
| 226 | + FalseIntersectionPoint(BeforeStart) |
| 227 | + } else { |
| 228 | + FalseIntersectionPoint(AfterEnd) |
| 229 | + }, |
| 230 | + cd: if T::zero() <= t_cd && t_cd <= T::one() { |
| 231 | + TrueIntersectionPoint |
| 232 | + } else if t_cd < T::zero() { |
| 233 | + FalseIntersectionPoint(BeforeStart) |
| 234 | + } else { |
| 235 | + FalseIntersectionPoint(AfterEnd) |
| 236 | + }, |
| 237 | + intersection, |
| 238 | + } |
| 239 | + }) |
| 240 | +} |
| 241 | + |
| 242 | +#[cfg(test)] |
| 243 | +mod test { |
| 244 | + use super::{ |
| 245 | + line_segment_intersection_with_parameters, line_segment_intersection_with_relationships, |
| 246 | + FalseIntersectionPointType, LineIntersectionResultWithRelationships, |
| 247 | + LineSegmentIntersectionType, |
| 248 | + }; |
| 249 | + use crate::{Coord}; |
| 250 | + use FalseIntersectionPointType::{AfterEnd, BeforeStart}; |
| 251 | + use LineSegmentIntersectionType::{FalseIntersectionPoint, TrueIntersectionPoint}; |
| 252 | + |
| 253 | + #[test] |
| 254 | + fn test_line_segment_intersection_with_parameters() { |
| 255 | + let a = Coord { x: 0f64, y: 0f64 }; |
| 256 | + let b = Coord { x: 2f64, y: 2f64 }; |
| 257 | + let c = Coord { x: 0f64, y: 1f64 }; |
| 258 | + let d = Coord { x: 1f64, y: 0f64 }; |
| 259 | + if let Some((t_ab, t_cd, intersection)) = |
| 260 | + line_segment_intersection_with_parameters(a, b, c, d) |
| 261 | + { |
| 262 | + assert_eq!(t_ab, 0.25f64); |
| 263 | + assert_eq!(t_cd, 0.50f64); |
| 264 | + assert_eq!( |
| 265 | + intersection, |
| 266 | + Coord { |
| 267 | + x: 0.5f64, |
| 268 | + y: 0.5f64 |
| 269 | + } |
| 270 | + ); |
| 271 | + } else { |
| 272 | + assert!(false) |
| 273 | + } |
| 274 | + } |
| 275 | + |
| 276 | + #[test] |
| 277 | + fn test_line_segment_intersection_with_parameters_parallel() { |
| 278 | + let a = Coord { x: 3f64, y: 4f64 }; |
| 279 | + let b = Coord { x: 6f64, y: 8f64 }; |
| 280 | + let c = Coord { x: 9f64, y: 9f64 }; |
| 281 | + let d = Coord { x: 12f64, y: 13f64 }; |
| 282 | + assert_eq!( |
| 283 | + line_segment_intersection_with_parameters(a, b, c, d), |
| 284 | + None |
| 285 | + ) |
| 286 | + } |
| 287 | + #[test] |
| 288 | + fn test_line_segment_intersection_with_parameters_colinear() { |
| 289 | + let a = Coord { x: 1f64, y: 2f64 }; |
| 290 | + let b = Coord { x: 2f64, y: 4f64 }; |
| 291 | + let c = Coord { x: 3f64, y: 6f64 }; |
| 292 | + let d = Coord { x: 5f64, y: 10f64 }; |
| 293 | + assert_eq!( |
| 294 | + line_segment_intersection_with_parameters(a, b, c, d), |
| 295 | + None |
| 296 | + ) |
| 297 | + } |
| 298 | + |
| 299 | + #[test] |
| 300 | + fn test_line_segment_intersection_with_relationships() { |
| 301 | + let a = Coord { x: 1f64, y: 2f64 }; |
| 302 | + let b = Coord { x: 2f64, y: 3f64 }; |
| 303 | + let c = Coord { x: 0f64, y: 2f64 }; |
| 304 | + let d = Coord { x: -2f64, y: 6f64 }; |
| 305 | + |
| 306 | + let expected_intersection_point = Coord { x: 1f64 / 3f64, y: 4f64 / 3f64 }; |
| 307 | + |
| 308 | + if let Some(LineIntersectionResultWithRelationships { |
| 309 | + ab, |
| 310 | + cd, |
| 311 | + intersection, |
| 312 | + }) = line_segment_intersection_with_relationships(a, b, c, d) |
| 313 | + { |
| 314 | + assert_eq!(ab, FalseIntersectionPoint(BeforeStart)); |
| 315 | + assert_eq!(cd, FalseIntersectionPoint(BeforeStart)); |
| 316 | + assert_relative_eq!(intersection, expected_intersection_point); |
| 317 | + } else { |
| 318 | + assert!(false); |
| 319 | + } |
| 320 | + |
| 321 | + if let Some(LineIntersectionResultWithRelationships { |
| 322 | + ab, |
| 323 | + cd, |
| 324 | + intersection, |
| 325 | + }) = line_segment_intersection_with_relationships(b, a, c, d) |
| 326 | + { |
| 327 | + assert_eq!(ab, FalseIntersectionPoint(AfterEnd)); |
| 328 | + assert_eq!(cd, FalseIntersectionPoint(BeforeStart)); |
| 329 | + assert_relative_eq!(intersection, expected_intersection_point); |
| 330 | + } else { |
| 331 | + assert!(false); |
| 332 | + } |
| 333 | + |
| 334 | + if let Some(LineIntersectionResultWithRelationships { |
| 335 | + ab, |
| 336 | + cd, |
| 337 | + intersection, |
| 338 | + }) = line_segment_intersection_with_relationships(a, b, d, c) |
| 339 | + { |
| 340 | + assert_eq!(ab, FalseIntersectionPoint(BeforeStart)); |
| 341 | + assert_eq!(cd, FalseIntersectionPoint(AfterEnd)); |
| 342 | + assert_relative_eq!(intersection, expected_intersection_point); |
| 343 | + } else { |
| 344 | + assert!(false); |
| 345 | + } |
| 346 | + } |
| 347 | + |
| 348 | + #[test] |
| 349 | + fn test_line_segment_intersection_with_relationships_true() { |
| 350 | + let a = Coord { x: 0f64, y: 1f64 }; |
| 351 | + let b = Coord { x: 2f64, y: 3f64 }; |
| 352 | + let c = Coord { x: 0f64, y: 2f64 }; |
| 353 | + let d = Coord { x: -2f64, y: 6f64 }; |
| 354 | + |
| 355 | + let expected_intersection_point = Coord { x: 1f64 / 3f64, y: 4f64 / 3f64 }; |
| 356 | + |
| 357 | + if let Some(LineIntersectionResultWithRelationships { |
| 358 | + ab, |
| 359 | + cd, |
| 360 | + intersection, |
| 361 | + }) = line_segment_intersection_with_relationships(a, b, c, d) |
| 362 | + { |
| 363 | + assert_eq!(ab, TrueIntersectionPoint); |
| 364 | + assert_eq!(cd, FalseIntersectionPoint(BeforeStart)); |
| 365 | + assert_relative_eq!(intersection, expected_intersection_point); |
| 366 | + } else { |
| 367 | + assert!(false); |
| 368 | + } |
| 369 | + } |
| 370 | +} |
0 commit comments