diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index cc5669a6..35eeed9e 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -133,8 +133,8 @@ fixed_width_signed_integer_impl!(i16); fixed_width_signed_integer_impl!(i32); /// A fixed point number represented using `I` with `N` bits of fractional precision -#[repr(C)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] pub struct Num(I); /// An often convenient representation for the Game Boy Advance using word sized @@ -571,7 +571,6 @@ impl Debug for Num { /// A vector of two points: (x, y) represened by integers or fixed point numbers #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] -#[repr(C)] pub struct Vector2D { /// The x coordinate pub x: T, @@ -689,6 +688,17 @@ impl Vector2D> { y: self.y.floor(), } } + + #[must_use] + /// Attempts to change the base returning None if the numbers cannot be represented + pub fn try_change_base, const M: usize>( + self, + ) -> Option>> { + Some(Vector2D::new( + self.x.try_change_base()?, + self.y.try_change_base()?, + )) + } } impl Vector2D> { diff --git a/agb/examples/affine_background.rs b/agb/examples/affine_background.rs index 7d783c25..69b63032 100644 --- a/agb/examples/affine_background.rs +++ b/agb/examples/affine_background.rs @@ -3,6 +3,7 @@ use agb::{ display::{ + affine::AffineMatrixBackground, tiled::{AffineBackgroundSize, TileFormat, TileSet, TiledMap}, Priority, }, @@ -32,8 +33,8 @@ fn main(mut gba: agb::Gba) -> ! { bg.commit(&mut vram); bg.show(); - let mut rotation: Num = num!(0.); - let rotation_increase = num!(1.); + let mut rotation = num!(0.); + let rotation_increase: Num = num!(0.01); let mut input = agb::input::ButtonController::new(); @@ -45,14 +46,19 @@ fn main(mut gba: agb::Gba) -> ! { scroll_x += input.x_tri() as i32; scroll_y += input.y_tri() as i32; - let scroll_pos = (scroll_x as i16, scroll_y as i16); - bg.set_scroll_pos(scroll_pos.into()); - bg.set_transform((0, 0), (1, 1), rotation); + let scroll_pos = (scroll_x, scroll_y).into(); rotation += rotation_increase; - if rotation >= num!(255.) { - rotation = 0.into(); - } + rotation = rotation.rem_euclid(1.into()); + + let transformation = AffineMatrixBackground::from_scale_rotation_position( + (0, 0).into(), + (1, 1).into(), + rotation, + scroll_pos, + ); + + bg.set_transform(transformation); vblank.wait_for_vblank(); bg.commit(&mut vram); diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 222293ec..2773a3a3 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -7,19 +7,82 @@ //! Affine matricies are used in two places on the GBA, for affine backgrounds //! and for affine objects. //! -//! # Linear Algebra basics -//! As a matrix, they can be manipulated using linear algebra, although you -//! shouldn't need to know linear algebra to use this apart from a few things +//! # Linear Algebra +//! As a matrix, they can be manipulated using linear algebra. The short version +//! of this section is to beware that the matrix is the inverse of the normal +//! transformation matricies. //! -//! If `A` and `B` are matricies, then matrix `C = A * B` represents the -//! transformation `A` performed on `B`, or alternatively `C` is transformation -//! `B` followed by transformation `A`. +//! One quick thing to point out at the start as it will become very relevant is +//! that matrix-matrix multiplication is not commutative, meaning swapping the +//! order changes the result, or **A** × **B** ≢ **B** × **A**. However, +//! matricies are, at least in the case they are used here, associative, meaning +//! (**AB**)**C** = **A**(**BC**). //! -//! Additionally matrix multiplication is not commutative, meaning swapping the -//! order changes the result, or `A * B ≢ B * A`. +//! ## Normal (wrong on GBA!) transformation matricies +//! +//! As a start, normal transformation matricies will transform a shape from it's +//! original position to it's new position. Generally when people talk about +//! transformation matricies they are talking about them in this sense. +//! +//! > If **A** and **B** are transformation matricies, then matrix **C** = **A** +//! > × **B** represents the transformation **A** performed on **B**, or +//! > alternatively **C** is transformation **B** followed by transformation +//! > **A**. +//! +//! This is not what they represent on the GBA! If you are looking up more +//! information about tranformation matricies bear this in mind. +//! +//! ## Correct (on GBA) transformation matricies +//! +//! On the GBA, the affine matrix works the other way around. The GBA wants to +//! know for each pixel what colour it should render, to do this it applies the +//! affine transformation matrix to the pixel it is rendering to lookup correct +//! pixel in the texture. +//! +//! This describes the inverse of the previously given transformation matricies. +//! +//! Above I described the matrix **C** = **A** × **B**, but what the GBA wants +//! is the inverse of **C**, or **C**-1 = (**AB**)-1 = +//! **B**-1 × **A**-1. This means that if we have the +//! matricies **I** and **J** in the form the GBA expects then +//! +//! > Transformation **K** = **I** × **J** is the transformation **I** followed +//! > by the transformation **J**. +//! +//! Beware if you are used to the other way around! +//! +//! ## Example, rotation around the center +//! +//! To rotate something around its center, you will need to move the thing such +//! that the center is at (0, 0) and then you can rotate it. After that you can +//! move it where you actually want it. +//! +//! These can be done in the order I stated, **A** = **Move To Origin** × +//! **Rotate** × **Move to Final Position**. Or in code, +//! +//! ```rust,no_run +//! # #![no_std] +//! # #![no_main] +//! use agb::fixnum::{Vector2D, Num, num}; +//! use agb::display::affine::AffineMatrix; +//! +//! # fn foo(_gba: &mut agb::Gba) { +//! // size of our thing is 10 pixels by 10 pixels +//! let size_of_thing: Vector2D> = (10, 10).into(); +//! // rotation by a quarter turn +//! let rotation: Num = num!(0.25); +//! // the final position +//! let position: Vector2D> = (100, 100).into(); +//! +//! // now lets calculate the final transformation matrix! +//! let a = AffineMatrix::from_translation(-size_of_thing / 2) +//! * AffineMatrix::from_rotation(rotation) +//! * AffineMatrix::from_translation(position); +//! # } +//! ``` use core::{ - convert::{TryFrom, TryInto}, + convert::TryFrom, ops::{Mul, MulAssign}, }; @@ -62,7 +125,7 @@ impl AffineMatrix { #[must_use] /// Generates the matrix that represents a rotation pub fn from_rotation(angle: Num) -> Self { - fn from_rotation(angle: Num) -> AffineMatrix { + fn from_rotation(angle: Num) -> AffineMatrix { let cos = angle.cos().change_base(); let sin = angle.sin().change_base(); @@ -71,8 +134,8 @@ impl AffineMatrix { // space rather than how you might conventionally think of it. AffineMatrix { a: cos, - b: sin, - c: -sin, + b: -sin, + c: sin, d: cos, x: 0.into(), y: 0.into(), @@ -90,27 +153,27 @@ impl AffineMatrix { b: 0.into(), c: 0.into(), d: 1.into(), - x: position.x, - y: position.y, + x: -position.x, + y: -position.y, } } #[must_use] /// The position fields of the matrix pub fn position(&self) -> Vector2D> { - (self.x, self.y).into() + (-self.x, -self.y).into() } /// Attempts to convert the matrix to one which can be used in affine /// backgrounds. pub fn try_to_background(&self) -> Result { Ok(AffineMatrixBackground { - a: self.a.to_raw().try_into().map_err(|_| OverflowError(()))?, - b: self.a.to_raw().try_into().map_err(|_| OverflowError(()))?, - c: self.a.to_raw().try_into().map_err(|_| OverflowError(()))?, - d: self.a.to_raw().try_into().map_err(|_| OverflowError(()))?, - x: self.a.to_raw(), - y: self.a.to_raw(), + a: self.a.try_change_base().ok_or(OverflowError(()))?, + b: self.b.try_change_base().ok_or(OverflowError(()))?, + c: self.c.try_change_base().ok_or(OverflowError(()))?, + d: self.d.try_change_base().ok_or(OverflowError(()))?, + x: self.x, + y: self.y, }) } @@ -119,28 +182,75 @@ impl AffineMatrix { /// wrapping any value which is too large to be represented there. pub fn to_background_wrapping(&self) -> AffineMatrixBackground { AffineMatrixBackground { - a: self.a.to_raw() as i16, - b: self.a.to_raw() as i16, - c: self.a.to_raw() as i16, - d: self.a.to_raw() as i16, - x: self.a.to_raw(), - y: self.a.to_raw(), + a: Num::from_raw(self.a.to_raw() as i16), + b: Num::from_raw(self.b.to_raw() as i16), + c: Num::from_raw(self.c.to_raw() as i16), + d: Num::from_raw(self.d.to_raw() as i16), + x: self.x, + y: self.y, } } + + /// Attempts to convert the matrix to one which can be used in affine + /// objects. + pub fn try_to_object(&self) -> Result { + Ok(AffineMatrixObject { + a: self.a.try_change_base().ok_or(OverflowError(()))?, + b: self.b.try_change_base().ok_or(OverflowError(()))?, + c: self.c.try_change_base().ok_or(OverflowError(()))?, + d: self.d.try_change_base().ok_or(OverflowError(()))?, + }) + } + + #[must_use] + /// Converts the matrix to one which can be used in affine objects + /// wrapping any value which is too large to be represented there. + pub fn to_object_wrapping(&self) -> AffineMatrixObject { + AffineMatrixObject { + a: Num::from_raw(self.a.to_raw() as i16), + b: Num::from_raw(self.b.to_raw() as i16), + c: Num::from_raw(self.c.to_raw() as i16), + d: Num::from_raw(self.d.to_raw() as i16), + } + } + + #[must_use] + /// Creates an affine matrix from a given (x, y) scaling. This will scale by + /// the inverse, ie (2, 2) will produce half the size. + pub fn from_scale(scale: Vector2D>) -> AffineMatrix { + AffineMatrix { + a: scale.x, + b: 0.into(), + c: 0.into(), + d: scale.y, + x: 0.into(), + y: 0.into(), + } + } +} + +impl Default for AffineMatrix { + fn default() -> Self { + AffineMatrix::identity() + } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(C, packed(4))] /// An affine matrix that can be used in affine backgrounds pub struct AffineMatrixBackground { - // Internally these can be thought of as Num - a: i16, - b: i16, - c: i16, - d: i16, - // These are Num - x: i32, - y: i32, + a: Num, + b: Num, + c: Num, + d: Num, + x: Num, + y: Num, +} + +impl Default for AffineMatrixBackground { + fn default() -> Self { + AffineMatrix::identity().to_background_wrapping() + } } impl TryFrom for AffineMatrixBackground { @@ -157,14 +267,49 @@ impl AffineMatrixBackground { /// calculations. pub fn to_affine_matrix(&self) -> AffineMatrix { AffineMatrix { - a: Num::from_raw(self.a.into()), - b: Num::from_raw(self.b.into()), - c: Num::from_raw(self.c.into()), - d: Num::from_raw(self.d.into()), - x: Num::from_raw(self.x), - y: Num::from_raw(self.y), + a: self.a.change_base(), + b: self.b.change_base(), + c: self.c.change_base(), + d: self.d.change_base(), + x: self.x, + y: self.y, } } + + #[must_use] + /// Creates a transformation matrix using GBA specific syscalls. + /// This can be done using the standard transformation matricies like + /// + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # use agb_fixnum::{Vector2D, Num}; + /// use agb::display::affine::AffineMatrix; + /// # fn from_scale_rotation_position( + /// # transform_origin: Vector2D>, + /// # scale: Vector2D>, + /// # rotation: Num, + /// # position: Vector2D>, + /// # ) { + /// let A = AffineMatrix::from_translation(-transform_origin) + /// * AffineMatrix::from_scale(scale) + /// * AffineMatrix::from_rotation(rotation) + /// * AffineMatrix::from_translation(position); + /// # } + /// ``` + pub fn from_scale_rotation_position( + transform_origin: Vector2D>, + scale: Vector2D>, + rotation: Num, + position: Vector2D>, + ) -> Self { + crate::syscall::bg_affine_matrix( + transform_origin, + position.try_change_base::().unwrap().floor(), + scale.try_change_base().unwrap(), + rotation.rem_euclid(1.into()).try_change_base().unwrap(), + ) + } } impl From for AffineMatrix { @@ -173,9 +318,49 @@ impl From for AffineMatrix { } } -impl Default for AffineMatrix { +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[repr(C, packed(4))] +/// An affine matrix that can be used in affine objects +pub struct AffineMatrixObject { + a: Num, + b: Num, + c: Num, + d: Num, +} + +impl Default for AffineMatrixObject { fn default() -> Self { - AffineMatrix::identity() + AffineMatrix::identity().to_object_wrapping() + } +} + +impl TryFrom for AffineMatrixObject { + type Error = OverflowError; + + fn try_from(value: AffineMatrix) -> Result { + value.try_to_object() + } +} + +impl AffineMatrixObject { + #[must_use] + /// Converts to the affine matrix that is usable in performing efficient + /// calculations. + pub fn to_affine_matrix(&self) -> AffineMatrix { + AffineMatrix { + a: self.a.change_base(), + b: self.b.change_base(), + c: self.c.change_base(), + d: self.d.change_base(), + x: 0.into(), + y: 0.into(), + } + } +} + +impl From for AffineMatrix { + fn from(mat: AffineMatrixObject) -> Self { + mat.to_affine_matrix() } } @@ -183,7 +368,7 @@ impl Mul for AffineMatrix { type Output = Self; fn mul(self, rhs: Self) -> Self::Output { AffineMatrix { - a: self.a * rhs.a + self.b + rhs.c, + a: self.a * rhs.a + self.b * rhs.c, b: self.a * rhs.b + self.b * rhs.d, c: self.c * rhs.a + self.d * rhs.c, d: self.c * rhs.b + self.d * rhs.d, @@ -193,26 +378,6 @@ impl Mul for AffineMatrix { } } -impl Mul> for AffineMatrix { - type Output = Self; - fn mul(self, rhs: Num) -> Self::Output { - self * AffineMatrix { - a: rhs, - b: 0.into(), - c: 0.into(), - d: rhs, - x: 0.into(), - y: 0.into(), - } - } -} - -impl MulAssign> for AffineMatrix { - fn mul_assign(&mut self, rhs: Num) { - *self = *self * rhs; - } -} - impl MulAssign for AffineMatrix { fn mul_assign(&mut self, rhs: Self) { *self = *self * rhs; diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 01771931..4f3b18ab 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -19,7 +19,7 @@ use super::{Priority, DISPLAY_CONTROL}; use crate::agb_alloc::block_allocator::BlockAllocator; use crate::agb_alloc::bump_allocator::StartEnd; use crate::dma; -use crate::fixnum::{Num, Vector2D}; +use crate::fixnum::Vector2D; use crate::hash_map::HashMap; use attributes::*; @@ -1236,42 +1236,6 @@ enum ColourMode { Eight, } -/// The parameters used for the PPU's affine transformation function -/// that can apply to objects and background layers in modes 1 and 2. -/// This can be obtained from X/Y scale and rotation angle with -/// [`agb::syscall::affine_matrix`]. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(C, packed(4))] -pub struct AffineMatrixAttributes { - /// Adjustment made to *X* coordinate when drawing *horizontal* lines. - /// Also known as "dx". - /// Typically computed as `x_scale * cos(angle)`. - pub p_a: Num, - /// Adjustment made to *X* coordinate along *vertical* lines. - /// Also known as "dmx". - /// Typically computed as `y_scale * sin(angle)`. - pub p_b: Num, - /// Adjustment made to *Y* coordinate along *horizontal* lines. - /// Also known as "dy". - /// Typically computed as `-x_scale * sin(angle)`. - pub p_c: Num, - /// Adjustment made to *Y* coordinate along *vertical* lines. - /// Also known as "dmy". - /// Typically computed as `y_scale * cos(angle)`. - pub p_d: Num, -} - -impl Default for AffineMatrixAttributes { - fn default() -> Self { - Self { - p_a: 1.into(), - p_b: Default::default(), - p_c: Default::default(), - p_d: 1.into(), - } - } -} - // this mod is not public, so the internal parts don't need documenting. #[allow(dead_code)] mod attributes { diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index bfe3399e..38a8a483 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -2,9 +2,10 @@ use core::cell::RefCell; use core::ops::{Deref, DerefMut}; use crate::bitarray::Bitarray; -use crate::display::{object::AffineMatrixAttributes, Priority, DISPLAY_CONTROL}; +use crate::display::affine::AffineMatrixBackground; +use crate::display::{Priority, DISPLAY_CONTROL}; use crate::dma::dma_copy16; -use crate::fixnum::{Num, Vector2D}; +use crate::fixnum::Vector2D; use crate::memory_mapped::MemoryMapped; use super::{ @@ -12,7 +13,6 @@ use super::{ RegularBackgroundSize, Tile, TileFormat, TileIndex, TileSet, TileSetting, VRamManager, }; -use crate::syscall::BgAffineSetData; use alloc::{vec, vec::Vec}; pub trait TiledMapTypes: private::Sealed { @@ -247,7 +247,7 @@ pub struct AffineMap { scroll: Vector2D, - transform: BgAffineSetData, + transform: AffineMatrixBackground, tiles: Vec, tiles_dirty: bool, @@ -259,7 +259,7 @@ impl TiledMapTypes for AffineMap { impl TiledMapPrivate for AffineMap { type TileType = u8; - type AffineMatrix = AffineMatrixAttributes; + type AffineMatrix = AffineMatrixBackground; fn tiles_mut(&mut self) -> &mut [Self::TileType] { &mut self.tiles @@ -280,10 +280,7 @@ impl TiledMapPrivate for AffineMap { self.size } fn update_bg_registers(&self) { - let register_pos = self.transform.position; - self.bg_x().set(register_pos.x); - self.bg_y().set(register_pos.y); - self.bg_affine_matrix().set(self.transform.matrix); + self.bg_affine_matrix().set(self.transform); } fn scroll_pos(&self) -> Vector2D { self.scroll @@ -347,25 +344,11 @@ impl AffineMap { *self.tiles_dirty() = true; } - pub fn set_transform( - &mut self, - transform_origin: impl Into>>, - scale: impl Into>>, - rotation: impl Into>, - ) { - let scale = scale.into(); - let rotation = rotation.into(); - self.transform = - crate::syscall::bg_affine_matrix(transform_origin.into(), self.scroll, scale, rotation); + pub fn set_transform(&mut self, transformation: impl Into) { + self.transform = transformation.into(); } - fn bg_x(&self) -> MemoryMapped> { - unsafe { MemoryMapped::new(0x0400_0008 + 0x10 * self.background_id()) } - } - fn bg_y(&self) -> MemoryMapped> { - unsafe { MemoryMapped::new(0x0400_000c + 0x10 * self.background_id()) } - } - fn bg_affine_matrix(&self) -> MemoryMapped { + fn bg_affine_matrix(&self) -> MemoryMapped { unsafe { MemoryMapped::new(0x0400_0000 + 0x10 * self.background_id()) } } } diff --git a/agb/src/syscall.rs b/agb/src/syscall.rs index 3ab60830..5d8f66a2 100644 --- a/agb/src/syscall.rs +++ b/agb/src/syscall.rs @@ -2,7 +2,7 @@ use agb_fixnum::Vector2D; use core::arch::asm; use core::mem::MaybeUninit; -use crate::display::object::AffineMatrixAttributes; +use crate::display::affine::AffineMatrixBackground; use crate::fixnum::Num; #[allow(non_snake_case)] @@ -139,40 +139,33 @@ pub fn arc_tan2(x: i16, y: i32) -> i16 { result } -#[repr(C, packed(4))] -pub struct BgAffineSetData { - pub matrix: AffineMatrixAttributes, - pub position: Vector2D>, -} -impl Default for BgAffineSetData { - fn default() -> Self { - Self { - matrix: AffineMatrixAttributes::default(), - position: (0, 0).into(), - } - } -} - -/// `rotation` is in revolutions. +/// `rotation` is in revolutions. It is hard to create the rotation, usually +/// you'll go in from a larger sized type. #[must_use] -pub fn bg_affine_matrix( +pub(crate) fn bg_affine_matrix( bg_center: Vector2D>, display_center: Vector2D, scale: Vector2D>, - rotation: Num, -) -> BgAffineSetData { + rotation: Num, +) -> AffineMatrixBackground { #[repr(C, packed(4))] struct Input { - bg_center: Vector2D>, - display_center: Vector2D, - scale: Vector2D>, - rotation: Num, + bg_center_x: Num, + bg_center_y: Num, + display_center_x: i16, + display_center_y: i16, + scale_x: Num, + scale_y: Num, + rotation: Num, } let input = Input { - bg_center, - display_center, - scale, + bg_center_x: bg_center.x, + bg_center_y: bg_center.y, + display_center_x: display_center.x, + display_center_y: display_center.y, + scale_x: scale.x, + scale_y: scale.y, rotation, }; @@ -193,56 +186,12 @@ pub fn bg_affine_matrix( unsafe { output.assume_init() } } -/// `rotation` is in revolutions. -#[must_use] -pub fn obj_affine_matrix( - scale: Vector2D>, - rotation: Num, -) -> AffineMatrixAttributes { - #[allow(dead_code)] - #[repr(C, packed(4))] - struct Input { - scale: Vector2D>, - rotation: u16, - } - - let input = Input { - scale, - rotation: u16::from(rotation.to_raw()) << 8, - }; - - let mut output = MaybeUninit::uninit(); - - unsafe { - asm!( - "swi {SWI}", - SWI = const { swi_map(0x0F) }, - in("r0") &input as *const Input, - in("r1") output.as_mut_ptr(), - in("r2") 1, - in("r3") 2, - ); - } - - unsafe { output.assume_init() } -} - #[cfg(test)] mod tests { + use crate::display::affine::AffineMatrix; + use super::*; - #[test_case] - fn affine_obj(_gba: &mut crate::Gba) { - // expect identity matrix - let one: Num = 1.into(); - - let aff = obj_affine_matrix((one, one).into(), Num::default()); - let (p_a, p_d) = (aff.p_a, aff.p_d); - - assert_eq!(p_a, one); - assert_eq!(p_d, one); - } - #[test_case] fn affine_bg(_gba: &mut crate::Gba) { // expect the identity matrix @@ -250,14 +199,10 @@ mod tests { (0, 0).into(), (0i16, 0i16).into(), (1i16, 1i16).into(), - 0.into(), + Default::default(), ); - let matrix = aff.matrix; - let (p_a, p_b, p_c, p_d) = (matrix.p_a, matrix.p_b, matrix.p_c, matrix.p_d); - assert_eq!(p_a, 1.into()); - assert_eq!(p_b, 0.into()); - assert_eq!(p_c, 0.into()); - assert_eq!(p_d, 1.into()); + let matrix = aff.to_affine_matrix(); + assert_eq!(matrix, AffineMatrix::identity()); } }