| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/gfx/geometry/matrix44.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "ui/gfx/geometry/decomposed_transform.h" |
| |
| namespace gfx { |
| |
| namespace { |
| |
| ALWAYS_INLINE Double4 SwapHighLow(Double4 v) { |
| return Double4{v[2], v[3], v[0], v[1]}; |
| } |
| |
| ALWAYS_INLINE Double4 SwapInPairs(Double4 v) { |
| return Double4{v[1], v[0], v[3], v[2]}; |
| } |
| |
| // This is based on |
| // https://github.com/niswegmann/small-matrix-inverse/blob/master/invert4x4_llvm.h, |
| // which is based on Intel AP-928 "Streaming SIMD Extensions - Inverse of 4x4 |
| // Matrix": https://drive.google.com/file/d/0B9rh9tVI0J5mX1RUam5nZm85OFE/view. |
| ALWAYS_INLINE bool InverseWithDouble4Cols(Double4& c0, |
| Double4& c1, |
| Double4& c2, |
| Double4& c3) { |
| // Note that r1 and r3 have components 2/3 and 0/1 swapped. |
| Double4 r0 = {c0[0], c1[0], c2[0], c3[0]}; |
| Double4 r1 = {c2[1], c3[1], c0[1], c1[1]}; |
| Double4 r2 = {c0[2], c1[2], c2[2], c3[2]}; |
| Double4 r3 = {c2[3], c3[3], c0[3], c1[3]}; |
| |
| Double4 t = SwapInPairs(r2 * r3); |
| c0 = r1 * t; |
| c1 = r0 * t; |
| |
| t = SwapHighLow(t); |
| c0 = r1 * t - c0; |
| c1 = SwapHighLow(r0 * t - c1); |
| |
| t = SwapInPairs(r1 * r2); |
| c0 += r3 * t; |
| c3 = r0 * t; |
| |
| t = SwapHighLow(t); |
| c0 -= r3 * t; |
| c3 = SwapHighLow(r0 * t - c3); |
| |
| t = SwapInPairs(SwapHighLow(r1) * r3); |
| r2 = SwapHighLow(r2); |
| c0 += r2 * t; |
| c2 = r0 * t; |
| |
| t = SwapHighLow(t); |
| c0 -= r2 * t; |
| |
| double det = Sum(r0 * c0); |
| if (!std::isnormal(static_cast<float>(det))) |
| return false; |
| |
| c2 = SwapHighLow(r0 * t - c2); |
| |
| t = SwapInPairs(r0 * r1); |
| c2 = r3 * t + c2; |
| c3 = r2 * t - c3; |
| |
| t = SwapHighLow(t); |
| c2 = r3 * t - c2; |
| c3 -= r2 * t; |
| |
| t = SwapInPairs(r0 * r3); |
| c1 -= r2 * t; |
| c2 = r1 * t + c2; |
| |
| t = SwapHighLow(t); |
| c1 = r2 * t + c1; |
| c2 -= r1 * t; |
| |
| t = SwapInPairs(r0 * r2); |
| c1 = r3 * t + c1; |
| c3 -= r1 * t; |
| |
| t = SwapHighLow(t); |
| c1 -= r3 * t; |
| c3 = r1 * t + c3; |
| |
| det = 1.0 / det; |
| c0 *= det; |
| c1 *= det; |
| c2 *= det; |
| c3 *= det; |
| return true; |
| } |
| |
| } // anonymous namespace |
| |
| void Matrix44::GetColMajor(double dst[16]) const { |
| const double* src = &matrix_[0][0]; |
| std::copy(src, src + 16, dst); |
| } |
| |
| void Matrix44::GetColMajorF(float dst[16]) const { |
| const double* src = &matrix_[0][0]; |
| std::copy(src, src + 16, dst); |
| } |
| |
| void Matrix44::PreTranslate(double dx, double dy) { |
| SetCol(3, Col(0) * dx + Col(1) * dy + Col(3)); |
| } |
| |
| void Matrix44::PreTranslate3d(double dx, double dy, double dz) { |
| if (AllTrue(Double4{dx, dy, dz, 0} == Double4{0, 0, 0, 0})) |
| return; |
| |
| SetCol(3, Col(0) * dx + Col(1) * dy + Col(2) * dz + Col(3)); |
| } |
| |
| void Matrix44::PostTranslate(double dx, double dy) { |
| if (LIKELY(!HasPerspective())) { |
| matrix_[3][0] += dx; |
| matrix_[3][1] += dy; |
| } else { |
| if (dx != 0) { |
| matrix_[0][0] += matrix_[0][3] * dx; |
| matrix_[1][0] += matrix_[1][3] * dx; |
| matrix_[2][0] += matrix_[2][3] * dx; |
| matrix_[3][0] += matrix_[3][3] * dx; |
| } |
| if (dy != 0) { |
| matrix_[0][1] += matrix_[0][3] * dy; |
| matrix_[1][1] += matrix_[1][3] * dy; |
| matrix_[2][1] += matrix_[2][3] * dy; |
| matrix_[3][1] += matrix_[3][3] * dy; |
| } |
| } |
| } |
| |
| void Matrix44::PostTranslate3d(double dx, double dy, double dz) { |
| Double4 t{dx, dy, dz, 0}; |
| if (AllTrue(t == Double4{0, 0, 0, 0})) |
| return; |
| |
| if (LIKELY(!HasPerspective())) { |
| SetCol(3, Col(3) + t); |
| } else { |
| for (int i = 0; i < 4; ++i) |
| SetCol(i, Col(i) + t * matrix_[i][3]); |
| } |
| } |
| |
| void Matrix44::PreScale(double sx, double sy) { |
| SetCol(0, Col(0) * sx); |
| SetCol(1, Col(1) * sy); |
| } |
| |
| void Matrix44::PreScale3d(double sx, double sy, double sz) { |
| if (AllTrue(Double4{sx, sy, sz, 1} == Double4{1, 1, 1, 1})) |
| return; |
| |
| SetCol(0, Col(0) * sx); |
| SetCol(1, Col(1) * sy); |
| SetCol(2, Col(2) * sz); |
| } |
| |
| void Matrix44::PostScale(double sx, double sy) { |
| if (sx != 1) { |
| matrix_[0][0] *= sx; |
| matrix_[1][0] *= sx; |
| matrix_[2][0] *= sx; |
| matrix_[3][0] *= sx; |
| } |
| if (sy != 1) { |
| matrix_[0][1] *= sy; |
| matrix_[1][1] *= sy; |
| matrix_[2][1] *= sy; |
| matrix_[3][1] *= sy; |
| } |
| } |
| |
| void Matrix44::PostScale3d(double sx, double sy, double sz) { |
| if (AllTrue(Double4{sx, sy, sz, 1} == Double4{1, 1, 1, 1})) |
| return; |
| |
| Double4 s{sx, sy, sz, 1}; |
| for (int i = 0; i < 4; i++) |
| SetCol(i, Col(i) * s); |
| } |
| |
| void Matrix44::RotateUnitSinCos(double x, |
| double y, |
| double z, |
| double sin_angle, |
| double cos_angle) { |
| // Optimize cases where the axis is along a major axis. Since we've already |
| // normalized the vector we don't need to check that the other two dimensions |
| // are zero. Tiny errors of the other two dimensions are ignored. |
| if (z == 1.0) { |
| RotateAboutZAxisSinCos(sin_angle, cos_angle); |
| return; |
| } |
| if (y == 1.0) { |
| RotateAboutYAxisSinCos(sin_angle, cos_angle); |
| return; |
| } |
| if (x == 1.0) { |
| RotateAboutXAxisSinCos(sin_angle, cos_angle); |
| return; |
| } |
| |
| double c = cos_angle; |
| double s = sin_angle; |
| double C = 1 - c; |
| double xs = x * s; |
| double ys = y * s; |
| double zs = z * s; |
| double xC = x * C; |
| double yC = y * C; |
| double zC = z * C; |
| double xyC = x * yC; |
| double yzC = y * zC; |
| double zxC = z * xC; |
| |
| PreConcat(Matrix44(x * xC + c, xyC + zs, zxC - ys, 0, // col 0 |
| xyC - zs, y * yC + c, yzC + xs, 0, // col 1 |
| zxC + ys, yzC - xs, z * zC + c, 0, // col 2 |
| 0, 0, 0, 1)); // col 3 |
| } |
| |
| void Matrix44::RotateAboutXAxisSinCos(double sin_angle, double cos_angle) { |
| Double4 c1 = Col(1); |
| Double4 c2 = Col(2); |
| SetCol(1, c1 * cos_angle + c2 * sin_angle); |
| SetCol(2, c2 * cos_angle - c1 * sin_angle); |
| } |
| |
| void Matrix44::RotateAboutYAxisSinCos(double sin_angle, double cos_angle) { |
| Double4 c0 = Col(0); |
| Double4 c2 = Col(2); |
| SetCol(0, c0 * cos_angle - c2 * sin_angle); |
| SetCol(2, c2 * cos_angle + c0 * sin_angle); |
| } |
| |
| void Matrix44::RotateAboutZAxisSinCos(double sin_angle, double cos_angle) { |
| Double4 c0 = Col(0); |
| Double4 c1 = Col(1); |
| SetCol(0, c0 * cos_angle + c1 * sin_angle); |
| SetCol(1, c1 * cos_angle - c0 * sin_angle); |
| } |
| |
| void Matrix44::Skew(double tan_skew_x, double tan_skew_y) { |
| Double4 c0 = Col(0); |
| Double4 c1 = Col(1); |
| SetCol(0, c0 + c1 * tan_skew_y); |
| SetCol(1, c1 + c0 * tan_skew_x); |
| } |
| |
| void Matrix44::ApplyDecomposedSkews(const double skews[3]) { |
| Double4 c0 = Col(0); |
| Double4 c1 = Col(1); |
| Double4 c2 = Col(2); |
| // / |1 0 0 0| |1 0 s1 0| |1 s0 0 0| |1 s0 s1 0| \ |
| // |c0 c1 c2 c3| * | |0 1 s2 0| * |0 1 0 0| * |0 1 0 0| = |0 1 s2 0| | |
| // | |0 0 1 0| |0 0 1 0| |0 0 1 0| |0 0 1 0| | |
| // \ |0 0 0 1| |0 0 0 1| |0 0 0 1| |0 0 0 1| / |
| SetCol(1, c1 + c0 * skews[0]); |
| SetCol(2, c0 * skews[1] + c1 * skews[2] + c2); |
| } |
| |
| void Matrix44::ApplyPerspectiveDepth(double perspective) { |
| DCHECK_NE(perspective, 0.0); |
| SetCol(2, Col(2) + Col(3) * (-1.0 / perspective)); |
| } |
| |
| void Matrix44::SetConcat(const Matrix44& x, const Matrix44& y) { |
| if (x.Is2dTransform() && y.Is2dTransform()) { |
| double a = x.matrix_[0][0]; |
| double b = x.matrix_[0][1]; |
| double c = x.matrix_[1][0]; |
| double d = x.matrix_[1][1]; |
| double e = x.matrix_[3][0]; |
| double f = x.matrix_[3][1]; |
| double ya = y.matrix_[0][0]; |
| double yb = y.matrix_[0][1]; |
| double yc = y.matrix_[1][0]; |
| double yd = y.matrix_[1][1]; |
| double ye = y.matrix_[3][0]; |
| double yf = y.matrix_[3][1]; |
| *this = Matrix44(a * ya + c * yb, b * ya + d * yb, 0, 0, // col 0 |
| a * yc + c * yd, b * yc + d * yd, 0, 0, // col 1 |
| 0, 0, 1, 0, // col 2 |
| a * ye + c * yf + e, b * ye + d * yf + f, 0, 1); // col 3 |
| return; |
| } |
| |
| auto c0 = x.Col(0); |
| auto c1 = x.Col(1); |
| auto c2 = x.Col(2); |
| auto c3 = x.Col(3); |
| |
| auto mc0 = y.Col(0); |
| auto mc1 = y.Col(1); |
| auto mc2 = y.Col(2); |
| auto mc3 = y.Col(3); |
| |
| SetCol(0, c0 * mc0[0] + c1 * mc0[1] + c2 * mc0[2] + c3 * mc0[3]); |
| SetCol(1, c0 * mc1[0] + c1 * mc1[1] + c2 * mc1[2] + c3 * mc1[3]); |
| SetCol(2, c0 * mc2[0] + c1 * mc2[1] + c2 * mc2[2] + c3 * mc2[3]); |
| SetCol(3, c0 * mc3[0] + c1 * mc3[1] + c2 * mc3[2] + c3 * mc3[3]); |
| } |
| |
| bool Matrix44::GetInverse(Matrix44& result) const { |
| if (Is2dTransform()) { |
| double determinant = Determinant(); |
| if (!std::isnormal(static_cast<float>(determinant))) |
| return false; |
| |
| double inv_det = 1.0 / determinant; |
| double a = matrix_[0][0]; |
| double b = matrix_[0][1]; |
| double c = matrix_[1][0]; |
| double d = matrix_[1][1]; |
| double e = matrix_[3][0]; |
| double f = matrix_[3][1]; |
| result = Matrix44(d * inv_det, -b * inv_det, 0, 0, // col 0 |
| -c * inv_det, a * inv_det, 0, 0, // col 1 |
| 0, 0, 1, 0, // col 2 |
| (c * f - d * e) * inv_det, (b * e - a * f) * inv_det, 0, |
| 1); // col 3 |
| return true; |
| } |
| |
| Double4 c0 = Col(0); |
| Double4 c1 = Col(1); |
| Double4 c2 = Col(2); |
| Double4 c3 = Col(3); |
| |
| if (!InverseWithDouble4Cols(c0, c1, c2, c3)) |
| return false; |
| |
| result.SetCol(0, c0); |
| result.SetCol(1, c1); |
| result.SetCol(2, c2); |
| result.SetCol(3, c3); |
| return true; |
| } |
| |
| bool Matrix44::IsInvertible() const { |
| return std::isnormal(static_cast<float>(Determinant())); |
| } |
| |
| // This is a simplified version of InverseWithDouble4Cols(). |
| double Matrix44::Determinant() const { |
| if (Is2dTransform()) |
| return matrix_[0][0] * matrix_[1][1] - matrix_[0][1] * matrix_[1][0]; |
| |
| Double4 c0 = Col(0); |
| Double4 c1 = Col(1); |
| Double4 c2 = Col(2); |
| Double4 c3 = Col(3); |
| |
| // Note that r1 and r3 have components 2/3 and 0/1 swapped. |
| Double4 r0 = {c0[0], c1[0], c2[0], c3[0]}; |
| Double4 r1 = {c2[1], c3[1], c0[1], c1[1]}; |
| Double4 r2 = {c0[2], c1[2], c2[2], c3[2]}; |
| Double4 r3 = {c2[3], c3[3], c0[3], c1[3]}; |
| |
| Double4 t = SwapInPairs(r2 * r3); |
| c0 = r1 * t; |
| t = SwapHighLow(t); |
| c0 = r1 * t - c0; |
| t = SwapInPairs(r1 * r2); |
| c0 += r3 * t; |
| t = SwapHighLow(t); |
| c0 -= r3 * t; |
| t = SwapInPairs(SwapHighLow(r1) * r3); |
| r2 = SwapHighLow(r2); |
| c0 += r2 * t; |
| t = SwapHighLow(t); |
| c0 -= r2 * t; |
| |
| return Sum(r0 * c0); |
| } |
| |
| void Matrix44::Transpose() { |
| using std::swap; |
| swap(matrix_[0][1], matrix_[1][0]); |
| swap(matrix_[0][2], matrix_[2][0]); |
| swap(matrix_[0][3], matrix_[3][0]); |
| swap(matrix_[1][2], matrix_[2][1]); |
| swap(matrix_[1][3], matrix_[3][1]); |
| swap(matrix_[2][3], matrix_[3][2]); |
| } |
| |
| void Matrix44::Zoom(double zoom_factor) { |
| matrix_[0][3] /= zoom_factor; |
| matrix_[1][3] /= zoom_factor; |
| matrix_[2][3] /= zoom_factor; |
| matrix_[3][0] *= zoom_factor; |
| matrix_[3][1] *= zoom_factor; |
| matrix_[3][2] *= zoom_factor; |
| } |
| |
| double Matrix44::MapVector2(double vec[2]) const { |
| double v0 = vec[0]; |
| double v1 = vec[1]; |
| double x = v0 * matrix_[0][0] + v1 * matrix_[1][0] + matrix_[3][0]; |
| double y = v0 * matrix_[0][1] + v1 * matrix_[1][1] + matrix_[3][1]; |
| double w = v0 * matrix_[0][3] + v1 * matrix_[1][3] + matrix_[3][3]; |
| vec[0] = x; |
| vec[1] = y; |
| return w; |
| } |
| |
| void Matrix44::MapVector4(double vec[4]) const { |
| Double4 v = LoadDouble4(vec); |
| Double4 r0{matrix_[0][0], matrix_[1][0], matrix_[2][0], matrix_[3][0]}; |
| Double4 r1{matrix_[0][1], matrix_[1][1], matrix_[2][1], matrix_[3][1]}; |
| Double4 r2{matrix_[0][2], matrix_[1][2], matrix_[2][2], matrix_[3][2]}; |
| Double4 r3{matrix_[0][3], matrix_[1][3], matrix_[2][3], matrix_[3][3]}; |
| StoreDouble4(Double4{Sum(r0 * v), Sum(r1 * v), Sum(r2 * v), Sum(r3 * v)}, |
| vec); |
| } |
| |
| void Matrix44::Flatten() { |
| matrix_[0][2] = 0; |
| matrix_[1][2] = 0; |
| matrix_[3][2] = 0; |
| SetCol(2, Double4{0, 0, 1, 0}); |
| } |
| |
| // TODO(crbug.com/1359528): Consider letting this function always succeed. |
| absl::optional<DecomposedTransform> Matrix44::Decompose2d() const { |
| DCHECK(Is2dTransform()); |
| |
| // https://www.w3.org/TR/css-transforms-1/#decomposing-a-2d-matrix. |
| // Decompose a 2D transformation matrix of the form: |
| // [m11 m21 0 m41] |
| // [m12 m22 0 m42] |
| // [ 0 0 1 0 ] |
| // [ 0 0 0 1 ] |
| // |
| // The decomposition is of the form: |
| // M = translate * rotate * skew * scale |
| // [1 0 0 Tx] [cos(R) -sin(R) 0 0] [1 K 0 0] [Sx 0 0 0] |
| // = [0 1 0 Ty] [sin(R) cos(R) 0 0] [0 1 0 0] [0 Sy 0 0] |
| // [0 0 1 0 ] [ 0 0 1 0] [0 0 1 0] [0 0 1 0] |
| // [0 0 0 1 ] [ 0 0 0 1] [0 0 0 1] [0 0 0 1] |
| |
| double m11 = matrix_[0][0]; |
| double m21 = matrix_[1][0]; |
| double m12 = matrix_[0][1]; |
| double m22 = matrix_[1][1]; |
| |
| double determinant = m11 * m22 - m12 * m21; |
| // Test for matrix being singular. |
| if (determinant == 0) |
| return absl::nullopt; |
| |
| DecomposedTransform decomp; |
| |
| // Translation transform. |
| // [m11 m21 0 m41] [1 0 0 Tx] [m11 m21 0 0] |
| // [m12 m22 0 m42] = [0 1 0 Ty] [m12 m22 0 0] |
| // [ 0 0 1 0 ] [0 0 1 0 ] [ 0 0 1 0] |
| // [ 0 0 0 1 ] [0 0 0 1 ] [ 0 0 0 1] |
| decomp.translate[0] = matrix_[3][0]; |
| decomp.translate[1] = matrix_[3][1]; |
| |
| // For the remainder of the decomposition process, we can focus on the upper |
| // 2x2 submatrix |
| // [m11 m21] = [cos(R) -sin(R)] [1 K] [Sx 0 ] |
| // [m12 m22] [sin(R) cos(R)] [0 1] [0 Sy] |
| // = [Sx*cos(R) Sy*(K*cos(R) - sin(R))] |
| // [Sx*sin(R) Sy*(K*sin(R) + cos(R))] |
| |
| // Determine sign of the x and y scale. |
| if (determinant < 0) { |
| // If the determinant is negative, we need to flip either the x or y scale. |
| // Flipping both is equivalent to rotating by 180 degrees. |
| if (m11 < m22) { |
| decomp.scale[0] *= -1; |
| } else { |
| decomp.scale[1] *= -1; |
| } |
| } |
| |
| // X Scale. |
| // m11^2 + m12^2 = Sx^2*(cos^2(R) + sin^2(R)) = Sx^2. |
| // Sx = +/-sqrt(m11^2 + m22^2) |
| decomp.scale[0] *= sqrt(m11 * m11 + m12 * m12); |
| m11 /= decomp.scale[0]; |
| m12 /= decomp.scale[0]; |
| |
| // Post normalization, the submatrix is now of the form: |
| // [m11 m21] = [cos(R) Sy*(K*cos(R) - sin(R))] |
| // [m12 m22] [sin(R) Sy*(K*sin(R) + cos(R))] |
| |
| // XY Shear. |
| // m11 * m21 + m12 * m22 = Sy*K*cos^2(R) - Sy*sin(R)*cos(R) + |
| // Sy*K*sin^2(R) + Sy*cos(R)*sin(R) |
| // = Sy*K |
| double scaled_shear = m11 * m21 + m12 * m22; |
| m21 -= m11 * scaled_shear; |
| m22 -= m12 * scaled_shear; |
| |
| // Post normalization, the submatrix is now of the form: |
| // [m11 m21] = [cos(R) -Sy*sin(R)] |
| // [m12 m22] [sin(R) Sy*cos(R)] |
| |
| // Y Scale. |
| // Similar process to determining x-scale. |
| decomp.scale[1] *= sqrt(m21 * m21 + m22 * m22); |
| m21 /= decomp.scale[1]; |
| m22 /= decomp.scale[1]; |
| decomp.skew[0] = scaled_shear / decomp.scale[1]; |
| |
| // Rotation transform. |
| // [1-2(yy+zz) 2(xy-zw) 2(xz+yw) ] [cos(R) -sin(R) 0] |
| // [2(xy+zw) 1-2(xx+zz) 2(yz-xw) ] = [sin(R) cos(R) 0] |
| // [2(xz-yw) 2*(yz+xw) 1-2(xx+yy)] [ 0 0 1] |
| // Comparing terms, we can conclude that x = y = 0. |
| // [1-2zz -2zw 0] [cos(R) -sin(R) 0] |
| // [ 2zw 1-2zz 0] = [sin(R) cos(R) 0] |
| // [ 0 0 1] [ 0 0 1] |
| // cos(R) = 1 - 2*z^2 |
| // From the double angle formula: cos(2a) = 1 - 2 sin(a)^2 |
| // cos(R) = 1 - 2*sin(R/2)^2 = 1 - 2*z^2 ==> z = sin(R/2) |
| // sin(R) = 2*z*w |
| // But sin(2a) = 2 sin(a) cos(a) |
| // sin(R) = 2 sin(R/2) cos(R/2) = 2*z*w ==> w = cos(R/2) |
| double angle = std::atan2(m12, m11); |
| decomp.quaternion.set_x(0); |
| decomp.quaternion.set_y(0); |
| decomp.quaternion.set_z(std::sin(0.5 * angle)); |
| decomp.quaternion.set_w(std::cos(0.5 * angle)); |
| |
| return decomp; |
| } |
| |
| absl::optional<DecomposedTransform> Matrix44::Decompose() const { |
| // See documentation of Transform::Decompose() for why we need the 2d branch. |
| if (Is2dTransform()) |
| return Decompose2d(); |
| |
| // https://www.w3.org/TR/css-transforms-2/#decomposing-a-3d-matrix. |
| |
| Double4 c0 = Col(0); |
| Double4 c1 = Col(1); |
| Double4 c2 = Col(2); |
| Double4 c3 = Col(3); |
| |
| // Normalize the matrix. |
| if (!std::isnormal(c3[3])) |
| return absl::nullopt; |
| |
| double inv_w = 1.0 / c3[3]; |
| c0 *= inv_w; |
| c1 *= inv_w; |
| c2 *= inv_w; |
| c3 *= inv_w; |
| |
| Double4 perspective = {c0[3], c1[3], c2[3], 1.0}; |
| // Clear the perspective partition. |
| c0[3] = c1[3] = c2[3] = 0; |
| c3[3] = 1; |
| |
| Double4 inverse_c0 = c0; |
| Double4 inverse_c1 = c1; |
| Double4 inverse_c2 = c2; |
| Double4 inverse_c3 = c3; |
| if (!InverseWithDouble4Cols(inverse_c0, inverse_c1, inverse_c2, inverse_c3)) |
| return absl::nullopt; |
| |
| DecomposedTransform decomp; |
| |
| // First, isolate perspective. |
| if (!AllTrue(perspective == Double4{0, 0, 0, 1})) { |
| // Solve the equation by multiplying perspective by the inverse. |
| decomp.perspective[0] = gfx::Sum(perspective * inverse_c0); |
| decomp.perspective[1] = gfx::Sum(perspective * inverse_c1); |
| decomp.perspective[2] = gfx::Sum(perspective * inverse_c2); |
| decomp.perspective[3] = gfx::Sum(perspective * inverse_c3); |
| } |
| |
| // Next take care of translation (easy). |
| decomp.translate[0] = c3[0]; |
| c3[0] = 0; |
| decomp.translate[1] = c3[1]; |
| c3[1] = 0; |
| decomp.translate[2] = c3[2]; |
| c3[2] = 0; |
| |
| // Note: Deviating from the spec in terms of variable naming. The matrix is |
| // stored on column major order and not row major. Using the variable 'row' |
| // instead of 'column' in the spec pseudocode has been the source of |
| // confusion, specifically in sorting out rotations. |
| |
| // From now on, only the first 3 components of the Double4 column is used. |
| auto sum3 = [](Double4 c) -> double { return c[0] + c[1] + c[2]; }; |
| auto extract_scale = [&sum3](Double4& c, double& scale) -> bool { |
| scale = std::sqrt(sum3(c * c)); |
| if (!std::isnormal(scale)) |
| return false; |
| c *= 1.0 / scale; |
| return true; |
| }; |
| auto epsilon_to_zero = [](double d) -> double { |
| return std::abs(d) < std::numeric_limits<float>::epsilon() ? 0 : d; |
| }; |
| |
| // Compute X scale factor and normalize the first column. |
| if (!extract_scale(c0, decomp.scale[0])) |
| return absl::nullopt; |
| |
| // Compute XY shear factor and make 2nd column orthogonal to 1st. |
| decomp.skew[0] = epsilon_to_zero(sum3(c0 * c1)); |
| c1 -= c0 * decomp.skew[0]; |
| |
| // Now, compute Y scale and normalize 2nd column. |
| if (!extract_scale(c1, decomp.scale[1])) |
| return absl::nullopt; |
| |
| decomp.skew[0] /= decomp.scale[1]; |
| |
| // Compute XZ and YZ shears, and orthogonalize the 3rd column. |
| decomp.skew[1] = epsilon_to_zero(sum3(c0 * c2)); |
| c2 -= c0 * decomp.skew[1]; |
| decomp.skew[2] = epsilon_to_zero(sum3(c1 * c2)); |
| c2 -= c1 * decomp.skew[2]; |
| |
| // Next, get Z scale and normalize the 3rd column. |
| if (!extract_scale(c2, decomp.scale[2])) |
| return absl::nullopt; |
| |
| decomp.skew[1] /= decomp.scale[2]; |
| decomp.skew[2] /= decomp.scale[2]; |
| |
| // At this point, the matrix is orthonormal. |
| // Check for a coordinate system flip. If the determinant is -1, then negate |
| // the matrix and the scaling factors. |
| auto cross3 = [](Double4 a, Double4 b) -> Double4 { |
| return Double4{a[1], a[2], a[0], a[3]} * Double4{b[2], b[0], b[1], b[3]} - |
| Double4{a[2], a[0], a[1], a[3]} * Double4{b[1], b[2], b[0], b[3]}; |
| }; |
| Double4 pdum3 = cross3(c1, c2); |
| if (sum3(c0 * pdum3) < 0) { |
| // Flip all 3 scaling factors, following the 3d decomposition spec. See |
| // documentation of Transform::Decompose() about the difference between |
| // the 2d spec and and 3d spec about scale flipping. |
| decomp.scale[0] *= -1; |
| decomp.scale[1] *= -1; |
| decomp.scale[2] *= -1; |
| c0 *= -1; |
| c1 *= -1; |
| c2 *= -1; |
| } |
| |
| // Lastly, compute the quaternions. |
| // See https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion. |
| // Note: deviating from spec (http://www.w3.org/TR/css3-transforms/) |
| // which has a degenerate case when the trace (t) of the orthonormal matrix |
| // (Q) approaches -1. In the Wikipedia article, Q_ij is indexing on row then |
| // column. Thus, Q_ij = column[j][i]. |
| |
| // The following are equivalent representations of the rotation matrix: |
| // |
| // Axis-angle form: |
| // |
| // [ c+(1-c)x^2 (1-c)xy-sz (1-c)xz+sy ] c = cos theta |
| // R = [ (1-c)xy+sz c+(1-c)y^2 (1-c)yz-sx ] s = sin theta |
| // [ (1-c)xz-sy (1-c)yz+sx c+(1-c)z^2 ] [x,y,z] = axis or rotation |
| // |
| // The sum of the diagonal elements (trace) is a simple function of the cosine |
| // of the angle. The w component of the quaternion is cos(theta/2), and we |
| // make use of the double angle formula to directly compute w from the |
| // trace. Differences between pairs of skew symmetric elements in this matrix |
| // isolate the remaining components. Since w can be zero (also numerically |
| // unstable if near zero), we cannot rely solely on this approach to compute |
| // the quaternion components. |
| // |
| // Quaternion form: |
| // |
| // [ 1-2(y^2+z^2) 2(xy-zw) 2(xz+yw) ] |
| // r = [ 2(xy+zw) 1-2(x^2+z^2) 2(yz-xw) ] q = (x,y,z,w) |
| // [ 2(xz-yw) 2(yz+xw) 1-2(x^2+y^2) ] |
| // |
| // Different linear combinations of the diagonal elements isolates x, y or z. |
| // Sums or differences between skew symmetric elements isolate the remainder. |
| |
| double r, s, t, x, y, z, w; |
| |
| t = c0[0] + c1[1] + c2[2]; // trace of Q |
| |
| // https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion |
| if (1 + t > 0.001) { |
| // Numerically stable as long as 1+t is not close to zero. Otherwise use the |
| // diagonal element with the greatest value to compute the quaternions. |
| r = std::sqrt(1.0 + t); |
| s = 0.5 / r; |
| w = 0.5 * r; |
| x = (c1[2] - c2[1]) * s; |
| y = (c2[0] - c0[2]) * s; |
| z = (c0[1] - c1[0]) * s; |
| } else if (c0[0] > c1[1] && c0[0] > c2[2]) { |
| // Q_xx is largest. |
| r = std::sqrt(1.0 + c0[0] - c1[1] - c2[2]); |
| s = 0.5 / r; |
| x = 0.5 * r; |
| y = (c1[0] + c0[1]) * s; |
| z = (c2[0] + c0[2]) * s; |
| w = (c1[2] - c2[1]) * s; |
| } else if (c1[1] > c2[2]) { |
| // Q_yy is largest. |
| r = std::sqrt(1.0 - c0[0] + c1[1] - c2[2]); |
| s = 0.5 / r; |
| x = (c1[0] + c0[1]) * s; |
| y = 0.5 * r; |
| z = (c2[1] + c1[2]) * s; |
| w = (c2[0] - c0[2]) * s; |
| } else { |
| // Q_zz is largest. |
| r = std::sqrt(1.0 - c0[0] - c1[1] + c2[2]); |
| s = 0.5 / r; |
| x = (c2[0] + c0[2]) * s; |
| y = (c2[1] + c1[2]) * s; |
| z = 0.5 * r; |
| w = (c0[1] - c1[0]) * s; |
| } |
| |
| decomp.quaternion.set_x(x); |
| decomp.quaternion.set_y(y); |
| decomp.quaternion.set_z(z); |
| decomp.quaternion.set_w(w); |
| |
| return decomp; |
| } |
| |
| } // namespace gfx |