public void Test_S2PolylineSimplifier_Reuse()
        {
            // Check that Init() can be called more than once.
            S1ChordAngle radius = new(S1Angle.FromDegrees(10));
            var          s      = new S2PolylineSimplifier(new S2Point(1, 0, 0));

            Assert.True(s.TargetDisc(new S2Point(1, 1, 0).Normalize(), radius));
            Assert.True(s.TargetDisc(new S2Point(1, 1, 0.1).Normalize(), radius));
            Assert.False(s.Extend(new S2Point(1, 1, 0.4).Normalize()));

            // s.Init(S2Point(0, 1, 0));
            Assert.True(s.TargetDisc(new S2Point(1, 1, 0.3).Normalize(), radius));
            Assert.True(s.TargetDisc(new S2Point(1, 1, 0.2).Normalize(), radius));
            Assert.False(s.Extend(new S2Point(1, 1, 0).Normalize()));
        }
        private static void CheckSimplify(string src, string dst,
                                          string target, string avoid,
                                          bool[] disc_on_left,
                                          double radius_degrees, bool expected_result)
        {
            S1ChordAngle radius = new(S1Angle.FromDegrees(radius_degrees));
            var          s      = new S2PolylineSimplifier(MakePointOrDie(src));

            foreach (S2Point p in ParsePointsOrDie(target))
            {
                s.TargetDisc(p, radius);
            }
            int i = 0;

            foreach (S2Point p in ParsePointsOrDie(avoid))
            {
                s.AvoidDisc(p, radius, disc_on_left[i++]);
            }
            Assert.Equal(expected_result, s.Extend(MakePointOrDie(dst)));
        }
        public void Test_S2PolylineSimplifier_Precision()
        {
            // This is a rough upper bound on both the error in constructing the disc
            // locations (i.e., S2.InterpolateAtDistance, etc), and also on the
            // padding that S2PolylineSimplifier uses to ensure that its results are
            // conservative (i.e., the error calculated by GetSemiwidth).
            S1Angle kMaxError = S1Angle.FromRadians(25 * S2.DoubleEpsilon);

            // We repeatedly generate a random edge.  We then target several discs that
            // barely overlap the edge, and avoid several discs that barely miss the
            // edge.  About half the time, we choose one disc and make it slightly too
            // large or too small so that targeting fails.
            int kIters = 1000;  // Passes with 1 million iterations.

            for (int iter = 0; iter < kIters; ++iter)
            {
                S2Testing.Random.Reset(iter + 1);  // Easier to reproduce a specific case.
                S2Point src        = S2Testing.RandomPoint();
                var     simplifier = new S2PolylineSimplifier(src);
                S2Point dst        = S2.InterpolateAtDistance(
                    S1Angle.FromRadians(S2Testing.Random.RandDouble()),
                    src, S2Testing.RandomPoint());
                S2Point n = S2.RobustCrossProd(src, dst).Normalize();

                // If bad_disc >= 0, then we make targeting fail for that disc.
                int kNumDiscs = 5;
                int bad_disc  = S2Testing.Random.Uniform(2 * kNumDiscs) - kNumDiscs;
                for (int i = 0; i < kNumDiscs; ++i)
                {
                    // The center of the disc projects to a point that is the given fraction
                    // "f" along the edge (src, dst).  If f < 0, the center is located
                    // behind "src" (in order to test this case).
                    double  f       = S2Testing.Random.UniformDouble(-0.5, 1.0);
                    S2Point a       = ((1 - f) * src + f * dst).Normalize();
                    S1Angle r       = S1Angle.FromRadians(S2Testing.Random.RandDouble());
                    bool    on_left = S2Testing.Random.OneIn(2);
                    S2Point x       = S2.InterpolateAtDistance(r, a, on_left ? n : -n);
                    // If the disc is behind "src", adjust its radius so that it just
                    // touches "src" rather than just touching the line through (src, dst).
                    if (f < 0)
                    {
                        r = new S1Angle(src, x);
                    }
                    // We grow the radius slightly if we want to target the disc and shrink
                    // it otherwise, *unless* we want targeting to fail for this disc, in
                    // which case these actions are reversed.
                    bool avoid       = S2Testing.Random.OneIn(2);
                    bool grow_radius = (avoid == (i == bad_disc));
                    var  radius      = new S1ChordAngle(grow_radius ? r + kMaxError : r - kMaxError);
                    if (avoid)
                    {
                        simplifier.AvoidDisc(x, radius, on_left);
                    }
                    else
                    {
                        simplifier.TargetDisc(x, radius);
                    }
                }
                // The result is true iff all the discraints were satisfiable.
                Assert.Equal(bad_disc < 0, simplifier.Extend(dst));
            }
        }