diff -r 8e74d4eb89f5 -r 16024046d458 rust/lib-hedgewars-engine/src/render/atlas.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/lib-hedgewars-engine/src/render/atlas.rs Sat Mar 23 03:44:11 2019 +0300 @@ -0,0 +1,163 @@ +use integral_geometry::{Rect, Size}; +use std::cmp::{max, min, Ordering}; + +pub struct Atlas { + size: Size, + free_rects: Vec, + used_rects: Vec, +} + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct Fit { + short_size: u32, + long_size: u32, +} + +impl Fit { + fn new() -> Self { + Self { + short_size: u32::max_value(), + long_size: u32::max_value(), + } + } + + fn measure(container: Size, size: Size) -> Option { + if container.contains(size) { + let x_leftover = container.width - size.width; + let y_leftover = container.height - size.height; + Some(Self { + short_size: min(x_leftover, y_leftover) as u32, + long_size: max(x_leftover, y_leftover) as u32, + }) + } else { + None + } + } +} + +fn split_rect(free_rect: Rect, rect: Rect) -> Vec { + let mut result = vec![]; + if free_rect.intersects(&rect) { + if rect.left() > free_rect.left() { + let trim = free_rect.right() - rect.left() + 1; + result.push(free_rect.with_margins(0, -trim, 0, 0)) + } + if rect.right() < free_rect.right() { + let trim = rect.right() - free_rect.left() + 1; + result.push(free_rect.with_margins(-trim, 0, 0, 0)) + } + if rect.top() > free_rect.top() { + let trim = free_rect.bottom() - rect.top() + 1; + result.push(free_rect.with_margins(0, 0, 0, -trim)); + } + if rect.bottom() < free_rect.bottom() { + let trim = rect.bottom() - free_rect.top() + 1; + result.push(free_rect.with_margins(0, 0, -trim, 0)); + } + } + result +} + +impl Atlas { + pub fn new(size: Size) -> Self { + Self { + size, + free_rects: vec![Rect::at_origin(size)], + used_rects: vec![], + } + } + + fn find_position(&self, size: Size) -> Option<(Rect, Fit)> { + let mut best_rect = Rect::EMPTY; + let mut best_fit = Fit::new(); + + for rect in &self.free_rects { + if let Some(fit) = Fit::measure(rect.size(), size) { + if fit < best_fit { + best_fit = fit; + best_rect = Rect::from_size(rect.top_left(), size); + } + } + + if let Some(fit) = Fit::measure(rect.size(), size.transpose()) { + if fit < best_fit { + best_fit = fit; + best_rect = Rect::from_size(rect.top_left(), size.transpose()); + } + } + } + + if best_rect == Rect::EMPTY { + None + } else { + Some((best_rect, best_fit)) + } + } + + fn prune(&mut self) { + self.free_rects = self + .free_rects + .iter() + .filter(|inner| { + self.free_rects + .iter() + .all(|outer| outer == *inner || !outer.contains_rect(inner)) + }) + .cloned() + .collect(); + } + + pub fn insert_adaptive(&mut self, size: Size) -> Option { + let (rect, fit) = self.find_position(size)?; + + let mut rects_to_process = self.free_rects.len(); + let mut i = 0; + + while i < rects_to_process { + let rects = split_rect(self.free_rects[i], rect); + if !rects.is_empty() { + self.free_rects.remove(i); + self.free_rects.extend(rects); + rects_to_process -= 1 + } else { + i += 1; + } + } + + self.used_rects.push(rect); + self.prune(); + Some(rect) + } + + pub fn insert_set(&mut self, sizes: Iter) -> Vec + where + Iter: Iterator, + { + unimplemented!() + } + + pub fn reset(&mut self) { + self.free_rects.clear(); + self.used_rects.clear(); + self.free_rects.push(Rect::at_origin(self.size)); + } +} + +#[cfg(test)] +mod tests { + use super::Atlas; + use integral_geometry::{Rect, Size}; + + #[test] + fn insert() { + let atlas_size = Size::square(16); + let mut atlas = Atlas::new(atlas_size); + + assert_eq!(None, atlas.insert_adaptive(Size::square(20))); + + let rect_size = Size::new(11, 3); + let rect = atlas.insert_adaptive(rect_size).unwrap(); + assert_eq!(rect, Rect::at_origin(rect_size)); + assert_eq!(2, atlas.free_rects.len()); + } +}