public void testIntersectionTolerance()
        {
            // We repeatedly construct two edges that cross near a random point "p",
            // and measure the distance from the actual intersection point "x" to the
            // the expected intersection point "p" and also to the edges that cross
            // near "p".
            //
            // Note that getIntersection() does not guarantee that "x" and "p" will be
            // close together (since the intersection point is numerically unstable
            // when the edges cross at a very small angle), but it does guarantee that
            // "x" will be close to both of the edges that cross.
            var maxPointDist = new S1Angle();
            var maxEdgeDist  = new S1Angle();

            for (var i = 0; i < 1000; ++i)
            {
                // We construct two edges AB and CD that intersect near "p". The angle
                // between AB and CD (expressed as a slope) is chosen randomly between
                // 1e-15 and 1.0 such that its logarithm is uniformly distributed. This
                // implies that small values are much more likely to be chosen.
                //
                // Once the slope is chosen, the four points ABCD must be offset from P
                // by at least (1e-15 / slope) so that the points are guaranteed to have
                // the correct circular ordering around P. This is the distance from P
                // at which the two edges are separated by about 1e-15, which is
                // approximately the minimum distance at which we can expect computed
                // points on the two lines to be distinct and have the correct ordering.
                //
                // The actual offset distance from P is chosen randomly in the range
                // [1e-15 / slope, 1.0], again uniformly distributing the logarithm.
                // This ensures that we test both long and very short segments that
                // intersect at both large and very small angles.

                var points = getRandomFrame();
                var p      = points[0];
                var d1     = points[1];
                var d2     = points[2];
                var slope  = Math.Pow(1e-15, rand.NextDouble());
                d2 = d1 + (d2 * slope);
                var a      = S2Point.Normalize(p + (d1 * Math.Pow(1e-15 / slope, rand.NextDouble())));
                var b      = S2Point.Normalize(p - (d1 * Math.Pow(1e-15 / slope, rand.NextDouble())));
                var c      = S2Point.Normalize(p + (d2 * Math.Pow(1e-15 / slope, rand.NextDouble())));
                var d      = S2Point.Normalize(p - (d2 * Math.Pow(1e-15 / slope, rand.NextDouble())));
                var x      = S2EdgeUtil.GetIntersection(a, b, c, d);
                var distAb = S2EdgeUtil.GetDistance(x, a, b);
                var distCd = S2EdgeUtil.GetDistance(x, c, d);

                assertTrue(distAb < S2EdgeUtil.DefaultIntersectionTolerance);
                assertTrue(distCd < S2EdgeUtil.DefaultIntersectionTolerance);

                // test getIntersection() post conditions
                assertTrue(S2.OrderedCcw(a, x, b, S2Point.Normalize(S2.RobustCrossProd(a, b))));
                assertTrue(S2.OrderedCcw(c, x, d, S2Point.Normalize(S2.RobustCrossProd(c, d))));

                maxEdgeDist  = S1Angle.Max(maxEdgeDist, S1Angle.Max(distAb, distCd));
                maxPointDist = S1Angle.Max(maxPointDist, new S1Angle(p, x));
            }
        }
 private void dumpCrossings(S2Loop loop)
 {
     Console.WriteLine("Ortho(v1): " + S2.Ortho(loop.Vertex(1)));
     Console.WriteLine("Contains(kOrigin): {0}\n", loop.Contains(S2.Origin));
     for (var i = 1; i <= loop.NumVertices; ++i)
     {
         var a = S2.Ortho(loop.Vertex(i));
         var b = loop.Vertex(i - 1);
         var c = loop.Vertex(i + 1);
         var o = loop.Vertex(i);
         Console.WriteLine("Vertex {0}: [%.17g, %.17g, %.17g], "
                           + "%d%dR=%d, %d%d%d=%d, R%d%d=%d, inside: %b\n",
                           i,
                           loop.Vertex(i).X,
                           loop.Vertex(i).Y,
                           loop.Vertex(i).Z,
                           i - 1,
                           i,
                           S2.RobustCcw(b, o, a),
                           i + 1,
                           i,
                           i - 1,
                           S2.RobustCcw(c, o, b),
                           i,
                           i + 1,
                           S2.RobustCcw(a, o, c),
                           S2.OrderedCcw(a, b, c, o));
     }
     for (var i = 0; i < loop.NumVertices + 2; ++i)
     {
         var     orig = S2.Origin;
         S2Point dest;
         if (i < loop.NumVertices)
         {
             dest = loop.Vertex(i);
             Console.WriteLine("Origin->{0} crosses:", i);
         }
         else
         {
             dest = new S2Point(0, 0, 1);
             if (i == loop.NumVertices + 1)
             {
                 orig = loop.Vertex(1);
             }
             Console.WriteLine("Case {0}:", i);
         }
         for (var j = 0; j < loop.NumVertices; ++j)
         {
             Console.WriteLine(
                 " " + S2EdgeUtil.EdgeOrVertexCrossing(orig, dest, loop.Vertex(j), loop.Vertex(j + 1)));
         }
         Console.WriteLine();
     }
     for (var i = 0; i <= 2; i += 2)
     {
         Console.WriteLine("Origin->v1 crossing v{0}->v1: ", i);
         var a = S2.Ortho(loop.Vertex(1));
         var b = loop.Vertex(i);
         var c = S2.Origin;
         var o = loop.Vertex(1);
         Console.WriteLine("{0}1R={1}, M1{2}={3}, R1M={4}, crosses: {5}\n",
                           i,
                           S2.RobustCcw(b, o, a),
                           i,
                           S2.RobustCcw(c, o, b),
                           S2.RobustCcw(a, o, c),
                           S2EdgeUtil.EdgeOrVertexCrossing(c, o, b, a));
     }
 }