/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2022 Blender Foundation. */ #pragma once /** \file * \ingroup bli */ #include "BLI_math_base.hh" #include "BLI_math_matrix_types.hh" #include "BLI_math_rotation_types.hh" #include "BLI_math_vector.hh" namespace blender::math { /* -------------------------------------------------------------------- */ /** \name Matrix Operations * \{ */ /** * Returns the inverse of a square matrix or zero matrix on failure. * \a r_success is optional and set to true if the matrix was inverted successfully. */ template [[nodiscard]] MatBase invert(const MatBase &mat, bool &r_success); /** * Flip the matrix across its diagonal. Also flips dimensions for non square matrices. */ template [[nodiscard]] MatBase transpose(const MatBase &mat); /** * Normalize each column of the matrix individually. */ template [[nodiscard]] MatBase normalize(const MatBase &a); /** * Normalize each column of the matrix individually. * Return the length of each column vector. */ template [[nodiscard]] MatBase normalize_and_get_size( const MatBase &a, VectorT &r_size); /** * Returns the determinant of the matrix. * It can be interpreted as the signed volume (or area) of the unit cube after transformation. */ template [[nodiscard]] T determinant(const MatBase &mat); /** * Returns the adjoint of the matrix (also known as adjugate matrix). */ template [[nodiscard]] MatBase adjoint(const MatBase &mat); /** * Equivalent to `mat * from_location(translation)` but with fewer operation. */ template [[nodiscard]] MatBase translate(const MatBase &mat, const VectorT &translation); /** * Equivalent to `mat * from_rotation(rotation)` but with fewer operation. * Optimized for AxisAngle rotation on basis vector (i.e: AxisAngle({1, 0, 0}, 0.2)). */ template [[nodiscard]] MatBase rotate(const MatBase &mat, const RotationT &rotation); /** * Equivalent to `mat * from_scale(scale)` but with fewer operation. */ template [[nodiscard]] MatBase scale(const MatBase &mat, const VectorT &scale); /** * Interpolate each component linearly. */ template [[nodiscard]] MatBase interpolate_linear(const MatBase &a, const MatBase &b, T t); /** * A polar-decomposition-based interpolation between matrix A and matrix B. * * \note This code is about five times slower than the 'naive' interpolation * (it typically remains below 2 usec on an average i74700, * while naive implementation remains below 0.4 usec). * However, it gives expected results even with non-uniformly scaled matrices, * see #46418 for an example. * * Based on "Matrix Animation and Polar Decomposition", by Ken Shoemake & Tom Duff * * \param A: Input matrix which is totally effective with `t = 0.0`. * \param B: Input matrix which is totally effective with `t = 1.0`. * \param t: Interpolation factor. */ template [[nodiscard]] MatBase interpolate(const MatBase &a, const MatBase &b, T t); /** * Complete transform matrix interpolation, * based on polar-decomposition-based interpolation from #interpolate. * * \param A: Input matrix which is totally effective with `t = 0.0`. * \param B: Input matrix which is totally effective with `t = 1.0`. * \param t: Interpolation factor. */ template [[nodiscard]] MatBase interpolate(const MatBase &a, const MatBase &b, T t); /** * Naive interpolation implementation, faster than polar decomposition * * \note This code is about five times faster than the polar decomposition. * However, it gives un-expected results even with non-uniformly scaled matrices, * see #46418 for an example. * * \param A: Input matrix which is totally effective with `t = 0.0`. * \param B: Input matrix which is totally effective with `t = 1.0`. * \param t: Interpolation factor. */ template [[nodiscard]] MatBase interpolate_fast(const MatBase &a, const MatBase &b, T t); /** * Naive transform matrix interpolation, * based on naive-decomposition-based interpolation from #interpolate_fast. * * \note This code is about five times faster than the polar decomposition. * However, it gives un-expected results even with non-uniformly scaled matrices, * see #46418 for an example. * * \param A: Input matrix which is totally effective with `t = 0.0`. * \param B: Input matrix which is totally effective with `t = 1.0`. * \param t: Interpolation factor. */ template [[nodiscard]] MatBase interpolate_fast(const MatBase &a, const MatBase &b, T t); /** * Compute Moore-Penrose pseudo inverse of matrix. * Singular values below epsilon are ignored for stability (truncated SVD). * Gives a good enough approximation of the regular inverse matrix if the given matrix is * non-invertible (ex: degenerate transform). * The returned pseudo inverse matrix `A+` of input matrix `A` * will *not* satisfy `A+ * A = Identity` * but will satisfy `A * A+ * A = A`. * For more detail, see https://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_inverse. */ template [[nodiscard]] MatBase pseudo_invert(const MatBase &mat, T epsilon = 1e-8); /** \} */ /* -------------------------------------------------------------------- */ /** \name Init helpers. * \{ */ /** * Create a translation only matrix. Matrix dimensions should be at least 4 col x 3 row. */ template [[nodiscard]] MatT from_location(const typename MatT::loc_type &location); /** * Create a matrix whose diagonal is defined by the given scale vector. * If vector dimension is lower than matrix diagonal, the missing terms are filled with ones. */ template [[nodiscard]] MatT from_scale(const VecBase &scale); /** * Create a rotation only matrix. */ template [[nodiscard]] MatT from_rotation(const RotationT &rotation); /** * Create a transform matrix with rotation and scale applied in this order. */ template [[nodiscard]] MatT from_rot_scale(const RotationT &rotation, const VectorT &scale); /** * Create a transform matrix with translation and rotation applied in this order. */ template [[nodiscard]] MatT from_loc_rot(const typename MatT::loc_type &location, const RotationT &rotation); /** * Create a transform matrix with translation, rotation and scale applied in this order. */ template [[nodiscard]] MatT from_loc_rot_scale(const typename MatT::loc_type &location, const RotationT &rotation, const VecBase &scale); /** * Create a rotation matrix from 2 basis vectors. * The matrix determinant is given to be positive and it can be converted to other rotation types. * \note `forward` and `up` must be normalized. */ template [[nodiscard]] MatT from_orthonormal_axes(const VectorT forward, const VectorT up); /** * Create a transform matrix with translation and rotation from 2 basis vectors and a translation. * \note `forward` and `up` must be normalized. */ template [[nodiscard]] MatT from_orthonormal_axes(const VectorT location, const VectorT forward, const VectorT up); /** * Construct a transformation that is pivoted around the given origin point. So for instance, * from_origin_transform(from_rotation(M_PI_2), float2(0.0f, 2.0f)) * will construct a transformation representing a 90 degree rotation around the point (0, 2). */ template [[nodiscard]] MatT from_origin_transform(const MatT &transform, const VectorT origin); /** \} */ /* -------------------------------------------------------------------- */ /** \name Conversion function. * \{ */ /** * Extract euler rotation from transform matrix. * \return the rotation with the smallest values from the potential candidates. */ template [[nodiscard]] inline detail::EulerXYZ to_euler(const MatBase &mat); template [[nodiscard]] inline detail::EulerXYZ to_euler(const MatBase &mat); /** * Extract quaternion rotation from transform matrix. */ template [[nodiscard]] inline detail::Quaternion to_quaternion(const MatBase &mat); template [[nodiscard]] inline detail::Quaternion to_quaternion(const MatBase &mat); /** * Extract the absolute 3d scale from a transform matrix. * \tparam AllowNegativeScale: if true, will compute determinant to know if matrix is negative. * This is a costly operation so it is disabled by default. */ template [[nodiscard]] inline VecBase to_scale(const MatBase &mat); template [[nodiscard]] inline VecBase to_scale(const MatBase &mat); /** * Decompose a matrix into location, rotation, and scale components. * \tparam AllowNegativeScale: if true, will compute determinant to know if matrix is negative. * Rotation and scale values will be flipped if it is negative. * This is a costly operation so it is disabled by default. */ template inline void to_rot_scale(const MatBase &mat, RotationT &r_rotation, VecBase &r_scale); template inline void to_loc_rot_scale(const MatBase &mat, VecBase &r_location, RotationT &r_rotation, VecBase &r_scale); /** \} */ /* -------------------------------------------------------------------- */ /** \name Transform functions. * \{ */ /** * Transform a 3d point using a 3x3 matrix (rotation & scale). */ template [[nodiscard]] VecBase transform_point(const MatBase &mat, const VecBase &point); /** * Transform a 3d point using a 4x4 matrix (location & rotation & scale). */ template [[nodiscard]] VecBase transform_point(const MatBase &mat, const VecBase &point); /** * Transform a 3d direction vector using a 3x3 matrix (rotation & scale). */ template [[nodiscard]] VecBase transform_direction(const MatBase &mat, const VecBase &direction); /** * Transform a 3d direction vector using a 4x4 matrix (rotation & scale). */ template [[nodiscard]] VecBase transform_direction(const MatBase &mat, const VecBase &direction); /** * Project a point using a matrix (location & rotation & scale & perspective divide). */ template [[nodiscard]] VectorT project_point(const MatT &mat, const VectorT &point); /** \} */ /* -------------------------------------------------------------------- */ /** \name Projection Matrices. * \{ */ namespace projection { /** * \brief Create an orthographic projection matrix using OpenGL coordinate convention: * Maps each axis range to [-1..1] range for all axes. * The resulting matrix can be used with either #project_point or #transform_point. */ template [[nodiscard]] MatBase orthographic( T left, T right, T bottom, T top, T near_clip, T far_clip); /** * \brief Create a perspective projection matrix using OpenGL coordinate convention: * Maps each axis range to [-1..1] range for all axes. * `left`, `right`, `bottom`, `top` are frustum side distances at `z=near_clip`. * The resulting matrix can be used with #project_point. */ template [[nodiscard]] MatBase perspective( T left, T right, T bottom, T top, T near_clip, T far_clip); /** * \brief Create a perspective projection matrix using OpenGL coordinate convention: * Maps each axis range to [-1..1] range for all axes. * Uses field of view angles instead of plane distances. * The resulting matrix can be used with #project_point. */ template [[nodiscard]] MatBase perspective_fov( T angle_left, T angle_right, T angle_bottom, T angle_top, T near_clip, T far_clip); } // namespace projection /** \} */ /* -------------------------------------------------------------------- */ /** \name Compare / Test * \{ */ /** * Returns true if matrix has inverted handedness. * * \note It doesn't use determinant(mat4x4) as only the 3x3 components are needed * when the matrix is used as a transformation to represent location/scale/rotation. */ template [[nodiscard]] bool is_negative(const MatBase &mat) { return determinant(mat) < T(0); } template [[nodiscard]] bool is_negative(const MatBase &mat); /** * Returns true if matrices are equal within the given epsilon. */ template [[nodiscard]] inline bool is_equal(const MatBase &a, const MatBase &b, const T epsilon = T(0)) { for (int i = 0; i < NumCol; i++) { for (int j = 0; j < NumRow; j++) { if (math::abs(a[i][j] - b[i][j]) > epsilon) { return false; } } } return true; } /** * Test if the X, Y and Z axes are perpendicular with each other. */ template [[nodiscard]] inline bool is_orthogonal(const MatT &mat) { if (math::abs(math::dot(mat.x_axis(), mat.y_axis())) > 1e-5f) { return false; } if (math::abs(math::dot(mat.y_axis(), mat.z_axis())) > 1e-5f) { return false; } if (math::abs(math::dot(mat.z_axis(), mat.x_axis())) > 1e-5f) { return false; } return true; } /** * Test if the X, Y and Z axes are perpendicular with each other and unit length. */ template [[nodiscard]] inline bool is_orthonormal(const MatT &mat) { if (!is_orthogonal(mat)) { return false; } if (math::abs(math::length_squared(mat.x_axis()) - 1) > 1e-5f) { return false; } if (math::abs(math::length_squared(mat.y_axis()) - 1) > 1e-5f) { return false; } if (math::abs(math::length_squared(mat.z_axis()) - 1) > 1e-5f) { return false; } return true; } /** * Test if the X, Y and Z axes are perpendicular with each other and the same length. */ template [[nodiscard]] inline bool is_uniformly_scaled(const MatT &mat) { if (!is_orthogonal(mat)) { return false; } using T = typename MatT::base_type; const T eps = 1e-7; const T x = math::length_squared(mat.x_axis()); const T y = math::length_squared(mat.y_axis()); const T z = math::length_squared(mat.z_axis()); return (math::abs(x - y) < eps) && math::abs(x - z) < eps; } template inline bool is_zero(const MatBase &mat) { for (int i = 0; i < NumCol; i++) { if (!is_zero(mat[i])) { return false; } } return true; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Implementation. * \{ */ /* Implementation details. */ namespace detail { template [[nodiscard]] MatBase from_rotation(const AngleRadian &rotation); template [[nodiscard]] MatBase from_rotation(const EulerXYZ &rotation); template [[nodiscard]] MatBase from_rotation(const Quaternion &rotation); template [[nodiscard]] MatBase from_rotation(const AxisAngle &rotation); } // namespace detail /* Returns true if each individual columns are unit scaled. Mainly for assert usage. */ template [[nodiscard]] inline bool is_unit_scale(const MatBase &m) { for (int i = 0; i < NumCol; i++) { if (!is_unit_scale(m[i])) { return false; } } return true; } template [[nodiscard]] MatBase invert(const MatBase &mat) { bool success; /* Explicit template parameter to please MSVC. */ return invert(mat, success); } template [[nodiscard]] MatBase transpose(const MatBase &mat) { MatBase result; unroll([&](auto i) { unroll([&](auto j) { result[i][j] = mat[j][i]; }); }); return result; } template [[nodiscard]] MatBase translate(const MatBase &mat, const VectorT &translation) { using MatT = MatBase; BLI_STATIC_ASSERT(VectorT::type_length <= MatT::col_len - 1, "Translation should be at least 1 column less than the matrix."); constexpr int location_col = MatT::col_len - 1; /* Avoid multiplying the last row if it exists. * Allows using non square matrices like float3x2 and saves computation. */ using IntermediateVecT = VecBase MatT::col_len - 1) ? (MatT::col_len - 1) : MatT::row_len>; MatT result = mat; unroll([&](auto c) { *reinterpret_cast( &result[location_col]) += translation[c] * *reinterpret_cast(&mat[c]); }); return result; } template [[nodiscard]] MatBase rotate(const MatBase &mat, const detail::AxisAngle &rotation) { using MatT = MatBase; using Vec3T = typename MatT::vec3_type; const T &angle_sin = rotation.angle_sin(); const T &angle_cos = rotation.angle_cos(); const Vec3T &axis_vec = rotation.axis(); MatT result = mat; /* axis_vec is given to be normalized. */ if (axis_vec.x == T(1)) { unroll([&](auto c) { result[2][c] = -angle_sin * mat[1][c] + angle_cos * mat[2][c]; result[1][c] = angle_cos * mat[1][c] + angle_sin * mat[2][c]; }); } else if (axis_vec.y == T(1)) { unroll([&](auto c) { result[0][c] = angle_cos * mat[0][c] - angle_sin * mat[2][c]; result[2][c] = angle_sin * mat[0][c] + angle_cos * mat[2][c]; }); } else if (axis_vec.z == T(1)) { unroll([&](auto c) { result[0][c] = angle_cos * mat[0][c] + angle_sin * mat[1][c]; result[1][c] = -angle_sin * mat[0][c] + angle_cos * mat[1][c]; }); } else { /* Un-optimized case. Arbitrary */ result *= from_rotation(rotation); } return result; } template [[nodiscard]] MatBase scale(const MatBase &mat, const VectorT &scale) { BLI_STATIC_ASSERT(VectorT::type_length <= NumCol, "Scale should be less or equal to the matrix in column count."); MatBase result = mat; unroll([&](auto c) { result[c] *= scale[c]; }); return result; } template [[nodiscard]] MatBase interpolate_linear(const MatBase &a, const MatBase &b, T t) { MatBase result; unroll([&](auto c) { result[c] = interpolate(a[c], b[c], t); }); return result; } template [[nodiscard]] MatBase normalize( const MatView &a) { MatBase result; unroll([&](auto i) { result[i] = math::normalize(a[i]); }); return result; } template [[nodiscard]] MatBase normalize_and_get_size( const MatView &a, VectorT &r_size) { BLI_STATIC_ASSERT(VectorT::type_length == NumCol, "r_size dimension should be equal to matrix column count."); MatBase result; unroll([&](auto i) { result[i] = math::normalize_and_get_length(a[i], r_size[i]); }); return result; } template [[nodiscard]] MatBase normalize(const MatBase &a) { MatBase result; unroll([&](auto i) { result[i] = math::normalize(a[i]); }); return result; } template [[nodiscard]] MatBase normalize_and_get_size( const MatBase &a, VectorT &r_size) { BLI_STATIC_ASSERT(VectorT::type_length == NumCol, "r_size dimension should be equal to matrix column count."); MatBase result; unroll([&](auto i) { result[i] = math::normalize_and_get_length(a[i], r_size[i]); }); return result; } namespace detail { template void normalized_to_eul2(const MatBase &mat, detail::EulerXYZ &eul1, detail::EulerXYZ &eul2) { BLI_assert(math::is_unit_scale(mat)); const T cy = math::hypot(mat[0][0], mat[0][1]); if (cy > T(16) * FLT_EPSILON) { eul1.x = math::atan2(mat[1][2], mat[2][2]); eul1.y = math::atan2(-mat[0][2], cy); eul1.z = math::atan2(mat[0][1], mat[0][0]); eul2.x = math::atan2(-mat[1][2], -mat[2][2]); eul2.y = math::atan2(-mat[0][2], -cy); eul2.z = math::atan2(-mat[0][1], -mat[0][0]); } else { eul1.x = math::atan2(-mat[2][1], mat[1][1]); eul1.y = math::atan2(-mat[0][2], cy); eul1.z = 0.0f; eul2 = eul1; } } /* Using explicit template instantiations in order to reduce compilation time. */ extern template void normalized_to_eul2(const float3x3 &mat, detail::EulerXYZ &eul1, detail::EulerXYZ &eul2); extern template void normalized_to_eul2(const double3x3 &mat, detail::EulerXYZ &eul1, detail::EulerXYZ &eul2); template detail::Quaternion normalized_to_quat_fast(const MatBase &mat) { BLI_assert(math::is_unit_scale(mat)); /* Caller must ensure matrices aren't negative for valid results, see: #24291, #94231. */ BLI_assert(!math::is_negative(mat)); detail::Quaternion q; /* Method outlined by Mike Day, ref: https://math.stackexchange.com/a/3183435/220949 * with an additional `sqrtf(..)` for higher precision result. * Removing the `sqrt` causes tests to fail unless the precision is set to 1e-6 or larger. */ if (mat[2][2] < 0.0f) { if (mat[0][0] > mat[1][1]) { const T trace = 1.0f + mat[0][0] - mat[1][1] - mat[2][2]; T s = 2.0f * math::sqrt(trace); if (mat[1][2] < mat[2][1]) { /* Ensure W is non-negative for a canonical result. */ s = -s; } q.y = 0.25f * s; s = 1.0f / s; q.x = (mat[1][2] - mat[2][1]) * s; q.z = (mat[0][1] + mat[1][0]) * s; q.w = (mat[2][0] + mat[0][2]) * s; if (UNLIKELY((trace == 1.0f) && (q.x == 0.0f && q.z == 0.0f && q.w == 0.0f))) { /* Avoids the need to normalize the degenerate case. */ q.y = 1.0f; } } else { const T trace = 1.0f - mat[0][0] + mat[1][1] - mat[2][2]; T s = 2.0f * math::sqrt(trace); if (mat[2][0] < mat[0][2]) { /* Ensure W is non-negative for a canonical result. */ s = -s; } q.z = 0.25f * s; s = 1.0f / s; q.x = (mat[2][0] - mat[0][2]) * s; q.y = (mat[0][1] + mat[1][0]) * s; q.w = (mat[1][2] + mat[2][1]) * s; if (UNLIKELY((trace == 1.0f) && (q.x == 0.0f && q.y == 0.0f && q.w == 0.0f))) { /* Avoids the need to normalize the degenerate case. */ q.z = 1.0f; } } } else { if (mat[0][0] < -mat[1][1]) { const T trace = 1.0f - mat[0][0] - mat[1][1] + mat[2][2]; T s = 2.0f * math::sqrt(trace); if (mat[0][1] < mat[1][0]) { /* Ensure W is non-negative for a canonical result. */ s = -s; } q.w = 0.25f * s; s = 1.0f / s; q.x = (mat[0][1] - mat[1][0]) * s; q.y = (mat[2][0] + mat[0][2]) * s; q.z = (mat[1][2] + mat[2][1]) * s; if (UNLIKELY((trace == 1.0f) && (q.x == 0.0f && q.y == 0.0f && q.z == 0.0f))) { /* Avoids the need to normalize the degenerate case. */ q.w = 1.0f; } } else { /* NOTE(@ideasman42): A zero matrix will fall through to this block, * needed so a zero scaled matrices to return a quaternion without rotation, see: #101848. */ const T trace = 1.0f + mat[0][0] + mat[1][1] + mat[2][2]; T s = 2.0f * math::sqrt(trace); q.x = 0.25f * s; s = 1.0f / s; q.y = (mat[1][2] - mat[2][1]) * s; q.z = (mat[2][0] - mat[0][2]) * s; q.w = (mat[0][1] - mat[1][0]) * s; if (UNLIKELY((trace == 1.0f) && (q.y == 0.0f && q.z == 0.0f && q.w == 0.0f))) { /* Avoids the need to normalize the degenerate case. */ q.x = 1.0f; } } } BLI_assert(!(q.x < 0.0f)); BLI_assert(math::is_unit_scale(VecBase(q))); return q; } template detail::Quaternion normalized_to_quat_with_checks(const MatBase &mat) { const T det = math::determinant(mat); if (UNLIKELY(!isfinite(det))) { return detail::Quaternion::identity(); } else if (UNLIKELY(det < T(0))) { return normalized_to_quat_fast(-mat); } return normalized_to_quat_fast(mat); } /* Using explicit template instantiations in order to reduce compilation time. */ extern template Quaternion normalized_to_quat_with_checks(const float3x3 &mat); extern template Quaternion normalized_to_quat_with_checks(const double3x3 &mat); template MatBase from_rotation(const EulerXYZ &rotation) { using MatT = MatBase; using DoublePrecision = typename TypeTraits::DoublePrecision; DoublePrecision ci = math::cos(rotation.x); DoublePrecision cj = math::cos(rotation.y); DoublePrecision ch = math::cos(rotation.z); DoublePrecision si = math::sin(rotation.x); DoublePrecision sj = math::sin(rotation.y); DoublePrecision sh = math::sin(rotation.z); DoublePrecision cc = ci * ch; DoublePrecision cs = ci * sh; DoublePrecision sc = si * ch; DoublePrecision ss = si * sh; MatT mat = MatT::identity(); mat[0][0] = T(cj * ch); mat[1][0] = T(sj * sc - cs); mat[2][0] = T(sj * cc + ss); mat[0][1] = T(cj * sh); mat[1][1] = T(sj * ss + cc); mat[2][1] = T(sj * cs - sc); mat[0][2] = T(-sj); mat[1][2] = T(cj * si); mat[2][2] = T(cj * ci); return mat; } template MatBase from_rotation(const Quaternion &rotation) { using MatT = MatBase; using DoublePrecision = typename TypeTraits::DoublePrecision; DoublePrecision q0 = M_SQRT2 * DoublePrecision(rotation.x); DoublePrecision q1 = M_SQRT2 * DoublePrecision(rotation.y); DoublePrecision q2 = M_SQRT2 * DoublePrecision(rotation.z); DoublePrecision q3 = M_SQRT2 * DoublePrecision(rotation.w); DoublePrecision qda = q0 * q1; DoublePrecision qdb = q0 * q2; DoublePrecision qdc = q0 * q3; DoublePrecision qaa = q1 * q1; DoublePrecision qab = q1 * q2; DoublePrecision qac = q1 * q3; DoublePrecision qbb = q2 * q2; DoublePrecision qbc = q2 * q3; DoublePrecision qcc = q3 * q3; MatT mat = MatT::identity(); mat[0][0] = T(1.0 - qbb - qcc); mat[0][1] = T(qdc + qab); mat[0][2] = T(-qdb + qac); mat[1][0] = T(-qdc + qab); mat[1][1] = T(1.0 - qaa - qcc); mat[1][2] = T(qda + qbc); mat[2][0] = T(qdb + qac); mat[2][1] = T(-qda + qbc); mat[2][2] = T(1.0 - qaa - qbb); return mat; } template MatBase from_rotation(const AxisAngle &rotation) { using MatT = MatBase; using Vec3T = typename MatT::vec3_type; const T angle_sin = rotation.angle_sin(); const T angle_cos = rotation.angle_cos(); const Vec3T &axis = rotation.axis(); BLI_assert(is_unit_scale(axis)); T ico = (T(1) - angle_cos); Vec3T nsi = axis * angle_sin; Vec3T n012 = (axis * axis) * ico; T n_01 = (axis[0] * axis[1]) * ico; T n_02 = (axis[0] * axis[2]) * ico; T n_12 = (axis[1] * axis[2]) * ico; MatT mat = from_scale(n012 + angle_cos); mat[0][1] = n_01 + nsi[2]; mat[0][2] = n_02 - nsi[1]; mat[1][0] = n_01 - nsi[2]; mat[1][2] = n_12 + nsi[0]; mat[2][0] = n_02 + nsi[1]; mat[2][1] = n_12 - nsi[0]; return mat; } template MatBase from_rotation(const AngleRadian &rotation) { using MatT = MatBase; T ci = math::cos(rotation.value); T si = math::sin(rotation.value); MatT mat = MatT::identity(); mat[0][0] = ci; mat[1][0] = -si; mat[0][1] = si; mat[1][1] = ci; return mat; } /* Using explicit template instantiations in order to reduce compilation time. */ extern template MatBase from_rotation(const AngleRadian &rotation); extern template MatBase from_rotation(const AngleRadian &rotation); extern template MatBase from_rotation(const EulerXYZ &rotation); extern template MatBase from_rotation(const EulerXYZ &rotation); extern template MatBase from_rotation(const Quaternion &rotation); extern template MatBase from_rotation(const Quaternion &rotation); extern template MatBase from_rotation(const AxisAngle &rotation); extern template MatBase from_rotation(const AxisAngle &rotation); } // namespace detail template [[nodiscard]] inline detail::EulerXYZ to_euler(const MatBase &mat) { detail::EulerXYZ eul1, eul2; if constexpr (Normalized) { detail::normalized_to_eul2(mat, eul1, eul2); } else { detail::normalized_to_eul2(normalize(mat), eul1, eul2); } /* Return best, which is just the one with lowest values it in. */ return (length_manhattan(VecBase(eul1)) > length_manhattan(VecBase(eul2))) ? eul2 : eul1; } template [[nodiscard]] inline detail::EulerXYZ to_euler(const MatBase &mat) { /* TODO(fclem): Avoid the copy with 3x3 ref. */ return to_euler(MatBase(mat)); } template [[nodiscard]] inline detail::Quaternion to_quaternion(const MatBase &mat) { using namespace math; if constexpr (Normalized) { return detail::normalized_to_quat_with_checks(mat); } else { return detail::normalized_to_quat_with_checks(normalize(mat)); } } template [[nodiscard]] inline detail::Quaternion to_quaternion(const MatBase &mat) { /* TODO(fclem): Avoid the copy with 3x3 ref. */ return to_quaternion(MatBase(mat)); } template [[nodiscard]] inline VecBase to_scale(const MatBase &mat) { VecBase result = {length(mat.x_axis()), length(mat.y_axis()), length(mat.z_axis())}; if constexpr (AllowNegativeScale) { if (UNLIKELY(is_negative(mat))) { result = -result; } } return result; } template [[nodiscard]] inline VecBase to_scale(const MatBase &mat) { VecBase result = {length(mat.x), length(mat.y)}; if constexpr (AllowNegativeScale) { if (UNLIKELY(is_negative(mat))) { result = -result; } } return result; } /* Implementation details. Use `to_euler` and `to_quaternion` instead. */ namespace detail { template inline void to_rotation(const MatBase &mat, detail::Quaternion &r_rotation) { r_rotation = to_quaternion(mat); } template inline void to_rotation(const MatBase &mat, detail::EulerXYZ &r_rotation) { r_rotation = to_euler(mat); } } // namespace detail template inline void to_rot_scale(const MatBase &mat, RotationT &r_rotation, VecBase &r_scale) { MatBase normalized_mat = normalize_and_get_size(mat, r_scale); if constexpr (AllowNegativeScale) { if (UNLIKELY(is_negative(normalized_mat))) { normalized_mat = -normalized_mat; r_scale = -r_scale; } } detail::to_rotation(normalized_mat, r_rotation); } template inline void to_loc_rot_scale(const MatBase &mat, VecBase &r_location, RotationT &r_rotation, VecBase &r_scale) { r_location = mat.location(); to_rot_scale(MatBase(mat), r_rotation, r_scale); } template [[nodiscard]] MatT from_location(const typename MatT::loc_type &location) { MatT mat = MatT::identity(); mat.location() = location; return mat; } template [[nodiscard]] MatT from_scale(const VecBase &scale) { BLI_STATIC_ASSERT(ScaleDim <= MatT::min_dim, "Scale dimension should fit the matrix diagonal length."); MatT result{}; unroll( [&](auto i) { result[i][i] = (i < ScaleDim) ? scale[i] : typename MatT::base_type(1); }); return result; } template [[nodiscard]] MatT from_rotation(const RotationT &rotation) { return detail::from_rotation(rotation); } template [[nodiscard]] MatT from_rot_scale(const RotationT &rotation, const VectorT &scale) { return from_rotation(rotation) * from_scale(scale); } template [[nodiscard]] MatT from_loc_rot_scale(const typename MatT::loc_type &location, const RotationT &rotation, const VecBase &scale) { using MatRotT = MatBase; MatT mat = MatT(from_rot_scale(rotation, scale)); mat.location() = location; return mat; } template [[nodiscard]] MatT from_loc_rot(const typename MatT::loc_type &location, const RotationT &rotation) { using MatRotT = MatBase; MatT mat = MatT(from_rotation(rotation)); mat.location() = location; return mat; } template [[nodiscard]] MatT from_orthonormal_axes(const VectorT forward, const VectorT up) { BLI_assert(is_unit_scale(forward)); BLI_assert(is_unit_scale(up)); MatT matrix; matrix.x_axis() = forward; /* Beware of handedness! Blender uses right-handedness. * Resulting matrix should have determinant of 1. */ matrix.y_axis() = math::cross(up, forward); matrix.z_axis() = up; return matrix; } template [[nodiscard]] MatT from_orthonormal_axes(const VectorT location, const VectorT forward, const VectorT up) { using Mat3x3 = MatBase; MatT matrix = MatT(from_orthonormal_axes(forward, up)); matrix.location() = location; return matrix; } template [[nodiscard]] MatT from_origin_transform(const MatT &transform, const VectorT origin) { return from_location(origin) * transform * from_location(-origin); } template VecBase transform_point(const MatBase &mat, const VecBase &point) { return mat * point; } template VecBase transform_point(const MatBase &mat, const VecBase &point) { return mat.template view<3, 3>() * point + mat.location(); } template VecBase transform_direction(const MatBase &mat, const VecBase &direction) { return mat * direction; } template VecBase transform_direction(const MatBase &mat, const VecBase &direction) { return mat.template view<3, 3>() * direction; } template VecBase project_point(const MatBase &mat, const VecBase &point) { VecBase tmp(point, T(1)); tmp = mat * tmp; /* Absolute value to not flip the frustum upside down behind the camera. */ return VecBase(tmp) / math::abs(tmp[N]); } extern template float3 transform_point(const float3x3 &mat, const float3 &point); extern template float3 transform_point(const float4x4 &mat, const float3 &point); extern template float3 transform_direction(const float3x3 &mat, const float3 &direction); extern template float3 transform_direction(const float4x4 &mat, const float3 &direction); extern template float3 project_point(const float4x4 &mat, const float3 &point); extern template float2 project_point(const float3x3 &mat, const float2 &point); namespace projection { template MatBase orthographic(T left, T right, T bottom, T top, T near_clip, T far_clip) { const T x_delta = right - left; const T y_delta = top - bottom; const T z_delta = far_clip - near_clip; MatBase mat = MatBase::identity(); if (x_delta != 0 && y_delta != 0 && z_delta != 0) { mat[0][0] = T(2.0) / x_delta; mat[3][0] = -(right + left) / x_delta; mat[1][1] = T(2.0) / y_delta; mat[3][1] = -(top + bottom) / y_delta; mat[2][2] = -T(2.0) / z_delta; /* NOTE: negate Z. */ mat[3][2] = -(far_clip + near_clip) / z_delta; } return mat; } template MatBase perspective(T left, T right, T bottom, T top, T near_clip, T far_clip) { const T x_delta = right - left; const T y_delta = top - bottom; const T z_delta = far_clip - near_clip; MatBase mat = MatBase::identity(); if (x_delta != 0 && y_delta != 0 && z_delta != 0) { mat[0][0] = near_clip * T(2.0) / x_delta; mat[1][1] = near_clip * T(2.0) / y_delta; mat[2][0] = (right + left) / x_delta; /* NOTE: negate Z. */ mat[2][1] = (top + bottom) / y_delta; mat[2][2] = -(far_clip + near_clip) / z_delta; mat[2][3] = -1.0f; mat[3][2] = (-2.0f * near_clip * far_clip) / z_delta; mat[3][3] = 0.0f; } return mat; } template [[nodiscard]] MatBase perspective_fov( T angle_left, T angle_right, T angle_bottom, T angle_top, T near_clip, T far_clip) { MatBase mat = perspective(math::tan(angle_left), math::tan(angle_right), math::tan(angle_bottom), math::tan(angle_top), near_clip, far_clip); mat[0][0] /= near_clip; mat[1][1] /= near_clip; return mat; } extern template float4x4 orthographic( float left, float right, float bottom, float top, float near_clip, float far_clip); extern template float4x4 perspective( float left, float right, float bottom, float top, float near_clip, float far_clip); } // namespace projection /** \} */ } // namespace blender::math