// L============================================================================= // L This software is distributed under the MIT license. // L Copyright 2021 Péter Kardos // L============================================================================= #pragma once #include "../Matrix/MatrixImpl.hpp" #include "../Vector.hpp" namespace mathter { template class ViewBuilder { using VectorT = Vector; public: ViewBuilder(const VectorT& eye, const VectorT& target, const std::array& bases, const std::array& flipAxes) : eye(eye), target(target), bases(bases), flipAxes(flipAxes) {} ViewBuilder& operator=(const ViewBuilder&) = delete; template operator Matrix() const { Matrix m; Set(m); return m; } template operator Matrix() const { Matrix m; Set(m); return m; } template operator Matrix() const { Matrix m; Set(m); return m; } private: template void Set(Matrix& matrix) const { VectorT columns[Dim]; std::array crossTable = {}; for (int i = 0; i < (int)bases.size(); ++i) { crossTable[i] = &bases[i]; } crossTable.back() = &columns[Dim - 1]; auto elem = [&matrix](int i, int j) -> U& { return Order == eMatrixOrder::FOLLOW_VECTOR ? matrix(i, j) : matrix(j, i); }; // calculate columns of the rotation matrix int j = Dim - 1; columns[j] = Normalize(eye - target); // right-handed: camera look towards -Z do { --j; columns[Dim - j - 2] = Normalize(Cross(crossTable)); // shift bases for (int s = 0; s < j; ++s) { crossTable[s] = crossTable[s + 1]; } crossTable[j] = &columns[Dim - j - 2]; } while (j > 0); // flip columns for (int i = 0; i < Dim; ++i) { if (flipAxes[i]) { columns[i] *= -T(1); } } // copy columns to matrix for (int i = 0; i < Dim; ++i) { for (int j = 0; j < Dim; ++j) { elem(i, j) = columns[j][i]; } } // calculate translation of the matrix for (int j = 0; j < Dim; ++j) { elem(Dim, j) = -Dot(eye, columns[j]); } // clear additional elements constexpr int AuxDim = Rows < Columns ? Rows : Columns; if (AuxDim > Dim) { for (int i = 0; i < Dim; ++i) { elem(i, AuxDim - 1) = 0; } elem(Dim, AuxDim - 1) = 1; } } const VectorT eye; const VectorT target; const std::array bases; const std::array flipAxes; }; /// Creates a general, n-dimensional camera look-at matrix. /// The camera's position. /// The camera's target. /// Basis vectors fixing the camera's orientation. /// Set any element to true to flip an axis in camera space. /// The camera looks down the vector going from to /// , but it can still rotate around that vector. To fix the rotation, /// an "up" vector must be provided in 3 dimensions. In higher dimensions, /// we need multiple up vectors. Unfortunately I can't remember how these /// basis vectors are used, but they are orthogonalized to each-other and to the look vector. /// I can't remember the order of orthogonalization. template auto LookAt(const Vector& eye, const Vector& target, const std::array, BaseDim>& bases, const std::array& flipAxes) { static_assert(BaseDim == Dim - 2, "You must provide 2 fewer bases than the dimension of the transform."); static_assert(Dim == FlipDim, "You must provide the same number of flips as the dimension of the transform."); return ViewBuilder(eye, target, bases, flipAxes); } /// Creates a 2D look-at matrix. /// The camera's position. /// The camera's target. /// True if the camera looks towards +Y in camera space, false if -Y. /// True to flip X in camera space. template auto LookAt(const Vector& eye, const Vector& target, bool positiveYForward, bool flipX) { return LookAt(eye, target, std::array, 0>{}, std::array{ flipX, positiveYForward }); } /// Creates a 3D look-at matrix. /// The camera's position. /// The camera's target. /// Up direction in world space. /// True if the camera looks towards +Z in camera space, false if -Z. /// True to flip X in camera space. /// True to flip Y in camera space. /// The camera space X is selected to be orthogonal to both the look direction and the vector. /// Afterwards, the vector is re-orthogonalized to the camera-space Z and X vectors. template auto LookAt(const Vector& eye, const Vector& target, const Vector& up, bool positiveZForward, bool flipX, bool flipY) { return LookAt(eye, target, std::array, 1>{ up }, std::array{ flipX, flipY, positiveZForward }); } } // namespace mathter