reznez 0.0.0

The high accuracy NES Emulator
Documentation
use enum_iterator::IntoEnumIterator;
use num_traits::FromPrimitive;

use crate::ppu::palette::palette_table::PaletteTable;
use crate::ppu::palette::rgbt::Rgbt;
use crate::ppu::pattern_table::{PatternIndex, PatternTable, Tile};
use crate::ppu::pixel_index::{ColumnInTile, PixelColumn, PixelRow, RowInTile};
use crate::ppu::render::frame::Frame;
use crate::ppu::sprite::sprite_attributes::{SpriteAttributes, Priority};
use crate::ppu::sprite::sprite_half::SpriteHalf;
use crate::ppu::sprite::sprite_height::SpriteHeight;
use crate::ppu::sprite::sprite_y::SpriteY;

#[derive(Clone, Copy, Debug)]
pub struct Sprite {
    x_coordinate: PixelColumn,
    y_coordinate: SpriteY,
    pattern_index: PatternIndex,
    attributes: SpriteAttributes,
}

impl Sprite {
    #[inline]
    pub fn from_u32(value: u32) -> Sprite {
        let [y_coordinate, raw_pattern_index, attributes, x_coordinate] =
            value.to_be_bytes();

        Sprite {
            x_coordinate: PixelColumn::new(x_coordinate),
            y_coordinate: SpriteY::new(y_coordinate),
            pattern_index: PatternIndex::new(raw_pattern_index),
            attributes: SpriteAttributes::from_u8(attributes),
        }
    }

    pub fn priority(self) -> Priority {
        self.attributes.priority()
    }

    pub fn pattern_index(self) -> PatternIndex {
        self.pattern_index
    }

    // For debug screens only.
    pub fn render_normal_height(
        self,
        pattern_table: &PatternTable,
        palette_table: &PaletteTable,
    ) -> Tile {
        self.render_tile(
            SpriteHeight::Normal,
            SpriteHalf::Top,
            pattern_table,
            palette_table,
        )
    }

    // For debug screens only.
    pub fn render_tall(
        self,
        pattern_table: &PatternTable,
        palette_table: &PaletteTable,
    ) -> (Tile, Tile) {
        use SpriteHalf::*;
        use SpriteHeight::Tall;
        (
            self.render_tile(Tall, Top, pattern_table, palette_table),
            self.render_tile(Tall, Bottom, pattern_table, palette_table),
        )
    }

    pub fn render_sliver(
        self,
        row: PixelRow,
        sprite_height: SpriteHeight,
        pattern_table: &PatternTable,
        palette_table: &PaletteTable,
        is_sprite_0: bool,
        frame: &mut Frame,
    ) {
        let Some((sprite_half, row_in_half, _visible)) =
            self.y_coordinate.row_in_sprite(self.attributes.flip_vertically(), sprite_height, row) else {

            return;
        };

        let mut sprite_sliver = [Rgbt::Transparent; 8];
        self.render_sliver_from_sprite_half(
            sprite_height,
            sprite_half,
            row_in_half,
            pattern_table,
            palette_table,
            &mut sprite_sliver,
        );

        for (column_in_sprite, &pixel) in sprite_sliver.iter().enumerate() {
            let column_in_sprite = ColumnInTile::from_usize(column_in_sprite).unwrap();
            if let Rgbt::Opaque(_) = pixel {
                if let Some(column) =
                    self.x_coordinate.add_column_in_tile(column_in_sprite)
                {
                    frame.set_sprite_pixel(
                        column,
                        row,
                        pixel,
                        self.priority(),
                        is_sprite_0,
                    );
                }
            }
        }
    }

    fn render_tile(
        self,
        sprite_height: SpriteHeight,
        sprite_half: SpriteHalf,
        pattern_table: &PatternTable,
        palette_table: &PaletteTable,
    ) -> Tile {
        let mut tile = Tile::new();
        for row in RowInTile::into_enum_iter() {
            self.render_sliver_from_sprite_half(
                sprite_height,
                sprite_half,
                row,
                pattern_table,
                palette_table,
                tile.row_mut(row),
            );
        }

        tile
    }

    fn render_sliver_from_sprite_half(
        self,
        sprite_height: SpriteHeight,
        sprite_half: SpriteHalf,
        mut row_in_half: RowInTile,
        pattern_table: &PatternTable,
        palette_table: &PaletteTable,
        sprite_sliver: &mut [Rgbt; 8],
    ) {
        #[rustfmt::skip]
        let pattern_index = match (sprite_height, sprite_half) {
            (SpriteHeight::Normal, SpriteHalf::Top) => self.pattern_index,
            (SpriteHeight::Normal, SpriteHalf::Bottom) => unreachable!(),
            (SpriteHeight::Tall,   SpriteHalf::Top) => self.pattern_index.to_tall_indexes().0,
            (SpriteHeight::Tall,   SpriteHalf::Bottom) => self.pattern_index.to_tall_indexes().1,
        };

        if self.attributes.flip_vertically() {
            row_in_half = row_in_half.flip();
        }

        let sprite_palette = palette_table.sprite_palette(self.attributes.palette_table_index());
        pattern_table.render_pixel_sliver(
            pattern_index,
            row_in_half,
            sprite_palette,
            sprite_sliver,
        );

        if self.attributes.flip_horizontally() {
            for i in 0..sprite_sliver.len() / 2 {
                sprite_sliver.swap(i, 7 - i);
            }
        }
    }
}