use std::{array, ops, fmt, marker::PhantomData};
use bytemuck::{Zeroable, Pod};
use crate::{Float, Scalar, Matrix, Vector, Point, HcPoint, Space, WorldSpace};
#[repr(C)]
pub struct HcMatrix<
T: Scalar,
const C: usize,
const R: usize,
Src: Space = WorldSpace,
Dst: Space = WorldSpace,
>(HcMatrixStorage<T, C, R>, PhantomData<(Src, Dst)>);
pub type HcMat3<T, Src = WorldSpace, Dst = WorldSpace> = HcMatrix<T, 3, 3, Src, Dst>;
pub type HcMat2<T, Src = WorldSpace, Dst = WorldSpace> = HcMatrix<T, 2, 2, Src, Dst>;
pub type HcMat3f<Src = WorldSpace, Dst = WorldSpace> = HcMat3<f32, Src, Dst>;
pub type HcMat2f<Src = WorldSpace, Dst = WorldSpace> = HcMat2<f32, Src, Dst>;
impl<
T: Scalar,
const C: usize,
const R: usize,
Src: Space,
Dst: Space,
> HcMatrix<T, C, R, Src, Dst> {
fn new_impl(data: HcMatrixStorage<T, C, R>) -> Self {
Self(data, PhantomData)
}
pub fn zero() -> Self {
let col = NPlusOneArray([T::zero(); R], T::zero());
Self::new_impl(HcMatrixStorage(NPlusOneArray([col; C], col)))
}
pub fn from_parts(
linear: Matrix<T, C, R, Src, Dst>,
translation: Vector<T, R, Src>,
projection: Vector<T, C, Src>,
q: T,
) -> Self {
Self::new_impl(HcMatrixStorage(NPlusOneArray(
array::from_fn(|c| NPlusOneArray(linear.col(c).to_array(), projection[c])),
NPlusOneArray(translation.into(), q),
)))
}
pub fn elem(&self, row: usize, col: usize) -> T {
self.0.0[col][row]
}
pub fn set_elem(&mut self, row: usize, col: usize, v: T) {
self.0.0[col][row] = v;
}
pub fn row(&self, row: usize) -> HcRow<'_, T, C, R> {
HcRow { matrix: &self.0, index: row }
}
pub fn col(&self, col: usize) -> HcCol<'_, T, C, R> {
HcCol { matrix: &self.0, index: col }
}
pub fn linear_part(&self) -> Matrix<T, C, R, Src, Dst> {
Matrix::from_cols(self.0.0.0.map(|c| c.0))
}
pub fn with_target_space<New: Space>(self) -> HcMatrix<T, C, R, Src, New> {
HcMatrix::new_impl(self.0)
}
pub fn with_source_space<New: Space>(self) -> HcMatrix<T, C, R, New, Dst> {
HcMatrix::new_impl(self.0)
}
pub fn with_spaces<NewSrc: Space, NewDst: Space>(self) -> HcMatrix<T, C, R, NewSrc, NewDst> {
HcMatrix::new_impl(self.0)
}
pub fn transform<'a, P>(&'a self, p: P) -> <&'a Self as ops::Mul<P>>::Output
where
&'a Self: ops::Mul<P>,
{
self * p
}
pub fn and_then<const R2: usize, Dst2: Space>(
self,
second: HcMatrix<T, R, R2, Dst, Dst2>,
) -> HcMatrix<T, C, R2, Src, Dst2> {
second * self
}
pub fn transposed<NewSrc: Space, NewDst: Space>(&self) -> HcMatrix<T, R, C, NewSrc, NewDst> {
let mut out = HcMatrix::zero();
for c in 0..=C {
for r in 0..=R {
out.set_elem(c, r, self.elem(r, c));
}
}
out
}
pub fn iter(&self) -> impl Iterator<Item = T> + '_ {
(0..(C + 1) * (R + 1)).map(|idx| self.elem(idx % (R + 1), idx / (R + 1)))
}
pub fn map<U: Scalar, F: FnMut(T) -> U>(&self, mut f: F) -> HcMatrix<U, C, R, Src, Dst> {
let mut out = HcMatrix::zero();
for c in 0..=C {
for r in 0..=R {
out.set_elem(r, c, f(self.elem(r, c)));
}
}
out
}
pub fn zip_map<U, O, F>(
&self,
other: &HcMatrix<U, C, R, Src, Dst>,
mut f: F,
) -> HcMatrix<O, C, R, Src, Dst>
where
U: Scalar,
O: Scalar,
F: FnMut(T, U) -> O,
{
let mut out = HcMatrix::zero();
for c in 0..=C {
for r in 0..=R {
out.set_elem(r, c, f(self.elem(r, c), other.elem(r, c)));
}
}
out
}
pub fn as_bytes(&self) -> &[u8] {
bytemuck::bytes_of(self)
}
}
macro_rules! impl_det_inv {
($d:expr, $dpo:expr) => {
impl<T: Float, Src: Space, Dst: Space> HcMatrix<T, $d, $d, Src, Dst> {
#[doc = include_str!("determinant_docs.md")]
pub fn determinant(&self) -> T {
self.to_mat().determinant()
}
#[doc = include_str!("inverted_docs.md")]
pub fn inverted(&self) -> Option<HcMatrix<T, $d, $d, Dst, Src>> {
let inv = self.to_mat().inverted()?;
Some(<HcMatrix<T, $d, $d, Dst, Src>>::from_cols(inv.0))
}
fn to_mat(&self) -> Matrix<T, $dpo, $dpo, Src, Dst> {
Matrix::from_cols(array::from_fn(|c| self.col(c).to_array()))
}
}
};
}
impl_det_inv!(1, 2);
impl_det_inv!(2, 3);
impl_det_inv!(3, 4);
macro_rules! inc {
(1) => { 2 };
(2) => { 3 };
(3) => { 4 };
}
macro_rules! gen_inc_methods {
($( ($c:tt, $r:tt) ),+ $(,)?) => {
$(
gen_inc_methods!(@imp [], $c, inc!($c), $r, inc!($r));
)+
};
(@imp [$($const_params:tt)*],$c:expr, $cpo:expr, $r:expr, $rpo:expr) => {
impl<T: Scalar $($const_params)*, Src: Space, Dst: Space> HcMatrix<T, $c, $r, Src, Dst> {
pub fn from_rows<V: Into<[T; $cpo]>>(rows: [V; $rpo]) -> Self {
let mut out = Self::zero();
for (r, row) in rows.into_iter().enumerate() {
out.set_row(r, row.into());
}
out
}
pub fn from_cols<V: Into<[T; $rpo]>>(cols: [V; $cpo]) -> Self {
Self::new_impl(cols.map(Into::into).into())
}
pub fn set_row(&mut self, index: usize, row: [T; $cpo]) {
for c in 0..=$c {
self.set_elem(index, c, row[c]);
}
}
pub fn set_col(&mut self, index: usize, col: [T; $rpo]) {
for r in 0..=$r {
self.set_elem(r, index, col[r]);
}
}
}
impl<T: Scalar $($const_params)*> AsRef<[[T; $rpo]; $cpo]> for HcMatrixStorage<T, $c, $r> {
fn as_ref(&self) -> &[[T; $rpo]; $cpo] {
bytemuck::cast_ref(self)
}
}
impl<T: Scalar $($const_params)*> AsMut<[[T; $rpo]; $cpo]> for HcMatrixStorage<T, $c, $r> {
fn as_mut(&mut self) -> &mut [[T; $rpo]; $cpo] {
bytemuck::cast_mut(self)
}
}
impl<T: Scalar $($const_params)*> From<[[T; $rpo]; $cpo]> for HcMatrixStorage<T, $c, $r> {
fn from(value: [[T; $rpo]; $cpo]) -> Self {
bytemuck::cast(value)
}
}
}
}
gen_inc_methods!(
(1, 1), (1, 2), (1, 3),
(2, 1), (2, 2), (2, 3),
(3, 1), (3, 2), (3, 3),
);
macro_rules! gen_quadratic_inc_methods {
($( $n:tt ),+) => {
$(
gen_quadratic_inc_methods!(@imp [], $n, inc!($n));
)+
};
(@imp [$($const_params:tt)*], $n:expr, $npo:expr) => {
impl<T: Scalar $($const_params)*, Src: Space, Dst: Space> HcMatrix<T, $n, $n, Src, Dst> {
pub fn from_diagonal(diagonal: [T; $npo]) -> Self {
let mut out = Self::zero();
out.set_diagonal(diagonal);
out
}
pub fn set_diagonal(&mut self, diagonal: [T; $npo]) {
for i in 0..=$n {
self.set_elem(i, i, diagonal[i]);
}
}
pub fn diagonal(&self) -> [T; $npo] {
array::from_fn(|i| self.elem(i, i))
}
}
}
}
gen_quadratic_inc_methods!(1, 2, 3);
impl<T: Scalar, const N: usize, Src: Space, Dst: Space> HcMatrix<T, N, N, Src, Dst> {
pub fn identity() -> Self {
Self::from_diagonal_parts([T::one(); N], T::one())
}
pub fn from_diagonal_parts(linear: [T; N], q: T) -> Self {
let mut out = Self::zero();
out.set_diagonal_parts(linear, q);
out
}
pub fn diagonal_parts(&self) -> ([T; N], T) {
(array::from_fn(|i| self.elem(i, i)), self.elem(N, N))
}
pub fn set_diagonal_parts(&mut self, linear: [T; N], q: T) {
for i in 0..N {
self.set_elem(i, i, linear[i]);
}
self.set_elem(N, N, q);
}
pub fn is_symmetric(&self) -> bool {
for c in 1..=N {
for r in 0..c {
if self.elem(c, r) != self.elem(r, c) {
return false;
}
}
}
true
}
pub fn trace(&self) -> T {
let (l, q) = self.diagonal_parts();
q + l.into_iter().fold(q, |acc, e| acc + e)
}
}
unsafe impl<
T: Scalar + Zeroable,
const C: usize,
const R: usize,
Src: Space,
Dst: Space,
> Zeroable for HcMatrix<T, C, R, Src, Dst> {}
unsafe impl<
T: Scalar + Pod,
const C: usize,
const R: usize,
Src: Space,
Dst: Space,
> Pod for HcMatrix<T, C, R, Src, Dst> {}
impl<
T: Scalar,
const C: usize,
const R: usize,
Src: Space,
Dst: Space,
> fmt::Debug for HcMatrix<T, C, R, Src, Dst> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "HcMatrix ")?;
super::debug_matrix_impl(f, C + 1, R + 1, |r, c| self.elem(r, c))
}
}
super::shared_trait_impls!(HcMatrix);
super::impl_scalar_mul!(HcMatrix => f32, f64, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
impl<
T: Scalar,
const C: usize,
const R: usize,
const S: usize,
Src: Space,
Mid: Space,
Dst: Space,
> ops::Mul<HcMatrix<T, C, S, Src, Mid>> for HcMatrix<T, S, R, Mid, Dst> {
type Output = HcMatrix<T, C, R, Src, Dst>;
fn mul(self, rhs: HcMatrix<T, C, S, Src, Mid>) -> Self::Output {
let mut out = Self::Output::zero();
for c in 0..=C {
for r in 0..=R {
let mut e = T::zero();
for s in 0..=S {
e += self.elem(r, s) * rhs.elem(s, c);
}
out.set_elem(r, c, e);
}
}
out
}
}
impl<
T: Scalar,
const C: usize,
const R: usize,
Src: Space,
Dst: Space,
> ops::Mul<Point<T, C, Src>> for &HcMatrix<T, C, R, Src, Dst> {
type Output = Point<T, R, Dst>;
fn mul(self, rhs: Point<T, C, Src>) -> Self::Output {
(self * rhs.to_hc_point()).to_point()
}
}
impl<
T: Scalar,
const C: usize,
const R: usize,
Src: Space,
Dst: Space,
> ops::Mul<HcPoint<T, C, Src>> for &HcMatrix<T, C, R, Src, Dst> {
type Output = HcPoint<T, R, Dst>;
fn mul(self, rhs: HcPoint<T, C, Src>) -> Self::Output {
let dot = |row| (0..=C)
.map(|col| self.elem(row, col) * rhs[col])
.fold(T::zero(), |acc, e| acc + e);
HcPoint::new(array::from_fn(dot), dot(C))
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
struct HcMatrixStorage<T, const C: usize, const R: usize>(NPlusOneArray<NPlusOneArray<T, R>, C>);
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
struct NPlusOneArray<T, const N: usize>([T; N], T);
unsafe impl<T: Scalar + Zeroable, const C: usize, const R: usize> Zeroable
for HcMatrixStorage<T, C, R> {}
unsafe impl<T: Scalar + Pod, const C: usize, const R: usize> Pod for HcMatrixStorage<T, C, R> {}
unsafe impl<T: Scalar + Zeroable, const N: usize> Zeroable for NPlusOneArray<T, N> {}
unsafe impl<T: Scalar + Pod, const N: usize> Pod for NPlusOneArray<T, N> {}
impl<T, const N: usize> ops::Index<usize> for NPlusOneArray<T, N> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
match () {
() if index < N => &self.0[index],
() if index == N => &self.1,
_ => panic!("index ({index}) out of bounds ({})", N + 1),
}
}
}
impl<T, const N: usize> ops::IndexMut<usize> for NPlusOneArray<T, N> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
match () {
() if index < N => &mut self.0[index],
() if index == N => &mut self.1,
_ => panic!("index ({index}) out of bounds ({})", N + 1),
}
}
}
impl<T: Pod + Scalar, const N: usize> AsRef<[T]> for NPlusOneArray<T, N>
where
[T]: Pod,
{
fn as_ref(&self) -> &[T] {
bytemuck::cast_ref(self)
}
}
#[derive(Clone, Copy)]
pub struct HcRow<'a, T: Scalar, const C: usize, const R: usize> {
matrix: &'a HcMatrixStorage<T, C, R>,
index: usize,
}
impl<'a, T: Scalar, const C: usize, const R: usize> HcRow<'a, T, C, R> {
pub fn col(self, col: usize) -> T {
self.matrix.0[col][self.index]
}
}
#[derive(Clone, Copy)]
pub struct HcCol<'a, T: Scalar, const C: usize, const R: usize> {
matrix: &'a HcMatrixStorage<T, C, R>,
index: usize,
}
impl<'a, T: Scalar, const C: usize, const R: usize> HcCol<'a, T, C, R> {
pub fn row(self, row: usize) -> T {
self.matrix.0[self.index][row]
}
}
impl<'a, T: Scalar, const C: usize, const R: usize> fmt::Debug for HcRow<'a, T, C, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
crate::util::debug_list_one_line((0..=C).map(|c| self.col(c)), f)
}
}
impl<'a, T: Scalar, const C: usize, const R: usize> fmt::Debug for HcCol<'a, T, C, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
crate::util::debug_list_one_line((0..=R).map(|r| self.row(r)), f)
}
}
macro_rules! gen_col_row_impls {
($( ($c:tt, $r:tt) ),+ $(,)?) => {
$(
gen_col_row_impls!(@imp [], $c, inc!($c), $r, inc!($r));
)+
};
(@imp [$($const_params:tt)*], $c:expr, $cpo:expr, $r:expr, $rpo:expr) => {
impl<'a, T: Scalar $($const_params)*> HcRow<'a, T, $c, $r> {
pub fn to_array(self) -> [T; $cpo] {
self.into()
}
pub fn to_vec<S: Space>(self) -> Vector<T, {$cpo}, S> {
self.into()
}
pub fn to_point<S: Space>(self) -> Point<T, {$cpo}, S> {
self.into()
}
}
impl<'a, T: Scalar $($const_params)*> From<HcRow<'a, T, $c, $r>> for [T; $cpo] {
fn from(src: HcRow<'a, T, $c, $r>) -> Self {
array::from_fn(|i| src.matrix.0[i][src.index])
}
}
impl<'a, T: Scalar $($const_params)*, S: Space> From<HcRow<'a, T, $c, $r>>
for Vector<T, {$cpo}, S>
{
fn from(src: HcRow<'a, T, $c, $r>) -> Self {
src.to_array().into()
}
}
impl<'a, T: Scalar $($const_params)*, S: Space> From<HcRow<'a, T, $c, $r>>
for Point<T, {$cpo}, S>
{
fn from(src: HcRow<'a, T, $c, $r>) -> Self {
src.to_array().into()
}
}
impl<'a, T: Scalar $($const_params)*> HcCol<'a, T, $c, $r> {
pub fn to_array(self) -> [T; $rpo] {
self.into()
}
pub fn to_vec<S: Space>(self) -> Vector<T, {$rpo}, S> {
self.into()
}
pub fn to_point<S: Space>(self) -> Point<T, {$rpo}, S> {
self.into()
}
}
impl<'a, T: Scalar $($const_params)*> From<HcCol<'a, T, $c, $r>> for [T; $rpo] {
fn from(src: HcCol<'a, T, $c, $r>) -> Self {
array::from_fn(|i| src.matrix.0[src.index][i])
}
}
impl<'a, T: Scalar $($const_params)*, S: Space> From<HcCol<'a, T, $c, $r>>
for Vector<T, {$rpo}, S>
{
fn from(src: HcCol<'a, T, $c, $r>) -> Self {
src.to_array().into()
}
}
impl<'a, T: Scalar $($const_params)*, S: Space> From<HcCol<'a, T, $c, $r>>
for Point<T, {$rpo}, S>
{
fn from(src: HcCol<'a, T, $c, $r>) -> Self {
src.to_array().into()
}
}
}
}
gen_col_row_impls!(
(1, 1), (1, 2), (1, 3),
(2, 1), (2, 2), (2, 3),
(3, 1), (3, 2), (3, 3),
);