diff --git a/ReactCommon/fabric/components/view/conversions.h b/ReactCommon/fabric/components/view/conversions.h index 0ac41614dd0..5a3cb1a8cc2 100644 --- a/ReactCommon/fabric/components/view/conversions.h +++ b/ReactCommon/fabric/components/view/conversions.h @@ -422,6 +422,8 @@ inline void fromRawValue(const RawValue &value, Transform &result) { for (auto number : numbers) { transformMatrix.matrix[i++] = number; } + transformMatrix.operations.push_back( + TransformOperation{TransformOperationType::Arbitrary, 0, 0, 0}); } else if (operation == "perspective") { transformMatrix = transformMatrix * Transform::Perspective((Float)parameters); diff --git a/ReactCommon/fabric/graphics/Quaternion.h b/ReactCommon/fabric/graphics/Quaternion.h deleted file mode 100644 index 4233a2c6cb4..00000000000 --- a/ReactCommon/fabric/graphics/Quaternion.h +++ /dev/null @@ -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 -#include -#include - -// 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 -class Quaternion { - public: - /** - * Copy constructor. - */ - Quaternion(const Quaternion &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 fromRotationMatrix(std::array 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 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 normalize() const { - assert(abs() > 0); // or this is not normalizable - T factor = abs(); - return *this / (factor != 0 ? factor : 1); - } - - inline T dot(const Quaternion &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 operator/=(T y) { - a_ /= y; - b_ /= y; - c_ /= y; - d_ /= y; - return *this; - } - - inline Quaternion operator*=(T y) { - a_ *= y; - b_ *= y; - c_ *= y; - d_ *= y; - return *this; - } - - inline Quaternion operator+=(Quaternion const &other) { - a_ += other.a_; - b_ += other.b_; - c_ += other.c_; - d_ += other.d_; - return *this; - } - - inline Quaternion operator-=(Quaternion 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 -inline Quaternion operator/(Quaternion const &lhs, T rhs) { - return Quaternion(lhs) /= rhs; -} - -template -inline Quaternion operator*(Quaternion const &lhs, T rhs) { - return Quaternion(lhs) *= rhs; -} - -template -inline Quaternion operator+( - Quaternion const &lhs, - Quaternion const &rhs) { - return Quaternion(lhs) += rhs; -} - -template -inline Quaternion operator-( - Quaternion const &lhs, - Quaternion const &rhs) { - return Quaternion(lhs) -= rhs; -} - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/graphics/Transform.cpp b/ReactCommon/fabric/graphics/Transform.cpp index f1711f0418f..6caea8e9749 100644 --- a/ReactCommon/fabric/graphics/Transform.cpp +++ b/ReactCommon/fabric/graphics/Transform.cpp @@ -7,7 +7,6 @@ #include "Transform.h" -#include #include #include @@ -34,203 +33,211 @@ Transform Transform::Identity() { Transform Transform::Perspective(Float perspective) { auto transform = Transform{}; + transform.operations.push_back(TransformOperation{ + TransformOperationType::Perspective, perspective, 0, 0}); transform.matrix[11] = -1 / perspective; return transform; } -Transform Transform::Scale(Float factorX, Float factorY, Float factorZ) { +Transform Transform::Scale(Float x, Float y, Float z) { auto transform = Transform{}; - transform.matrix[0] = factorX; - transform.matrix[5] = factorY; - transform.matrix[10] = factorZ; + Float xprime = isZero(x) ? 0 : x; + Float yprime = isZero(y) ? 0 : y; + Float zprime = isZero(z) ? 0 : z; + if (xprime != 1 || yprime != 1 || zprime != 1) { + transform.operations.push_back(TransformOperation{ + TransformOperationType::Scale, xprime, yprime, zprime}); + transform.matrix[0] = xprime; + transform.matrix[5] = yprime; + transform.matrix[10] = zprime; + } return transform; } Transform Transform::Translate(Float x, Float y, Float z) { auto transform = Transform{}; - transform.matrix[12] = x; - transform.matrix[13] = y; - transform.matrix[14] = z; + Float xprime = isZero(x) ? 0 : x; + Float yprime = isZero(y) ? 0 : y; + Float zprime = isZero(z) ? 0 : z; + if (xprime != 0 || yprime != 0 || zprime != 0) { + transform.operations.push_back(TransformOperation{ + TransformOperationType::Translate, xprime, yprime, zprime}); + transform.matrix[12] = xprime; + transform.matrix[13] = yprime; + transform.matrix[14] = zprime; + } return transform; } Transform Transform::Skew(Float x, Float y) { auto transform = Transform{}; - transform.matrix[4] = std::tan(x); - transform.matrix[1] = std::tan(y); + Float xprime = isZero(x) ? 0 : x; + Float yprime = isZero(y) ? 0 : y; + transform.operations.push_back( + TransformOperation{TransformOperationType::Skew, xprime, yprime, 0}); + transform.matrix[4] = std::tan(xprime); + transform.matrix[1] = std::tan(yprime); return transform; } Transform Transform::RotateX(Float radians) { auto transform = Transform{}; - transform.matrix[5] = std::cos(radians); - transform.matrix[6] = std::sin(radians); - transform.matrix[9] = -std::sin(radians); - transform.matrix[10] = std::cos(radians); + if (!isZero(radians)) { + transform.operations.push_back( + TransformOperation{TransformOperationType::Rotate, radians, 0, 0}); + transform.matrix[5] = std::cos(radians); + transform.matrix[6] = std::sin(radians); + transform.matrix[9] = -std::sin(radians); + transform.matrix[10] = std::cos(radians); + } return transform; } Transform Transform::RotateY(Float radians) { auto transform = Transform{}; - transform.matrix[0] = std::cos(radians); - transform.matrix[2] = -std::sin(radians); - transform.matrix[8] = std::sin(radians); - transform.matrix[10] = std::cos(radians); + if (!isZero(radians)) { + transform.operations.push_back( + TransformOperation{TransformOperationType::Rotate, 0, radians, 0}); + transform.matrix[0] = std::cos(radians); + transform.matrix[2] = -std::sin(radians); + transform.matrix[8] = std::sin(radians); + transform.matrix[10] = std::cos(radians); + } return transform; } Transform Transform::RotateZ(Float radians) { auto transform = Transform{}; - transform.matrix[0] = std::cos(radians); - transform.matrix[1] = std::sin(radians); - transform.matrix[4] = -std::sin(radians); - transform.matrix[5] = std::cos(radians); + if (!isZero(radians)) { + transform.operations.push_back( + TransformOperation{TransformOperationType::Rotate, 0, 0, radians}); + transform.matrix[0] = std::cos(radians); + transform.matrix[1] = std::sin(radians); + transform.matrix[4] = -std::sin(radians); + transform.matrix[5] = std::cos(radians); + } return transform; } Transform Transform::Rotate(Float x, Float y, Float z) { auto transform = Transform{}; - if (x != 0) { + transform.operations.push_back( + TransformOperation{TransformOperationType::Rotate, x, y, z}); + if (!isZero(x)) { transform = transform * Transform::RotateX(x); } - if (y != 0) { + if (!isZero(y)) { transform = transform * Transform::RotateY(y); } - if (z != 0) { + if (!isZero(z)) { transform = transform * Transform::RotateZ(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. - // TODO T68587989: If ViewProps retains the underlying transform props instead - // of just the matrix version of transforms, then we can use those properties - // directly instead of decomposing properties from a matrix which will always - // be lossy. Because of these assumptions, animations involving negative - // scale/rotation and anything involving skews will not look great. - // 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]"); +Transform Transform::FromTransformOperation( + TransformOperation transformOperation) { + if (transformOperation.type == TransformOperationType::Perspective) { + return Transform::Perspective(transformOperation.x); + } + if (transformOperation.type == TransformOperationType::Scale) { + return Transform::Scale( + transformOperation.x, transformOperation.y, transformOperation.z); + } + if (transformOperation.type == TransformOperationType::Translate) { + return Transform::Translate( + transformOperation.x, transformOperation.y, transformOperation.z); + } + if (transformOperation.type == TransformOperationType::Skew) { + return Transform::Skew(transformOperation.x, transformOperation.y); + } + if (transformOperation.type == TransformOperationType::Rotate) { + return Transform::Rotate( + transformOperation.x, transformOperation.y, transformOperation.z); + } - // lhs: - // Translation: extract the values from the rightmost column - Float translationX = t.matrix[3]; - Float translationY = t.matrix[7]; - Float translationZ = t.matrix[11]; + // Identity or Arbitrary + return Transform::Identity(); +} - // 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{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 q = - Quaternion::fromRotationMatrix(rotationMatrix).normalize(); - - return Transform::SRT{ - translationX, translationY, translationZ, scaleX, scaleY, scaleZ, q}; +TransformOperation Transform::DefaultTransformOperation( + TransformOperationType type) { + switch (type) { + case TransformOperationType::Arbitrary: + return TransformOperation{TransformOperationType::Arbitrary, 0, 0, 0}; + case TransformOperationType::Perspective: + return TransformOperation{TransformOperationType::Perspective, 0, 0, 0}; + case TransformOperationType::Scale: + return TransformOperation{TransformOperationType::Scale, 1, 1, 1}; + case TransformOperationType::Translate: + return TransformOperation{TransformOperationType::Translate, 0, 0, 0}; + case TransformOperationType::Rotate: + return TransformOperation{TransformOperationType::Rotate, 0, 0, 0}; + case TransformOperationType::Skew: + return TransformOperation{TransformOperationType::Skew, 0, 0, 0}; + default: + case TransformOperationType::Identity: + return TransformOperation{TransformOperationType::Identity, 0, 0, 0}; + } } 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); + // Iterate through operations and reconstruct an interpolated resulting + // transform If at any point we hit an "Arbitrary" Transform, return at that + // point + Transform result = Transform::Identity(); + for (int i = 0, j = 0; + i < lhs.operations.size() || j < rhs.operations.size();) { + bool haveLHS = i < lhs.operations.size(); + bool haveRHS = j < rhs.operations.size(); - // 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); + if ((haveLHS && + lhs.operations[i].type == TransformOperationType::Arbitrary) || + (haveRHS && + rhs.operations[i].type == TransformOperationType::Arbitrary)) { + return result; + } + if (haveLHS && lhs.operations[i].type == TransformOperationType::Identity) { + i++; + continue; + } + if (haveRHS && rhs.operations[j].type == TransformOperationType::Identity) { + j++; + continue; + } - // Use the quaternion vectors to produce an interpolated rotation via SLERP - // dot: cos of the angle between the two quaternion vectors - Quaternion q1 = lhsSRT.rotation; - Quaternion 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; - } - // Interpolated angle - Float theta = acosf(dot) * animationProgress; + // Here we either set: + // 1. lhs = next left op, rhs = next right op (when types are identical and + // both exist) + // 2. lhs = next left op, rhs = default of type (if types unequal, or rhs + // doesn't exist) + // 3. lhs = default of type, rhs = next right op (if types unequal, or rhs + // doesn't exist) This guarantees that the types of both sides are equal, + // and that one or both indices moves forward. + TransformOperationType type = + (haveLHS ? lhs.operations[i] : rhs.operations[j]).type; + TransformOperation lhsOp = + (haveLHS ? lhs.operations[i++] + : Transform::DefaultTransformOperation(type)); + TransformOperation rhsOp = + (haveRHS && rhs.operations[j].type == type + ? rhs.operations[j++] + : Transform::DefaultTransformOperation(type)); + assert(type == lhsOp.type); + assert(type == rhsOp.type); - Transform rotation = Transform::Identity(); - - // Compute orthonormal basis - Quaternion orthonormalBasis = (q2 - q1 * dot); - - if (orthonormalBasis.abs() > 0) { - Quaternion orthonormalBasisNormalized = orthonormalBasis.normalize(); - - // Compute orthonormal basis - // Final quaternion result - slerp! - Quaternion resultingRotationVec = - (q1 * (Float)cos(theta) + - orthonormalBasisNormalized * (Float)sin(theta)) - .normalize(); - - // Convert quaternion to matrix - rotation.matrix = resultingRotationVec.toRotationMatrix4x4(); + result = result * + Transform::FromTransformOperation(TransformOperation{ + type, + lhsOp.x + (rhsOp.x - lhsOp.x) * animationProgress, + lhsOp.y + (rhsOp.y - lhsOp.y) * animationProgress, + lhsOp.z + (rhsOp.z - lhsOp.z) * animationProgress}); } - // Compose matrices and return - return (Scale(scaleX, scaleY, scaleZ) * rotation) * - Translate(translateX, translateY, translateZ); + return result; } bool Transform::operator==(Transform const &rhs) const { @@ -253,6 +260,20 @@ Transform Transform::operator*(Transform const &rhs) const { const auto &lhs = *this; auto result = Transform{}; + for (const auto &op : this->operations) { + if (op.type == TransformOperationType::Identity && + result.operations.size() > 0) { + continue; + } + result.operations.push_back(op); + } + for (const auto &op : rhs.operations) { + if (op.type == TransformOperationType::Identity && + result.operations.size() > 0) { + continue; + } + result.operations.push_back(op); + } auto lhs00 = lhs.matrix[0], lhs01 = lhs.matrix[1], lhs02 = lhs.matrix[2], lhs03 = lhs.matrix[3], lhs10 = lhs.matrix[4], lhs11 = lhs.matrix[5], diff --git a/ReactCommon/fabric/graphics/Transform.h b/ReactCommon/fabric/graphics/Transform.h index 357e2863d7d..7d14a635afb 100644 --- a/ReactCommon/fabric/graphics/Transform.h +++ b/ReactCommon/fabric/graphics/Transform.h @@ -8,11 +8,11 @@ #pragma once #include +#include #include #include #include -#include #ifdef ANDROID #include @@ -21,21 +21,38 @@ namespace facebook { namespace react { -struct ScaleRotationTranslation { - Float translationX; - Float translationY; - Float translationZ; - Float scaleX; - Float scaleY; - Float scaleZ; - Quaternion rotation; +inline bool isZero(Float n) { + // We use this ternary expression instead of abs, fabsf, etc, because + // Float can be double or float depending on compilation target. + return (n < 0 ? n * (-1) : n) < 0.00001; +} + +/** + * Defines operations used to construct a transform matrix. + * An "Arbitrary" operation means that the transform was seeded with some + * arbitrary initial result. + */ +enum class TransformOperationType { + Arbitrary, + Identity, + Perspective, + Scale, + Translate, + Rotate, + Skew +}; +struct TransformOperation { + TransformOperationType type; + Float x; + Float y; + Float z; }; /* * Defines transform matrix to apply affine transformations. */ struct Transform { - using SRT = ScaleRotationTranslation; + std::vector operations{}; std::array matrix{ {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}}; @@ -47,6 +64,14 @@ struct Transform { static void print(Transform const &t, std::string prefix); #endif + /* + * Given a TransformOperation, return the proper transform. + */ + static Transform FromTransformOperation( + TransformOperation transformOperation); + static TransformOperation DefaultTransformOperation( + TransformOperationType type); + /* * Returns the identity transform (`[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]`). */ @@ -80,25 +105,6 @@ struct Transform { static Transform RotateZ(Float angle); 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,