blob: 54ecba93f489f9e7be551795b884b4d71b5a7089 [file] [log] [blame]
// 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/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<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() {}
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<float>(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<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());
}
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<InterpolatedTranslation> translation(
new InterpolatedTranslation(start_translation, end_translation,
start_time(), end_time()));
scoped_ptr<InterpolatedScale> scale(
new InterpolatedScale(start_scale, end_scale,
start_time(), end_time()));
scoped_ptr<InterpolatedRotation> 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