use itertools::Itertools;
use std::borrow::Borrow;
use std::collections::HashMap;
pub trait SpriteMap<E> {
fn map(&self, e: E) -> u32;
}
impl<E, F> SpriteMap<E> for F
where
F: Fn(E) -> u32,
{
fn map(&self, e: E) -> u32 {
self(e)
}
}
#[derive(Clone, Debug)]
pub struct Sprite {
id: usize,
pixels: Box<[u8]>,
}
pub struct SpriteTexture {
width: usize,
height: usize,
sprite_width: usize,
sprite_height: usize,
pixels: Box<[u8]>,
id_map: HashMap<usize, usize>,
}
impl SpriteTexture {
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn sprite_width(&self) -> usize {
self.sprite_width
}
pub fn sprite_height(&self) -> usize {
self.sprite_height
}
}
impl<'a> SpriteTexture {
pub fn new_from_pixels(
pixels: &[u8],
width: usize,
height: usize,
sprite_width: usize,
sprite_height: usize,
sprite_count: usize,
) -> Result<SpriteTexture, String> {
if width % sprite_width != 0 {
return Err(String::from("Sprite width must divide image width"));
}
if height % sprite_height != 0 {
return Err(String::from("Sprite height must divide image height"));
}
if sprite_count > (width / sprite_width) * (height / sprite_height) {
return Err(String::from(
"Too many sprites for specified image dimensions",
));
}
Ok(SpriteTexture {
width: width,
height: height,
sprite_width: sprite_width,
sprite_height: sprite_height,
pixels: Box::from(pixels),
id_map: HashMap::new(), })
}
pub fn pixels(&'a self) -> &'a [u8] {
self.pixels.borrow()
}
pub fn id_map(&'a self) -> &'a HashMap<usize, usize> {
&self.id_map
}
}
pub trait SpriteCollection {
type Iter: Iterator<Item = Sprite>;
fn dimensions(&self) -> (u32, u32);
fn size(&self) -> usize;
fn iter(&self) -> Self::Iter;
fn get(&self, sprite: usize) -> Option<Sprite>;
fn generate_sprite_texture(&self) -> SpriteTexture {
let sprites_wide = (self.size() as f32).sqrt().ceil() as usize;
let sprites_high = ((self.size() as f32) / sprites_wide as f32).ceil() as usize;
let sprite_width = self.dimensions().0;
let sprite_height = self.dimensions().1;
let texture_width = sprites_wide * sprite_width as usize;
let texture_height = sprites_high * sprite_height as usize;
assert!(texture_width <= 8192 && texture_height <= 8192);
let mut pixels = Vec::<u8>::with_capacity(texture_width * texture_height);
let mut id_map = HashMap::<usize, usize>::with_capacity(self.size());
let mut i = 0;
for chunk in &self.iter().chunks(sprites_wide) {
let sprite_row: Vec<Sprite> = chunk.collect();
for s in sprite_row.iter() {
id_map.insert(s.id, i);
i += 1;
}
for y in 0..sprite_height {
for s in sprite_row.iter() {
for x in 0..sprite_width {
pixels.push(s.pixels[(y * sprite_width + x) as usize]);
}
}
if sprite_row.len() < sprites_wide {
pixels.extend(vec![
0;
(sprites_wide - sprite_row.len())
* sprite_width as usize
]);
}
}
}
SpriteTexture {
width: texture_width,
height: texture_height,
sprite_width: self.dimensions().0 as usize,
sprite_height: self.dimensions().1 as usize,
pixels: pixels.into_boxed_slice(),
id_map: id_map,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use hamcrest::*;
use std;
struct TestSpriteCollection {
sprites: Box<[Sprite]>,
sprite_width: usize,
sprite_height: usize,
}
impl SpriteCollection for TestSpriteCollection {
type Iter = std::vec::IntoIter<Sprite>;
fn dimensions(&self) -> (u32, u32) {
(self.sprite_width as u32, self.sprite_height as u32)
}
fn size(&self) -> usize {
self.sprites.len()
}
fn get(&self, _sprite: usize) -> Option<Sprite> {
unimplemented!();
}
fn iter(&self) -> Self::Iter {
let v: Vec<Sprite> = self.sprites.iter().cloned().collect();
return v.into_iter();
}
}
#[test]
fn sprite_texture_basic() {
let sprites = vec![
#[cfg_attr(rustfmt, rustfmt_skip)]
Sprite{
id: 0,
pixels: Box::new([0, 1, 1, 0,
1, 0, 0, 1,
0, 0, 1, 1,
0, 0, 0, 1]),
},
#[cfg_attr(rustfmt, rustfmt_skip)]
Sprite{
id: 1,
pixels: Box::new([1, 1, 1, 1,
1, 1, 1, 0,
1, 1, 0, 0,
1, 0, 0, 0]),
},
];
let collection = TestSpriteCollection {
sprites: sprites.into_boxed_slice(),
sprite_width: 4,
sprite_height: 4,
};
let texture = collection.generate_sprite_texture();
assert_that!(texture.width, is(equal_to(8)));
assert_that!(texture.height, is(equal_to(4)));
assert_that!(texture.sprite_width, is(equal_to(4)));
assert_that!(texture.sprite_height, is(equal_to(4)));
let expected_texture: Vec<u8> = {
#[cfg_attr(rustfmt, rustfmt_skip)]
vec![0, 1, 1, 0, 1, 1, 1, 1,
1, 0, 0, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
]
};
assert_that!(
texture.pixels,
is(equal_to(expected_texture.into_boxed_slice()))
);
}
#[test]
fn sprite_texture_uneven() {
let sprites = vec![
#[cfg_attr(rustfmt, rustfmt_skip)]
Sprite{
id: 0,
pixels: Box::new([0, 0, 0, 0,
0, 0, 0, 1,
0, 0, 1, 1,
0, 1, 1, 1]),
},
#[cfg_attr(rustfmt, rustfmt_skip)]
Sprite{
id: 1,
pixels: Box::new([1, 1, 1, 1,
1, 1, 1, 0,
1, 1, 0, 0,
1, 0, 0, 0]),
},
#[cfg_attr(rustfmt, rustfmt_skip)]
Sprite{
id: 2,
pixels: Box::new([1, 1, 1, 1,
0, 1, 1, 0,
0, 1, 1, 0,
1, 1, 1, 1]),
},
];
let collection = TestSpriteCollection {
sprites: sprites.into_boxed_slice(),
sprite_width: 4,
sprite_height: 4,
};
let texture = collection.generate_sprite_texture();
assert_that!(texture.width, is(equal_to(8)));
assert_that!(texture.height, is(equal_to(8)));
assert_that!(texture.sprite_width, is(equal_to(4)));
assert_that!(texture.sprite_height, is(equal_to(4)));
let expected_texture: Vec<u8> = {
#[cfg_attr(rustfmt, rustfmt_skip)]
vec![0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 0, 0, 0,
1, 1, 1, 1, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 0, 0,
]
};
assert_that!(
texture.pixels,
is(equal_to(expected_texture.into_boxed_slice()))
);
}
#[test]
fn sprite_texture_non_square() {
let sprites = vec![
#[cfg_attr(rustfmt, rustfmt_skip)]
Sprite{
id: 0,
pixels: Box::new([1, 0, 1,
0, 1, 0,
0, 1, 0,
1, 0, 1]),
},
#[cfg_attr(rustfmt, rustfmt_skip)]
Sprite{
id: 1,
pixels: Box::new([1, 1, 1,
1, 1, 0,
1, 1, 0,
1, 0, 0]),
},
#[cfg_attr(rustfmt, rustfmt_skip)]
Sprite{
id: 2,
pixels: Box::new([1, 1, 1,
0, 1, 0,
0, 1, 0,
1, 1, 1]),
},
];
let collection = TestSpriteCollection {
sprites: sprites.into_boxed_slice(),
sprite_width: 3,
sprite_height: 4,
};
let texture = collection.generate_sprite_texture();
assert_that!(texture.width, is(equal_to(6)));
assert_that!(texture.height, is(equal_to(8)));
assert_that!(texture.sprite_width, is(equal_to(3)));
assert_that!(texture.sprite_height, is(equal_to(4)));
let expected_texture: Vec<u8> = {
#[cfg_attr(rustfmt, rustfmt_skip)]
vec![1, 0, 1, 1, 1, 1,
0, 1, 0, 1, 1, 0,
0, 1, 0, 1, 1, 0,
1, 0, 1, 1, 0, 0,
1, 1, 1, 0, 0, 0,
0, 1, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0,
1, 1, 1, 0, 0, 0,
]
};
assert_that!(
texture.pixels,
is(equal_to(expected_texture.into_boxed_slice()))
);
}
}