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