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 }