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 
16 // Author: ericv@google.com (Eric Veach)
17 
18 #include "s2/s2edge_crossings.h"
19 #include "s2/s2edge_crossings_internal.h"
20 
21 #include <vector>
22 
23 #include "s2/base/logging.h"
24 #include <gtest/gtest.h>
25 #include "s2/s2edge_distances.h"
26 #include "s2/s2predicates.h"
27 #include "s2/s2testing.h"
28 
29 // CrossingSign, VertexCrossing, and EdgeOrVertexCrossing are tested in
30 // s2edge_crosser_test.cc.
31 
32 using S2::internal::IntersectionMethod;
33 using std::max;
34 using std::swap;
35 using std::vector;
36 
37 // This class records statistics about the intersection methods used by
38 // GetIntersection().
39 class GetIntersectionStats {
40  public:
GetIntersectionStats()41   GetIntersectionStats() {
42     S2::internal::intersection_method_tally_ = tally_;
43   }
44 
~GetIntersectionStats()45   ~GetIntersectionStats() {
46     S2::internal::intersection_method_tally_ = nullptr;
47   }
48 
Print()49   void Print() {
50     int total = 0;
51     int totals[kNumMethods];
52     for (int i = kNumMethods; --i >= 0; ) {
53       total += tally_[i];
54       totals[i] = total;
55     }
56     printf("%10s %16s %16s  %6s\n",
57            "Method", "Successes", "Attempts", "Rate");
58     for (int i = 0; i < kNumMethods; ++i) {
59       if (tally_[i] == 0) continue;
60       printf("%10s %9d %5.1f%% %9d %5.1f%%  %5.1f%%\n",
61              S2::internal::GetIntersectionMethodName(
62                  static_cast<IntersectionMethod>(i)),
63              tally_[i], 100.0 * tally_[i] / total,
64              totals[i], 100.0 * totals[i] / total,
65              100.0 * tally_[i] / totals[i]);
66     }
67     for (int i = 0; i < kNumMethods; ++i) tally_[i] = 0;
68   }
69 
70  private:
71   static int constexpr kNumMethods =
72       static_cast<int>(IntersectionMethod::NUM_METHODS);
73   int tally_[kNumMethods] = {0};
74 };
75 
76 // This returns the true intersection point of two line segments (a0,a1) and
77 // (b0,b1), with a relative error of at most DBL_EPSILON in each coordinate
78 // (i.e., one ulp, or twice the double precision rounding error).
GetIntersectionExact(const S2Point & a0,const S2Point & a1,const S2Point & b0,const S2Point & b1)79 static S2Point GetIntersectionExact(const S2Point& a0, const S2Point& a1,
80                                     const S2Point& b0, const S2Point& b1) {
81   S2Point x = S2::internal::GetIntersectionExact(a0, a1, b0, b1);
82   if (x.DotProd((a0 + a1) + (b0 + b1)) < 0) x = -x;
83   return x;
84 }
85 
86 // The approximate maximum error in GetDistance() for small distances.
87 S1Angle kGetDistanceAbsError = S1Angle::Radians(3 * DBL_EPSILON);
88 
TEST(S2EdgeUtil,IntersectionError)89 TEST(S2EdgeUtil, IntersectionError) {
90   // We repeatedly construct two edges that cross near a random point "p", and
91   // measure the distance from the actual intersection point "x" to the
92   // exact intersection point and also to the edges.
93 
94   GetIntersectionStats stats;
95   S1Angle max_point_dist, max_edge_dist;
96   S2Testing::Random* rnd = &S2Testing::rnd;
97   for (int iter = 0; iter < 5000; ++iter) {
98     // We construct two edges AB and CD that intersect near "p".  The angle
99     // between AB and CD (expressed as a slope) is chosen randomly between
100     // 1e-15 and 1e15 such that its logarithm is uniformly distributed.
101     // Similarly, two edge lengths approximately between 1e-15 and 1 are
102     // chosen.  The edge endpoints are chosen such that they are often very
103     // close to the other edge (i.e., barely crossing).  Taken together this
104     // ensures that we test both long and very short edges that intersect at
105     // both large and very small angles.
106     //
107     // Sometimes the edges we generate will not actually cross, in which case
108     // we simply try again.
109     Vector3_d p, d1, d2;
110     S2Testing::GetRandomFrame(&p, &d1, &d2);
111     double slope = 1e-15 * pow(1e30, rnd->RandDouble());
112     d2 = (d1 + slope * d2).Normalize();
113     S2Point a, b, c, d;
114     do {
115       double ab_len = pow(1e-15, rnd->RandDouble());
116       double cd_len = pow(1e-15, rnd->RandDouble());
117       double a_fraction = pow(1e-5, rnd->RandDouble());
118       if (rnd->OneIn(2)) a_fraction = 1 - a_fraction;
119       double c_fraction = pow(1e-5, rnd->RandDouble());
120       if (rnd->OneIn(2)) c_fraction = 1 - c_fraction;
121       a = (p - a_fraction * ab_len * d1).Normalize();
122       b = (p + (1 - a_fraction) * ab_len * d1).Normalize();
123       c = (p - c_fraction * cd_len * d2).Normalize();
124       d = (p + (1 - c_fraction) * cd_len * d2).Normalize();
125     } while (S2::CrossingSign(a, b, c, d) <= 0);
126 
127     // Each constructed edge should be at most 1.5 * DBL_EPSILON away from the
128     // original point P.
129     EXPECT_LE(S2::GetDistance(p, a, b),
130               S1Angle::Radians(1.5 * DBL_EPSILON) + kGetDistanceAbsError);
131     EXPECT_LE(S2::GetDistance(p, c, d),
132               S1Angle::Radians(1.5 * DBL_EPSILON) + kGetDistanceAbsError);
133 
134     // Verify that the expected intersection point is close to both edges and
135     // also close to the original point P.  (It might not be very close to P
136     // if the angle between the edges is very small.)
137     S2Point expected = GetIntersectionExact(a, b, c, d);
138     EXPECT_LE(S2::GetDistance(expected, a, b),
139               S1Angle::Radians(3 * DBL_EPSILON) + kGetDistanceAbsError);
140     EXPECT_LE(S2::GetDistance(expected, c, d),
141               S1Angle::Radians(3 * DBL_EPSILON) + kGetDistanceAbsError);
142     EXPECT_LE(S1Angle(expected, p),
143               S1Angle::Radians(3 * DBL_EPSILON / slope) +
144               S2::kIntersectionError);
145 
146     // Now we actually test the GetIntersection() method.
147     S2Point actual = S2::GetIntersection(a, b, c, d);
148     S1Angle dist_ab = S2::GetDistance(actual, a, b);
149     S1Angle dist_cd = S2::GetDistance(actual, c, d);
150     EXPECT_LE(dist_ab, S2::kIntersectionError + kGetDistanceAbsError);
151     EXPECT_LE(dist_cd, S2::kIntersectionError + kGetDistanceAbsError);
152     max_edge_dist = max(max_edge_dist, max(dist_ab, dist_cd));
153     S1Angle point_dist(expected, actual);
154     EXPECT_LE(point_dist, S2::kIntersectionError);
155     max_point_dist = max(max_point_dist, point_dist);
156   }
157   stats.Print();
158   S2_LOG(INFO) << "Max distance to either edge being intersected: "
159             << max_edge_dist.radians();
160   S2_LOG(INFO) << "Maximum distance to expected intersection point: "
161             << max_point_dist.radians();
162 }
163 
164 // Chooses a point in the XY plane that is separated from X by at least 1e-15
165 // (to avoid choosing too many duplicate points) and by at most Pi/2 - 1e-3
166 // (to avoid nearly-diametric edges, since the test below is not sophisticated
167 // enough to test such edges).
ChooseSemicirclePoint(const S2Point & x,const S2Point & y)168 static S2Point ChooseSemicirclePoint(const S2Point& x, const S2Point& y) {
169   S2Testing::Random* rnd = &S2Testing::rnd;
170   double sign = (2 * rnd->Uniform(2)) - 1;
171   return (x + sign * 1e3 * pow(1e-18, rnd->RandDouble()) * y).Normalize();
172 }
173 
TEST(S2EdgeUtil,GrazingIntersections)174 TEST(S2EdgeUtil, GrazingIntersections) {
175   // This test choose 5 points along a great circle (i.e., as collinear as
176   // possible), and uses them to construct an edge AB and a triangle CDE such
177   // that CD and CE both cross AB.  It then checks that the intersection
178   // points returned by GetIntersection() have the correct relative ordering
179   // along AB (to within kIntersectionError).
180   GetIntersectionStats stats;
181   for (int iter = 0; iter < 1000; ++iter) {
182     Vector3_d x, y, z;
183     S2Testing::GetRandomFrame(&x, &y, &z);
184     S2Point a, b, c, d, e, ab;
185     do {
186       a = ChooseSemicirclePoint(x, y);
187       b = ChooseSemicirclePoint(x, y);
188       c = ChooseSemicirclePoint(x, y);
189       d = ChooseSemicirclePoint(x, y);
190       e = ChooseSemicirclePoint(x, y);
191       ab = (a - b).CrossProd(a + b);
192     } while (ab.Norm() < 50 * DBL_EPSILON ||
193              S2::CrossingSign(a, b, c, d) <= 0 ||
194              S2::CrossingSign(a, b, c, e) <= 0);
195     S2Point xcd = S2::GetIntersection(a, b, c, d);
196     S2Point xce = S2::GetIntersection(a, b, c, e);
197     // Essentially this says that if CDE and CAB have the same orientation,
198     // then CD and CE should intersect along AB in that order.
199     ab = ab.Normalize();
200     if (S1Angle(xcd, xce) > 2 * S2::kIntersectionError) {
201       EXPECT_EQ(s2pred::Sign(c, d, e) == s2pred::Sign(c, a, b),
202                 s2pred::Sign(ab, xcd, xce) > 0);
203     }
204   }
205   stats.Print();
206 }
207 
TEST(S2EdgeUtil,ExactIntersectionUnderflow)208 TEST(S2EdgeUtil, ExactIntersectionUnderflow) {
209   // Tests that a correct intersection is computed even when two edges are
210   // exactly collinear and the normals of both edges underflow in double
211   // precision when normalized (see S2PointFromExact function for details).
212   S2Point a0(1, 0, 0), a1(1, 2e-300, 0);
213   S2Point b0(1, 1e-300, 0), b1(1, 3e-300, 0);
214   EXPECT_EQ(S2Point(1, 1e-300, 0), S2::GetIntersection(a0, a1, b0, b1));
215 }
216 
TEST(S2EdgeUtil,GetIntersectionInvariants)217 TEST(S2EdgeUtil, GetIntersectionInvariants) {
218   // Test that the result of GetIntersection does not change when the edges
219   // are swapped and/or reversed.  The number of iterations is high because it
220   // is difficult to generate test cases that show that CompareEdges() is
221   // necessary and correct, for example.
222   const int kIters = google::DEBUG_MODE ? 5000 : 50000;
223   for (int iter = 0; iter < kIters; ++iter) {
224     S2Point a, b, c, d;
225     do {
226       // GetIntersectionStable() sorts the two edges by length, so construct
227       // edges (a,b) and (c,d) that cross and have exactly the same length.
228       // This can be done by swapping the "x" and "y" coordinates.
229       // [Swapping other coordinate pairs doesn't work because it changes the
230       // order of addition in Norm2() == (x**2 + y**2) + z**2.]
231       a = c = S2Testing::RandomPoint();
232       b = d = S2Testing::RandomPoint();
233       swap(c[0], c[1]);
234       swap(d[0], d[1]);
235     } while (S2::CrossingSign(a, b, c, d) <= 0);
236     EXPECT_EQ((a - b).Norm2(), (c - d).Norm2());
237 
238     // Now verify that GetIntersection returns exactly the same result when
239     // the edges are swapped and/or reversed.
240     S2Point result = S2::GetIntersection(a, b, c, d);
241     if (S2Testing::rnd.OneIn(2)) { swap(a, b); }
242     if (S2Testing::rnd.OneIn(2)) { swap(c, d); }
243     if (S2Testing::rnd.OneIn(2)) { swap(a, c); swap(b, d); }
244     EXPECT_EQ(result, S2::GetIntersection(a, b, c, d));
245   }
246 }
247