diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index ebbe2179..2bb653a6 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -1,4 +1,6 @@ #![no_std] +#![deny(missing_docs)] +//! Fixed point number implementation for representing non integers efficiently. use core::{ cmp::{Eq, Ord, PartialEq, PartialOrd}, @@ -12,6 +14,13 @@ use core::{ #[doc(hidden)] pub use agb_macros::num as num_inner; +/// Can be thought of having the signature `num!(float) -> Num`. +/// ``` +/// # use agb_fixnum::Num; +/// # use agb_fixnum::num; +/// let n: Num = num!(0.75); +/// assert_eq!(n, Num::new(3) / 4, "0.75 == 3/4"); +/// ``` #[macro_export] macro_rules! num { ($value:literal) => {{ @@ -19,6 +28,8 @@ macro_rules! num { }}; } +/// A trait for everything required to use as the internal representation of the +/// fixed point number. pub trait Number: Sized + Copy @@ -37,6 +48,7 @@ pub trait Number: impl Number for Num {} impl Number for I {} +/// A trait for integers that don't implement unary negation pub trait FixedWidthUnsignedInteger: Sized + Copy @@ -57,14 +69,20 @@ pub trait FixedWidthUnsignedInteger: + Debug + Display { + /// Returns the representation of zero fn zero() -> Self; + /// Returns the representation of one fn one() -> Self; + /// Returns the representation of ten fn ten() -> Self; + /// Converts an i32 to it's own representation, panics on failure fn from_as_i32(v: i32) -> Self; } +/// Trait for an integer that includes negation pub trait FixedWidthSignedInteger: FixedWidthUnsignedInteger + Neg { #[must_use] + /// Returns the absolute value of the number fn fixed_abs(self) -> Self; } @@ -111,12 +129,14 @@ fixed_width_unsigned_integer_impl!(usize); 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); +/// An often convenient representation for the Game Boy Advance using word sized +/// internal representation for maximum efficiency pub type FixedNum = Num; -pub type Integer = Num; impl From for Num { fn from(value: I) -> Self { @@ -266,6 +286,7 @@ impl Neg for Num { } impl Num { + /// Performs the conversion between two integer types and between two different fractional precisions pub fn change_base, const M: usize>(self) -> Num { let n: J = self.0.into(); if N < M { @@ -275,19 +296,40 @@ impl Num { } } + /// A bit for bit conversion from a number to a fixed num pub fn from_raw(n: I) -> Self { Num(n) } + /// The internal representation of the fixed point number pub fn to_raw(self) -> I { self.0 } + /// Truncates the fixed point number returning the integral part + /// ```rust + /// # use agb_fixnum::*; + /// let n: Num = num!(5.67); + /// assert_eq!(n.trunc(), 5); + /// let n: Num = num!(-5.67); + /// assert_eq!(n.trunc(), -5); + /// ``` pub fn trunc(self) -> I { self.0 / (I::one() << N) } #[must_use] + /// Performs the equivalent to the integer rem_euclid, which is modulo numbering. + /// ```rust + /// # use agb_fixnum::*; + /// let n: Num = num!(5.67); + /// let r: Num = num!(4.); + /// assert_eq!(n.rem_euclid(r), num!(1.67)); + /// + /// let n: Num = num!(-1.5); + /// let r: Num = num!(4.); + /// assert_eq!(n.rem_euclid(r), num!(2.5)); + /// ``` pub fn rem_euclid(self, rhs: Self) -> Self { let r = self % rhs; if r < I::zero().into() { @@ -301,18 +343,41 @@ impl Num { } } + /// Performs rounding towards negative infinity + /// ```rust + /// # use agb_fixnum::*; + /// let n: Num = num!(5.67); + /// assert_eq!(n.floor(), 5); + /// let n: Num = num!(-5.67); + /// assert_eq!(n.floor(), -6); + /// ``` pub fn floor(self) -> I { self.0 >> N } + /// Returns the fractional component of a number as it's integer representation + /// ``` + /// # use agb_fixnum::*; + /// let n: Num = num!(5.5); + /// assert_eq!(n.frac(), 1 << 7); + /// ``` pub fn frac(self) -> I { self.0 & ((I::one() << N) - I::one()) } + /// Creates an integer represented by a fixed point number + /// ``` + /// # use agb_fixnum::*; + /// let n: Num = Num::new(5); + /// assert_eq!(n.frac(), 0); // no fractional component + /// assert_eq!(n, num!(5.)); // just equals the number 5 + /// ``` pub fn new(integral: I) -> Self { Self(integral << N) } + #[doc(hidden)] + /// Called by the [num!] macro in order to create a fixed point number pub fn new_from_parts(num: (i32, i32)) -> Self { Self(I::from_as_i32(((num.0) << N) + (num.1 >> (30 - N)))) } @@ -320,6 +385,14 @@ impl Num { impl Num { #[must_use] + /// Returns the square root of a number, it is calcuated a digit at a time. + /// ``` + /// # use agb_fixnum::*; + /// let n: Num = num!(16.); + /// assert_eq!(n.sqrt(), num!(4.)); + /// let n: Num = num!(2.25); + /// assert_eq!(n.sqrt(), num!(1.5)); + /// ``` pub fn sqrt(self) -> Self { assert_eq!(N % 2, 0, "N must be even to be able to square root"); assert!(self.0 >= 0, "sqrt is only valid for positive numbers"); @@ -346,12 +419,33 @@ impl Num { impl Num { #[must_use] + /// Returns the absolute value of a fixed point number + /// ``` + /// # use agb_fixnum::*; + /// let n: Num = num!(5.5); + /// assert_eq!(n.abs(), num!(5.5)); + /// let n: Num = num!(-5.5); + /// assert_eq!(n.abs(), num!(5.5)); + /// ``` pub fn abs(self) -> Self { Num(self.0.fixed_abs()) } - /// domain of [0, 1]. - /// see https://github.com/tarcieri/micromath/blob/24584465b48ff4e87cffb709c7848664db896b4f/src/float/cos.rs#L226 + /// Calculates the cosign of a fixed point number with the domain of [0, 1]. + /// see + /// ``` + /// # use agb_fixnum::*; + /// let n: Num = num!(0.); // 0 radians + /// assert_eq!(n.cos(), num!(1.)); + /// let n: Num = num!(0.25); // pi / 2 radians + /// assert_eq!(n.cos(), num!(0.)); + /// let n: Num = num!(0.5); // pi radians + /// assert_eq!(n.cos(), num!(-1.)); + /// let n: Num = num!(0.75); // 3pi/2 radians + /// assert_eq!(n.cos(), num!(0.)); + /// let n: Num = num!(1.); // 2 pi radians (whole rotation) + /// assert_eq!(n.cos(), num!(1.)); + /// ``` #[must_use] pub fn cos(self) -> Self { let one: Self = I::one().into(); @@ -368,11 +462,25 @@ impl Num { x } + /// Calculates the sign of a number with domain of [0, 1]. + /// ``` + /// # use agb_fixnum::*; + /// let n: Num = num!(0.); // 0 radians + /// assert_eq!(n.sin(), num!(0.)); + /// let n: Num = num!(0.25); // pi / 2 radians + /// assert_eq!(n.sin(), num!(1.)); + /// let n: Num = num!(0.5); // pi radians + /// assert_eq!(n.sin(), num!(0.)); + /// let n: Num = num!(0.75); // 3pi/2 radians + /// assert_eq!(n.sin(), num!(-1.)); + /// let n: Num = num!(1.); // 2 pi radians (whole rotation) + /// assert_eq!(n.sin(), num!(0.)); + /// ``` #[must_use] pub fn sin(self) -> Self { let one: Self = I::one().into(); let four: I = 4.into(); - (self + one / four).cos() + (self - one / four).cos() } } @@ -417,9 +525,12 @@ impl Debug for Num { } } +/// A vector of two points: (x, y) represened by integers or fixed point numbers #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Vector2D { + /// The x coordinate pub x: T, + /// The y coordinate pub y: T, } @@ -505,6 +616,13 @@ impl SubAssign for Vector2D { impl Vector2D> { #[must_use] + /// Truncates the x and y coordinate, see [Num::trunc] + /// ``` + /// # use agb_fixnum::*; + /// let v1: Vector2D> = (num!(1.56), num!(-2.2)).into(); + /// let v2: Vector2D = (1, -2).into(); + /// assert_eq!(v1.trunc(), v2); + /// ``` pub fn trunc(self) -> Vector2D { Vector2D { x: self.x.trunc(), @@ -513,6 +631,13 @@ impl Vector2D> { } #[must_use] + /// Floors the x and y coordnate, see [Num::floor] + /// ``` + /// # use agb_fixnum::*; + /// let v1: Vector2D> = Vector2D::new(num!(1.56), num!(-2.2)); + /// let v2: Vector2D = (1, -3).into(); + /// assert_eq!(v1.floor(), v2); + /// ``` pub fn floor(self) -> Vector2D { Vector2D { x: self.x.floor(), @@ -523,24 +648,47 @@ impl Vector2D> { impl Vector2D> { #[must_use] + /// Calculates the magnitude squared, ie (x*x + y*y) + /// ``` + /// # use agb_fixnum::*; + /// let v1: Vector2D> = (num!(3.), num!(4.)).into(); + /// assert_eq!(v1.magnitude_squared(), 25.into()); + /// ``` pub fn magnitude_squared(self) -> Num { self.x * self.x + self.y * self.y } #[must_use] + /// Calculates the manhattan distance, x.abs() + y.abs(). + /// ``` + /// # use agb_fixnum::*; + /// let v1: Vector2D> = (num!(3.), num!(4.)).into(); + /// assert_eq!(v1.manhattan_distance(), 7.into()); + /// ``` pub fn manhattan_distance(self) -> Num { self.x.abs() + self.y.abs() } #[must_use] + /// Calculates the magnitude by square root + /// ``` + /// # use agb_fixnum::*; + /// let v1: Vector2D> = (num!(3.), num!(4.)).into(); + /// assert_eq!(v1.magnitude(), 5.into()); + /// ``` pub fn magnitude(self) -> Num { self.magnitude_squared().sqrt() } - // calculates the magnitude of a vector using the alpha max plus beta min - // algorithm https://en.wikipedia.org/wiki/Alpha_max_plus_beta_min_algorithm - // this has a maximum error of less than 4% of the true magnitude, probably - // depending on the size of your fixed point approximation + /// Calculates the magnitude of a vector using the alpha max plus beta min + /// algorithm https://en.wikipedia.org/wiki/Alpha_max_plus_beta_min_algorithm + /// this has a maximum error of less than 4% of the true magnitude, probably + /// depending on the size of your fixed point approximation + /// ``` + /// # use agb_fixnum::*; + /// let v1: Vector2D> = (num!(3.), num!(4.)).into(); + /// assert!(v1.fast_magnitude() > num!(4.9) && v1.fast_magnitude() < num!(5.1)); + /// ``` #[must_use] pub fn fast_magnitude(self) -> Num { let max = core::cmp::max(self.x, self.y); @@ -550,11 +698,24 @@ impl Vector2D> { } #[must_use] + /// Normalises the vector to magnitude of one by performing a square root, + /// due to fixed point imprecision this magnitude may not be exactly one + /// ``` + /// # use agb_fixnum::*; + /// let v1: Vector2D> = (num!(4.), num!(4.)).into(); + /// assert_eq!(v1.normalise().magnitude(), 1.into()); + /// ``` pub fn normalise(self) -> Self { self / self.magnitude() } #[must_use] + /// Normalises the vector to magnitude of one using [Vector2D::fast_magnitude]. + /// ``` + /// # use agb_fixnum::*; + /// let v1: Vector2D> = (num!(4.), num!(4.)).into(); + /// assert_eq!(v1.fast_normalise().magnitude(), 1.into()); + /// ``` pub fn fast_normalise(self) -> Self { self / self.fast_magnitude() } @@ -567,12 +728,25 @@ impl> From<(P, P)> for Vector2D { } impl Vector2D { + /// Converts the representation of the vector to another type + /// ``` + /// # use agb_fixnum::*; + /// let v1: Vector2D = Vector2D::new(1, 2); + /// let v2: Vector2D = v1.change_base(); + /// ``` pub fn change_base>(self) -> Vector2D { (self.x, self.y).into() } } impl Vector2D> { + /// Creates a unit vector from an angle, noting that the domain of the angle + /// is [0, 1], see [Num::cos] and [Num::sin]. + /// ``` + /// # use agb_fixnum::*; + /// let v: Vector2D> = Vector2D::new_from_angle(num!(0.0)); + /// assert_eq!(v, (num!(1.0), num!(0.0)).into()); + /// ``` pub fn new_from_angle(angle: Num) -> Self { Vector2D { x: angle.cos(), @@ -591,24 +765,61 @@ impl From> for Vector2 } #[derive(Debug, PartialEq, Eq, Clone)] +/// A rectangle with a position in 2d space and a 2d size pub struct Rect { + /// The position of the rectangle pub position: Vector2D, + /// The size of the rectangle pub size: Vector2D, } impl Rect { #[must_use] + /// Creates a rectangle from it's position and size + /// ``` + /// # use agb_fixnum::*; + /// let r = Rect::new(Vector2D::new(1,1), Vector2D::new(2,3)); + /// assert_eq!(r.position, Vector2D::new(1,1)); + /// assert_eq!(r.size, Vector2D::new(2,3)); + /// ``` pub fn new(position: Vector2D, size: Vector2D) -> Self { Rect { position, size } } + /// Returns true if the rectangle contains the point given, note that the boundary counts as containing the rectangle. + /// ``` + /// # use agb_fixnum::*; + /// let r = Rect::new(Vector2D::new(1,1), Vector2D::new(3,3)); + /// assert!(r.contains_point(Vector2D::new(1,1))); + /// assert!(r.contains_point(Vector2D::new(2,2))); + /// assert!(r.contains_point(Vector2D::new(3,3))); + /// assert!(r.contains_point(Vector2D::new(4,4))); + /// + /// assert!(!r.contains_point(Vector2D::new(0,2))); + /// assert!(!r.contains_point(Vector2D::new(5,2))); + /// assert!(!r.contains_point(Vector2D::new(2,0))); + /// assert!(!r.contains_point(Vector2D::new(2,5))); + /// ``` pub fn contains_point(&self, point: Vector2D) -> bool { - point.x > self.position.x - && point.x < self.position.x + self.size.x - && point.y > self.position.y - && point.y < self.position.y + self.size.y + point.x >= self.position.x + && point.x <= self.position.x + self.size.x + && point.y >= self.position.y + && point.y <= self.position.y + self.size.y } + /// Returns true if the other rectangle touches or overlaps the first. + /// ``` + /// # use agb_fixnum::*; + /// let r = Rect::new(Vector2D::new(1,1), Vector2D::new(3,3)); + /// + /// assert!(r.touches(r.clone())); + /// + /// let r1 = Rect::new(Vector2D::new(2,2), Vector2D::new(3,3)); + /// assert!(r.touches(r1)); + /// + /// let r2 = Rect::new(Vector2D::new(-10,-10), Vector2D::new(3,3)); + /// assert!(!r.touches(r2)); + /// ``` pub fn touches(&self, other: Rect) -> bool { self.position.x < other.position.x + other.size.x && self.position.x + self.size.x > other.position.x @@ -617,7 +828,28 @@ impl Rect { } #[must_use] - pub fn overlapping_rect(&self, other: Rect) -> Self { + /// Returns the rectangle that is the region that the two rectangles have in + /// common, or [None] if they don't overlap + /// ``` + /// # use agb_fixnum::*; + /// let r = Rect::new(Vector2D::new(1,1), Vector2D::new(3,3)); + /// let r2 = Rect::new(Vector2D::new(2,2), Vector2D::new(3,3)); + /// + /// assert_eq!(r.overlapping_rect(r2), Some(Rect::new(Vector2D::new(2,2), Vector2D::new(2,2)))); + /// ``` + /// + /// ``` + /// # use agb_fixnum::*; + /// let r = Rect::new(Vector2D::new(1,1), Vector2D::new(3,3)); + /// let r2 = Rect::new(Vector2D::new(-10,-10), Vector2D::new(3,3)); + /// + /// assert_eq!(r.overlapping_rect(r2), None); + /// ``` + pub fn overlapping_rect(&self, other: Rect) -> Option { + if !self.touches(other.clone()) { + return None; + } + fn max(x: E, y: E) -> E { if x > y { x @@ -650,11 +882,21 @@ impl Rect { ) .into(); - Rect::new(top_left, bottom_right - top_left) + Some(Rect::new(top_left, bottom_right - top_left)) } } impl Rect { + /// Iterate over the points in a rectangle in row major order. + /// ``` + /// # use agb_fixnum::*; + /// let r = Rect::new(Vector2D::new(1,1), Vector2D::new(2,3)); + /// + /// let expected_points = vec![(1,1), (2,1), (1,2), (2,2), (1,3), (2,3)]; + /// let rect_points: Vec<(i32, i32)> = r.iter().collect(); + /// + /// assert_eq!(rect_points, expected_points); + /// ``` pub fn iter(self) -> impl Iterator { let mut x = self.position.x; let mut y = self.position.y; @@ -676,15 +918,37 @@ impl Rect { } impl Vector2D { + /// Created a vector from the given coordinates + /// ``` + /// # use agb_fixnum::*; + /// let v = Vector2D::new(1, 2); + /// assert_eq!(v.x, 1); + /// assert_eq!(v.y, 2); + /// ``` pub fn new(x: T, y: T) -> Self { Vector2D { x, y } } + /// Returns the tuple of the coorinates + /// ``` + /// # use agb_fixnum::*; + /// let v = Vector2D::new(1, 2); + /// assert_eq!(v.get(), (1, 2)); + /// ``` pub fn get(self) -> (T, T) { (self.x, self.y) } #[must_use] + /// Calculates the hadamard product of two vectors + /// ``` + /// # use agb_fixnum::*; + /// let v1 = Vector2D::new(2, 3); + /// let v2 = Vector2D::new(4, 5); + /// + /// let r = v1.hadamard(v2); + /// assert_eq!(r, Vector2D::new(v1.x * v2.x, v1.y * v2.y)); + /// ``` pub fn hadamard(self, other: Self) -> Self { Self { x: self.x * other.x, @@ -693,6 +957,12 @@ impl Vector2D { } #[must_use] + /// Swaps the x and y coordinate + /// ``` + /// # use agb_fixnum::*; + /// let v1 = Vector2D::new(2, 3); + /// assert_eq!(v1.swap(), Vector2D::new(3, 2)); + /// ``` pub fn swap(self) -> Self { Self { x: self.y,