use polyclip::*;
use proptest::prelude::*;
fn both<T>(f: impl Fn() -> T) -> (T, T) {
set_offset_reference(true);
set_always_monolithic(true);
let a = f();
set_offset_reference(false);
set_always_monolithic(false);
(a, f())
}
fn join() -> impl Strategy<Value = Join> {
prop_oneof![
Just(Join::Round),
Just(Join::Bevel),
Just(Join::Square),
(1.0f64..4.0).prop_map(|limit| Join::Miter { limit })
]
}
fn side() -> impl Strategy<Value = Side> {
prop_oneof![Just(Side::Outside), Just(Side::Inside), Just(Side::Nearest)]
}
fn cap() -> impl Strategy<Value = EndCap> {
prop_oneof![
Just(EndCap::Round),
Just(EndCap::Square),
Just(EndCap::Butt),
Just(EndCap::Joined)
]
}
fn ring(range: i64) -> impl Strategy<Value = Vec<Point>> {
prop_oneof![
prop::collection::vec((-range..=range, -range..=range), 3..10)
.prop_map(|v| v.into_iter().map(Point::from).collect()),
(3usize..40, 1i64..=range, any::<bool>(), -range..=range).prop_map(|(n, r, rev, cx)| {
let mut v: Vec<Point> = (0..n)
.map(|k| {
let a = k as f64 / n as f64 * core::f64::consts::TAU;
Point::new(
cx + (r as f64 * a.cos()).round() as i64,
(r as f64 * a.sin()).round() as i64,
)
})
.collect();
if rev {
v.reverse();
}
v
}),
]
}
fn tagged(range: i64) -> impl Strategy<Value = Vec<TaggedRing>> {
prop::collection::vec(
(ring(range), any::<u64>()).prop_map(|(points, seed)| {
let tags = (0..points.len() as u64)
.map(|i| (seed >> (i % 60)) & 3)
.collect();
TaggedRing { points, tags }
}),
1..4,
)
}
fn plain(rs: &[TaggedRing]) -> Vec<Ring> {
rs.iter().map(|r| Ring(r.points.clone())).collect()
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1500))]
#[test]
fn offset_matches_reference(rs in tagged(5000), d in -2000i64..2000, j in join(), s in side(),
t in 1i64..40) {
let tol = ArcTol::new(t, s);
let p = plain(&rs);
let (a, b) = both(|| offset(&p, d, j, tol));
prop_assert_eq!(a, b);
let (a, b) = both(|| offset(&p[0], d, j, tol));
prop_assert_eq!(a, b);
let (a, b) = both(|| offset_tagged(&rs, d, j, tol, 99));
prop_assert_eq!(a, b);
let (a, b) = both(|| offset_tagged(&rs[0], d, j, tol, 99));
prop_assert_eq!(a, b);
}
#[test]
fn canonical_and_holes_match_reference(rs in tagged(5000), d in -2000i64..2000, j in join(),
s in side()) {
let tol = ArcTol::new(7, s);
let frame = Ring::from([(-6000, -6000), (6000, -6000), (6000, 6000), (-6000, 6000)]);
let holed = boolean(Op::Difference, &frame, &plain(&rs), FillRule::NonZero).unwrap();
let (a, b) = both(|| offset(&holed, d, j, tol));
prop_assert_eq!(a, b);
let tree = Boolean::new().subject(&rs, FillRule::NonZero).execute_tree().unwrap();
let (a, b) = both(|| offset_tagged(&tree, d, j, tol, 3));
prop_assert_eq!(a, b);
let (a, b) = both(|| opening(&holed, d.abs(), tol));
prop_assert_eq!(a, b);
let (a, b) = both(|| closing(&holed, d.abs(), tol));
prop_assert_eq!(a, b);
}
#[test]
fn paths_match_reference(rs in tagged(5000), d in 0i64..1500, j in join(), c in cap(),
s in side()) {
let tol = ArcTol::new(5, s);
let paths: Vec<TaggedPath> = rs
.iter()
.map(|r| TaggedPath { points: r.points.clone(), tags: r.tags.clone() })
.collect();
let (a, b) = both(|| offset_paths_tagged(&paths, d, j, c, tol, 8));
prop_assert_eq!(a, b);
let (a, b) = both(|| offset_paths_tagged(&paths[0], d, j, c, tol, 8));
prop_assert_eq!(a, b);
let track = Path(paths[0].points[..2].to_vec());
let (a, b) = both(|| offset_paths(&track, d, j, c, tol));
prop_assert_eq!(a, b);
}
#[test]
fn small_coordinates_match_reference(rs in tagged(12), d in -8i64..8, j in join(),
s in side(), c in cap()) {
let tol = ArcTol::new(1, s);
let p = plain(&rs);
let (a, b) = both(|| offset(&p[0], d, j, tol));
prop_assert_eq!(a, b);
let (a, b) = both(|| offset_tagged(&rs, d, j, tol, 1));
prop_assert_eq!(a, b);
let path = Path(p[0].0.clone());
let (a, b) = both(|| offset_paths(&path, d.abs(), j, c, tol));
prop_assert_eq!(a, b);
}
}
#[test]
fn large_inputs_match_reference() {
let tol = ArcTol::new(1000, Side::Outside);
let circle = |cx: i64, r: f64, n: usize| -> Ring {
(0..n)
.map(|k| {
let a = k as f64 / n as f64 * core::f64::consts::TAU;
Point::new(
cx + (r * a.cos()).round() as i64,
(r * a.sin()).round() as i64,
)
})
.collect()
};
let big = circle(0, 50_000_000.0, 10_000);
for d in [100_000, -100_000, 2] {
for j in [Join::Round, Join::Miter { limit: 2.0 }, Join::Square] {
let (a, b) = both(|| offset(&big, d, j, tol).unwrap());
assert_eq!(a, b);
}
}
let many: Vec<Ring> = (0..3000)
.map(|k| circle(k * 3_000_000, 1_000_000.0 + (k % 7) as f64 * 1e5, 24))
.collect();
let set = union_all(&many, FillRule::NonZero).unwrap();
for d in [400_000, -300_000] {
let (a, b) = both(|| offset(&set, d, Join::Round, tol).unwrap());
assert_eq!(a, b);
}
}