public void Test_FaceUVtoXYZ() { // Check that each face appears exactly once. S2Point sum = S2Point.Empty; for (int face = 0; face < 6; ++face) { S2Point center = S2.FaceUVtoXYZ(face, 0, 0); Assert.Equal(S2.GetNorm(face), center); Assert.Equal(1, Math.Abs(center[center.LargestAbsComponent()])); sum += center.Fabs(); } Assert.Equal(sum, new S2Point(2, 2, 2)); // Check that each face has a right-handed coordinate system. for (int face = 0; face < 6; ++face) { Assert.Equal(1, S2.GetUAxis(face).CrossProd(S2.GetVAxis(face)) .DotProd(S2.FaceUVtoXYZ(face, 0, 0))); } // Check that the Hilbert curves on each face combine to form a // continuous curve over the entire cube. for (int face = 0; face < 6; ++face) { // The Hilbert curve on each face starts at (-1,-1) and terminates // at either (1,-1) (if axes not swapped) or (-1,1) (if swapped). int sign = ((face & S2.kSwapMask) != 0) ? -1 : 1; Assert.Equal(S2.FaceUVtoXYZ(face, sign, -sign), S2.FaceUVtoXYZ((face + 1) % 6, -1, -1)); } }
// In order to make it easier to construct tests that encode particular // values, this function duplicates the part of EncodedS2PointVector that // converts an encoded 64-bit value back to an S2Point. private static S2Point EncodedValueToPoint(UInt64 value, int level) { BitsInterleave.DeinterleaveUInt32(value, out var sj, out var tj); int shift = S2.kMaxCellLevel - level; uint si = (((sj << 1) | 1) << shift) & 0x7fffffff; uint ti = (((tj << 1) | 1) << shift) & 0x7fffffff; int face = (int)(((sj << shift) >> 30) | (((tj << (shift + 1)) >> 29) & 4)); return(S2.FaceUVtoXYZ(face, S2.STtoUV(S2.SiTitoST(si)), S2.STtoUV(S2.SiTitoST(ti))).Normalize()); }
private static S2Shape NewPaddedCell(S2CellId id, double padding_uv) { var ij = new int[2]; var face = id.ToFaceIJOrientation(out ij[0], out ij[1], out _, false); var uv = S2CellId.IJLevelToBoundUV(ij, id.Level()).Expanded(padding_uv); var vertices = new S2Point[4]; for (int i = 0; i < 4; ++i) { vertices[i] = S2.FaceUVtoXYZ(face, uv.GetVertex(i)).Normalize(); } return(new S2LaxLoopShape(vertices)); }
public void Test_VisitCells_QueryEdgeOnFaceBoundary() { int kIters = 100; for (int iter = 0; iter < kIters; ++iter) { _logger.WriteLine("Iteration " + iter); // Choose an edge AB such that B is nearly on the edge between two S2 cube // faces, and such that the result of clipping AB to the face that nominally // contains B (according to S2.GetFace) is empty when no padding is used. int a_face, b_face; S2Point a, b; R2Point a_uv, b_uv; do { a_face = S2Testing.Random.Uniform(6); a = S2.FaceUVtoXYZ(a_face, S2Testing.Random.UniformDouble(-1, 1), S2Testing.Random.UniformDouble(-1, 1)).Normalize(); b_face = S2.GetUVWFace(a_face, 0, 1); // Towards positive u-axis var uTmp = 1 - S2Testing.Random.Uniform(2) * 0.5 * S2.DoubleEpsilon; b = S2.FaceUVtoXYZ(b_face, uTmp, S2Testing.Random.UniformDouble(-1, 1)).Normalize(); } while (S2.GetFace(b) != b_face || S2EdgeClipping.ClipToFace(a, b, b_face, out a_uv, out b_uv)); // Verify that the clipping result is non-empty when a padding of // S2EdgeClipping.kFaceClipErrorUVCoord is used instead. Assert.True(S2EdgeClipping.ClipToPaddedFace(a, b, b_face, S2EdgeClipping.kFaceClipErrorUVCoord, out a_uv, out b_uv)); // Create an S2ShapeIndex containing a single edge BC, where C is on the // same S2 cube face as B (which is different than the face containing A). S2Point c = S2.FaceUVtoXYZ(b_face, S2Testing.Random.UniformDouble(-1, 1), S2Testing.Random.UniformDouble(-1, 1)).Normalize(); MutableS2ShapeIndex index = new(); index.Add(new S2Polyline.OwningShape( new S2Polyline(new S2Point[] { b, c }))); // Check that the intersection between AB and BC is detected when the face // containing BC is specified as a root cell. (Note that VisitCells() // returns false only if the CellVisitor returns false, and otherwise // returns true.) S2CrossingEdgeQuery query = new(index); S2PaddedCell root = new(S2CellId.FromFace(b_face), 0); Assert.False(query.VisitCells(a, b, root, (S2ShapeIndexCell x) => false)); } }
public void Test_GetCrossingCandidates_PerturbedCubeEdges() { List <(S2Point, S2Point)> edges = new(); for (int iter = 0; iter < 10; ++iter) { int face = S2Testing.Random.Uniform(6); double scale = Math.Pow(S2.DoubleError, S2Testing.Random.RandDouble()); R2Point uv = new(2 * S2Testing.Random.Uniform(2) - 1, 2 * S2Testing.Random.Uniform(2) - 1); // vertex S2Point a0 = S2.FaceUVtoXYZ(face, scale * uv); S2Point b0 = a0 - 2 * S2.GetNorm(face); // TODO(ericv): This test is currently slow because *every* crossing test // needs to invoke S2Pred.ExpensiveSign(). GetPerturbedSubEdges(a0, b0, 30, edges); TestAllCrossings(edges); } }
public void Test_UVNorms() { // Check that GetUNorm and GetVNorm compute right-handed normals for // an edge in the increasing U or V direction. for (int face = 0; face < 6; ++face) { for (double x = -1; x <= 1; x += 1 / 1024.0) { Assert2.DoubleEqual(S2.FaceUVtoXYZ(face, x, -1) .CrossProd(S2.FaceUVtoXYZ(face, x, 1)) .Angle(S2.GetUNorm(face, x)), 0); Assert2.DoubleEqual(S2.FaceUVtoXYZ(face, -1, x) .CrossProd(S2.FaceUVtoXYZ(face, 1, x)) .Angle(S2.GetVNorm(face, x)), 0); } } }
public void Test_UVWAxis() { for (int face = 0; face < 6; ++face) { // Check that axes are consistent with FaceUVtoXYZ. Assert.Equal(S2.FaceUVtoXYZ(face, 1, 0) - S2.FaceUVtoXYZ(face, 0, 0), S2.GetUAxis(face)); Assert.Equal(S2.FaceUVtoXYZ(face, 0, 1) - S2.FaceUVtoXYZ(face, 0, 0), S2.GetVAxis(face)); Assert.Equal(S2.FaceUVtoXYZ(face, 0, 0), S2.GetNorm(face)); // Check that every face coordinate frame is right-handed. Assert.Equal(1, S2.GetUAxis(face).CrossProd(S2.GetVAxis(face)) .DotProd(S2.GetNorm(face))); // Check that GetUVWAxis is consistent with GetUAxis, GetVAxis, GetNorm. Assert.Equal(S2.GetUAxis(face), S2.GetUVWAxis(face, 0)); Assert.Equal(S2.GetVAxis(face), S2.GetUVWAxis(face, 1)); Assert.Equal(S2.GetNorm(face), S2.GetUVWAxis(face, 2)); } }
public void Test_S2_FaceClipping() { // Start with a few simple cases. // An edge that is entirely contained within one cube face: TestFaceClippingEdgePair(new S2Point(1, -0.5, -0.5), new S2Point(1, 0.5, 0.5)); // An edge that crosses one cube edge: TestFaceClippingEdgePair(new S2Point(1, 0, 0), new S2Point(0, 1, 0)); // An edge that crosses two opposite edges of face 0: TestFaceClippingEdgePair(new S2Point(0.75, 0, -1), new S2Point(0.75, 0, 1)); // An edge that crosses two adjacent edges of face 2: TestFaceClippingEdgePair(new S2Point(1, 0, 0.75), new S2Point(0, 1, 0.75)); // An edge that crosses three cube edges (four faces): TestFaceClippingEdgePair(new S2Point(1, 0.9, 0.95), new S2Point(-1, 0.95, 0.9)); // Comprehensively test edges that are difficult to handle, especially those // that nearly follow one of the 12 cube edges. R2Rect biunit = new(new R1Interval(-1, 1), new R1Interval(-1, 1)); int kIters = 1000; // Test passes with 1e6 iterations for (int iter = 0; iter < kIters; ++iter) { _logger.WriteLine($"Iteration {iter}"); // Choose two adjacent cube corners P and Q. int face = S2Testing.Random.Uniform(6); int i = S2Testing.Random.Uniform(4); int j = (i + 1) & 3; S2Point p = S2.FaceUVtoXYZ(face, biunit.GetVertex(i)); S2Point q = S2.FaceUVtoXYZ(face, biunit.GetVertex(j)); // Now choose two points that are nearly on the edge PQ, preferring points // that are near cube corners, face midpoints, or edge midpoints. S2Point a = PerturbedCornerOrMidpoint(p, q); S2Point b = PerturbedCornerOrMidpoint(p, q); TestFaceClipping(a, b); } }
private static S2Point FacePiQitoXYZ(int face, int pi, int qi, int level) { return(S2.FaceUVtoXYZ(face, S2.STtoUV(PiQitoST(pi, level)), S2.STtoUV(PiQitoST(qi, level))).Normalize()); }
private void TestFaceClipping(S2Point a_raw, S2Point b_raw) { S2Point a = a_raw.Normalize(); S2Point b = b_raw.Normalize(); // First we test GetFaceSegments. FaceSegmentVector segments = new(); GetFaceSegments(a, b, segments); int n = segments.Count; Assert.True(n >= 1); var msg = new StringBuilder($"\nA={a_raw}\nB={b_raw}\nN={S2.RobustCrossProd(a, b)}\nSegments:\n"); int i1 = 0; foreach (var s in segments) { msg.AppendLine($"{i1++}: face={s.face}, a={s.a}, b={s.b}"); } _logger.WriteLine(msg.ToString()); R2Rect biunit = new(new R1Interval(-1, 1), new R1Interval(-1, 1)); var kErrorRadians = kFaceClipErrorRadians; // The first and last vertices should approximately equal A and B. Assert.True(a.Angle(S2.FaceUVtoXYZ(segments[0].face, segments[0].a)) <= kErrorRadians); Assert.True(b.Angle(S2.FaceUVtoXYZ(segments[n - 1].face, segments[n - 1].b)) <= kErrorRadians); S2Point norm = S2.RobustCrossProd(a, b).Normalize(); S2Point a_tangent = norm.CrossProd(a); S2Point b_tangent = b.CrossProd(norm); for (int i = 0; i < n; ++i) { // Vertices may not protrude outside the biunit square. Assert.True(biunit.Contains(segments[i].a)); Assert.True(biunit.Contains(segments[i].b)); if (i == 0) { continue; } // The two representations of each interior vertex (on adjacent faces) // must correspond to exactly the same S2Point. Assert.NotEqual(segments[i - 1].face, segments[i].face); Assert.Equal(S2.FaceUVtoXYZ(segments[i - 1].face, segments[i - 1].b), S2.FaceUVtoXYZ(segments[i].face, segments[i].a)); // Interior vertices should be in the plane containing A and B, and should // be contained in the wedge of angles between A and B (i.e., the dot // products with a_tangent and b_tangent should be non-negative). S2Point p = S2.FaceUVtoXYZ(segments[i].face, segments[i].a).Normalize(); Assert.True(Math.Abs(p.DotProd(norm)) <= kErrorRadians); Assert.True(p.DotProd(a_tangent) >= -kErrorRadians); Assert.True(p.DotProd(b_tangent) >= -kErrorRadians); } // Now we test ClipToPaddedFace (sometimes with a padding of zero). We do // this by defining an (x,y) coordinate system for the plane containing AB, // and converting points along the great circle AB to angles in the range // [-Pi, Pi]. We then accumulate the angle intervals spanned by each // clipped edge; the union over all 6 faces should approximately equal the // interval covered by the original edge. double padding = S2Testing.Random.OneIn(10) ? 0.0 : 1e-10 * Math.Pow(1e-5, S2Testing.Random.RandDouble()); S2Point x_axis = a, y_axis = a_tangent; S1Interval expected_angles = new(0, a.Angle(b)); S1Interval max_angles = expected_angles.Expanded(kErrorRadians); S1Interval actual_angles = new(); for (int face = 0; face < 6; ++face) { if (ClipToPaddedFace(a, b, face, padding, out var a_uv, out var b_uv)) { S2Point a_clip = S2.FaceUVtoXYZ(face, a_uv).Normalize(); S2Point b_clip = S2.FaceUVtoXYZ(face, b_uv).Normalize(); Assert.True(Math.Abs(a_clip.DotProd(norm)) <= kErrorRadians); Assert.True(Math.Abs(b_clip.DotProd(norm)) <= kErrorRadians); if (a_clip.Angle(a) > kErrorRadians) { Assert2.DoubleEqual(1 + padding, Math.Max(Math.Abs(a_uv[0]), Math.Abs(a_uv[1]))); } if (b_clip.Angle(b) > kErrorRadians) { Assert2.DoubleEqual(1 + padding, Math.Max(Math.Abs(b_uv[0]), Math.Abs(b_uv[1]))); } double a_angle = Math.Atan2(a_clip.DotProd(y_axis), a_clip.DotProd(x_axis)); double b_angle = Math.Atan2(b_clip.DotProd(y_axis), b_clip.DotProd(x_axis)); // Rounding errors may cause b_angle to be slightly less than a_angle. // We handle this by constructing the interval with FromPointPair(), // which is okay since the interval length is much less than Math.PI. S1Interval face_angles = S1Interval.FromPointPair(a_angle, b_angle); Assert.True(max_angles.Contains(face_angles)); actual_angles = actual_angles.Union(face_angles); } } Assert.True(actual_angles.Expanded(kErrorRadians).Contains(expected_angles)); }
public void Test_S2Cap_S2CellMethods() { // For each cube face, we construct some cells on // that face and some caps whose positions are relative to that face, // and then check for the expected intersection/containment results. for (var face = 0; face < 6; ++face) { // The cell consisting of the entire face. S2Cell root_cell = S2Cell.FromFace(face); // A leaf cell at the midpoint of the v=1 edge. S2Cell edge_cell = new(S2.FaceUVtoXYZ(face, 0, 1 - kEps)); // A leaf cell at the u=1, v=1 corner. S2Cell corner_cell = new(S2.FaceUVtoXYZ(face, 1 - kEps, 1 - kEps)); // Quick check for full and empty caps. Assert.True(S2Cap.Full.Contains(root_cell)); Assert.False(S2Cap.Empty.MayIntersect(root_cell)); // Check intersections with the bounding caps of the leaf cells that are // adjacent to 'corner_cell' along the Hilbert curve. Because this corner // is at (u=1,v=1), the curve stays locally within the same cube face. S2CellId first = corner_cell.Id.Advance(-3); S2CellId last = corner_cell.Id.Advance(4); for (S2CellId id = first; id < last; id = id.Next()) { S2Cell cell = new(id); Assert.Equal(id == corner_cell.Id, cell.GetCapBound().Contains(corner_cell)); Assert.Equal(id.Parent().Contains(corner_cell.Id), cell.GetCapBound().MayIntersect(corner_cell)); } var anti_face = (face + 3) % 6; // Opposite face. for (var cap_face = 0; cap_face < 6; ++cap_face) { // A cap that barely contains all of 'cap_face'. S2Point center = S2.GetNorm(cap_face); S2Cap covering = new(center, S1Angle.FromRadians(kFaceRadius + kEps)); Assert.Equal(cap_face == face, covering.Contains(root_cell)); Assert.Equal(cap_face != anti_face, covering.MayIntersect(root_cell)); Assert.Equal(center.DotProd(edge_cell.Center()) > 0.1, covering.Contains(edge_cell)); Assert.Equal(covering.MayIntersect(edge_cell), covering.Contains(edge_cell)); Assert.Equal(cap_face == face, covering.Contains(corner_cell)); Assert.Equal(center.DotProd(corner_cell.Center()) > 0, covering.MayIntersect(corner_cell)); // A cap that barely intersects the edges of 'cap_face'. S2Cap bulging = new(center, S1Angle.FromRadians(S2.M_PI_4 + kEps)); Assert.False(bulging.Contains(root_cell)); Assert.Equal(cap_face != anti_face, bulging.MayIntersect(root_cell)); Assert.Equal(cap_face == face, bulging.Contains(edge_cell)); Assert.Equal(center.DotProd(edge_cell.Center()) > 0.1, bulging.MayIntersect(edge_cell)); Assert.False(bulging.Contains(corner_cell)); Assert.False(bulging.MayIntersect(corner_cell)); // A singleton cap. S2Cap singleton = new(center, S1Angle.Zero); Assert.Equal(cap_face == face, singleton.MayIntersect(root_cell)); Assert.False(singleton.MayIntersect(edge_cell)); Assert.False(singleton.MayIntersect(corner_cell)); } } }