|  | // Copyright (c) 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 "ui/gfx/interpolated_transform.h" | 
|  |  | 
|  | #include <cmath> | 
|  |  | 
|  | #ifndef M_PI | 
|  | #define M_PI 3.14159265358979323846 | 
|  | #endif | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "ui/gfx/animation/tween.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | static const double EPSILON = 1e-6; | 
|  |  | 
|  | bool IsMultipleOfNinetyDegrees(double degrees) { | 
|  | double remainder = fabs(fmod(degrees, 90.0)); | 
|  | return remainder < EPSILON || 90.0 - remainder < EPSILON; | 
|  | } | 
|  |  | 
|  | // Returns false if |degrees| is not a multiple of ninety degrees or if | 
|  | // |rotation| is NULL. It does not affect |rotation| in this case. Otherwise | 
|  | // *rotation is set to be the appropriate sanitized rotation matrix. That is, | 
|  | // the rotation matrix corresponding to |degrees| which has entries that are all | 
|  | // either 0, 1 or -1. | 
|  | bool MassageRotationIfMultipleOfNinetyDegrees(gfx::Transform* rotation, | 
|  | float degrees) { | 
|  | if (!IsMultipleOfNinetyDegrees(degrees) || !rotation) | 
|  | return false; | 
|  |  | 
|  | gfx::Transform transform; | 
|  | SkMatrix44& m = transform.matrix(); | 
|  | float degrees_by_ninety = degrees / 90.0f; | 
|  |  | 
|  | int n = static_cast<int>(degrees_by_ninety > 0 | 
|  | ? floor(degrees_by_ninety + 0.5f) | 
|  | : ceil(degrees_by_ninety - 0.5f)); | 
|  |  | 
|  | n %= 4; | 
|  | if (n < 0) | 
|  | n += 4; | 
|  |  | 
|  | // n should now be in the range [0, 3] | 
|  | if (n == 1) { | 
|  | m.set3x3( 0,  1,  0, | 
|  | -1,  0,  0, | 
|  | 0,  0,  1); | 
|  | } else if (n == 2) { | 
|  | m.set3x3(-1,  0,  0, | 
|  | 0, -1,  0, | 
|  | 0,  0,  1); | 
|  | } else if (n == 3) { | 
|  | m.set3x3( 0, -1,  0, | 
|  | 1,  0,  0, | 
|  | 0,  0,  1); | 
|  | } | 
|  |  | 
|  | *rotation = transform; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | namespace ui { | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | // InterpolatedTransform | 
|  | // | 
|  |  | 
|  | InterpolatedTransform::InterpolatedTransform() | 
|  | : start_time_(0.0f), | 
|  | end_time_(1.0f), | 
|  | reversed_(false) { | 
|  | } | 
|  |  | 
|  | InterpolatedTransform::InterpolatedTransform(float start_time, | 
|  | float end_time) | 
|  | : start_time_(start_time), | 
|  | end_time_(end_time), | 
|  | reversed_(false) { | 
|  | } | 
|  |  | 
|  | InterpolatedTransform::~InterpolatedTransform() {} | 
|  |  | 
|  | gfx::Transform InterpolatedTransform::Interpolate(float t) const { | 
|  | if (reversed_) | 
|  | t = 1.0f - t; | 
|  | gfx::Transform result = InterpolateButDoNotCompose(t); | 
|  | if (child_.get()) { | 
|  | result.ConcatTransform(child_->Interpolate(t)); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void InterpolatedTransform::SetChild(InterpolatedTransform* child) { | 
|  | child_.reset(child); | 
|  | } | 
|  |  | 
|  | inline float InterpolatedTransform::ValueBetween(float time, | 
|  | float start_value, | 
|  | float end_value) const { | 
|  | // can't handle NaN | 
|  | DCHECK(time == time && start_time_ == start_time_ && end_time_ == end_time_); | 
|  | if (time != time || start_time_ != start_time_ || end_time_ != end_time_) | 
|  | return start_value; | 
|  |  | 
|  | // Ok if equal -- we'll get a step function. Note: if end_time_ == | 
|  | // start_time_ == x, then if none of the numbers are NaN, then it | 
|  | // must be true that time < x or time >= x, so we will return early | 
|  | // due to one of the following if statements. | 
|  | DCHECK(end_time_ >= start_time_); | 
|  |  | 
|  | if (time < start_time_) | 
|  | return start_value; | 
|  |  | 
|  | if (time >= end_time_) | 
|  | return end_value; | 
|  |  | 
|  | float t = (time - start_time_) / (end_time_ - start_time_); | 
|  | return static_cast<float>( | 
|  | gfx::Tween::DoubleValueBetween(t, start_value, end_value)); | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | // InterpolatedRotation | 
|  | // | 
|  |  | 
|  | InterpolatedRotation::InterpolatedRotation(float start_degrees, | 
|  | float end_degrees) | 
|  | : InterpolatedTransform(), | 
|  | start_degrees_(start_degrees), | 
|  | end_degrees_(end_degrees) { | 
|  | } | 
|  |  | 
|  | InterpolatedRotation::InterpolatedRotation(float start_degrees, | 
|  | float end_degrees, | 
|  | float start_time, | 
|  | float end_time) | 
|  | : InterpolatedTransform(start_time, end_time), | 
|  | start_degrees_(start_degrees), | 
|  | end_degrees_(end_degrees) { | 
|  | } | 
|  |  | 
|  | InterpolatedRotation::~InterpolatedRotation() {} | 
|  |  | 
|  | gfx::Transform InterpolatedRotation::InterpolateButDoNotCompose(float t) const { | 
|  | gfx::Transform result; | 
|  | float interpolated_degrees = ValueBetween(t, start_degrees_, end_degrees_); | 
|  | result.Rotate(interpolated_degrees); | 
|  | if (t == 0.0f || t == 1.0f) | 
|  | MassageRotationIfMultipleOfNinetyDegrees(&result, interpolated_degrees); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | // InterpolatedAxisAngleRotation | 
|  | // | 
|  |  | 
|  | InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation( | 
|  | const gfx::Vector3dF& axis, | 
|  | float start_degrees, | 
|  | float end_degrees) | 
|  | : InterpolatedTransform(), | 
|  | axis_(axis), | 
|  | start_degrees_(start_degrees), | 
|  | end_degrees_(end_degrees) { | 
|  | } | 
|  |  | 
|  | InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation( | 
|  | const gfx::Vector3dF& axis, | 
|  | float start_degrees, | 
|  | float end_degrees, | 
|  | float start_time, | 
|  | float end_time) | 
|  | : InterpolatedTransform(start_time, end_time), | 
|  | axis_(axis), | 
|  | start_degrees_(start_degrees), | 
|  | end_degrees_(end_degrees) { | 
|  | } | 
|  |  | 
|  | InterpolatedAxisAngleRotation::~InterpolatedAxisAngleRotation() {} | 
|  |  | 
|  | gfx::Transform | 
|  | InterpolatedAxisAngleRotation::InterpolateButDoNotCompose(float t) const { | 
|  | gfx::Transform result; | 
|  | result.RotateAbout(axis_, ValueBetween(t, start_degrees_, end_degrees_)); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | // InterpolatedScale | 
|  | // | 
|  |  | 
|  | InterpolatedScale::InterpolatedScale(float start_scale, float end_scale) | 
|  | : InterpolatedTransform(), | 
|  | start_scale_(gfx::Point3F(start_scale, start_scale, start_scale)), | 
|  | end_scale_(gfx::Point3F(end_scale, end_scale, end_scale)) { | 
|  | } | 
|  |  | 
|  | InterpolatedScale::InterpolatedScale(float start_scale, float end_scale, | 
|  | float start_time, float end_time) | 
|  | : InterpolatedTransform(start_time, end_time), | 
|  | start_scale_(gfx::Point3F(start_scale, start_scale, start_scale)), | 
|  | end_scale_(gfx::Point3F(end_scale, end_scale, end_scale)) { | 
|  | } | 
|  |  | 
|  | InterpolatedScale::InterpolatedScale(const gfx::Point3F& start_scale, | 
|  | const gfx::Point3F& end_scale) | 
|  | : InterpolatedTransform(), | 
|  | start_scale_(start_scale), | 
|  | end_scale_(end_scale) { | 
|  | } | 
|  |  | 
|  | InterpolatedScale::InterpolatedScale(const gfx::Point3F& start_scale, | 
|  | const gfx::Point3F& end_scale, | 
|  | float start_time, | 
|  | float end_time) | 
|  | : InterpolatedTransform(start_time, end_time), | 
|  | start_scale_(start_scale), | 
|  | end_scale_(end_scale) { | 
|  | } | 
|  |  | 
|  | InterpolatedScale::~InterpolatedScale() {} | 
|  |  | 
|  | gfx::Transform InterpolatedScale::InterpolateButDoNotCompose(float t) const { | 
|  | gfx::Transform result; | 
|  | float scale_x = ValueBetween(t, start_scale_.x(), end_scale_.x()); | 
|  | float scale_y = ValueBetween(t, start_scale_.y(), end_scale_.y()); | 
|  | float scale_z = ValueBetween(t, start_scale_.z(), end_scale_.z()); | 
|  | result.Scale3d(scale_x, scale_y, scale_z); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | // InterpolatedTranslation | 
|  | // | 
|  |  | 
|  | InterpolatedTranslation::InterpolatedTranslation(const gfx::PointF& start_pos, | 
|  | const gfx::PointF& end_pos) | 
|  | : InterpolatedTransform(), start_pos_(start_pos), end_pos_(end_pos) {} | 
|  |  | 
|  | InterpolatedTranslation::InterpolatedTranslation(const gfx::PointF& start_pos, | 
|  | const gfx::PointF& end_pos, | 
|  | float start_time, | 
|  | float end_time) | 
|  | : InterpolatedTransform(start_time, end_time), | 
|  | start_pos_(start_pos), | 
|  | end_pos_(end_pos) {} | 
|  |  | 
|  | InterpolatedTranslation::InterpolatedTranslation(const gfx::Point3F& start_pos, | 
|  | const gfx::Point3F& end_pos) | 
|  | : InterpolatedTransform(), start_pos_(start_pos), end_pos_(end_pos) { | 
|  | } | 
|  |  | 
|  | InterpolatedTranslation::InterpolatedTranslation(const gfx::Point3F& start_pos, | 
|  | const gfx::Point3F& end_pos, | 
|  | float start_time, | 
|  | float end_time) | 
|  | : InterpolatedTransform(start_time, end_time), | 
|  | start_pos_(start_pos), | 
|  | end_pos_(end_pos) { | 
|  | } | 
|  |  | 
|  | InterpolatedTranslation::~InterpolatedTranslation() {} | 
|  |  | 
|  | gfx::Transform | 
|  | InterpolatedTranslation::InterpolateButDoNotCompose(float t) const { | 
|  | gfx::Transform result; | 
|  | result.Translate3d(ValueBetween(t, start_pos_.x(), end_pos_.x()), | 
|  | ValueBetween(t, start_pos_.y(), end_pos_.y()), | 
|  | ValueBetween(t, start_pos_.z(), end_pos_.z())); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | // InterpolatedConstantTransform | 
|  | // | 
|  |  | 
|  | InterpolatedConstantTransform::InterpolatedConstantTransform( | 
|  | const gfx::Transform& transform) | 
|  | : InterpolatedTransform(), | 
|  | transform_(transform) { | 
|  | } | 
|  |  | 
|  | gfx::Transform | 
|  | InterpolatedConstantTransform::InterpolateButDoNotCompose(float t) const { | 
|  | return transform_; | 
|  | } | 
|  |  | 
|  | InterpolatedConstantTransform::~InterpolatedConstantTransform() {} | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | // InterpolatedTransformAboutPivot | 
|  | // | 
|  |  | 
|  | InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot( | 
|  | const gfx::Point& pivot, | 
|  | InterpolatedTransform* transform) | 
|  | : InterpolatedTransform() { | 
|  | Init(pivot, transform); | 
|  | } | 
|  |  | 
|  | InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot( | 
|  | const gfx::Point& pivot, | 
|  | InterpolatedTransform* transform, | 
|  | float start_time, | 
|  | float end_time) | 
|  | : InterpolatedTransform() { | 
|  | Init(pivot, transform); | 
|  | } | 
|  |  | 
|  | InterpolatedTransformAboutPivot::~InterpolatedTransformAboutPivot() {} | 
|  |  | 
|  | gfx::Transform | 
|  | InterpolatedTransformAboutPivot::InterpolateButDoNotCompose(float t) const { | 
|  | if (transform_.get()) { | 
|  | return transform_->Interpolate(t); | 
|  | } | 
|  | return gfx::Transform(); | 
|  | } | 
|  |  | 
|  | void InterpolatedTransformAboutPivot::Init(const gfx::Point& pivot, | 
|  | InterpolatedTransform* xform) { | 
|  | gfx::Transform to_pivot; | 
|  | gfx::Transform from_pivot; | 
|  | to_pivot.Translate(SkIntToMScalar(-pivot.x()), SkIntToMScalar(-pivot.y())); | 
|  | from_pivot.Translate(SkIntToMScalar(pivot.x()), SkIntToMScalar(pivot.y())); | 
|  |  | 
|  | scoped_ptr<InterpolatedTransform> pre_transform( | 
|  | new InterpolatedConstantTransform(to_pivot)); | 
|  | scoped_ptr<InterpolatedTransform> post_transform( | 
|  | new InterpolatedConstantTransform(from_pivot)); | 
|  |  | 
|  | pre_transform->SetChild(xform); | 
|  | xform->SetChild(post_transform.release()); | 
|  | transform_.reset(pre_transform.release()); | 
|  | } | 
|  |  | 
|  | InterpolatedMatrixTransform::InterpolatedMatrixTransform( | 
|  | const gfx::Transform& start_transform, | 
|  | const gfx::Transform& end_transform) | 
|  | : InterpolatedTransform() { | 
|  | Init(start_transform, end_transform); | 
|  | } | 
|  |  | 
|  | InterpolatedMatrixTransform::InterpolatedMatrixTransform( | 
|  | const gfx::Transform& start_transform, | 
|  | const gfx::Transform& end_transform, | 
|  | float start_time, | 
|  | float end_time) | 
|  | : InterpolatedTransform() { | 
|  | Init(start_transform, end_transform); | 
|  | } | 
|  |  | 
|  | InterpolatedMatrixTransform::~InterpolatedMatrixTransform() {} | 
|  |  | 
|  | gfx::Transform | 
|  | InterpolatedMatrixTransform::InterpolateButDoNotCompose(float t) const { | 
|  | gfx::DecomposedTransform blended; | 
|  | bool success = gfx::BlendDecomposedTransforms(&blended, | 
|  | end_decomp_, | 
|  | start_decomp_, | 
|  | t); | 
|  | DCHECK(success); | 
|  | return gfx::ComposeTransform(blended); | 
|  | } | 
|  |  | 
|  | void InterpolatedMatrixTransform::Init(const gfx::Transform& start_transform, | 
|  | const gfx::Transform& end_transform) { | 
|  | bool success = gfx::DecomposeTransform(&start_decomp_, start_transform); | 
|  | DCHECK(success); | 
|  | success = gfx::DecomposeTransform(&end_decomp_, end_transform); | 
|  | DCHECK(success); | 
|  | } | 
|  |  | 
|  | } // namespace ui |