use std::panic::{catch_unwind, AssertUnwindSafe};
use tess2_rust::{ElementType, Tessellator, WindingRule};
const RULES: &[WindingRule] = &[
WindingRule::Odd,
WindingRule::NonZero,
WindingRule::Positive,
WindingRule::Negative,
WindingRule::AbsGeqTwo,
];
fn try_tessellate(contours: &[Vec<f64>], rule: WindingRule) -> Result<bool, String> {
let mut tess = Tessellator::new();
for c in contours {
tess.add_contour(2, c);
}
let result = catch_unwind(AssertUnwindSafe(|| {
tess.tessellate(rule, ElementType::Polygons, 3, 2, None)
}));
match result {
Ok(ok) => Ok(ok),
Err(payload) => {
let msg = payload
.downcast_ref::<String>()
.cloned()
.or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
.unwrap_or_else(|| "<unknown panic payload>".into());
Err(msg)
}
}
}
fn assert_no_panic(name: &str, contours: &[Vec<f64>]) {
for &rule in RULES {
let result = try_tessellate(contours, rule);
match result {
Ok(_) => {}
Err(msg) => panic!(
"tessellator panicked for shape `{}` with winding rule {:?}: {}",
name, rule, msg
),
}
}
}
#[test]
fn overlapping_rectangles_window_chrome_must_not_panic() {
let r = |x: f64, y: f64, w: f64, h: f64| -> Vec<f64> {
vec![x, y, x + w, y, x + w, y + h, x, y + h]
};
let contours = vec![
r(0.0, 0.0, 200.0, 150.0), r(2.0, 2.0, 196.0, 24.0), r(2.0, 26.0, 196.0, 122.0), r(140.0, 4.0, 18.0, 18.0), r(120.0, 4.0, 18.0, 18.0), r(8.0, 32.0, 60.0, 110.0), r(72.0, 32.0, 124.0, 110.0),
];
assert_no_panic("overlapping window chrome", &contours);
}
#[test]
fn ring_with_inner_hole_must_not_panic() {
let outer = vec![0.0, 0.0, 100.0, 0.0, 100.0, 100.0, 0.0, 100.0];
let inner = vec![30.0, 30.0, 30.0, 70.0, 70.0, 70.0, 70.0, 30.0];
assert_no_panic("ring with hole", &[outer, inner]);
}
#[test]
fn many_concentric_rings_must_not_panic() {
let mut contours = Vec::new();
for i in 0..8 {
let r = (i as f64) * 5.0 + 5.0;
let mut ring = Vec::new();
let n = 24;
for k in 0..n {
let theta = (k as f64) / (n as f64) * std::f64::consts::TAU;
ring.push(50.0 + r * theta.cos());
ring.push(50.0 + r * theta.sin());
}
contours.push(ring);
}
assert_no_panic("concentric rings", &contours);
}
#[test]
fn self_intersecting_pentagram_must_not_panic() {
let mut star = Vec::new();
let n = 5;
let r = 50.0;
for k in 0..n {
let theta = (k as f64) * 2.0 * std::f64::consts::TAU / (n as f64) - std::f64::consts::FRAC_PI_2;
star.push(50.0 + r * theta.cos());
star.push(50.0 + r * theta.sin());
}
assert_no_panic("self-intersecting pentagram", &[star]);
}
#[test]
fn near_collinear_sliver_must_not_panic() {
let contour = vec![
0.0, 0.0, 100.0, 0.0, 100.0, 0.000_000_1, 50.0, 0.000_000_05,
];
assert_no_panic("near-collinear sliver", &[contour]);
}
#[test]
fn jittered_quasi_duplicates_must_not_panic() {
let mut c = Vec::new();
let base = [50.0, 50.0];
let n = 64;
for k in 0..n {
let theta = (k as f64) / (n as f64) * std::f64::consts::TAU;
let r = 30.0 + 1e-9 * (k as f64).sin();
c.push(base[0] + r * theta.cos());
c.push(base[1] + r * theta.sin());
}
assert_no_panic("jittered quasi-duplicates", &[c]);
}
#[test]
fn pseudo_random_polygon_swarm_must_not_panic() {
struct Lcg(u64);
impl Lcg {
fn next_f64(&mut self) -> f64 {
self.0 = self.0.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
(self.0 >> 11) as f64 / (1u64 << 53) as f64
}
}
for seed in 0u64..32 {
let mut rng = Lcg(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15));
let n_contours = 1 + (rng.next_f64() * 5.0) as usize;
let mut contours = Vec::new();
for _ in 0..n_contours {
let n = 3 + (rng.next_f64() * 12.0) as usize;
let cx = rng.next_f64() * 100.0;
let cy = rng.next_f64() * 100.0;
let r = 5.0 + rng.next_f64() * 40.0;
let mut c = Vec::new();
for k in 0..n {
let theta = (k as f64) / (n as f64) * std::f64::consts::TAU
+ rng.next_f64() * 0.2;
let rk = r * (0.5 + rng.next_f64());
c.push(cx + rk * theta.cos());
c.push(cy + rk * theta.sin());
}
contours.push(c);
}
assert_no_panic(&format!("random seed {seed}"), &contours);
}
}