1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/gfx/interpolated_transform.h"
6
7#include <cmath>
8
9#ifndef M_PI
10#define M_PI 3.14159265358979323846
11#endif
12
13#include "base/logging.h"
14#include "ui/gfx/animation/tween.h"
15
16namespace {
17
18static const double EPSILON = 1e-6;
19
20bool IsMultipleOfNinetyDegrees(double degrees) {
21  double remainder = fabs(fmod(degrees, 90.0));
22  return remainder < EPSILON || 90.0 - remainder < EPSILON;
23}
24
25// Returns false if |degrees| is not a multiple of ninety degrees or if
26// |rotation| is NULL. It does not affect |rotation| in this case. Otherwise
27// *rotation is set to be the appropriate sanitized rotation matrix. That is,
28// the rotation matrix corresponding to |degrees| which has entries that are all
29// either 0, 1 or -1.
30bool MassageRotationIfMultipleOfNinetyDegrees(gfx::Transform* rotation,
31                                              float degrees) {
32  if (!IsMultipleOfNinetyDegrees(degrees) || !rotation)
33    return false;
34
35  gfx::Transform transform;
36  SkMatrix44& m = transform.matrix();
37  float degrees_by_ninety = degrees / 90.0f;
38
39  int n = static_cast<int>(degrees_by_ninety > 0
40      ? floor(degrees_by_ninety + 0.5f)
41      : ceil(degrees_by_ninety - 0.5f));
42
43  n %= 4;
44  if (n < 0)
45    n += 4;
46
47  // n should now be in the range [0, 3]
48  if (n == 1) {
49    m.set3x3( 0,  1,  0,
50             -1,  0,  0,
51              0,  0,  1);
52  } else if (n == 2) {
53    m.set3x3(-1,  0,  0,
54              0, -1,  0,
55              0,  0,  1);
56  } else if (n == 3) {
57    m.set3x3( 0, -1,  0,
58              1,  0,  0,
59              0,  0,  1);
60  }
61
62  *rotation = transform;
63  return true;
64}
65
66} // namespace
67
68namespace ui {
69
70///////////////////////////////////////////////////////////////////////////////
71// InterpolatedTransform
72//
73
74InterpolatedTransform::InterpolatedTransform()
75    : start_time_(0.0f),
76      end_time_(1.0f),
77      reversed_(false) {
78}
79
80InterpolatedTransform::InterpolatedTransform(float start_time,
81                                             float end_time)
82    : start_time_(start_time),
83      end_time_(end_time),
84      reversed_(false) {
85}
86
87InterpolatedTransform::~InterpolatedTransform() {}
88
89gfx::Transform InterpolatedTransform::Interpolate(float t) const {
90  if (reversed_)
91    t = 1.0f - t;
92  gfx::Transform result = InterpolateButDoNotCompose(t);
93  if (child_.get()) {
94    result.ConcatTransform(child_->Interpolate(t));
95  }
96  return result;
97}
98
99void InterpolatedTransform::SetChild(InterpolatedTransform* child) {
100  child_.reset(child);
101}
102
103inline float InterpolatedTransform::ValueBetween(float time,
104                                                 float start_value,
105                                                 float end_value) const {
106  // can't handle NaN
107  DCHECK(time == time && start_time_ == start_time_ && end_time_ == end_time_);
108  if (time != time || start_time_ != start_time_ || end_time_ != end_time_)
109    return start_value;
110
111  // Ok if equal -- we'll get a step function. Note: if end_time_ ==
112  // start_time_ == x, then if none of the numbers are NaN, then it
113  // must be true that time < x or time >= x, so we will return early
114  // due to one of the following if statements.
115  DCHECK(end_time_ >= start_time_);
116
117  if (time < start_time_)
118    return start_value;
119
120  if (time >= end_time_)
121    return end_value;
122
123  float t = (time - start_time_) / (end_time_ - start_time_);
124  return static_cast<float>(
125      gfx::Tween::DoubleValueBetween(t, start_value, end_value));
126}
127
128///////////////////////////////////////////////////////////////////////////////
129// InterpolatedRotation
130//
131
132InterpolatedRotation::InterpolatedRotation(float start_degrees,
133                                           float end_degrees)
134    : InterpolatedTransform(),
135      start_degrees_(start_degrees),
136      end_degrees_(end_degrees) {
137}
138
139InterpolatedRotation::InterpolatedRotation(float start_degrees,
140                                           float end_degrees,
141                                           float start_time,
142                                           float end_time)
143    : InterpolatedTransform(start_time, end_time),
144      start_degrees_(start_degrees),
145      end_degrees_(end_degrees) {
146}
147
148InterpolatedRotation::~InterpolatedRotation() {}
149
150gfx::Transform InterpolatedRotation::InterpolateButDoNotCompose(float t) const {
151  gfx::Transform result;
152  float interpolated_degrees = ValueBetween(t, start_degrees_, end_degrees_);
153  result.Rotate(interpolated_degrees);
154  if (t == 0.0f || t == 1.0f)
155    MassageRotationIfMultipleOfNinetyDegrees(&result, interpolated_degrees);
156  return result;
157}
158
159///////////////////////////////////////////////////////////////////////////////
160// InterpolatedAxisAngleRotation
161//
162
163InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation(
164    const gfx::Vector3dF& axis,
165    float start_degrees,
166    float end_degrees)
167    : InterpolatedTransform(),
168      axis_(axis),
169      start_degrees_(start_degrees),
170      end_degrees_(end_degrees) {
171}
172
173InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation(
174    const gfx::Vector3dF& axis,
175    float start_degrees,
176    float end_degrees,
177    float start_time,
178    float end_time)
179    : InterpolatedTransform(start_time, end_time),
180      axis_(axis),
181      start_degrees_(start_degrees),
182      end_degrees_(end_degrees) {
183}
184
185InterpolatedAxisAngleRotation::~InterpolatedAxisAngleRotation() {}
186
187gfx::Transform
188InterpolatedAxisAngleRotation::InterpolateButDoNotCompose(float t) const {
189  gfx::Transform result;
190  result.RotateAbout(axis_, ValueBetween(t, start_degrees_, end_degrees_));
191  return result;
192}
193
194///////////////////////////////////////////////////////////////////////////////
195// InterpolatedScale
196//
197
198InterpolatedScale::InterpolatedScale(float start_scale, float end_scale)
199    : InterpolatedTransform(),
200      start_scale_(gfx::Point3F(start_scale, start_scale, start_scale)),
201      end_scale_(gfx::Point3F(end_scale, end_scale, end_scale)) {
202}
203
204InterpolatedScale::InterpolatedScale(float start_scale, float end_scale,
205                                     float start_time, float end_time)
206    : InterpolatedTransform(start_time, end_time),
207      start_scale_(gfx::Point3F(start_scale, start_scale, start_scale)),
208      end_scale_(gfx::Point3F(end_scale, end_scale, end_scale)) {
209}
210
211InterpolatedScale::InterpolatedScale(const gfx::Point3F& start_scale,
212                                     const gfx::Point3F& end_scale)
213    : InterpolatedTransform(),
214      start_scale_(start_scale),
215      end_scale_(end_scale) {
216}
217
218InterpolatedScale::InterpolatedScale(const gfx::Point3F& start_scale,
219                                     const gfx::Point3F& end_scale,
220                                     float start_time,
221                                     float end_time)
222    : InterpolatedTransform(start_time, end_time),
223      start_scale_(start_scale),
224      end_scale_(end_scale) {
225}
226
227InterpolatedScale::~InterpolatedScale() {}
228
229gfx::Transform InterpolatedScale::InterpolateButDoNotCompose(float t) const {
230  gfx::Transform result;
231  float scale_x = ValueBetween(t, start_scale_.x(), end_scale_.x());
232  float scale_y = ValueBetween(t, start_scale_.y(), end_scale_.y());
233  // TODO(vollick) 3d xforms.
234  result.Scale(scale_x, scale_y);
235  return result;
236}
237
238///////////////////////////////////////////////////////////////////////////////
239// InterpolatedTranslation
240//
241
242InterpolatedTranslation::InterpolatedTranslation(const gfx::Point& start_pos,
243                                                 const gfx::Point& end_pos)
244    : InterpolatedTransform(),
245      start_pos_(start_pos),
246      end_pos_(end_pos) {
247}
248
249InterpolatedTranslation::InterpolatedTranslation(const gfx::Point& start_pos,
250                                                 const gfx::Point& end_pos,
251                                                 float start_time,
252                                                 float end_time)
253    : InterpolatedTransform(start_time, end_time),
254      start_pos_(start_pos),
255      end_pos_(end_pos) {
256}
257
258InterpolatedTranslation::~InterpolatedTranslation() {}
259
260gfx::Transform
261InterpolatedTranslation::InterpolateButDoNotCompose(float t) const {
262  gfx::Transform result;
263  // TODO(vollick) 3d xforms.
264  result.Translate(ValueBetween(t, start_pos_.x(), end_pos_.x()),
265                      ValueBetween(t, start_pos_.y(), end_pos_.y()));
266  return result;
267}
268
269///////////////////////////////////////////////////////////////////////////////
270// InterpolatedConstantTransform
271//
272
273InterpolatedConstantTransform::InterpolatedConstantTransform(
274  const gfx::Transform& transform)
275    : InterpolatedTransform(),
276  transform_(transform) {
277}
278
279gfx::Transform
280InterpolatedConstantTransform::InterpolateButDoNotCompose(float t) const {
281  return transform_;
282}
283
284InterpolatedConstantTransform::~InterpolatedConstantTransform() {}
285
286///////////////////////////////////////////////////////////////////////////////
287// InterpolatedTransformAboutPivot
288//
289
290InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot(
291  const gfx::Point& pivot,
292  InterpolatedTransform* transform)
293    : InterpolatedTransform() {
294  Init(pivot, transform);
295}
296
297InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot(
298  const gfx::Point& pivot,
299  InterpolatedTransform* transform,
300  float start_time,
301  float end_time)
302    : InterpolatedTransform() {
303  Init(pivot, transform);
304}
305
306InterpolatedTransformAboutPivot::~InterpolatedTransformAboutPivot() {}
307
308gfx::Transform
309InterpolatedTransformAboutPivot::InterpolateButDoNotCompose(float t) const {
310  if (transform_.get()) {
311    return transform_->Interpolate(t);
312  }
313  return gfx::Transform();
314}
315
316void InterpolatedTransformAboutPivot::Init(const gfx::Point& pivot,
317                                           InterpolatedTransform* xform) {
318  gfx::Transform to_pivot;
319  gfx::Transform from_pivot;
320  to_pivot.Translate(-pivot.x(), -pivot.y());
321  from_pivot.Translate(pivot.x(), pivot.y());
322
323  scoped_ptr<InterpolatedTransform> pre_transform(
324    new InterpolatedConstantTransform(to_pivot));
325  scoped_ptr<InterpolatedTransform> post_transform(
326    new InterpolatedConstantTransform(from_pivot));
327
328  pre_transform->SetChild(xform);
329  xform->SetChild(post_transform.release());
330  transform_.reset(pre_transform.release());
331}
332
333InterpolatedMatrixTransform::InterpolatedMatrixTransform(
334    const gfx::Transform& start_transform,
335    const gfx::Transform& end_transform)
336    : InterpolatedTransform() {
337  Init(start_transform, end_transform);
338}
339
340InterpolatedMatrixTransform::InterpolatedMatrixTransform(
341    const gfx::Transform& start_transform,
342    const gfx::Transform& end_transform,
343    float start_time,
344    float end_time)
345    : InterpolatedTransform() {
346  Init(start_transform, end_transform);
347}
348
349InterpolatedMatrixTransform::~InterpolatedMatrixTransform() {}
350
351gfx::Transform
352InterpolatedMatrixTransform::InterpolateButDoNotCompose(float t) const {
353  gfx::DecomposedTransform blended;
354  bool success = gfx::BlendDecomposedTransforms(&blended,
355                                                end_decomp_,
356                                                start_decomp_,
357                                                t);
358  DCHECK(success);
359  return gfx::ComposeTransform(blended);
360}
361
362void InterpolatedMatrixTransform::Init(const gfx::Transform& start_transform,
363                                       const gfx::Transform& end_transform) {
364  bool success = gfx::DecomposeTransform(&start_decomp_, start_transform);
365  DCHECK(success);
366  success = gfx::DecomposeTransform(&end_decomp_, end_transform);
367  DCHECK(success);
368}
369
370} // namespace ui
371