#![deny(missing_docs)]
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DamageRect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
impl DamageRect {
#[inline]
#[must_use]
pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
Self {
x,
y,
width,
height,
}
}
#[inline]
#[must_use]
pub fn right(&self) -> f32 {
self.x + self.width
}
#[inline]
#[must_use]
pub fn bottom(&self) -> f32 {
self.y + self.height
}
#[inline]
#[must_use]
pub fn area(&self) -> f32 {
self.width * self.height
}
#[inline]
#[must_use]
pub fn intersects(&self, other: &DamageRect) -> bool {
self.x < other.right()
&& other.x < self.right()
&& self.y < other.bottom()
&& other.y < self.bottom()
}
#[must_use]
pub fn union(&self, other: &DamageRect) -> DamageRect {
let x = self.x.min(other.x);
let y = self.y.min(other.y);
let right = self.right().max(other.right());
let bottom = self.bottom().max(other.bottom());
DamageRect::new(x, y, right - x, bottom - y)
}
#[inline]
#[must_use]
pub fn contains_point(&self, px: f32, py: f32) -> bool {
px >= self.x && px < self.right() && py >= self.y && py < self.bottom()
}
}
impl fmt::Display for DamageRect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[{:.1}, {:.1}] {:.1}x{:.1}",
self.x, self.y, self.width, self.height
)
}
}
#[derive(Debug, Clone, Default)]
pub struct DamageTracker {
rects: Vec<DamageRect>,
full: bool,
}
impl DamageTracker {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
rects: Vec::new(),
full: false,
}
}
#[inline]
#[must_use]
pub const fn with_full_damage() -> Self {
Self {
rects: Vec::new(),
full: true,
}
}
pub fn add(&mut self, rect: DamageRect) {
if !self.full {
self.rects.push(rect);
}
}
pub fn mark_full(&mut self) {
self.rects.clear();
self.full = true;
}
pub fn clear(&mut self) {
self.rects.clear();
self.full = false;
}
#[inline]
#[must_use]
pub fn is_full(&self) -> bool {
self.full
}
#[inline]
#[must_use]
pub fn has_damage(&self) -> bool {
self.full || !self.rects.is_empty()
}
#[inline]
#[must_use]
pub fn rects(&self) -> &[DamageRect] {
&self.rects
}
#[must_use]
pub fn merged(&self) -> Option<DamageRect> {
if self.full {
return None;
}
self.rects.iter().copied().reduce(|a, b| a.union(&b))
}
#[must_use]
pub fn area_upper_bound(&self) -> f32 {
self.rects.iter().map(DamageRect::area).sum()
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.rects.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.rects.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f32 = 0.001;
fn approx_eq(a: f32, b: f32) -> bool {
(a - b).abs() < EPSILON
}
#[test]
fn new_tracker_has_no_damage() {
let t = DamageTracker::new();
assert!(!t.is_full());
assert!(!t.has_damage());
assert!(t.merged().is_none());
}
#[test]
fn with_full_damage_sets_flag() {
let t = DamageTracker::with_full_damage();
assert!(t.is_full());
assert!(t.has_damage());
assert!(t.merged().is_none()); }
#[test]
fn add_then_merged_returns_union() {
let mut t = DamageTracker::new();
t.add(DamageRect::new(0.0, 0.0, 100.0, 20.0));
t.add(DamageRect::new(50.0, 10.0, 100.0, 30.0));
let merged = t.merged().expect("has merged");
assert!(approx_eq(merged.x, 0.0));
assert!(approx_eq(merged.y, 0.0));
assert!(approx_eq(merged.right(), 150.0));
assert!(approx_eq(merged.bottom(), 40.0));
}
#[test]
fn mark_full_clears_individual_rects() {
let mut t = DamageTracker::new();
t.add(DamageRect::new(0.0, 0.0, 10.0, 10.0));
t.add(DamageRect::new(50.0, 50.0, 10.0, 10.0));
t.mark_full();
assert!(t.is_full());
assert_eq!(t.len(), 0);
}
#[test]
fn add_is_ignored_when_full() {
let mut t = DamageTracker::with_full_damage();
t.add(DamageRect::new(0.0, 0.0, 10.0, 10.0));
assert!(t.is_full());
assert_eq!(t.len(), 0);
}
#[test]
fn clear_resets_both_flag_and_rects() {
let mut t = DamageTracker::with_full_damage();
t.clear();
assert!(!t.is_full());
assert!(!t.has_damage());
}
#[test]
fn area_upper_bound_overcounts_overlap() {
let mut t = DamageTracker::new();
t.add(DamageRect::new(0.0, 0.0, 10.0, 10.0)); t.add(DamageRect::new(5.0, 5.0, 10.0, 10.0)); assert!(approx_eq(t.area_upper_bound(), 200.0));
}
#[test]
fn single_rect_merges_to_itself() {
let mut t = DamageTracker::new();
let r = DamageRect::new(1.0, 2.0, 3.0, 4.0);
t.add(r);
assert_eq!(t.merged(), Some(r));
}
#[test]
fn rect_intersects() {
let a = DamageRect::new(0.0, 0.0, 10.0, 10.0);
let b = DamageRect::new(5.0, 5.0, 10.0, 10.0);
assert!(a.intersects(&b));
assert!(b.intersects(&a));
let c = DamageRect::new(20.0, 20.0, 10.0, 10.0);
assert!(!a.intersects(&c));
let d = DamageRect::new(10.0, 0.0, 10.0, 10.0);
assert!(!a.intersects(&d));
}
#[test]
fn rect_union() {
let a = DamageRect::new(0.0, 0.0, 10.0, 10.0);
let b = DamageRect::new(20.0, 5.0, 10.0, 10.0);
let u = a.union(&b);
assert!(approx_eq(u.x, 0.0));
assert!(approx_eq(u.y, 0.0));
assert!(approx_eq(u.right(), 30.0));
assert!(approx_eq(u.bottom(), 15.0));
}
#[test]
fn contains_point_half_open() {
let r = DamageRect::new(0.0, 0.0, 10.0, 10.0);
assert!(r.contains_point(0.0, 0.0)); assert!(r.contains_point(5.0, 5.0));
assert!(!r.contains_point(10.0, 5.0)); assert!(!r.contains_point(5.0, 10.0)); assert!(!r.contains_point(-1.0, 5.0));
}
}