diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 6cabaccf..31772109 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -3,7 +3,7 @@ extern crate agb; use agb::{ - display::{object::ObjectStandard, HEIGHT, WIDTH}, + display::{background::Map, object::ObjectStandard, HEIGHT, WIDTH}, input::Button, }; use core::convert::TryInto; @@ -48,18 +48,19 @@ fn main() -> ! { let vblank = agb::interrupt::VBlank::get(); let mut input = agb::input::ButtonController::new(); - gfx.set_sprite_palette_raw(&CHICKEN_PALETTE); - gfx.set_sprite_tilemap(&CHICKEN_TILES); - gfx.set_background_palette_raw(&MAP_PALETTE); gfx.set_background_tilemap(0, &MAP_TILES); - let mut background = gfx.get_background().unwrap(); - background.draw_full_map(&MAP_MAP, (32_u32, 32_u32).into(), 0); + let mut background = gfx.get_regular().unwrap(); + background.set_map(Map::new(&MAP_MAP, (32_u32, 32_u32).into(), 0)); background.show(); + background.commit(); let mut object = gba.display.object.get(); + object.set_sprite_palette_raw(&CHICKEN_PALETTE); + object.set_sprite_tilemap(&CHICKEN_TILES); + object.enable(); let mut chicken = Character { object: object.get_object_standard(), diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs new file mode 100644 index 00000000..05a0d404 --- /dev/null +++ b/agb/src/display/background.rs @@ -0,0 +1,401 @@ +use core::ops::Index; + +use crate::{ + memory_mapped::{MemoryMapped, MemoryMapped1DArray}, + number::{Rect, Vector2D}, +}; + +use super::{ + palette16, set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority, + DISPLAY_CONTROL, +}; + +const PALETTE_BACKGROUND: MemoryMapped1DArray = + unsafe { MemoryMapped1DArray::new(0x0500_0000) }; + +const TILE_BACKGROUND: MemoryMapped1DArray = + unsafe { MemoryMapped1DArray::new(0x06000000) }; + +const MAP: *mut [[[u16; 32]; 32]; 32] = 0x0600_0000 as *mut _; + +pub enum ColourMode { + FourBitPerPixel = 0, + EightBitPerPixel = 1, +} + +#[derive(Clone, Copy)] +pub enum BackgroundSize { + S32x32 = 0, + S64x32 = 1, + S32x64 = 2, + S64x64 = 3, +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum Mutability { + Immutable, + Mutable, +} + +struct MapStorage<'a> { + s: *const [u16], + mutability: Mutability, + _phantom: core::marker::PhantomData<&'a ()>, +} + +impl<'a> Index for MapStorage<'a> { + type Output = u16; + fn index(&self, index: usize) -> &Self::Output { + &self.get()[index] + } +} + +impl<'a> MapStorage<'a> { + fn new(store: &[u16]) -> MapStorage { + MapStorage { + s: store as *const _, + mutability: Mutability::Immutable, + _phantom: core::marker::PhantomData, + } + } + fn new_mutable(store: &mut [u16]) -> MapStorage { + MapStorage { + s: store as *const _, + mutability: Mutability::Mutable, + _phantom: core::marker::PhantomData, + } + } + fn get(&self) -> &[u16] { + unsafe { &*self.s } + } + fn get_mut(&mut self) -> &mut [u16] { + assert!( + self.mutability == Mutability::Mutable, + "backing storage must be mutable in order to get internal storage mutably" + ); + unsafe { &mut *(self.s as *mut _) } + } +} + +/// The map background is the method of drawing game maps to the screen. It +/// automatically handles copying the correct portion of a provided map to the +/// assigned block depending on given coordinates. +pub struct BackgroundRegular<'a> { + background: u8, + block: u8, + commited_position: Vector2D, + shadowed_position: Vector2D, + poisoned: bool, + shadowed_register: u16, + copy_size: Vector2D, + background_size: BackgroundSize, + map: Option>, +} + +pub struct Map<'a> { + store: MapStorage<'a>, + pub dimensions: Vector2D, + pub default: u16, +} + +impl<'a> Map<'a> { + pub fn new(map: &[u16], dimensions: Vector2D, default: u16) -> Map { + Map { + store: MapStorage::new(map), + dimensions, + default, + } + } + pub fn new_mutable(map: &mut [u16], dimensions: Vector2D, default: u16) -> Map { + Map { + store: MapStorage::new_mutable(map), + dimensions, + default, + } + } + fn get_position(&self, x: i32, y: i32) -> u16 { + if x < 0 || x as u32 >= self.dimensions.x || y < 0 || y as u32 >= self.dimensions.y { + self.default + } else { + self.store[y as usize * self.dimensions.x as usize + x as usize] + } + } + pub fn get_store(&self) -> &[u16] { + self.store.get() + } + pub fn get_mutable_store(&mut self) -> &mut [u16] { + self.store.get_mut() + } +} + +impl<'a> BackgroundRegular<'a> { + unsafe fn new(background: u8, block: u8, size: BackgroundSize) -> BackgroundRegular<'a> { + let mut b = BackgroundRegular { + background, + block, + commited_position: (0, 0).into(), + shadowed_position: (0, 0).into(), + shadowed_register: 0, + copy_size: (30_u16, 20_u16).into(), + background_size: size, + poisoned: true, + map: None, + }; + b.set_block(block); + b.set_colour_mode(ColourMode::FourBitPerPixel); + b.set_background_size(size); + b + } + + /// Sets the background to be shown on screen. Requires the background to + /// have a map enabled otherwise a panic is caused. + pub fn show(&mut self) { + assert!(self.map.is_some()); + let mode = DISPLAY_CONTROL.get(); + let new_mode = mode | (1 << (self.background + 0x08)); + DISPLAY_CONTROL.set(new_mode); + } + + /// Hides the background, nothing from this background is rendered to screen. + pub fn hide(&mut self) { + let mode = DISPLAY_CONTROL.get(); + let new_mode = mode & !(1 << (self.background + 0x08)); + DISPLAY_CONTROL.set(new_mode); + } + + unsafe fn set_shadowed_register_bits(&mut self, value: u16, length: u16, shift: u16) { + let mask = !(((1 << length) - 1) << shift); + let new = (self.shadowed_register & mask) | (value << shift); + self.shadowed_register = new; + } + + unsafe fn get_register(&self) -> MemoryMapped { + MemoryMapped::new(0x0400_0008 + 2 * self.background as usize) + } + + unsafe fn set_block(&mut self, block: u8) { + self.set_shadowed_register_bits(block as u16, 5, 0x8); + } + + unsafe fn set_colour_mode(&mut self, mode: ColourMode) { + self.set_shadowed_register_bits(mode as u16, 0x1, 0x7); + } + + pub fn set_priority(&mut self, p: Priority) { + unsafe { self.set_shadowed_register_bits(p as u16, 0x2, 0x0) }; + } + + unsafe fn set_background_size(&mut self, size: BackgroundSize) { + self.set_shadowed_register_bits(size as u16, 0x2, 0xE); + } + + unsafe fn set_position_x_register(&self, x: u16) { + *((0x0400_0010 + 4 * self.background as usize) as *mut u16) = x + } + unsafe fn set_position_y_register(&self, y: u16) { + *((0x0400_0012 + 4 * self.background as usize) as *mut u16) = y + } + + pub fn set_position(&mut self, position: Vector2D) { + self.shadowed_position = position; + } + + pub fn get_map(&mut self) -> Option<&mut Map<'a>> { + self.poisoned = true; + self.map.as_mut() + } + + pub fn set_map(&mut self, map: Map<'a>) { + self.poisoned = true; + self.map = Some(map); + } + + pub fn commit(&mut self) { + // commit shadowed register + unsafe { self.get_register().set(self.shadowed_register) }; + + let commited_screen = Rect::new(self.commited_position, self.copy_size.change_base()); + let shadowed_screen = Rect::new(self.shadowed_position, self.copy_size.change_base()); + + if self.poisoned || !shadowed_screen.touches(commited_screen) { + let positions_to_be_updated = Rect::new( + self.shadowed_position / 8 - (1, 1).into(), + self.copy_size.change_base() + (1, 1).into(), + ) + .iter(); + + if let Some(map) = &self.map { + for (x, y) in positions_to_be_updated { + unsafe { + (&mut (*MAP)[self.block as usize][y.rem_euclid(32) as usize] + [x.rem_euclid(32) as usize] as *mut u16) + .write_volatile(map.get_position(x, y)); + } + } + } + } else { + let commited_block = self.commited_position / 8; + let shadowed_block = self.shadowed_position / 8; + + let top_bottom_rect: Rect = { + let top_bottom_height = commited_block.y - shadowed_block.y; + let new_y = if top_bottom_height < 0 { + commited_block.y + self.copy_size.y as i32 + } else { + shadowed_block.y + }; + Rect::new( + (shadowed_block.x, new_y).into(), + (30, top_bottom_height.abs()).into(), + ) + }; + + let left_right_rect: Rect = { + let left_right_width = commited_block.x - shadowed_block.x; + let new_x = if left_right_width < 0 { + commited_block.x + self.copy_size.x as i32 + } else { + shadowed_block.x + }; + Rect::new( + (new_x, shadowed_block.y).into(), + (left_right_width.abs(), 20).into(), + ) + }; + + if let Some(map) = &self.map { + for (x, y) in top_bottom_rect.iter().chain(left_right_rect.iter()) { + unsafe { + (&mut (*MAP)[self.block as usize][y.rem_euclid(32) as usize] + [x.rem_euclid(32) as usize] as *mut u16) + .write_volatile(map.get_position(x, y)); + } + } + } + }; + + // update commited position + + self.commited_position = self.shadowed_position; + + self.poisoned = false; + + // update position in registers + + unsafe { + self.set_position_x_register((self.commited_position.x % (32 * 8)) as u16); + self.set_position_y_register((self.commited_position.y % (32 * 8)) as u16); + } + } +} + +fn decide_background_mode(num_regular: u8, num_affine: u8) -> Option { + if num_affine == 0 && num_regular <= 4 { + Some(DisplayMode::Tiled0) + } else if num_affine == 1 && num_regular <= 2 { + Some(DisplayMode::Tiled1) + } else if num_affine == 2 && num_regular == 0 { + Some(DisplayMode::Tiled2) + } else { + None + } +} + +pub struct BackgroundDistributor { + used_blocks: u32, + num_regular: u8, + num_affine: u8, +} + +impl<'b> BackgroundDistributor { + pub(crate) unsafe fn new() -> Self { + set_graphics_settings(GraphicsSettings::empty() | GraphicsSettings::SPRITE1_D); + set_graphics_mode(DisplayMode::Tiled0); + BackgroundDistributor { + used_blocks: 0, + num_regular: 0, + num_affine: 0, + } + } + + fn set_background_tilemap_entry(&mut self, index: u32, data: u32) { + TILE_BACKGROUND.set(index as usize, data); + } + + /// Copies raw palettes to the background palette without any checks. + pub fn set_background_palette_raw(&mut self, palette: &[u16]) { + for (index, &colour) in palette.iter().enumerate() { + PALETTE_BACKGROUND.set(index, colour); + } + } + + fn set_background_palette(&mut self, pal_index: u8, palette: &palette16::Palette16) { + for (colour_index, &colour) in palette.colours.iter().enumerate() { + PALETTE_BACKGROUND.set(pal_index as usize * 16 + colour_index, colour); + } + } + + /// Copies palettes to the background palettes without any checks. + pub fn set_background_palettes(&mut self, palettes: &[palette16::Palette16]) { + for (palette_index, entry) in palettes.iter().enumerate() { + self.set_background_palette(palette_index as u8, entry) + } + } + + /// Gets a map background if possible and assigns an unused block to it. + pub fn get_regular(&mut self) -> Result, &'static str> { + let new_mode = decide_background_mode(self.num_regular + 1, self.num_affine) + .ok_or("there is no mode compatible with the requested backgrounds")?; + + unsafe { set_graphics_mode(new_mode) }; + + if !self.used_blocks == 0 { + return Err("all blocks are used"); + } + + let mut availiable_block = u8::MAX; + + for i in 0..32 { + if (1 << i) & self.used_blocks == 0 { + availiable_block = i; + break; + } + } + + assert!( + availiable_block != u8::MAX, + "should be able to find a block" + ); + + self.used_blocks |= 1 << availiable_block; + + let background = self.num_regular; + self.num_regular += 1; + Ok(unsafe { BackgroundRegular::new(background, availiable_block, BackgroundSize::S32x32) }) + } + + /// Copies tiles to tilemap starting at the starting tile. Cannot overwrite + /// blocks that are already written to, panic is caused if this is attempted. + pub fn set_background_tilemap(&mut self, start_tile: u32, tiles: &[u32]) { + let u32_per_block = 512; + + let start_block = (start_tile * 8) / u32_per_block; + // round up rather than down + let end_block = (start_tile * 8 + tiles.len() as u32 + u32_per_block - 1) / u32_per_block; + + let blocks_to_use: u32 = ((1 << (end_block - start_block)) - 1) << start_block; + + assert!( + self.used_blocks & blocks_to_use == 0, + "blocks {} to {} should be unused for this copy to succeed", + start_block, + end_block + ); + + self.used_blocks |= blocks_to_use; + + for (index, &tile) in tiles.iter().enumerate() { + self.set_background_tilemap_entry(start_tile * 8 + index as u32, tile) + } + } +} diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index 7b9c520d..424a740f 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -1,12 +1,13 @@ -use crate::display::tiled0::Tiled0; +use crate::display::background::BackgroundDistributor; crate::include_gfx!("gfx/agb_logo.toml"); -pub fn display_logo(gfx: &mut Tiled0) { +pub fn display_logo(gfx: &mut BackgroundDistributor) { + use super::background::Map; gfx.set_background_palettes(agb_logo::test_logo.palettes); gfx.set_background_tilemap(0, agb_logo::test_logo.tiles); - let mut back = gfx.get_background().unwrap(); + let mut back = gfx.get_regular().unwrap(); let mut entries: [u16; 30 * 20] = [0; 30 * 20]; for tile_id in 0..(30 * 20) { @@ -14,8 +15,9 @@ pub fn display_logo(gfx: &mut Tiled0) { entries[tile_id as usize] = tile_id | (palette_entry << 12); } - back.draw_full_map(&entries, (30_u32, 20_u32).into(), 0); + back.set_map(Map::new(&entries, (30_u32, 20_u32).into(), 0)); back.show(); + back.commit(); } #[test_case] diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index a05b137f..e4ce5206 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -5,6 +5,8 @@ use video::Video; use self::object::ObjectControl; +/// Graphics mode 0. Four regular backgrounds. +pub mod background; /// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer. pub mod bitmap3; /// Graphics mode 4. Bitmap 4 provides two 8-bit paletted framebuffers with page switching. @@ -17,8 +19,6 @@ pub mod object; pub mod palette16; /// Data produced by agb-image-converter pub mod tile_data; -/// Graphics mode 0. Four regular backgrounds. -pub mod tiled0; /// Giving out graphics mode. pub mod video; diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 89954ccb..5d1ecd77 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -1,12 +1,16 @@ use core::cell::RefCell; -use super::{Priority, DISPLAY_CONTROL}; +use super::{palette16, Priority, DISPLAY_CONTROL}; use crate::bitarray::Bitarray; use crate::memory_mapped::MemoryMapped1DArray; use crate::number::Vector2D; const OBJECT_ATTRIBUTE_MEMORY: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x0700_0000) }; +const PALETTE_SPRITE: MemoryMapped1DArray = + unsafe { MemoryMapped1DArray::new(0x0500_0200) }; +const TILE_SPRITE: MemoryMapped1DArray = + unsafe { MemoryMapped1DArray::new(0x06010000) }; /// Handles distributing objects and matricies along with operations that effect all objects. pub struct ObjectControl { @@ -311,6 +315,39 @@ impl ObjectControl { } } + fn set_sprite_tilemap_entry(&self, index: usize, data: u32) { + TILE_SPRITE.set(index, data); + } + + /// Copies raw palettes to the background palette without any checks. + pub fn set_sprite_palette_raw(&self, colour: &[u16]) { + for (index, &entry) in colour.iter().enumerate() { + self.set_sprite_palette_entry(index, entry) + } + } + fn set_sprite_palette_entry(&self, index: usize, colour: u16) { + PALETTE_SPRITE.set(index, colour) + } + + fn set_sprite_palette(&self, pal_index: u8, palette: &palette16::Palette16) { + for (colour_index, &colour) in palette.colours.iter().enumerate() { + PALETTE_SPRITE.set(pal_index as usize * 16 + colour_index, colour); + } + } + + pub fn set_sprite_palettes(&self, palettes: &[palette16::Palette16]) { + for (palette_index, entry) in palettes.iter().enumerate() { + self.set_sprite_palette(palette_index as u8, entry) + } + } + + /// Copies tiles to the sprite tilemap without any checks. + pub fn set_sprite_tilemap(&self, tiles: &[u32]) { + for (index, &tile) in tiles.iter().enumerate() { + self.set_sprite_tilemap_entry(index, tile) + } + } + /// Enable objects on the GBA. pub fn enable(&mut self) { let disp = DISPLAY_CONTROL.get(); diff --git a/agb/src/display/tiled0.rs b/agb/src/display/tiled0.rs deleted file mode 100644 index 0ee5e8d9..00000000 --- a/agb/src/display/tiled0.rs +++ /dev/null @@ -1,430 +0,0 @@ -use core::{convert::TryInto, ops::Deref}; - -use crate::{ - memory_mapped::MemoryMapped1DArray, - number::{Rect, Vector2D}, -}; - -use super::{ - palette16, set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority, - DISPLAY_CONTROL, -}; - -const PALETTE_BACKGROUND: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x0500_0000) }; -const PALETTE_SPRITE: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x0500_0200) }; - -const TILE_BACKGROUND: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x06000000) }; -const TILE_SPRITE: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x06010000) }; - -const MAP: *mut [[[u16; 32]; 32]; 32] = 0x0600_0000 as *mut _; - -pub enum ColourMode { - FourBitPerPixel = 0, - EightBitPerPixel = 1, -} - -pub enum BackgroundSize { - S32x32 = 0, - S64x32 = 1, - S32x64 = 2, - S64x64 = 3, -} - -#[non_exhaustive] -/// The map background is the method of drawing game maps to the screen. It -/// automatically handles copying the correct portion of a provided map to the -/// assigned block depending on given coordinates. -pub struct Background { - background: u8, - block: u8, - pos_x: i32, - pos_y: i32, -} - -impl Background { - unsafe fn new(layer: u8, block: u8) -> Background { - let mut background = Background { - background: layer, - block, - pos_x: 0, - pos_y: 0, - }; - background.set_colour_mode(ColourMode::FourBitPerPixel); - background.set_background_size(BackgroundSize::S32x32); - background.set_block(block); - background - } -} - -impl Background { - /// Sets the background to be shown on screen. Requires the background to - /// have a map enabled otherwise a panic is caused. - pub fn show(&mut self) { - let mode = DISPLAY_CONTROL.get(); - let new_mode = mode | (1 << (self.background + 0x08)); - DISPLAY_CONTROL.set(new_mode); - } - - /// Hides the background, nothing from this background is rendered to screen. - pub fn hide(&mut self) { - let mode = DISPLAY_CONTROL.get(); - let new_mode = mode & !(1 << (self.background + 0x08)); - DISPLAY_CONTROL.set(new_mode); - } - - unsafe fn get_register(&mut self) -> *mut u16 { - (0x0400_0008 + 2 * self.background as usize) as *mut u16 - } - - unsafe fn set_block(&mut self, block: u8) { - self.set_bits(0x08, 5, block as u16) - } - - unsafe fn set_bits(&mut self, start: u16, num_bits: u16, bits: u16) { - let reg = self.get_register(); - let control = reg.read_volatile(); - let mask = !(((1 << num_bits) - 1) << start); - let new_control = (control & mask) | ((bits as u16) << start); - reg.write_volatile(new_control); - } - - /// Sets priority of the background layer. Backgrounds with higher priority - /// are drawn (above/below) backgrounds with lower priority. - pub fn set_priority(&mut self, p: Priority) { - unsafe { self.set_bits(0, 2, p as u16) } - } - - fn set_colour_mode(&mut self, mode: ColourMode) { - unsafe { self.set_bits(0x07, 1, mode as u16) } - } - - fn set_background_size(&mut self, size: BackgroundSize) { - unsafe { self.set_bits(0x0E, 2, size as u16) } - } - - fn map_get(&self, map: &T, dim_x: u32, dim_y: u32, x: i32, y: i32, default: u16) -> u16 - where - T: Deref, - { - if x >= dim_x as i32 || x < 0 || y >= dim_y as i32 || y < 0 { - default - } else { - map[(dim_x as i32 * y + x) as usize] - } - } - - fn set_x(&self, x: u16) { - unsafe { ((0x0400_0010 + 4 * self.background as usize) as *mut u16).write_volatile(x) } - } - fn set_y(&self, y: u16) { - unsafe { ((0x0400_0012 + 4 * self.background as usize) as *mut u16).write_volatile(y) } - } - - /// Forces the portion of the map in current view to be copied to the map - /// block assigned to this background. This is currently unnecesary to call. - /// Setting position already updates the drawn map, and changing map forces - /// an update. - pub fn draw_full_map(&mut self, map: &[u16], dimensions: Vector2D, default: u16) { - let area: Rect = Rect { - position: Vector2D::new(-1, -1), - size: Vector2D::new(32, 22), - }; - self.draw_area(map, dimensions, area, default); - } - - /// Forces a specific area of the screen to be drawn, taking into account any positonal offsets. - pub fn draw_area(&self, map: &[u16], dimensions: Vector2D, area: Rect, default: u16) { - self.draw_area_mapped( - &map, - dimensions.x, - dimensions.y, - area.position.x, - area.position.y, - area.size.x, - area.size.y, - default, - ); - } - - #[allow(clippy::too_many_arguments)] - fn draw_area_mapped( - &self, - map: &T, - dim_x: u32, - dim_y: u32, - left: i32, - top: i32, - width: i32, - height: i32, - default: u16, - ) where - T: Deref, - { - let x_map_space = self.pos_x / 8; - let y_map_space = self.pos_y / 8; - - let x_block_space = x_map_space % 32; - let y_block_space = y_map_space % 32; - - for x in left..(left + width) { - for y in top..(top + height) { - unsafe { - (&mut (*MAP)[self.block as usize][(y_block_space + y).rem_euclid(32) as usize] - [(x_block_space + x).rem_euclid(32) as usize] - as *mut u16) - .write_volatile(self.map_get( - map, - dim_x, - dim_y, - x_map_space + x, - y_map_space + y, - default, - )) - }; - } - } - } - - pub fn set_offset(&mut self, position: Vector2D) { - self.set_x(position.x as u16); - self.set_y(position.y as u16); - } - - /// Sets the position of the map to be shown on screen. This automatically - /// manages copying the correct portion to the map block and moving the map - /// registers. - pub fn set_position( - &mut self, - map: &[u16], - dimensions: Vector2D, - position: Vector2D, - default: u16, - ) { - self.set_position_mapped( - &map, - dimensions.x, - dimensions.y, - position.x, - position.y, - default, - ); - } - - fn set_position_mapped( - &mut self, - map: &T, - dim_x: u32, - dim_y: u32, - x: i32, - y: i32, - default: u16, - ) where - T: Deref, - { - let x_map_space = x / 8; - let y_map_space = y / 8; - - let prev_x_map_space = self.pos_x / 8; - let prev_y_map_space = self.pos_y / 8; - - let x_difference = x_map_space - prev_x_map_space; - let y_difference = y_map_space - prev_y_map_space; - - let x_block_space = x_map_space % 32; - let y_block_space = y_map_space % 32; - - self.pos_x = x; - self.pos_y = y; - - // don't fancily handle if we've moved more than one tile, just copy the whole new map - if x_difference.abs() > 1 || y_difference.abs() > 1 { - self.draw_area_mapped(map, dim_x, dim_y, -1, -1, 32, 22, default); - } else { - if x_difference != 0 { - let x_offset = match x_difference { - -1 => -1, - 1 => 30, - _ => unreachable!(), - }; - for y in -1..21 { - unsafe { - (&mut (*MAP)[self.block as usize] - [(y_block_space + y).rem_euclid(32) as usize] - [(x_block_space + x_offset).rem_euclid(32) as usize] - as *mut u16) - .write_volatile(self.map_get( - map, - dim_x, - dim_y, - x_map_space + x_offset, - y_map_space + y, - default, - )) - }; - } - } - if y_difference != 0 { - let y_offset = match y_difference { - -1 => -1, - 1 => 20, - _ => unreachable!(), - }; - for x in -1..31 { - unsafe { - (&mut (*MAP)[self.block as usize] - [(y_block_space + y_offset).rem_euclid(32) as usize] - [(x_block_space + x).rem_euclid(32) as usize] - as *mut u16) - .write_volatile(self.map_get( - map, - dim_x, - dim_y, - x_map_space + x, - y_map_space + y_offset, - default, - )) - }; - } - } - } - - let x_remainder = x % (32 * 8); - let y_remainder = y % (32 * 8); - - self.set_x(x_remainder as u16); - self.set_y(y_remainder as u16); - } -} - -#[non_exhaustive] -pub struct Tiled0 { - used_blocks: u32, - num_backgrounds: u8, -} - -impl Tiled0 { - pub(crate) unsafe fn new() -> Self { - set_graphics_settings(GraphicsSettings::empty() | GraphicsSettings::SPRITE1_D); - set_graphics_mode(DisplayMode::Tiled0); - Tiled0 { - used_blocks: 0, - num_backgrounds: 0, - } - } - - fn set_sprite_palette_entry(&mut self, index: u8, colour: u16) { - PALETTE_SPRITE.set(index as usize, colour) - } - fn set_sprite_tilemap_entry(&mut self, index: u32, data: u32) { - TILE_SPRITE.set(index as usize, data); - } - - fn set_background_tilemap_entry(&mut self, index: u32, data: u32) { - TILE_BACKGROUND.set(index as usize, data); - } - - /// Copies raw palettes to the background palette without any checks. - pub fn set_sprite_palette_raw(&mut self, colour: &[u16]) { - for (index, &entry) in colour.iter().enumerate() { - self.set_sprite_palette_entry(index.try_into().unwrap(), entry) - } - } - fn set_sprite_palette(&mut self, pal_index: u8, palette: &palette16::Palette16) { - for (colour_index, &colour) in palette.colours.iter().enumerate() { - PALETTE_SPRITE.set(pal_index as usize * 16 + colour_index, colour); - } - } - - pub fn set_sprite_palettes(&mut self, palettes: &[palette16::Palette16]) { - for (palette_index, entry) in palettes.iter().enumerate() { - self.set_sprite_palette(palette_index as u8, entry) - } - } - - /// Copies raw palettes to the background palette without any checks. - pub fn set_background_palette_raw(&mut self, palette: &[u16]) { - for (index, &colour) in palette.iter().enumerate() { - PALETTE_BACKGROUND.set(index, colour); - } - } - - fn set_background_palette(&mut self, pal_index: u8, palette: &palette16::Palette16) { - for (colour_index, &colour) in palette.colours.iter().enumerate() { - PALETTE_BACKGROUND.set(pal_index as usize * 16 + colour_index, colour); - } - } - - /// Copies palettes to the background palettes without any checks. - pub fn set_background_palettes(&mut self, palettes: &[palette16::Palette16]) { - for (palette_index, entry) in palettes.iter().enumerate() { - self.set_background_palette(palette_index as u8, entry) - } - } - - /// Copies tiles to the sprite tilemap without any checks. - pub fn set_sprite_tilemap(&mut self, tiles: &[u32]) { - for (index, &tile) in tiles.iter().enumerate() { - self.set_sprite_tilemap_entry(index as u32, tile) - } - } - - /// Gets a map background if possible and assigns an unused block to it. - pub fn get_background(&mut self) -> Result { - if self.num_backgrounds >= 4 { - return Err("too many backgrounds created, maximum is 4"); - } - - if !self.used_blocks == 0 { - return Err("all blocks are used"); - } - - let mut availiable_block = u8::MAX; - - for i in 0..32 { - if (1 << i) & self.used_blocks == 0 { - availiable_block = i; - break; - } - } - - assert!( - availiable_block != u8::MAX, - "should be able to find a block" - ); - - self.used_blocks |= 1 << availiable_block; - - let background = self.num_backgrounds; - self.num_backgrounds = background + 1; - Ok(unsafe { Background::new(background, availiable_block) }) - } - - /// Copies tiles to tilemap starting at the starting tile. Cannot overwrite - /// blocks that are already written to, panic is caused if this is attempted. - pub fn set_background_tilemap(&mut self, start_tile: u32, tiles: &[u32]) { - let u32_per_block = 512; - - let start_block = (start_tile * 8) / u32_per_block; - // round up rather than down - let end_block = (start_tile * 8 + tiles.len() as u32 + u32_per_block - 1) / u32_per_block; - - let blocks_to_use: u32 = ((1 << (end_block - start_block)) - 1) << start_block; - - assert!( - self.used_blocks & blocks_to_use == 0, - "blocks {} to {} should be unused for this copy to succeed", - start_block, - end_block - ); - - self.used_blocks |= blocks_to_use; - - for (index, &tile) in tiles.iter().enumerate() { - self.set_background_tilemap_entry(start_tile * 8 + index as u32, tile) - } - } -} diff --git a/agb/src/display/video.rs b/agb/src/display/video.rs index 3c7974a2..2652b8a2 100644 --- a/agb/src/display/video.rs +++ b/agb/src/display/video.rs @@ -1,4 +1,4 @@ -use super::{bitmap3::Bitmap3, bitmap4::Bitmap4, tiled0::Tiled0}; +use super::{background::BackgroundDistributor, bitmap3::Bitmap3, bitmap4::Bitmap4}; #[non_exhaustive] pub struct Video {} @@ -14,7 +14,7 @@ impl Video { unsafe { Bitmap4::new() } } - pub fn tiled0(&mut self) -> Tiled0 { - unsafe { Tiled0::new() } + pub fn tiled0(&mut self) -> BackgroundDistributor { + unsafe { BackgroundDistributor::new() } } } diff --git a/agb/src/number.rs b/agb/src/number.rs index bf9c57f3..2ff6e7e8 100644 --- a/agb/src/number.rs +++ b/agb/src/number.rs @@ -648,6 +648,83 @@ impl Rect { pub fn new(position: Vector2D, size: Vector2D) -> Self { Rect { position, size } } + + pub fn contains_point(&self, point: Vector2D) -> bool { + point.x > self.position.x + && point.x < self.position.x + self.size.x + && point.y > self.position.y + && point.y < self.position.y + self.size.y + } + + pub fn touches(self, other: Rect) -> bool { + self.position.x < other.position.x + other.size.x + && self.position.x + self.size.x > other.position.x + && self.position.y < other.position.y + other.size.y + && self.position.y + self.size.y > self.position.y + } + + pub fn overlapping_rect(&self, other: Rect) -> Rect { + fn max(x: E, y: E) -> E { + if x > y { + x + } else { + y + } + } + fn min(x: E, y: E) -> E { + if x > y { + y + } else { + x + } + } + + let top_left: Vector2D = ( + max(self.position.x, other.position.x), + max(self.position.y, other.position.y), + ) + .into(); + let bottom_right: Vector2D = ( + min( + self.position.x + self.size.x, + other.position.x + other.size.x, + ), + min( + self.position.y + self.size.y, + other.position.y + other.size.y, + ), + ) + .into(); + + Rect::new(top_left, bottom_right - top_left) + } +} + +impl Rect { + pub fn iter(self) -> impl Iterator { + let mut current_x = self.position.x; + let mut current_y = self.position.y; + let mut is_done = false; + core::iter::from_fn(move || { + if is_done { + None + } else { + let (old_x, old_y) = (current_x, current_y); + + current_x = current_x + T::one(); + if current_x > self.position.x + self.size.x { + current_x = self.position.x; + current_y = current_y + T::one(); + } + + if current_y > self.position.y + self.size.y { + is_done = true; + } + + Some((old_x, old_y)) + } + }) + } } impl Vector2D {