| // Copyright 2015 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/math/matrix_interpolation.h" |
| |
| #include <cmath> |
| |
| #include "cobalt/math/transform_2d.h" |
| |
| namespace cobalt { |
| namespace math { |
| |
| // All algorithms here are based on the specifications provided by the W3C here: |
| // https://www.w3.org/TR/css-transforms/#interpolation-of-2d-matrices |
| DecomposedMatrix DecomposeMatrix(const math::Matrix3F& matrix) { |
| DecomposedMatrix decomposition; |
| |
| float col0x = matrix.Get(0, 0); |
| float col0y = matrix.Get(1, 0); |
| float col1x = matrix.Get(0, 1); |
| float col1y = matrix.Get(1, 1); |
| |
| decomposition.translation[0] = matrix.Get(0, 2); |
| decomposition.translation[1] = matrix.Get(1, 2); |
| |
| decomposition.scale[0] = sqrt(col0x * col0x + col0y * col0y); |
| decomposition.scale[1] = sqrt(col1x * col1x + col1y * col1y); |
| |
| // If determinant is negative, one axis was flipped. |
| float determinant = col0x * col1y - col0y * col1x; |
| if (determinant < 0) { |
| // Flip axis with minimum unit vector dot product. |
| if (col0x < col1y) { |
| decomposition.scale[0] = -decomposition.scale[0]; |
| } else { |
| decomposition.scale[1] = -decomposition.scale[1]; |
| } |
| } |
| |
| // Renormalize matrix to remove scale. |
| if ((0.0f != decomposition.scale[0])) { |
| col0x *= 1 / decomposition.scale[0]; |
| col0y *= 1 / decomposition.scale[0]; |
| } |
| if ((0.0f != decomposition.scale[1])) { |
| col1x *= 1 / decomposition.scale[1]; |
| col1y *= 1 / decomposition.scale[1]; |
| } |
| |
| // Compute rotation and renormalize matrix. |
| decomposition.angle = atan2(col0y, col0x); |
| |
| if ((0.0f != decomposition.angle)) { |
| // Rotate(-angle) = [ cos(angle), sin(angle) ] |
| // [ -sin(angle), cos(angle) ] |
| // = [ col0x, col0y ] |
| // [ -col0y, col0x ] |
| // Thanks to the normalization above. |
| float sin_angle = col0y; |
| float cos_angle = col0x; |
| float m11 = col0x; |
| float m12 = col1x; |
| float m21 = col0y; |
| float m22 = col1y; |
| col0x = cos_angle * m11 + sin_angle * m21; |
| col0y = -sin_angle * m11 + cos_angle * m21; |
| col1x = cos_angle * m12 + sin_angle * m22; |
| col1y = -sin_angle * m12 + cos_angle * m22; |
| } |
| |
| decomposition.m11 = col0x; |
| decomposition.m12 = col1x; |
| decomposition.m21 = col0y; |
| decomposition.m22 = col1y; |
| |
| return decomposition; |
| } |
| |
| float Lerp(float a, float b, float progress) { |
| return a + (b - a) * progress; |
| } |
| |
| namespace { |
| |
| // Prepare certain attributes of the decomposed matrices for interpolation. |
| void SanitizeInputsForInterpolation( |
| const DecomposedMatrix& a, const DecomposedMatrix& b, |
| float* a_scale_sanitized, float* a_angle_sanitized, |
| float* b_angle_sanitized) { |
| static const float kPi = static_cast<float>(M_PI); |
| |
| // If x-axis of one is flipped, and y-axis of the other, |
| // convert to an unflipped rotation. |
| if ((a.scale[0] < 0 && b.scale[1] < 0) || |
| (a.scale[1] < 0 && b.scale[0] < 0)) { |
| a_scale_sanitized[0] = -a.scale[0]; |
| a_scale_sanitized[1] = -a.scale[1]; |
| *a_angle_sanitized = a.angle + a.angle < 0 ? kPi : -kPi; |
| } else { |
| a_scale_sanitized[0] = a.scale[0]; |
| a_scale_sanitized[1] = a.scale[1]; |
| *a_angle_sanitized = a.angle; |
| } |
| |
| // Don’t rotate the long way around. |
| *b_angle_sanitized = b.angle; |
| float angle_difference = std::abs(*a_angle_sanitized - *b_angle_sanitized); |
| if (angle_difference > kPi) { |
| float rounded_up_revolutions_difference = |
| std::ceil(angle_difference / (2 * kPi)); |
| if (*a_angle_sanitized > *b_angle_sanitized) { |
| *a_angle_sanitized -= 2 * kPi * rounded_up_revolutions_difference; |
| } else { |
| *b_angle_sanitized -= 2 * kPi * rounded_up_revolutions_difference; |
| } |
| } |
| } |
| |
| } // namespace |
| |
| DecomposedMatrix InterpolateMatrixDecompositions(const DecomposedMatrix& a, |
| const DecomposedMatrix& b, |
| float progress) { |
| float a_scale[2]; |
| float a_angle; |
| float b_angle; |
| SanitizeInputsForInterpolation(a, b, a_scale, &a_angle, &b_angle); |
| |
| // And now we lerp each component individually. |
| DecomposedMatrix result; |
| result.translation[0] = Lerp(a.translation[0], b.translation[0], progress); |
| result.translation[1] = Lerp(a.translation[1], b.translation[1], progress); |
| result.scale[0] = Lerp(a_scale[0], b.scale[0], progress); |
| result.scale[1] = Lerp(a_scale[1], b.scale[1], progress); |
| result.angle = Lerp(a_angle, b_angle, progress); |
| result.m11 = Lerp(a.m11, b.m11, progress); |
| result.m12 = Lerp(a.m12, b.m12, progress); |
| result.m21 = Lerp(a.m21, b.m21, progress); |
| result.m22 = Lerp(a.m22, b.m22, progress); |
| return result; |
| } |
| |
| // Reconstruct a matrix from a given matrix decomposition. |
| // https://www.w3.org/TR/css-transforms/#recomposing-to-a-2d-matrix |
| math::Matrix3F RecomposeMatrix(const DecomposedMatrix& decomposition) { |
| math::Matrix3F matrix(math::Matrix3F::Identity()); |
| |
| matrix(0, 0) = decomposition.m11; |
| matrix(0, 1) = decomposition.m12; |
| matrix(1, 0) = decomposition.m21; |
| matrix(1, 1) = decomposition.m22; |
| |
| // Rotate matrix. |
| matrix = math::RotateMatrix(-decomposition.angle) * matrix; |
| |
| // Scale matrix. |
| matrix(0, 0) *= decomposition.scale[0]; |
| matrix(1, 0) *= decomposition.scale[0]; |
| matrix(0, 1) *= decomposition.scale[1]; |
| matrix(1, 1) *= decomposition.scale[1]; |
| |
| // Translate matrix. Note that we deviate from the specification here |
| // because the specification's "recompose" algorithm is inconsistent with |
| // its "decompose" algorithm. The modification acheives this consistency. |
| matrix(0, 2) = decomposition.translation[0]; |
| matrix(1, 2) = decomposition.translation[1]; |
| |
| return matrix; |
| } |
| |
| math::Matrix3F InterpolateMatrices(const math::Matrix3F& a, |
| const math::Matrix3F& b, float progress) { |
| // The main algorithm here is to decompose each matrix, interpolate the |
| // decompositions, and then reconstruct a matrix from the interpolated |
| // compositions. |
| DecomposedMatrix decomposed_a = DecomposeMatrix(a); |
| DecomposedMatrix decomposed_b = DecomposeMatrix(b); |
| |
| DecomposedMatrix decomposed_interpolated = |
| InterpolateMatrixDecompositions(decomposed_a, decomposed_b, progress); |
| |
| return RecomposeMatrix(decomposed_interpolated); |
| } |
| |
| } // namespace math |
| } // namespace cobalt |