diff --git a/textium/src/packer/mod.rs b/textium/src/packer/mod.rs index 4cd53e3..4600468 100644 --- a/textium/src/packer/mod.rs +++ b/textium/src/packer/mod.rs @@ -1,6 +1,10 @@ +mod skyline; + use ::texture::{Buffer2d, ResizeableBuffer2d}; use ::geometry::Rect; +pub use self::skyline::SkylinePacker; + pub trait Packer { type Buffer: Buffer2d; @@ -18,6 +22,7 @@ pub trait Packer { } pub trait GrowingPacker: Packer { + /// `resize_fn` should describe a strategy for growing the atlas buffer based on the current size. fn pack_resize(&mut self, buf: &O, resize_fn: F) -> Rect where O: Buffer2d::Buffer as Buffer2d>::Pixel>, F: Fn((usize, usize)) -> (usize, usize); diff --git a/textium/src/packer/skyline.rs b/textium/src/packer/skyline.rs new file mode 100644 index 0000000..1da62be --- /dev/null +++ b/textium/src/packer/skyline.rs @@ -0,0 +1,195 @@ +use std::cmp::max; + +use ::texture::Buffer2d; +use ::geometry::Rect; +use super::Packer; + +struct Skyline { + pub x: usize, + pub y: usize, + pub w: usize, +} + +pub struct SkylinePacker where B: Buffer2d { + buf: B, + width: usize, + height: usize, + skylines: Vec, + margin: usize, +} + +impl SkylinePacker where B: Buffer2d { + fn find_skyline(&self, w: usize, h: usize) -> Option<(usize, Rect)> { + let mut min_height = ::std::usize::MAX; + let mut min_width = ::std::usize::MAX; + let mut index = None; + let mut rect: Rect = Rect::new(0, 0, 0, 0); + + // Note: Try to keep the min_height as small as possible + for i in 0..self.skylines.len() { + if let Some(y) = self.can_place(i, w, h) { + if y + h < min_height || + (y + h == min_height && self.skylines[i].w < min_width) { + min_height = y+h; + min_width = self.skylines[i].w; + index = Some(i); + rect.x = self.skylines[i].x; + rect.y = y; + rect.w = w; + rect.h = h; + } + } + } + + if let Some(i) = index { + Some((i, rect)) + } else { + None + } + } + + fn can_place(&self, i: usize, w: usize, h: usize) -> Option { + let x = self.skylines[i].x; + if x + w > self.width { + return None; + } + let mut width_left = w; + let mut i = i; + let mut y = self.skylines[i].y; + loop { + y = max(y, self.skylines[i].y); + if y+h > self.height { + return None; + } + if self.skylines[i].w > width_left { + return Some(y); + } + width_left -= self.skylines[i].w; + i += 1; + if i >= self.skylines.len() { + return None; + } + } + } + + fn split(&mut self, index: usize, rect: &Rect) { + let skyline = Skyline { + x: rect.x, + y: rect.y + rect.h, + w: rect.w + }; + + assert!(skyline.x + skyline.w <= self.width); + assert!(skyline.y <= self.height); + + self.skylines.insert(index, skyline); + let i = index + 1; + while i < self.skylines.len() { + assert!(self.skylines[i-1].x <= self.skylines[i].x); + if self.skylines[i].x < self.skylines[i-1].x + self.skylines[i-1].w { + let shrink = self.skylines[i-1].x + self.skylines[i-1].w - self.skylines[i].x; + if self.skylines[i].w <= shrink { + self.skylines.remove(i); + } else { + self.skylines[i].x += shrink; + self.skylines[i].w -= shrink; + break; + } + } else { + break; + } + } + } + + /// Merge skylines with the same y value. + fn merge(&mut self) { + let mut i = 1; + while i < self.skylines.len() { + if self.skylines[i-1].y == self.skylines[i].y { + self.skylines[i-1].w += self.skylines[i].w; + self.skylines.remove(i); + i -= 1; + } + i += 1; + } + } +} + +impl Packer for SkylinePacker where B: Buffer2d { + type Buffer = B; + + fn dimensions(&self) -> (usize, usize) {(self.width, self.height)} + fn set_dimensions(&mut self, w: usize, h: usize) { + let (old_w, _) = self.dimensions(); + + self.width = w; + self.height = h; + + // create a "ceiling" which fills in the skyline data structure + // with a line at y=0 from the old width to the new width. + self.skylines.push(Skyline { + x: old_w, y: 0, + w: w-old_w, + }); + } + + fn set_margin(&mut self, margin: usize) { + self.margin = margin; + } + + fn new(buf: B) -> SkylinePacker { + let (w, h) = buf.dimensions(); + let mut skylines = Vec::new(); + + // Fill with a single skyline. + skylines.push(Skyline { + x: 0, y: 0, + w: w + }); + + SkylinePacker { + buf: buf, + width: w, height: h, + skylines: skylines, + margin: 0, + } + } + + fn pack(&mut self, buf: &O) -> Option> + where O: Buffer2d + { + let (mut w, mut h) = buf.dimensions(); + w += self.margin; + h += self.margin; + + if let Some((i, mut rect)) = self.find_skyline(w, h) { + if w == rect.w { + self.buf.patch(rect.x, rect.y, buf); + } else { + self.buf.patch_rotated(rect.x, rect.y, buf); + } + + self.split(i, &rect); + self.merge(); + + rect.w -= self.margin; + rect.h -= self.margin; + + Some(rect) + } else { + None + } + } + + fn buf(&self) -> &B { + &self.buf + } + + fn buf_mut(&mut self) -> &mut B { + &mut self.buf + } + + fn into_buf(self) -> B { + self.buf + } +}