oxygen_quark 0.0.11

Oxygen Quark is a maths library mainly developed for the Oxygen Game Engine.
Documentation
#![allow(
    clippy::erasing_op,
    clippy::identity_op,
    clippy::suspicious_op_assign_impl,
    clippy::suspicious_arithmetic_impl
)]

use std::fmt;

use super::{Matrix, MatrixError, TransformVector};
use crate::fraction::Fraction;
use crate::vector::{ VectorError, vector2d::Vector2D };

pub struct Matrix2x2 {
    /// The matrix data.
    pub data: [Fraction; 2 * 2],
}

impl Matrix for Matrix2x2 {
    type Data = Fraction;

    /// Generates a new `Matrix2x2` with values `0`.
    ///
    fn new() -> Self {
        Matrix2x2 {
            data: [
                Fraction::new_denom(0, 1),
                Fraction::new_denom(0, 1),
                Fraction::new_denom(0, 1),
                Fraction::new_denom(0, 1),
            ],
        }
    }

    /// Generates a new `Matrix2x2` from the `Fraction` specified.
    ///
    fn from(value: Self::Data) -> Self {
        Matrix2x2 {
            data: [value, value, value, value],
        }
    }

    /// Generates a new `Matrix2x2` from a list of `Fraction` values.
    ///
    fn from_slice(values: &[Self::Data]) -> Self {
        let mut new_matrix = Matrix2x2::new();

        new_matrix.data[0] = values[0];
        new_matrix.data[1] = values[1];
        new_matrix.data[2] = values[2];
        new_matrix.data[3] = values[3];

        new_matrix
    }

    /// Scales the given `Matrix2x2` by the given `Fraction`.
    ///
    fn scale(&self, scale: Self::Data) -> Self {
        let mut scaled_matrix = Matrix2x2::new();

        for i in 0..2 {
            for j in 0..2 {
                scaled_matrix.data[2 * i + j] = self.data[2 * i + j] * scale;
            }
        }

        scaled_matrix
    }

    /// Calculates the determinant of the `Matrix2x2` and returns it, a `Fraction`.
    ///
    fn determinant(&self) -> Result<Self::Data, MatrixError> {
        Ok(self.data[2 * 0 + 0] * self.data[2 * 1 + 1]
            - self.data[2 * 0 + 1] * self.data[2 * 1 + 0])
    }

    /// Transposes the `Matrix2x2` and returns the result.
    ///
    fn transpose(&self) -> Self {
        let mut transposed_matrix = Matrix2x2::new();

        for i in 0..2 {
            for j in 0..2 {
                transposed_matrix.data[2 * i + j] = self.data[i + 2 * j];
            }
        }

        transposed_matrix
    }

    /// Returns the cofactor of the `Matrix2x2`.
    ///
    fn cofactor(&self) -> Result<Self, MatrixError> {
        let mut cofactor_matrix = Matrix2x2::new();

        cofactor_matrix.data[2 * 0 + 0] = self.data[2 * 1 + 1];
        cofactor_matrix.data[2 * 0 + 1] = -self.data[2 * 1 + 0];
        cofactor_matrix.data[2 * 1 + 0] = -self.data[2 * 0 + 1];
        cofactor_matrix.data[2 * 1 + 1] = self.data[2 * 0 + 0];

        Ok(cofactor_matrix)
    }

    /// Returns the adjugate of the `Matrix2x2`.
    /// Equal to calling cofactor() and then transpose().
    ///
    fn adjugate(&self) -> Result<Self, MatrixError> {
        Ok(self.cofactor().unwrap().transpose())
    }

    /// Returns a `Result<Matrix2x2, ZeroDeterminantError>`.
    /// It returns an `Result<Matrix2x2, ZeroDeterminantError>` because there won't always be an inverse (if the determinant is `0`), hence requires some extra checking.
    ///
    /// # Panics
    /// Panics when trying to `unwrap()` a `None`-value.
    ///
    fn inverse(&self) -> Result<Self, MatrixError>
    where
        Self: Sized,
    {
        let determinant = self.determinant().unwrap();
        if determinant == Self::Data::from(0_i16) {
            return Err(MatrixError::ZeroDeterminantError);
        }

        let inverse_matrix = self.adjugate().unwrap();

        Ok(inverse_matrix.scale(Self::Data::from(1_i16) / determinant))
    }
}

