rust/lib-hedgewars-engine/src/render/atlas.rs
author alfadur
Sat, 23 Mar 2019 03:44:11 +0300
changeset 14717 16024046d458
child 14720 b110cbe52e51
permissions -rw-r--r--
rescue the atlas

use integral_geometry::{Rect, Size};
use std::cmp::{max, min, Ordering};

pub struct Atlas {
    size: Size,
    free_rects: Vec<Rect>,
    used_rects: Vec<Rect>,
}

#[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<Self> {
        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<Rect> {
    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<Rect> {
        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<Iter>(&mut self, sizes: Iter) -> Vec<Rect>
    where
        Iter: Iterator<Item = Size>,
    {
        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());
    }
}