// // Lol Engine // // Copyright © 2010—2019 Sam Hocevar // // Lol Engine is free software. It comes without any warranty, to // the extent permitted by applicable law. You can redistribute it // and/or modify it under the terms of the Do What the Fuck You Want // to Public License, Version 2, as published by the WTFPL Task Force. // See http://www.wtfpl.net/ for more details. // #pragma once // // The matrix classes // ------------------ // #include #include #include #if _WIN32 # pragma push_macro("near") # pragma push_macro("far") # undef near # undef far #endif namespace lol { /* * The generic “mat_t” type, which is fixed-size */ template struct lol_attr_nodiscard mat_t : public linear_ops::base> { static int const count = COLS; typedef T scalar_element; typedef vec_t element; typedef mat_t type; inline mat_t() {} explicit inline mat_t(T const &val) { T const zero = T(0); for (int i = 0; i < COLS; ++i) for (int j = 0; j < ROWS; ++j) m_data[i][j] = i == j ? val : zero; } /* Explicit constructor for type conversion */ template explicit inline mat_t(mat_t const &m) { for (int i = 0; i < COLS; ++i) m_data[i] = (vec_t)m[i]; } inline vec_t& operator[](size_t n) { return m_data[n]; } inline vec_t const& operator[](size_t n) const { return m_data[n]; } private: vec_t m_data[COLS]; }; /* * 2×2-element matrices */ template struct lol_attr_nodiscard mat_t : public linear_ops::base> { static int const count = 2; typedef T scalar_element; typedef vec_t element; typedef mat_t type; inline mat_t() {} inline mat_t(vec_t v0, vec_t v1) : m_data{ v0, v1 } {} explicit inline mat_t(T const &val) : m_data{ vec_t(val, T(0)), vec_t(T(0), val) } {} explicit inline mat_t(mat_t const &m) : m_data{ m[0].xy, m[1].xy } {} /* Explicit constructor for type conversion */ template explicit inline mat_t(mat_t const &m) : m_data{ (element)m[0], (element)m[1] } {} inline vec_t& operator[](size_t n) { return m_data[n]; } inline vec_t const& operator[](size_t n) const { return m_data[n]; } /* Helpers for transformation matrices */ static mat_t rotate(T radians); static inline mat_t rotate(mat_t m, T radians) { return rotate(radians) * m; } void printf() const; std::string tostring() const; static const mat_t identity; private: vec_t m_data[2]; }; static_assert(sizeof(imat2) == 16, "sizeof(imat2) == 16"); static_assert(sizeof(f16mat2) == 8, "sizeof(f16mat2) == 8"); static_assert(sizeof(mat2) == 16, "sizeof(mat2) == 16"); static_assert(sizeof(dmat2) == 32, "sizeof(dmat2) == 32"); /* * 3×3-element matrices */ template struct lol_attr_nodiscard mat_t : public linear_ops::base> { static int const count = 3; typedef T scalar_element; typedef vec_t element; typedef mat_t type; inline mat_t() {} inline mat_t(vec_t v0, vec_t v1, vec_t v2) : m_data{ v0, v1, v2 } {} explicit inline mat_t(T const &val) : m_data{ vec_t(val, (T)0, (T)0), vec_t((T)0, val, (T)0), vec_t((T)0, (T)0, val) } {} explicit inline mat_t(mat_t m, T const &val = T(1)) : m_data{ vec_t(m[0], (T)0), vec_t(m[1], (T)0), vec_t((T)0, (T)0, val) } {} explicit inline mat_t(mat_t const &m) : m_data{ m[0].xyz, m[1].xyz, m[2].xyz } {} /* Explicit constructor for type conversion */ template explicit inline mat_t(mat_t const &m) : m_data{ (element)m[0], (element)m[1], (element)m[2] } {} explicit mat_t(quat_t const &q); inline vec_t& operator[](size_t n) { return m_data[n]; } inline vec_t const& operator[](size_t n) const { return m_data[n]; } /* Helpers for transformation matrices */ static mat_t scale(T x); static mat_t scale(T x, T y, T z); static mat_t scale(vec_t v); static mat_t rotate(T radians, T x, T y, T z); static mat_t rotate(T radians, vec_t v); static mat_t fromeuler_xyz(vec_t const &v); static mat_t fromeuler_xzy(vec_t const &v); static mat_t fromeuler_yxz(vec_t const &v); static mat_t fromeuler_yzx(vec_t const &v); static mat_t fromeuler_zxy(vec_t const &v); static mat_t fromeuler_zyx(vec_t const &v); static mat_t fromeuler_xyz(T phi, T theta, T psi); static mat_t fromeuler_xzy(T phi, T theta, T psi); static mat_t fromeuler_yxz(T phi, T theta, T psi); static mat_t fromeuler_yzx(T phi, T theta, T psi); static mat_t fromeuler_zxy(T phi, T theta, T psi); static mat_t fromeuler_zyx(T phi, T theta, T psi); static mat_t fromeuler_xyx(vec_t const &v); static mat_t fromeuler_xzx(vec_t const &v); static mat_t fromeuler_yxy(vec_t const &v); static mat_t fromeuler_yzy(vec_t const &v); static mat_t fromeuler_zxz(vec_t const &v); static mat_t fromeuler_zyz(vec_t const &v); static mat_t fromeuler_xyx(T phi, T theta, T psi); static mat_t fromeuler_xzx(T phi, T theta, T psi); static mat_t fromeuler_yxy(T phi, T theta, T psi); static mat_t fromeuler_yzy(T phi, T theta, T psi); static mat_t fromeuler_zxz(T phi, T theta, T psi); static mat_t fromeuler_zyz(T phi, T theta, T psi); static inline mat_t rotate(mat_t m, T radians, vec_t v) { return rotate(radians, v) * m; } void printf() const; std::string tostring() const; static const mat_t identity; private: vec_t m_data[3]; }; static_assert(sizeof(imat3) == 36, "sizeof(imat3) == 36"); static_assert(sizeof(f16mat3) == 18, "sizeof(f16mat3) == 18"); static_assert(sizeof(mat3) == 36, "sizeof(mat3) == 36"); static_assert(sizeof(dmat3) == 72, "sizeof(dmat3) == 72"); /* * 4×4-element matrices */ template struct lol_attr_nodiscard mat_t : public linear_ops::base> { static int const count = 4; typedef T scalar_element; typedef vec_t element; typedef mat_t type; inline mat_t() {} inline mat_t(vec_t v0, vec_t v1, vec_t v2, vec_t v3) : m_data{ v0, v1, v2, v3 } {} explicit inline mat_t(T const &val) : m_data{ vec_t(val, (T)0, (T)0, (T)0), vec_t((T)0, val, (T)0, (T)0), vec_t((T)0, (T)0, val, (T)0), vec_t((T)0, (T)0, (T)0, val) } {} explicit inline mat_t(mat_t m, T const &val = T(1)) : m_data{ vec_t(m[0], (T)0, (T)0), vec_t(m[1], (T)0, (T)0), vec_t((T)0, (T)0, val, (T)0), vec_t((T)0, (T)0, (T)0, val) } {} explicit inline mat_t(mat_t m, T const &val = T(1)) : m_data{ vec_t(m[0], (T)0), vec_t(m[1], (T)0), vec_t(m[2], (T)0), vec_t((T)0, (T)0, (T)0, val) } {} /* Explicit constructor for type conversion */ template explicit inline mat_t(mat_t const &m) : m_data{ (element)m[0], (element)m[1], (element)m[2], (element)m[3] } {} explicit mat_t(quat_t const &q); inline vec_t& operator[](size_t n) { return m_data[n]; } inline vec_t const& operator[](size_t n) const { return m_data[n]; } /* Helpers for transformation matrices */ static mat_t translate(T x, T y, T z); static mat_t translate(vec_t v); static inline mat_t scale(T x) { return mat_t(mat_t::scale(x), (T)1); } static inline mat_t scale(T x, T y, T z) { return mat_t(mat_t::scale(x, y, z), (T)1); } static inline mat_t scale(vec_t v) { return mat_t(mat_t::scale(v), (T)1); } static inline mat_t translate(mat_t const &m, vec_t v) { return translate(v) * m; } static inline mat_t rotate(T radians, T x, T y, T z) { return mat_t(mat_t::rotate(radians, x, y, z), (T)1); } static inline mat_t rotate(T radians, vec_t v) { return mat_t(mat_t::rotate(radians, v), (T)1); } static inline mat_t rotate(mat_t &m, T radians, vec_t v) { return rotate(radians, v) * m; } static mat_t fromeuler_xyz(vec_t const &v); static mat_t fromeuler_xzy(vec_t const &v); static mat_t fromeuler_yxz(vec_t const &v); static mat_t fromeuler_yzx(vec_t const &v); static mat_t fromeuler_zxy(vec_t const &v); static mat_t fromeuler_zyx(vec_t const &v); static mat_t fromeuler_xyz(T phi, T theta, T psi); static mat_t fromeuler_xzy(T phi, T theta, T psi); static mat_t fromeuler_yxz(T phi, T theta, T psi); static mat_t fromeuler_yzx(T phi, T theta, T psi); static mat_t fromeuler_zxy(T phi, T theta, T psi); static mat_t fromeuler_zyx(T phi, T theta, T psi); static mat_t fromeuler_xyx(vec_t const &v); static mat_t fromeuler_xzx(vec_t const &v); static mat_t fromeuler_yxy(vec_t const &v); static mat_t fromeuler_yzy(vec_t const &v); static mat_t fromeuler_zxz(vec_t const &v); static mat_t fromeuler_zyz(vec_t const &v); static mat_t fromeuler_xyx(T phi, T theta, T psi); static mat_t fromeuler_xzx(T phi, T theta, T psi); static mat_t fromeuler_yxy(T phi, T theta, T psi); static mat_t fromeuler_yzy(T phi, T theta, T psi); static mat_t fromeuler_zxz(T phi, T theta, T psi); static mat_t fromeuler_zyz(T phi, T theta, T psi); /* Helpers for view matrices */ static mat_t lookat(vec_t eye, vec_t center, vec_t up); /* Helpers for projection matrices; FOV values are in radians */ static mat_t ortho(T left, T right, T bottom, T top, T near, T far); static mat_t ortho(T width, T height, T near, T far); static mat_t frustum(T left, T right, T bottom, T top, T near, T far); static mat_t perspective(T fov_y, T width, T height, T near, T far); static mat_t shifted_perspective(T fov_y, T screen_size, T screen_ratio_yx, T near, T far); void printf() const; std::string tostring() const; static const mat_t identity; private: vec_t m_data[4]; }; static_assert(sizeof(imat4) == 64, "sizeof(imat4) == 64"); static_assert(sizeof(f16mat4) == 32, "sizeof(f16mat4) == 32"); static_assert(sizeof(mat4) == 64, "sizeof(mat4) == 64"); static_assert(sizeof(dmat4) == 128, "sizeof(dmat4) == 128"); /* * stdstream method implementations */ template static std::ostream &operator<<(std::ostream &stream, mat_t const &m) { for (int y = 0; y < ROWS; ++y) { stream << (y == 0 ? "(" : ", "); for (int x = 0; x < COLS; ++x) stream << (x == 0 ? "(" : ", ") << m[x][y]; stream << ")"; } return stream << ")"; } /* * Transpose any matrix */ template static inline mat_t transpose(mat_t const &m) { mat_t ret; for (int i = 0; i < COLS; ++i) for (int j = 0; j < ROWS; ++j) ret[j][i] = m[i][j]; return ret; } /* * Compute a square submatrix, useful for minors and cofactor matrices */ template mat_t submatrix(mat_t const &m, int i, int j) { ASSERT(i >= 0); ASSERT(j >= 0); ASSERT(i < N); ASSERT(j < N); mat_t ret; for (int i2 = 0; i2 < N - 1; ++i2) for (int j2 = 0; j2 < N - 1; ++j2) ret[i2][j2] = m[i2 + (i2 >= i)][j2 + (j2 >= j)]; return ret; } /* * Compute square matrix cofactor */ template lol_attr_nodiscard T cofactor(mat_t const &m, int i, int j) { ASSERT(i >= 0); ASSERT(j >= 0); ASSERT(i < N); ASSERT(j < N); T tmp = determinant(submatrix(m, i, j)); return ((i + j) & 1) ? -tmp : tmp; } template lol_attr_nodiscard T cofactor(mat_t const &m, int i, int j) { /* This specialisation shouldn't be needed, but Visual Studio. */ ASSERT(i >= 0); ASSERT(j >= 0); ASSERT(i < 2); ASSERT(j < 2); T tmp = m[1 - i][1 - j]; return (i ^ j) ? -tmp : tmp; } // Lu decomposition with partial pivoting template lol_attr_nodiscard std::tuple, vec_t, int> lu_decomposition(mat_t const &m) { mat_t lu = m; vec_t perm; int sign = 1; for (int i = 0; i < N; ++i) perm[i] = i; for (int k = 0; k < N; ++k) { // Find row with the largest absolute value int best_j = k; for (int j = k + 1; j < N; ++j) if (abs(lu[k][j]) > lol::abs(lu[k][best_j])) best_j = j; // Swap rows in result if (best_j != k) { std::swap(perm[k], perm[best_j]); sign = -sign; for (int i = 0; i < N; ++i) std::swap(lu[i][k], lu[i][best_j]); } // Compute the Schur complement in the lower triangular part for (int j = k + 1; j < N; ++j) { lu[k][j] /= lu[k][k]; for (int i = k + 1; i < N; ++i) lu[i][j] -= lu[i][k] * lu[k][j]; } } return std::make_tuple(lu, perm, sign); } /* * Compute square matrix determinant, with a specialisation for 1×1 matrices */ template lol_attr_nodiscard T determinant(mat_t const &m) { auto lup = lu_decomposition(m); T det(T(std::get<2>(lup))); for (int i = 0; i < N; ++i) det *= std::get<0>(lup)[i][i]; return det; } template lol_attr_nodiscard T const & determinant(mat_t const &m) { return m[0][0]; } // Compute inverse of the L matrix of an LU decomposition template mat_t l_inverse(mat_t const & lu) { mat_t ret { 0 }; for (int j = 0; j < N; ++j) { for (int i = j; i >= 0; --i) { T sum = 0; for (int k = i + 1; k <= j; ++k) sum += ret[k][j] * lu[i][k]; ret[i][j] = T(j == i ? 1 : 0) - sum; } } return ret; } // Compute inverse of the U matrix of an LU decomposition template mat_t u_inverse(mat_t const & lu) { mat_t ret { 0 }; for (int i = 0; i < N; ++i) { for (int j = i; j < N; ++j) { T sum = 0; for (int k = i; k < j; ++k) sum += ret[k][i] * lu[j][k]; ret[j][i] = ((i == j ? 1 : 0) - sum) / lu[j][j]; } } return ret; } /* * Compute square matrix inverse */ template mat_t inverse(mat_t const &m) { auto lup = lu_decomposition(m); auto lu = std::get<0>(lup); auto p = std::get<1>(lup); auto invlu = u_inverse(lu) * l_inverse(lu); // Rearrange columns according to the original permutation vector mat_t ret; for (int i = 0; i < N; ++i) ret[p[i]] = invlu[i]; return ret; } /* * Matrix-vector and vector-matrix multiplication */ template static inline vec_t operator *(mat_t const &m, vec_t const &v) { vec_t ret(T(0)); for (int i = 0; i < COLS; ++i) ret += m[i] * v[i]; return ret; } template static inline vec_t operator *(vec_t const &v, mat_t const &m) { vec_t ret(T(0)); for (int i = 0; i < COLS; ++i) ret[i] = dot(v, m[i]); return ret; } /* * Matrix-matrix multiplication */ template static inline mat_t operator *(mat_t const &a, mat_t const &b) { mat_t ret; for (int i = 0; i < COLS; ++i) ret[i] = a * b[i]; return ret; } template static inline mat_t &operator *=(mat_t &a, mat_t const &b) { return a = a * b; } /* * Vector-vector outer product */ template static inline mat_t outer(vec_t const &a, vec_t const &b) { /* Valid cast because mat_t and vec_t have similar layouts */ return *reinterpret_cast const *>(&a) * *reinterpret_cast const *>(&b); } /* * Matrix-matrix outer product (Kronecker product) */ template static inline mat_t outer(mat_t const &a, mat_t const &b) { mat_t ret; for (int i1 = 0; i1 < COLS1; ++i1) for (int i2 = 0; i2 < COLS2; ++i2) { /* Valid cast because mat_t and vec_t have similar layouts */ *reinterpret_cast *>(&ret[i1 * COLS2 + i2]) = outer(b[i2], a[i1]); } return ret; } /* * Constants */ template mat_t const mat_t::identity = mat_t((T)1); template mat_t const mat_t::identity = mat_t((T)1); template mat_t const mat_t::identity = mat_t((T)1); } /* namespace lol */ #if _WIN32 # pragma pop_macro("near") # pragma pop_macro("far") #endif