Merge pull request #324 from corwinkuiper/affine-matrix-background

Use the new Affine Matrix stuff in the affine background
This commit is contained in:
Corwin 2022-10-09 20:51:26 +01:00 committed by GitHub
commit e30e0b76e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 289 additions and 216 deletions

View file

@ -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: FixedWidthUnsignedInteger, const N: usize>(I);
/// 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
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
#[repr(C)]
pub struct Vector2D<T: Number> {
/// The x coordinate
pub x: T,
@ -689,6 +688,17 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> Vector2D<Num<I, N>> {
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>> {

View file

@ -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<u16, 8> = num!(0.);
let rotation_increase = num!(1.);
let mut rotation = num!(0.);
let rotation_increase: Num<i32, 16> = 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);

View file

@ -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**<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::{
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<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 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<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
/// backgrounds.
pub fn try_to_background(&self) -> Result<AffineMatrixBackground, OverflowError> {
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<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)]
#[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<i16, 8>
a: i16,
b: i16,
c: i16,
d: i16,
// These are Num<i32, 8>
x: i32,
y: i32,
a: Num<i16, 8>,
b: Num<i16, 8>,
c: Num<i16, 8>,
d: Num<i16, 8>,
x: Num<i32, 8>,
y: Num<i32, 8>,
}
impl Default for AffineMatrixBackground {
fn default() -> Self {
AffineMatrix::identity().to_background_wrapping()
}
}
impl TryFrom<AffineMatrix> 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<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 {
@ -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 {
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;
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<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 {
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;

View file

@ -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<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.
#[allow(dead_code)]
mod attributes {

View file

@ -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<i16>,
transform: BgAffineSetData,
transform: AffineMatrixBackground,
tiles: Vec<u8>,
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<i16> {
self.scroll
@ -347,25 +344,11 @@ impl AffineMap {
*self.tiles_dirty() = true;
}
pub fn set_transform(
&mut self,
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);
pub fn set_transform(&mut self, transformation: impl Into<AffineMatrixBackground>) {
self.transform = transformation.into();
}
fn bg_x(&self) -> MemoryMapped<Num<i32, 8>> {
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> {
fn bg_affine_matrix(&self) -> MemoryMapped<AffineMatrixBackground> {
unsafe { MemoryMapped::new(0x0400_0000 + 0x10 * self.background_id()) }
}
}

View file

@ -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<Num<i32, 8>>,
}
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<Num<i32, 8>>,
display_center: Vector2D<i16>,
scale: Vector2D<Num<i16, 8>>,
rotation: Num<u16, 8>,
) -> BgAffineSetData {
rotation: Num<u16, 16>,
) -> AffineMatrixBackground {
#[repr(C, packed(4))]
struct Input {
bg_center: Vector2D<Num<i32, 8>>,
display_center: Vector2D<i16>,
scale: Vector2D<Num<i16, 8>>,
rotation: Num<u16, 8>,
bg_center_x: Num<i32, 8>,
bg_center_y: Num<i32, 8>,
display_center_x: i16,
display_center_y: i16,
scale_x: Num<i16, 8>,
scale_y: Num<i16, 8>,
rotation: Num<u16, 16>,
}
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<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)]
mod tests {
use crate::display::affine::AffineMatrix;
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]
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());
}
}