kopia lustrzana https://github.com/jameshball/osci-render
391 wiersze
13 KiB
C++
391 wiersze
13 KiB
C++
// 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 <stdexcept>
|
|
|
|
|
|
namespace mathter {
|
|
|
|
|
|
template <class T>
|
|
class Rotation3DAxisBuilder {
|
|
public:
|
|
Rotation3DAxisBuilder(const T& angle, int axis) : angle(angle), axis(axis) {}
|
|
Rotation3DAxisBuilder& operator=(const Rotation3DAxisBuilder&) = delete;
|
|
|
|
template <class U, eMatrixOrder Order, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 4, 4, Order, Layout, MPacked>() const {
|
|
Matrix<U, 4, 4, Order, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, eMatrixOrder Order, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 3, 3, Order, Layout, MPacked>() const {
|
|
Matrix<U, 3, 3, Order, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 3, 4, eMatrixOrder::PRECEDE_VECTOR, Layout, MPacked>() const {
|
|
Matrix<U, 3, 4, eMatrixOrder::PRECEDE_VECTOR, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 4, 3, eMatrixOrder::FOLLOW_VECTOR, Layout, MPacked>() const {
|
|
Matrix<U, 4, 3, eMatrixOrder::FOLLOW_VECTOR, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, bool QPacked>
|
|
operator Quaternion<U, QPacked>() const;
|
|
|
|
private:
|
|
template <class U, int Rows, int Columns, eMatrixOrder Order, eMatrixLayout Layout, bool MPacked>
|
|
void Set(Matrix<U, Rows, Columns, Order, Layout, MPacked>& 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;
|
|
};
|
|
|
|
|
|
|
|
/// <summary> Rotates around coordinate axis. </summary>
|
|
/// <param name="axis"> 0 for X, 1 for Y, 2 for Z and so on... </param>
|
|
/// <param name="angle"> Angle of rotation in radians. </param>
|
|
/// <remarks> Positive angles rotate according to the right-hand rule in right-handed
|
|
/// coordinate systems (left-handed according to left-hand rule).
|
|
template <class T>
|
|
auto RotationAxis(T angle, int axis) {
|
|
return Rotation3DAxisBuilder(angle, axis);
|
|
}
|
|
|
|
/// <summary> Rotates around coordinate axis. </summary>
|
|
/// <typeparam name="Axis"> 0 for X, 1 for Y, 2 for Z and so on... </typeparam>
|
|
/// <param name="angle"> Angle of rotation in radians. </param>
|
|
/// <remarks> Positive angles rotate according to the right-hand rule in right-handed
|
|
/// coordinate systems (left-handed according to left-hand rule).
|
|
template <int Axis, class T>
|
|
auto RotationAxis(T angle) {
|
|
return Rotation3DAxisBuilder(angle, Axis);
|
|
}
|
|
|
|
|
|
/// <summary> Rotates around the X axis according to the right (left) hand rule in right (left) handed systems. </summary>
|
|
/// <param name="angle"> Angle of rotation in radians. </param>
|
|
template <class T>
|
|
auto RotationX(T angle) {
|
|
return RotationAxis<0>(angle);
|
|
}
|
|
|
|
/// <summary> Rotates around the Y axis according to the right (left) hand rule in right (left) handed systems. </summary>
|
|
/// <param name="angle"> Angle of rotation in radians. </param>
|
|
template <class T>
|
|
auto RotationY(T angle) {
|
|
return RotationAxis<1>(angle);
|
|
}
|
|
|
|
/// <summary> Rotates around the Z axis according to the right (left) hand rule in right (left) handed systems. </summary>
|
|
/// <param name="angle"> Angle of rotation in radians. </param>
|
|
template <class T>
|
|
auto RotationZ(T angle) {
|
|
return RotationAxis<2>(angle);
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
class Rotation3DTriAxisBuilder {
|
|
public:
|
|
Rotation3DTriAxisBuilder(const std::array<T, 3>& angles, std::array<int, 3> axes) : angles(angles), axes(axes) {}
|
|
Rotation3DTriAxisBuilder& operator=(const Rotation3DTriAxisBuilder&) = delete;
|
|
|
|
template <class U, eMatrixOrder Order, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 4, 4, Order, Layout, MPacked>() const {
|
|
Matrix<U, 4, 4, Order, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, eMatrixOrder Order, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 3, 3, Order, Layout, MPacked>() const {
|
|
Matrix<U, 3, 3, Order, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 3, 4, eMatrixOrder::PRECEDE_VECTOR, Layout, MPacked>() const {
|
|
Matrix<U, 3, 4, eMatrixOrder::PRECEDE_VECTOR, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 4, 3, eMatrixOrder::FOLLOW_VECTOR, Layout, MPacked>() const {
|
|
Matrix<U, 4, 3, eMatrixOrder::FOLLOW_VECTOR, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, bool QPacked>
|
|
operator Quaternion<U, QPacked>() const;
|
|
|
|
private:
|
|
template <class U, int Rows, int Columns, eMatrixOrder Order, eMatrixLayout Layout, bool MPacked>
|
|
void Set(Matrix<U, Rows, Columns, Order, Layout, MPacked>& m) const {
|
|
using MatT = Matrix<U, 3, 3, Order, Layout, MPacked>;
|
|
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<T, 3> angles;
|
|
const std::array<int, 3> axes;
|
|
};
|
|
|
|
|
|
|
|
/// <summary> Rotates around three axes in succession. </summary>
|
|
/// <remarks> 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. </remarks>
|
|
template <int FirstAxis, int SecondAxis, int ThirdAxis, class T>
|
|
auto RotationAxis3(T angle0, T angle1, T angle2) {
|
|
return Rotation3DTriAxisBuilder(std::array<T, 3>{ angle0, angle1, angle2 }, std::array<int, 3>{ FirstAxis, SecondAxis, ThirdAxis });
|
|
}
|
|
|
|
/// <summary> Rotation matrix from Euler angles. Rotations are Z-X-Z. </summary>
|
|
/// <param name="z1"> Angle of the first rotation around Z in radians. </param>
|
|
/// <param name="x2"> Angle of the second rotation around X in radians. </param>
|
|
/// <param name="z3"> Angle of the third rotation around Z in radians. </param>
|
|
/// <remarks> Each rotation according to the right (and left) hand rule in right (and left) handed systems. </remarks>
|
|
template <class T>
|
|
auto RotationEuler(T z1, T x2, T z3) {
|
|
return RotationAxis3<2, 0, 2>(z1, x2, z3);
|
|
}
|
|
|
|
/// <summary> Rotation matrix from roll-pitch-yaw angles. Rotations are X-Y-Z. </summary>
|
|
/// <param name="z1"> Angle of the first rotation around X in radians. </param>
|
|
/// <param name="x2"> Angle of the second rotation around Y in radians. </param>
|
|
/// <param name="z3"> Angle of the third rotation around Z in radians. </param>
|
|
/// /// <remarks> Each rotation according to the right (and left) hand rule in right (and left) handed systems. </remarks>
|
|
template <class T>
|
|
auto RotationRPY(T x1, T y2, T z3) {
|
|
return RotationAxis3<0, 1, 2>(x1, y2, z3);
|
|
}
|
|
|
|
|
|
|
|
template <class T, bool Packed>
|
|
class Rotation3DAxisAngleBuilder {
|
|
public:
|
|
Rotation3DAxisAngleBuilder(const Vector<T, 3, Packed>& axis, T angle) : axis(axis), angle(angle) {}
|
|
Rotation3DAxisAngleBuilder& operator=(const Rotation3DAxisAngleBuilder&) = delete;
|
|
|
|
template <class U, eMatrixOrder Order, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 4, 4, Order, Layout, MPacked>() const {
|
|
Matrix<U, 4, 4, Order, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, eMatrixOrder Order, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 3, 3, Order, Layout, MPacked>() const {
|
|
Matrix<U, 3, 3, Order, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 3, 4, eMatrixOrder::PRECEDE_VECTOR, Layout, MPacked>() const {
|
|
Matrix<U, 3, 4, eMatrixOrder::PRECEDE_VECTOR, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, eMatrixLayout Layout, bool MPacked>
|
|
operator Matrix<U, 4, 3, eMatrixOrder::FOLLOW_VECTOR, Layout, MPacked>() const {
|
|
Matrix<U, 4, 3, eMatrixOrder::FOLLOW_VECTOR, Layout, MPacked> m;
|
|
Set(m);
|
|
return m;
|
|
}
|
|
|
|
template <class U, bool QPacked>
|
|
operator Quaternion<U, QPacked>() const;
|
|
|
|
private:
|
|
template <class U, int Rows, int Columns, eMatrixOrder Order, eMatrixLayout Layout, bool MPacked>
|
|
void Set(Matrix<U, Rows, Columns, Order, Layout, MPacked>& m) const {
|
|
assert(IsNormalized(axis));
|
|
|
|
T C = cos(angle);
|
|
T S = sin(angle);
|
|
|
|
// 3x3 rotation sub-matrix
|
|
using RotMat = Matrix<U, 3, 3, eMatrixOrder::FOLLOW_VECTOR, Layout, Packed>;
|
|
Matrix<U, 3, 1, eMatrixOrder::FOLLOW_VECTOR, Layout, Packed> 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<T, 3, Packed> axis;
|
|
const T angle;
|
|
};
|
|
|
|
/// <summary> Rotates around an arbitrary axis. </summary>
|
|
/// <param name="axis"> Axis of rotation, must be normalized. </param>
|
|
/// <param name="angle"> Angle of rotation in radians. </param>
|
|
/// <remarks> Right-hand (left-hand) rule is followed in right-handed (left-handed) systems. </remarks>
|
|
template <class T, bool Vpacked, class U>
|
|
auto RotationAxisAngle(const Vector<T, 3, Vpacked>& axis, U angle) {
|
|
return Rotation3DAxisAngleBuilder(axis, T(angle));
|
|
}
|
|
|
|
|
|
/// <summary> Determines if the matrix is a proper rotation matrix. </summary>
|
|
/// <remarks> Proper rotation matrices are orthogonal and have a determinant of +1. </remarks>
|
|
template <class T, int Rows, int Columns, eMatrixOrder Order, eMatrixLayout Layout, bool Packed>
|
|
bool IsRotationMatrix3D(const Matrix<T, Rows, Columns, Order, Layout, Packed>& m) {
|
|
static_assert(Rows == 3 || Rows == 4);
|
|
static_assert(Columns == 3 || Columns == 4);
|
|
Vector<T, 3> 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<T, 3, 3, Order, Layout, Packed>(m.template Submatrix<3, 3>(0, 0))) > 0; // not an improper rotation
|
|
}
|
|
|
|
|
|
template <class T>
|
|
template <class U, bool QPacked>
|
|
Rotation3DAxisBuilder<T>::operator Quaternion<U, QPacked>() const {
|
|
using QuatT = Quaternion<U, QPacked>;
|
|
switch (axis) {
|
|
case 0: return QuatT(RotationAxisAngle(Vector<U, 3, QPacked>(1, 0, 0), angle));
|
|
case 1: return QuatT(RotationAxisAngle(Vector<U, 3, QPacked>(0, 1, 0), angle));
|
|
case 2: return QuatT(RotationAxisAngle(Vector<U, 3, QPacked>(0, 0, 1), angle));
|
|
}
|
|
assert(false);
|
|
throw std::invalid_argument("Axis must be 0, 1, or 2.");
|
|
}
|
|
|
|
template <class T>
|
|
template <class U, bool QPacked>
|
|
Rotation3DTriAxisBuilder<T>::operator Quaternion<U, QPacked>() const {
|
|
using QuatT = Quaternion<U, QPacked>;
|
|
return QuatT(RotationAxis(angles[2], axes[2])) * QuatT(RotationAxis(angles[1], axes[1])) * QuatT(RotationAxis(angles[0], axes[0]));
|
|
}
|
|
|
|
template <class T, bool Packed>
|
|
template <class U, bool QPacked>
|
|
Rotation3DAxisAngleBuilder<T, Packed>::operator Quaternion<U, QPacked>() const {
|
|
auto halfAngle = U(angle) * U(0.5);
|
|
return Quaternion(std::cos(halfAngle), Vector<U, 3, QPacked>(axis) * std::sin(halfAngle));
|
|
}
|
|
|
|
|
|
|
|
} // namespace mathter
|