| /* | 
 |  * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) | 
 |  * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Library General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2 of the License, or (at your option) any later version. | 
 |  * | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 |  * Library General Public License for more details. | 
 |  * | 
 |  * You should have received a copy of the GNU Library General Public License | 
 |  * along with this library; see the file COPYING.LIB.  If not, write to | 
 |  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 
 |  * Boston, MA 02110-1301, USA. | 
 |  * | 
 |  */ | 
 |  | 
 | #include "sky/engine/config.h" | 
 | #include "sky/engine/platform/transforms/TransformOperations.h" | 
 |  | 
 | #include <algorithm> | 
 | #include "sky/engine/platform/animation/AnimationUtilities.h" | 
 | #include "sky/engine/platform/geometry/FloatBox.h" | 
 | #include "sky/engine/platform/transforms/IdentityTransformOperation.h" | 
 | #include "sky/engine/platform/transforms/InterpolatedTransformOperation.h" | 
 | #include "sky/engine/platform/transforms/RotateTransformOperation.h" | 
 |  | 
 | namespace blink { | 
 |  | 
 | TransformOperations::TransformOperations(bool makeIdentity) | 
 | { | 
 |     if (makeIdentity) | 
 |         m_operations.append(IdentityTransformOperation::create()); | 
 | } | 
 |  | 
 | bool TransformOperations::operator==(const TransformOperations& o) const | 
 | { | 
 |     if (m_operations.size() != o.m_operations.size()) | 
 |         return false; | 
 |  | 
 |     unsigned s = m_operations.size(); | 
 |     for (unsigned i = 0; i < s; i++) { | 
 |         if (*m_operations[i] != *o.m_operations[i]) | 
 |             return false; | 
 |     } | 
 |  | 
 |     return true; | 
 | } | 
 |  | 
 | bool TransformOperations::operationsMatch(const TransformOperations& other) const | 
 | { | 
 |     size_t numOperations = operations().size(); | 
 |     // If the sizes of the function lists don't match, the lists don't match | 
 |     if (numOperations != other.operations().size()) | 
 |         return false; | 
 |  | 
 |     // If the types of each function are not the same, the lists don't match | 
 |     for (size_t i = 0; i < numOperations; ++i) { | 
 |         if (!operations()[i]->isSameType(*other.operations()[i])) | 
 |             return false; | 
 |     } | 
 |     return true; | 
 | } | 
 |  | 
 | TransformOperations TransformOperations::blendByMatchingOperations(const TransformOperations& from, const double& progress) const | 
 | { | 
 |     TransformOperations result; | 
 |  | 
 |     unsigned fromSize = from.operations().size(); | 
 |     unsigned toSize = operations().size(); | 
 |     unsigned size = std::max(fromSize, toSize); | 
 |     for (unsigned i = 0; i < size; i++) { | 
 |         RefPtr<TransformOperation> fromOperation = (i < fromSize) ? from.operations()[i].get() : 0; | 
 |         RefPtr<TransformOperation> toOperation = (i < toSize) ? operations()[i].get() : 0; | 
 |         RefPtr<TransformOperation> blendedOperation = toOperation ? toOperation->blend(fromOperation.get(), progress) : (fromOperation ? fromOperation->blend(0, progress, true) : nullptr); | 
 |         if (blendedOperation) | 
 |             result.operations().append(blendedOperation); | 
 |         else { | 
 |             RefPtr<TransformOperation> identityOperation = IdentityTransformOperation::create(); | 
 |             if (progress > 0.5) | 
 |                 result.operations().append(toOperation ? toOperation : identityOperation); | 
 |             else | 
 |                 result.operations().append(fromOperation ? fromOperation : identityOperation); | 
 |         } | 
 |     } | 
 |  | 
 |     return result; | 
 | } | 
 |  | 
 | TransformOperations TransformOperations::blendByUsingMatrixInterpolation(const TransformOperations& from, double progress) const | 
 | { | 
 |     TransformOperations result; | 
 |     result.operations().append(InterpolatedTransformOperation::create(from, *this, progress)); | 
 |     return result; | 
 | } | 
 |  | 
 | TransformOperations TransformOperations::blend(const TransformOperations& from, double progress) const | 
 | { | 
 |     if (from == *this || (!from.size() && !size())) | 
 |         return *this; | 
 |  | 
 |     // If either list is empty, use blendByMatchingOperations which has special logic for this case. | 
 |     if (!from.size() || !size() || from.operationsMatch(*this)) | 
 |         return blendByMatchingOperations(from, progress); | 
 |  | 
 |     return blendByUsingMatrixInterpolation(from, progress); | 
 | } | 
 |  | 
 | static void findCandidatesInPlane(double px, double py, double nz, double* candidates, int* numCandidates) | 
 | { | 
 |     // The angle that this point is rotated with respect to the plane nz | 
 |     double phi = atan2(px, py); | 
 |  | 
 |     *numCandidates = 4; | 
 |     candidates[0] = phi; // The element at 0deg (maximum x) | 
 |  | 
 |     for (int i = 1; i < *numCandidates; ++i) | 
 |         candidates[i] = candidates[i - 1] + M_PI_2; // every 90 deg | 
 |     if (nz < 0.f) { | 
 |         for (int i = 0; i < *numCandidates; ++i) | 
 |             candidates[i] *= -1; | 
 |     } | 
 | } | 
 |  | 
 | // This method returns the bounding box that contains the starting point, | 
 | // the ending point, and any of the extrema (in each dimension) found across | 
 | // the circle described by the arc. These are then filtered to points that | 
 | // actually reside on the arc. | 
 | static void boundingBoxForArc(const FloatPoint3D& point, const RotateTransformOperation& fromTransform, const RotateTransformOperation& toTransform, double minProgress, double maxProgress, FloatBox& box) | 
 | { | 
 |     double candidates[6]; | 
 |     int numCandidates = 0; | 
 |  | 
 |     FloatPoint3D axis(fromTransform.axis()); | 
 |     double fromDegrees = fromTransform.angle(); | 
 |     double toDegrees = toTransform.angle(); | 
 |  | 
 |     if (axis.dot(toTransform.axis()) < 0) | 
 |         toDegrees *= -1; | 
 |  | 
 |     fromDegrees  = blend(fromDegrees, toTransform.angle(), minProgress); | 
 |     toDegrees = blend(toDegrees, fromTransform.angle(), 1.0 - maxProgress); | 
 |     if (fromDegrees > toDegrees) | 
 |         std::swap(fromDegrees, toDegrees); | 
 |  | 
 |     TransformationMatrix fromMatrix; | 
 |     TransformationMatrix toMatrix; | 
 |     fromMatrix.rotate3d(fromTransform.x(), fromTransform.y(), fromTransform.z(), fromDegrees); | 
 |     toMatrix.rotate3d(fromTransform.x(), fromTransform.y(), fromTransform.z(), toDegrees); | 
 |  | 
 |     FloatPoint3D fromPoint = fromMatrix.mapPoint(point); | 
 |     FloatPoint3D toPoint = toMatrix.mapPoint(point); | 
 |  | 
 |     if (box.isEmpty()) | 
 |         box.setOrigin(fromPoint); | 
 |     else | 
 |         box.expandTo(fromPoint); | 
 |  | 
 |     box.expandTo(toPoint); | 
 |  | 
 |     switch (fromTransform.type()) { | 
 |     case TransformOperation::RotateX: | 
 |         findCandidatesInPlane(point.y(), point.z(), fromTransform.x(), candidates, &numCandidates); | 
 |         break; | 
 |     case TransformOperation::RotateY: | 
 |         findCandidatesInPlane(point.z(), point.x(), fromTransform.y(), candidates, &numCandidates); | 
 |         break; | 
 |     case TransformOperation::RotateZ: | 
 |         findCandidatesInPlane(point.x(), point.y(), fromTransform.z(), candidates, &numCandidates); | 
 |         break; | 
 |     default: | 
 |         { | 
 |             FloatPoint3D normal = axis; | 
 |             if (normal.isZero()) | 
 |                 return; | 
 |             normal.normalize(); | 
 |             FloatPoint3D origin; | 
 |             FloatPoint3D toPoint = point - origin; | 
 |             FloatPoint3D center = origin + normal * toPoint.dot(normal); | 
 |             FloatPoint3D v1 = point - center; | 
 |             if (v1.isZero()) | 
 |                 return; | 
 |  | 
 |             v1.normalize(); | 
 |             FloatPoint3D v2 = normal.cross(v1); | 
 |             // v1 is the basis vector in the direction of the point. | 
 |             // i.e. with a rotation of 0, v1 is our +x vector. | 
 |             // v2 is a perpenticular basis vector of our plane (+y). | 
 |  | 
 |             // Take the parametric equation of a circle. | 
 |             // (x = r*cos(t); y = r*sin(t); | 
 |             // We can treat that as a circle on the plane v1xv2 | 
 |             // From that we get the parametric equations for a circle on the | 
 |             // plane in 3d space of | 
 |             // x(t) = r*cos(t)*v1.x + r*sin(t)*v2.x + cx | 
 |             // y(t) = r*cos(t)*v1.y + r*sin(t)*v2.y + cy | 
 |             // z(t) = r*cos(t)*v1.z + r*sin(t)*v2.z + cz | 
 |             // taking the derivative of (x, y, z) and solving for 0 gives us our | 
 |             // maximum/minimum x, y, z values | 
 |             // x'(t) = r*cos(t)*v2.x - r*sin(t)*v1.x = 0 | 
 |             // tan(t) = v2.x/v1.x | 
 |             // t = atan2(v2.x, v1.x) + n*M_PI; | 
 |  | 
 |             candidates[0] = atan2(v2.x(), v1.x()); | 
 |             candidates[1] = candidates[0] + M_PI; | 
 |             candidates[2] = atan2(v2.y(), v1.y()); | 
 |             candidates[3] = candidates[2] + M_PI; | 
 |             candidates[4] = atan2(v2.z(), v1.z()); | 
 |             candidates[5] = candidates[4] + M_PI; | 
 |             numCandidates = 6; | 
 |         } | 
 |         break; | 
 |     } | 
 |  | 
 |     double minRadians = deg2rad(fromDegrees); | 
 |     double maxRadians = deg2rad(toDegrees); | 
 |     // Once we have the candidates, we now filter them down to ones that | 
 |     // actually live on the arc, rather than the entire circle. | 
 |     for (int i = 0; i < numCandidates; ++i) { | 
 |         double radians = candidates[i]; | 
 |  | 
 |         while (radians < minRadians) | 
 |             radians += 2.0 * M_PI; | 
 |         while (radians > maxRadians) | 
 |             radians -= 2.0 * M_PI; | 
 |         if (radians < minRadians) | 
 |             continue; | 
 |  | 
 |         TransformationMatrix rotation; | 
 |         rotation.rotate3d(axis.x(), axis.y(), axis.z(), rad2deg(radians)); | 
 |         box.expandTo(rotation.mapPoint(point)); | 
 |     } | 
 | } | 
 |  | 
 | bool TransformOperations::blendedBoundsForBox(const FloatBox& box, const TransformOperations& from, const double& minProgress, const double& maxProgress, FloatBox* bounds) const | 
 | { | 
 |  | 
 |     int fromSize = from.operations().size(); | 
 |     int toSize = operations().size(); | 
 |     int size = std::max(fromSize, toSize); | 
 |  | 
 |     *bounds = box; | 
 |     for (int i = size - 1; i >= 0; i--) { | 
 |         RefPtr<TransformOperation> fromOperation = (i < fromSize) ? from.operations()[i] : nullptr; | 
 |         RefPtr<TransformOperation> toOperation = (i < toSize) ? operations()[i] : nullptr; | 
 |         if (fromOperation && fromOperation->type() == TransformOperation::None) | 
 |             fromOperation = nullptr; | 
 |  | 
 |         if (toOperation && toOperation->type() == TransformOperation::None) | 
 |             toOperation = nullptr; | 
 |  | 
 |         TransformOperation::OperationType interpolationType = toOperation ? toOperation->type() : | 
 |             fromOperation ? fromOperation->type() : | 
 |             TransformOperation::None; | 
 |         if (fromOperation && toOperation && !fromOperation->canBlendWith(*toOperation.get())) | 
 |             return false; | 
 |  | 
 |         switch (interpolationType) { | 
 |         case TransformOperation::Identity: | 
 |             bounds->expandTo(box); | 
 |             continue; | 
 |         case TransformOperation::Translate: | 
 |         case TransformOperation::TranslateX: | 
 |         case TransformOperation::TranslateY: | 
 |         case TransformOperation::TranslateZ: | 
 |         case TransformOperation::Translate3D: | 
 |         case TransformOperation::Scale: | 
 |         case TransformOperation::ScaleX: | 
 |         case TransformOperation::ScaleY: | 
 |         case TransformOperation::ScaleZ: | 
 |         case TransformOperation::Scale3D: | 
 |         case TransformOperation::Skew: | 
 |         case TransformOperation::SkewX: | 
 |         case TransformOperation::SkewY: | 
 |         case TransformOperation::Perspective: | 
 |             { | 
 |                 RefPtr<TransformOperation> fromTransform; | 
 |                 RefPtr<TransformOperation> toTransform; | 
 |                 if (!toOperation) { | 
 |                     fromTransform = fromOperation->blend(toOperation.get(), 1-minProgress, false); | 
 |                     toTransform = fromOperation->blend(toOperation.get(), 1-maxProgress, false); | 
 |                 } else { | 
 |                     fromTransform = toOperation->blend(fromOperation.get(), minProgress, false); | 
 |                     toTransform = toOperation->blend(fromOperation.get(), maxProgress, false); | 
 |                 } | 
 |                 if (!fromTransform || !toTransform) | 
 |                     continue; | 
 |                 TransformationMatrix fromMatrix; | 
 |                 TransformationMatrix toMatrix; | 
 |                 fromTransform->apply(fromMatrix, FloatSize()); | 
 |                 toTransform->apply(toMatrix, FloatSize()); | 
 |                 FloatBox fromBox = *bounds; | 
 |                 FloatBox toBox = *bounds; | 
 |                 fromMatrix.transformBox(fromBox); | 
 |                 toMatrix.transformBox(toBox); | 
 |                 *bounds = fromBox; | 
 |                 bounds->expandTo(toBox); | 
 |                 continue; | 
 |             } | 
 |         case TransformOperation::Rotate: // This is also RotateZ | 
 |         case TransformOperation::Rotate3D: | 
 |         case TransformOperation::RotateX: | 
 |         case TransformOperation::RotateY: | 
 |             { | 
 |                 RefPtr<RotateTransformOperation> identityRotation; | 
 |                 const RotateTransformOperation* fromRotation = nullptr; | 
 |                 const RotateTransformOperation* toRotation = nullptr; | 
 |                 if (fromOperation) { | 
 |                     fromRotation = static_cast<const RotateTransformOperation*>(fromOperation.get()); | 
 |                     if (fromRotation->axis().isZero()) | 
 |                         fromRotation = nullptr; | 
 |                 } | 
 |  | 
 |                 if (toOperation) { | 
 |                     toRotation = static_cast<const RotateTransformOperation*>(toOperation.get()); | 
 |                     if (toRotation->axis().isZero()) | 
 |                         toRotation = nullptr; | 
 |                 } | 
 |  | 
 |                 double fromAngle; | 
 |                 double toAngle; | 
 |                 FloatPoint3D axis; | 
 |                 if (!RotateTransformOperation::shareSameAxis(fromRotation, toRotation, &axis, &fromAngle, &toAngle)) { | 
 |                     return(false); | 
 |                 } | 
 |  | 
 |                 if (!fromRotation) { | 
 |                     identityRotation = RotateTransformOperation::create(axis.x(), axis.y(), axis.z(), 0, fromOperation ? fromOperation->type() : toOperation->type()); | 
 |                     fromRotation = identityRotation.get(); | 
 |                 } | 
 |  | 
 |                 if (!toRotation) { | 
 |                     if (!identityRotation) | 
 |                         identityRotation = RotateTransformOperation::create(axis.x(), axis.y(), axis.z(), 0, fromOperation ? fromOperation->type() : toOperation->type()); | 
 |                     toRotation = identityRotation.get(); | 
 |                 } | 
 |  | 
 |                 FloatBox fromBox = *bounds; | 
 |                 bool first = true; | 
 |                 for (size_t i = 0; i < 2; ++i) { | 
 |                     for (size_t j = 0; j < 2; ++j) { | 
 |                         for (size_t k = 0; k < 2; ++k) { | 
 |                             FloatBox boundsForArc; | 
 |                             FloatPoint3D corner(fromBox.x(), fromBox.y(), fromBox.z()); | 
 |                             corner += FloatPoint3D(i * fromBox.width(), j * fromBox.height(), k * fromBox.depth()); | 
 |                             boundingBoxForArc(corner, *fromRotation, *toRotation, minProgress, maxProgress, boundsForArc); | 
 |                             if (first) { | 
 |                                 *bounds = boundsForArc; | 
 |                                 first = false; | 
 |                             } else { | 
 |                                 bounds->expandTo(boundsForArc); | 
 |                             } | 
 |                         } | 
 |                     } | 
 |                 } | 
 |             } | 
 |             continue; | 
 |         case TransformOperation::None: | 
 |             continue; | 
 |         case TransformOperation::Matrix: | 
 |         case TransformOperation::Matrix3D: | 
 |         case TransformOperation::Interpolated: | 
 |             return(false); | 
 |         } | 
 |     } | 
 |  | 
 |     return true; | 
 | } | 
 |  | 
 | TransformOperations TransformOperations::add(const TransformOperations& addend) const | 
 | { | 
 |     TransformOperations result; | 
 |     result.m_operations = operations(); | 
 |     result.m_operations.appendVector(addend.operations()); | 
 |     return result; | 
 | } | 
 |  | 
 | } // namespace blink |