use egui::Pos2;
pub fn clip_poly_to_rect(tri: &[Pos2; 3], rect: egui::Rect) -> Vec<Pos2> {
let mut poly: Vec<Pos2> = tri.to_vec();
let edges: [(f32, u8); 4] = [
(rect.left(), 0), (rect.right(), 1), (rect.top(), 2), (rect.bottom(), 3), ];
for (bound, which) in edges {
if poly.is_empty() {
break;
}
let inside = |p: &Pos2| match which {
0 => p.x >= bound,
1 => p.x <= bound,
2 => p.y >= bound,
_ => p.y <= bound,
};
let intersect = |a: &Pos2, b: &Pos2| -> Pos2 {
let t = match which {
0 | 1 => (bound - a.x) / (b.x - a.x),
_ => (bound - a.y) / (b.y - a.y),
};
let t = t.clamp(0.0, 1.0);
Pos2::new(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t)
};
let mut out: Vec<Pos2> = Vec::with_capacity(poly.len() + 1);
for i in 0..poly.len() {
let cur = poly[i];
let prev = poly[(i + poly.len() - 1) % poly.len()];
let (cin, pin) = (inside(&cur), inside(&prev));
if cin {
if !pin {
out.push(intersect(&prev, &cur));
}
out.push(cur);
} else if pin {
out.push(intersect(&prev, &cur));
}
}
poly = out;
}
poly
}
pub fn ink_outside_rect(tris: &[[Pos2; 3]], rect: egui::Rect) -> usize {
let eps = 1e-2;
let mut escapes = 0usize;
for tri in tris {
for p in &clip_poly_to_rect(tri, rect) {
let over = (rect.left() - p.x).max(p.x - rect.right()).max(rect.top() - p.y).max(p.y - rect.bottom());
if over > eps {
escapes += 1;
}
}
}
escapes
}
#[cfg(test)]
mod tests {
use super::*;
use egui::pos2;
#[test]
fn clip_confines_every_vertex_to_the_rect() {
let rect = egui::Rect::from_min_max(pos2(0.0, 0.0), pos2(100.0, 100.0));
let sprawl = [pos2(-500.0, 50.0), pos2(50.0, -500.0), pos2(600.0, 600.0)];
let c = clip_poly_to_rect(&sprawl, rect);
assert!(c.len() >= 3, "a crossing face still produces a polygon");
for p in &c {
assert!(p.x >= rect.left() - 1e-3 && p.x <= rect.right() + 1e-3, "x inside");
assert!(p.y >= rect.top() - 1e-3 && p.y <= rect.bottom() + 1e-3, "y inside");
}
let outside = [pos2(200.0, 200.0), pos2(300.0, 200.0), pos2(250.0, 300.0)];
assert!(clip_poly_to_rect(&outside, rect).len() < 3, "fully-outside face dropped");
let within = [pos2(10.0, 10.0), pos2(90.0, 10.0), pos2(50.0, 90.0)];
let cw = clip_poly_to_rect(&within, rect);
assert_eq!(cw.len(), 3, "an inside face is preserved as-is");
}
#[test]
fn ink_outside_rect_is_zero_after_scissor() {
let rect = egui::Rect::from_min_max(pos2(0.0, 0.0), pos2(100.0, 100.0));
let sprawl = [pos2(-9000.0, 40.0), pos2(40.0, -9000.0), pos2(9000.0, 9000.0)];
assert_eq!(ink_outside_rect(&[sprawl], rect), 0, "scissor leaves no ink outside");
let inside = [pos2(10.0, 10.0), pos2(90.0, 10.0), pos2(50.0, 90.0)];
assert_eq!(ink_outside_rect(&[inside], rect), 0, "an inside face never escapes");
}
}