1 // Copyright 2005 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS-IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Original author: ericv@google.com (Eric Veach) 16 // Converted to D: madric@gmail.com (Vijay Nayar) 17 18 module s2.s2edge_crossings; 19 20 // Defines functions related to determining whether two geodesic edges cross 21 // and for computing intersection points. 22 // 23 // The predicates CrossingSign(), VertexCrossing(), and EdgeOrVertexCrossing() 24 // are robust, meaning that they produce correct, consistent results even in 25 // pathological cases. See s2predicates.h for additional robust predicates. 26 // 27 // See also S2EdgeCrosser (which efficiently tests an edge against a sequence 28 // of other edges) and S2CrossingEdgeQuery (which uses an index to speed up 29 // the process). 30 31 import s2.s1angle; 32 import s2.s1chord_angle; 33 import s2.s1interval; 34 import s2.s2latlng; 35 import s2.s2latlng; 36 import s2.s2point; 37 import s2.s2pointutil; 38 import s2.s2predicates; 39 import s2.s2edge_crosser; 40 import s2.util.math.exactfloat; 41 import s2.util.math.vector; 42 import std.stdio; 43 import algorithm = std.algorithm; 44 import math = std.math; 45 import traits = std.traits; 46 47 package int[IntersectionMethod]* intersectionMethodTally; 48 49 /** 50 * This function determines whether the edge AB intersects the edge CD. 51 * Returns +1 if AB crosses CD at a point that is interior to both edges. 52 * Returns 0 if any two vertices from different edges are the same. 53 * Returns -1 otherwise. 54 * 55 * Note that if an edge is degenerate (A == B or C == D), the return value 56 * is 0 if two vertices from different edges are the same and -1 otherwise. 57 * 58 * Properties of CrossingSign: 59 * 60 * (1) CrossingSign(b,a,c,d) == CrossingSign(a,b,c,d) 61 * (2) CrossingSign(c,d,a,b) == CrossingSign(a,b,c,d) 62 * (3) CrossingSign(a,b,c,d) == 0 if a==c, a==d, b==c, b==d 63 * (3) CrossingSign(a,b,c,d) <= 0 if a==b or c==d (see above) 64 * 65 * This function implements an exact, consistent perturbation model such 66 * that no three points are ever considered to be collinear. This means 67 * that even if you have 4 points A, B, C, D that lie exactly in a line 68 * (say, around the equator), C and D will be treated as being slightly to 69 * one side or the other of AB. This is done in a way such that the 70 * results are always consistent (see s2pred::Sign). 71 * 72 * Note that if you want to check an edge against a collection of other edges, 73 * it is much more efficient to use an S2EdgeCrosser (see s2edge_crosser.h). 74 */ 75 int crossingSign(in S2Point a, in S2Point b, in S2Point c, in S2Point d) { 76 S2EdgeCrosser crosser = new S2EdgeCrosser(a, b, c); 77 return crosser.crossingSign(d); 78 } 79 80 /** 81 * Given two edges AB and CD where at least two vertices are identical 82 * (i.e. CrossingSign(a,b,c,d) == 0), this function defines whether the 83 * two edges "cross" in a such a way that point-in-polygon containment tests 84 * can be implemented by counting the number of edge crossings. The basic 85 * rule is that a "crossing" occurs if AB is encountered after CD during a 86 * CCW sweep around the shared vertex starting from a fixed reference point. 87 * 88 * Note that according to this rule, if AB crosses CD then in general CD 89 * does not cross AB. However, this leads to the correct result when 90 * counting polygon edge crossings. For example, suppose that A,B,C are 91 * three consecutive vertices of a CCW polygon. If we now consider the edge 92 * crossings of a segment BP as P sweeps around B, the crossing number 93 * changes parity exactly when BP crosses BA or BC. 94 * 95 * Useful properties of VertexCrossing (VC): 96 * 97 * (1) VC(a,a,c,d) == VC(a,b,c,c) == false 98 * (2) VC(a,b,a,b) == VC(a,b,b,a) == true 99 * (3) VC(a,b,c,d) == VC(a,b,d,c) == VC(b,a,c,d) == VC(b,a,d,c) 100 * (3) If exactly one of a,b equals one of c,d, then exactly one of 101 * VC(a,b,c,d) and VC(c,d,a,b) is true 102 * 103 * It is an error to call this method with 4 distinct vertices. 104 */ 105 bool vertexCrossing(in S2Point a, in S2Point b, in S2Point c, in S2Point d) { 106 // If A == B or C == D there is no intersection. We need to check this 107 // case first in case 3 or more input points are identical. 108 if (a == b || c == d) { 109 return false; 110 } 111 112 // If any other pair of vertices is equal, there is a crossing if and only 113 // if OrderedCCW() indicates that the edge AB is further CCW around the 114 // shared vertex O (either A or B) than the edge CD, starting from an 115 // arbitrary fixed reference point. 116 // 117 // Optimization: if AB=CD or AB=DC, we can avoid most of the calculations. 118 if (a == c) { 119 return (b == d) || orderedCCW(ortho(a), d, b, a); 120 } 121 if (b == d) { 122 return orderedCCW(ortho(b), c, a, b); 123 } 124 125 if (a == d) { 126 return (b == c) || orderedCCW(ortho(a), c, b, a); 127 } 128 if (b == c) { 129 return orderedCCW(ortho(b), d, a, b); 130 } 131 132 return false; 133 } 134 135 136 /** 137 * A convenience function that calls CrossingSign() to handle cases 138 * where all four vertices are distinct, and VertexCrossing() to handle 139 * cases where two or more vertices are the same. This defines a crossing 140 * function such that point-in-polygon containment tests can be implemented 141 * by simply counting edge crossings. 142 */ 143 bool edgeOrVertexCrossing(in S2Point a, in S2Point b, in S2Point c, in S2Point d) { 144 int crossing = crossingSign(a, b, c, d); 145 if (crossing < 0) { 146 return false; 147 } 148 if (crossing > 0) { 149 return true; 150 } 151 return vertexCrossing(a, b, c, d); 152 } 153 154 /** 155 * Returns whether (a0,a1) is less than (b0,b1) with respect to a total 156 * ordering on edges that is invariant under edge reversals. 157 */ 158 private bool compareEdges(T)( 159 in Vector!(T, 3) a0, in Vector!(T, 3) a1, in Vector!(T, 3) b0, in Vector!(T, 3) b1) 160 if (traits.isFloatingPoint!T) { 161 const(Vector3!(T))* pa0 = &a0; 162 const(Vector3!(T))* pa1 = &a1; 163 const(Vector3!(T))* pb0 = &b0; 164 const(Vector3!(T))* pb1 = &b1; 165 if (*pa0 >= *pa1) algorithm.swap(pa0, pa1); 166 if (*pb0 >= *pb1) algorithm.swap(pb0, pb1); 167 return *pa0 < *pb0 || (*pa0 == *pb0 && *pb0 < *pb1); 168 } 169 170 /** 171 * If the intersection point of the edges (a0,a1) and (b0,b1) can be computed 172 * to within an error of at most INTERSECTION_ERROR by this function, then set 173 * "result" to the intersection point and return true. 174 * 175 * The intersection point is not guaranteed to have the correct sign 176 * (i.e., it may be either "result" or "-result"). 177 */ 178 private bool getIntersectionStable(T)( 179 in Vector!(T, 3) a0, in Vector!(T, 3) a1, 180 in Vector!(T, 3) b0, in Vector!(T, 3) b1, 181 out Vector!(T, 3) result) 182 if (traits.isFloatingPoint!T) { 183 // Sort the two edges so that (a0,a1) is longer, breaking ties in a 184 // deterministic way that does not depend on the ordering of the endpoints. 185 // This is desirable for two reasons: 186 // - So that the result doesn't change when edges are swapped or reversed. 187 // - It reduces error, since the first edge is used to compute the edge 188 // normal (where a longer edge means less error), and the second edge 189 // is used for interpolation (where a shorter edge means less error). 190 T a_len2 = (a1 - a0).norm2(); 191 T b_len2 = (b1 - b0).norm2(); 192 if (a_len2 < b_len2 || (a_len2 == b_len2 && compareEdges(a0, a1, b0, b1))) { 193 return getIntersectionStableSorted(b0, b1, a0, a1, result); 194 } else { 195 return getIntersectionStableSorted(a0, a1, b0, b1, result); 196 } 197 } 198 199 /** 200 * Given a point X and a vector "a_norm" (not necessarily unit length), 201 * compute x.DotProd(a_norm) and return a bound on the error in the result. 202 * The remaining parameters allow this dot product to be computed more 203 * accurately and efficiently. They include the length of "a_norm" 204 * ("a_norm_len") and the edge endpoints "a0" and "a1". 205 */ 206 private T getProjection(T)(in Vector!(T, 3) x, in Vector!(T, 3) a_norm, in T a_norm_len, 207 in Vector!(T, 3) a0, in Vector!(T, 3) a1, out T error) 208 if (traits.isFloatingPoint!T) { 209 // The error in the dot product is proportional to the lengths of the input 210 // vectors, so rather than using "x" itself (a unit-length vector) we use 211 // the vectors from "x" to the closer of the two edge endpoints. This 212 // typically reduces the error by a huge factor. 213 Vector3!T x0 = x - a0; 214 Vector3!T x1 = x - a1; 215 T x0_dist2 = x0.norm2(); 216 T x1_dist2 = x1.norm2(); 217 218 // If both distances are the same, we need to be careful to choose one 219 // endpoint deterministically so that the result does not change if the 220 // order of the endpoints is reversed. 221 T dist, result; 222 if (x0_dist2 < x1_dist2 || (x0_dist2 == x1_dist2 && x0 < x1)) { 223 dist = math.sqrt(x0_dist2); 224 result = x0.dotProd(a_norm); 225 } else { 226 dist = math.sqrt(x1_dist2); 227 result = x1.dotProd(a_norm); 228 } 229 // This calculation bounds the error from all sources: the computation of 230 // the normal, the subtraction of one endpoint, and the dot product itself. 231 // (DBL_ERR appears because the input points are assumed to be normalized in 232 // double precision rather than in the given type T.) 233 // 234 // For reference, the bounds that went into this calculation are: 235 // ||N'-N|| <= ((1 + 2 * sqrt(3))||N|| + 32 * sqrt(3) * DBL_ERR) * T_ERR 236 // |(A.B)'-(A.B)| <= (1.5 * (A.B) + 1.5 * ||A|| * ||B||) * T_ERR 237 // ||(X-Y)'-(X-Y)|| <= ||X-Y|| * T_ERR 238 T T_ERR = roundingEpsilon!T(); 239 error = (((3.5 + 2 * math.sqrt(3.0)) * a_norm_len + 32 * math.sqrt(3.0) * DBL_ERR) 240 * dist + 1.5 * math.abs(result)) * T_ERR; 241 return result; 242 } 243 244 /** 245 * Helper function for GetIntersectionStable(). It expects that the edges 246 * (a0,a1) and (b0,b1) have been sorted so that the first edge is longer. 247 */ 248 private bool getIntersectionStableSorted(T)( 249 in Vector!(T, 3) a0, in Vector!(T, 3) a1, 250 in Vector!(T, 3) b0, in Vector!(T, 3) b1, 251 out Vector!(T, 3) result) 252 in { 253 assert((a1 - a0).norm2() >= (b1 - b0).norm2()); 254 } do { 255 256 // Compute the normal of the plane through (a0, a1) in a stable way. 257 Vector3!T a_norm = (a0 - a1).crossProd(a0 + a1); 258 T a_norm_len = a_norm.norm(); 259 T b_len = (b1 - b0).norm(); 260 261 // Compute the projection (i.e., signed distance) of b0 and b1 onto the 262 // plane through (a0, a1). Distances are scaled by the length of a_norm. 263 T b0_error, b1_error; 264 T b0_dist = getProjection(b0, a_norm, a_norm_len, a0, a1, b0_error); 265 T b1_dist = getProjection(b1, a_norm, a_norm_len, a0, a1, b1_error); 266 267 // The total distance from b0 to b1 measured perpendicularly to (a0,a1) is 268 // |b0_dist - b1_dist|. Note that b0_dist and b1_dist generally have 269 // opposite signs because b0 and b1 are on opposite sides of (a0, a1). The 270 // code below finds the intersection point by interpolating along the edge 271 // (b0, b1) to a fractional distance of b0_dist / (b0_dist - b1_dist). 272 // 273 // It can be shown that the maximum error in the interpolation fraction is 274 // 275 // (b0_dist * b1_error - b1_dist * b0_error) / 276 // (dist_sum * (dist_sum - error_sum)) 277 // 278 // We save ourselves some work by scaling the result and the error bound by 279 // "dist_sum", since the result is normalized to be unit length anyway. 280 T dist_sum = math.abs(b0_dist - b1_dist); 281 T error_sum = b0_error + b1_error; 282 if (dist_sum <= error_sum) { 283 return false; // Error is unbounded in this case. 284 } 285 Vector3!T x = b0_dist * b1 - b1_dist * b0; 286 T T_ERR = roundingEpsilon!T(); 287 T error = b_len * math.abs(b0_dist * b1_error - b1_dist * b0_error) / 288 (dist_sum - error_sum) + 2 * T_ERR * dist_sum; 289 290 // Finally we normalize the result, compute the corresponding error, and 291 // check whether the total error is acceptable. 292 T x_len = x.norm(); 293 const T kMaxError = INTERSECTION_ERROR.radians(); 294 if (error > (kMaxError - T_ERR) * x_len) { 295 return false; 296 } 297 result = (1 / x_len) * x; 298 return true; 299 } 300 301 static bool getIntersectionStableR( 302 in S2Point a0, in S2Point a1, in S2Point b0, in S2Point b1, out S2Point result) { 303 Vector3_r result_r; 304 if (getIntersectionStable( 305 Vector3_r.from(a0), Vector3_r.from(a1), 306 Vector3_r.from(b0), Vector3_r.from(b1), 307 result_r)) { 308 result = S2Point.from(result_r); 309 return true; 310 } 311 return false; 312 } 313 314 package enum IntersectionMethod { 315 SIMPLE, 316 SIMPLE_R, 317 STABLE, 318 STABLE_R, 319 EXACT, 320 NUM_METHODS 321 } 322 323 /** 324 * Given three points "a", "x", "b", returns true if these three points occur 325 * in the given order along the edge (a,b) to within the given tolerance. 326 * More precisely, either "x" must be within "tolerance" of "a" or "b", or 327 * when "x" is projected onto the great circle through "a" and "b" it must lie 328 * along the edge (a,b) (i.e., the shortest path from "a" to "b"). 329 */ 330 private bool approximatelyOrdered( 331 in S2Point a, in S2Point x, in S2Point b, double tolerance) { 332 if ((x - a).norm2() <= tolerance * tolerance) { 333 return true; 334 } 335 if ((x - b).norm2() <= tolerance * tolerance) { 336 return true; 337 } 338 return orderedCCW(a, x, b, robustCrossProd(a, b).normalize()); 339 } 340 341 /** 342 * Given two edges AB and CD such that CrossingSign(A, B, C, D) > 0, returns 343 * their intersection point. Useful properties of GetIntersection (GI): 344 * 345 * (1) GI(b,a,c,d) == GI(a,b,d,c) == GI(a,b,c,d) 346 * (2) GI(c,d,a,b) == GI(a,b,c,d) 347 * 348 * The returned intersection point X is guaranteed to be very close to the 349 * true intersection point of AB and CD, even if the edges intersect at a 350 * very small angle. See "INTERSECTION_ERROR" below for details. 351 */ 352 S2Point getIntersection(in S2Point a0, in S2Point a1, in S2Point b0, in S2Point b1) 353 in { 354 assert(crossingSign(a0, a1, b0, b1) > 0); 355 } out (result) { 356 // Make sure that the intersection point lies on both edges. 357 assert(approximatelyOrdered(a0, result, a1, INTERSECTION_ERROR.radians())); 358 assert(approximatelyOrdered(b0, result, b1, INTERSECTION_ERROR.radians())); 359 } do { 360 361 // It is difficult to compute the intersection point of two edges accurately 362 // when the angle between the edges is very small. Previously we handled 363 // this by only guaranteeing that the returned intersection point is within 364 // INTERSECTION_ERROR of each edge. However, this means that when the edges 365 // cross at a very small angle, the computed result may be very far from the 366 // true intersection point. 367 // 368 // Instead this function now guarantees that the result is always within 369 // INTERSECTION_ERROR of the true intersection. This requires using more 370 // sophisticated techniques and in some cases extended precision. 371 // 372 // Three different techniques are implemented, but only two are used: 373 // 374 // - GetIntersectionSimple() computes the intersection point using 375 // numerically stable cross products in "long double" precision. 376 // 377 // - GetIntersectionStable() computes the intersection point using 378 // projection and interpolation, taking care to minimize cancellation 379 // error. This method exists in "double" and "long double" versions. 380 // 381 // - GetIntersectionExact() computes the intersection point using exact 382 // arithmetic and converts the final result back to an S2Point. 383 // 384 // We don't actually use the first method (GetIntersectionSimple) because it 385 // turns out that GetIntersectionStable() is twice as fast and also much 386 // more accurate (even in double precision). The "long double" version 387 // (only available on Intel platforms) uses 80-bit precision and is about 388 // twice as slow. The exact arithmetic version is about 100x slower. 389 // 390 // So our strategy is to first call GetIntersectionStable() in double 391 // precision; if that doesn't work and this platform supports "long double", 392 // then we try again in "long double"; if that doesn't work then we fall 393 // back to exact arithmetic. 394 395 S2Point result; 396 IntersectionMethod method; 397 if (getIntersectionStable(a0, a1, b0, b1, result)) { 398 method = IntersectionMethod.STABLE; 399 } else if (getIntersectionStableR(a0, a1, b0, b1, result)) { 400 method = IntersectionMethod.STABLE_R; 401 } else { 402 result = getIntersectionExact(a0, a1, b0, b1); 403 method = IntersectionMethod.EXACT; 404 method = IntersectionMethod.STABLE_R; 405 } 406 407 if (intersectionMethodTally) { 408 ++(*intersectionMethodTally)[method]; 409 } 410 411 // Make sure the intersection point is on the correct side of the sphere. 412 // Since all vertices are unit length, and edges are less than 180 degrees, 413 // (a0 + a1) and (b0 + b1) both have positive dot product with the 414 // intersection point. We use the sum of all vertices to make sure that the 415 // result is unchanged when the edges are swapped or reversed. 416 if (result.dotProd((a0 + a1) + (b0 + b1)) < 0) result = -result; 417 418 return result; 419 } 420 421 /** 422 * INTERSECTION_ERROR is an upper bound on the distance from the intersection 423 * point returned by GetIntersection() to the true intersection point. 424 */ 425 immutable S1Angle INTERSECTION_ERROR = S1Angle.fromRadians(8 * DBL_ERR); 426 427 /** 428 * This value can be used as the S2Builder snap_radius() to ensure that edges 429 * that have been displaced by up to INTERSECTION_ERROR are merged back 430 * together again. For example this can happen when geometry is intersected 431 * with a set of tiles and then unioned. It is equal to twice the 432 * intersection error because input edges might have been displaced in 433 * opposite directions. 434 */ 435 immutable S1Angle INTERSECTION_MERGE_RADIUS = 2 * INTERSECTION_ERROR; 436 437 438 // Compute the intersection point of (a0, a1) and (b0, b1) using exact 439 // arithmetic. Note that the result is not exact because it is rounded to 440 // double precision. Also, the intersection point is not guaranteed to have 441 // the correct sign (i.e., the return value may need to be negated). 442 package S2Point getIntersectionExact( 443 in S2Point a0, in S2Point a1, in S2Point b0, in S2Point b1) 444 out(x) { 445 assert(isUnitLength(x)); 446 } do { 447 // Since we are using exact arithmetic, we don't need to worry about 448 // numerical stability. 449 Vector3_xf a0_xf = Vector3_xf.from(a0); 450 Vector3_xf a1_xf = Vector3_xf.from(a1); 451 Vector3_xf b0_xf = Vector3_xf.from(b0); 452 Vector3_xf b1_xf = Vector3_xf.from(b1); 453 Vector3_xf a_norm_xf = a0_xf.crossProd(a1_xf); 454 Vector3_xf b_norm_xf = b0_xf.crossProd(b1_xf); 455 Vector3_xf x_xf = a_norm_xf.crossProd(b_norm_xf); 456 457 // The final Normalize() call is done in double precision, which creates a 458 // directional error of up to 2 * DBL_ERR. (ToDouble() and Normalize() each 459 // contribute up to DBL_ERR of directional error.) 460 S2Point x = S2PointFromExact(x_xf); 461 462 if (x == S2Point(0, 0, 0)) { 463 // The two edges are exactly collinear, but we still consider them to be 464 // "crossing" because of simulation of simplicity. Out of the four 465 // endpoints, exactly two lie in the interior of the other edge. Of 466 // those two we return the one that is lexicographically smallest. 467 x = S2Point(10, 10, 10); // Greater than any valid S2Point 468 S2Point a_norm = S2PointFromExact(a_norm_xf); 469 S2Point b_norm = S2PointFromExact(b_norm_xf); 470 if (a_norm == S2Point(0, 0, 0) || b_norm == S2Point(0, 0, 0)) { 471 // TODO(ericv): To support antipodal edges properly, we would need to 472 // add an s2pred::CrossProd() function that computes the cross product 473 // using simulation of simplicity and rounds the result to the nearest 474 // floating-point representation. 475 writeln("Exactly antipodal edges not supported by GetIntersection"); 476 } 477 if (orderedCCW(b0, a0, b1, b_norm) && a0 < x) x = a0; 478 if (orderedCCW(b0, a1, b1, b_norm) && a1 < x) x = a1; 479 if (orderedCCW(a0, b0, a1, a_norm) && b0 < x) x = b0; 480 if (orderedCCW(a0, b1, a1, a_norm) && b1 < x) x = b1; 481 } 482 return x; 483 } 484 485 private S2Point S2PointFromExact(in Vector3_xf xf) { 486 // If all components of "x" have absolute value less than about 1e-154, 487 // then x.Norm2() is zero in double precision due to underflow. Therefore 488 // we need to scale "x" by an appropriate power of 2 before the conversion. 489 S2Point x = S2Point(xf[0].toDouble(), xf[1].toDouble(), xf[2].toDouble()); 490 if (x.norm2() > 0) return x.normalize(); 491 492 // Scale so that the largest component magnitude is in the range [0.5, 1). 493 int exp = ExactFloat.MIN_EXP - 1; 494 for (int i = 0; i < 3; ++i) { 495 if (xf[i].isNormal()) exp = algorithm.max(exp, xf[i].exp()); 496 } 497 if (exp < ExactFloat.MIN_EXP) { 498 return S2Point(0, 0, 0); 499 } 500 return S2Point(ldexp(xf[0], -exp).toDouble(), 501 ldexp(xf[1], -exp).toDouble(), 502 ldexp(xf[2], -exp).toDouble()).normalize(); 503 } 504