This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenlib/intern/math_matrix.cc
Clément Foucault b0b9e746fa BLI: Use BLI_math_matrix_type.hh instead of BLI_math_float4x4.hh
Straightforward port. I took the oportunity to remove some C vector
functions (ex: copy_v2_v2).

This makes some changes to DRWView to accomodate the alignement
requirements of the float4x4 type.
2023-02-06 21:25:45 +01:00

460 lines
16 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. */
/** \file
* \ingroup bli
*/
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.hh"
#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/Eigenvalues>
/* -------------------------------------------------------------------- */
/** \name Matrix multiplication
* \{ */
namespace blender {
template<> float4x4 float4x4::operator*(const float4x4 &b) const
{
using namespace math;
const float4x4 &a = *this;
float4x4 result;
#ifdef BLI_HAVE_SSE2
__m128 A0 = _mm_load_ps(a[0]);
__m128 A1 = _mm_load_ps(a[1]);
__m128 A2 = _mm_load_ps(a[2]);
__m128 A3 = _mm_load_ps(a[3]);
for (int i = 0; i < 4; i++) {
__m128 B0 = _mm_set1_ps(b[i][0]);
__m128 B1 = _mm_set1_ps(b[i][1]);
__m128 B2 = _mm_set1_ps(b[i][2]);
__m128 B3 = _mm_set1_ps(b[i][3]);
__m128 sum = _mm_add_ps(_mm_add_ps(_mm_mul_ps(B0, A0), _mm_mul_ps(B1, A1)),
_mm_add_ps(_mm_mul_ps(B2, A2), _mm_mul_ps(B3, A3)));
_mm_store_ps(result[i], sum);
}
#else
const float4x4 T = transpose(b);
result.x = float4(dot(a.x, T.x), dot(a.x, T.y), dot(a.x, T.z), dot(a.x, T.w));
result.y = float4(dot(a.y, T.x), dot(a.y, T.y), dot(a.y, T.z), dot(a.y, T.w));
result.z = float4(dot(a.z, T.x), dot(a.z, T.y), dot(a.z, T.z), dot(a.z, T.w));
result.w = float4(dot(a.w, T.x), dot(a.w, T.y), dot(a.w, T.z), dot(a.w, T.w));
#endif
return result;
}
template<> float3x3 float3x3::operator*(const float3x3 &b) const
{
using namespace math;
const float3x3 &a = *this;
float3x3 result;
#if 0 /* 1.2 times slower. Could be used as reference for aligned version. */
__m128 A0 = _mm_set_ps(0, a[0][2], a[0][1], a[0][0]);
__m128 A1 = _mm_set_ps(0, a[1][2], a[1][1], a[1][0]);
__m128 A2 = _mm_set_ps(0, a[2][2], a[2][1], a[2][0]);
for (int i = 0; i < 2; i++) {
__m128 B0 = _mm_set1_ps(b[i][0]);
__m128 B1 = _mm_set1_ps(b[i][1]);
__m128 B2 = _mm_set1_ps(b[i][2]);
__m128 sum = _mm_add_ps(_mm_add_ps(_mm_mul_ps(B0, A0), _mm_mul_ps(B1, A1)),
_mm_mul_ps(B2, A2));
_mm_storeu_ps(result[i], sum);
}
_mm_storeu_ps(result[1], sum[1]);
/* Manual per component store to avoid segfault. */
_mm_store_ss(&result[2][0], sum[2]);
sum[2] = _mm_shuffle_ps(sum[2], sum[2], _MM_SHUFFLE(3, 2, 1, 1));
_mm_store_ss(&result[2][1], sum[2]);
sum[2] = _mm_shuffle_ps(sum[2], sum[2], _MM_SHUFFLE(3, 2, 1, 2));
_mm_store_ss(&result[2][2], sum[2]);
#else
/** Manual unrolling since MSVC doesn't seem to unroll properly. */
result[0][0] = b[0][0] * a[0][0] + b[0][1] * a[1][0] + b[0][2] * a[2][0];
result[0][1] = b[0][0] * a[0][1] + b[0][1] * a[1][1] + b[0][2] * a[2][1];
result[0][2] = b[0][0] * a[0][2] + b[0][1] * a[1][2] + b[0][2] * a[2][2];
result[1][0] = b[1][0] * a[0][0] + b[1][1] * a[1][0] + b[1][2] * a[2][0];
result[1][1] = b[1][0] * a[0][1] + b[1][1] * a[1][1] + b[1][2] * a[2][1];
result[1][2] = b[1][0] * a[0][2] + b[1][1] * a[1][2] + b[1][2] * a[2][2];
result[2][0] = b[2][0] * a[0][0] + b[2][1] * a[1][0] + b[2][2] * a[2][0];
result[2][1] = b[2][0] * a[0][1] + b[2][1] * a[1][1] + b[2][2] * a[2][1];
result[2][2] = b[2][0] * a[0][2] + b[2][1] * a[1][2] + b[2][2] * a[2][2];
#endif
return result;
}
template float2x2 float2x2::operator*(const float2x2 &b) const;
template double2x2 double2x2::operator*(const double2x2 &b) const;
template double3x3 double3x3::operator*(const double3x3 &b) const;
template double4x4 double4x4::operator*(const double4x4 &b) const;
} // namespace blender
/** \} */
namespace blender::math {
/* -------------------------------------------------------------------- */
/** \name Determinant
* \{ */
template<typename T, int Size> T determinant(const MatBase<T, Size, Size> &mat)
{
return Eigen::Map<const Eigen::Matrix<T, Size, Size>>(mat.base_ptr()).determinant();
}
template float determinant(const float2x2 &mat);
template float determinant(const float3x3 &mat);
template float determinant(const float4x4 &mat);
template double determinant(const double2x2 &mat);
template double determinant(const double3x3 &mat);
template double determinant(const double4x4 &mat);
template<typename T> bool is_negative(const MatBase<T, 4, 4> &mat)
{
return Eigen::Map<const Eigen::Matrix<T, 3, 3>, 0, Eigen::Stride<4, 1>>(mat.base_ptr())
.determinant() < T(0);
}
template bool is_negative(const float4x4 &mat);
template bool is_negative(const double4x4 &mat);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Adjoint
* \{ */
template<typename T, int Size> MatBase<T, Size, Size> adjoint(const MatBase<T, Size, Size> &mat)
{
MatBase<T, Size, Size> adj;
unroll<Size>([&](auto c) {
unroll<Size>([&](auto r) {
/* Copy other cells except the "cross" to compute the determinant. */
MatBase<T, Size - 1, Size - 1> tmp;
unroll<Size>([&](auto m_c) {
unroll<Size>([&](auto m_r) {
if (m_c != c && m_r != r) {
int d_c = (m_c < c) ? m_c : (m_c - 1);
int d_r = (m_r < r) ? m_r : (m_r - 1);
tmp[d_c][d_r] = mat[m_c][m_r];
}
});
});
T minor = determinant(tmp);
/* Transpose directly to get the adjugate. Swap destination row and col. */
adj[r][c] = ((c + r) & 1) ? -minor : minor;
});
});
return adj;
}
template float2x2 adjoint(const float2x2 &mat);
template float3x3 adjoint(const float3x3 &mat);
template float4x4 adjoint(const float4x4 &mat);
template double2x2 adjoint(const double2x2 &mat);
template double3x3 adjoint(const double3x3 &mat);
template double4x4 adjoint(const double4x4 &mat);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Inverse
* \{ */
template<typename T, int Size>
MatBase<T, Size, Size> invert(const MatBase<T, Size, Size> &mat, bool &r_success)
{
MatBase<T, Size, Size> result;
Eigen::Map<const Eigen::Matrix<T, Size, Size>> M(mat.base_ptr());
Eigen::Map<Eigen::Matrix<T, Size, Size>> R(result.base_ptr());
M.computeInverseWithCheck(R, r_success, 0.0f);
if (!r_success) {
R = R.Zero();
}
return result;
}
template float2x2 invert(const float2x2 &mat, bool &r_success);
template float3x3 invert(const float3x3 &mat, bool &r_success);
template float4x4 invert(const float4x4 &mat, bool &r_success);
template double2x2 invert(const double2x2 &mat, bool &r_success);
template double3x3 invert(const double3x3 &mat, bool &r_success);
template double4x4 invert(const double4x4 &mat, bool &r_success);
template<typename T, int Size>
MatBase<T, Size, Size> pseudo_invert(const MatBase<T, Size, Size> &mat, T epsilon)
{
/* Start by trying normal inversion first. */
bool success;
MatBase<T, Size, Size> inv = invert(mat, success);
if (success) {
return inv;
}
/**
* Compute the Single Value Decomposition of an arbitrary matrix A
* That is compute the 3 matrices U,W,V with U column orthogonal (m,n)
* ,W a diagonal matrix and V an orthogonal square matrix `s.t.A = U.W.Vt`.
* From this decomposition it is trivial to compute the (pseudo-inverse)
* of `A` as `Ainv = V.Winv.transpose(U)`.
*/
MatBase<T, Size, Size> U, W, V;
VecBase<T, Size> S_val;
{
using namespace Eigen;
using MatrixT = Eigen::Matrix<T, Size, Size>;
using VectorT = Eigen::Matrix<T, Size, 1>;
/* Blender and Eigen matrices are both column-major by default.
* Since our matrix is squared, we can use thinU/V. */
/** WORKAROUND:
* (ComputeThinU | ComputeThinV) must be set as runtime parameters in Eigen < 3.4.0.
* But this requires the matrix type to be dynamic to avoid an assert.
*/
using MatrixDynamicT = Eigen::Matrix<T, Dynamic, Dynamic>;
JacobiSVD<MatrixDynamicT, NoQRPreconditioner> svd(
Eigen::Map<const MatrixDynamicT>(mat.base_ptr(), Size, Size), ComputeThinU | ComputeThinV);
Eigen::Map<MatrixT>(U.base_ptr()) = svd.matrixU();
(Eigen::Map<VectorT>(S_val)) = svd.singularValues();
Eigen::Map<MatrixT>(V.base_ptr()) = svd.matrixV();
}
/* Invert or nullify component based on epsilon comparison. */
unroll<Size>([&](auto i) { S_val[i] = (S_val[i] < epsilon) ? T(0) : (T(1) / S_val[i]); });
W = from_scale<MatBase<T, Size, Size>>(S_val);
return (V * W) * transpose(U);
}
template float2x2 pseudo_invert(const float2x2 &mat, float epsilon);
template float3x3 pseudo_invert(const float3x3 &mat, float epsilon);
template float4x4 pseudo_invert(const float4x4 &mat, float epsilon);
template double2x2 pseudo_invert(const double2x2 &mat, double epsilon);
template double3x3 pseudo_invert(const double3x3 &mat, double epsilon);
template double4x4 pseudo_invert(const double4x4 &mat, double epsilon);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Polar Decomposition
* \{ */
/**
* Right polar decomposition:
* M = UP
*
* U is the 'rotation'-like component, the closest orthogonal matrix to M.
* P is the 'scaling'-like component, defined in U space.
*
* See https://en.wikipedia.org/wiki/Polar_decomposition for more.
*/
template<typename T>
static void polar_decompose(const MatBase<T, 3, 3> &mat3,
MatBase<T, 3, 3> &r_U,
MatBase<T, 3, 3> &r_P)
{
/* From svd decomposition (M = WSV*), we have:
* U = WV*
* P = VSV*
*/
MatBase<T, 3, 3> W, V;
VecBase<T, 3> S_val;
{
using namespace Eigen;
using MatrixT = Eigen::Matrix<T, 3, 3>;
using VectorT = Eigen::Matrix<T, 3, 1>;
/* Blender and Eigen matrices are both column-major by default.
* Since our matrix is squared, we can use thinU/V. */
/** WORKAROUND:
* (ComputeThinU | ComputeThinV) must be set as runtime parameters in Eigen < 3.4.0.
* But this requires the matrix type to be dynamic to avoid an assert.
*/
using MatrixDynamicT = Eigen::Matrix<T, Dynamic, Dynamic>;
JacobiSVD<MatrixDynamicT, NoQRPreconditioner> svd(
Eigen::Map<const MatrixDynamicT>(mat3.base_ptr(), 3, 3), ComputeThinU | ComputeThinV);
Eigen::Map<MatrixT>(W.base_ptr()) = svd.matrixU();
(Eigen::Map<VectorT>(S_val)) = svd.singularValues();
Map<MatrixT>(V.base_ptr()) = svd.matrixV();
}
MatBase<T, 3, 3> S = from_scale<MatBase<T, 3, 3>>(S_val);
MatBase<T, 3, 3> Vt = transpose(V);
r_U = W * Vt;
r_P = (V * S) * Vt;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Interpolate
* \{ */
template<typename T>
MatBase<T, 3, 3> interpolate(const MatBase<T, 3, 3> &A, const MatBase<T, 3, 3> &B, T t)
{
using Mat3T = MatBase<T, 3, 3>;
/* 'Rotation' component ('U' part of polar decomposition,
* the closest orthogonal matrix to M3 rot/scale
* transformation matrix), spherically interpolated. */
Mat3T U_A, U_B;
/* 'Scaling' component ('P' part of polar decomposition, i.e. scaling in U-defined space),
* linearly interpolated. */
Mat3T P_A, P_B;
polar_decompose(A, U_A, P_A);
polar_decompose(B, U_B, P_B);
/* Quaternions cannot represent an axis flip. If such a singularity is detected, choose a
* different decomposition of the matrix that still satisfies A = U_A * P_A but which has a
* positive determinant and thus no axis flips. This resolves T77154.
*
* Note that a flip of two axes is just a rotation of 180 degrees around the third axis, and
* three flipped axes are just an 180 degree rotation + a single axis flip. It is thus sufficient
* to solve this problem for single axis flips. */
if (is_negative(U_A)) {
U_A = -U_A;
P_A = -P_A;
}
if (is_negative(U_B)) {
U_B = -U_B;
P_B = -P_B;
}
detail::Quaternion<T> quat_A = math::to_quaternion(U_A);
detail::Quaternion<T> quat_B = math::to_quaternion(U_B);
detail::Quaternion<T> quat = math::interpolate(quat_A, quat_B, t);
Mat3T U = from_rotation<Mat3T>(quat);
Mat3T P = interpolate_linear(P_A, P_B, t);
/* And we reconstruct rot/scale matrix from interpolated polar components */
return U * P;
}
template float3x3 interpolate(const float3x3 &a, const float3x3 &b, float t);
template double3x3 interpolate(const double3x3 &a, const double3x3 &b, double t);
template<typename T>
MatBase<T, 4, 4> interpolate(const MatBase<T, 4, 4> &A, const MatBase<T, 4, 4> &B, T t)
{
MatBase<T, 4, 4> result = MatBase<T, 4, 4>(
interpolate(MatBase<T, 3, 3>(A), MatBase<T, 3, 3>(B), t));
/* Location component, linearly interpolated. */
const auto &loc_a = static_cast<const MatBase<T, 4, 4> &>(A).location();
const auto &loc_b = static_cast<const MatBase<T, 4, 4> &>(B).location();
result.location() = interpolate(loc_a, loc_b, t);
return result;
}
template float4x4 interpolate(const float4x4 &a, const float4x4 &b, float t);
template double4x4 interpolate(const double4x4 &a, const double4x4 &b, double t);
template<typename T>
MatBase<T, 3, 3> interpolate_fast(const MatBase<T, 3, 3> &a, const MatBase<T, 3, 3> &b, T t)
{
using QuaternionT = detail::Quaternion<T>;
using Vec3T = typename MatBase<T, 3, 3>::vec3_type;
Vec3T a_scale, b_scale;
QuaternionT a_quat, b_quat;
to_rot_scale<true>(a, a_quat, a_scale);
to_rot_scale<true>(b, b_quat, b_scale);
const Vec3T scale = interpolate(a_scale, b_scale, t);
const QuaternionT rotation = interpolate(a_quat, b_quat, t);
return from_rot_scale<MatBase<T, 3, 3>>(rotation, scale);
}
template float3x3 interpolate_fast(const float3x3 &a, const float3x3 &b, float t);
template double3x3 interpolate_fast(const double3x3 &a, const double3x3 &b, double t);
template<typename T>
MatBase<T, 4, 4> interpolate_fast(const MatBase<T, 4, 4> &a, const MatBase<T, 4, 4> &b, T t)
{
using QuaternionT = detail::Quaternion<T>;
using Vec3T = typename MatBase<T, 3, 3>::vec3_type;
Vec3T a_loc, b_loc;
Vec3T a_scale, b_scale;
QuaternionT a_quat, b_quat;
to_loc_rot_scale<true>(a, a_loc, a_quat, a_scale);
to_loc_rot_scale<true>(b, b_loc, b_quat, b_scale);
const Vec3T location = interpolate(a_loc, b_loc, t);
const Vec3T scale = interpolate(a_scale, b_scale, t);
const QuaternionT rotation = interpolate(a_quat, b_quat, t);
return from_loc_rot_scale<MatBase<T, 4, 4>>(location, rotation, scale);
}
template float4x4 interpolate_fast(const float4x4 &a, const float4x4 &b, float t);
template double4x4 interpolate_fast(const double4x4 &a, const double4x4 &b, double t);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Template instantiation
* \{ */
namespace detail {
template void normalized_to_eul2(const float3x3 &mat,
detail::EulerXYZ<float> &eul1,
detail::EulerXYZ<float> &eul2);
template void normalized_to_eul2(const double3x3 &mat,
detail::EulerXYZ<double> &eul1,
detail::EulerXYZ<double> &eul2);
template detail::Quaternion<float> normalized_to_quat_with_checks(const float3x3 &mat);
template detail::Quaternion<double> normalized_to_quat_with_checks(const double3x3 &mat);
template MatBase<float, 2, 2> from_rotation(const detail::AngleRadian<float> &rotation);
template MatBase<float, 3, 3> from_rotation(const detail::AngleRadian<float> &rotation);
template MatBase<float, 3, 3> from_rotation(const detail::EulerXYZ<float> &rotation);
template MatBase<float, 4, 4> from_rotation(const detail::EulerXYZ<float> &rotation);
template MatBase<float, 3, 3> from_rotation(const detail::Quaternion<float> &rotation);
template MatBase<float, 4, 4> from_rotation(const detail::Quaternion<float> &rotation);
template MatBase<float, 3, 3> from_rotation(const detail::AxisAngle<float> &rotation);
template MatBase<float, 4, 4> from_rotation(const detail::AxisAngle<float> &rotation);
} // namespace detail
template float3 transform_point(const float3x3 &mat, const float3 &point);
template float3 transform_point(const float4x4 &mat, const float3 &point);
template float3 transform_direction(const float3x3 &mat, const float3 &direction);
template float3 transform_direction(const float4x4 &mat, const float3 &direction);
template float3 project_point(const float4x4 &mat, const float3 &point);
template float2 project_point(const float3x3 &mat, const float2 &point);
namespace projection {
template float4x4 orthographic(
float left, float right, float bottom, float top, float near_clip, float far_clip);
template float4x4 perspective(
float left, float right, float bottom, float top, float near_clip, float far_clip);
} // namespace projection
/** \} */
} // namespace blender::math