/* * 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 "flutter/sky/engine/platform/transforms/TransformOperations.h" #include #include "flutter/sky/engine/platform/animation/AnimationUtilities.h" #include "flutter/sky/engine/platform/geometry/FloatBox.h" #include "flutter/sky/engine/platform/transforms/IdentityTransformOperation.h" #include "flutter/sky/engine/platform/transforms/InterpolatedTransformOperation.h" #include "flutter/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 fromOperation = (i < fromSize) ? from.operations()[i].get() : 0; RefPtr toOperation = (i < toSize) ? operations()[i].get() : 0; RefPtr blendedOperation = toOperation ? toOperation->blend(fromOperation.get(), progress) : (fromOperation ? fromOperation->blend(0, progress, true) : nullptr); if (blendedOperation) result.operations().append(blendedOperation); else { RefPtr 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 fromOperation = (i < fromSize) ? from.operations()[i] : nullptr; RefPtr 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 fromTransform; RefPtr 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 identityRotation; const RotateTransformOperation* fromRotation = nullptr; const RotateTransformOperation* toRotation = nullptr; if (fromOperation) { fromRotation = static_cast(fromOperation.get()); if (fromRotation->axis().isZero()) fromRotation = nullptr; } if (toOperation) { toRotation = static_cast(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