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