| /* |
| * 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 "third_party/blink/renderer/platform/transforms/transform_operations.h" |
| |
| #include <algorithm> |
| #include "third_party/blink/renderer/platform/geometry/blend.h" |
| #include "third_party/blink/renderer/platform/geometry/float_box.h" |
| #include "third_party/blink/renderer/platform/transforms/identity_transform_operation.h" |
| #include "third_party/blink/renderer/platform/transforms/interpolated_transform_operation.h" |
| #include "third_party/blink/renderer/platform/transforms/matrix_3d_transform_operation.h" |
| #include "third_party/blink/renderer/platform/transforms/rotate_transform_operation.h" |
| |
| namespace blink { |
| |
| TransformOperations::TransformOperations(bool make_identity) { |
| if (make_identity) |
| operations_.push_back(IdentityTransformOperation::Create()); |
| } |
| |
| bool TransformOperations::operator==(const TransformOperations& o) const { |
| if (operations_.size() != o.operations_.size()) |
| return false; |
| |
| wtf_size_t s = operations_.size(); |
| for (wtf_size_t i = 0; i < s; i++) { |
| if (*operations_[i] != *o.operations_[i]) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void TransformOperations::ApplyRemaining(const FloatSize& border_box_size, |
| wtf_size_t start, |
| TransformationMatrix& t) const { |
| for (wtf_size_t i = start; i < operations_.size(); i++) { |
| operations_[i]->Apply(t, border_box_size); |
| } |
| } |
| |
| wtf_size_t TransformOperations::MatchingPrefixLength( |
| const TransformOperations& other) const { |
| wtf_size_t num_operations = |
| std::min(Operations().size(), other.Operations().size()); |
| for (wtf_size_t i = 0; i < num_operations; ++i) { |
| if (Operations()[i]->PrimitiveType() != |
| other.Operations()[i]->PrimitiveType()) { |
| // Remaining operations in each operations list require matrix/matrix3d |
| // interpolation. |
| return i; |
| } |
| } |
| // If the operations match to the length of the shorter list, then pad its |
| // length with the matching identity operations. |
| // https://drafts.csswg.org/css-transforms/#transform-function-lists |
| return std::max(Operations().size(), other.Operations().size()); |
| } |
| |
| TransformOperations TransformOperations::BlendPrefixByMatchingOperations( |
| const TransformOperations& from, |
| wtf_size_t matching_prefix_length, |
| double progress, |
| bool* success) const { |
| TransformOperations result; |
| wtf_size_t from_size = from.Operations().size(); |
| wtf_size_t to_size = Operations().size(); |
| for (wtf_size_t i = 0; i < matching_prefix_length; i++) { |
| scoped_refptr<TransformOperation> from_operation = |
| (i < from_size) ? from.Operations()[i].get() : nullptr; |
| scoped_refptr<TransformOperation> to_operation = |
| (i < to_size) ? Operations()[i].get() : nullptr; |
| |
| scoped_refptr<TransformOperation> blended_operation = |
| to_operation |
| ? to_operation->Blend(from_operation.get(), progress) |
| : (from_operation ? from_operation->Blend(nullptr, progress, true) |
| : nullptr); |
| |
| if (blended_operation) |
| result.Operations().push_back(blended_operation); |
| else { |
| *success = false; |
| return result; |
| } |
| } |
| return result; |
| } |
| |
| scoped_refptr<TransformOperation> |
| TransformOperations::BlendRemainingByUsingMatrixInterpolation( |
| const TransformOperations& from, |
| wtf_size_t matching_prefix_length, |
| double progress) const { |
| // Not safe to use a cached transform if any of the operations are size |
| // dependent. |
| if (DependsOnBoxSize() || from.DependsOnBoxSize()) { |
| return InterpolatedTransformOperation::Create( |
| from, *this, matching_prefix_length, progress); |
| } |
| |
| // Evaluate blended matrix here to avoid creating a nested data structure of |
| // unbounded depth. |
| TransformationMatrix from_transform; |
| TransformationMatrix to_transform; |
| from.ApplyRemaining(FloatSize(), matching_prefix_length, from_transform); |
| ApplyRemaining(FloatSize(), matching_prefix_length, to_transform); |
| |
| // Fallback to discrete interpolation if either transform matrix is singular. |
| if (!(from_transform.IsInvertible() && to_transform.IsInvertible())) { |
| return nullptr; |
| } |
| |
| to_transform.Blend(from_transform, progress); |
| return Matrix3DTransformOperation::Create(to_transform); |
| } |
| |
| // https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms |
| // TODO(crbug.com/914397): Consolidate blink and cc implementations of transform |
| // interpolation. |
| TransformOperations TransformOperations::Blend(const TransformOperations& from, |
| double progress) const { |
| if (from == *this || (!from.size() && !size())) |
| return *this; |
| |
| wtf_size_t matching_prefix_length = MatchingPrefixLength(from); |
| wtf_size_t max_path_length = |
| std::max(Operations().size(), from.Operations().size()); |
| |
| bool success = true; |
| TransformOperations result = BlendPrefixByMatchingOperations( |
| from, matching_prefix_length, progress, &success); |
| if (success && matching_prefix_length < max_path_length) { |
| scoped_refptr<TransformOperation> matrix_op = |
| BlendRemainingByUsingMatrixInterpolation(from, matching_prefix_length, |
| progress); |
| if (matrix_op) |
| result.Operations().push_back(matrix_op); |
| else |
| success = false; |
| } |
| if (!success) { |
| return progress < 0.5 ? from : *this; |
| } |
| return result; |
| } |
| |
| static void FindCandidatesInPlane(double px, |
| double py, |
| double nz, |
| double* candidates, |
| int* num_candidates) { |
| // The angle that this point is rotated with respect to the plane nz |
| double phi = atan2(px, py); |
| |
| *num_candidates = 4; |
| candidates[0] = phi; // The element at 0deg (maximum x) |
| |
| for (int i = 1; i < *num_candidates; ++i) |
| candidates[i] = candidates[i - 1] + M_PI_2; // every 90 deg |
| if (nz < 0.f) { |
| for (int i = 0; i < *num_candidates; ++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& from_transform, |
| const RotateTransformOperation& to_transform, |
| double min_progress, |
| double max_progress, |
| FloatBox& box) { |
| double candidates[6]; |
| int num_candidates = 0; |
| |
| FloatPoint3D axis(from_transform.Axis()); |
| double from_degrees = from_transform.Angle(); |
| double to_degrees = to_transform.Angle(); |
| |
| if (axis.Dot(to_transform.Axis()) < 0) |
| to_degrees *= -1; |
| |
| from_degrees = Blend(from_degrees, to_transform.Angle(), min_progress); |
| to_degrees = Blend(to_degrees, from_transform.Angle(), 1.0 - max_progress); |
| if (from_degrees > to_degrees) |
| std::swap(from_degrees, to_degrees); |
| |
| TransformationMatrix from_matrix; |
| TransformationMatrix to_matrix; |
| from_matrix.Rotate3d(from_transform.X(), from_transform.Y(), |
| from_transform.Z(), from_degrees); |
| to_matrix.Rotate3d(from_transform.X(), from_transform.Y(), from_transform.Z(), |
| to_degrees); |
| |
| FloatPoint3D from_point = from_matrix.MapPoint(point); |
| FloatPoint3D to_point = to_matrix.MapPoint(point); |
| |
| if (box.IsEmpty()) |
| box.SetOrigin(from_point); |
| else |
| box.ExpandTo(from_point); |
| |
| box.ExpandTo(to_point); |
| |
| switch (from_transform.GetType()) { |
| case TransformOperation::kRotateX: |
| FindCandidatesInPlane(point.Y(), point.Z(), from_transform.X(), |
| candidates, &num_candidates); |
| break; |
| case TransformOperation::kRotateY: |
| FindCandidatesInPlane(point.Z(), point.X(), from_transform.Y(), |
| candidates, &num_candidates); |
| break; |
| case TransformOperation::kRotateZ: |
| FindCandidatesInPlane(point.X(), point.Y(), from_transform.Z(), |
| candidates, &num_candidates); |
| break; |
| default: { |
| FloatPoint3D normal = axis; |
| if (normal.IsZero()) |
| return; |
| normal.Normalize(); |
| FloatPoint3D origin; |
| FloatPoint3D to_point = point - origin; |
| FloatPoint3D center = origin + normal * to_point.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; |
| num_candidates = 6; |
| } break; |
| } |
| |
| double min_radians = deg2rad(from_degrees); |
| double max_radians = deg2rad(to_degrees); |
| // 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 < num_candidates; ++i) { |
| double radians = candidates[i]; |
| |
| while (radians < min_radians) |
| radians += 2.0 * M_PI; |
| while (radians > max_radians) |
| radians -= 2.0 * M_PI; |
| if (radians < min_radians) |
| 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& min_progress, |
| const double& max_progress, |
| FloatBox* bounds) const { |
| int from_size = from.Operations().size(); |
| int to_size = Operations().size(); |
| int size = std::max(from_size, to_size); |
| |
| *bounds = box; |
| for (int i = size - 1; i >= 0; i--) { |
| scoped_refptr<TransformOperation> from_operation = |
| (i < from_size) ? from.Operations()[i] : nullptr; |
| scoped_refptr<TransformOperation> to_operation = |
| (i < to_size) ? Operations()[i] : nullptr; |
| |
| DCHECK(from_operation || to_operation); |
| TransformOperation::OperationType interpolation_type = |
| to_operation ? to_operation->GetType() : from_operation->GetType(); |
| if (from_operation && to_operation && |
| !from_operation->CanBlendWith(*to_operation.get())) |
| return false; |
| |
| switch (interpolation_type) { |
| case TransformOperation::kIdentity: |
| bounds->ExpandTo(box); |
| continue; |
| case TransformOperation::kTranslate: |
| case TransformOperation::kTranslateX: |
| case TransformOperation::kTranslateY: |
| case TransformOperation::kTranslateZ: |
| case TransformOperation::kTranslate3D: |
| case TransformOperation::kScale: |
| case TransformOperation::kScaleX: |
| case TransformOperation::kScaleY: |
| case TransformOperation::kScaleZ: |
| case TransformOperation::kScale3D: |
| case TransformOperation::kSkew: |
| case TransformOperation::kSkewX: |
| case TransformOperation::kSkewY: |
| case TransformOperation::kPerspective: { |
| scoped_refptr<TransformOperation> from_transform; |
| scoped_refptr<TransformOperation> to_transform; |
| if (!to_operation) { |
| from_transform = from_operation->Blend(to_operation.get(), |
| 1 - min_progress, false); |
| to_transform = from_operation->Blend(to_operation.get(), |
| 1 - max_progress, false); |
| } else { |
| from_transform = |
| to_operation->Blend(from_operation.get(), min_progress, false); |
| to_transform = |
| to_operation->Blend(from_operation.get(), max_progress, false); |
| } |
| if (!from_transform || !to_transform) |
| continue; |
| TransformationMatrix from_matrix; |
| TransformationMatrix to_matrix; |
| from_transform->Apply(from_matrix, FloatSize()); |
| to_transform->Apply(to_matrix, FloatSize()); |
| FloatBox from_box = *bounds; |
| FloatBox to_box = *bounds; |
| from_matrix.TransformBox(from_box); |
| to_matrix.TransformBox(to_box); |
| *bounds = from_box; |
| bounds->ExpandTo(to_box); |
| continue; |
| } |
| case TransformOperation::kRotate: // This is also RotateZ |
| case TransformOperation::kRotate3D: |
| case TransformOperation::kRotateX: |
| case TransformOperation::kRotateY: { |
| scoped_refptr<RotateTransformOperation> identity_rotation; |
| const RotateTransformOperation* from_rotation = nullptr; |
| const RotateTransformOperation* to_rotation = nullptr; |
| if (from_operation) { |
| from_rotation = static_cast<const RotateTransformOperation*>( |
| from_operation.get()); |
| if (from_rotation->Axis().IsZero()) |
| from_rotation = nullptr; |
| } |
| |
| if (to_operation) { |
| to_rotation = |
| static_cast<const RotateTransformOperation*>(to_operation.get()); |
| if (to_rotation->Axis().IsZero()) |
| to_rotation = nullptr; |
| } |
| |
| double from_angle; |
| double to_angle; |
| FloatPoint3D axis; |
| if (!RotateTransformOperation::GetCommonAxis( |
| from_rotation, to_rotation, axis, from_angle, to_angle)) { |
| return false; |
| } |
| |
| if (!from_rotation) { |
| identity_rotation = RotateTransformOperation::Create( |
| axis.X(), axis.Y(), axis.Z(), 0, |
| from_operation ? from_operation->GetType() |
| : to_operation->GetType()); |
| from_rotation = identity_rotation.get(); |
| } |
| |
| if (!to_rotation) { |
| if (!identity_rotation) |
| identity_rotation = RotateTransformOperation::Create( |
| axis.X(), axis.Y(), axis.Z(), 0, |
| from_operation ? from_operation->GetType() |
| : to_operation->GetType()); |
| to_rotation = identity_rotation.get(); |
| } |
| |
| FloatBox from_box = *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 bounds_for_arc; |
| FloatPoint3D corner(from_box.X(), from_box.Y(), from_box.Z()); |
| corner += |
| FloatPoint3D(i * from_box.Width(), j * from_box.Height(), |
| k * from_box.Depth()); |
| BoundingBoxForArc(corner, *from_rotation, *to_rotation, |
| min_progress, max_progress, bounds_for_arc); |
| if (first) { |
| *bounds = bounds_for_arc; |
| first = false; |
| } else { |
| bounds->ExpandTo(bounds_for_arc); |
| } |
| } |
| } |
| } |
| } |
| continue; |
| case TransformOperation::kMatrix: |
| case TransformOperation::kMatrix3D: |
| case TransformOperation::kInterpolated: |
| case TransformOperation::kRotateAroundOrigin: |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| TransformOperations TransformOperations::Add( |
| const TransformOperations& addend) const { |
| TransformOperations result; |
| result.operations_ = Operations(); |
| result.operations_.AppendVector(addend.Operations()); |
| return result; |
| } |
| |
| TransformOperations TransformOperations::Zoom(double factor) const { |
| TransformOperations result; |
| for (auto& transform_operation : operations_) |
| result.operations_.push_back(transform_operation->Zoom(factor)); |
| return result; |
| } |
| |
| } // namespace blink |