summaryrefslogtreecommitdiffstats
path: root/cc/animation
diff options
context:
space:
mode:
authorjamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-18 09:34:48 +0000
committerjamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-18 09:34:48 +0000
commitcdb284d7a99f11f80fa07ac0390eacf65322d1ef (patch)
tree9268fd0d17b0d41dbfd3548e6286434393e045e3 /cc/animation
parentd59adfc29b142ce07e26822203f432658473504a (diff)
downloadchromium_src-cdb284d7a99f11f80fa07ac0390eacf65322d1ef.zip
chromium_src-cdb284d7a99f11f80fa07ac0390eacf65322d1ef.tar.gz
chromium_src-cdb284d7a99f11f80fa07ac0390eacf65322d1ef.tar.bz2
Part 11 of cc/ directory shuffles: fix up stragglers
Continuation of https://src.chromium.org/viewvc/chrome?view=rev&revision=188681 This moves files left behind in previous patches and completes the series. BUG=190824 TBR=enne@chromium.org Review URL: https://codereview.chromium.org/12916002 Review URL: https://codereview.chromium.org/12604010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@188707 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'cc/animation')
-rw-r--r--cc/animation/keyframed_animation_curve.h4
-rw-r--r--cc/animation/keyframed_animation_curve_unittest.cc2
-rw-r--r--cc/animation/layer_animation_controller_unittest.cc2
-rw-r--r--cc/animation/timing_function.cc139
-rw-r--r--cc/animation/timing_function.h67
-rw-r--r--cc/animation/timing_function_unittest.cc41
-rw-r--r--cc/animation/transform_operation.cc184
-rw-r--r--cc/animation/transform_operation.h63
-rw-r--r--cc/animation/transform_operations.cc200
-rw-r--r--cc/animation/transform_operations.h81
-rw-r--r--cc/animation/transform_operations_unittest.cc603
11 files changed, 1382 insertions, 4 deletions
diff --git a/cc/animation/keyframed_animation_curve.h b/cc/animation/keyframed_animation_curve.h
index aa53b67..09f269c 100644
--- a/cc/animation/keyframed_animation_curve.h
+++ b/cc/animation/keyframed_animation_curve.h
@@ -6,10 +6,10 @@
#define CC_ANIMATION_KEYFRAMED_ANIMATION_CURVE_H_
#include "cc/animation/animation_curve.h"
+#include "cc/animation/timing_function.h"
+#include "cc/animation/transform_operations.h"
#include "cc/base/cc_export.h"
#include "cc/base/scoped_ptr_vector.h"
-#include "cc/timing_function.h"
-#include "cc/transform_operations.h"
namespace cc {
diff --git a/cc/animation/keyframed_animation_curve_unittest.cc b/cc/animation/keyframed_animation_curve_unittest.cc
index 446801d..dd7f869 100644
--- a/cc/animation/keyframed_animation_curve_unittest.cc
+++ b/cc/animation/keyframed_animation_curve_unittest.cc
@@ -4,7 +4,7 @@
#include "cc/animation/keyframed_animation_curve.h"
-#include "cc/transform_operations.h"
+#include "cc/animation/transform_operations.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
diff --git a/cc/animation/layer_animation_controller_unittest.cc b/cc/animation/layer_animation_controller_unittest.cc
index 2e7c29b..d7ecf9f 100644
--- a/cc/animation/layer_animation_controller_unittest.cc
+++ b/cc/animation/layer_animation_controller_unittest.cc
@@ -7,8 +7,8 @@
#include "cc/animation/animation.h"
#include "cc/animation/animation_curve.h"
#include "cc/animation/keyframed_animation_curve.h"
+#include "cc/animation/transform_operations.h"
#include "cc/test/animation_test_common.h"
-#include "cc/transform_operations.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/transform.h"
diff --git a/cc/animation/timing_function.cc b/cc/animation/timing_function.cc
new file mode 100644
index 0000000..c0af3c2
--- /dev/null
+++ b/cc/animation/timing_function.cc
@@ -0,0 +1,139 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/animation/timing_function.h"
+
+#include "third_party/skia/include/core/SkMath.h"
+
+// TODO(danakj) These methods come from SkInterpolator.cpp. When such a method
+// is available in the public Skia API, we should switch to using that.
+// http://crbug.com/159735
+namespace {
+
+// Dot14 has 14 bits for decimal places, and the remainder for whole numbers.
+typedef int Dot14;
+#define DOT14_ONE (1 << 14)
+#define DOT14_HALF (1 << 13)
+
+#define Dot14ToFloat(x) ((x) / 16384.f)
+
+static inline Dot14 Dot14Mul(Dot14 a, Dot14 b) {
+ return (a * b + DOT14_HALF) >> 14;
+}
+
+static inline Dot14 EvalCubic(Dot14 t, Dot14 A, Dot14 B, Dot14 C) {
+ return Dot14Mul(Dot14Mul(Dot14Mul(C, t) + B, t) + A, t);
+}
+
+static inline Dot14 PinAndConvert(SkScalar x) {
+ if (x <= 0)
+ return 0;
+ if (x >= SK_Scalar1)
+ return DOT14_ONE;
+ return SkScalarToFixed(x) >> 2;
+}
+
+SkScalar SkUnitCubicInterp(SkScalar bx, SkScalar by,
+ SkScalar cx, SkScalar cy,
+ SkScalar value) {
+ Dot14 x = PinAndConvert(value);
+
+ if (x == 0) return 0;
+ if (x == DOT14_ONE) return SK_Scalar1;
+
+ Dot14 b = PinAndConvert(bx);
+ Dot14 c = PinAndConvert(cx);
+
+ // Now compute our coefficients from the control points.
+ // t -> 3b
+ // t^2 -> 3c - 6b
+ // t^3 -> 3b - 3c + 1
+ Dot14 A = 3 * b;
+ Dot14 B = 3 * (c - 2 * b);
+ Dot14 C = 3 * (b - c) + DOT14_ONE;
+
+ // Now search for a t value given x.
+ Dot14 t = DOT14_HALF;
+ Dot14 dt = DOT14_HALF;
+ for (int i = 0; i < 13; i++) {
+ dt >>= 1;
+ Dot14 guess = EvalCubic(t, A, B, C);
+ if (x < guess)
+ t -= dt;
+ else
+ t += dt;
+ }
+
+ // Now we have t, so compute the coefficient for Y and evaluate.
+ b = PinAndConvert(by);
+ c = PinAndConvert(cy);
+ A = 3 * b;
+ B = 3 * (c - 2 * b);
+ C = 3 * (b - c) + DOT14_ONE;
+ return SkFixedToScalar(EvalCubic(t, A, B, C) << 2);
+}
+
+} // namespace
+
+namespace cc {
+
+TimingFunction::TimingFunction() {
+}
+
+TimingFunction::~TimingFunction() {
+}
+
+double TimingFunction::Duration() const {
+ return 1.0;
+}
+
+scoped_ptr<CubicBezierTimingFunction> CubicBezierTimingFunction::create(
+ double x1, double y1, double x2, double y2) {
+ return make_scoped_ptr(new CubicBezierTimingFunction(x1, y1, x2, y2));
+}
+
+CubicBezierTimingFunction::CubicBezierTimingFunction(double x1, double y1,
+ double x2, double y2)
+ : x1_(SkDoubleToScalar(x1)),
+ y1_(SkDoubleToScalar(y1)),
+ x2_(SkDoubleToScalar(x2)),
+ y2_(SkDoubleToScalar(y2)) {
+}
+
+CubicBezierTimingFunction::~CubicBezierTimingFunction() {
+}
+
+float CubicBezierTimingFunction::GetValue(double x) const {
+ SkScalar value = SkUnitCubicInterp(x1_, y1_, x2_, y2_, x);
+ return SkScalarToFloat(value);
+}
+
+scoped_ptr<AnimationCurve> CubicBezierTimingFunction::Clone() const {
+ return make_scoped_ptr(
+ new CubicBezierTimingFunction(*this)).PassAs<AnimationCurve>();
+}
+
+// These numbers come from
+// http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag.
+scoped_ptr<TimingFunction> EaseTimingFunction::create() {
+ return CubicBezierTimingFunction::create(
+ 0.25, 0.1, 0.25, 1).PassAs<TimingFunction>();
+}
+
+scoped_ptr<TimingFunction> EaseInTimingFunction::create() {
+ return CubicBezierTimingFunction::create(
+ 0.42, 0, 1.0, 1).PassAs<TimingFunction>();
+}
+
+scoped_ptr<TimingFunction> EaseOutTimingFunction::create() {
+ return CubicBezierTimingFunction::create(
+ 0, 0, 0.58, 1).PassAs<TimingFunction>();
+}
+
+scoped_ptr<TimingFunction> EaseInOutTimingFunction::create() {
+ return CubicBezierTimingFunction::create(
+ 0.42, 0, 0.58, 1).PassAs<TimingFunction>();
+}
+
+} // namespace cc
diff --git a/cc/animation/timing_function.h b/cc/animation/timing_function.h
new file mode 100644
index 0000000..3c88ecb
--- /dev/null
+++ b/cc/animation/timing_function.h
@@ -0,0 +1,67 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_ANIMATION_TIMING_FUNCTION_H_
+#define CC_ANIMATION_TIMING_FUNCTION_H_
+
+#include "cc/animation/animation_curve.h"
+#include "cc/base/cc_export.h"
+#include "third_party/skia/include/core/SkScalar.h"
+
+namespace cc {
+
+// See http://www.w3.org/TR/css3-transitions/.
+class CC_EXPORT TimingFunction : public FloatAnimationCurve {
+ public:
+ virtual ~TimingFunction();
+
+ // Partial implementation of FloatAnimationCurve.
+ virtual double Duration() const OVERRIDE;
+
+ protected:
+ TimingFunction();
+};
+
+class CC_EXPORT CubicBezierTimingFunction : public TimingFunction {
+ public:
+ static scoped_ptr<CubicBezierTimingFunction> create(double x1, double y1,
+ double x2, double y2);
+ virtual ~CubicBezierTimingFunction();
+
+ // Partial implementation of FloatAnimationCurve.
+ virtual float GetValue(double time) const OVERRIDE;
+ virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE;
+
+ protected:
+ CubicBezierTimingFunction(double x1, double y1, double x2, double y2);
+
+ SkScalar x1_;
+ SkScalar y1_;
+ SkScalar x2_;
+ SkScalar y2_;
+};
+
+class CC_EXPORT EaseTimingFunction {
+ public:
+ static scoped_ptr<TimingFunction> create();
+};
+
+class CC_EXPORT EaseInTimingFunction {
+ public:
+ static scoped_ptr<TimingFunction> create();
+};
+
+class CC_EXPORT EaseOutTimingFunction {
+ public:
+ static scoped_ptr<TimingFunction> create();
+};
+
+class CC_EXPORT EaseInOutTimingFunction {
+ public:
+ static scoped_ptr<TimingFunction> create();
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_TIMING_FUNCTION_H_
diff --git a/cc/animation/timing_function_unittest.cc b/cc/animation/timing_function_unittest.cc
new file mode 100644
index 0000000..f97201b
--- /dev/null
+++ b/cc/animation/timing_function_unittest.cc
@@ -0,0 +1,41 @@
+// Copyright 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/animation/timing_function.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+TEST(TimingFunctionTest, CubicBezierTimingFunction) {
+ scoped_ptr<CubicBezierTimingFunction> function =
+ CubicBezierTimingFunction::create(0.25, 0, 0.75, 1);
+
+ double epsilon = 0.00015;
+
+ EXPECT_NEAR(function->GetValue(0), 0, epsilon);
+ EXPECT_NEAR(function->GetValue(0.05), 0.01136, epsilon);
+ EXPECT_NEAR(function->GetValue(0.1), 0.03978, epsilon);
+ EXPECT_NEAR(function->GetValue(0.15), 0.079780, epsilon);
+ EXPECT_NEAR(function->GetValue(0.2), 0.12803, epsilon);
+ EXPECT_NEAR(function->GetValue(0.25), 0.18235, epsilon);
+ EXPECT_NEAR(function->GetValue(0.3), 0.24115, epsilon);
+ EXPECT_NEAR(function->GetValue(0.35), 0.30323, epsilon);
+ EXPECT_NEAR(function->GetValue(0.4), 0.36761, epsilon);
+ EXPECT_NEAR(function->GetValue(0.45), 0.43345, epsilon);
+ EXPECT_NEAR(function->GetValue(0.5), 0.5, epsilon);
+ EXPECT_NEAR(function->GetValue(0.6), 0.63238, epsilon);
+ EXPECT_NEAR(function->GetValue(0.65), 0.69676, epsilon);
+ EXPECT_NEAR(function->GetValue(0.7), 0.75884, epsilon);
+ EXPECT_NEAR(function->GetValue(0.75), 0.81764, epsilon);
+ EXPECT_NEAR(function->GetValue(0.8), 0.87196, epsilon);
+ EXPECT_NEAR(function->GetValue(0.85), 0.92021, epsilon);
+ EXPECT_NEAR(function->GetValue(0.9), 0.96021, epsilon);
+ EXPECT_NEAR(function->GetValue(0.95), 0.98863, epsilon);
+ EXPECT_NEAR(function->GetValue(1), 1, epsilon);
+}
+
+} // namespace
+} // namespace cc
diff --git a/cc/animation/transform_operation.cc b/cc/animation/transform_operation.cc
new file mode 100644
index 0000000..65769b4
--- /dev/null
+++ b/cc/animation/transform_operation.cc
@@ -0,0 +1,184 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cmath>
+#include <limits>
+
+#include "cc/animation/transform_operation.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace {
+const double kAngleEpsilon = 1e-4;
+}
+
+namespace cc {
+
+bool TransformOperation::IsIdentity() const {
+ return matrix.IsIdentity();
+}
+
+static bool IsOperationIdentity(const TransformOperation* operation) {
+ return !operation || operation->IsIdentity();
+}
+
+static bool ShareSameAxis(const TransformOperation* from,
+ const TransformOperation* to,
+ double& axis_x, double& axis_y, double& axis_z,
+ double& angle_from) {
+ if (IsOperationIdentity(from) && IsOperationIdentity(to))
+ return false;
+
+ if (IsOperationIdentity(from) && !IsOperationIdentity(to)) {
+ axis_x = to->rotate.axis.x;
+ axis_y = to->rotate.axis.y;
+ axis_z = to->rotate.axis.z;
+ angle_from = 0;
+ return true;
+ }
+
+ if (!IsOperationIdentity(from) && IsOperationIdentity(to)) {
+ axis_x = from->rotate.axis.x;
+ axis_y = from->rotate.axis.y;
+ axis_z = from->rotate.axis.z;
+ angle_from = from->rotate.angle;
+ return true;
+ }
+
+ double length_2 = from->rotate.axis.x * from->rotate.axis.x +
+ from->rotate.axis.y * from->rotate.axis.y +
+ from->rotate.axis.z * from->rotate.axis.z;
+ double other_length_2 = to->rotate.axis.x * to->rotate.axis.x +
+ to->rotate.axis.y * to->rotate.axis.y +
+ to->rotate.axis.z * to->rotate.axis.z;
+
+ if (length_2 <= kAngleEpsilon || other_length_2 <= kAngleEpsilon)
+ return false;
+
+ double dot = to->rotate.axis.x * from->rotate.axis.x +
+ to->rotate.axis.y * from->rotate.axis.y +
+ to->rotate.axis.z * from->rotate.axis.z;
+ double error = std::fabs(1.0 - (dot * dot) / (length_2 * other_length_2));
+ bool result = error < kAngleEpsilon;
+ if (result) {
+ axis_x = to->rotate.axis.x;
+ axis_y = to->rotate.axis.y;
+ axis_z = to->rotate.axis.z;
+ // If the axes are pointing in opposite directions, we need to reverse
+ // the angle.
+ angle_from = dot > 0 ? from->rotate.angle : -from->rotate.angle;
+ }
+ return result;
+}
+
+static double BlendDoubles(double from, double to, double progress) {
+ if (progress <= 0.0)
+ return from;
+
+ if (progress >= 1.0)
+ return to;
+
+ return from * (1 - progress) + to * progress;
+}
+
+bool TransformOperation::BlendTransformOperations(
+ const TransformOperation* from,
+ const TransformOperation* to,
+ double progress,
+ gfx::Transform& result) {
+ if (IsOperationIdentity(from) && IsOperationIdentity(to))
+ return true;
+
+ TransformOperation::Type interpolation_type =
+ TransformOperation::TransformOperationIdentity;
+ if (IsOperationIdentity(to))
+ interpolation_type = from->type;
+ else
+ interpolation_type = to->type;
+
+ switch (interpolation_type) {
+ case TransformOperation::TransformOperationTranslate: {
+ double from_x = IsOperationIdentity(from) ? 0 : from->translate.x;
+ double from_y = IsOperationIdentity(from) ? 0 : from->translate.y;
+ double from_z = IsOperationIdentity(from) ? 0 : from->translate.z;
+ double to_x = IsOperationIdentity(to) ? 0 : to->translate.x;
+ double to_y = IsOperationIdentity(to) ? 0 : to->translate.y;
+ double to_z = IsOperationIdentity(to) ? 0 : to->translate.z;
+ result.Translate3d(BlendDoubles(from_x, to_x, progress),
+ BlendDoubles(from_y, to_y, progress),
+ BlendDoubles(from_z, to_z, progress));
+ break;
+ }
+ case TransformOperation::TransformOperationRotate: {
+ double axis_x = 0;
+ double axis_y = 0;
+ double axis_z = 1;
+ double from_angle = 0;
+ double to_angle = IsOperationIdentity(to) ? 0 : to->rotate.angle;
+ if (ShareSameAxis(from, to, axis_x, axis_y, axis_z, from_angle))
+ result.RotateAbout(gfx::Vector3dF(axis_x, axis_y, axis_z),
+ BlendDoubles(from_angle, to_angle, progress));
+ else {
+ gfx::Transform to_matrix;
+ if (!IsOperationIdentity(to))
+ to_matrix = to->matrix;
+ gfx::Transform from_matrix;
+ if (!IsOperationIdentity(from))
+ from_matrix = from->matrix;
+ result = to_matrix;
+ if (!result.Blend(from_matrix, progress))
+ return false;
+ }
+ break;
+ }
+ case TransformOperation::TransformOperationScale: {
+ double from_x = IsOperationIdentity(from) ? 1 : from->scale.x;
+ double from_y = IsOperationIdentity(from) ? 1 : from->scale.y;
+ double from_z = IsOperationIdentity(from) ? 1 : from->scale.z;
+ double to_x = IsOperationIdentity(to) ? 1 : to->scale.x;
+ double to_y = IsOperationIdentity(to) ? 1 : to->scale.y;
+ double to_z = IsOperationIdentity(to) ? 1 : to->scale.z;
+ result.Scale3d(BlendDoubles(from_x, to_x, progress),
+ BlendDoubles(from_y, to_y, progress),
+ BlendDoubles(from_z, to_z, progress));
+ break;
+ }
+ case TransformOperation::TransformOperationSkew: {
+ double from_x = IsOperationIdentity(from) ? 0 : from->skew.x;
+ double from_y = IsOperationIdentity(from) ? 0 : from->skew.y;
+ double to_x = IsOperationIdentity(to) ? 0 : to->skew.x;
+ double to_y = IsOperationIdentity(to) ? 0 : to->skew.y;
+ result.SkewX(BlendDoubles(from_x, to_x, progress));
+ result.SkewY(BlendDoubles(from_y, to_y, progress));
+ break;
+ }
+ case TransformOperation::TransformOperationPerspective: {
+ double from_perspective_depth = IsOperationIdentity(from) ?
+ std::numeric_limits<double>::max() : from->perspective_depth;
+ double to_perspective_depth = IsOperationIdentity(to) ?
+ std::numeric_limits<double>::max() : to->perspective_depth;
+ result.ApplyPerspectiveDepth(
+ BlendDoubles(from_perspective_depth, to_perspective_depth, progress));
+ break;
+ }
+ case TransformOperation::TransformOperationMatrix: {
+ gfx::Transform to_matrix;
+ if (!IsOperationIdentity(to))
+ to_matrix = to->matrix;
+ gfx::Transform from_matrix;
+ if (!IsOperationIdentity(from))
+ from_matrix = from->matrix;
+ result = to_matrix;
+ if (!result.Blend(from_matrix, progress))
+ return false;
+ break;
+ }
+ case TransformOperation::TransformOperationIdentity:
+ // Do nothing.
+ break;
+ }
+
+ return true;
+}
+
+} // namespace cc
diff --git a/cc/animation/transform_operation.h b/cc/animation/transform_operation.h
new file mode 100644
index 0000000..0581862
--- /dev/null
+++ b/cc/animation/transform_operation.h
@@ -0,0 +1,63 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_ANIMATION_TRANSFORM_OPERATION_H_
+#define CC_ANIMATION_TRANSFORM_OPERATION_H_
+
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+struct TransformOperation {
+ enum Type {
+ TransformOperationTranslate,
+ TransformOperationRotate,
+ TransformOperationScale,
+ TransformOperationSkew,
+ TransformOperationPerspective,
+ TransformOperationMatrix,
+ TransformOperationIdentity
+ };
+
+ TransformOperation()
+ : type(TransformOperationIdentity) {
+ }
+
+ Type type;
+ gfx::Transform matrix;
+
+ union {
+ double perspective_depth;
+
+ struct {
+ double x, y;
+ } skew;
+
+ struct {
+ double x, y, z;
+ } scale;
+
+ struct {
+ double x, y, z;
+ } translate;
+
+ struct {
+ struct {
+ double x, y, z;
+ } axis;
+
+ double angle;
+ } rotate;
+ };
+
+ bool IsIdentity() const;
+ static bool BlendTransformOperations(const TransformOperation* from,
+ const TransformOperation* to,
+ double progress,
+ gfx::Transform& result);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_TRANSFORM_OPERATION_H_
diff --git a/cc/animation/transform_operations.cc b/cc/animation/transform_operations.cc
new file mode 100644
index 0000000..4de283e
--- /dev/null
+++ b/cc/animation/transform_operations.cc
@@ -0,0 +1,200 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/animation/transform_operations.h"
+#include "ui/gfx/transform_util.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace cc {
+
+TransformOperations::TransformOperations()
+ : decomposed_transform_dirty_(true) {
+}
+
+TransformOperations::TransformOperations(const TransformOperations& other) {
+ operations_ = other.operations_;
+ decomposed_transform_dirty_ = other.decomposed_transform_dirty_;
+ if (!decomposed_transform_dirty_) {
+ decomposed_transform_.reset(
+ new gfx::DecomposedTransform(*other.decomposed_transform_.get()));
+ }
+}
+
+TransformOperations::~TransformOperations() {
+}
+
+gfx::Transform TransformOperations::Apply() const {
+ gfx::Transform to_return;
+ for (size_t i = 0; i < operations_.size(); ++i)
+ to_return.PreconcatTransform(operations_[i].matrix);
+ return to_return;
+}
+
+gfx::Transform TransformOperations::Blend(
+ const TransformOperations& from, double progress) const {
+ gfx::Transform to_return;
+ BlendInternal(from, progress, &to_return);
+ return to_return;
+}
+
+bool TransformOperations::MatchesTypes(const TransformOperations& other) const {
+ if (IsIdentity() || other.IsIdentity())
+ return true;
+
+ if (operations_.size() != other.operations_.size())
+ return false;
+
+ for (size_t i = 0; i < operations_.size(); ++i) {
+ if (operations_[i].type != other.operations_[i].type
+ && !operations_[i].IsIdentity()
+ && !other.operations_[i].IsIdentity())
+ return false;
+ }
+
+ return true;
+}
+
+bool TransformOperations::CanBlendWith(
+ const TransformOperations& other) const {
+ gfx::Transform dummy;
+ return BlendInternal(other, 0.5, &dummy);
+}
+
+void TransformOperations::AppendTranslate(double x, double y, double z) {
+ TransformOperation to_add;
+ to_add.matrix.Translate3d(x, y, z);
+ to_add.type = TransformOperation::TransformOperationTranslate;
+ to_add.translate.x = x;
+ to_add.translate.y = y;
+ to_add.translate.z = z;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendRotate(double x, double y, double z,
+ double degrees) {
+ TransformOperation to_add;
+ to_add.matrix.RotateAbout(gfx::Vector3dF(x, y, z), degrees);
+ to_add.type = TransformOperation::TransformOperationRotate;
+ to_add.rotate.axis.x = x;
+ to_add.rotate.axis.y = y;
+ to_add.rotate.axis.z = z;
+ to_add.rotate.angle = degrees;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendScale(double x, double y, double z) {
+ TransformOperation to_add;
+ to_add.matrix.Scale3d(x, y, z);
+ to_add.type = TransformOperation::TransformOperationScale;
+ to_add.scale.x = x;
+ to_add.scale.y = y;
+ to_add.scale.z = z;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendSkew(double x, double y) {
+ TransformOperation to_add;
+ to_add.matrix.SkewX(x);
+ to_add.matrix.SkewY(y);
+ to_add.type = TransformOperation::TransformOperationSkew;
+ to_add.skew.x = x;
+ to_add.skew.y = y;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendPerspective(double depth) {
+ TransformOperation to_add;
+ to_add.matrix.ApplyPerspectiveDepth(depth);
+ to_add.type = TransformOperation::TransformOperationPerspective;
+ to_add.perspective_depth = depth;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendMatrix(const gfx::Transform& matrix) {
+ TransformOperation to_add;
+ to_add.matrix = matrix;
+ to_add.type = TransformOperation::TransformOperationMatrix;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendIdentity() {
+ operations_.push_back(TransformOperation());
+}
+
+bool TransformOperations::IsIdentity() const {
+ for (size_t i = 0; i < operations_.size(); ++i) {
+ if (!operations_[i].IsIdentity())
+ return false;
+ }
+ return true;
+}
+
+bool TransformOperations::BlendInternal(const TransformOperations& from,
+ double progress,
+ gfx::Transform* result) const {
+ bool from_identity = from.IsIdentity();
+ bool to_identity = IsIdentity();
+ if (from_identity && to_identity)
+ return true;
+
+ if (MatchesTypes(from)) {
+ size_t num_operations =
+ std::max(from_identity ? 0 : from.operations_.size(),
+ to_identity ? 0 : operations_.size());
+ for (size_t i = 0; i < num_operations; ++i) {
+ gfx::Transform blended;
+ if (!TransformOperation::BlendTransformOperations(
+ from_identity ? 0 : &from.operations_[i],
+ to_identity ? 0 : &operations_[i],
+ progress,
+ blended))
+ return false;
+ result->PreconcatTransform(blended);
+ }
+ return true;
+ }
+
+ if (progress <= 0.0) {
+ *result = from.Apply();
+ return true;
+ }
+
+ if (progress >= 1.0) {
+ *result = Apply();
+ return true;
+ }
+
+ if (!ComputeDecomposedTransform() || !from.ComputeDecomposedTransform())
+ return false;
+
+ gfx::DecomposedTransform to_return;
+ if (!gfx::BlendDecomposedTransforms(&to_return,
+ *decomposed_transform_.get(),
+ *from.decomposed_transform_.get(),
+ progress))
+ return false;
+
+ *result = ComposeTransform(to_return);
+ return true;
+}
+
+bool TransformOperations::ComputeDecomposedTransform() const {
+ if (decomposed_transform_dirty_) {
+ if (!decomposed_transform_)
+ decomposed_transform_.reset(new gfx::DecomposedTransform());
+ gfx::Transform transform = Apply();
+ if (!gfx::DecomposeTransform(decomposed_transform_.get(), transform))
+ return false;
+ decomposed_transform_dirty_ = false;
+ }
+ return true;
+}
+
+} // namespace cc
diff --git a/cc/animation/transform_operations.h b/cc/animation/transform_operations.h
new file mode 100644
index 0000000..9ab0196
--- /dev/null
+++ b/cc/animation/transform_operations.h
@@ -0,0 +1,81 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_ANIMATION_TRANSFORM_OPERATIONS_H_
+#define CC_ANIMATION_TRANSFORM_OPERATIONS_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/animation/transform_operation.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/transform.h"
+
+namespace gfx {
+struct DecomposedTransform;
+}
+
+namespace cc {
+
+// Transform operations are a decomposed transformation matrix. It can be
+// applied to obtain a gfx::Transform at any time, and can be blended
+// intelligently with other transform operations, so long as they represent the
+// same decomposition. For example, if we have a transform that is made up of
+// a rotation followed by skew, it can be blended intelligently with another
+// transform made up of a rotation followed by a skew. Blending is possible if
+// we have two dissimilar sets of transform operations, but the effect may not
+// be what was intended. For more information, see the comments for the blend
+// function below.
+class CC_EXPORT TransformOperations {
+ public:
+ TransformOperations();
+ TransformOperations(const TransformOperations& other);
+ ~TransformOperations();
+
+ // Returns a transformation matrix representing these transform operations.
+ gfx::Transform Apply() const;
+
+ // Given another set of transform operations and a progress in the range
+ // [0, 1], returns a transformation matrix representing the intermediate
+ // value. If this->MatchesTypes(from), then each of the operations are
+ // blended separately and then combined. Otherwise, the two sets of
+ // transforms are baked to matrices (using apply), and the matrices are
+ // then decomposed and interpolated. For more information, see
+ // http://www.w3.org/TR/2011/WD-css3-2d-transforms-20111215/#matrix-decomposition.
+ gfx::Transform Blend(const TransformOperations& from, double progress) const;
+
+ // Returns true if this operation and its descendants have the same types
+ // as other and its descendants.
+ bool MatchesTypes(const TransformOperations& other) const;
+
+ // Returns true if these operations can be blended. It will only return
+ // false if we must resort to matrix interpolation, and matrix interpolation
+ // fails (this can happen if either matrix cannot be decomposed).
+ bool CanBlendWith(const TransformOperations& other) const;
+
+ void AppendTranslate(double x, double y, double z);
+ void AppendRotate(double x, double y, double z, double degrees);
+ void AppendScale(double x, double y, double z);
+ void AppendSkew(double x, double y);
+ void AppendPerspective(double depth);
+ void AppendMatrix(const gfx::Transform& matrix);
+ void AppendIdentity();
+ bool IsIdentity() const;
+
+ private:
+ bool BlendInternal(const TransformOperations& from, double progress,
+ gfx::Transform* result) const;
+
+ std::vector<TransformOperation> operations_;
+
+ bool ComputeDecomposedTransform() const;
+
+ // For efficiency, we cache the decomposed transform.
+ mutable scoped_ptr<gfx::DecomposedTransform> decomposed_transform_;
+ mutable bool decomposed_transform_dirty_;
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_TRANSFORM_OPERATIONS_H_
diff --git a/cc/animation/transform_operations_unittest.cc b/cc/animation/transform_operations_unittest.cc
new file mode 100644
index 0000000..206a9f6
--- /dev/null
+++ b/cc/animation/transform_operations_unittest.cc
@@ -0,0 +1,603 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/scoped_vector.h"
+#include "cc/animation/transform_operations.h"
+#include "cc/test/geometry_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace cc {
+namespace {
+
+TEST(TransformOperationTest, TransformTypesAreUnique) {
+ ScopedVector<TransformOperations> transforms;
+
+ TransformOperations* to_add = new TransformOperations();
+ to_add->AppendTranslate(1, 0, 0);
+ transforms.push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendRotate(0, 0, 1, 2);
+ transforms.push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendScale(2, 2, 2);
+ transforms.push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendSkew(1, 0);
+ transforms.push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendPerspective(800);
+ transforms.push_back(to_add);
+
+ for (size_t i = 0; i < transforms.size(); ++i) {
+ for (size_t j = 0; j < transforms.size(); ++j) {
+ bool matches_type = transforms[i]->MatchesTypes(*transforms[j]);
+ EXPECT_TRUE((i == j && matches_type) || !matches_type);
+ }
+ }
+}
+
+TEST(TransformOperationTest, MatchTypesSameLength) {
+ TransformOperations translates;
+ translates.AppendTranslate(1, 0, 0);
+ translates.AppendTranslate(1, 0, 0);
+ translates.AppendTranslate(1, 0, 0);
+
+ TransformOperations skews;
+ skews.AppendSkew(0, 2);
+ skews.AppendSkew(0, 2);
+ skews.AppendSkew(0, 2);
+
+ TransformOperations translates2;
+ translates2.AppendTranslate(0, 2, 0);
+ translates2.AppendTranslate(0, 2, 0);
+ translates2.AppendTranslate(0, 2, 0);
+
+ TransformOperations translates3 = translates2;
+
+ EXPECT_FALSE(translates.MatchesTypes(skews));
+ EXPECT_TRUE(translates.MatchesTypes(translates2));
+ EXPECT_TRUE(translates.MatchesTypes(translates3));
+}
+
+TEST(TransformOperationTest, MatchTypesDifferentLength) {
+ TransformOperations translates;
+ translates.AppendTranslate(1, 0, 0);
+ translates.AppendTranslate(1, 0, 0);
+ translates.AppendTranslate(1, 0, 0);
+
+ TransformOperations skews;
+ skews.AppendSkew(2, 0);
+ skews.AppendSkew(2, 0);
+
+ TransformOperations translates2;
+ translates2.AppendTranslate(0, 2, 0);
+ translates2.AppendTranslate(0, 2, 0);
+
+ EXPECT_FALSE(translates.MatchesTypes(skews));
+ EXPECT_FALSE(translates.MatchesTypes(translates2));
+}
+
+void GetIdentityOperations(ScopedVector<TransformOperations>* operations) {
+ TransformOperations* to_add = new TransformOperations();
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendTranslate(0, 0, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendTranslate(0, 0, 0);
+ to_add->AppendTranslate(0, 0, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendScale(1, 1, 1);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendScale(1, 1, 1);
+ to_add->AppendScale(1, 1, 1);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendSkew(0, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendSkew(0, 0);
+ to_add->AppendSkew(0, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendRotate(0, 0, 1, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendRotate(0, 0, 1, 0);
+ to_add->AppendRotate(0, 0, 1, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendMatrix(gfx::Transform());
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendMatrix(gfx::Transform());
+ to_add->AppendMatrix(gfx::Transform());
+ operations->push_back(to_add);
+}
+
+TEST(TransformOperationTest, IdentityAlwaysMatches) {
+ ScopedVector<TransformOperations> operations;
+ GetIdentityOperations(&operations);
+
+ for (size_t i = 0; i < operations.size(); ++i) {
+ for (size_t j = 0; j < operations.size(); ++j)
+ EXPECT_TRUE(operations[i]->MatchesTypes(*operations[j]));
+ }
+}
+
+TEST(TransformOperationTest, ApplyTranslate) {
+ double x = 1;
+ double y = 2;
+ double z = 3;
+ TransformOperations operations;
+ operations.AppendTranslate(x, y, z);
+ gfx::Transform expected;
+ expected.Translate3d(x, y, z);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected, operations.Apply());
+}
+
+TEST(TransformOperationTest, ApplyRotate) {
+ double x = 1;
+ double y = 2;
+ double z = 3;
+ double degrees = 80;
+ TransformOperations operations;
+ operations.AppendRotate(x, y, z, degrees);
+ gfx::Transform expected;
+ expected.RotateAbout(gfx::Vector3dF(x, y, z), degrees);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected, operations.Apply());
+}
+
+TEST(TransformOperationTest, ApplyScale) {
+ double x = 1;
+ double y = 2;
+ double z = 3;
+ TransformOperations operations;
+ operations.AppendScale(x, y, z);
+ gfx::Transform expected;
+ expected.Scale3d(x, y, z);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected, operations.Apply());
+}
+
+TEST(TransformOperationTest, ApplySkew) {
+ double x = 1;
+ double y = 2;
+ TransformOperations operations;
+ operations.AppendSkew(x, y);
+ gfx::Transform expected;
+ expected.SkewX(x);
+ expected.SkewY(y);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected, operations.Apply());
+}
+
+TEST(TransformOperationTest, ApplyPerspective) {
+ double depth = 800;
+ TransformOperations operations;
+ operations.AppendPerspective(depth);
+ gfx::Transform expected;
+ expected.ApplyPerspectiveDepth(depth);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected, operations.Apply());
+}
+
+TEST(TransformOperationTest, ApplyMatrix) {
+ double dx = 1;
+ double dy = 2;
+ double dz = 3;
+ gfx::Transform expected_matrix;
+ expected_matrix.Translate3d(dx, dy, dz);
+ TransformOperations matrix_transform;
+ matrix_transform.AppendMatrix(expected_matrix);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_matrix, matrix_transform.Apply());
+}
+
+TEST(TransformOperationTest, ApplyOrder) {
+ double sx = 2;
+ double sy = 4;
+ double sz = 8;
+
+ double dx = 1;
+ double dy = 2;
+ double dz = 3;
+
+ TransformOperations operations;
+ operations.AppendScale(sx, sy, sz);
+ operations.AppendTranslate(dx, dy, dz);
+
+ gfx::Transform expected_scale_matrix;
+ expected_scale_matrix.Scale3d(sx, sy, sz);
+
+ gfx::Transform expected_translate_matrix;
+ expected_translate_matrix.Translate3d(dx, dy, dz);
+
+ gfx::Transform expected_combined_matrix = expected_scale_matrix;
+ expected_combined_matrix.PreconcatTransform(expected_translate_matrix);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_combined_matrix, operations.Apply());
+}
+
+TEST(TransformOperationTest, BlendOrder) {
+ double sx1 = 2;
+ double sy1 = 4;
+ double sz1 = 8;
+
+ double dx1 = 1;
+ double dy1 = 2;
+ double dz1 = 3;
+
+ double sx2 = 4;
+ double sy2 = 8;
+ double sz2 = 16;
+
+ double dx2 = 10;
+ double dy2 = 20;
+ double dz2 = 30;
+
+ TransformOperations operations_from;
+ operations_from.AppendScale(sx1, sy1, sz1);
+ operations_from.AppendTranslate(dx1, dy1, dz1);
+
+ TransformOperations operations_to;
+ operations_to.AppendScale(sx2, sy2, sz2);
+ operations_to.AppendTranslate(dx2, dy2, dz2);
+
+ gfx::Transform scale_from;
+ scale_from.Scale3d(sx1, sy1, sz1);
+ gfx::Transform translate_from;
+ translate_from.Translate3d(dx1, dy1, dz1);
+
+ gfx::Transform scale_to;
+ scale_to.Scale3d(sx2, sy2, sz2);
+ gfx::Transform translate_to;
+ translate_to.Translate3d(dx2, dy2, dz2);
+
+ double progress = 0.25;
+
+ gfx::Transform blended_scale = scale_to;
+ blended_scale.Blend(scale_from, progress);
+
+ gfx::Transform blended_translate = translate_to;
+ blended_translate.Blend(translate_from, progress);
+
+ gfx::Transform expected = blended_scale;
+ expected.PreconcatTransform(blended_translate);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations_to.Blend(operations_from, progress));
+}
+
+static void CheckProgress(double progress,
+ const gfx::Transform& from_matrix,
+ const gfx::Transform& to_matrix,
+ const TransformOperations& from_transform,
+ const TransformOperations& to_transform) {
+ gfx::Transform expected_matrix = to_matrix;
+ expected_matrix.Blend(from_matrix, progress);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_matrix, to_transform.Blend(from_transform, progress));
+}
+
+TEST(TransformOperationTest, BlendProgress) {
+ double sx = 2;
+ double sy = 4;
+ double sz = 8;
+ TransformOperations operations_from;
+ operations_from.AppendScale(sx, sy, sz);
+
+ gfx::Transform matrix_from;
+ matrix_from.Scale3d(sx, sy, sz);
+
+ sx = 4;
+ sy = 8;
+ sz = 16;
+ TransformOperations operations_to;
+ operations_to.AppendScale(sx, sy, sz);
+
+ gfx::Transform matrix_to;
+ matrix_to.Scale3d(sx, sy, sz);
+
+ CheckProgress(-1, matrix_from, matrix_to, operations_from, operations_to);
+ CheckProgress(0, matrix_from, matrix_to, operations_from, operations_to);
+ CheckProgress(0.25, matrix_from, matrix_to, operations_from, operations_to);
+ CheckProgress(0.5, matrix_from, matrix_to, operations_from, operations_to);
+ CheckProgress(1, matrix_from, matrix_to, operations_from, operations_to);
+ CheckProgress(2, matrix_from, matrix_to, operations_from, operations_to);
+}
+
+TEST(TransformOperationTest, BlendWhenTypesDoNotMatch) {
+ double sx1 = 2;
+ double sy1 = 4;
+ double sz1 = 8;
+
+ double dx1 = 1;
+ double dy1 = 2;
+ double dz1 = 3;
+
+ double sx2 = 4;
+ double sy2 = 8;
+ double sz2 = 16;
+
+ double dx2 = 10;
+ double dy2 = 20;
+ double dz2 = 30;
+
+ TransformOperations operations_from;
+ operations_from.AppendScale(sx1, sy1, sz1);
+ operations_from.AppendTranslate(dx1, dy1, dz1);
+
+ TransformOperations operations_to;
+ operations_to.AppendTranslate(dx2, dy2, dz2);
+ operations_to.AppendScale(sx2, sy2, sz2);
+
+ gfx::Transform from;
+ from.Scale3d(sx1, sy1, sz1);
+ from.Translate3d(dx1, dy1, dz1);
+
+ gfx::Transform to;
+ to.Translate3d(dx2, dy2, dz2);
+ to.Scale3d(sx2, sy2, sz2);
+
+ double progress = 0.25;
+
+ gfx::Transform expected = to;
+ expected.Blend(from, progress);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations_to.Blend(operations_from, progress));
+}
+
+TEST(TransformOperationTest, LargeRotationsWithSameAxis) {
+ TransformOperations operations_from;
+ operations_from.AppendRotate(0, 0, 1, 0);
+
+ TransformOperations operations_to;
+ operations_to.AppendRotate(0, 0, 2, 360);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 180);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations_to.Blend(operations_from, progress));
+}
+
+TEST(TransformOperationTest, LargeRotationsWithSameAxisInDifferentDirection) {
+ TransformOperations operations_from;
+ operations_from.AppendRotate(0, 0, 1, 180);
+
+ TransformOperations operations_to;
+ operations_to.AppendRotate(0, 0, -1, 180);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations_to.Blend(operations_from, progress));
+}
+
+TEST(TransformOperationTest, LargeRotationsWithDifferentAxes) {
+ TransformOperations operations_from;
+ operations_from.AppendRotate(0, 0, 1, 175);
+
+ TransformOperations operations_to;
+ operations_to.AppendRotate(0, 1, 0, 175);
+
+ double progress = 0.5;
+ gfx::Transform matrix_from;
+ matrix_from.RotateAbout(gfx::Vector3dF(0, 0, 1), 175);
+
+ gfx::Transform matrix_to;
+ matrix_to.RotateAbout(gfx::Vector3dF(0, 1, 0), 175);
+
+ gfx::Transform expected = matrix_to;
+ expected.Blend(matrix_from, progress);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations_to.Blend(operations_from, progress));
+}
+
+TEST(TransformOperationTest, BlendRotationFromIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendRotate(0, 0, 1, 360);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 180);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendTranslationFromIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendTranslate(2, 2, 2);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.Translate3d(1, 1, 1);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendScaleFromIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendScale(3, 3, 3);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.Scale3d(2, 2, 2);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendSkewFromIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendSkew(2, 2);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.SkewX(1);
+ expected.SkewY(1);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendPerspectiveFromIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendPerspective(1000);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.ApplyPerspectiveDepth(
+ 500 + 0.5 * std::numeric_limits<double>::max());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendRotationToIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendRotate(0, 0, 1, 360);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 180);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, identity_operations[i]->Blend(operations, progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendTranslationToIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendTranslate(2, 2, 2);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.Translate3d(1, 1, 1);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, identity_operations[i]->Blend(operations, progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendScaleToIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendScale(3, 3, 3);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.Scale3d(2, 2, 2);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, identity_operations[i]->Blend(operations, progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendSkewToIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendSkew(2, 2);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.SkewX(1);
+ expected.SkewY(1);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, identity_operations[i]->Blend(operations, progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendPerspectiveToIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendPerspective(1000);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.ApplyPerspectiveDepth(
+ 500 + 0.5 * std::numeric_limits<double>::max());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, identity_operations[i]->Blend(operations, progress));
+ }
+}
+
+} // namespace
+} // namespace cc