1 // Copyright 2013 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 // Needed on Windows to get |M_PI| from <cmath> 6 #ifdef _WIN32 7 #define _USE_MATH_DEFINES 8 #endif 9 10 #include <algorithm> 11 #include <cmath> 12 #include <limits> 13 14 #include "base/logging.h" 15 #include "cc/animation/transform_operation.h" 16 #include "cc/animation/transform_operations.h" 17 #include "ui/gfx/box_f.h" 18 #include "ui/gfx/transform_util.h" 19 #include "ui/gfx/vector3d_f.h" 20 21 namespace { 22 const SkMScalar kAngleEpsilon = 1e-4f; 23 } 24 25 namespace cc { 26 27 bool TransformOperation::IsIdentity() const { 28 return matrix.IsIdentity(); 29 } 30 31 static bool IsOperationIdentity(const TransformOperation* operation) { 32 return !operation || operation->IsIdentity(); 33 } 34 35 static bool ShareSameAxis(const TransformOperation* from, 36 const TransformOperation* to, 37 SkMScalar* axis_x, 38 SkMScalar* axis_y, 39 SkMScalar* axis_z, 40 SkMScalar* angle_from) { 41 if (IsOperationIdentity(from) && IsOperationIdentity(to)) 42 return false; 43 44 if (IsOperationIdentity(from) && !IsOperationIdentity(to)) { 45 *axis_x = to->rotate.axis.x; 46 *axis_y = to->rotate.axis.y; 47 *axis_z = to->rotate.axis.z; 48 *angle_from = 0; 49 return true; 50 } 51 52 if (!IsOperationIdentity(from) && IsOperationIdentity(to)) { 53 *axis_x = from->rotate.axis.x; 54 *axis_y = from->rotate.axis.y; 55 *axis_z = from->rotate.axis.z; 56 *angle_from = from->rotate.angle; 57 return true; 58 } 59 60 SkMScalar length_2 = from->rotate.axis.x * from->rotate.axis.x + 61 from->rotate.axis.y * from->rotate.axis.y + 62 from->rotate.axis.z * from->rotate.axis.z; 63 SkMScalar other_length_2 = to->rotate.axis.x * to->rotate.axis.x + 64 to->rotate.axis.y * to->rotate.axis.y + 65 to->rotate.axis.z * to->rotate.axis.z; 66 67 if (length_2 <= kAngleEpsilon || other_length_2 <= kAngleEpsilon) 68 return false; 69 70 SkMScalar dot = to->rotate.axis.x * from->rotate.axis.x + 71 to->rotate.axis.y * from->rotate.axis.y + 72 to->rotate.axis.z * from->rotate.axis.z; 73 SkMScalar error = 74 std::abs(SK_MScalar1 - (dot * dot) / (length_2 * other_length_2)); 75 bool result = error < kAngleEpsilon; 76 if (result) { 77 *axis_x = to->rotate.axis.x; 78 *axis_y = to->rotate.axis.y; 79 *axis_z = to->rotate.axis.z; 80 // If the axes are pointing in opposite directions, we need to reverse 81 // the angle. 82 *angle_from = dot > 0 ? from->rotate.angle : -from->rotate.angle; 83 } 84 return result; 85 } 86 87 static SkMScalar BlendSkMScalars(SkMScalar from, 88 SkMScalar to, 89 SkMScalar progress) { 90 return from * (1 - progress) + to * progress; 91 } 92 93 bool TransformOperation::BlendTransformOperations( 94 const TransformOperation* from, 95 const TransformOperation* to, 96 SkMScalar progress, 97 gfx::Transform* result) { 98 if (IsOperationIdentity(from) && IsOperationIdentity(to)) 99 return true; 100 101 TransformOperation::Type interpolation_type = 102 TransformOperation::TransformOperationIdentity; 103 if (IsOperationIdentity(to)) 104 interpolation_type = from->type; 105 else 106 interpolation_type = to->type; 107 108 switch (interpolation_type) { 109 case TransformOperation::TransformOperationTranslate: { 110 SkMScalar from_x = IsOperationIdentity(from) ? 0 : from->translate.x; 111 SkMScalar from_y = IsOperationIdentity(from) ? 0 : from->translate.y; 112 SkMScalar from_z = IsOperationIdentity(from) ? 0 : from->translate.z; 113 SkMScalar to_x = IsOperationIdentity(to) ? 0 : to->translate.x; 114 SkMScalar to_y = IsOperationIdentity(to) ? 0 : to->translate.y; 115 SkMScalar to_z = IsOperationIdentity(to) ? 0 : to->translate.z; 116 result->Translate3d(BlendSkMScalars(from_x, to_x, progress), 117 BlendSkMScalars(from_y, to_y, progress), 118 BlendSkMScalars(from_z, to_z, progress)); 119 break; 120 } 121 case TransformOperation::TransformOperationRotate: { 122 SkMScalar axis_x = 0; 123 SkMScalar axis_y = 0; 124 SkMScalar axis_z = 1; 125 SkMScalar from_angle = 0; 126 SkMScalar to_angle = IsOperationIdentity(to) ? 0 : to->rotate.angle; 127 if (ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle)) { 128 result->RotateAbout(gfx::Vector3dF(axis_x, axis_y, axis_z), 129 BlendSkMScalars(from_angle, to_angle, progress)); 130 } else { 131 gfx::Transform to_matrix; 132 if (!IsOperationIdentity(to)) 133 to_matrix = to->matrix; 134 gfx::Transform from_matrix; 135 if (!IsOperationIdentity(from)) 136 from_matrix = from->matrix; 137 *result = to_matrix; 138 if (!result->Blend(from_matrix, progress)) 139 return false; 140 } 141 break; 142 } 143 case TransformOperation::TransformOperationScale: { 144 SkMScalar from_x = IsOperationIdentity(from) ? 1 : from->scale.x; 145 SkMScalar from_y = IsOperationIdentity(from) ? 1 : from->scale.y; 146 SkMScalar from_z = IsOperationIdentity(from) ? 1 : from->scale.z; 147 SkMScalar to_x = IsOperationIdentity(to) ? 1 : to->scale.x; 148 SkMScalar to_y = IsOperationIdentity(to) ? 1 : to->scale.y; 149 SkMScalar to_z = IsOperationIdentity(to) ? 1 : to->scale.z; 150 result->Scale3d(BlendSkMScalars(from_x, to_x, progress), 151 BlendSkMScalars(from_y, to_y, progress), 152 BlendSkMScalars(from_z, to_z, progress)); 153 break; 154 } 155 case TransformOperation::TransformOperationSkew: { 156 SkMScalar from_x = IsOperationIdentity(from) ? 0 : from->skew.x; 157 SkMScalar from_y = IsOperationIdentity(from) ? 0 : from->skew.y; 158 SkMScalar to_x = IsOperationIdentity(to) ? 0 : to->skew.x; 159 SkMScalar to_y = IsOperationIdentity(to) ? 0 : to->skew.y; 160 result->SkewX(BlendSkMScalars(from_x, to_x, progress)); 161 result->SkewY(BlendSkMScalars(from_y, to_y, progress)); 162 break; 163 } 164 case TransformOperation::TransformOperationPerspective: { 165 SkMScalar from_perspective_depth = 166 IsOperationIdentity(from) ? std::numeric_limits<SkMScalar>::max() 167 : from->perspective_depth; 168 SkMScalar to_perspective_depth = 169 IsOperationIdentity(to) ? std::numeric_limits<SkMScalar>::max() 170 : to->perspective_depth; 171 if (from_perspective_depth == 0.f || to_perspective_depth == 0.f) 172 return false; 173 174 SkMScalar blended_perspective_depth = BlendSkMScalars( 175 1.f / from_perspective_depth, 1.f / to_perspective_depth, progress); 176 177 if (blended_perspective_depth == 0.f) 178 return false; 179 180 result->ApplyPerspectiveDepth(1.f / blended_perspective_depth); 181 break; 182 } 183 case TransformOperation::TransformOperationMatrix: { 184 gfx::Transform to_matrix; 185 if (!IsOperationIdentity(to)) 186 to_matrix = to->matrix; 187 gfx::Transform from_matrix; 188 if (!IsOperationIdentity(from)) 189 from_matrix = from->matrix; 190 *result = to_matrix; 191 if (!result->Blend(from_matrix, progress)) 192 return false; 193 break; 194 } 195 case TransformOperation::TransformOperationIdentity: 196 // Do nothing. 197 break; 198 } 199 200 return true; 201 } 202 203 // If p = (px, py) is a point in the plane being rotated about (0, 0, nz), this 204 // function computes the angles we would have to rotate from p to get to 205 // (length(p), 0), (-length(p), 0), (0, length(p)), (0, -length(p)). If nz is 206 // negative, these angles will need to be reversed. 207 static void FindCandidatesInPlane(float px, 208 float py, 209 float nz, 210 double* candidates, 211 int* num_candidates) { 212 double phi = atan2(px, py); 213 *num_candidates = 4; 214 candidates[0] = phi; 215 for (int i = 1; i < *num_candidates; ++i) 216 candidates[i] = candidates[i - 1] + M_PI_2; 217 if (nz < 0.f) { 218 for (int i = 0; i < *num_candidates; ++i) 219 candidates[i] *= -1.f; 220 } 221 } 222 223 static float RadiansToDegrees(float radians) { 224 return (180.f * radians) / M_PI; 225 } 226 227 static float DegreesToRadians(float degrees) { 228 return (M_PI * degrees) / 180.f; 229 } 230 231 static void BoundingBoxForArc(const gfx::Point3F& point, 232 const TransformOperation* from, 233 const TransformOperation* to, 234 SkMScalar min_progress, 235 SkMScalar max_progress, 236 gfx::BoxF* box) { 237 const TransformOperation* exemplar = from ? from : to; 238 gfx::Vector3dF axis(exemplar->rotate.axis.x, 239 exemplar->rotate.axis.y, 240 exemplar->rotate.axis.z); 241 242 const bool x_is_zero = axis.x() == 0.f; 243 const bool y_is_zero = axis.y() == 0.f; 244 const bool z_is_zero = axis.z() == 0.f; 245 246 // We will have at most 6 angles to test (excluding from->angle and 247 // to->angle). 248 static const int kMaxNumCandidates = 6; 249 double candidates[kMaxNumCandidates]; 250 int num_candidates = kMaxNumCandidates; 251 252 if (x_is_zero && y_is_zero && z_is_zero) 253 return; 254 255 SkMScalar from_angle = from ? from->rotate.angle : 0.f; 256 SkMScalar to_angle = to ? to->rotate.angle : 0.f; 257 258 // If the axes of rotation are pointing in opposite directions, we need to 259 // flip one of the angles. Note, if both |from| and |to| exist, then axis will 260 // correspond to |from|. 261 if (from && to) { 262 gfx::Vector3dF other_axis( 263 to->rotate.axis.x, to->rotate.axis.y, to->rotate.axis.z); 264 if (gfx::DotProduct(axis, other_axis) < 0.f) 265 to_angle *= -1.f; 266 } 267 268 float min_degrees = 269 SkMScalarToFloat(BlendSkMScalars(from_angle, to_angle, min_progress)); 270 float max_degrees = 271 SkMScalarToFloat(BlendSkMScalars(from_angle, to_angle, max_progress)); 272 if (max_degrees < min_degrees) 273 std::swap(min_degrees, max_degrees); 274 275 gfx::Transform from_transform; 276 from_transform.RotateAbout(axis, min_degrees); 277 gfx::Transform to_transform; 278 to_transform.RotateAbout(axis, max_degrees); 279 280 *box = gfx::BoxF(); 281 282 gfx::Point3F point_rotated_from = point; 283 from_transform.TransformPoint(&point_rotated_from); 284 gfx::Point3F point_rotated_to = point; 285 to_transform.TransformPoint(&point_rotated_to); 286 287 box->set_origin(point_rotated_from); 288 box->ExpandTo(point_rotated_to); 289 290 if (x_is_zero && y_is_zero) { 291 FindCandidatesInPlane( 292 point.x(), point.y(), axis.z(), candidates, &num_candidates); 293 } else if (x_is_zero && z_is_zero) { 294 FindCandidatesInPlane( 295 point.z(), point.x(), axis.y(), candidates, &num_candidates); 296 } else if (y_is_zero && z_is_zero) { 297 FindCandidatesInPlane( 298 point.y(), point.z(), axis.x(), candidates, &num_candidates); 299 } else { 300 gfx::Vector3dF normal = axis; 301 normal.Scale(1.f / normal.Length()); 302 303 // First, find center of rotation. 304 gfx::Point3F origin; 305 gfx::Vector3dF to_point = point - origin; 306 gfx::Point3F center = 307 origin + gfx::ScaleVector3d(normal, gfx::DotProduct(to_point, normal)); 308 309 // Now we need to find two vectors in the plane of rotation. One pointing 310 // towards point and another, perpendicular vector in the plane. 311 gfx::Vector3dF v1 = point - center; 312 float v1_length = v1.Length(); 313 if (v1_length == 0.f) 314 return; 315 316 v1.Scale(1.f / v1_length); 317 318 gfx::Vector3dF v2 = gfx::CrossProduct(normal, v1); 319 320 // Now figure out where (1, 0, 0) and (0, 0, 1) project on the rotation 321 // plane. 322 gfx::Point3F px(1.f, 0.f, 0.f); 323 gfx::Vector3dF to_px = px - center; 324 gfx::Point3F px_projected = 325 px - gfx::ScaleVector3d(normal, gfx::DotProduct(to_px, normal)); 326 gfx::Vector3dF vx = px_projected - origin; 327 328 gfx::Point3F pz(0.f, 0.f, 1.f); 329 gfx::Vector3dF to_pz = pz - center; 330 gfx::Point3F pz_projected = 331 pz - ScaleVector3d(normal, gfx::DotProduct(to_pz, normal)); 332 gfx::Vector3dF vz = pz_projected - origin; 333 334 double phi_x = atan2(gfx::DotProduct(v2, vx), gfx::DotProduct(v1, vx)); 335 double phi_z = atan2(gfx::DotProduct(v2, vz), gfx::DotProduct(v1, vz)); 336 337 candidates[0] = atan2(normal.y(), normal.x() * normal.z()) + phi_x; 338 candidates[1] = candidates[0] + M_PI; 339 candidates[2] = atan2(-normal.z(), normal.x() * normal.y()) + phi_x; 340 candidates[3] = candidates[2] + M_PI; 341 candidates[4] = atan2(normal.y(), -normal.x() * normal.z()) + phi_z; 342 candidates[5] = candidates[4] + M_PI; 343 } 344 345 double min_radians = DegreesToRadians(min_degrees); 346 double max_radians = DegreesToRadians(max_degrees); 347 348 for (int i = 0; i < num_candidates; ++i) { 349 double radians = candidates[i]; 350 while (radians < min_radians) 351 radians += 2.0 * M_PI; 352 while (radians > max_radians) 353 radians -= 2.0 * M_PI; 354 if (radians < min_radians) 355 continue; 356 357 gfx::Transform rotation; 358 rotation.RotateAbout(axis, RadiansToDegrees(radians)); 359 gfx::Point3F rotated = point; 360 rotation.TransformPoint(&rotated); 361 362 box->ExpandTo(rotated); 363 } 364 } 365 366 bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box, 367 const TransformOperation* from, 368 const TransformOperation* to, 369 SkMScalar min_progress, 370 SkMScalar max_progress, 371 gfx::BoxF* bounds) { 372 bool is_identity_from = IsOperationIdentity(from); 373 bool is_identity_to = IsOperationIdentity(to); 374 if (is_identity_from && is_identity_to) { 375 *bounds = box; 376 return true; 377 } 378 379 TransformOperation::Type interpolation_type = 380 TransformOperation::TransformOperationIdentity; 381 if (is_identity_to) 382 interpolation_type = from->type; 383 else 384 interpolation_type = to->type; 385 386 switch (interpolation_type) { 387 case TransformOperation::TransformOperationIdentity: 388 *bounds = box; 389 return true; 390 case TransformOperation::TransformOperationTranslate: 391 case TransformOperation::TransformOperationSkew: 392 case TransformOperation::TransformOperationPerspective: 393 case TransformOperation::TransformOperationScale: { 394 gfx::Transform from_transform; 395 gfx::Transform to_transform; 396 if (!BlendTransformOperations(from, to, min_progress, &from_transform) || 397 !BlendTransformOperations(from, to, max_progress, &to_transform)) 398 return false; 399 400 *bounds = box; 401 from_transform.TransformBox(bounds); 402 403 gfx::BoxF to_box = box; 404 to_transform.TransformBox(&to_box); 405 bounds->ExpandTo(to_box); 406 407 return true; 408 } 409 case TransformOperation::TransformOperationRotate: { 410 SkMScalar axis_x = 0; 411 SkMScalar axis_y = 0; 412 SkMScalar axis_z = 1; 413 SkMScalar from_angle = 0; 414 if (!ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle)) 415 return false; 416 417 bool first_point = true; 418 for (int i = 0; i < 8; ++i) { 419 gfx::Point3F corner = box.origin(); 420 corner += gfx::Vector3dF(i & 1 ? box.width() : 0.f, 421 i & 2 ? box.height() : 0.f, 422 i & 4 ? box.depth() : 0.f); 423 gfx::BoxF box_for_arc; 424 BoundingBoxForArc( 425 corner, from, to, min_progress, max_progress, &box_for_arc); 426 if (first_point) 427 *bounds = box_for_arc; 428 else 429 bounds->Union(box_for_arc); 430 first_point = false; 431 } 432 return true; 433 } 434 case TransformOperation::TransformOperationMatrix: 435 return false; 436 } 437 NOTREACHED(); 438 return false; 439 } 440 441 } // namespace cc 442