#![allow(dead_code)]
#[derive(Debug, Clone, Copy)]
pub struct SpriteFrame {
pub id: u32,
pub u_min: f32,
pub v_min: f32,
pub u_max: f32,
pub v_max: f32,
}
#[derive(Debug, Clone)]
pub struct SpriteAtlasConfig {
pub columns: u32,
pub rows: u32,
pub atlas_width: u32,
pub atlas_height: u32,
}
impl Default for SpriteAtlasConfig {
fn default() -> Self {
Self {
columns: 4,
rows: 4,
atlas_width: 1024,
atlas_height: 1024,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct SpriteAtlas {
pub frames: Vec<SpriteFrame>,
pub config: SpriteAtlasConfig,
}
impl SpriteAtlas {
pub fn build(config: SpriteAtlasConfig) -> Self {
let mut frames = Vec::new();
let col_size = 1.0 / config.columns as f32;
let row_size = 1.0 / config.rows as f32;
let total = config.columns * config.rows;
for i in 0..total {
let col = i % config.columns;
let row = i / config.columns;
frames.push(SpriteFrame {
id: i,
u_min: col as f32 * col_size,
v_min: row as f32 * row_size,
u_max: (col + 1) as f32 * col_size,
v_max: (row + 1) as f32 * row_size,
});
}
Self { frames, config }
}
pub fn frame(&self, id: u32) -> Option<&SpriteFrame> {
self.frames.iter().find(|f| f.id == id)
}
pub fn frame_count(&self) -> usize {
self.frames.len()
}
}
pub fn cell_pixel_size(config: &SpriteAtlasConfig) -> (u32, u32) {
(
config.atlas_width / config.columns.max(1),
config.atlas_height / config.rows.max(1),
)
}
pub fn frame_uv_center(frame: &SpriteFrame) -> [f32; 2] {
[
(frame.u_min + frame.u_max) * 0.5,
(frame.v_min + frame.v_max) * 0.5,
]
}
pub fn validate_atlas_uvs(atlas: &SpriteAtlas) -> bool {
atlas.frames.iter().all(|f| {
(0.0..=1.0).contains(&f.u_min)
&& (0.0..=1.0).contains(&f.u_max)
&& (0.0..=1.0).contains(&f.v_min)
&& (0.0..=1.0).contains(&f.v_max)
})
}
#[cfg(test)]
mod tests {
use super::*;
fn default_atlas() -> SpriteAtlas {
SpriteAtlas::build(SpriteAtlasConfig::default())
}
#[test]
fn test_frame_count() {
assert_eq!(default_atlas().frame_count(), 16);
}
#[test]
fn test_frame_zero_exists() {
assert!(default_atlas().frame(0).is_some());
}
#[test]
fn test_frame_out_of_range() {
assert!(default_atlas().frame(99).is_none());
}
#[test]
fn test_uvs_in_range() {
assert!(validate_atlas_uvs(&default_atlas()));
}
#[test]
fn test_cell_pixel_size() {
let (w, h) = cell_pixel_size(&SpriteAtlasConfig::default());
assert_eq!(w, 256);
assert_eq!(h, 256);
}
#[test]
fn test_frame_uv_center_first() {
let atlas = default_atlas();
let frame = atlas.frame(0).expect("should succeed");
let [cu, cv] = frame_uv_center(frame);
assert!((cu - 0.125).abs() < 0.001);
assert!((cv - 0.125).abs() < 0.001);
}
#[test]
fn test_single_cell_atlas() {
let cfg = SpriteAtlasConfig {
columns: 1,
rows: 1,
atlas_width: 256,
atlas_height: 256,
};
let atlas = SpriteAtlas::build(cfg);
let f = atlas.frame(0).expect("should succeed");
assert_eq!(f.u_max, 1.0);
assert_eq!(f.v_max, 1.0);
}
#[test]
fn test_frame_ids_sequential() {
let atlas = default_atlas();
for (i, f) in atlas.frames.iter().enumerate() {
assert_eq!(f.id, i as u32);
}
}
#[test]
fn test_u_min_less_than_u_max() {
for f in default_atlas().frames.iter() {
assert!(f.u_min < f.u_max);
}
}
#[test]
fn test_default_config() {
let cfg = SpriteAtlasConfig::default();
assert_eq!(cfg.columns, 4);
}
}