impl TransformVector<Vector2D> for Matrix2x2 {
    fn transform_vector(&self, other: Vector2D) -> Result<Vector2D, VectorError> {
        Ok(crate::vector::vector2d::Vector2D {
            data: [other[0] * self.data[2 * 0 + 0] + other[1] * self.data[2 * 0 + 1],
            other[0] * self.data[2 * 1 + 0] + other[1] * self.data[2 * 1 + 1],]
        })
    }
}

impl std::cmp::PartialEq for Matrix2x2 {
    fn eq(&self, other: &Matrix2x2) -> bool {
        self.data.into_iter().eq(other.data.into_iter())
    }
}

impl std::cmp::Eq for Matrix2x2 {}

impl std::ops::Add for Matrix2x2 {
    type Output = Matrix2x2;
    fn add(self, other: Matrix2x2) -> Matrix2x2 {
        let mut sum_matrix = Matrix2x2::new();

        for i in 0..4 {
            sum_matrix[i] = self.data[i] + other[i];
        }

        sum_matrix
    }
}

impl std::ops::AddAssign for Matrix2x2 {
    fn add_assign(&mut self, other: Matrix2x2) {
        self.data[0] += other[0];
        self.data[1] += other[1];
        self.data[2] += other[2];
        self.data[3] += other[3];
    }
}

impl std::ops::Sub for Matrix2x2 {
    type Output = Matrix2x2;
    fn sub(self, other: Matrix2x2) -> Matrix2x2 {
        let mut difference_matrix = Matrix2x2::new();

        for i in 0..4 {
            difference_matrix[i] = self.data[i] - other[i];
        }

        difference_matrix
    }
}

impl std::ops::SubAssign for Matrix2x2 {
    fn sub_assign(&mut self, other: Matrix2x2) {
        self.data[0] -= other[0];
        self.data[1] -= other[1];
        self.data[2] -= other[2];
        self.data[3] -= other[3];
    }
}

impl std::ops::Mul for Matrix2x2 {
    type Output = Matrix2x2;
    fn mul(self, other: Matrix2x2) -> Matrix2x2 {
        let mut product_matrix = Matrix2x2::new();

        for i in 0..2 {
            for j in 0..2 {
                for m in 0..2 {
                    product_matrix[2 * i + j] += self.data[2 * i + m] * other[2 * m + j];
                }
            }
        }

        product_matrix
    }
}

impl std::ops::MulAssign for Matrix2x2 {
    fn mul_assign(&mut self, other: Matrix2x2) {
        let mut product_matrix = Matrix2x2::new();

        for i in 0..2 {
            for j in 0..2 {
                for m in 0..2 {
                    product_matrix[2 * i + j] += self.data[2 * i + m] * other[2 * m + j];
                }
            }
        }

        self.data[0..4].clone_from_slice(&product_matrix.data[0..4]);
    }
}

impl std::ops::Neg for Matrix2x2 {
    type Output = Matrix2x2;
    fn neg(self) -> Matrix2x2 {
        let mut negated_matrix = Matrix2x2::new();

        negated_matrix[0] = -self.data[0];
        negated_matrix[1] = -self.data[1];
        negated_matrix[2] = -self.data[2];
        negated_matrix[3] = -self.data[3];

        negated_matrix
    }
}

impl std::ops::Index<usize> for Matrix2x2 {
    type Output = Fraction;
    fn index(&self, index: usize) -> &Fraction {
        &self.data[index]
    }
}

impl std::ops::IndexMut<usize> for Matrix2x2 {
    fn index_mut(&mut self, index: usize) -> &mut Fraction {
        &mut self.data[index]
    }
}

