// L============================================================================= // L This software is distributed under the MIT license. // L Copyright 2021 Péter Kardos // L============================================================================= #pragma once #include "../Matrix/MatrixFunction.hpp" #include "../Matrix/MatrixImpl.hpp" #include "../Quaternion/QuaternionImpl.hpp" #include "../Vector.hpp" #include "IdentityBuilder.hpp" #include namespace mathter { template class Rotation3DAxisBuilder { public: Rotation3DAxisBuilder(const T& angle, int axis) : angle(angle), axis(axis) {} Rotation3DAxisBuilder& operator=(const Rotation3DAxisBuilder&) = 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; } template operator Matrix() const { Matrix m; Set(m); return m; } template operator Quaternion() const; private: template void Set(Matrix& m) const { T C = cos(angle); T S = sin(angle); auto elem = [&m](int i, int j) -> U& { return Order == eMatrixOrder::FOLLOW_VECTOR ? m(i, j) : m(j, i); }; assert(0 <= axis && axis < 3); // Indices according to follow vector order if (axis == 0) { // Rotate around X elem(0, 0) = U(1); elem(0, 1) = U(0); elem(0, 2) = U(0); elem(1, 0) = U(0); elem(1, 1) = U(C); elem(1, 2) = U(S); elem(2, 0) = U(0); elem(2, 1) = U(-S); elem(2, 2) = U(C); } else if (axis == 1) { // Rotate around Y elem(0, 0) = U(C); elem(0, 1) = U(0); elem(0, 2) = U(-S); elem(1, 0) = U(0); elem(1, 1) = U(1); elem(1, 2) = U(0); elem(2, 0) = U(S); elem(2, 1) = U(0); elem(2, 2) = U(C); } else { // Rotate around Z elem(0, 0) = U(C); elem(0, 1) = U(S); elem(0, 2) = U(0); elem(1, 0) = U(-S); elem(1, 1) = U(C); elem(1, 2) = U(0); elem(2, 0) = U(0); elem(2, 1) = U(0); elem(2, 2) = U(1); } // Rest for (int j = 0; j < m.ColumnCount(); ++j) { for (int i = (j < 3 ? 3 : 0); i < m.RowCount(); ++i) { m(i, j) = U(j == i); } } } const float angle; const int axis; }; /// Rotates around coordinate axis. /// 0 for X, 1 for Y, 2 for Z and so on... /// Angle of rotation in radians. /// Positive angles rotate according to the right-hand rule in right-handed /// coordinate systems (left-handed according to left-hand rule). template auto RotationAxis(T angle, int axis) { return Rotation3DAxisBuilder(angle, axis); } /// Rotates around coordinate axis. /// 0 for X, 1 for Y, 2 for Z and so on... /// Angle of rotation in radians. /// Positive angles rotate according to the right-hand rule in right-handed /// coordinate systems (left-handed according to left-hand rule). template auto RotationAxis(T angle) { return Rotation3DAxisBuilder(angle, Axis); } /// Rotates around the X axis according to the right (left) hand rule in right (left) handed systems. /// Angle of rotation in radians. template auto RotationX(T angle) { return RotationAxis<0>(angle); } /// Rotates around the Y axis according to the right (left) hand rule in right (left) handed systems. /// Angle of rotation in radians. template auto RotationY(T angle) { return RotationAxis<1>(angle); } /// Rotates around the Z axis according to the right (left) hand rule in right (left) handed systems. /// Angle of rotation in radians. template auto RotationZ(T angle) { return RotationAxis<2>(angle); } template class Rotation3DTriAxisBuilder { public: Rotation3DTriAxisBuilder(const std::array& angles, std::array axes) : angles(angles), axes(axes) {} Rotation3DTriAxisBuilder& operator=(const Rotation3DTriAxisBuilder&) = 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; } template operator Matrix() const { Matrix m; Set(m); return m; } template operator Quaternion() const; private: template void Set(Matrix& m) const { using MatT = Matrix; if constexpr (Order == eMatrixOrder::FOLLOW_VECTOR) { m.template Submatrix<3, 3>(0, 0) = MatT(RotationAxis(angles[0], axes[0])) * MatT(RotationAxis(angles[1], axes[1])) * MatT(RotationAxis(angles[2], axes[2])); } else { m.template Submatrix<3, 3>(0, 0) = MatT(RotationAxis(angles[2], axes[2])) * MatT(RotationAxis(angles[1], axes[1])) * MatT(RotationAxis(angles[0], axes[0])); } // Rest for (int j = 0; j < m.ColumnCount(); ++j) { for (int i = (j < 3 ? 3 : 0); i < m.RowCount(); ++i) { m(i, j) = U(j == i); } } } const std::array angles; const std::array axes; }; /// Rotates around three axes in succession. /// Axes: 0 for X, 1 for Y and 2 for Z. /// Angles in radians. Each rotation according to the right (and left) hand rule in right (and left) handed systems. template auto RotationAxis3(T angle0, T angle1, T angle2) { return Rotation3DTriAxisBuilder(std::array{ angle0, angle1, angle2 }, std::array{ FirstAxis, SecondAxis, ThirdAxis }); } /// Rotation matrix from Euler angles. Rotations are Z-X-Z. /// Angle of the first rotation around Z in radians. /// Angle of the second rotation around X in radians. /// Angle of the third rotation around Z in radians. /// Each rotation according to the right (and left) hand rule in right (and left) handed systems. template auto RotationEuler(T z1, T x2, T z3) { return RotationAxis3<2, 0, 2>(z1, x2, z3); } /// Rotation matrix from roll-pitch-yaw angles. Rotations are X-Y-Z. /// Angle of the first rotation around X in radians. /// Angle of the second rotation around Y in radians. /// Angle of the third rotation around Z in radians. /// /// Each rotation according to the right (and left) hand rule in right (and left) handed systems. template auto RotationRPY(T x1, T y2, T z3) { return RotationAxis3<0, 1, 2>(x1, y2, z3); } template class Rotation3DAxisAngleBuilder { public: Rotation3DAxisAngleBuilder(const Vector& axis, T angle) : axis(axis), angle(angle) {} Rotation3DAxisAngleBuilder& operator=(const Rotation3DAxisAngleBuilder&) = 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; } template operator Matrix() const { Matrix m; Set(m); return m; } template operator Quaternion() const; private: template void Set(Matrix& m) const { assert(IsNormalized(axis)); T C = cos(angle); T S = sin(angle); // 3x3 rotation sub-matrix using RotMat = Matrix; Matrix u(axis(0), axis(1), axis(2)); RotMat cross = { U(0), -u(2), u(1), u(2), U(0), -u(0), -u(1), u(0), U(0) }; RotMat rot = C * RotMat(Identity()) + S * cross + (1 - C) * (u * Transpose(u)); // Elements auto elem = [&m](int i, int j) -> U& { return Order == eMatrixOrder::PRECEDE_VECTOR ? m(i, j) : m(j, i); }; for (int j = 0; j < 3; ++j) { for (int i = 0; i < 3; ++i) { elem(i, j) = rot(i, j); } } // Rest for (int j = 0; j < m.Width(); ++j) { for (int i = (j < 3 ? 3 : 0); i < m.Height(); ++i) { m(i, j) = U(j == i); } } } const Vector axis; const T angle; }; /// Rotates around an arbitrary axis. /// Axis of rotation, must be normalized. /// Angle of rotation in radians. /// Right-hand (left-hand) rule is followed in right-handed (left-handed) systems. template auto RotationAxisAngle(const Vector& axis, U angle) { return Rotation3DAxisAngleBuilder(axis, T(angle)); } /// Determines if the matrix is a proper rotation matrix. /// Proper rotation matrices are orthogonal and have a determinant of +1. template bool IsRotationMatrix3D(const Matrix& m) { static_assert(Rows == 3 || Rows == 4); static_assert(Columns == 3 || Columns == 4); Vector rows[3] = { { m(0, 0), m(0, 1), m(0, 2) }, { m(1, 0), m(1, 1), m(1, 2) }, { m(2, 0), m(2, 1), m(2, 2) }, }; return (std::abs(Dot(rows[0], rows[1])) + std::abs(Dot(rows[0], rows[2])) + std::abs(Dot(rows[1], rows[2]))) < T(0.0005) // rows are orthogonal to each other && IsNormalized(rows[0]) && IsNormalized(rows[1]) && IsNormalized(rows[2]) // all rows are normalized && Determinant(Matrix(m.template Submatrix<3, 3>(0, 0))) > 0; // not an improper rotation } template template Rotation3DAxisBuilder::operator Quaternion() const { using QuatT = Quaternion; switch (axis) { case 0: return QuatT(RotationAxisAngle(Vector(1, 0, 0), angle)); case 1: return QuatT(RotationAxisAngle(Vector(0, 1, 0), angle)); case 2: return QuatT(RotationAxisAngle(Vector(0, 0, 1), angle)); } assert(false); throw std::invalid_argument("Axis must be 0, 1, or 2."); } template template Rotation3DTriAxisBuilder::operator Quaternion() const { using QuatT = Quaternion; return QuatT(RotationAxis(angles[2], axes[2])) * QuatT(RotationAxis(angles[1], axes[1])) * QuatT(RotationAxis(angles[0], axes[0])); } template template Rotation3DAxisAngleBuilder::operator Quaternion() const { auto halfAngle = U(angle) * U(0.5); return Quaternion(std::cos(halfAngle), Vector(axis) * std::sin(halfAngle)); } } // namespace mathter