// 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 #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(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( 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::Point& start_pos, const gfx::Point& end_pos) : InterpolatedTransform(), start_pos_(start_pos), end_pos_(end_pos) { } InterpolatedTranslation::InterpolatedTranslation(const gfx::Point& start_pos, const gfx::Point& 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 pre_transform( new InterpolatedConstantTransform(to_pivot)); scoped_ptr 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