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)); } }