mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-24 00:31:34 +11:00
Merge pull request #324 from corwinkuiper/affine-matrix-background
Use the new Affine Matrix stuff in the affine background
This commit is contained in:
commit
e30e0b76e2
|
@ -133,8 +133,8 @@ fixed_width_signed_integer_impl!(i16);
|
||||||
fixed_width_signed_integer_impl!(i32);
|
fixed_width_signed_integer_impl!(i32);
|
||||||
|
|
||||||
/// A fixed point number represented using `I` with `N` bits of fractional precision
|
/// A fixed point number represented using `I` with `N` bits of fractional precision
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[repr(transparent)]
|
||||||
pub struct Num<I: FixedWidthUnsignedInteger, const N: usize>(I);
|
pub struct Num<I: FixedWidthUnsignedInteger, const N: usize>(I);
|
||||||
|
|
||||||
/// An often convenient representation for the Game Boy Advance using word sized
|
/// An often convenient representation for the Game Boy Advance using word sized
|
||||||
|
@ -571,7 +571,6 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> Debug for Num<I, N> {
|
||||||
|
|
||||||
/// A vector of two points: (x, y) represened by integers or fixed point numbers
|
/// A vector of two points: (x, y) represened by integers or fixed point numbers
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||||
#[repr(C)]
|
|
||||||
pub struct Vector2D<T: Number> {
|
pub struct Vector2D<T: Number> {
|
||||||
/// The x coordinate
|
/// The x coordinate
|
||||||
pub x: T,
|
pub x: T,
|
||||||
|
@ -689,6 +688,17 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> Vector2D<Num<I, N>> {
|
||||||
y: self.y.floor(),
|
y: self.y.floor(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
/// Attempts to change the base returning None if the numbers cannot be represented
|
||||||
|
pub fn try_change_base<J: FixedWidthUnsignedInteger + TryFrom<I>, const M: usize>(
|
||||||
|
self,
|
||||||
|
) -> Option<Vector2D<Num<J, M>>> {
|
||||||
|
Some(Vector2D::new(
|
||||||
|
self.x.try_change_base()?,
|
||||||
|
self.y.try_change_base()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Vector2D<Num<i32, N>> {
|
impl<const N: usize> Vector2D<Num<i32, N>> {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
use agb::{
|
use agb::{
|
||||||
display::{
|
display::{
|
||||||
|
affine::AffineMatrixBackground,
|
||||||
tiled::{AffineBackgroundSize, TileFormat, TileSet, TiledMap},
|
tiled::{AffineBackgroundSize, TileFormat, TileSet, TiledMap},
|
||||||
Priority,
|
Priority,
|
||||||
},
|
},
|
||||||
|
@ -32,8 +33,8 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
bg.commit(&mut vram);
|
bg.commit(&mut vram);
|
||||||
bg.show();
|
bg.show();
|
||||||
|
|
||||||
let mut rotation: Num<u16, 8> = num!(0.);
|
let mut rotation = num!(0.);
|
||||||
let rotation_increase = num!(1.);
|
let rotation_increase: Num<i32, 16> = num!(0.01);
|
||||||
|
|
||||||
let mut input = agb::input::ButtonController::new();
|
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_x += input.x_tri() as i32;
|
||||||
scroll_y += input.y_tri() as i32;
|
scroll_y += input.y_tri() as i32;
|
||||||
|
|
||||||
let scroll_pos = (scroll_x as i16, scroll_y as i16);
|
let scroll_pos = (scroll_x, scroll_y).into();
|
||||||
bg.set_scroll_pos(scroll_pos.into());
|
|
||||||
bg.set_transform((0, 0), (1, 1), rotation);
|
|
||||||
|
|
||||||
rotation += rotation_increase;
|
rotation += rotation_increase;
|
||||||
if rotation >= num!(255.) {
|
rotation = rotation.rem_euclid(1.into());
|
||||||
rotation = 0.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();
|
vblank.wait_for_vblank();
|
||||||
bg.commit(&mut vram);
|
bg.commit(&mut vram);
|
||||||
|
|
|
@ -7,19 +7,82 @@
|
||||||
//! Affine matricies are used in two places on the GBA, for affine backgrounds
|
//! Affine matricies are used in two places on the GBA, for affine backgrounds
|
||||||
//! and for affine objects.
|
//! and for affine objects.
|
||||||
//!
|
//!
|
||||||
//! # Linear Algebra basics
|
//! # Linear Algebra
|
||||||
//! As a matrix, they can be manipulated using linear algebra, although you
|
//! As a matrix, they can be manipulated using linear algebra. The short version
|
||||||
//! shouldn't need to know linear algebra to use this apart from a few things
|
//! 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
|
//! One quick thing to point out at the start as it will become very relevant is
|
||||||
//! transformation `A` performed on `B`, or alternatively `C` is transformation
|
//! that matrix-matrix multiplication is not commutative, meaning swapping the
|
||||||
//! `B` followed by transformation `A`.
|
//! 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
|
//! ## Normal (wrong on GBA!) transformation matricies
|
||||||
//! order changes the result, or `A * B ≢ B * A`.
|
//!
|
||||||
|
//! 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**<sup>-1</sup> = (**AB**)<sup>-1</sup> =
|
||||||
|
//! **B**<sup>-1</sup> × **A**<sup>-1</sup>. 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<Num<i32, 8>> = (10, 10).into();
|
||||||
|
//! // rotation by a quarter turn
|
||||||
|
//! let rotation: Num<i32, 8> = num!(0.25);
|
||||||
|
//! // the final position
|
||||||
|
//! let position: Vector2D<Num<i32, 8>> = (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::{
|
use core::{
|
||||||
convert::{TryFrom, TryInto},
|
convert::TryFrom,
|
||||||
ops::{Mul, MulAssign},
|
ops::{Mul, MulAssign},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,7 +125,7 @@ impl AffineMatrix {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
/// Generates the matrix that represents a rotation
|
/// Generates the matrix that represents a rotation
|
||||||
pub fn from_rotation<const N: usize>(angle: Num<i32, N>) -> Self {
|
pub fn from_rotation<const N: usize>(angle: Num<i32, N>) -> Self {
|
||||||
fn from_rotation(angle: Num<i32, 28>) -> AffineMatrix {
|
fn from_rotation(angle: Num<i32, 8>) -> AffineMatrix {
|
||||||
let cos = angle.cos().change_base();
|
let cos = angle.cos().change_base();
|
||||||
let sin = angle.sin().change_base();
|
let sin = angle.sin().change_base();
|
||||||
|
|
||||||
|
@ -71,8 +134,8 @@ impl AffineMatrix {
|
||||||
// space rather than how you might conventionally think of it.
|
// space rather than how you might conventionally think of it.
|
||||||
AffineMatrix {
|
AffineMatrix {
|
||||||
a: cos,
|
a: cos,
|
||||||
b: sin,
|
b: -sin,
|
||||||
c: -sin,
|
c: sin,
|
||||||
d: cos,
|
d: cos,
|
||||||
x: 0.into(),
|
x: 0.into(),
|
||||||
y: 0.into(),
|
y: 0.into(),
|
||||||
|
@ -90,27 +153,27 @@ impl AffineMatrix {
|
||||||
b: 0.into(),
|
b: 0.into(),
|
||||||
c: 0.into(),
|
c: 0.into(),
|
||||||
d: 1.into(),
|
d: 1.into(),
|
||||||
x: position.x,
|
x: -position.x,
|
||||||
y: position.y,
|
y: -position.y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
/// The position fields of the matrix
|
/// The position fields of the matrix
|
||||||
pub fn position(&self) -> Vector2D<Num<i32, 8>> {
|
pub fn position(&self) -> Vector2D<Num<i32, 8>> {
|
||||||
(self.x, self.y).into()
|
(-self.x, -self.y).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to convert the matrix to one which can be used in affine
|
/// Attempts to convert the matrix to one which can be used in affine
|
||||||
/// backgrounds.
|
/// backgrounds.
|
||||||
pub fn try_to_background(&self) -> Result<AffineMatrixBackground, OverflowError> {
|
pub fn try_to_background(&self) -> Result<AffineMatrixBackground, OverflowError> {
|
||||||
Ok(AffineMatrixBackground {
|
Ok(AffineMatrixBackground {
|
||||||
a: self.a.to_raw().try_into().map_err(|_| OverflowError(()))?,
|
a: self.a.try_change_base().ok_or(OverflowError(()))?,
|
||||||
b: self.a.to_raw().try_into().map_err(|_| OverflowError(()))?,
|
b: self.b.try_change_base().ok_or(OverflowError(()))?,
|
||||||
c: self.a.to_raw().try_into().map_err(|_| OverflowError(()))?,
|
c: self.c.try_change_base().ok_or(OverflowError(()))?,
|
||||||
d: self.a.to_raw().try_into().map_err(|_| OverflowError(()))?,
|
d: self.d.try_change_base().ok_or(OverflowError(()))?,
|
||||||
x: self.a.to_raw(),
|
x: self.x,
|
||||||
y: self.a.to_raw(),
|
y: self.y,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,28 +182,75 @@ impl AffineMatrix {
|
||||||
/// wrapping any value which is too large to be represented there.
|
/// wrapping any value which is too large to be represented there.
|
||||||
pub fn to_background_wrapping(&self) -> AffineMatrixBackground {
|
pub fn to_background_wrapping(&self) -> AffineMatrixBackground {
|
||||||
AffineMatrixBackground {
|
AffineMatrixBackground {
|
||||||
a: self.a.to_raw() as i16,
|
a: Num::from_raw(self.a.to_raw() as i16),
|
||||||
b: self.a.to_raw() as i16,
|
b: Num::from_raw(self.b.to_raw() as i16),
|
||||||
c: self.a.to_raw() as i16,
|
c: Num::from_raw(self.c.to_raw() as i16),
|
||||||
d: self.a.to_raw() as i16,
|
d: Num::from_raw(self.d.to_raw() as i16),
|
||||||
x: self.a.to_raw(),
|
x: self.x,
|
||||||
y: self.a.to_raw(),
|
y: self.y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to convert the matrix to one which can be used in affine
|
||||||
|
/// objects.
|
||||||
|
pub fn try_to_object(&self) -> Result<AffineMatrixObject, OverflowError> {
|
||||||
|
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<Num<i32, 8>>) -> 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)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
#[repr(C, packed(4))]
|
#[repr(C, packed(4))]
|
||||||
/// An affine matrix that can be used in affine backgrounds
|
/// An affine matrix that can be used in affine backgrounds
|
||||||
pub struct AffineMatrixBackground {
|
pub struct AffineMatrixBackground {
|
||||||
// Internally these can be thought of as Num<i16, 8>
|
a: Num<i16, 8>,
|
||||||
a: i16,
|
b: Num<i16, 8>,
|
||||||
b: i16,
|
c: Num<i16, 8>,
|
||||||
c: i16,
|
d: Num<i16, 8>,
|
||||||
d: i16,
|
x: Num<i32, 8>,
|
||||||
// These are Num<i32, 8>
|
y: Num<i32, 8>,
|
||||||
x: i32,
|
}
|
||||||
y: i32,
|
|
||||||
|
impl Default for AffineMatrixBackground {
|
||||||
|
fn default() -> Self {
|
||||||
|
AffineMatrix::identity().to_background_wrapping()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<AffineMatrix> for AffineMatrixBackground {
|
impl TryFrom<AffineMatrix> for AffineMatrixBackground {
|
||||||
|
@ -157,14 +267,49 @@ impl AffineMatrixBackground {
|
||||||
/// calculations.
|
/// calculations.
|
||||||
pub fn to_affine_matrix(&self) -> AffineMatrix {
|
pub fn to_affine_matrix(&self) -> AffineMatrix {
|
||||||
AffineMatrix {
|
AffineMatrix {
|
||||||
a: Num::from_raw(self.a.into()),
|
a: self.a.change_base(),
|
||||||
b: Num::from_raw(self.b.into()),
|
b: self.b.change_base(),
|
||||||
c: Num::from_raw(self.c.into()),
|
c: self.c.change_base(),
|
||||||
d: Num::from_raw(self.d.into()),
|
d: self.d.change_base(),
|
||||||
x: Num::from_raw(self.x),
|
x: self.x,
|
||||||
y: Num::from_raw(self.y),
|
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<Num<i32, 8>>,
|
||||||
|
/// # scale: Vector2D<Num<i32, 8>>,
|
||||||
|
/// # rotation: Num<i32, 16>,
|
||||||
|
/// # position: Vector2D<Num<i32, 8>>,
|
||||||
|
/// # ) {
|
||||||
|
/// 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<Num<i32, 8>>,
|
||||||
|
scale: Vector2D<Num<i32, 8>>,
|
||||||
|
rotation: Num<i32, 16>,
|
||||||
|
position: Vector2D<Num<i32, 8>>,
|
||||||
|
) -> Self {
|
||||||
|
crate::syscall::bg_affine_matrix(
|
||||||
|
transform_origin,
|
||||||
|
position.try_change_base::<i16, 8>().unwrap().floor(),
|
||||||
|
scale.try_change_base().unwrap(),
|
||||||
|
rotation.rem_euclid(1.into()).try_change_base().unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AffineMatrixBackground> for AffineMatrix {
|
impl From<AffineMatrixBackground> for AffineMatrix {
|
||||||
|
@ -173,9 +318,49 @@ impl From<AffineMatrixBackground> 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<i16, 8>,
|
||||||
|
b: Num<i16, 8>,
|
||||||
|
c: Num<i16, 8>,
|
||||||
|
d: Num<i16, 8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AffineMatrixObject {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
AffineMatrix::identity()
|
AffineMatrix::identity().to_object_wrapping()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<AffineMatrix> for AffineMatrixObject {
|
||||||
|
type Error = OverflowError;
|
||||||
|
|
||||||
|
fn try_from(value: AffineMatrix) -> Result<Self, Self::Error> {
|
||||||
|
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<AffineMatrixObject> for AffineMatrix {
|
||||||
|
fn from(mat: AffineMatrixObject) -> Self {
|
||||||
|
mat.to_affine_matrix()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +368,7 @@ impl Mul for AffineMatrix {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn mul(self, rhs: Self) -> Self::Output {
|
fn mul(self, rhs: Self) -> Self::Output {
|
||||||
AffineMatrix {
|
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,
|
b: self.a * rhs.b + self.b * rhs.d,
|
||||||
c: self.c * rhs.a + self.d * rhs.c,
|
c: self.c * rhs.a + self.d * rhs.c,
|
||||||
d: self.c * rhs.b + self.d * rhs.d,
|
d: self.c * rhs.b + self.d * rhs.d,
|
||||||
|
@ -193,26 +378,6 @@ impl Mul for AffineMatrix {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mul<Num<i32, 8>> for AffineMatrix {
|
|
||||||
type Output = Self;
|
|
||||||
fn mul(self, rhs: Num<i32, 8>) -> Self::Output {
|
|
||||||
self * AffineMatrix {
|
|
||||||
a: rhs,
|
|
||||||
b: 0.into(),
|
|
||||||
c: 0.into(),
|
|
||||||
d: rhs,
|
|
||||||
x: 0.into(),
|
|
||||||
y: 0.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MulAssign<Num<i32, 8>> for AffineMatrix {
|
|
||||||
fn mul_assign(&mut self, rhs: Num<i32, 8>) {
|
|
||||||
*self = *self * rhs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MulAssign for AffineMatrix {
|
impl MulAssign for AffineMatrix {
|
||||||
fn mul_assign(&mut self, rhs: Self) {
|
fn mul_assign(&mut self, rhs: Self) {
|
||||||
*self = *self * rhs;
|
*self = *self * rhs;
|
||||||
|
|
|
@ -19,7 +19,7 @@ use super::{Priority, DISPLAY_CONTROL};
|
||||||
use crate::agb_alloc::block_allocator::BlockAllocator;
|
use crate::agb_alloc::block_allocator::BlockAllocator;
|
||||||
use crate::agb_alloc::bump_allocator::StartEnd;
|
use crate::agb_alloc::bump_allocator::StartEnd;
|
||||||
use crate::dma;
|
use crate::dma;
|
||||||
use crate::fixnum::{Num, Vector2D};
|
use crate::fixnum::Vector2D;
|
||||||
use crate::hash_map::HashMap;
|
use crate::hash_map::HashMap;
|
||||||
|
|
||||||
use attributes::*;
|
use attributes::*;
|
||||||
|
@ -1236,42 +1236,6 @@ enum ColourMode {
|
||||||
Eight,
|
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<i16, 8>,
|
|
||||||
/// Adjustment made to *X* coordinate along *vertical* lines.
|
|
||||||
/// Also known as "dmx".
|
|
||||||
/// Typically computed as `y_scale * sin(angle)`.
|
|
||||||
pub p_b: Num<i16, 8>,
|
|
||||||
/// Adjustment made to *Y* coordinate along *horizontal* lines.
|
|
||||||
/// Also known as "dy".
|
|
||||||
/// Typically computed as `-x_scale * sin(angle)`.
|
|
||||||
pub p_c: Num<i16, 8>,
|
|
||||||
/// Adjustment made to *Y* coordinate along *vertical* lines.
|
|
||||||
/// Also known as "dmy".
|
|
||||||
/// Typically computed as `y_scale * cos(angle)`.
|
|
||||||
pub p_d: Num<i16, 8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
// this mod is not public, so the internal parts don't need documenting.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod attributes {
|
mod attributes {
|
||||||
|
|
|
@ -2,9 +2,10 @@ use core::cell::RefCell;
|
||||||
use core::ops::{Deref, DerefMut};
|
use core::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::bitarray::Bitarray;
|
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::dma::dma_copy16;
|
||||||
use crate::fixnum::{Num, Vector2D};
|
use crate::fixnum::Vector2D;
|
||||||
use crate::memory_mapped::MemoryMapped;
|
use crate::memory_mapped::MemoryMapped;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -12,7 +13,6 @@ use super::{
|
||||||
RegularBackgroundSize, Tile, TileFormat, TileIndex, TileSet, TileSetting, VRamManager,
|
RegularBackgroundSize, Tile, TileFormat, TileIndex, TileSet, TileSetting, VRamManager,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::syscall::BgAffineSetData;
|
|
||||||
use alloc::{vec, vec::Vec};
|
use alloc::{vec, vec::Vec};
|
||||||
|
|
||||||
pub trait TiledMapTypes: private::Sealed {
|
pub trait TiledMapTypes: private::Sealed {
|
||||||
|
@ -247,7 +247,7 @@ pub struct AffineMap {
|
||||||
|
|
||||||
scroll: Vector2D<i16>,
|
scroll: Vector2D<i16>,
|
||||||
|
|
||||||
transform: BgAffineSetData,
|
transform: AffineMatrixBackground,
|
||||||
|
|
||||||
tiles: Vec<u8>,
|
tiles: Vec<u8>,
|
||||||
tiles_dirty: bool,
|
tiles_dirty: bool,
|
||||||
|
@ -259,7 +259,7 @@ impl TiledMapTypes for AffineMap {
|
||||||
|
|
||||||
impl TiledMapPrivate for AffineMap {
|
impl TiledMapPrivate for AffineMap {
|
||||||
type TileType = u8;
|
type TileType = u8;
|
||||||
type AffineMatrix = AffineMatrixAttributes;
|
type AffineMatrix = AffineMatrixBackground;
|
||||||
|
|
||||||
fn tiles_mut(&mut self) -> &mut [Self::TileType] {
|
fn tiles_mut(&mut self) -> &mut [Self::TileType] {
|
||||||
&mut self.tiles
|
&mut self.tiles
|
||||||
|
@ -280,10 +280,7 @@ impl TiledMapPrivate for AffineMap {
|
||||||
self.size
|
self.size
|
||||||
}
|
}
|
||||||
fn update_bg_registers(&self) {
|
fn update_bg_registers(&self) {
|
||||||
let register_pos = self.transform.position;
|
self.bg_affine_matrix().set(self.transform);
|
||||||
self.bg_x().set(register_pos.x);
|
|
||||||
self.bg_y().set(register_pos.y);
|
|
||||||
self.bg_affine_matrix().set(self.transform.matrix);
|
|
||||||
}
|
}
|
||||||
fn scroll_pos(&self) -> Vector2D<i16> {
|
fn scroll_pos(&self) -> Vector2D<i16> {
|
||||||
self.scroll
|
self.scroll
|
||||||
|
@ -347,25 +344,11 @@ impl AffineMap {
|
||||||
*self.tiles_dirty() = true;
|
*self.tiles_dirty() = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_transform(
|
pub fn set_transform(&mut self, transformation: impl Into<AffineMatrixBackground>) {
|
||||||
&mut self,
|
self.transform = transformation.into();
|
||||||
transform_origin: impl Into<Vector2D<Num<i32, 8>>>,
|
|
||||||
scale: impl Into<Vector2D<Num<i16, 8>>>,
|
|
||||||
rotation: impl Into<Num<u16, 8>>,
|
|
||||||
) {
|
|
||||||
let scale = scale.into();
|
|
||||||
let rotation = rotation.into();
|
|
||||||
self.transform =
|
|
||||||
crate::syscall::bg_affine_matrix(transform_origin.into(), self.scroll, scale, rotation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bg_x(&self) -> MemoryMapped<Num<i32, 8>> {
|
fn bg_affine_matrix(&self) -> MemoryMapped<AffineMatrixBackground> {
|
||||||
unsafe { MemoryMapped::new(0x0400_0008 + 0x10 * self.background_id()) }
|
|
||||||
}
|
|
||||||
fn bg_y(&self) -> MemoryMapped<Num<i32, 8>> {
|
|
||||||
unsafe { MemoryMapped::new(0x0400_000c + 0x10 * self.background_id()) }
|
|
||||||
}
|
|
||||||
fn bg_affine_matrix(&self) -> MemoryMapped<AffineMatrixAttributes> {
|
|
||||||
unsafe { MemoryMapped::new(0x0400_0000 + 0x10 * self.background_id()) }
|
unsafe { MemoryMapped::new(0x0400_0000 + 0x10 * self.background_id()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use agb_fixnum::Vector2D;
|
||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
|
|
||||||
use crate::display::object::AffineMatrixAttributes;
|
use crate::display::affine::AffineMatrixBackground;
|
||||||
use crate::fixnum::Num;
|
use crate::fixnum::Num;
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -139,40 +139,33 @@ pub fn arc_tan2(x: i16, y: i32) -> i16 {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed(4))]
|
/// `rotation` is in revolutions. It is hard to create the rotation, usually
|
||||||
pub struct BgAffineSetData {
|
/// you'll go in from a larger sized type.
|
||||||
pub matrix: AffineMatrixAttributes,
|
|
||||||
pub position: Vector2D<Num<i32, 8>>,
|
|
||||||
}
|
|
||||||
impl Default for BgAffineSetData {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
matrix: AffineMatrixAttributes::default(),
|
|
||||||
position: (0, 0).into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `rotation` is in revolutions.
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn bg_affine_matrix(
|
pub(crate) fn bg_affine_matrix(
|
||||||
bg_center: Vector2D<Num<i32, 8>>,
|
bg_center: Vector2D<Num<i32, 8>>,
|
||||||
display_center: Vector2D<i16>,
|
display_center: Vector2D<i16>,
|
||||||
scale: Vector2D<Num<i16, 8>>,
|
scale: Vector2D<Num<i16, 8>>,
|
||||||
rotation: Num<u16, 8>,
|
rotation: Num<u16, 16>,
|
||||||
) -> BgAffineSetData {
|
) -> AffineMatrixBackground {
|
||||||
#[repr(C, packed(4))]
|
#[repr(C, packed(4))]
|
||||||
struct Input {
|
struct Input {
|
||||||
bg_center: Vector2D<Num<i32, 8>>,
|
bg_center_x: Num<i32, 8>,
|
||||||
display_center: Vector2D<i16>,
|
bg_center_y: Num<i32, 8>,
|
||||||
scale: Vector2D<Num<i16, 8>>,
|
display_center_x: i16,
|
||||||
rotation: Num<u16, 8>,
|
display_center_y: i16,
|
||||||
|
scale_x: Num<i16, 8>,
|
||||||
|
scale_y: Num<i16, 8>,
|
||||||
|
rotation: Num<u16, 16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let input = Input {
|
let input = Input {
|
||||||
bg_center,
|
bg_center_x: bg_center.x,
|
||||||
display_center,
|
bg_center_y: bg_center.y,
|
||||||
scale,
|
display_center_x: display_center.x,
|
||||||
|
display_center_y: display_center.y,
|
||||||
|
scale_x: scale.x,
|
||||||
|
scale_y: scale.y,
|
||||||
rotation,
|
rotation,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -193,56 +186,12 @@ pub fn bg_affine_matrix(
|
||||||
unsafe { output.assume_init() }
|
unsafe { output.assume_init() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `rotation` is in revolutions.
|
|
||||||
#[must_use]
|
|
||||||
pub fn obj_affine_matrix(
|
|
||||||
scale: Vector2D<Num<i16, 8>>,
|
|
||||||
rotation: Num<u8, 8>,
|
|
||||||
) -> AffineMatrixAttributes {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[repr(C, packed(4))]
|
|
||||||
struct Input {
|
|
||||||
scale: Vector2D<Num<i16, 8>>,
|
|
||||||
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::display::affine::AffineMatrix;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test_case]
|
|
||||||
fn affine_obj(_gba: &mut crate::Gba) {
|
|
||||||
// expect identity matrix
|
|
||||||
let one: Num<i16, 8> = 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]
|
#[test_case]
|
||||||
fn affine_bg(_gba: &mut crate::Gba) {
|
fn affine_bg(_gba: &mut crate::Gba) {
|
||||||
// expect the identity matrix
|
// expect the identity matrix
|
||||||
|
@ -250,14 +199,10 @@ mod tests {
|
||||||
(0, 0).into(),
|
(0, 0).into(),
|
||||||
(0i16, 0i16).into(),
|
(0i16, 0i16).into(),
|
||||||
(1i16, 1i16).into(),
|
(1i16, 1i16).into(),
|
||||||
0.into(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let matrix = aff.matrix;
|
let matrix = aff.to_affine_matrix();
|
||||||
let (p_a, p_b, p_c, p_d) = (matrix.p_a, matrix.p_b, matrix.p_c, matrix.p_d);
|
assert_eq!(matrix, AffineMatrix::identity());
|
||||||
assert_eq!(p_a, 1.into());
|
|
||||||
assert_eq!(p_b, 0.into());
|
|
||||||
assert_eq!(p_c, 0.into());
|
|
||||||
assert_eq!(p_d, 1.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue