1 /*
2  * Copyright 2006 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.geometry;
18 
19 import com.google.common.collect.ImmutableList;
20 
21 /**
22  * Tests for {@link S2EdgeUtil}.
23  *
24  */
25 public strictfp class S2EdgeUtilTest extends GeometryTestCase {
26 
27   public static final int DEGENERATE = -2;
28 
compareResult(int actual, int expected)29   private void compareResult(int actual, int expected) {
30     // HACK ALERT: RobustCrossing() is allowed to return 0 or -1 if either edge
31     // is degenerate. We use the value kDegen to represent this possibility.
32     if (expected == DEGENERATE) {
33       assertTrue(actual <= 0);
34     } else {
35       assertEquals(expected, actual);
36     }
37   }
38 
assertCrossing(S2Point a, S2Point b, S2Point c, S2Point d, int robust, boolean edgeOrVertex, boolean simple)39   private void assertCrossing(S2Point a,
40       S2Point b,
41       S2Point c,
42       S2Point d,
43       int robust,
44       boolean edgeOrVertex,
45       boolean simple) {
46     a = S2Point.normalize(a);
47     b = S2Point.normalize(b);
48     c = S2Point.normalize(c);
49     d = S2Point.normalize(d);
50 
51     compareResult(S2EdgeUtil.robustCrossing(a, b, c, d), robust);
52     if (simple) {
53       assertEquals(robust > 0, S2EdgeUtil.simpleCrossing(a, b, c, d));
54     }
55     S2EdgeUtil.EdgeCrosser crosser = new S2EdgeUtil.EdgeCrosser(a, b, c);
56     compareResult(crosser.robustCrossing(d), robust);
57     compareResult(crosser.robustCrossing(c), robust);
58 
59     assertEquals(S2EdgeUtil.edgeOrVertexCrossing(a, b, c, d), edgeOrVertex);
60     assertEquals(edgeOrVertex, crosser.edgeOrVertexCrossing(d));
61     assertEquals(edgeOrVertex, crosser.edgeOrVertexCrossing(c));
62   }
63 
assertCrossings(S2Point a, S2Point b, S2Point c, S2Point d, int robust, boolean edgeOrVertex, boolean simple)64   private void assertCrossings(S2Point a,
65       S2Point b,
66       S2Point c,
67       S2Point d,
68       int robust,
69       boolean edgeOrVertex,
70       boolean simple) {
71     assertCrossing(a, b, c, d, robust, edgeOrVertex, simple);
72     assertCrossing(b, a, c, d, robust, edgeOrVertex, simple);
73     assertCrossing(a, b, d, c, robust, edgeOrVertex, simple);
74     assertCrossing(b, a, d, c, robust, edgeOrVertex, simple);
75     assertCrossing(a, a, c, d, DEGENERATE, false, false);
76     assertCrossing(a, b, c, c, DEGENERATE, false, false);
77     assertCrossing(a, b, a, b, 0, true, false);
78     assertCrossing(c, d, a, b, robust, (edgeOrVertex ^ (robust == 0)), simple);
79   }
80 
testCrossings()81   public void testCrossings() {
82     // The real tests of edge crossings are in s2{loop,polygon}_unittest,
83     // but we do a few simple tests here.
84 
85     // Two regular edges that cross.
86     assertCrossings(new S2Point(1, 2, 1),
87         new S2Point(1, -3, 0.5),
88         new S2Point(1, -0.5, -3),
89         new S2Point(0.1, 0.5, 3),
90         1,
91         true,
92         true);
93 
94     // Two regular edges that cross antipodal points.
95     assertCrossings(new S2Point(1, 2, 1),
96         new S2Point(1, -3, 0.5),
97         new S2Point(-1, 0.5, 3),
98         new S2Point(-0.1, -0.5, -3),
99         -1,
100         false,
101         true);
102 
103     // Two edges on the same great circle.
104     assertCrossings(new S2Point(0, 0, -1),
105         new S2Point(0, 1, 0),
106         new S2Point(0, 1, 1),
107         new S2Point(0, 0, 1),
108         -1,
109         false,
110         true);
111 
112     // Two edges that cross where one vertex is S2.Origin().
113     assertCrossings(new S2Point(1, 0, 0),
114         new S2Point(0, 1, 0),
115         new S2Point(0, 0, 1),
116         new S2Point(1, 1, -1),
117         1,
118         true,
119         true);
120 
121     // Two edges that cross antipodal points where one vertex is S2.Origin().
122     assertCrossings(new S2Point(1, 0, 0),
123         new S2Point(0, 1, 0),
124         new S2Point(0, 0, -1),
125         new S2Point(-1, -1, 1),
126         -1,
127         false,
128         true);
129 
130     // Two edges that share an endpoint. The Ortho() direction is (-4,0,2),
131     // and edge CD is further CCW around (2,3,4) than AB.
132     assertCrossings(new S2Point(2, 3, 4),
133         new S2Point(-1, 2, 5),
134         new S2Point(7, -2, 3),
135         new S2Point(2, 3, 4),
136         0,
137         false,
138         true);
139 
140     // Two edges that barely cross edge other.
141     assertCrossings(new S2Point(1, 1, 1),
142         new S2Point(1, 1 - 1e-15, -1),
143         new S2Point(-1, -1, 0),
144         new S2Point(1, 1, 0),
145         1,
146         true,
147         false);
148   }
149 
getEdgeBound(double x1, double y1, double z1, double x2, double y2, double z2)150   private S2LatLngRect getEdgeBound(double x1,
151       double y1,
152       double z1,
153       double x2,
154       double y2,
155       double z2) {
156     S2EdgeUtil.RectBounder bounder = new S2EdgeUtil.RectBounder();
157     S2Point p1 = S2Point.normalize(new S2Point(x1, y1, z1));
158     S2Point p2 = S2Point.normalize(new S2Point(x2, y2, z2));
159     bounder.addPoint(p1);
160     bounder.addPoint(p2);
161     return bounder.getBound();
162   }
163 
testRectBounder()164   public void testRectBounder() {
165     // Check cases where min/max latitude is not at a vertex.
166     // Max, CW
167     assertDoubleNear(getEdgeBound(1, 1, 1, 1, -1, 1).lat().hi(), S2.M_PI_4);
168     // Max, CCW
169     assertDoubleNear(getEdgeBound(1, -1, 1, 1, 1, 1).lat().hi(), S2.M_PI_4);
170     // Min, CW
171     assertDoubleNear(getEdgeBound(1, -1, -1, -1, -1, -1).lat().lo(), -S2.M_PI_4);
172     // Min, CCW
173     assertDoubleNear(getEdgeBound(-1, 1, -1, -1, -1, -1).lat().lo(), -S2.M_PI_4);
174 
175     // Check cases where the edge passes through one of the poles.
176     assertDoubleNear(getEdgeBound(.3, .4, 1, -.3, -.4, 1).lat().hi(), S2.M_PI_2);
177     assertDoubleNear(getEdgeBound(.3, .4, -1, -.3, -.4, -1).lat().lo(), -S2.M_PI_2);
178 
179     // Check cases where the min/max latitude is attained at a vertex.
180     double kCubeLat = Math.asin(Math.sqrt(1. / 3)); // 35.26 degrees
181     assertTrue(
182         getEdgeBound(1, 1, 1, 1, -1, -1).lat().approxEquals(new R1Interval(-kCubeLat, kCubeLat)));
183     assertTrue(
184         getEdgeBound(1, -1, 1, 1, 1, -1).lat().approxEquals(new R1Interval(-kCubeLat, kCubeLat)));
185   }
186 
187   // Produce a normalized S2Point for testing.
S2NP(double x, double y, double z)188   private S2Point S2NP(double x, double y, double z) {
189     return S2Point.normalize(new S2Point(x, y, z));
190   }
191 
testXYZPruner()192   public void testXYZPruner() {
193     S2EdgeUtil.XYZPruner pruner = new S2EdgeUtil.XYZPruner();
194 
195     // We aren't actually normalizing these points but it doesn't
196     // matter too much as long as we are reasonably close to unit vectors.
197     // This is a simple triangle on the equator.
198     pruner.addEdgeToBounds(S2NP(0, 1, 0), S2NP(0.1, 1, 0));
199     pruner.addEdgeToBounds(S2NP(0.1, 1, 0), S2NP(0.1, 1, 0.1));
200     pruner.addEdgeToBounds(S2NP(0.1, 1, 0.1), S2NP(0, 1, 0));
201 
202     // try a loop around the triangle but far enough out to not overlap.
203     pruner.setFirstIntersectPoint(S2NP(-0.1, 1.0, 0.0));
204     assertFalse(pruner.intersects(S2NP(-0.1, 1.0, 0.2)));
205     assertFalse(pruner.intersects(S2NP(0.0, 1.0, 0.2)));
206     assertFalse(pruner.intersects(S2NP(0.2, 1.0, 0.2)));
207     assertFalse(pruner.intersects(S2NP(0.2, 1.0, 0.05)));
208     assertFalse(pruner.intersects(S2NP(0.2, 1.0, -0.1)));
209     assertFalse(pruner.intersects(S2NP(-0.1, 1.0, -0.1)));
210     assertFalse(pruner.intersects(S2NP(-0.1, 1.0, 0.0)));
211 
212     // now we go to a point in the bounding box of the triangle but well
213     // out of the loop. This will be a hit even though it really does not
214     // need to be.
215     assertTrue(pruner.intersects(S2NP(0.02, 1.0, 0.04)));
216 
217     // now we zoom out to do an edge *just* below the triangle. This should
218     // be a hit because we are within the deformation zone.
219     assertTrue(pruner.intersects(S2NP(-0.1, 1.0, -0.03)));
220     assertFalse(pruner.intersects(S2NP(0.05, 1.0, -0.03))); // not close
221     assertTrue(pruner.intersects(S2NP(0.05, 1.0, -0.01))); // close
222     assertTrue(pruner.intersects(S2NP(0.05, 1.0, 0.13)));
223     assertFalse(pruner.intersects(S2NP(0.13, 1.0, 0.14)));
224 
225     // Create a new pruner with very small area and correspondingly narrow
226     // deformation tolerances.
227     S2EdgeUtil.XYZPruner spruner = new S2EdgeUtil.XYZPruner();
228     spruner.addEdgeToBounds(S2NP(0, 1, 0.000), S2NP(0.001, 1, 0));
229     spruner.addEdgeToBounds(S2NP(0.001, 1, 0.000), S2NP(0.001, 1, 0.001));
230     spruner.addEdgeToBounds(S2NP(0.001, 1, 0.001), S2NP(0.000, 1, 0));
231 
232     spruner.setFirstIntersectPoint(S2NP(0, 1.0, -0.1));
233     assertFalse(spruner.intersects(S2NP(0.0005, 1.0, -0.0005)));
234     assertFalse(spruner.intersects(S2NP(0.0005, 1.0, -0.0005)));
235     assertFalse(spruner.intersects(S2NP(0.0005, 1.0, -0.00001)));
236     assertTrue(spruner.intersects(S2NP(0.0005, 1.0, -0.0000001)));
237   }
238 
testLongitudePruner()239   public void testLongitudePruner() {
240     S2EdgeUtil.LongitudePruner pruner1 = new S2EdgeUtil.LongitudePruner(
241         new S1Interval(0.75 * S2.M_PI, -0.75 * S2.M_PI), new S2Point(0, 1, 2));
242 
243     assertFalse(pruner1.intersects(new S2Point(1, 1, 3)));
244     assertTrue(pruner1.intersects(new S2Point(-1 - 1e-15, -1, 0)));
245     assertTrue(pruner1.intersects(new S2Point(-1, 0, 0)));
246     assertTrue(pruner1.intersects(new S2Point(-1, 0, 0)));
247     assertTrue(pruner1.intersects(new S2Point(1, -1, 8)));
248     assertFalse(pruner1.intersects(new S2Point(1, 0, -2)));
249     assertTrue(pruner1.intersects(new S2Point(-1, -1e-15, 0)));
250 
251     S2EdgeUtil.LongitudePruner pruner2 = new S2EdgeUtil.LongitudePruner(
252         new S1Interval(0.25 * S2.M_PI, 0.25 * S2.M_PI), new S2Point(1, 0, 0));
253 
254     assertFalse(pruner2.intersects(new S2Point(2, 1, 2)));
255     assertTrue(pruner2.intersects(new S2Point(1, 2, 3)));
256     assertFalse(pruner2.intersects(new S2Point(0, 1, 4)));
257     assertFalse(pruner2.intersects(new S2Point(-1e-15, -1, -1)));
258   }
259 
assertWedge(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2, boolean contains, boolean intersects, boolean crosses)260   private void assertWedge(S2Point a0,
261       S2Point ab1,
262       S2Point a2,
263       S2Point b0,
264       S2Point b2,
265       boolean contains,
266       boolean intersects,
267       boolean crosses) {
268     a0 = S2Point.normalize(a0);
269     ab1 = S2Point.normalize(ab1);
270     a2 = S2Point.normalize(a2);
271     b0 = S2Point.normalize(b0);
272     b2 = S2Point.normalize(b2);
273 
274     assertEquals(new S2EdgeUtil.WedgeContains().test(a0, ab1, a2, b0, b2), contains ? 1 : 0);
275     assertEquals(new S2EdgeUtil.WedgeIntersects().test(a0, ab1, a2, b0, b2), intersects ? -1 : 0);
276     assertEquals(new S2EdgeUtil.WedgeContainsOrIntersects().test(a0, ab1, a2, b0, b2),
277         contains ? 1 : intersects ? -1 : 0);
278     assertEquals(new S2EdgeUtil.WedgeContainsOrCrosses().test(a0, ab1, a2, b0, b2),
279         contains ? 1 : crosses ? -1 : 0);
280   }
281 
testWedges()282   public void testWedges() {
283     // For simplicity, all of these tests use an origin of (0, 0, 1).
284     // This shouldn't matter as long as the lower-level primitives are
285     // implemented correctly.
286 
287     // Intersection in one wedge.
288     assertWedge(new S2Point(-1, 0, 10),
289         new S2Point(0, 0, 1),
290         new S2Point(1, 2, 10),
291         new S2Point(0, 1, 10),
292         new S2Point(1, -2, 10),
293         false,
294         true,
295         true);
296     // Intersection in two wedges.
297     assertWedge(new S2Point(-1, -1, 10),
298         new S2Point(0, 0, 1),
299         new S2Point(1, -1, 10),
300         new S2Point(1, 0, 10),
301         new S2Point(-1, 1, 10),
302         false,
303         true,
304         true);
305 
306     // Normal containment.
307     assertWedge(new S2Point(-1, -1, 10),
308         new S2Point(0, 0, 1),
309         new S2Point(1, -1, 10),
310         new S2Point(-1, 0, 10),
311         new S2Point(1, 0, 10),
312         true,
313         true,
314         false);
315     // Containment with equality on one side.
316     assertWedge(new S2Point(2, 1, 10),
317         new S2Point(0, 0, 1),
318         new S2Point(-1, -1, 10),
319         new S2Point(2, 1, 10),
320         new S2Point(1, -5, 10),
321         true,
322         true,
323         false);
324     // Containment with equality on the other side.
325     assertWedge(new S2Point(2, 1, 10),
326         new S2Point(0, 0, 1),
327         new S2Point(-1, -1, 10),
328         new S2Point(1, -2, 10),
329         new S2Point(-1, -1, 10),
330         true,
331         true,
332         false);
333     // Containment with equality on both sides.
334     assertWedge(new S2Point(-2, 3, 10),
335         new S2Point(0, 0, 1),
336         new S2Point(4, -5, 10),
337         new S2Point(-2, 3, 10),
338         new S2Point(4, -5, 10),
339         true,
340         true,
341         false);
342 
343     // Disjoint with equality on one side.
344     assertWedge(new S2Point(-2, 3, 10),
345         new S2Point(0, 0, 1),
346         new S2Point(4, -5, 10),
347         new S2Point(4, -5, 10),
348         new S2Point(-2, -3, 10),
349         false,
350         false,
351         false);
352     // Disjoint with equality on the other side.
353     assertWedge(new S2Point(-2, 3, 10),
354         new S2Point(0, 0, 1),
355         new S2Point(0, 5, 10),
356         new S2Point(4, -5, 10),
357         new S2Point(-2, 3, 10),
358         false,
359         false,
360         false);
361     // Disjoint with equality on both sides.
362     assertWedge(new S2Point(-2, 3, 10),
363         new S2Point(0, 0, 1),
364         new S2Point(4, -5, 10),
365         new S2Point(4, -5, 10),
366         new S2Point(-2, 3, 10),
367         false,
368         false,
369         false);
370 
371     // B contains A with equality on one side.
372     assertWedge(new S2Point(2, 1, 10),
373         new S2Point(0, 0, 1),
374         new S2Point(1, -5, 10),
375         new S2Point(2, 1, 10),
376         new S2Point(-1, -1, 10),
377         false,
378         true,
379         false);
380     // B contains A with equality on the other side.
381     assertWedge(new S2Point(2, 1, 10),
382         new S2Point(0, 0, 1),
383         new S2Point(1, -5, 10),
384         new S2Point(-2, 1, 10),
385         new S2Point(1, -5, 10),
386         false,
387         true,
388         false);
389   }
390 
testGetClosestPoint()391   public void testGetClosestPoint() {
392     final double kMargin = 1e-6;
393 
394     S2Point a = S2LatLng.fromDegrees(-0.5, 0).toPoint();
395     S2Point b = S2LatLng.fromDegrees(+0.5, 0).toPoint();
396 
397     // On edge at end points.
398     assertEquals(a, S2EdgeUtil.getClosestPoint(a, a, b));
399     assertEquals(b, S2EdgeUtil.getClosestPoint(b, a, b));
400 
401     // On edge in between.
402     S2Point mid = S2LatLng.fromDegrees(0, 0).toPoint();
403     assertEquals(mid, S2EdgeUtil.getClosestPoint(mid, a, b));
404 
405     // End points are closest
406     assertEquals(a, S2EdgeUtil.getClosestPoint(S2LatLng.fromDegrees(-1, 0).toPoint(), a, b));
407     assertEquals(b, S2EdgeUtil.getClosestPoint(S2LatLng.fromDegrees(+1, 0).toPoint(), a, b));
408 
409     // Intermediate point is closest.
410     S2Point x = S2LatLng.fromDegrees(+0.1, 1).toPoint();
411     S2Point expectedClosestPoint = S2LatLng.fromDegrees(+0.1, 0).toPoint();
412 
413     assertTrue(expectedClosestPoint.aequal(S2EdgeUtil.getClosestPoint(x, a, b), kMargin));
414   }
415 
416   // Given a point X and an edge AB, check that the distance from X to AB is
417   // "distanceRadians" and the closest point on AB is "expectedClosest".
checkDistance( S2Point x, S2Point a, S2Point b, double distanceRadians, S2Point expectedClosest)418   private static void checkDistance(
419       S2Point x, S2Point a, S2Point b, double distanceRadians, S2Point expectedClosest) {
420     final double kEpsilon = 1e-10;
421     x = S2Point.normalize(x);
422     a = S2Point.normalize(a);
423     b = S2Point.normalize(b);
424     expectedClosest = S2Point.normalize(expectedClosest);
425 
426     assertEquals(distanceRadians, S2EdgeUtil.getDistance(x, a, b).radians(), kEpsilon);
427 
428     S2Point closest = S2EdgeUtil.getClosestPoint(x, a, b);
429     if (expectedClosest.equals(new S2Point(0, 0, 0))) {
430       // This special value says that the result should be A or B.
431       assertTrue(closest == a || closest == b);
432     } else {
433       assertTrue(S2.approxEquals(closest, expectedClosest));
434     }
435   }
436 
testGetDistance()437   public void testGetDistance() {
438     checkDistance(
439         new S2Point(1, 0, 0), new S2Point(1, 0, 0), new S2Point(0, 1, 0), 0, new S2Point(1, 0, 0));
440     checkDistance(
441         new S2Point(0, 1, 0), new S2Point(1, 0, 0), new S2Point(0, 1, 0), 0, new S2Point(0, 1, 0));
442     checkDistance(
443         new S2Point(1, 3, 0), new S2Point(1, 0, 0), new S2Point(0, 1, 0), 0, new S2Point(1, 3, 0));
444     checkDistance(new S2Point(0, 0, 1), new S2Point(1, 0, 0), new S2Point(0, 1, 0), Math.PI / 2,
445         new S2Point(1, 0, 0));
446     checkDistance(new S2Point(0, 0, -1), new S2Point(1, 0, 0), new S2Point(0, 1, 0), Math.PI / 2,
447         new S2Point(1, 0, 0));
448     checkDistance(new S2Point(-1, -1, 0), new S2Point(1, 0, 0), new S2Point(0, 1, 0),
449         0.75 * Math.PI, new S2Point(0, 0, 0));
450     checkDistance(new S2Point(0, 1, 0), new S2Point(1, 0, 0), new S2Point(1, 1, 0), Math.PI / 4,
451         new S2Point(1, 1, 0));
452     checkDistance(new S2Point(0, -1, 0), new S2Point(1, 0, 0), new S2Point(1, 1, 0), Math.PI / 2,
453         new S2Point(1, 0, 0));
454     checkDistance(new S2Point(0, -1, 0), new S2Point(1, 0, 0), new S2Point(-1, 1, 0), Math.PI / 2,
455         new S2Point(1, 0, 0));
456     checkDistance(new S2Point(-1, -1, 0), new S2Point(1, 0, 0), new S2Point(-1, 1, 0), Math.PI / 2,
457         new S2Point(-1, 1, 0));
458     checkDistance(new S2Point(1, 1, 1), new S2Point(1, 0, 0), new S2Point(0, 1, 0),
459         Math.asin(Math.sqrt(1. / 3)), new S2Point(1, 1, 0));
460     checkDistance(new S2Point(1, 1, -1), new S2Point(1, 0, 0), new S2Point(0, 1, 0),
461         Math.asin(Math.sqrt(1. / 3)), new S2Point(1, 1, 0));
462     checkDistance(new S2Point(-1, 0, 0), new S2Point(1, 1, 0), new S2Point(1, 1, 0), 0.75 * Math.PI,
463         new S2Point(1, 1, 0));
464     checkDistance(new S2Point(0, 0, -1), new S2Point(1, 1, 0), new S2Point(1, 1, 0), Math.PI / 2,
465         new S2Point(1, 1, 0));
466     checkDistance(new S2Point(-1, 0, 0), new S2Point(1, 0, 0), new S2Point(1, 0, 0), Math.PI,
467         new S2Point(1, 0, 0));
468   }
469 
testIntersectionTolerance()470   public void testIntersectionTolerance() {
471     // We repeatedly construct two edges that cross near a random point "p",
472     // and measure the distance from the actual intersection point "x" to the
473     // the expected intersection point "p" and also to the edges that cross
474     // near "p".
475     //
476     // Note that getIntersection() does not guarantee that "x" and "p" will be
477     // close together (since the intersection point is numerically unstable
478     // when the edges cross at a very small angle), but it does guarantee that
479     // "x" will be close to both of the edges that cross.
480     S1Angle maxPointDist = new S1Angle();
481     S1Angle maxEdgeDist = new S1Angle();
482 
483     for (int i = 0; i < 1000; ++i) {
484       // We construct two edges AB and CD that intersect near "p". The angle
485       // between AB and CD (expressed as a slope) is chosen randomly between
486       // 1e-15 and 1.0 such that its logarithm is uniformly distributed. This
487       // implies that small values are much more likely to be chosen.
488       //
489       // Once the slope is chosen, the four points ABCD must be offset from P
490       // by at least (1e-15 / slope) so that the points are guaranteed to have
491       // the correct circular ordering around P. This is the distance from P
492       // at which the two edges are separated by about 1e-15, which is
493       // approximately the minimum distance at which we can expect computed
494       // points on the two lines to be distinct and have the correct ordering.
495       //
496       // The actual offset distance from P is chosen randomly in the range
497       // [1e-15 / slope, 1.0], again uniformly distributing the logarithm.
498       // This ensures that we test both long and very short segments that
499       // intersect at both large and very small angles.
500 
501       ImmutableList<S2Point> points = getRandomFrame();
502       S2Point p = points.get(0);
503       S2Point d1 = points.get(1);
504       S2Point d2 = points.get(2);
505       double slope = Math.pow(1e-15, rand.nextDouble());
506       d2 = S2Point.add(d1, S2Point.mul(d2, slope));
507       S2Point a = S2Point.normalize(
508           S2Point.add(p, S2Point.mul(d1, Math.pow(1e-15 / slope, rand.nextDouble()))));
509       S2Point b = S2Point.normalize(
510           S2Point.sub(p, S2Point.mul(d1, Math.pow(1e-15 / slope, rand.nextDouble()))));
511       S2Point c = S2Point.normalize(
512           S2Point.add(p, S2Point.mul(d2, Math.pow(1e-15 / slope, rand.nextDouble()))));
513       S2Point d = S2Point.normalize(
514           S2Point.sub(p, S2Point.mul(d2, Math.pow(1e-15 / slope, rand.nextDouble()))));
515       S2Point x = S2EdgeUtil.getIntersection(a, b, c, d);
516       S1Angle distAb = S2EdgeUtil.getDistance(x, a, b);
517       S1Angle distCd = S2EdgeUtil.getDistance(x, c, d);
518 
519       assertTrue(distAb.lessThan(S2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE));
520       assertTrue(distCd.lessThan(S2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE));
521 
522       // test getIntersection() post conditions
523       assertTrue(S2.orderedCCW(a, x, b, S2Point.normalize(S2.robustCrossProd(a, b))));
524       assertTrue(S2.orderedCCW(c, x, d, S2Point.normalize(S2.robustCrossProd(c, d))));
525 
526       maxEdgeDist = S1Angle.max(maxEdgeDist, S1Angle.max(distAb, distCd));
527       maxPointDist = S1Angle.max(maxPointDist, new S1Angle(p, x));
528     }
529   }
530 }
531