diff options
author | vollick@chromium.org <vollick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-05 02:35:20 +0000 |
---|---|---|
committer | vollick@chromium.org <vollick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-05 02:35:20 +0000 |
commit | 4590cc0472b366be6023528f4925f7120beaa3d9 (patch) | |
tree | 9c97f621fd7bccc04a611d15ce242b8b8e098a47 /cc/animation | |
parent | 61a035ae029d96f1fef27dbbdf2b7f91b414f0ea (diff) | |
download | chromium_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.cc | 4 | ||||
-rw-r--r-- | cc/animation/transform_operation.cc | 149 | ||||
-rw-r--r-- | cc/animation/transform_operations_unittest.cc | 210 |
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); |