// Given a point X and an edge AB, check that the distance from X to AB is // "distanceRadians" and the closest point on AB is "expectedClosest". private static void checkDistance( S2Point x, S2Point a, S2Point b, double distanceRadians, S2Point expectedClosest) { var kEpsilon = 1e-10; x = S2Point.Normalize(x); a = S2Point.Normalize(a); b = S2Point.Normalize(b); expectedClosest = S2Point.Normalize(expectedClosest); assertEquals(distanceRadians, S2EdgeUtil.GetDistance(x, a, b).Radians, kEpsilon); var closest = S2EdgeUtil.GetClosestPoint(x, a, b); if (expectedClosest.Equals(new S2Point(0, 0, 0))) { // This special value says that the result should be A or B. assertTrue(closest == a || closest == b); } else { assertTrue(S2.ApproxEquals(closest, expectedClosest)); } }
/** * A relatively expensive calculation invoked by RobustCCW() if the sign of * the determinant is uncertain. */ private static int ExpensiveCcw(S2Point a, S2Point b, S2Point c) { // Return zero if and only if two points are the same. This ensures (1). if (a.Equals(b) || b.Equals(c) || c.Equals(a)) { return 0; } // Now compute the determinant in a stable way. Since all three points are // unit length and we know that the determinant is very close to zero, this // means that points are very nearly colinear. Furthermore, the most common // situation is where two points are nearly identical or nearly antipodal. // To get the best accuracy in this situation, it is important to // immediately reduce the magnitude of the arguments by computing either // A+B or A-B for each pair of points. Note that even if A and B differ // only in their low bits, A-B can be computed very accurately. On the // other hand we can't accurately represent an arbitrary linear combination // of two vectors as would be required for Gaussian elimination. The code // below chooses the vertex opposite the longest edge as the "origin" for // the calculation, and computes the different vectors to the other two // vertices. This minimizes the sum of the lengths of these vectors. // // This implementation is very stable numerically, but it still does not // return consistent results in all cases. For example, if three points are // spaced far apart from each other along a great circle, the sign of the // result will basically be random (although it will still satisfy the // conditions documented in the header file). The only way to return // consistent results in all cases is to compute the result using // arbitrary-precision arithmetic. I considered using the Gnu MP library, // but this would be very expensive (up to 2000 bits of precision may be // needed to store the intermediate results) and seems like overkill for // this problem. The MP library is apparently also quite particular about // compilers and compilation options and would be a pain to maintain. // We want to handle the case of nearby points and nearly antipodal points // accurately, so determine whether A+B or A-B is smaller in each case. double sab = (a.DotProd(b) > 0) ? -1 : 1; double sbc = (b.DotProd(c) > 0) ? -1 : 1; double sca = (c.DotProd(a) > 0) ? -1 : 1; var vab = a + (b * sab); var vbc = b + (c * sbc); var vca = c + (a * sca); var dab = vab.Norm2; var dbc = vbc.Norm2; var dca = vca.Norm2; // Sort the difference vectors to find the longest edge, and use the // opposite vertex as the origin. If two difference vectors are the same // length, we break ties deterministically to ensure that the symmetry // properties guaranteed in the header file will be true. double sign; if (dca < dbc || (dca == dbc && a < b)) { if (dab < dbc || (dab == dbc && a < c)) { // The "sab" factor converts A +/- B into B +/- A. sign = S2Point.CrossProd(vab, vca).DotProd(a)*sab; // BC is longest // edge } else { sign = S2Point.CrossProd(vca, vbc).DotProd(c)*sca; // AB is longest // edge } } else { if (dab < dca || (dab == dca && b < c)) { sign = S2Point.CrossProd(vbc, vab).DotProd(b)*sbc; // CA is longest // edge } else { sign = S2Point.CrossProd(vca, vbc).DotProd(c)*sca; // AB is longest // edge } } if (sign > 0) { return 1; } if (sign < 0) { return -1; } // The points A, B, and C are numerically indistinguishable from coplanar. // This may be due to roundoff error, or the points may in fact be exactly // coplanar. We handle this situation by perturbing all of the points by a // vector (eps, eps**2, eps**3) where "eps" is an infinitesmally small // positive number (e.g. 1 divided by a googolplex). The perturbation is // done symbolically, i.e. we compute what would happen if the points were // perturbed by this amount. It turns out that this is equivalent to // checking whether the points are ordered CCW around the origin first in // the Y-Z plane, then in the Z-X plane, and then in the X-Y plane. var ccw = PlanarOrderedCcw(new R2Vector(a.Y, a.Z), new R2Vector(b.Y, b.Z), new R2Vector(c.Y, c.Z)); if (ccw == 0) { ccw = PlanarOrderedCcw(new R2Vector(a.Z, a.X), new R2Vector(b.Z, b.X), new R2Vector(c.Z, c.X)); if (ccw == 0) { ccw = PlanarOrderedCcw( new R2Vector(a.X, a.Y), new R2Vector(b.X, b.Y), new R2Vector(c.X, c.Y)); // assert (ccw != 0); } } return ccw; }
/** * Given two edge chains (see WedgeRelation above), this function returns +1 * if A contains B, 0 if A and B are disjoint, and -1 if A intersects but * does not contain B. */ public int Test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2) { // This is similar to WedgeContainsOrCrosses, except that we want to // distinguish cases (1) [A contains B], (3) [A and B are disjoint], // and (2,4,5,6) [A intersects but does not contain B]. if (S2.OrderedCcw(a0, a2, b2, ab1)) { // We are in case 1, 5, or 6, or case 2 if a2 == b2. return S2.OrderedCcw(b2, b0, a0, ab1) ? 1 : -1; // Case 1 vs. 2,5,6. } // We are in cases 2, 3, or 4. if (!S2.OrderedCcw(a2, b0, b2, ab1)) { return 0; // Case 3. } // We are in case 2 or 4, or case 3 if a2 == b0. return (a2.Equals(b0)) ? 0 : -1; // Case 3 vs. 2,4. }
/** * Given two edge chains (see WedgeRelation above), this function returns +1 * if A contains B, 0 if B contains A or the two wedges do not intersect, * and -1 if the edge chains A and B cross each other (i.e. if A intersects * both the interior and exterior of the region to the left of B). In * degenerate cases where more than one of these conditions is satisfied, * the maximum possible result is returned. For example, if A == B then the * result is +1. */ public int Test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2) { // There are 6 possible edge orderings at a shared vertex (all // of these orderings are circular, i.e. abcd == bcda): // // (1) a2 b2 b0 a0: A contains B // (2) a2 a0 b0 b2: B contains A // (3) a2 a0 b2 b0: A and B are disjoint // (4) a2 b0 a0 b2: A and B intersect in one wedge // (5) a2 b2 a0 b0: A and B intersect in one wedge // (6) a2 b0 b2 a0: A and B intersect in two wedges // // In cases (4-6), the boundaries of A and B cross (i.e. the boundary // of A intersects the interior and exterior of B and vice versa). // Thus we want to distinguish cases (1), (2-3), and (4-6). // // Note that the vertices may satisfy more than one of the edge // orderings above if two or more vertices are the same. The tests // below are written so that we take the most favorable // interpretation, i.e. preferring (1) over (2-3) over (4-6). In // particular note that if orderedCCW(a,b,c,o) returns true, it may be // possible that orderedCCW(c,b,a,o) is also true (if a == b or b == c). if (S2.OrderedCcw(a0, a2, b2, ab1)) { // The cases with this vertex ordering are 1, 5, and 6, // although case 2 is also possible if a2 == b2. if (S2.OrderedCcw(b2, b0, a0, ab1)) { return 1; // Case 1 (A contains B) } // We are in case 5 or 6, or case 2 if a2 == b2. return (a2.Equals(b2)) ? 0 : -1; // Case 2 vs. 5,6. } // We are in case 2, 3, or 4. return S2.OrderedCcw(a0, b0, a2, ab1) ? 0 : -1; // Case 2,3 vs. 4. }
/** * Given a point X and an edge AB, return the distance ratio AX / (AX + BX). * If X happens to be on the line segment AB, this is the fraction "t" such * that X == Interpolate(A, B, t). Requires that A and B are distinct. */ public static double GetDistanceFraction(S2Point x, S2Point a0, S2Point a1) { Preconditions.CheckArgument(!a0.Equals(a1)); var d0 = x.Angle(a0); var d1 = x.Angle(a1); return d0/(d0 + d1); }
/** * Given two edges AB and CD where at least two vertices are identical (i.e. * robustCrossing(a,b,c,d) == 0), this function defines whether the two edges * "cross" in a such a way that point-in-polygon containment tests can be * implemented by counting the number of edge crossings. The basic rule is * that a "crossing" occurs if AB is encountered after CD during a CCW sweep * around the shared vertex starting from a fixed reference point. * * Note that according to this rule, if AB crosses CD then in general CD does * not cross AB. However, this leads to the correct result when counting * polygon edge crossings. For example, suppose that A,B,C are three * consecutive vertices of a CCW polygon. If we now consider the edge * crossings of a segment BP as P sweeps around B, the crossing number changes * parity exactly when BP crosses BA or BC. * * Useful properties of VertexCrossing (VC): * * (1) VC(a,a,c,d) == VC(a,b,c,c) == false (2) VC(a,b,a,b) == VC(a,b,b,a) == * true (3) VC(a,b,c,d) == VC(a,b,d,c) == VC(b,a,c,d) == VC(b,a,d,c) (3) If * exactly one of a,b Equals one of c,d, then exactly one of VC(a,b,c,d) and * VC(c,d,a,b) is true * * It is an error to call this method with 4 distinct vertices. */ public static bool VertexCrossing(S2Point a, S2Point b, S2Point c, S2Point d) { // If A == B or C == D there is no intersection. We need to check this // case first in case 3 or more input points are identical. if (a.Equals(b) || c.Equals(d)) { return false; } // If any other pair of vertices is equal, there is a crossing if and only // if orderedCCW() indicates that the edge AB is further CCW around the // shared vertex than the edge CD. if (a.Equals(d)) { return S2.OrderedCcw(S2.Ortho(a), c, b, a); } if (b.Equals(c)) { return S2.OrderedCcw(S2.Ortho(b), d, a, b); } if (a.Equals(c)) { return S2.OrderedCcw(S2.Ortho(a), d, b, a); } if (b.Equals(d)) { return S2.OrderedCcw(S2.Ortho(b), c, a, b); } // assert (false); return false; }