summaryrefslogtreecommitdiffstats
path: root/cc/animation
diff options
context:
space:
mode:
authorvollick@chromium.org <vollick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-05 02:35:20 +0000
committervollick@chromium.org <vollick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-05 02:35:20 +0000
commit4590cc0472b366be6023528f4925f7120beaa3d9 (patch)
tree9c97f621fd7bccc04a611d15ce242b8b8e098a47 /cc/animation
parent61a035ae029d96f1fef27dbbdf2b7f91b414f0ea (diff)
downloadchromium_src-4590cc0472b366be6023528f4925f7120beaa3d9.zip
chromium_src-4590cc0472b366be6023528f4925f7120beaa3d9.tar.gz
chromium_src-4590cc0472b366be6023528f4925f7120beaa3d9.tar.bz2
Allow the computation of inflated bounds for non matrix transform operations.
This CL expands the list of transform operations for which we may compute inflated bounds for animation. R=hartmanng@chromium.org,ajuma@chromium.org BUG= Review URL: https://codereview.chromium.org/55763004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@232889 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'cc/animation')
-rw-r--r--cc/animation/layer_animation_controller_unittest.cc4
-rw-r--r--cc/animation/transform_operation.cc149
-rw-r--r--cc/animation/transform_operations_unittest.cc210
3 files changed, 206 insertions, 157 deletions
diff --git a/cc/animation/layer_animation_controller_unittest.cc b/cc/animation/layer_animation_controller_unittest.cc
index 0505c67..c3387f48 100644
--- a/cc/animation/layer_animation_controller_unittest.cc
+++ b/cc/animation/layer_animation_controller_unittest.cc
@@ -1260,9 +1260,11 @@ TEST(LayerAnimationControllerTest, AnimatedBounds) {
scoped_ptr<KeyframedTransformAnimationCurve> curve3(
KeyframedTransformAnimationCurve::Create());
TransformOperations operations3;
+ gfx::Transform transform3;
+ transform3.Scale3d(1.0, 2.0, 3.0);
curve3->AddKeyframe(TransformKeyframe::Create(
0.0, operations3, scoped_ptr<TimingFunction>()));
- operations3.AppendSkew(1.0, 2.0);
+ operations3.AppendMatrix(transform3);
curve3->AddKeyframe(TransformKeyframe::Create(
1.0, operations3, scoped_ptr<TimingFunction>()));
animation = Animation::Create(
diff --git a/cc/animation/transform_operation.cc b/cc/animation/transform_operation.cc
index 889f7bd..e4d0dad 100644
--- a/cc/animation/transform_operation.cc
+++ b/cc/animation/transform_operation.cc
@@ -13,7 +13,9 @@
#include "base/logging.h"
#include "cc/animation/transform_operation.h"
+#include "cc/animation/transform_operations.h"
#include "ui/gfx/box_f.h"
+#include "ui/gfx/transform_util.h"
#include "ui/gfx/vector3d_f.h"
namespace {
@@ -198,30 +200,6 @@ bool TransformOperation::BlendTransformOperations(
return true;
}
-static void ApplyScaleToBox(float x_scale,
- float y_scale,
- float z_scale,
- gfx::BoxF* box) {
- if (x_scale < 0)
- box->set_x(-box->right());
- if (y_scale < 0)
- box->set_y(-box->bottom());
- if (z_scale < 0)
- box->set_z(-box->front());
- box->Scale(std::abs(x_scale), std::abs(y_scale), std::abs(z_scale));
-}
-
-static void UnionBoxWithZeroScale(gfx::BoxF* box) {
- float min_x = std::min(box->x(), 0.f);
- float min_y = std::min(box->y(), 0.f);
- float min_z = std::min(box->z(), 0.f);
- float max_x = std::max(box->right(), 0.f);
- float max_y = std::max(box->bottom(), 0.f);
- float max_z = std::max(box->front(), 0.f);
- *box = gfx::BoxF(
- min_x, min_y, min_z, max_x - min_x, max_y - min_y, max_z - min_z);
-}
-
// If p = (px, py) is a point in the plane being rotated about (0, 0, nz), this
// function computes the angles we would have to rotate from p to get to
// (length(p), 0), (-length(p), 0), (0, length(p)), (0, -length(p)). If nz is
@@ -250,20 +228,6 @@ static float DegreesToRadians(float degrees) {
return (M_PI * degrees) / 180.f;
}
-// Div by zero doesn't always result in Inf as you might hope, so we'll do this
-// explicitly here.
-static float SafeDivide(float numerator, float denominator) {
- if (numerator == 0.f)
- return 0.f;
-
- if (denominator == 0.f) {
- return numerator > 0.f ? std::numeric_limits<float>::infinity()
- : -std::numeric_limits<float>::infinity();
- }
-
- return numerator / denominator;
-}
-
static void BoundingBoxForArc(const gfx::Point3F& point,
const TransformOperation* from,
const TransformOperation* to,
@@ -291,6 +255,16 @@ static void BoundingBoxForArc(const gfx::Point3F& point,
SkMScalar from_angle = from ? from->rotate.angle : 0.f;
SkMScalar to_angle = to ? to->rotate.angle : 0.f;
+ // If the axes of rotation are pointing in opposite directions, we need to
+ // flip one of the angles. Note, if both |from| and |to| exist, then axis will
+ // correspond to |from|.
+ if (from && to) {
+ gfx::Vector3dF other_axis(
+ to->rotate.axis.x, to->rotate.axis.y, to->rotate.axis.z);
+ if (gfx::DotProduct(axis, other_axis) < 0.f)
+ to_angle *= -1.f;
+ }
+
float min_degrees =
SkMScalarToFloat(BlendSkMScalars(from_angle, to_angle, min_progress));
float max_degrees =
@@ -360,16 +334,11 @@ static void BoundingBoxForArc(const gfx::Point3F& point,
double phi_x = atan2(gfx::DotProduct(v2, vx), gfx::DotProduct(v1, vx));
double phi_z = atan2(gfx::DotProduct(v2, vz), gfx::DotProduct(v1, vz));
- // NB: it is fine if the denominators here are zero and these values go to
- // infinity; atan can handle it.
- double tan_theta1 = SafeDivide(normal.y(), (normal.x() * normal.z()));
- double tan_theta2 = SafeDivide(-normal.z(), (normal.x() * normal.y()));
-
- candidates[0] = atan(tan_theta1) + phi_x;
+ candidates[0] = atan2(normal.y(), normal.x() * normal.z()) + phi_x;
candidates[1] = candidates[0] + M_PI;
- candidates[2] = atan(tan_theta2) + phi_x;
+ candidates[2] = atan2(-normal.z(), normal.x() * normal.y()) + phi_x;
candidates[3] = candidates[2] + M_PI;
- candidates[4] = atan(-tan_theta1) + phi_z;
+ candidates[4] = atan2(normal.y(), -normal.x() * normal.z()) + phi_z;
candidates[5] = candidates[4] + M_PI;
}
@@ -415,78 +384,36 @@ bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box,
interpolation_type = to->type;
switch (interpolation_type) {
- case TransformOperation::TransformOperationTranslate: {
- SkMScalar from_x, from_y, from_z;
- if (is_identity_from) {
- from_x = from_y = from_z = 0.0;
- } else {
- from_x = from->translate.x;
- from_y = from->translate.y;
- from_z = from->translate.z;
- }
- SkMScalar to_x, to_y, to_z;
- if (is_identity_to) {
- to_x = to_y = to_z = 0.0;
- } else {
- to_x = to->translate.x;
- to_y = to->translate.y;
- to_z = to->translate.z;
- }
+ case TransformOperation::TransformOperationIdentity:
*bounds = box;
- *bounds += gfx::Vector3dF(BlendSkMScalars(from_x, to_x, min_progress),
- BlendSkMScalars(from_y, to_y, min_progress),
- BlendSkMScalars(from_z, to_z, min_progress));
- gfx::BoxF bounds_max = box;
- bounds_max += gfx::Vector3dF(BlendSkMScalars(from_x, to_x, max_progress),
- BlendSkMScalars(from_y, to_y, max_progress),
- BlendSkMScalars(from_z, to_z, max_progress));
- bounds->Union(bounds_max);
return true;
- }
+ case TransformOperation::TransformOperationTranslate:
+ case TransformOperation::TransformOperationSkew:
+ case TransformOperation::TransformOperationPerspective:
case TransformOperation::TransformOperationScale: {
- SkMScalar from_x, from_y, from_z;
- if (is_identity_from) {
- from_x = from_y = from_z = 1.0;
- } else {
- from_x = from->scale.x;
- from_y = from->scale.y;
- from_z = from->scale.z;
- }
- SkMScalar to_x, to_y, to_z;
- if (is_identity_to) {
- to_x = to_y = to_z = 1.0;
- } else {
- to_x = to->scale.x;
- to_y = to->scale.y;
- to_z = to->scale.z;
- }
+ gfx::Transform from_transform;
+ gfx::Transform to_transform;
+ if (!BlendTransformOperations(from, to, min_progress, &from_transform) ||
+ !BlendTransformOperations(from, to, max_progress, &to_transform))
+ return false;
+
*bounds = box;
- ApplyScaleToBox(
- SkMScalarToFloat(BlendSkMScalars(from_x, to_x, min_progress)),
- SkMScalarToFloat(BlendSkMScalars(from_y, to_y, min_progress)),
- SkMScalarToFloat(BlendSkMScalars(from_z, to_z, min_progress)),
- bounds);
- gfx::BoxF bounds_max = box;
- ApplyScaleToBox(
- SkMScalarToFloat(BlendSkMScalars(from_x, to_x, max_progress)),
- SkMScalarToFloat(BlendSkMScalars(from_y, to_y, max_progress)),
- SkMScalarToFloat(BlendSkMScalars(from_z, to_z, max_progress)),
- &bounds_max);
- if (!bounds->IsEmpty() && !bounds_max.IsEmpty()) {
- bounds->Union(bounds_max);
- } else if (!bounds->IsEmpty()) {
- UnionBoxWithZeroScale(bounds);
- } else if (!bounds_max.IsEmpty()) {
- UnionBoxWithZeroScale(&bounds_max);
- *bounds = bounds_max;
- }
+ from_transform.TransformBox(bounds);
+
+ gfx::BoxF to_box = box;
+ to_transform.TransformBox(&to_box);
+ bounds->ExpandTo(to_box);
return true;
}
- case TransformOperation::TransformOperationIdentity:
- *bounds = box;
- return true;
case TransformOperation::TransformOperationRotate: {
+ SkMScalar axis_x = 0;
+ SkMScalar axis_y = 0;
+ SkMScalar axis_z = 1;
+ SkMScalar from_angle = 0;
+ if (!ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle))
+ return false;
+
bool first_point = true;
for (int i = 0; i < 8; ++i) {
gfx::Point3F corner = box.origin();
@@ -504,8 +431,6 @@ bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box,
}
return true;
}
- case TransformOperation::TransformOperationSkew:
- case TransformOperation::TransformOperationPerspective:
case TransformOperation::TransformOperationMatrix:
return false;
}
diff --git a/cc/animation/transform_operations_unittest.cc b/cc/animation/transform_operations_unittest.cc
index 49b208e..831ce71 100644
--- a/cc/animation/transform_operations_unittest.cc
+++ b/cc/animation/transform_operations_unittest.cc
@@ -11,6 +11,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/box_f.h"
+#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/vector3d_f.h"
namespace cc {
@@ -886,6 +887,30 @@ TEST(TransformOperationTest, BlendedBoundsForRotationAllExtrema) {
bounds.ToString());
}
+TEST(TransformOperationTest, BlendedBoundsForRotationDifferentAxes) {
+ // We can handle rotations about a single axis. If the axes are different,
+ // we revert to matrix interpolation for which inflated bounds cannot be
+ // computed.
+ TransformOperations operations_from;
+ operations_from.AppendRotate(1.f, 1.f, 1.f, 30.f);
+ TransformOperations operations_to_same;
+ operations_to_same.AppendRotate(1.f, 1.f, 1.f, 390.f);
+ TransformOperations operations_to_opposite;
+ operations_to_opposite.AppendRotate(-1.f, -1.f, -1.f, 390.f);
+ TransformOperations operations_to_different;
+ operations_to_different.AppendRotate(1.f, 3.f, 1.f, 390.f);
+
+ gfx::BoxF box(1.f, 0.f, 0.f, 0.f, 0.f, 0.f);
+ gfx::BoxF bounds;
+
+ EXPECT_TRUE(operations_to_same.BlendedBoundsForBox(
+ box, operations_from, 0.f, 1.f, &bounds));
+ EXPECT_TRUE(operations_to_opposite.BlendedBoundsForBox(
+ box, operations_from, 0.f, 1.f, &bounds));
+ EXPECT_FALSE(operations_to_different.BlendedBoundsForBox(
+ box, operations_from, 0.f, 1.f, &bounds));
+}
+
TEST(TransformOperationTest, BlendedBoundsForRotationPointOnAxis) {
// Checks that if the point to rotate is sitting on the axis of rotation, that
// it does not get affected.
@@ -968,7 +993,80 @@ struct TestProgress {
float max_progress;
};
-TEST(TransformOperationsTest, BlendedBoundsForRotationEmpiricalTests) {
+static void ExpectBoxesApproximatelyEqual(const gfx::BoxF& lhs,
+ const gfx::BoxF& rhs,
+ float tolerance) {
+ EXPECT_NEAR(lhs.x(), rhs.x(), tolerance);
+ EXPECT_NEAR(lhs.y(), rhs.y(), tolerance);
+ EXPECT_NEAR(lhs.z(), rhs.z(), tolerance);
+ EXPECT_NEAR(lhs.width(), rhs.width(), tolerance);
+ EXPECT_NEAR(lhs.height(), rhs.height(), tolerance);
+ EXPECT_NEAR(lhs.depth(), rhs.depth(), tolerance);
+}
+
+static void EmpiricallyTestBounds(const TransformOperations& from,
+ const TransformOperations& to,
+ SkMScalar min_progress,
+ SkMScalar max_progress,
+ bool test_containment_only) {
+ gfx::BoxF box(200.f, 500.f, 100.f, 100.f, 300.f, 200.f);
+ gfx::BoxF bounds;
+ EXPECT_TRUE(
+ to.BlendedBoundsForBox(box, from, min_progress, max_progress, &bounds));
+
+ bool first_time = true;
+ gfx::BoxF empirical_bounds;
+ static const size_t kNumSteps = 10;
+ for (size_t step = 0; step < kNumSteps; ++step) {
+ float t = step / (kNumSteps - 1.f);
+ t = gfx::Tween::FloatValueBetween(t, min_progress, max_progress);
+ gfx::Transform partial_transform = to.Blend(from, t);
+ gfx::BoxF transformed = box;
+ partial_transform.TransformBox(&transformed);
+
+ if (first_time) {
+ empirical_bounds = transformed;
+ first_time = false;
+ } else {
+ empirical_bounds.Union(transformed);
+ }
+ }
+
+ if (test_containment_only) {
+ gfx::BoxF unified_bounds = bounds;
+ unified_bounds.Union(empirical_bounds);
+ // Convert to the screen space rects these boxes represent.
+ gfx::Rect bounds_rect = ToEnclosingRect(
+ gfx::RectF(bounds.x(), bounds.y(), bounds.width(), bounds.height()));
+ gfx::Rect unified_bounds_rect =
+ ToEnclosingRect(gfx::RectF(unified_bounds.x(),
+ unified_bounds.y(),
+ unified_bounds.width(),
+ unified_bounds.height()));
+ EXPECT_EQ(bounds_rect.ToString(), unified_bounds_rect.ToString());
+ } else {
+ // Our empirical estimate will be a little rough since we're only doing
+ // 100 samples.
+ static const float kTolerance = 1e-2f;
+ ExpectBoxesApproximatelyEqual(empirical_bounds, bounds, kTolerance);
+ }
+}
+
+static void EmpiricallyTestBoundsEquality(const TransformOperations& from,
+ const TransformOperations& to,
+ SkMScalar min_progress,
+ SkMScalar max_progress) {
+ EmpiricallyTestBounds(from, to, min_progress, max_progress, false);
+}
+
+static void EmpiricallyTestBoundsContainment(const TransformOperations& from,
+ const TransformOperations& to,
+ SkMScalar min_progress,
+ SkMScalar max_progress) {
+ EmpiricallyTestBounds(from, to, min_progress, max_progress, true);
+}
+
+TEST(TransformOperationTest, BlendedBoundsForRotationEmpiricalTests) {
// Sets up various axis angle combinations, computes the bounding box and
// empirically tests that the transformed bounds are indeed contained by the
// computed bounding box.
@@ -1012,7 +1110,6 @@ TEST(TransformOperationsTest, BlendedBoundsForRotationEmpiricalTests) {
{ -.25f, 1.25f },
};
- size_t num_steps = 150;
for (size_t i = 0; i < arraysize(axes); ++i) {
for (size_t j = 0; j < arraysize(angles); ++j) {
for (size_t k = 0; k < arraysize(progress); ++k) {
@@ -1023,48 +1120,10 @@ TEST(TransformOperationsTest, BlendedBoundsForRotationEmpiricalTests) {
operations_from.AppendRotate(x, y, z, angles[j].theta_from);
TransformOperations operations_to;
operations_to.AppendRotate(x, y, z, angles[j].theta_to);
-
- gfx::BoxF box(2.f, 5.f, 6.f, 1.f, 3.f, 2.f);
- gfx::BoxF bounds;
-
- EXPECT_TRUE(operations_to.BlendedBoundsForBox(box,
- operations_from,
- progress[k].min_progress,
- progress[k].max_progress,
- &bounds));
- bool first_point = true;
- gfx::BoxF empirical_bounds;
- for (size_t step = 0; step < num_steps; ++step) {
- float t = step / (num_steps - 1.f);
- t = gfx::Tween::FloatValueBetween(
- t, progress[k].min_progress, progress[k].max_progress);
- gfx::Transform partial_rotation =
- operations_to.Blend(operations_from, t);
-
- for (int corner = 0; corner < 8; ++corner) {
- gfx::Point3F point = box.origin();
- point += gfx::Vector3dF(corner & 1 ? box.width() : 0.f,
- corner & 2 ? box.height() : 0.f,
- corner & 4 ? box.depth() : 0.f);
- partial_rotation.TransformPoint(&point);
- if (first_point) {
- empirical_bounds.set_origin(point);
- first_point = false;
- } else {
- empirical_bounds.ExpandTo(point);
- }
- }
- }
-
- // Our empirical estimate will be a little rough since we're only doing
- // 100 samples.
- static const float kTolerance = 1e-2f;
- EXPECT_NEAR(empirical_bounds.x(), bounds.x(), kTolerance);
- EXPECT_NEAR(empirical_bounds.y(), bounds.y(), kTolerance);
- EXPECT_NEAR(empirical_bounds.z(), bounds.z(), kTolerance);
- EXPECT_NEAR(empirical_bounds.width(), bounds.width(), kTolerance);
- EXPECT_NEAR(empirical_bounds.height(), bounds.height(), kTolerance);
- EXPECT_NEAR(empirical_bounds.depth(), bounds.depth(), kTolerance);
+ EmpiricallyTestBoundsContainment(operations_from,
+ operations_to,
+ progress[k].min_progress,
+ progress[k].max_progress);
}
}
}
@@ -1097,6 +1156,69 @@ TEST(TransformOperationTest, PerspectiveMatrixAndTransformBlendingEquivalency) {
}
}
+struct TestPerspectiveDepths {
+ float from_depth;
+ float to_depth;
+};
+
+TEST(TransformOperationTest, BlendedBoundsForPerspective) {
+ TestPerspectiveDepths perspective_depths[] = {
+ { 600.f, 400.f },
+ { 800.f, 1000.f },
+ { 800.f, std::numeric_limits<float>::infinity() },
+ };
+
+ TestProgress progress[] = {
+ { 0.f, 1.f },
+ { -0.1f, 1.1f },
+ };
+
+ for (size_t i = 0; i < arraysize(perspective_depths); ++i) {
+ for (size_t j = 0; j < arraysize(progress); ++j) {
+ TransformOperations operations_from;
+ operations_from.AppendPerspective(perspective_depths[i].from_depth);
+ TransformOperations operations_to;
+ operations_to.AppendPerspective(perspective_depths[i].to_depth);
+ EmpiricallyTestBoundsEquality(operations_from,
+ operations_to,
+ progress[j].min_progress,
+ progress[j].max_progress);
+ }
+ }
+}
+
+struct TestSkews {
+ float from_x;
+ float from_y;
+ float to_x;
+ float to_y;
+};
+
+TEST(TransformOperationTest, BlendedBoundsForSkew) {
+ TestSkews skews[] = {
+ { 1.f, 0.5f, 0.5f, 1.f },
+ { 2.f, 1.f, 0.5f, 0.5f },
+ };
+
+ TestProgress progress[] = {
+ { 0.f, 1.f },
+ { -0.1f, 1.1f },
+ };
+
+ for (size_t i = 0; i < arraysize(skews); ++i) {
+ for (size_t j = 0; j < arraysize(progress); ++j) {
+ TransformOperations operations_from;
+ operations_from.AppendSkew(skews[i].from_x, skews[i].from_y);
+ TransformOperations operations_to;
+ operations_to.AppendSkew(skews[i].to_x, skews[i].to_y);
+ EmpiricallyTestBoundsEquality(operations_from,
+ operations_to,
+ progress[j].min_progress,
+ progress[j].max_progress);
+ }
+ }
+}
+
TEST(TransformOperationTest, BlendedBoundsForSequence) {
TransformOperations operations_from;
operations_from.AppendTranslate(2.0, 4.0, -1.0);