impl Copy for Matrix2x2 {}

impl Clone for Matrix2x2 {
    fn clone(&self) -> Matrix2x2 {
        *self
    }
}

impl fmt::Debug for Matrix2x2 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "\n[{}, {}]\n[{}, {}]",
            self.data[2 * 0 + 0],
            self.data[2 * 0 + 1],
            self.data[2 * 1 + 0],
            self.data[2 * 1 + 1]
        )
    }
}

impl fmt::Display for Matrix2x2 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "\n({}, {})\n({}, {})",
            self.data[2 * 0 + 0],
            self.data[2 * 0 + 1],
            self.data[2 * 1 + 0],
            self.data[2 * 1 + 1]
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_test() {
        let values: [Fraction; 4] = [
            Fraction::new_denom(0, 1),
            Fraction::new_denom(0, 1),
            Fraction::new_denom(0, 1),
            Fraction::new_denom(0, 1),
        ];

        let matrix1: Matrix2x2 = Matrix2x2 {
            data: [
                Fraction::new_denom(0, 1),
                Fraction::new_denom(0, 1),
                Fraction::new_denom(0, 1),
                Fraction::new_denom(0, 1),
            ],
        };

        let matrix2 = Matrix2x2::new();
        let matrix3 = Matrix::from(Fraction::new_denom(0, 1));
        let matrix4 = Matrix2x2::from_slice(&values);

        assert_eq!(matrix1, matrix2);
        assert_eq!(matrix2, matrix3);
        assert_eq!(matrix3, matrix4);
    }

    #[test]
    fn addition_test() {
        let matrix1: Matrix2x2 = Matrix::from(Fraction::new_denom(5, 1));
        let matrix2 = Matrix::from(Fraction::new_denom(6, 1));

        let result_matrix = Matrix::from(Fraction::new_denom(11, 1));

        assert_eq!(matrix1 + matrix2, result_matrix);
        assert_eq!(matrix2 + matrix1, result_matrix);
    }

    #[test]
    fn subtraction_test() {
        let matrix1: Matrix2x2 = Matrix::from(Fraction::new_denom(5, 1));
        let matrix2 = Matrix::from(Fraction::new_denom(6, 1));

        let result_matrix1 = Matrix::from(Fraction::new_denom(-1, 1));
        let result_matrix2 = Matrix::from(Fraction::new_denom(1, 1));

        assert_eq!(matrix1 - matrix2, result_matrix1);
        assert_eq!(matrix2 - matrix1, result_matrix2);
    }

    #[test]
    fn multiplication_test() {
        let values1: [Fraction; 4] = [
            Fraction::new_denom(4, 1),
            Fraction::new_denom(2, 1),
            Fraction::new_denom(5, 1),
            Fraction::new_denom(1, 1),
        ];

        let values2: [Fraction; 4] = [
            Fraction::new_denom(5, 1),
            Fraction::new_denom(2, 1),
            Fraction::new_denom(6, 1),
            Fraction::new_denom(3, 1),
        ];

        let result_values1: [Fraction; 4] = [
            Fraction::new_denom(32, 1),
            Fraction::new_denom(14, 1),
            Fraction::new_denom(31, 1),
            Fraction::new_denom(13, 1),
        ];

        let result_values2: [Fraction; 4] = [
            Fraction::new_denom(30, 1),
            Fraction::new_denom(12, 1),
            Fraction::new_denom(39, 1),
            Fraction::new_denom(15, 1),
        ];

        let matrix1: Matrix2x2 = Matrix2x2::from_slice(&values1);
        let matrix2 = Matrix2x2::from_slice(&values2);
        let result_matrix1 = Matrix2x2::from_slice(&result_values1);
        let result_matrix2 = Matrix2x2::from_slice(&result_values2);

        assert_eq!(matrix1 * matrix2, result_matrix1);
        assert_eq!(matrix2 * matrix1, result_matrix2);
    }

    #[test]
    fn scale_test() {
        let scale = Fraction::new_denom(5, 1);
        let values: [Fraction; 4] = [
            Fraction::new_denom(4, 1),
            Fraction::new_denom(2, 1),
            Fraction::new_denom(5, 1),
            Fraction::new_denom(1, 1),
        ];

        let result_values: [Fraction; 4] = [
            Fraction::new_denom(4 * 5, 1),
            Fraction::new_denom(2 * 5, 1),
            Fraction::new_denom(5 * 5, 1),
            Fraction::new_denom(1 * 5, 1),
        ];

        let matrix = Matrix2x2::from_slice(&values);
        let result_matrix = Matrix2x2::from_slice(&result_values);

        assert_eq!(matrix.scale(scale), result_matrix);
    }

    #[test]
    fn transpose_test() {
        let values1: [Fraction; 4] = [
            Fraction::new_denom(2, 1),
            Fraction::new_denom(6, 1),
            Fraction::new_denom(5, 1),
            Fraction::new_denom(1, 1),
        ];

        let result_values: [Fraction; 4] = [
            Fraction::new_denom(2, 1),
            Fraction::new_denom(5, 1),
            Fraction::new_denom(6, 1),
            Fraction::new_denom(1, 1),
        ];

        let matrix = Matrix2x2::from_slice(&values1);
        let result_matrix = Matrix2x2::from_slice(&result_values);

        assert_eq!(matrix.transpose(), result_matrix);
    }

    #[test]
    fn cofactor_test() {
        let values: [Fraction; 4] = [
            Fraction::new_denom(5, 1),
            Fraction::new_denom(2, 1),
            Fraction::new_denom(7, 1),
            Fraction::new_denom(2, 1),
        ];

        let result_values: [Fraction; 4] = [
            Fraction::new_denom(2, 1),
            Fraction::new_denom(-7, 1),
            Fraction::new_denom(-2, 1),
            Fraction::new_denom(5, 1),
        ];

        let matrix = Matrix2x2::from_slice(&values);
        let result_matrix = Matrix2x2::from_slice(&result_values);

        assert_eq!(matrix.cofactor().unwrap(), result_matrix);
    }

    #[test]
    fn adjugate_test() {
        let values: [Fraction; 4] = [
            Fraction::new_denom(5, 1),
            Fraction::new_denom(2, 1),
            Fraction::new_denom(7, 1),
            Fraction::new_denom(2, 1),
        ];

        let result_values: [Fraction; 4] = [
            Fraction::new_denom(2, 1),
            Fraction::new_denom(-2, 1),
            Fraction::new_denom(-7, 1),
            Fraction::new_denom(5, 1),
        ];

        let matrix = Matrix2x2::from_slice(&values);
        let result_matrix = Matrix2x2::from_slice(&result_values);;

        assert_eq!(matrix.adjugate().unwrap(), result_matrix);
    }

    #[test]
    fn determinant_test() {
        let values: [Fraction; 4] = [
            Fraction::new_denom(4, 1),
            Fraction::new_denom(2, 1),
            Fraction::new_denom(5, 1),
            Fraction::new_denom(1, 1),
        ];

        let matrix = Matrix2x2::from_slice(&values);

        let determinant = Fraction::new_denom(-6, 1);

        assert_eq!(matrix.determinant().unwrap(), determinant);
    }

    #[test]
    fn inverse_test() {
        let values: [Fraction; 4] = [
            Fraction::new_denom(5, 1),
            Fraction::new_denom(2, 1),
            Fraction::new_denom(4, 1),
            Fraction::new_denom(7, 1),
        ];

        let result_values: [Fraction; 4] = [
            Fraction::new_denom(7, 27),
            Fraction::new_denom(-2, 27),
            Fraction::new_denom(-4, 27),
            Fraction::new_denom(5, 27),
        ];

        let matrix = Matrix2x2::from_slice(&values);
        let result_matrix = Matrix2x2::from_slice(&result_values);

        assert_eq!(matrix.inverse().unwrap(), result_matrix);
    }
}