use crate::ppu::palette::{Color, PaletteColorIndex};
use crate::ppu::sprite::SpriteData;
use crate::ppu::{Ppu, TILE_DIM};
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub struct BgTileToShow {
row_color_index_bytes: (u8, u8),
palette_index: u8,
}
impl BgTileToShow {
pub fn new(row_color_index_bytes: (u8, u8), palette_index: u8) -> Self {
Self {
row_color_index_bytes,
palette_index,
}
}
}
impl Ppu {
#[inline]
fn get_color_index_from_row_bytes(row_bytes: (u8, u8), local_x: u8) -> u8 {
let local_x = 7 - (local_x & 0x7);
let high_bit = (row_bytes.0 >> local_x) & 1;
let low_bit = (row_bytes.1 >> local_x) & 1;
(high_bit << 1) | low_bit
}
#[inline]
fn calculate_bg_color(&self, x: u8, tile: BgTileToShow) -> Option<Color> {
let local_x = (x.wrapping_add(self.regs.scroll().0)) % TILE_DIM;
let color_index = Ppu::get_color_index_from_row_bytes(tile.row_color_index_bytes, local_x);
if color_index != 0 {
let color = self.palette.read_background_palette_color(
PaletteColorIndex::new(tile.palette_index, color_index),
self.regs.ppu_mask().into(),
self.regs.greyscale_flag(),
);
return Some(color);
}
None
}
#[inline]
fn calculate_sprite_color(&self, x: u8, sprite: &SpriteData) -> Option<Color> {
let row_bytes = sprite
.row_color_index_bytes()
.unwrap_or((0xF, 0xF));
let mut local_x = x.saturating_sub(sprite.obj_attributes().x()) % TILE_DIM;
if sprite.obj_attributes().horizontal_flip_flag() {
local_x = !local_x;
}
let color_index = Ppu::get_color_index_from_row_bytes(row_bytes, local_x);
if color_index != 0 {
let color = self.palette.read_sprite_palette_color(
PaletteColorIndex::new(sprite.obj_attributes().palette_index(), color_index),
self.regs.ppu_mask().into(),
self.regs.greyscale_flag(),
);
return Some(color);
}
None
}
#[inline]
pub(super) fn calculate_pixel_color_index(
&mut self,
coords: (u8, u8),
tile: Option<BgTileToShow>,
show_sprites: bool,
) -> Color {
let sprites = if show_sprites {
self.sprites_at_current_pixel.as_slice()
} else {
[].as_slice()
};
let mut first_opaque: Option<(&SpriteData, Color)> = None;
for sprite in sprites.iter() {
if let Some(sprite_color) = self.calculate_sprite_color(coords.0, sprite) {
first_opaque = Some((sprite, sprite_color));
break;
}
}
if let Some((sprite, _)) = first_opaque {
if sprite.oam_index() == 0 {
if let Some(tile) = tile {
if self.calculate_bg_color(coords.0, tile).is_some() {
self.regs.set_sprite_zero_hit();
}
}
}
}
if let Some((sprite, sprite_color)) = first_opaque {
let sprite_behind_bg = !sprite.obj_attributes().draw_in_front_flag();
if sprite_behind_bg {
if let Some(tile) = tile {
if let Some(bg_color) = self.calculate_bg_color(coords.0, tile) {
return bg_color;
}
}
}
return sprite_color;
}
if let Some(tile) = tile {
if let Some(bg_color) = self.calculate_bg_color(coords.0, tile) {
return bg_color;
}
}
self.palette
.read_background_color(self.regs.ppu_mask().into(), self.regs.greyscale_flag())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn color_index_from_row() {
for i in 0..8 {
let high_byte = 1 << (7 - i);
let low_byte = !high_byte;
for x in 0..8 {
let color_index = Ppu::get_color_index_from_row_bytes((high_byte, low_byte), x);
if i == x {
assert_eq!(color_index, 2);
} else {
assert_eq!(color_index, 1);
}
}
}
}
}