diff options
Diffstat (limited to 'graphics/src/damage.rs')
-rw-r--r-- | graphics/src/damage.rs | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs new file mode 100644 index 00000000..ff8edaf5 --- /dev/null +++ b/graphics/src/damage.rs @@ -0,0 +1,80 @@ +//! Compute the damage between frames. +use crate::core::Rectangle; + +/// Diffs the damage regions given some previous and current primitives. +pub fn diff<T>( + previous: &[T], + current: &[T], + bounds: impl Fn(&T) -> Vec<Rectangle>, + diff: impl Fn(&T, &T) -> Vec<Rectangle>, +) -> Vec<Rectangle> { + let damage = previous.iter().zip(current).flat_map(|(a, b)| diff(a, b)); + + if previous.len() == current.len() { + damage.collect() + } else { + let (smaller, bigger) = if previous.len() < current.len() { + (previous, current) + } else { + (current, previous) + }; + + // Extend damage by the added/removed primitives + damage + .chain(bigger[smaller.len()..].iter().flat_map(bounds)) + .collect() + } +} + +/// Computes the damage regions given some previous and current primitives. +pub fn list<T>( + previous: &[T], + current: &[T], + bounds: impl Fn(&T) -> Vec<Rectangle>, + are_equal: impl Fn(&T, &T) -> bool, +) -> Vec<Rectangle> { + diff(previous, current, &bounds, |a, b| { + if are_equal(a, b) { + vec![] + } else { + bounds(a).into_iter().chain(bounds(b)).collect() + } + }) +} + +/// Groups the given damage regions that are close together inside the given +/// bounds. +pub fn group(mut damage: Vec<Rectangle>, bounds: Rectangle) -> Vec<Rectangle> { + use std::cmp::Ordering; + + const AREA_THRESHOLD: f32 = 20_000.0; + + damage.sort_by(|a, b| { + a.x.partial_cmp(&b.x) + .unwrap_or(Ordering::Equal) + .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal)) + }); + + let mut output = Vec::new(); + let mut scaled = damage + .into_iter() + .filter_map(|region| region.intersection(&bounds)) + .filter(|region| region.width >= 1.0 && region.height >= 1.0); + + if let Some(mut current) = scaled.next() { + for region in scaled { + let union = current.union(®ion); + + if union.area() - current.area() - region.area() <= AREA_THRESHOLD { + current = union; + } else { + output.push(current); + current = region; + } + } + + output.push(current); + } + + output +} |