Пример #1
0
        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));
            }
        }
Пример #2
0
    // 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());
    }
Пример #3
0
    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));
    }
Пример #4
0
    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));
        }
    }
Пример #5
0
    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);
        }
    }
Пример #6
0
 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);
         }
     }
 }
Пример #7
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));
            }
        }
Пример #8
0
    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);
        }
    }
Пример #9
0
 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());
 }
Пример #10
0
    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));
    }
Пример #11
0
        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));
                }
            }
        }