blob: 5cfdc781877861e2b1524f38847cfc08c4563403 [file] [log] [blame]
// 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