From 99f01caea4245b1a9a85c98c7a86f772c921a631 Mon Sep 17 00:00:00 2001 From: Corwin Date: Fri, 7 Oct 2022 19:21:53 +0100 Subject: [PATCH 1/9] some fun affine matrix functions! --- agb/src/display/affine.rs | 177 ++++++++++++++++++++++++++++++++++++++ agb/src/display/mod.rs | 1 + 2 files changed, 178 insertions(+) create mode 100644 agb/src/display/affine.rs diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs new file mode 100644 index 00000000..12b9390c --- /dev/null +++ b/agb/src/display/affine.rs @@ -0,0 +1,177 @@ +use core::{ + convert::TryInto, + ops::{Mul, MulAssign}, +}; + +use agb_fixnum::{Num, Vector2D}; + +type AffineMatrixElement = Num; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct AffineMatrix { + a: AffineMatrixElement, + b: AffineMatrixElement, + c: AffineMatrixElement, + d: AffineMatrixElement, + x: AffineMatrixElement, + y: AffineMatrixElement, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OverflowError(pub(crate) ()); + +impl AffineMatrix { + #[must_use] + pub fn identity() -> Self { + AffineMatrix { + a: 1.into(), + b: 0.into(), + c: 0.into(), + d: 1.into(), + x: 0.into(), + y: 0.into(), + } + } + + #[must_use] + pub fn from_rotation(angle: Num) -> Self { + let cos = angle.cos().change_base(); + let sin = angle.sin().change_base(); + + AffineMatrix { + a: cos, + b: sin, + c: -sin, + d: cos, + x: 0.into(), + y: 0.into(), + } + } + + // Identity for rotation / scale / skew + #[must_use] + pub fn from_position(position: Vector2D>) -> Self { + AffineMatrix { + a: 1.into(), + b: 0.into(), + c: 0.into(), + d: 1.into(), + x: position.x, + y: position.y, + } + } + + #[must_use] + pub fn position(&self) -> Vector2D> { + (self.x, self.y).into() + } + + #[must_use] + pub fn try_to_background(&self) -> Option { + Some(AffineMatrixBackground { + a: self.a.to_raw().try_into().ok()?, + b: self.a.to_raw().try_into().ok()?, + c: self.a.to_raw().try_into().ok()?, + d: self.a.to_raw().try_into().ok()?, + x: self.a.to_raw(), + y: self.a.to_raw(), + }) + } + + #[must_use] + 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(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[repr(C, packed(4))] +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, +} + +impl AffineMatrixBackground { + #[must_use] + 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), + } + } +} + +impl From for AffineMatrix { + fn from(mat: AffineMatrixBackground) -> Self { + mat.to_affine_matrix() + } +} + +impl Default for AffineMatrix { + fn default() -> Self { + AffineMatrix::identity() + } +} + +impl Mul for AffineMatrix { + type Output = Self; + fn mul(self, rhs: Self) -> Self::Output { + AffineMatrix { + 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, + x: self.a * rhs.x + self.b * rhs.y + self.x, + y: self.c * rhs.x + self.d * rhs.y + self.y, + } + } +} + +impl MulAssign for AffineMatrix { + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +#[cfg(test)] +mod tests { + use crate::fixnum::num; + + use super::*; + + #[test_case] + fn test_simple_multiply(_: &mut crate::Gba) { + let position = (20, 10).into(); + + let a = AffineMatrix::from_position(position); + let b = AffineMatrix::default(); + + let c = a * b; + + assert_eq!(c.position(), position); + + let d = AffineMatrix::from_rotation(num!(0.5)); + + let e = a * d; + + assert_eq!(e.position(), position); + assert_eq!(d * d, AffineMatrix::identity()); + } +} diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index 620c1070..ab0110eb 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -25,6 +25,7 @@ pub mod video; pub mod blend; pub mod window; +pub mod affine; mod font; pub use font::{Font, FontLetter}; From d83f0ea7100306e452fe47c083da3398adda280a Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 8 Oct 2022 18:07:29 +0100 Subject: [PATCH 2/9] from rotation accept generic fixnum --- agb/src/display/affine.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 12b9390c..c7c0728d 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -34,18 +34,21 @@ impl AffineMatrix { } #[must_use] - pub fn from_rotation(angle: Num) -> Self { - let cos = angle.cos().change_base(); - let sin = angle.sin().change_base(); + pub fn from_rotation(angle: Num) -> Self { + fn from_rotation(angle: Num) -> AffineMatrix { + let cos = angle.cos().change_base(); + let sin = angle.sin().change_base(); - AffineMatrix { - a: cos, - b: sin, - c: -sin, - d: cos, - x: 0.into(), - y: 0.into(), + AffineMatrix { + a: cos, + b: sin, + c: -sin, + d: cos, + x: 0.into(), + y: 0.into(), + } } + from_rotation(angle.rem_euclid(1.into()).change_base()) } // Identity for rotation / scale / skew @@ -167,7 +170,7 @@ mod tests { assert_eq!(c.position(), position); - let d = AffineMatrix::from_rotation(num!(0.5)); + let d = AffineMatrix::from_rotation::<2>(num!(0.5)); let e = a * d; From 5e8a50159ee4dd084b58bf41c396050f305f1ad3 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 8 Oct 2022 19:54:30 +0100 Subject: [PATCH 3/9] implement try from --- agb/src/display/affine.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index c7c0728d..ea4ee22b 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -1,5 +1,5 @@ use core::{ - convert::TryInto, + convert::{TryFrom, TryInto}, ops::{Mul, MulAssign}, }; @@ -69,13 +69,12 @@ impl AffineMatrix { (self.x, self.y).into() } - #[must_use] - pub fn try_to_background(&self) -> Option { - Some(AffineMatrixBackground { - a: self.a.to_raw().try_into().ok()?, - b: self.a.to_raw().try_into().ok()?, - c: self.a.to_raw().try_into().ok()?, - d: self.a.to_raw().try_into().ok()?, + 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(), }) @@ -107,6 +106,14 @@ pub struct AffineMatrixBackground { y: i32, } +impl TryFrom for AffineMatrixBackground { + type Error = OverflowError; + + fn try_from(value: AffineMatrix) -> Result { + value.try_to_background() + } +} + impl AffineMatrixBackground { #[must_use] pub fn to_affine_matrix(&self) -> AffineMatrix { From c09c0b77f4ae608a84ad5c0f6388a6153e235316 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 8 Oct 2022 20:32:45 +0100 Subject: [PATCH 4/9] add docs --- agb/src/display/affine.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index ea4ee22b..1a96a817 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -1,3 +1,23 @@ +#![warn(missing_docs)] +//! # Affine matricies for the Game Boy Advance +//! +//! An affine matrix represents an affine transformation, an affine +//! transformation being one which preserves parallel lines (note that this +//! therefore cannot represent perspective seen in games like Super Mario Kart). +//! 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 +//! +//! 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`. +//! +//! Additionally matrix multiplication is not commutative, meaning swapping the +//! order changes the result, or `A * B ≢ B * A`. + use core::{ convert::{TryFrom, TryInto}, ops::{Mul, MulAssign}, @@ -8,6 +28,8 @@ use agb_fixnum::{Num, Vector2D}; type AffineMatrixElement = Num; #[derive(Debug, PartialEq, Eq, Clone, Copy)] +/// An affine matrix stored in a way that is efficient for the GBA to perform +/// operations on. This implements multiplication. pub struct AffineMatrix { a: AffineMatrixElement, b: AffineMatrixElement, @@ -18,10 +40,14 @@ pub struct AffineMatrix { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The error emitted upon a conversion that could not be performed due to +/// overflowing the destination data size pub struct OverflowError(pub(crate) ()); impl AffineMatrix { #[must_use] + /// The Identity matrix. The identity matrix can be thought of as 1 and is + /// represented by `I`. For a matrix `A`, `A ≡ A * I ≡ I * A`. pub fn identity() -> Self { AffineMatrix { a: 1.into(), @@ -34,6 +60,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 { let cos = angle.cos().change_base(); @@ -52,6 +79,7 @@ impl AffineMatrix { } // Identity for rotation / scale / skew + /// Generates the matrix that represents a translation by the position #[must_use] pub fn from_position(position: Vector2D>) -> Self { AffineMatrix { @@ -65,10 +93,13 @@ impl AffineMatrix { } #[must_use] + /// The position fields of the matrix pub fn position(&self) -> Vector2D> { (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(()))?, @@ -81,6 +112,8 @@ impl AffineMatrix { } #[must_use] + /// Converts the matrix to one which can be used in affine backgrounds + /// 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, @@ -95,6 +128,7 @@ impl AffineMatrix { #[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, @@ -116,6 +150,8 @@ impl TryFrom for AffineMatrixBackground { impl AffineMatrixBackground { #[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()), From 6927f845977b53d2b34229419d08d5f807b03ae6 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 8 Oct 2022 23:30:14 +0100 Subject: [PATCH 5/9] deny undocumented --- 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 1a96a817..31b9e46a 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -1,4 +1,4 @@ -#![warn(missing_docs)] +#![deny(missing_docs)] //! # Affine matricies for the Game Boy Advance //! //! An affine matrix represents an affine transformation, an affine From c33f99aaea280bc84c7bb8143353de2983884f92 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 8 Oct 2022 23:30:29 +0100 Subject: [PATCH 6/9] add comment on what the matrix represents --- agb/src/display/affine.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 31b9e46a..8b7877cd 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -66,6 +66,9 @@ impl AffineMatrix { let cos = angle.cos().change_base(); let sin = angle.sin().change_base(); + // This might look backwards, but the gba does texture mapping, ie a + // point in screen base is transformed using the matrix to graphics + // space rather than how you might conventionally think of it. AffineMatrix { a: cos, b: sin, From a41e85302a547c1107bc9644f00e143e5df0febf Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 8 Oct 2022 23:40:12 +0100 Subject: [PATCH 7/9] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5809d563..8f355046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added implementation of `HashMap.retain()`. - Added support for affine backgrounds (tiled modes 1 and 2) which allows for scaling, rotating etc of tiled backgrounds. - Added support for 256 colour backgrounds (when working with affine ones). +- Added affine matrix module. This allows for manipulation of affine matricies for use in backgrounds and in the future objects. ### Changes - Many of the places that originally disabled IRQs now use the `sync` module, reducing the chance of missed interrupts. From 9ed5ee2295bb807b838cfcc3c8ac57116fc4b935 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 8 Oct 2022 23:41:21 +0100 Subject: [PATCH 8/9] change name of function to match the name of the transformation --- agb/src/display/affine.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 8b7877cd..5cd53ecc 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -84,7 +84,7 @@ impl AffineMatrix { // Identity for rotation / scale / skew /// Generates the matrix that represents a translation by the position #[must_use] - pub fn from_position(position: Vector2D>) -> Self { + pub fn from_translation(position: Vector2D>) -> Self { AffineMatrix { a: 1.into(), b: 0.into(), @@ -209,7 +209,7 @@ mod tests { fn test_simple_multiply(_: &mut crate::Gba) { let position = (20, 10).into(); - let a = AffineMatrix::from_position(position); + let a = AffineMatrix::from_translation(position); let b = AffineMatrix::default(); let c = a * b; From 96401c283378726c0a93e3c9a6954d7ba04253ef Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 8 Oct 2022 23:44:22 +0100 Subject: [PATCH 9/9] implement multiplication by scalar --- agb/src/display/affine.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 5cd53ecc..222293ec 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -193,6 +193,26 @@ 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;