use crate::palette::{ColorIndex, GbaColor, GbColor, Palette};
use crate::tile::{TileFormat, TileSet, TILE_PIXELS};
use crate::FbSurface;
pub const OAM_PRIORITY: u8 = 0x80; pub const OAM_Y_FLIP: u8 = 0x40; pub const OAM_X_FLIP: u8 = 0x20; pub const OAM_PALETTE: u8 = 0x10;
pub const MAX_OAM_ENTRIES: usize = 40;
#[derive(Debug, Clone, Copy, Default)]
pub struct SpriteOamEntry {
pub y: i32,
pub x: i32,
pub tile_id: u8,
pub attributes: u8,
}
impl SpriteOamEntry {
pub fn new(y: i32, x: i32, tile_id: u8, attributes: u8) -> Self {
Self {
y,
x,
tile_id,
attributes,
}
}
pub fn from_raw(raw_y: u8, raw_x: u8, tile_id: u8, attributes: u8) -> Self {
Self {
y: raw_y as i32 - 16,
x: raw_x as i32 - 8,
tile_id,
attributes,
}
}
#[inline]
pub fn bg_priority(&self) -> bool {
self.attributes & OAM_PRIORITY != 0
}
#[inline]
pub fn y_flip(&self) -> bool {
self.attributes & OAM_Y_FLIP != 0
}
#[inline]
pub fn x_flip(&self) -> bool {
self.attributes & OAM_X_FLIP != 0
}
#[inline]
pub fn uses_obp1(&self) -> bool {
self.attributes & OAM_PALETTE != 0
}
pub fn is_on_screen(&self, screen_width: u32, screen_height: u32) -> bool {
let x_end = self.x + TILE_PIXELS as i32;
let y_end = self.y + TILE_PIXELS as i32;
x_end > 0 && self.x < screen_width as i32 && y_end > 0 && self.y < screen_height as i32
}
}
#[derive(Debug, Clone)]
pub struct SpriteLayer {
pub entries: Vec<SpriteOamEntry>,
}
impl SpriteLayer {
pub fn new() -> Self {
Self {
entries: Vec::with_capacity(MAX_OAM_ENTRIES),
}
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn add(&mut self, entry: SpriteOamEntry) {
self.entries.push(entry);
}
pub fn render(
&self,
fb: &mut impl FbSurface,
tileset: &TileSet,
obp0: &Palette,
obp1: &Palette,
bg_color_buffer: Option<&[u8]>,
) {
for entry in self.entries.iter().rev() {
render_sprite(fb, tileset, obp0, obp1, entry, bg_color_buffer);
}
}
}
impl Default for SpriteLayer {
fn default() -> Self {
Self::new()
}
}
fn render_sprite(
fb: &mut impl FbSurface,
tileset: &TileSet,
obp0: &Palette,
obp1: &Palette,
entry: &SpriteOamEntry,
bg_color_buffer: Option<&[u8]>,
) {
if !entry.is_on_screen(fb.width(), fb.height()) {
return;
}
let format = tileset.tile_format();
let fb_w = fb.width() as usize;
let fb_h = fb.height() as i32;
if format == TileFormat::FullColor {
if let Some(rgba_tile) = tileset.get_rgba(entry.tile_id as usize) {
for row in 0..TILE_PIXELS {
let screen_y = entry.y + row as i32;
if screen_y < 0 || screen_y >= fb_h {
continue;
}
let tile_row = if entry.y_flip() {
TILE_PIXELS - 1 - row
} else {
row
};
for col in 0..TILE_PIXELS {
let screen_x = entry.x + col as i32;
if screen_x < 0 || screen_x >= fb.width() as i32 {
continue;
}
let tile_col = if entry.x_flip() {
TILE_PIXELS - 1 - col
} else {
col
};
let rgba = rgba_tile.pixels[tile_row][tile_col];
if rgba.a == 0 {
continue;
}
if entry.bg_priority() {
if let Some(bg_buf) = bg_color_buffer {
let pixel_offset = screen_y as usize * fb_w + screen_x as usize;
if pixel_offset < bg_buf.len() && bg_buf[pixel_offset] != 0 {
continue;
}
}
}
fb.set_pixel(screen_x as u32, screen_y as u32, rgba);
}
}
}
return;
}
let tile = tileset.get(entry.tile_id as usize);
let palette = if entry.uses_obp1() { obp1 } else { obp0 };
let is_gba = format == TileFormat::Gba4bpp;
for row in 0..TILE_PIXELS {
let screen_y = entry.y + row as i32;
if screen_y < 0 || screen_y >= fb_h {
continue;
}
let tile_row = if entry.y_flip() {
TILE_PIXELS - 1 - row
} else {
row
};
for col in 0..TILE_PIXELS {
let screen_x = entry.x + col as i32;
if screen_x < 0 || screen_x >= fb.width() as i32 {
continue;
}
let tile_col = if entry.x_flip() {
TILE_PIXELS - 1 - col
} else {
col
};
let color_idx = tile.get(tile_row, tile_col);
if color_idx == 0 {
continue;
}
if entry.bg_priority() {
if let Some(bg_buf) = bg_color_buffer {
let pixel_offset =
screen_y as usize * fb_w + screen_x as usize;
if pixel_offset < bg_buf.len() && bg_buf[pixel_offset] != 0 {
continue;
}
}
}
let rgba = if is_gba {
palette.colors[GbaColor::from_u8(color_idx).to_index()]
} else {
palette.color(GbColor::from_u8(color_idx))
};
fb.set_pixel(screen_x as u32, screen_y as u32, rgba);
}
}
}