private static S1ChordAngle GetMaxDistanceToEdgeBruteForce(S2Cell cell, S2Point a, S2Point b) { // If any antipodal endpoint is within the cell, the max distance is Pi. if (cell.Contains(-a) || cell.Contains(-b)) { return(S1ChordAngle.Straight); } S1ChordAngle max_dist = S1ChordAngle.Negative; for (int i = 0; i < 4; ++i) { S2Point v0 = cell.Vertex(i); S2Point v1 = cell.Vertex(i + 1); // If the antipodal edge crosses through the cell, max distance is Pi. if (S2.CrossingSign(-a, -b, v0, v1) >= 0) { return(S1ChordAngle.Straight); } S2.UpdateMaxDistance(a, v0, v1, ref max_dist); S2.UpdateMaxDistance(b, v0, v1, ref max_dist); S2.UpdateMaxDistance(v0, a, b, ref max_dist); } return(max_dist); }
// Returns the pair of points (a, b) that achieves the minimum distance // between edges a0a1 and b0b1, where "a" is a point on a0a1 and "b" is a // point on b0b1. If the two edges intersect, "a" and "b" are both equal to // the intersection point. Handles a0 == a1 and b0 == b1 correctly. public static (S2Point, S2Point) GetEdgePairClosestPoints(S2Point a0, S2Point a1, S2Point b0, S2Point b1) { if (S2.CrossingSign(a0, a1, b0, b1) > 0) { S2Point x = S2.GetIntersection(a0, a1, b0, b1, null); return(x, x); } // We save some work by first determining which vertex/edge pair achieves // the minimum distance, and then computing the closest point on that edge. var min_dist = S1ChordAngle.Zero; AlwaysUpdateMinDistance(a0, b0, b1, ref min_dist, true); var closest_vertex = 0; if (UpdateMinDistance(a1, b0, b1, ref min_dist)) { closest_vertex = 1; } if (UpdateMinDistance(b0, a0, a1, ref min_dist)) { closest_vertex = 2; } if (UpdateMinDistance(b1, a0, a1, ref min_dist)) { closest_vertex = 3; } return(closest_vertex switch { 0 => (a0, Project(a0, b0, b1)), 1 => (a1, Project(a1, b0, b1)), 2 => (Project(b0, a0, a1), b0), 3 => (Project(b1, a0, a1), b1), _ => throw new ApplicationException("Unreached (to suppress Android compiler warning)"), });
public void Test_S2_CoincidentZeroLengthEdgesThatDontTouch() { // It is important that the edge primitives can handle vertices that exactly // exactly proportional to each other, i.e. that are not identical but are // nevertheless exactly coincident when projected onto the unit sphere. // There are various ways that such points can arise. For example, // Normalize() itself is not idempotent: there exist distinct points A,B // such that Normalize(A) == B and Normalize(B) == A. Another issue is // that sometimes calls to Normalize() are skipped when the result of a // calculation "should" be unit length mathematically (e.g., when computing // the cross product of two orthonormal vectors). // // This test checks pairs of edges AB and CD where A,B,C,D are exactly // coincident on the sphere and the norms of A,B,C,D are monotonically // increasing. Such edge pairs should never intersect. (This is not // obvious, since it depends on the particular symbolic perturbations used // by S2Pred.Sign(). It would be better to replace this with a test that // says that the CCW results must be consistent with each other.) int kIters = 1000; for (int iter = 0; iter < kIters; ++iter) { // Construct a point P where every component is zero or a power of 2. var t = new double[3]; for (int i = 0; i < 3; ++i) { int binary_exp = S2Testing.Random.Skewed(11); t[i] = (binary_exp > 1022) ? 0 : Math.Pow(2, -binary_exp); } // If all components were zero, try again. Note that normalization may // convert a non-zero point into a zero one due to underflow (!) var p = new S2Point(t).Normalize(); if (p == S2Point.Empty) { --iter; continue; } // Now every non-zero component should have exactly the same mantissa. // This implies that if we scale the point by an arbitrary factor, every // non-zero component will still have the same mantissa. Scale the points // so that they are all distinct and are still very likely to satisfy // S2.IsUnitLength (which allows for a small amount of error in the norm). S2Point a = (1 - 3e-16) * p; S2Point b = (1 - 1e-16) * p; S2Point c = p; S2Point d = (1 + 2e-16) * p; if (!a.IsUnitLength() || !d.IsUnitLength()) { --iter; continue; } // Verify that the expected edges do not cross. Assert.True(0 > S2.CrossingSign(a, b, c, d)); S2EdgeCrosser crosser = new(a, b, c); Assert.True(0 > crosser.CrossingSign(d)); Assert.True(0 > crosser.CrossingSign(c)); } }
public void Test_S2_CollinearEdgesThatDontTouch() { int kIters = 500; for (int iter = 0; iter < kIters; ++iter) { S2Point a = S2Testing.RandomPoint(); S2Point d = S2Testing.RandomPoint(); S2Point b = S2.Interpolate(0.05, a, d); S2Point c = S2.Interpolate(0.95, a, d); Assert.True(0 > S2.CrossingSign(a, b, c, d)); Assert.True(0 > S2.CrossingSign(a, b, c, d)); S2EdgeCrosser crosser = new(a, b, c); Assert.True(0 > crosser.CrossingSign(d)); Assert.True(0 > crosser.CrossingSign(c)); } }
// As above, but for maximum distances. If one edge crosses the antipodal // reflection of the other, the distance is Pi. public static bool UpdateEdgePairMaxDistance(S2Point a0, S2Point a1, S2Point b0, S2Point b1, ref S1ChordAngle max_dist) { if (max_dist == S1ChordAngle.Straight) { return(false); } if (S2.CrossingSign(a0, a1, -b0, -b1) >= 0) { max_dist = S1ChordAngle.Straight; return(true); } // Otherwise, the maximum distance is achieved at an endpoint of at least // one of the two edges. We use "|" rather than "||" below to ensure that // all four possibilities are always checked. // // The calculation below computes each of the six vertex-vertex distances // twice (this could be optimized). return(UpdateMaxDistance(a0, b0, b1, ref max_dist) | UpdateMaxDistance(a1, b0, b1, ref max_dist) | UpdateMaxDistance(b0, a0, a1, ref max_dist) | UpdateMaxDistance(b1, a0, a1, ref max_dist)); }
private static S1ChordAngle GetDistanceToEdgeBruteForce(S2Cell cell, S2Point a, S2Point b) { if (cell.Contains(a) || cell.Contains(b)) { return(S1ChordAngle.Zero); } S1ChordAngle min_dist = S1ChordAngle.Infinity; for (int i = 0; i < 4; ++i) { S2Point v0 = cell.Vertex(i); S2Point v1 = cell.Vertex(i + 1); // If the edge crosses through the cell, max distance is 0. if (S2.CrossingSign(a, b, v0, v1) >= 0) { return(S1ChordAngle.Zero); } S2.UpdateMinDistance(a, v0, v1, ref min_dist); S2.UpdateMinDistance(b, v0, v1, ref min_dist); S2.UpdateMinDistance(v0, a, b, ref min_dist); } return(min_dist); }
private static void TestCrossing(S2Point a, S2Point b, S2Point c, S2Point d, int crossing_sign, int signed_crossing_sign) { // For degenerate edges, CrossingSign() is documented to return 0 if two // vertices from different edges are the same and -1 otherwise. The // TestCrossings() function below uses various argument permutations that // can sometimes create this case, so we fix it now if necessary. if (a == c || a == d || b == c || b == d) { crossing_sign = 0; } // As a sanity check, make sure that the expected value of // "signed_crossing_sign" is consistent with its documented properties. if (crossing_sign == 1) { Assert.Equal(signed_crossing_sign, S2Pred.Sign(a, b, c)); } else if (crossing_sign == 0 && signed_crossing_sign != 0) { Assert.Equal(signed_crossing_sign, (a == c || b == d) ? 1 : -1); } Assert.Equal(crossing_sign, S2.CrossingSign(a, b, c, d)); S2EdgeCrosser crosser = new(a, b, c); Assert.Equal(crossing_sign, crosser.CrossingSign(d)); Assert.Equal(crossing_sign, crosser.CrossingSign(c)); Assert.Equal(crossing_sign, crosser.CrossingSign(d, c)); Assert.Equal(crossing_sign, crosser.CrossingSign(c, d)); Assert.Equal(signed_crossing_sign != 0, S2.EdgeOrVertexCrossing(a, b, c, d)); crosser.RestartAt(c); Assert.Equal(signed_crossing_sign != 0, crosser.EdgeOrVertexCrossing(d)); Assert.Equal(signed_crossing_sign != 0, crosser.EdgeOrVertexCrossing(c)); Assert.Equal(signed_crossing_sign != 0, crosser.EdgeOrVertexCrossing(d, c)); Assert.Equal(signed_crossing_sign != 0, crosser.EdgeOrVertexCrossing(c, d)); crosser.RestartAt(c); Assert.Equal(signed_crossing_sign, crosser.SignedEdgeOrVertexCrossing(d)); Assert.Equal(-signed_crossing_sign, crosser.SignedEdgeOrVertexCrossing(c)); Assert.Equal(-signed_crossing_sign, crosser.SignedEdgeOrVertexCrossing(d, c)); Assert.Equal(signed_crossing_sign, crosser.SignedEdgeOrVertexCrossing(c, d)); // Check that the crosser can be re-used. crosser.Init(c, d); crosser.RestartAt(a); Assert.Equal(crossing_sign, crosser.CrossingSign(b)); Assert.Equal(crossing_sign, crosser.CrossingSign(a)); // Now try all the same tests with CopyingEdgeCrosser. S2CopyingEdgeCrosser crosser2 = new(a, b, c); Assert.Equal(crossing_sign, crosser2.CrossingSign(d)); Assert.Equal(crossing_sign, crosser2.CrossingSign(c)); Assert.Equal(crossing_sign, crosser2.CrossingSign(d, c)); Assert.Equal(crossing_sign, crosser2.CrossingSign(c, d)); crosser2.RestartAt(c); Assert.Equal(signed_crossing_sign != 0, crosser2.EdgeOrVertexCrossing(d)); Assert.Equal(signed_crossing_sign != 0, crosser2.EdgeOrVertexCrossing(c)); Assert.Equal(signed_crossing_sign != 0, crosser2.EdgeOrVertexCrossing(d, c)); Assert.Equal(signed_crossing_sign != 0, crosser2.EdgeOrVertexCrossing(c, d)); crosser2.RestartAt(c); Assert.Equal(signed_crossing_sign, crosser2.SignedEdgeOrVertexCrossing(d)); Assert.Equal(-signed_crossing_sign, crosser2.SignedEdgeOrVertexCrossing(c)); Assert.Equal(-signed_crossing_sign, crosser2.SignedEdgeOrVertexCrossing(d, c)); Assert.Equal(signed_crossing_sign, crosser2.SignedEdgeOrVertexCrossing(c, d)); // Check that the crosser can be re-used. crosser2.Init(c, d); crosser2.RestartAt(a); Assert.Equal(crossing_sign, crosser2.CrossingSign(b)); Assert.Equal(crossing_sign, crosser2.CrossingSign(a)); }