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_distances; 19 20 // Defines a collection of functions for computing the distance to an edge, 21 // interpolating along an edge, projecting points onto edges, etc. 22 23 import s2.s1angle; 24 import s2.s1chord_angle; 25 import s2.s2point; 26 import s2.s2pointutil : isUnitLength, robustCrossProd; 27 import s2.s2edge_crossings : crossingSign; 28 import s2.util.math.vector; 29 import s2.s2predicates : sign; 30 import std.algorithm : min, max; 31 import math = std.math; 32 import std.exception; 33 34 ///////////////////////////////////////////////////////////////////////////// 35 /////////////// (point, edge) functions /////////////// 36 37 // Returns the minimum distance from X to any point on the edge AB. All 38 // arguments should be unit length. The result is very accurate for small 39 // distances but may have some numerical error if the distance is large 40 // (approximately Pi/2 or greater). The case A == B is handled correctly. 41 // 42 // If you want to compare a distance against a fixed threshold, e.g. 43 // if (S2::GetDistance(x, a, b) < limit) 44 // then it is significantly faster to use UpdateMinDistance() below. 45 S1Angle getDistance(in S2Point x, in S2Point a, in S2Point b) { 46 S1ChordAngle min_dist; 47 alwaysUpdateMinDistance!(true)(x, a, b, min_dist); 48 return min_dist.toS1Angle(); 49 } 50 51 // Returns true if the distance from X to the edge AB is less than "limit". 52 // (Specify limit.Successor() for "less than or equal to".) This method is 53 // significantly faster than GetDistance(). If you want to compare against a 54 // fixed S1Angle, you should convert it to an S1ChordAngle once and save the 55 // value, since this step is relatively expensive. 56 // 57 // See s2pred::CompareEdgeDistance() for an exact version of this predicate. 58 bool isDistanceLess(in S2Point x, in S2Point a, in S2Point b, S1ChordAngle limit) { 59 return updateMinDistance(x, a, b, limit); 60 } 61 62 63 // If the distance from X to the edge AB is less than "min_dist", this 64 // method updates "min_dist" and returns true. Otherwise it returns false. 65 // The case A == B is handled correctly. 66 // 67 // Use this method when you want to compute many distances and keep track of 68 // the minimum. It is significantly faster than using GetDistance(), 69 // because (1) using S1ChordAngle is much faster than S1Angle, and (2) it 70 // can save a lot of work by not actually computing the distance when it is 71 // obviously larger than the current minimum. 72 bool updateMinDistance(in S2Point x, in S2Point a, in S2Point b, ref S1ChordAngle min_dist) { 73 return alwaysUpdateMinDistance!(false)(x, a, b, min_dist); 74 } 75 76 // This function computes the distance from a point X to a line segment AB. 77 // If the distance is less than "min_dist" or "always_update" is true, it 78 // updates "min_dist" and returns true. Otherwise it returns false. 79 // 80 // The "Always" in the function name refers to the template argument, i.e. 81 // AlwaysUpdateMinDistance<true> always updates the given distance, while 82 // AlwaysUpdateMinDistance<false> does not. This optimization increases the 83 // speed of GetDistance() by about 10% without creating code duplication. 84 bool alwaysUpdateMinDistance(bool alwaysUpdate)( 85 in S2Point x, in S2Point a, in S2Point b, ref S1ChordAngle min_dist) 86 in { 87 assert(isUnitLength(x) && isUnitLength(a) && isUnitLength(b)); 88 } do { 89 double xa2 = (x-a).norm2(); 90 double xb2 = (x-b).norm2(); 91 if (alwaysUpdateMinInteriorDistance!(alwaysUpdate)(x, a, b, xa2, xb2, min_dist)) { 92 return true; // Minimum distance is attained along the edge interior. 93 } 94 // Otherwise the minimum distance is to one of the endpoints. 95 double dist2 = min(xa2, xb2); 96 if (!alwaysUpdate && dist2 >= min_dist.length2()) { 97 return false; 98 } 99 min_dist = S1ChordAngle.fromLength2(dist2); 100 return true; 101 } 102 103 // If the distance from X to the edge AB is greater than "max_dist", this 104 // method updates "max_dist" and returns true. Otherwise it returns false. 105 // The case A == B is handled correctly. 106 bool updateMaxDistance(in S2Point x, in S2Point a, in S2Point b, ref S1ChordAngle max_dist) { 107 auto dist = max(S1ChordAngle(x, a), S1ChordAngle(x, b)); 108 if (dist > S1ChordAngle.right()) { 109 alwaysUpdateMinDistance!true(-x, a, b, dist); 110 dist = S1ChordAngle.straight() - dist; 111 } 112 if (max_dist < dist) { 113 max_dist = dist; 114 return true; 115 } 116 117 return false; 118 } 119 120 // Returns the maximum error in the result of UpdateMinDistance (and 121 // associated functions such as UpdateMinInteriorDistance, IsDistanceLess, 122 // etc), assuming that all input points are normalized to within the bounds 123 // guaranteed by S2Point::Normalize(). The error can be added or subtracted 124 // from an S1ChordAngle "x" using x.PlusError(error). 125 // 126 // Note that accuracy goes down as the distance approaches 0 degrees or 180 127 // degrees (for different reasons). Near 0 degrees the error is acceptable 128 // for all practical purposes (about 1.2e-15 radians ~= 8 nanometers). For 129 // exactly antipodal points the maximum error is quite high (0.5 meters), but 130 // this error drops rapidly as the points move away from antipodality 131 // (approximately 1 millimeter for points that are 50 meters from antipodal, 132 // and 1 micrometer for points that are 50km from antipodal). 133 // 134 // TODO(ericv): Currently the error bound does not hold for edges whose 135 // endpoints are antipodal to within about 1e-15 radians (less than 1 micron). 136 // This could be fixed by extending S2::RobustCrossProd to use higher 137 // precision when necessary. 138 double getUpdateMinDistanceMaxError(S1ChordAngle dist) { 139 // There are two cases for the maximum error in UpdateMinDistance(), 140 // depending on whether the closest point is interior to the edge. 141 return max(getUpdateMinInteriorDistanceMaxError(dist), dist.getS2PointConstructorMaxError()); 142 } 143 144 // Returns the maximum error in the result of UpdateMinInteriorDistance, 145 // assuming that all input points are normalized to within the bounds 146 // guaranteed by S2Point::Normalize(). The error can be added or subtracted 147 // from an S1ChordAngle "x" using x.PlusError(error). 148 private double getUpdateMinInteriorDistanceMaxError(S1ChordAngle dist) { 149 // If a point is more than 90 degrees from an edge, then the minimum 150 // distance is always to one of the endpoints, not to the edge interior. 151 if (dist >= S1ChordAngle.right()) return 0.0; 152 153 // This bound includes all source of error, assuming that the input points 154 // are normalized to within the bounds guaranteed to S2Point::Normalize(). 155 // "a" and "b" are components of chord length that are perpendicular and 156 // parallel to the plane containing the edge respectively. 157 double b = min(1.0, 0.5 * dist.length2()); 158 double a = math.sqrt(b * (2 - b)); 159 return ((2.5 + 2 * math.sqrt(3.0) + 8.5 * a) * a 160 + (2 + 2 * math.sqrt(3.0) / 3 + 6.5 * (1 - b)) * b 161 + (23 + 16 / math.sqrt(3.0)) * double.epsilon) * double.epsilon; 162 } 163 164 165 // Returns true if the minimum distance from X to the edge AB is attained at 166 // an interior point of AB (i.e., not an endpoint), and that distance is less 167 // than "limit". (Specify limit.Successor() for "less than or equal to".) 168 //bool IsInteriorDistanceLess(const S2Point& x, 169 // const S2Point& a, const S2Point& b, 170 // S1ChordAngle limit); 171 172 // If the minimum distance from X to AB is attained at an interior point of AB 173 // (i.e., not an endpoint), and that distance is less than "min_dist", then 174 // this method updates "min_dist" and returns true. Otherwise returns false. 175 //bool UpdateMinInteriorDistance(const S2Point& x, 176 // const S2Point& a, const S2Point& b, 177 // S1ChordAngle* min_dist); 178 179 // Returns the point along the edge AB that is closest to the point X. 180 // The fractional distance of this point along the edge AB can be obtained 181 // using GetDistanceFraction() above. Requires that all vectors have 182 // unit length. 183 S2Point project(in S2Point x, in S2Point a, in S2Point b) { 184 return project(x, a, b, robustCrossProd(a, b)); 185 } 186 187 /** 188 * A slightly more efficient version of Project() where the cross product of 189 * the two endpoints has been precomputed. The cross product does not need to 190 * be normalized, but should be computed using S2::RobustCrossProd() for the 191 * most accurate results. Requires that x, a, and b have unit length. 192 */ 193 S2Point project(in S2Point x, in S2Point a, in S2Point b, in Vector3_d a_cross_b) 194 in { 195 assert(isUnitLength(a)); 196 assert(isUnitLength(b)); 197 assert(isUnitLength(x)); 198 } do { 199 // Find the closest point to X along the great circle through AB. 200 S2Point p = x - (x.dotProd(a_cross_b) / a_cross_b.norm2()) * a_cross_b; 201 202 // If this point is on the edge AB, then it's the closest point. 203 if (sign(a_cross_b, a, p) > 0 && sign(p, b, a_cross_b) > 0) { 204 return p.normalize(); 205 } 206 // Otherwise, the closest point is either A or B. 207 return ((x - a).norm2() <= (x - b).norm2()) ? a : b; 208 } 209 210 211 ///////////////////////////////////////////////////////////////////////////// 212 /////////////// (point along edge) functions /////////////// 213 214 215 // Given a point X and an edge AB, returns the distance ratio AX / (AX + BX). 216 // If X happens to be on the line segment AB, this is the fraction "t" such 217 // that X == Interpolate(t, A, B). Requires that A and B are distinct. 218 //double GetDistanceFraction(const S2Point& x, 219 // const S2Point& a, const S2Point& b); 220 221 // Returns the point X along the line segment AB whose distance from A is the 222 // given fraction "t" of the distance AB. Does NOT require that "t" be 223 // between 0 and 1. Note that all distances are measured on the surface of 224 // the sphere, so this is more complicated than just computing (1-t)*a + t*b 225 // and normalizing the result. 226 S2Point interpolate(double t, in S2Point a, in S2Point b) { 227 if (t == 0) return a; 228 if (t == 1) return b; 229 auto ab = S1Angle(a, b); 230 return interpolateAtDistance(t * ab, a, b); 231 } 232 233 // Like Interpolate(), except that the parameter "ax" represents the desired 234 // distance from A to the result X rather than a fraction between 0 and 1. 235 S2Point interpolateAtDistance(S1Angle ax_angle, in S2Point a, in S2Point b) 236 in { 237 assert(isUnitLength(a)); 238 assert(isUnitLength(b)); 239 } do { 240 double ax = ax_angle.radians(); 241 242 // Use RobustCrossProd() to compute the tangent vector at A towards B. The 243 // result is always perpendicular to A, even if A=B or A=-B, but it is not 244 // necessarily unit length. (We effectively normalize it below.) 245 Vector3_d normal = robustCrossProd(a, b); 246 Vector3_d tangent = normal.crossProd(a); 247 assert(tangent != S2Point(0, 0, 0)); 248 249 // Now compute the appropriate linear combination of A and "tangent". With 250 // infinite precision the result would always be unit length, but we 251 // normalize it anyway to ensure that the error is within acceptable bounds. 252 // (Otherwise errors can build up when the result of one interpolation is 253 // fed into another interpolation.) 254 return (math.cos(ax) * a + (math.sin(ax) / tangent.norm()) * tangent).normalize(); 255 } 256 257 258 ///////////////////////////////////////////////////////////////////////////// 259 /////////////// (edge, edge) functions /////////////// 260 261 262 // Like UpdateMinDistance(), but computes the minimum distance between the 263 // given pair of edges. (If the two edges cross, the distance is zero.) 264 // The cases a0 == a1 and b0 == b1 are handled correctly. 265 bool updateEdgePairMinDistance( 266 in S2Point a0, in S2Point a1, in S2Point b0, in S2Point b1, ref S1ChordAngle min_dist) { 267 if (min_dist == S1ChordAngle.zero()) { 268 return false; 269 } 270 if (crossingSign(a0, a1, b0, b1) > 0) { 271 min_dist = S1ChordAngle.zero(); 272 return true; 273 } 274 // Otherwise, the minimum distance is achieved at an endpoint of at least 275 // one of the two edges. We use "|" rather than "||" below to ensure that 276 // all four possibilities are always checked. 277 // 278 // The calculation below computes each of the six vertex-vertex distances 279 // twice (this could be optimized). 280 return (updateMinDistance(a0, b0, b1, min_dist) | 281 updateMinDistance(a1, b0, b1, min_dist) | 282 updateMinDistance(b0, a0, a1, min_dist) | 283 updateMinDistance(b1, a0, a1, min_dist)); 284 } 285 286 // As above, but for maximum distances. If one edge crosses the antipodal 287 // reflection of the other, the distance is Pi. 288 // bool UpdateEdgePairMaxDistance(const S2Point& a0, const S2Point& a1, 289 // const S2Point& b0, const S2Point& b1, 290 // S1ChordAngle* max_dist); 291 292 // Returns the pair of points (a, b) that achieves the minimum distance 293 // between edges a0a1 and b0b1, where "a" is a point on a0a1 and "b" is a 294 // point on b0b1. If the two edges intersect, "a" and "b" are both equal to 295 // the intersection point. Handles a0 == a1 and b0 == b1 correctly. 296 // std::pair<S2Point, S2Point> GetEdgePairClosestPoints( 297 // const S2Point& a0, const S2Point& a1, 298 // const S2Point& b0, const S2Point& b1); 299 300 // Returns true if every point on edge B=b0b1 is no further than "tolerance" 301 // from some point on edge A=a0a1. Equivalently, returns true if the directed 302 // Hausdorff distance from B to A is no more than "tolerance". 303 // Requires that tolerance is less than 90 degrees. 304 bool isEdgeBNearEdgeA(in S2Point a0, in S2Point a1, in S2Point b0, in S2Point b1, S1Angle tolerance) 305 in { 306 assert(tolerance.radians() < M_PI / 2); 307 assert(tolerance.radians() > 0); 308 } do { 309 // The point on edge B=b0b1 furthest from edge A=a0a1 is either b0, b1, or 310 // some interior point on B. If it is an interior point on B, then it must be 311 // one of the two points where the great circle containing B (circ(B)) is 312 // furthest from the great circle containing A (circ(A)). At these points, 313 // the distance between circ(B) and circ(A) is the angle between the planes 314 // containing them. 315 316 Vector3_d a_ortho = robustCrossProd(a0, a1).normalize(); 317 const S2Point a_nearest_b0 = project(b0, a0, a1, a_ortho); 318 const S2Point a_nearest_b1 = project(b1, a0, a1, a_ortho); 319 // If a_nearest_b0 and a_nearest_b1 have opposite orientation from a0 and a1, 320 // we invert a_ortho so that it points in the same direction as a_nearest_b0 x 321 // a_nearest_b1. This helps us handle the case where A and B are oppositely 322 // oriented but otherwise might be near each other. We check orientation and 323 // invert rather than computing a_nearest_b0 x a_nearest_b1 because those two 324 // points might be equal, and have an unhelpful cross product. 325 if (sign(a_ortho, a_nearest_b0, a_nearest_b1) < 0) a_ortho *= -1; 326 327 // To check if all points on B are within tolerance of A, we first check to 328 // see if the endpoints of B are near A. If they are not, B is not near A. 329 const S1Angle b0_distance = S1Angle(b0, a_nearest_b0); 330 const S1Angle b1_distance = S1Angle(b1, a_nearest_b1); 331 if (b0_distance > tolerance || b1_distance > tolerance) 332 return false; 333 334 // If b0 and b1 are both within tolerance of A, we check to see if the angle 335 // between the planes containing B and A is greater than tolerance. If it is 336 // not, no point on B can be further than tolerance from A (recall that we 337 // already know that b0 and b1 are close to A, and S2Edges are all shorter 338 // than 180 degrees). The angle between the planes containing circ(A) and 339 // circ(B) is the angle between their normal vectors. 340 const Vector3_d b_ortho = robustCrossProd(b0, b1).normalize(); 341 const S1Angle planar_angle = S1Angle(a_ortho, b_ortho); 342 if (planar_angle <= tolerance) 343 return true; 344 345 346 // As planar_angle approaches M_PI, the projection of a_ortho onto the plane 347 // of B approaches the null vector, and normalizing it is numerically 348 // unstable. This makes it unreliable or impossible to identify pairs of 349 // points where circ(A) is furthest from circ(B). At this point in the 350 // algorithm, this can only occur for two reasons: 351 // 352 // 1.) b0 and b1 are closest to A at distinct endpoints of A, in which case 353 // the opposite orientation of a_ortho and b_ortho means that A and B are 354 // in opposite hemispheres and hence not close to each other. 355 // 356 // 2.) b0 and b1 are closest to A at the same endpoint of A, in which case 357 // the orientation of a_ortho was chosen arbitrarily to be that of a0 358 // cross a1. B must be shorter than 2*tolerance and all points in B are 359 // close to one endpoint of A, and hence to A. 360 // 361 // The logic applies when planar_angle is robustly greater than M_PI/2, but 362 // may be more computationally expensive than the logic beyond, so we choose a 363 // value close to M_PI. 364 if (planar_angle >= S1Angle.fromRadians(M_PI - 0.01)) { 365 return (S1Angle(b0, a0) < S1Angle(b0, a1)) == (S1Angle(b1, a0) < S1Angle(b1, a1)); 366 } 367 368 // Finally, if either of the two points on circ(B) where circ(B) is furthest 369 // from circ(A) lie on edge B, edge B is not near edge A. 370 // 371 // The normalized projection of a_ortho onto the plane of circ(B) is one of 372 // the two points along circ(B) where it is furthest from circ(A). The other 373 // is -1 times the normalized projection. 374 S2Point furthest = (a_ortho - a_ortho.dotProd(b_ortho) * b_ortho).normalize(); 375 enforce(isUnitLength(furthest)); 376 S2Point furthest_inv = -1 * furthest; 377 378 // A point p lies on B if you can proceed from b_ortho to b0 to p to b1 and 379 // back to b_ortho without ever turning right. We test this for furthest and 380 // furthest_inv, and return true if neither point lies on B. 381 return !((sign(b_ortho, b0, furthest) > 0 && sign(furthest, b1, b_ortho) > 0) 382 || (sign(b_ortho, b0, furthest_inv) > 0 && sign(furthest_inv, b1, b_ortho) > 0)); 383 } 384 385 386 ////////////////// Implementation details follow //////////////////// 387 388 389 // inline bool IsInteriorDistanceLess(const S2Point& x, const S2Point& a, 390 // const S2Point& b, S1ChordAngle limit) { 391 // return UpdateMinInteriorDistance(x, a, b, &limit); 392 // } 393 394 // If the minimum distance from X to AB is attained at an interior point of AB 395 // (i.e., not an endpoint), and that distance is less than "min_dist" or 396 // "always_update" is true, then update "min_dist" and return true. Otherwise 397 // return false. 398 // 399 // The "Always" in the function name refers to the template argument, i.e. 400 // AlwaysUpdateMinInteriorDistance<true> always updates the given distance, 401 // while AlwaysUpdateMinInteriorDistance<false> does not. This optimization 402 // increases the speed of GetDistance() by about 10% without creating code 403 // duplication. 404 bool alwaysUpdateMinInteriorDistance(bool alwaysUpdate)( 405 in S2Point x, in S2Point a, in S2Point b, 406 in double xa2, in double xb2, ref S1ChordAngle min_dist) 407 in { 408 assert(isUnitLength(x) && isUnitLength(a) && isUnitLength(b)); 409 assert(xa2 == (x-a).norm2()); 410 assert(xb2 == (x-b).norm2()); 411 } do { 412 413 // The closest point on AB could either be one of the two vertices (the 414 // "vertex case") or in the interior (the "interior case"). Let C = A x B. 415 // If X is in the spherical wedge extending from A to B around the axis 416 // through C, then we are in the interior case. Otherwise we are in the 417 // vertex case. 418 // 419 // Check whether we might be in the interior case. For this to be true, XAB 420 // and XBA must both be acute angles. Checking this condition exactly is 421 // expensive, so instead we consider the planar triangle ABX (which passes 422 // through the sphere's interior). The planar angles XAB and XBA are always 423 // less than the corresponding spherical angles, so if we are in the 424 // interior case then both of these angles must be acute. 425 // 426 // We check this by computing the squared edge lengths of the planar 427 // triangle ABX, and testing acuteness using the law of cosines: 428 // 429 // max(XA^2, XB^2) < min(XA^2, XB^2) + AB^2 430 // 431 if (max(xa2, xb2) >= min(xa2, xb2) + (a-b).norm2()) { 432 return false; 433 } 434 // The minimum distance might be to a point on the edge interior. Let R 435 // be closest point to X that lies on the great circle through AB. Rather 436 // than computing the geodesic distance along the surface of the sphere, 437 // instead we compute the "chord length" through the sphere's interior. 438 // If the squared chord length exceeds min_dist.length2() then we can 439 // return "false" immediately. 440 // 441 // The squared chord length XR^2 can be expressed as XQ^2 + QR^2, where Q 442 // is the point X projected onto the plane through the great circle AB. 443 // The distance XQ^2 can be written as (X.C)^2 / |C|^2 where C = A x B. 444 // We ignore the QR^2 term and instead use XQ^2 as a lower bound, since it 445 // is faster and the corresponding distance on the Earth's surface is 446 // accurate to within 1% for distances up to about 1800km. 447 S2Point c = robustCrossProd(a, b); 448 double c2 = c.norm2(); 449 double x_dot_c = x.dotProd(c); 450 double x_dot_c2 = x_dot_c * x_dot_c; 451 if (!alwaysUpdate && x_dot_c2 >= c2 * min_dist.length2()) { 452 // The closest point on the great circle AB is too far away. 453 return false; 454 } 455 // Otherwise we do the exact, more expensive test for the interior case. 456 // This test is very likely to succeed because of the conservative planar 457 // test we did initially. 458 S2Point cx = c.crossProd(x); 459 if (a.dotProd(cx) >= 0 || b.dotProd(cx) <= 0) { 460 return false; 461 } 462 // Compute the squared chord length XR^2 = XQ^2 + QR^2 (see above). 463 // This calculation has good accuracy for all chord lengths since it 464 // is based on both the dot product and cross product (rather than 465 // deriving one from the other). However, note that the chord length 466 // representation itself loses accuracy as the angle approaches Pi. 467 double qr = 1 - math.sqrt(cx.norm2() / c2); 468 double dist2 = (x_dot_c2 / c2) + (qr * qr); 469 if (!alwaysUpdate && dist2 >= min_dist.length2()) { 470 return false; 471 } 472 min_dist = S1ChordAngle.fromLength2(dist2); 473 return true; 474 }