/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright 2011-2015 Danny Robson */ #ifndef CRUFT_UTIL_MATRIX_HPP #define CRUFT_UTIL_MATRIX_HPP #include "point.hpp" #include "range.hpp" #include "vector.hpp" #include "iterator.hpp" #include #include namespace util { /////////////////////////////////////////////////////////////////////////// /// a row-major matrix with parameterised size and type /// /// while sizes and types are arbitrary the library is optimised for /// dimensions up to ~4, and value types of float. though others _should_ /// work depending on what operations you pick and behaviours you expect /// (eg, an int will round a little differently to floats...) template < std::size_t Rows, std::size_t Cols, typename ValueT > struct matrix { static constexpr auto rows = Rows; static constexpr auto cols = Cols; using row_t = util::vector; /////////////////////////////////////////////////////////////////////// constexpr matrix () noexcept = default; constexpr matrix(ValueT fill) { for (auto &r: values) r = fill; } //--------------------------------------------------------------------- constexpr matrix (const ValueT(&_data)[Rows][Cols]) noexcept: values {} { static_assert (sizeof (*this) == sizeof (ValueT) * Rows * Cols); for (std::size_t r = 0; r < Rows; ++r) for (std::size_t c = 0; c < Cols; ++c) values[r][c] = _data[r][c]; } //--------------------------------------------------------------------- template constexpr matrix (const util::coord::base (&_data)[Rows]) noexcept { for (std::size_t r = 0; r < Rows; ++r) for (std::size_t c = 0; c < Cols; ++c) values[r][c] = _data[r][c]; } /////////////////////////////////////////////////////////////////////// // index operators return a pointer into the data array so that // multidimensional array syntax can be used transparently on this // type. constexpr row_t& operator[] (std::size_t idx)& { CHECK_LT (idx, rows); return values[idx]; } //--------------------------------------------------------------------- constexpr const row_t& operator[] (std::size_t idx) const& { CHECK_LT (idx, rows); return values[idx]; } /////////////////////////////////////////////////////////////////////// constexpr row_t* data (void)& noexcept { return &values[0]; } //--------------------------------------------------------------------- constexpr const row_t* data (void) const& noexcept { return &values[0]; } //--------------------------------------------------------------------- constexpr auto begin (void) const& noexcept { return data (); } //--------------------------------------------------------------------- constexpr auto begin (void)& noexcept { return data (); } //--------------------------------------------------------------------- constexpr row_t* end (void)& noexcept { return &values[Rows]; } //--------------------------------------------------------------------- constexpr const row_t* end (void) const& noexcept { return &values[Rows]; } //--------------------------------------------------------------------- constexpr auto cbegin (void) const& noexcept { return begin (); } //--------------------------------------------------------------------- constexpr auto cend (void) const& noexcept { return end (); } /////////////////////////////////////////////////////////////////////// ValueT determinant (void) const; matrix inverse (void) const; matrix inverse_affine (void) const { // TODO: ensure we have specialisations for typical dimensions return inverse (); } template < typename VectorT, typename = std::enable_if_t> > auto operator* (const VectorT &rhs) const { if constexpr (VectorT::elements + 1 == Cols) { return ( *this * rhs.homog () ).template redim (); } else { VectorT out; for (std::size_t r = 0; r < Rows; ++r) out[r] = dot (rhs, values[r]); return out; } } bool is_affine (void) const; template matrix cast (void) const noexcept { matrix out; std::copy (cbegin (), cend (), std::begin (out)); return out; } // Constant matrices static constexpr matrix identity (void) noexcept { auto m = zeroes (); for (std::size_t i = 0; i < Rows; ++i) m[i][i] = 1; return m; } static constexpr matrix zeroes (void) noexcept { matrix ret {}; std::fill (std::begin (ret), std::end (ret), row_t{0}); return ret; } private: row_t values[Rows]; }; // Perspective matrices template matrix<4,4,T> ortho (T left, T right, T bottom, T top, T near, T far); template matrix<4,4,T> ortho2D (T left, T right, T bottom, T top); template matrix<4,4,T> perspective (T fov, T aspect, range Z); template matrix<4,4,T> look_at (point<3,T> eye, point<3,T> target, vector<3,T> up); // Affine matrices template matrix translation (vector offset) { matrix res {}; for (size_t i = 0; i != S; ++i) { res[i][i] = 1; res[i][S] = offset[i]; } res[S][S] = 1; return res; } template matrix scale (vector factor) { matrix res {}; for (size_t i = 0; i != S; ++i) res[i][i] = factor[i]; res[S][S] = 1; return res; } //template matrix<4,4,T> translation (vector<3,T>); //template matrix<4,4,T> scale (vector<3,T>); //template matrix<4,4,T> scale (T); template matrix<4,4,T> rotation (T angle, vector<3,T> about); //template matrix<3,3,T> translation (vector<2,T>); //template matrix<3,3,T> scale (vector<2,T>); template bool relatively_equal (const util::matrix &a, const util::matrix &b, const float percentage) { for (size_t r = 0; r < Rows; ++r) if (!all (relatively_equal (a[r], b[r], percentage))) return false; return true; } /////////////////////////////////////////////////////////////////////////// // Convert an affine rotation matrix to euler angles. // // Results are undefined if the matrix is not purely a rotation matrix, // or if the dimension is not 3x3 or 4x4. template vector<3,T> to_euler (const matrix&); /////////////////////////////////////////////////////////////////////////// // logical operations template constexpr bool operator== (const matrix &a, const matrix &b) { return std::equal (std::cbegin (a), std::cend (a), std::cbegin (b)); } //------------------------------------------------------------------------- template constexpr bool operator!= (const matrix &a, const matrix &b) { return !(a == b); } /////////////////////////////////////////////////////////////////////////// // element operations template constexpr matrix operator+ (const matrix&, const matrix&); template constexpr matrix operator- (const matrix&, const matrix&); template constexpr matrix div (const matrix &a, const matrix &b) { matrix out {}; for (std::size_t r = 0; r < Rows; ++r) for (std::size_t c = 0; c < Cols; ++c) out[r][c] = a[r][c] / b[r][c]; return out; } template constexpr T max (const matrix &m) { T val = m[0][0]; for (std::size_t r = 0; r < Rows; ++r) for (std::size_t c = 0; c < Cols; ++c) val = max (val, m[r][c]); return val; } /////////////////////////////////////////////////////////////////////////// // scalar operations template constexpr matrix operator* (const matrix&, T); template constexpr matrix operator/ (const matrix&, T); template constexpr matrix operator+ (const matrix&, T); template constexpr matrix operator- (const matrix&, T); template constexpr matrix operator* (T, const matrix&); template constexpr matrix operator/ (T, const matrix&); template constexpr matrix operator+ (T, const matrix&); template constexpr matrix operator- (T, const matrix&); template constexpr matrix& operator*= (matrix&, T); template constexpr matrix& operator/= (matrix&, T); template constexpr matrix& operator+= (matrix&, T); template constexpr matrix& operator-= (matrix&, T); /////////////////////////////////////////////////////////////////////////// // matrix operations template < std::size_t R1, //int C1, std::size_t RC, //int R2, std::size_t C2, typename T > constexpr matrix operator* (const matrix&a, const matrix&b) { matrix res {}; // TODO: iterating over r,c rather than c,r will cause an ICE with // clang#xxxx: 'X86 DAG->DAG Instruction Selection'. // // this is likely related to gold and LTO support. for the time being // we switch the orders because it appears to confuse the optimiser // sufficiently. :( for (std::size_t c = 0; c < C2; ++c) { for (std::size_t r = 0; r < R1; ++r) { T accum {0}; for (std::size_t i = 0; i < RC; ++i) accum += a[r][i] * b[i][c]; res[r][c] = accum; } } return res; } //------------------------------------------------------------------------- template < std::size_t R1, std::size_t C1, std::size_t R2, std::size_t C2, typename T > constexpr matrix& operator*= (matrix &a, const matrix &b) { return a = a * b; }; /////////////////////////////////////////////////////////////////////////// template T determinant (const matrix&); //------------------------------------------------------------------------- template matrix inverse (const matrix&); //------------------------------------------------------------------------- template matrix transposed (const matrix&); /////////////////////////////////////////////////////////////////////////// template matrix abs (const matrix &src) { matrix dst; for (size_t r = 0; r < Rows; ++r) dst[r] = abs (src[r]); return dst; } template constexpr T sum (const matrix &src) { util::vector accum {}; for (size_t r = 0; r < Rows; ++r) accum[r] = sum (src[r]); return sum (accum); } /////////////////////////////////////////////////////////////////////////// #define MATRIX_ELEMENT_OP(OP) \ template \ constexpr \ matrix \ operator OP ( \ const matrix &a, \ const matrix &b) \ { \ matrix res {}; \ \ for (std::size_t i = 0; i < a.rows; ++i) \ for (std::size_t j = 0; j < a.cols; ++j) \ res[i][j] = a[i][j] OP b[i][j]; \ \ return res; \ } MATRIX_ELEMENT_OP(-) MATRIX_ELEMENT_OP(+) #undef MATRIX_ELEMENT_OP /////////////////////////////////////////////////////////////////////////// #define MATRIX_SCALAR_OP(OP) \ template \ constexpr \ matrix \ operator OP (const matrix &m, const T t) \ { \ matrix res {}; \ \ std::transform ( \ std::cbegin (m), \ std::cend (m), \ std::begin (res), \ [&t] (auto x) { return x OP t; } \ ); \ \ return res; \ } \ \ \ template \ constexpr \ matrix \ operator OP (const T t, const matrix &m) \ { \ return m OP t; \ } \ \ \ template \ constexpr \ matrix& \ operator OP##= (matrix &m, T t) \ { \ std::transform ( \ std::cbegin (m), \ std::cend (m), \ std::begin (m), \ [&t] (auto x) { return x OP t; } \ ); \ \ return m; \ } MATRIX_SCALAR_OP(*) MATRIX_SCALAR_OP(/) MATRIX_SCALAR_OP(+) MATRIX_SCALAR_OP(-) #undef MATRIX_SCALAR_OP /////////////////////////////////////////////////////////////////////////// template using matrix3 = matrix<3,3,T>; template using matrix4 = matrix<4,4,T>; template using matrixf = matrix; template using matrixd = matrix; typedef matrix<2,2,float> matrix2f; typedef matrix<2,2,double> matrix2d; typedef matrix<3,3,float> matrix3f; typedef matrix<3,3,double> matrix3d; typedef matrix<4,4,float> matrix4f; typedef matrix<4,4,double> matrix4d; /////////////////////////////////////////////////////////////////////////// template std::ostream& operator<< (std::ostream &os, const matrix &m) { os << '['; std::copy ( std::cbegin (m), std::cend (m), util::infix_iterator (os, ", ") ); return os << ']'; } }; #endif