Revert D21615829: LayoutAnimations: Use Quaternions to interpolate rotation transforms

Differential Revision:
D21615829

Original commit changeset: da6cb931ce85

fbshipit-source-id: 4b5bc392a35aac627b89ccccb73e10a2b1c4aaa6
This commit is contained in:
Ishan Khot
2020-05-20 16:34:27 -07:00
committed by Facebook GitHub Bot
parent adccef7f48
commit 953c39293e
3 changed files with 10 additions and 387 deletions
-208
View File
@@ -1,208 +0,0 @@
/*
* Portions Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/graphics/Float.h>
#include <array>
#include <cmath>
// The following is a modified, stripped-down version of the Quaternion class
// by Frank Astier. Copyright notice below.
// The original has many, many more features, and has been stripped down
// to support the exact data-structures and use-cases we need for React Native.
/**
* The MIT License (MIT)
*
* Copyright (c) 2015 Frank Astier
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace facebook {
namespace react {
template <typename T = Float>
class Quaternion {
public:
/**
* Copy constructor.
*/
Quaternion(const Quaternion<T> &y) : a_(y.a_), b_(y.b_), c_(y.c_), d_(y.d_) {}
Quaternion(T a, T b, T c, T d) : a_(a), b_(b), c_(c), d_(d) {}
static Quaternion<T> fromRotationMatrix(std::array<T, 16> const &rm) {
T t = rm[0 * 4 + 0] + rm[1 * 4 + 1] + rm[2 * 4 + 2];
if (t > 0) {
T s = (T)0.5 / std::sqrt(t + 1);
return {(T)0.25 / s,
(rm[2 * 4 + 1] - rm[1 * 4 + 2]) * s,
(rm[0 * 4 + 2] - rm[2 * 4 + 0]) * s,
(rm[1 * 4 + 0] - rm[0 * 4 + 1]) * s};
} else if (rm[0 * 4 + 0] > rm[1 * 4 + 1] && rm[0 * 4 + 0] > rm[2 * 4 + 2]) {
T s = (T)2.0 *
std::sqrt(
1.0 + rm[0 * 4 + 0] - rm[1 * 4 + 1] - rm[2 * 4 + 2]); // S=4*qx
return {(rm[2 * 4 + 1] - rm[1 * 4 + 2]) / s,
(T)0.25 * s,
(rm[0 * 4 + 1] + rm[1 * 4 + 0]) / s,
(rm[0 * 4 + 2] + rm[2 * 4 + 0]) / s};
} else if (rm[1 * 4 + 1] > rm[2 * 4 + 2]) {
T s = (T)2.0 *
std::sqrt(
1.0 + rm[1 * 4 + 1] - rm[0 * 4 + 0] - rm[2 * 4 + 2]); // S=4*qy
return {(rm[0 * 4 + 2] - rm[2 * 4 + 0]) / s,
(rm[0 * 4 + 1] + rm[1 * 4 + 0]) / s,
(T)0.25 * s,
(rm[1 * 4 + 2] + rm[2 * 4 + 1]) / s};
} else {
T s = (T)2.0 *
std::sqrt(
1.0 + rm[2 * 4 + 2] - rm[0 * 4 + 0] - rm[1 * 4 + 1]); // S=4*qz
return {(rm[1 * 4 + 0] - rm[0 * 4 + 1]) / s,
(rm[0 * 4 + 2] + rm[2 * 4 + 0]) / s,
(rm[1 * 4 + 2] + rm[2 * 4 + 1]) / s,
(T)0.25 * s};
}
}
/**
* Returns a 3D, 4x4 rotation matrix.
* This is the "homogeneous" expression to convert to a rotation matrix,
* which works even when the Quaternion is not a unit Quaternion.
*/
inline std::array<T, 16> toRotationMatrix4x4() {
T a2 = a_ * a_, b2 = b_ * b_, c2 = c_ * c_, d2 = d_ * d_;
T ab = a_ * b_, ac = a_ * c_, ad = a_ * d_;
T bc = b_ * c_, bd = b_ * d_;
T cd = c_ * d_;
return {a2 + b2 - c2 - d2,
2 * (bc - ad),
2 * (bd + ac),
0,
2 * (bc + ad),
a2 - b2 + c2 - d2,
2 * (cd - ab),
0,
2 * (bd - ac),
2 * (cd + ab),
a2 - b2 - c2 + d2,
0,
0,
0,
0,
1};
}
inline Quaternion<T> normalize() const {
assert(abs() > 0); // or this is not normalizable
T factor = abs();
return *this / (factor != 0 ? factor : 1);
}
inline T dot(const Quaternion<T> &other) {
return a_ * other.a_ + b_ * other.b_ + c_ * other.c_ + d_ * other.d_;
}
/**
* The square of the norm of the Quaternion.
* (The square is sometimes useful, and it avoids paying for a sqrt).
*/
inline T norm_squared() const {
return a_ * a_ + b_ * b_ + c_ * c_ + d_ * d_;
}
/**
* The norm of the Quaternion (the l2 norm).
*/
inline T abs() const {
return std::sqrt(norm_squared());
}
inline Quaternion<T> operator/=(T y) {
a_ /= y;
b_ /= y;
c_ /= y;
d_ /= y;
return *this;
}
inline Quaternion<T> operator*=(T y) {
a_ *= y;
b_ *= y;
c_ *= y;
d_ *= y;
return *this;
}
inline Quaternion<T> operator+=(Quaternion<T> const &other) {
a_ += other.a_;
b_ += other.b_;
c_ += other.c_;
d_ += other.d_;
return *this;
}
inline Quaternion<T> operator-=(Quaternion<T> const &other) {
a_ -= other.a_;
b_ -= other.b_;
c_ -= other.c_;
d_ -= other.d_;
return *this;
}
private:
T a_; // AKA w, qw
T b_; // AKA x, qx
T c_; // AKA y, qy
T d_; // AKA z, qz
};
template <typename T = Float>
inline Quaternion<T> operator/(Quaternion<T> const &lhs, T rhs) {
return Quaternion<T>(lhs) /= rhs;
}
template <typename T = Float>
inline Quaternion<T> operator*(Quaternion<T> const &lhs, T rhs) {
return Quaternion<T>(lhs) *= rhs;
}
template <typename T = Float>
inline Quaternion<T> operator+(
Quaternion<T> const &lhs,
Quaternion<T> const &rhs) {
return Quaternion<T>(lhs) += rhs;
}
template <typename T = Float>
inline Quaternion<T> operator-(
Quaternion<T> const &lhs,
Quaternion<T> const &rhs) {
return Quaternion<T>(lhs) -= rhs;
}
} // namespace react
} // namespace facebook
+6 -136
View File
@@ -7,34 +7,18 @@
#include "Transform.h"
#include <react/graphics/Quaternion.h>
#include <cmath>
#include <Glog/logging.h>
namespace facebook {
namespace react {
#ifdef RN_DEBUG_STRING_CONVERTIBLE
void Transform::print(Transform const &t, std::string prefix) {
LOG(ERROR) << prefix << "[ " << t.matrix[0] << " " << t.matrix[1] << " "
<< t.matrix[2] << " " << t.matrix[3] << " ]";
LOG(ERROR) << prefix << "[ " << t.matrix[4] << " " << t.matrix[5] << " "
<< t.matrix[6] << " " << t.matrix[7] << " ]";
LOG(ERROR) << prefix << "[ " << t.matrix[8] << " " << t.matrix[9] << " "
<< t.matrix[10] << " " << t.matrix[11] << " ]";
LOG(ERROR) << prefix << "[ " << t.matrix[12] << " " << t.matrix[13] << " "
<< t.matrix[14] << " " << t.matrix[15] << " ]";
}
#endif
Transform Transform::Identity() {
return {};
}
Transform Transform::Perspective(Float perspective) {
auto transform = Transform{};
transform.matrix[11] = -1 / perspective;
transform.matrix[11] = -1.0 / perspective;
return transform;
}
@@ -102,130 +86,16 @@ Transform Transform::Rotate(Float x, Float y, Float z) {
return transform;
}
Transform::SRT Transform::ExtractSRT(Transform const &t) {
// First we need to extract translation, rotation, and scale from both
// matrices, in that order. Matrices must be in this form: [a b c d] [e f g h]
// [i j k l]
// [0 0 0 1]
// We also assume that all scale factors are non-negative, because in
assert(
t.matrix[12] == 0 && t.matrix[13] == 0 && t.matrix[14] == 0 &&
t.matrix[15] == 1 && "Last row of matrix must be [0,0,0,1]");
// lhs:
// Translation: extract the values from the rightmost column
Float translationX = t.matrix[3];
Float translationY = t.matrix[7];
Float translationZ = t.matrix[11];
// Scale: the length of the first three column vectors
// TODO: do we need to do anything special for negative scale factors?
// the last element is a uniform scale factor
Float scaleX = t.matrix[15] *
sqrt(pow(t.matrix[0], 2) + pow(t.matrix[4], 2) +
pow(t.matrix[8], 2)); // sqrt(a^2 + e^2 + i^2)
Float scaleY = t.matrix[15] *
sqrt(pow(t.matrix[1], 2) + pow(t.matrix[5], 2) +
pow(t.matrix[9], 2)); // sqrt(b^2 + f^2 + j^2)
Float scaleZ = t.matrix[15] *
sqrt(pow(t.matrix[2], 2) + pow(t.matrix[6], 2) +
pow(t.matrix[10], 2)); // sqrt(c^2 + g^2 + k^2)
Float rScaleFactorX = scaleX == 0 ? 1 : scaleX;
Float rScaleFactorY = scaleY == 0 ? 1 : scaleY;
Float rScaleFactorZ = scaleZ == 0 ? 1 : scaleZ;
// Construct a rotation matrix and convert that to quaternions
auto rotationMatrix = std::array<Float, 16>{t.matrix[0] / rScaleFactorX,
t.matrix[1] / rScaleFactorY,
t.matrix[2] / rScaleFactorZ,
0,
t.matrix[4] / rScaleFactorX,
t.matrix[5] / rScaleFactorY,
t.matrix[6] / rScaleFactorZ,
0,
t.matrix[8] / rScaleFactorX,
t.matrix[9] / rScaleFactorY,
t.matrix[10] / rScaleFactorZ,
0,
0,
0,
0,
1};
Quaternion<Float> q =
Quaternion<Float>::fromRotationMatrix(rotationMatrix).normalize();
return Transform::SRT{
translationX, translationY, translationZ, scaleX, scaleY, scaleZ, q};
}
Transform Transform::Interpolate(
float animationProgress,
Transform const &lhs,
Transform const &rhs) {
// Extract SRT for both sides
// This is extracted in the form: X,Y,Z coordinates for translations; X,Y,Z
// coordinates for scale; and a quaternion for rotation.
auto lhsSRT = ExtractSRT(lhs);
auto rhsSRT = ExtractSRT(rhs);
// Interpolate translation and scale terms linearly (LERP)
Float translateX =
(lhsSRT.translationX +
(rhsSRT.translationX - lhsSRT.translationX) * animationProgress);
Float translateY =
(lhsSRT.translationY +
(rhsSRT.translationY - lhsSRT.translationY) * animationProgress);
Float translateZ =
(lhsSRT.translationZ +
(rhsSRT.translationZ - lhsSRT.translationZ) * animationProgress);
Float scaleX =
(lhsSRT.scaleX + (rhsSRT.scaleX - lhsSRT.scaleX) * animationProgress);
Float scaleY =
(lhsSRT.scaleY + (rhsSRT.scaleY - lhsSRT.scaleY) * animationProgress);
Float scaleZ =
(lhsSRT.scaleZ + (rhsSRT.scaleZ - lhsSRT.scaleZ) * animationProgress);
// Use the quaternion vectors to produce an interpolated rotation via SLERP
// dot: cos of the angle between the two quaternion vectors
Quaternion<Float> q1 = lhsSRT.rotation;
Quaternion<Float> q2 = rhsSRT.rotation;
Float dot = q1.dot(q2);
// Clamp dot between -1 and 1
dot = (dot < -1 ? -1 : (dot > 1 ? 1 : dot));
// There are two ways of performing an identical slerp: q1 and -q1.
// If the dot-product is negative, we can multiply q1 by -1 and our animation
// will take the "short way" around instead of the "long way".
if (dot < 0) {
q1 = q1 * (Float)-1;
dot = dot * -1;
auto result = Transform{};
for (size_t i = 0; i < 16; i++) {
result.matrix[i] =
lhs.matrix[i] + (rhs.matrix[i] - lhs.matrix[i]) * animationProgress;
}
// Interpolated angle
Float theta = acosf(dot) * animationProgress;
Transform rotation = Transform::Identity();
// Compute orthonormal basis
Quaternion<Float> orthonormalBasis = (q2 - q1 * dot);
if (orthonormalBasis.abs() > 0) {
Quaternion<Float> orthonormalBasisNormalized = orthonormalBasis.normalize();
// Compute orthonormal basis
// Final quaternion result - slerp!
Quaternion<Float> resultingRotationVec =
(q1 * (Float)cos(theta) +
orthonormalBasisNormalized * (Float)sin(theta))
.normalize();
// Convert quaternion to matrix
rotation.matrix = resultingRotationVec.toRotationMatrix4x4();
}
// Compose matrices and return
return (Scale(scaleX, scaleY, scaleZ) * rotation) *
Translate(translateX, translateY, translateZ);
return result;
}
bool Transform::operator==(Transform const &rhs) const {
+4 -43
View File
@@ -12,7 +12,6 @@
#include <folly/Hash.h>
#include <react/graphics/Float.h>
#include <react/graphics/Geometry.h>
#include <react/graphics/Quaternion.h>
#ifdef ANDROID
#include <folly/dynamic.h>
@@ -21,32 +20,13 @@
namespace facebook {
namespace react {
struct ScaleRotationTranslation {
Float translationX;
Float translationY;
Float translationZ;
Float scaleX;
Float scaleY;
Float scaleZ;
Quaternion<Float> rotation;
};
/*
* Defines transform matrix to apply affine transformations.
*/
struct Transform {
using SRT = ScaleRotationTranslation;
std::array<Float, 16> matrix{
{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}};
/**
* For debugging only. Prints out the matrix.
*/
#ifdef RN_DEBUG_STRING_CONVERTIBLE
static void print(Transform const &t, std::string prefix);
#endif
/*
* Returns the identity transform (`[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]`).
*/
@@ -81,29 +61,10 @@ struct Transform {
static Transform Rotate(Float angleX, Float angleY, Float angleZ);
/**
* Extract SRT (scale, rotation, transformation) from a Transform matrix.
*
* CAVEATS:
* 1. The input matrix must not have Skew applied.
* 2. Scaling factors must be non-negative. Scaling by a negative factor is
* equivalent to a rotation, and though it is possible to detect if 1 or
* 3 of the scale signs are flipped (but not two), it is not possible
* to detect WHICH of the scales are flipped. Thus, any animation
* that involves a negative scale factor will not crash but will
* interpolate over nonsensical values.
* 3. Another caveat is that if the animation interpolates TO a 90º
* rotation in the X, Y, or Z axis, the View will appear to suddenly
* explode in size. Interpolating THROUGH 90º is fine as long as you don't end
* up at 90º or close to it (89.99). The same is true for 0±90 and 360n+90,
* etc.
*/
static SRT ExtractSRT(Transform const &transform);
/**
* Perform an interpolation between lhs and rhs, given progress.
* This first decomposes the matrices into translation, scale, and rotation,
* performs slerp between the two rotations, and a linear interpolation
* of scale and translation.
* Perform a simple interpolation between lhs and rhs, given "progress"
* between the two assuming that we are "moving" from lhs to rhs. This is a
* simple linear interpolation between each matrix index and will only work
* for simple scaling or translation; this will not work for rotation.
*
* @param progress
* @param lhs