Suppress UBSan's false positives for virtual base classes.
[chromium-blink-merge.git] / cc / animation / transform_operation.cc
blobe9ae86dd47106a0cfb4e1f66d781ebb619c3379b
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.
5 // Needed on Windows to get |M_PI| from <cmath>
6 #ifdef _WIN32
7 #define _USE_MATH_DEFINES
8 #endif
10 #include <algorithm>
11 #include <cmath>
12 #include <limits>
14 #include "base/logging.h"
15 #include "cc/animation/transform_operation.h"
16 #include "cc/animation/transform_operations.h"
17 #include "ui/gfx/geometry/box_f.h"
18 #include "ui/gfx/geometry/vector3d_f.h"
19 #include "ui/gfx/transform_util.h"
21 namespace {
22 const SkMScalar kAngleEpsilon = 1e-4f;
25 namespace cc {
27 bool TransformOperation::IsIdentity() const {
28 return matrix.IsIdentity();
31 static bool IsOperationIdentity(const TransformOperation* operation) {
32 return !operation || operation->IsIdentity();
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;
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;
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;
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;
67 if (length_2 <= kAngleEpsilon || other_length_2 <= kAngleEpsilon)
68 return false;
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;
84 return result;
87 static SkMScalar BlendSkMScalars(SkMScalar from,
88 SkMScalar to,
89 SkMScalar progress) {
90 return from * (1 - progress) + to * progress;
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;
101 TransformOperation::Type interpolation_type =
102 TransformOperation::TransformOperationIdentity;
103 if (IsOperationIdentity(to))
104 interpolation_type = from->type;
105 else
106 interpolation_type = to->type;
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;
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;
141 break;
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;
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;
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;
174 SkMScalar blended_perspective_depth = BlendSkMScalars(
175 1.f / from_perspective_depth, 1.f / to_perspective_depth, progress);
177 if (blended_perspective_depth == 0.f)
178 return false;
180 result->ApplyPerspectiveDepth(1.f / blended_perspective_depth);
181 break;
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;
195 case TransformOperation::TransformOperationIdentity:
196 // Do nothing.
197 break;
200 return true;
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;
223 static float RadiansToDegrees(float radians) {
224 return (180.f * radians) / M_PI;
227 static float DegreesToRadians(float degrees) {
228 return (M_PI * degrees) / 180.f;
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);
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;
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;
252 if (x_is_zero && y_is_zero && z_is_zero)
253 return;
255 SkMScalar from_angle = from ? from->rotate.angle : 0.f;
256 SkMScalar to_angle = to ? to->rotate.angle : 0.f;
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;
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);
275 gfx::Transform from_transform;
276 from_transform.RotateAbout(axis, min_degrees);
277 gfx::Transform to_transform;
278 to_transform.RotateAbout(axis, max_degrees);
280 *box = gfx::BoxF();
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);
287 box->set_origin(point_rotated_from);
288 box->ExpandTo(point_rotated_to);
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());
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));
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;
316 v1.Scale(1.f / v1_length);
317 gfx::Vector3dF v2 = gfx::CrossProduct(normal, v1);
318 // v1 is the basis vector in the direction of the point.
319 // i.e. with a rotation of 0, v1 is our +x vector.
320 // v2 is a perpenticular basis vector of our plane (+y).
322 // Take the parametric equation of a circle.
323 // x = r*cos(t); y = r*sin(t);
324 // We can treat that as a circle on the plane v1xv2.
325 // From that we get the parametric equations for a circle on the
326 // plane in 3d space of:
327 // x(t) = r*cos(t)*v1.x + r*sin(t)*v2.x + cx
328 // y(t) = r*cos(t)*v1.y + r*sin(t)*v2.y + cy
329 // z(t) = r*cos(t)*v1.z + r*sin(t)*v2.z + cz
330 // Taking the derivative of (x, y, z) and solving for 0 gives us our
331 // maximum/minimum x, y, z values.
332 // x'(t) = r*cos(t)*v2.x - r*sin(t)*v1.x = 0
333 // tan(t) = v2.x/v1.x
334 // t = atan2(v2.x, v1.x) + n*M_PI;
335 candidates[0] = atan2(v2.x(), v1.x());
336 candidates[1] = candidates[0] + M_PI;
337 candidates[2] = atan2(v2.y(), v1.y());
338 candidates[3] = candidates[2] + M_PI;
339 candidates[4] = atan2(v2.z(), v1.z());
340 candidates[5] = candidates[4] + M_PI;
343 double min_radians = DegreesToRadians(min_degrees);
344 double max_radians = DegreesToRadians(max_degrees);
346 for (int i = 0; i < num_candidates; ++i) {
347 double radians = candidates[i];
348 while (radians < min_radians)
349 radians += 2.0 * M_PI;
350 while (radians > max_radians)
351 radians -= 2.0 * M_PI;
352 if (radians < min_radians)
353 continue;
355 gfx::Transform rotation;
356 rotation.RotateAbout(axis, RadiansToDegrees(radians));
357 gfx::Point3F rotated = point;
358 rotation.TransformPoint(&rotated);
360 box->ExpandTo(rotated);
364 bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box,
365 const TransformOperation* from,
366 const TransformOperation* to,
367 SkMScalar min_progress,
368 SkMScalar max_progress,
369 gfx::BoxF* bounds) {
370 bool is_identity_from = IsOperationIdentity(from);
371 bool is_identity_to = IsOperationIdentity(to);
372 if (is_identity_from && is_identity_to) {
373 *bounds = box;
374 return true;
377 TransformOperation::Type interpolation_type =
378 TransformOperation::TransformOperationIdentity;
379 if (is_identity_to)
380 interpolation_type = from->type;
381 else
382 interpolation_type = to->type;
384 switch (interpolation_type) {
385 case TransformOperation::TransformOperationIdentity:
386 *bounds = box;
387 return true;
388 case TransformOperation::TransformOperationTranslate:
389 case TransformOperation::TransformOperationSkew:
390 case TransformOperation::TransformOperationPerspective:
391 case TransformOperation::TransformOperationScale: {
392 gfx::Transform from_transform;
393 gfx::Transform to_transform;
394 if (!BlendTransformOperations(from, to, min_progress, &from_transform) ||
395 !BlendTransformOperations(from, to, max_progress, &to_transform))
396 return false;
398 *bounds = box;
399 from_transform.TransformBox(bounds);
401 gfx::BoxF to_box = box;
402 to_transform.TransformBox(&to_box);
403 bounds->ExpandTo(to_box);
405 return true;
407 case TransformOperation::TransformOperationRotate: {
408 SkMScalar axis_x = 0;
409 SkMScalar axis_y = 0;
410 SkMScalar axis_z = 1;
411 SkMScalar from_angle = 0;
412 if (!ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle))
413 return false;
415 bool first_point = true;
416 for (int i = 0; i < 8; ++i) {
417 gfx::Point3F corner = box.origin();
418 corner += gfx::Vector3dF(i & 1 ? box.width() : 0.f,
419 i & 2 ? box.height() : 0.f,
420 i & 4 ? box.depth() : 0.f);
421 gfx::BoxF box_for_arc;
422 BoundingBoxForArc(
423 corner, from, to, min_progress, max_progress, &box_for_arc);
424 if (first_point)
425 *bounds = box_for_arc;
426 else
427 bounds->Union(box_for_arc);
428 first_point = false;
430 return true;
432 case TransformOperation::TransformOperationMatrix:
433 return false;
435 NOTREACHED();
436 return false;
439 } // namespace cc