earthbound-battle-backgrounds 0.1.0

Emulate and render the battle backgrounds from EarthBound / Mother 2.
Documentation
use crate::rom::block::Block;
use crate::rom::palette_cycle::PaletteCycle;

#[derive(Debug)]
pub struct RomGraphics {
    bits_per_pixel: u8,
    gfx_rom_graphics: Vec<i16>,
    tiles: Vec<[[i16; 8]; 8]>,
}

impl RomGraphics {
    pub fn new(bits_per_pixel: u8) -> Self {
        RomGraphics {
            bits_per_pixel,
            gfx_rom_graphics: vec![],
            tiles: vec![],
        }
    }

    /// Internal function - builds the tile array from the graphics buffer.
    fn build_tiles(&mut self) {
        let n = self.gfx_rom_graphics.len() / (8 * usize::from(self.bits_per_pixel));
        self.tiles = vec![];
        for i in 0..n {
            self.tiles.push([[0; 8]; 8]);
            let o = i * 8 * usize::from(self.bits_per_pixel);
            for x in 0..8 {
                self.tiles[i][x] = [0; 8];
                for y in 0..8 {
                    let mut c = 0;
                    for bp in 0..usize::from(self.bits_per_pixel) {
                        let half_bp = bp / 2;
                        let gfx = self.gfx_rom_graphics[o + y * 2 + (half_bp * 16 + (bp & 1))];
                        c += ((gfx & (1 << 7 - x)) >> 7 - x) << bp;
                    }
                    self.tiles[i][x][y] = c;
                }
            }
        }
    }

    pub fn draw(&mut self, bmp: &mut [i16], palette: &PaletteCycle, array_rom_graphics: &[i16]) {
        let data = bmp;
        let mut block;
        let mut tile;
        let mut sub_palette;
        let mut n;
        let mut b1;
        let mut b2;
        let mut vertical_flip;
        let mut horizontal_flip;
        // TODO: Hardcoding is bad; how do I get the stride normally?
        let stride = 1024;
        // For each pixel in the 256×256 grid, we need to render the image found in the dump
        for i in 0..32 {
            for j in 0..32 {
                n = j * 32 + i;
                b1 = array_rom_graphics[n * 2];
                b2 = array_rom_graphics[n * 2 + 1] << 8;
                block = i64::from(b1 + b2);
                tile = block & 0x3FF;
                vertical_flip = (block & 0x8000) != 0;
                horizontal_flip = (block & 0x4000) != 0;
                sub_palette = (block >> 10) & 7;
                self.draw_tile(
                    data,
                    stride,
                    i * 8,
                    j * 8,
                    palette,
                    tile as usize,
                    sub_palette as usize,
                    vertical_flip,
                    horizontal_flip,
                );
            }
        }
    }

    fn draw_tile(
        &mut self,
        pixels: &mut [i16],
        stride: usize,
        x: usize,
        y: usize,
        palette: &PaletteCycle,
        tile: usize,
        sub_palette: usize,
        vertical_flip: bool,
        horizontal_flip: bool,
    ) {
        let x = x as isize;
        let y = y as isize;
        let sub_palette_array = palette.get_colors(sub_palette);
        let mut px;
        let mut py;
        let mut pos;
        for i in 0usize..8 {
            if horizontal_flip {
                px = x + 7 - i as isize;
            } else {
                px = x + i as isize;
            }
            for j in 0usize..8 {
                let rgb_array = sub_palette_array[self.tiles[tile][i][j] as usize];
                if vertical_flip {
                    py = y + 7 - j as isize;
                } else {
                    py = y + j as isize;
                }
                pos = 4 * px + (stride as isize) * py;
                pixels[(pos + 0) as usize] = ((rgb_array >> 16) & 0xFF) as i16;
                pixels[(pos + 1) as usize] = ((rgb_array >> 8) & 0xFF) as i16;
                pixels[(pos + 2) as usize] = ((rgb_array) & 0xFF) as i16;
            }
        }
    }

    /// Internal function - reads graphics from the specified block and builds tileset.
    ///
    /// - `block` - The block to read graphics data from
    pub fn load_graphics(&mut self, mut block: Block) {
        self.gfx_rom_graphics = block.decompress();
        self.build_tiles();
    }
}