// 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/base/animation/tween.h" namespace { static const float EPSILON = 1e-6f; bool IsMultipleOfNinetyDegrees(float degrees) { float remainder = fabs(fmod(degrees, 90.0f)); return remainder < EPSILON || 90.0f - 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(ui::Transform* rotation, float degrees) { if (!IsMultipleOfNinetyDegrees(degrees) || !rotation) return false; ui::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() {} ui::Transform InterpolatedTransform::Interpolate(float t) const { if (reversed_) t = 1.0f - t; ui::Transform result = InterpolateButDoNotCompose(t); if (child_.get()) { result.ConcatTransform(child_->Interpolate(t)); } return result; } void InterpolatedTransform::SetChild(InterpolatedTransform* child) { child_.reset(child); } bool InterpolatedTransform::FactorTRS(const ui::Transform& transform, gfx::Point* translation, float* rotation, gfx::Point3f* scale) { const SkMatrix44& m = transform.matrix(); float m00 = m.get(0, 0); float m01 = m.get(0, 1); float m10 = m.get(1, 0); float m11 = m.get(1, 1); // A factorable 2D TRS matrix must be of the form: // [ sx*cos_theta -(sy*sin_theta) 0 tx ] // [ sx*sin_theta sy*cos_theta 0 ty ] // [ 0 0 1 0 ] // [ 0 0 0 1 ] if (m.get(0, 2) != 0 || m.get(1, 2) != 0 || m.get(2, 0) != 0 || m.get(2, 1) != 0 || m.get(2, 2) != 1 || m.get(2, 3) != 0 || m.get(3, 0) != 0 || m.get(3, 1) != 0 || m.get(3, 2) != 0 || m.get(3, 3) != 1) { return false; } float scale_x = sqrt(m00 * m00 + m10 * m10); float scale_y = sqrt(m01 * m01 + m11 * m11); if (scale_x == 0 || scale_y == 0) return false; float cos_theta = m00 / scale_x; float sin_theta = m10 / scale_x; if ((fabs(cos_theta - (m11 / scale_y))) > EPSILON || (fabs(sin_theta + (m01 / scale_y))) > EPSILON || (fabs(cos_theta*cos_theta + sin_theta*sin_theta - 1.0f) > EPSILON)) { return false; } float radians = atan2(sin_theta, cos_theta); if (translation) *translation = gfx::Point(m.get(0, 3), m.get(1, 3)); if (rotation) *rotation = radians * 180 / M_PI; if (scale) *scale = gfx::Point3f(scale_x, scale_y, 1.0f); return true; } 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(Tween::ValueBetween(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() {} ui::Transform InterpolatedRotation::InterpolateButDoNotCompose(float t) const { ui::Transform result; float interpolated_degrees = ValueBetween(t, start_degrees_, end_degrees_); result.SetRotate(interpolated_degrees); if (t == 0.0f || t == 1.0f) MassageRotationIfMultipleOfNinetyDegrees(&result, interpolated_degrees); return result; } /////////////////////////////////////////////////////////////////////////////// // InterpolatedAxisAngleRotation // InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation( gfx::Point3f axis, float start_degrees, float end_degrees) : InterpolatedTransform(), axis_(axis), start_degrees_(start_degrees), end_degrees_(end_degrees) { } InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation( gfx::Point3f 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() {} ui::Transform InterpolatedAxisAngleRotation::InterpolateButDoNotCompose(float t) const { ui::Transform result; result.SetRotateAbout(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() {} ui::Transform InterpolatedScale::InterpolateButDoNotCompose(float t) const { ui::Transform result; float scale_x = ValueBetween(t, start_scale_.x(), end_scale_.x()); float scale_y = ValueBetween(t, start_scale_.y(), end_scale_.y()); // TODO(vollick) 3d xforms. result.SetScale(scale_x, scale_y); 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() {} ui::Transform InterpolatedTranslation::InterpolateButDoNotCompose(float t) const { ui::Transform result; // TODO(vollick) 3d xforms. result.SetTranslate(ValueBetween(t, start_pos_.x(), end_pos_.x()), ValueBetween(t, start_pos_.y(), end_pos_.y())); return result; } /////////////////////////////////////////////////////////////////////////////// // InterpolatedConstantTransform // InterpolatedConstantTransform::InterpolatedConstantTransform( const ui::Transform& transform) : InterpolatedTransform(), transform_(transform) { } ui::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() {} ui::Transform InterpolatedTransformAboutPivot::InterpolateButDoNotCompose(float t) const { if (transform_.get()) { return transform_->Interpolate(t); } return Transform(); } void InterpolatedTransformAboutPivot::Init(const gfx::Point& pivot, InterpolatedTransform* xform) { ui::Transform to_pivot; ui::Transform from_pivot; to_pivot.SetTranslate(-pivot.x(), -pivot.y()); from_pivot.SetTranslate(pivot.x(), 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()); } InterpolatedTRSTransform::InterpolatedTRSTransform( const ui::Transform& start_transform, const ui::Transform& end_transform) : InterpolatedTransform() { Init(start_transform, end_transform); } InterpolatedTRSTransform::InterpolatedTRSTransform( const ui::Transform& start_transform, const ui::Transform& end_transform, float start_time, float end_time) : InterpolatedTransform() { Init(start_transform, end_transform); } InterpolatedTRSTransform::~InterpolatedTRSTransform() {} ui::Transform InterpolatedTRSTransform::InterpolateButDoNotCompose(float t) const { if (transform_.get()) { return transform_->Interpolate(t); } return Transform(); } void InterpolatedTRSTransform::Init(const Transform& start_transform, const Transform& end_transform) { gfx::Point start_translation, end_translation; gfx::Point3f start_scale, end_scale; float start_degrees, end_degrees; if (FactorTRS(start_transform, &start_translation, &start_degrees, &start_scale) && FactorTRS(end_transform, &end_translation, &end_degrees, &end_scale)) { scoped_ptr translation( new InterpolatedTranslation(start_translation, end_translation, start_time(), end_time())); scoped_ptr scale( new InterpolatedScale(start_scale, end_scale, start_time(), end_time())); scoped_ptr rotation( new InterpolatedRotation(start_degrees, end_degrees, start_time(), end_time())); rotation->SetChild(translation.release()); scale->SetChild(rotation.release()); transform_.reset(scale.release()); } else { transform_.reset(new InterpolatedConstantTransform(end_transform)); } } } // namespace ui