From 3497f7a720ca3fee8d200249b5ea7a92794f4ff3 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 00:42:14 +0100 Subject: [PATCH 01/16] switch to using new affine matrix --- agb/src/display/affine.rs | 94 +++++++++++++++++++++++++++++++----- agb/src/display/object.rs | 38 +-------------- agb/src/display/tiled/map.rs | 21 +++----- agb/src/syscall.rs | 38 ++++----------- 4 files changed, 98 insertions(+), 93 deletions(-) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 222293ec..ac2294e8 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -106,11 +106,11 @@ impl AffineMatrix { 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(), + b: self.b.to_raw().try_into().map_err(|_| OverflowError(()))?, + c: self.c.to_raw().try_into().map_err(|_| OverflowError(()))?, + d: self.d.to_raw().try_into().map_err(|_| OverflowError(()))?, + x: self.x.to_raw(), + y: self.y.to_raw(), }) } @@ -120,11 +120,34 @@ impl AffineMatrix { 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(), + b: self.b.to_raw() as i16, + c: self.c.to_raw() as i16, + d: self.d.to_raw() as i16, + x: self.x.to_raw(), + y: self.y.to_raw(), + } + } + + /// 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.to_raw().try_into().map_err(|_| OverflowError(()))?, + b: self.b.to_raw().try_into().map_err(|_| OverflowError(()))?, + c: self.c.to_raw().try_into().map_err(|_| OverflowError(()))?, + d: self.d.to_raw().try_into().map_err(|_| 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: self.a.to_raw() as i16, + b: self.b.to_raw() as i16, + c: self.c.to_raw() as i16, + d: self.d.to_raw() as i16, } } } @@ -143,6 +166,12 @@ pub struct AffineMatrixBackground { y: i32, } +impl Default for AffineMatrixBackground { + fn default() -> Self { + AffineMatrix::identity().to_background_wrapping() + } +} + impl TryFrom for AffineMatrixBackground { type Error = OverflowError; @@ -173,9 +202,50 @@ 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 { + // Internally these can be thought of as Num + a: i16, + b: i16, + c: i16, + d: i16, +} + +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: 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: 0.into(), + y: 0.into(), + } + } +} + +impl From for AffineMatrix { + fn from(mat: AffineMatrixObject) -> Self { + mat.to_affine_matrix() } } diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index f33c6430..19274e3e 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -18,7 +18,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::*; @@ -1193,42 +1193,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..50cab171 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -2,7 +2,8 @@ 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::memory_mapped::MemoryMapped; @@ -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 @@ -359,13 +356,7 @@ impl AffineMap { crate::syscall::bg_affine_matrix(transform_origin.into(), self.scroll, scale, rotation); } - 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..1d0c869c 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, AffineMatrixObject}; use crate::fixnum::Num; #[allow(non_snake_case)] @@ -139,20 +139,6 @@ 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. #[must_use] pub fn bg_affine_matrix( @@ -160,7 +146,7 @@ pub fn bg_affine_matrix( display_center: Vector2D, scale: Vector2D>, rotation: Num, -) -> BgAffineSetData { +) -> AffineMatrixBackground { #[repr(C, packed(4))] struct Input { bg_center: Vector2D>, @@ -195,10 +181,7 @@ pub fn bg_affine_matrix( /// `rotation` is in revolutions. #[must_use] -pub fn obj_affine_matrix( - scale: Vector2D>, - rotation: Num, -) -> AffineMatrixAttributes { +pub fn obj_affine_matrix(scale: Vector2D>, rotation: Num) -> AffineMatrixObject { #[allow(dead_code)] #[repr(C, packed(4))] struct Input { @@ -229,6 +212,8 @@ pub fn obj_affine_matrix( #[cfg(test)] mod tests { + use crate::display::affine::AffineMatrix; + use super::*; #[test_case] @@ -237,10 +222,9 @@ mod tests { 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); + let matrix = aff.to_affine_matrix(); - assert_eq!(p_a, one); - assert_eq!(p_d, one); + assert_eq!(matrix, AffineMatrix::identity()); } #[test_case] @@ -253,11 +237,7 @@ mod tests { 0.into(), ); - 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()); } } From c02454ef16f098f0cf3b76ad2330955f60d8d8e5 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 00:42:21 +0100 Subject: [PATCH 02/16] remove repr guarentee --- agb-fixnum/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index cc5669a6..297d1615 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -133,7 +133,6 @@ 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)] pub struct Num(I); @@ -571,7 +570,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, From b43408e6e1c69d6b71ff02069649c5f731817020 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 00:50:41 +0100 Subject: [PATCH 03/16] reimplement default --- agb/src/display/affine.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index ac2294e8..6a38fa9e 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -152,6 +152,12 @@ impl AffineMatrix { } } +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 From 963fbc8c23f65eccd4d8e299860c2c43dc798e32 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 00:53:48 +0100 Subject: [PATCH 04/16] should be a multiply --- agb/src/display/affine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 6a38fa9e..53d128d7 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -259,7 +259,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, From 002d409db8012f0fcf5a3c7ac7decebae4e9e8f1 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 00:56:34 +0100 Subject: [PATCH 05/16] this is not valid because we don't track the lower right entry for rotation and translation this remains as 1, but it should change for scaling --- agb/src/display/affine.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 53d128d7..4145d0a9 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -269,25 +269,25 @@ 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 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: Num) { +// *self = *self * rhs; +// } +// } impl MulAssign for AffineMatrix { fn mul_assign(&mut self, rhs: Self) { From b9481116f1a8fc3eba1370f9abbcd321e88ede11 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 01:00:56 +0100 Subject: [PATCH 06/16] reintroduce C repr for Num --- agb-fixnum/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 297d1615..c17ce0af 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -134,6 +134,7 @@ fixed_width_signed_integer_impl!(i32); /// A fixed point number represented using `I` with `N` bits of fractional precision #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(C)] pub struct Num(I); /// An often convenient representation for the Game Boy Advance using word sized From 0be595b47e77b00f68c4d6670e6c909f0fe15e65 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 11:31:28 +0100 Subject: [PATCH 07/16] make guarentee even stronger --- agb-fixnum/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index c17ce0af..e20bda94 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -134,7 +134,7 @@ fixed_width_signed_integer_impl!(i32); /// A fixed point number represented using `I` with `N` bits of fractional precision #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(C)] +#[repr(transparent)] pub struct Num(I); /// An often convenient representation for the Game Boy Advance using word sized From dcdf678126e7e54a3ba44a949affd5f70dd2ea17 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 11:31:54 +0100 Subject: [PATCH 08/16] completely remove scalar mul --- agb/src/display/affine.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 4145d0a9..3d74c936 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -269,26 +269,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; From c69fdd7bec33061c2419f0cfe283de85d9efd9ed Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 11:41:31 +0100 Subject: [PATCH 09/16] switch to using fixed point numbers in gba representation --- agb/src/display/affine.rs | 85 +++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 3d74c936..f62fa1a1 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -19,7 +19,7 @@ //! order changes the result, or `A * B ≢ B * A`. use core::{ - convert::{TryFrom, TryInto}, + convert::TryFrom, ops::{Mul, MulAssign}, }; @@ -105,12 +105,12 @@ impl AffineMatrix { /// backgrounds. pub fn try_to_background(&self) -> Result { Ok(AffineMatrixBackground { - a: self.a.to_raw().try_into().map_err(|_| OverflowError(()))?, - b: self.b.to_raw().try_into().map_err(|_| OverflowError(()))?, - c: self.c.to_raw().try_into().map_err(|_| OverflowError(()))?, - d: self.d.to_raw().try_into().map_err(|_| OverflowError(()))?, - x: self.x.to_raw(), - y: self.y.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,12 +119,12 @@ 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.b.to_raw() as i16, - c: self.c.to_raw() as i16, - d: self.d.to_raw() as i16, - x: self.x.to_raw(), - y: self.y.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, } } @@ -132,10 +132,10 @@ impl AffineMatrix { /// objects. pub fn try_to_object(&self) -> Result { Ok(AffineMatrixObject { - a: self.a.to_raw().try_into().map_err(|_| OverflowError(()))?, - b: self.b.to_raw().try_into().map_err(|_| OverflowError(()))?, - c: self.c.to_raw().try_into().map_err(|_| OverflowError(()))?, - d: self.d.to_raw().try_into().map_err(|_| OverflowError(()))?, + 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(()))?, }) } @@ -144,10 +144,10 @@ impl AffineMatrix { /// wrapping any value which is too large to be represented there. pub fn to_object_wrapping(&self) -> AffineMatrixObject { AffineMatrixObject { - a: self.a.to_raw() as i16, - b: self.b.to_raw() as i16, - c: self.c.to_raw() as i16, - d: self.d.to_raw() as i16, + 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), } } } @@ -162,14 +162,12 @@ impl Default for AffineMatrix { #[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 { @@ -192,12 +190,12 @@ 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, } } } @@ -212,11 +210,10 @@ impl From for AffineMatrix { #[repr(C, packed(4))] /// An affine matrix that can be used in affine objects pub struct AffineMatrixObject { - // Internally these can be thought of as Num - a: i16, - b: i16, - c: i16, - d: i16, + a: Num, + b: Num, + c: Num, + d: Num, } impl Default for AffineMatrixObject { @@ -239,10 +236,10 @@ impl AffineMatrixObject { /// 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()), + 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(), } From ed1e8bcbdc4214ae20332b68d38764b65b3f8396 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 16:59:49 +0100 Subject: [PATCH 10/16] use new affine stuff in map --- agb-fixnum/src/lib.rs | 11 +++++++ agb/examples/affine_background.rs | 22 ++++++++----- agb/src/display/affine.rs | 55 +++++++++++++++++++++++++++---- agb/src/display/tiled/map.rs | 12 ++----- agb/src/syscall.rs | 22 ++++++++----- 5 files changed, 90 insertions(+), 32 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index e20bda94..35eeed9e 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -688,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..e5d64d09 100644 --- a/agb/examples/affine_background.rs +++ b/agb/examples/affine_background.rs @@ -3,6 +3,7 @@ use agb::{ display::{ + affine::{AffineMatrix, 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 f62fa1a1..8fd23f73 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -62,7 +62,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 +71,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,15 +90,15 @@ 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 @@ -150,6 +150,20 @@ impl AffineMatrix { 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 { @@ -198,6 +212,35 @@ impl AffineMatrixBackground { 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 + /// use agb::display::affine::AffineMatrix; + /// # #![no_std] + /// # #![no_main] + /// # fn something() { + /// 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 { diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index 50cab171..43302806 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -344,16 +344,8 @@ 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_affine_matrix(&self) -> MemoryMapped { diff --git a/agb/src/syscall.rs b/agb/src/syscall.rs index 1d0c869c..14a0451c 100644 --- a/agb/src/syscall.rs +++ b/agb/src/syscall.rs @@ -145,20 +145,26 @@ pub fn bg_affine_matrix( bg_center: Vector2D>, display_center: Vector2D, scale: Vector2D>, - rotation: Num, + 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, }; From 89107f7a4e7974a594cacb59225e3b8c9efeb4d9 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 16:59:58 +0100 Subject: [PATCH 11/16] correct (and much better) docs --- agb/src/display/affine.rs | 79 +++++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 8fd23f73..e05ce53c 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -7,16 +7,79 @@ //! 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, From fb08d7e208631be4d83e22dd0c6e826a7986820b Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 17:05:06 +0100 Subject: [PATCH 12/16] remove unused import --- agb/src/display/tiled/map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index 43302806..38a8a483 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -5,7 +5,7 @@ use crate::bitarray::Bitarray; 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::{ From 6d7bc75bbb71b87b5ddeeff4da895f826ffd77a4 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 17:21:21 +0100 Subject: [PATCH 13/16] remove another unused import (in the classic case of dev by CI) --- agb/examples/affine_background.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb/examples/affine_background.rs b/agb/examples/affine_background.rs index e5d64d09..69b63032 100644 --- a/agb/examples/affine_background.rs +++ b/agb/examples/affine_background.rs @@ -3,7 +3,7 @@ use agb::{ display::{ - affine::{AffineMatrix, AffineMatrixBackground}, + affine::AffineMatrixBackground, tiled::{AffineBackgroundSize, TileFormat, TileSet, TiledMap}, Priority, }, From f6dd65ae52d631275d4a1bb8fcfb981e4bc8967c Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 17:21:30 +0100 Subject: [PATCH 14/16] fix test --- agb/src/syscall.rs | 49 ++++------------------------------------------ 1 file changed, 4 insertions(+), 45 deletions(-) diff --git a/agb/src/syscall.rs b/agb/src/syscall.rs index 14a0451c..5b57f006 100644 --- a/agb/src/syscall.rs +++ b/agb/src/syscall.rs @@ -139,9 +139,10 @@ pub fn arc_tan2(x: i16, y: i32) -> i16 { result } -/// `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>, @@ -185,54 +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) -> AffineMatrixObject { - #[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 matrix = aff.to_affine_matrix(); - - assert_eq!(matrix, AffineMatrix::identity()); - } - #[test_case] fn affine_bg(_gba: &mut crate::Gba) { // expect the identity matrix @@ -240,7 +199,7 @@ mod tests { (0, 0).into(), (0i16, 0i16).into(), (1i16, 1i16).into(), - 0.into(), + Default::default(), ); let matrix = aff.to_affine_matrix(); From 4dfa6c25a588a20d05d772bcfb616d037a7c445f Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 17:27:07 +0100 Subject: [PATCH 15/16] REMOVE UNUSED IMPORT THAT I JUST MADE --- agb/src/syscall.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb/src/syscall.rs b/agb/src/syscall.rs index 5b57f006..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::affine::{AffineMatrixBackground, AffineMatrixObject}; +use crate::display::affine::AffineMatrixBackground; use crate::fixnum::Num; #[allow(non_snake_case)] From ae8d2b2fa0a14ff2ea04e9e2c162ab5c55b9b9a8 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 9 Oct 2022 17:52:24 +0100 Subject: [PATCH 16/16] fix broken docs --- agb/src/display/affine.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index e05ce53c..2773a3a3 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -68,7 +68,7 @@ //! //! # fn foo(_gba: &mut agb::Gba) { //! // size of our thing is 10 pixels by 10 pixels -//! let size_of_thing: Vector2D> = (10, 10).into() +//! let size_of_thing: Vector2D> = (10, 10).into(); //! // rotation by a quarter turn //! let rotation: Num = num!(0.25); //! // the final position @@ -281,10 +281,16 @@ impl AffineMatrixBackground { /// This can be done using the standard transformation matricies like /// /// ```rust,no_run - /// use agb::display::affine::AffineMatrix; /// # #![no_std] /// # #![no_main] - /// # fn something() { + /// # 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)