blob: 8f792c92ab66e6f6cd8d2687d09b8f5c77d6d244 [file] [log] [blame]
// Copyright 2013 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 <algorithm>
#include <limits>
#include "base/logging.h"
#include "base/numerics/math_constants.h"
#include "base/numerics/ranges.h"
#include "cc/animation/transform_operation.h"
#include "cc/animation/transform_operations.h"
#include "ui/gfx/geometry/angle_conversions.h"
#include "ui/gfx/geometry/box_f.h"
#include "ui/gfx/geometry/vector3d_f.h"
#include "ui/gfx/transform_util.h"
namespace {
const SkMScalar kAngleEpsilon = 1e-4f;
}
namespace cc {
bool TransformOperation::IsIdentity() const {
return matrix.IsIdentity();
}
static bool IsOperationIdentity(const TransformOperation* operation) {
return !operation || operation->IsIdentity();
}
static bool ShareSameAxis(const TransformOperation* from,
const TransformOperation* to,
SkMScalar* axis_x,
SkMScalar* axis_y,
SkMScalar* axis_z,
SkMScalar* angle_from) {
if (IsOperationIdentity(from) && IsOperationIdentity(to))
return false;
if (IsOperationIdentity(from) && !IsOperationIdentity(to)) {
*axis_x = to->rotate.axis.x;
*axis_y = to->rotate.axis.y;
*axis_z = to->rotate.axis.z;
*angle_from = 0;
return true;
}
if (!IsOperationIdentity(from) && IsOperationIdentity(to)) {
*axis_x = from->rotate.axis.x;
*axis_y = from->rotate.axis.y;
*axis_z = from->rotate.axis.z;
*angle_from = from->rotate.angle;
return true;
}
SkMScalar length_2 = from->rotate.axis.x * from->rotate.axis.x +
from->rotate.axis.y * from->rotate.axis.y +
from->rotate.axis.z * from->rotate.axis.z;
SkMScalar other_length_2 = to->rotate.axis.x * to->rotate.axis.x +
to->rotate.axis.y * to->rotate.axis.y +
to->rotate.axis.z * to->rotate.axis.z;
if (length_2 <= kAngleEpsilon || other_length_2 <= kAngleEpsilon)
return false;
SkMScalar dot = to->rotate.axis.x * from->rotate.axis.x +
to->rotate.axis.y * from->rotate.axis.y +
to->rotate.axis.z * from->rotate.axis.z;
SkMScalar error =
SkMScalarAbs(SK_MScalar1 - (dot * dot) / (length_2 * other_length_2));
bool result = error < kAngleEpsilon;
if (result) {
*axis_x = to->rotate.axis.x;
*axis_y = to->rotate.axis.y;
*axis_z = to->rotate.axis.z;
// If the axes are pointing in opposite directions, we need to reverse
// the angle.
*angle_from = dot > 0 ? from->rotate.angle : -from->rotate.angle;
}
return result;
}
static SkMScalar BlendSkMScalars(SkMScalar from,
SkMScalar to,
SkMScalar progress) {
return from * (1 - progress) + to * progress;
}
void TransformOperation::Bake() {
matrix.MakeIdentity();
switch (type) {
case TransformOperation::TRANSFORM_OPERATION_TRANSLATE:
matrix.Translate3d(translate.x, translate.y, translate.z);
break;
case TransformOperation::TRANSFORM_OPERATION_ROTATE:
matrix.RotateAbout(
gfx::Vector3dF(rotate.axis.x, rotate.axis.y, rotate.axis.z),
rotate.angle);
break;
case TransformOperation::TRANSFORM_OPERATION_SCALE:
matrix.Scale3d(scale.x, scale.y, scale.z);
break;
case TransformOperation::TRANSFORM_OPERATION_SKEW:
matrix.Skew(skew.x, skew.y);
break;
case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE:
matrix.ApplyPerspectiveDepth(perspective_depth);
break;
case TransformOperation::TRANSFORM_OPERATION_MATRIX:
case TransformOperation::TRANSFORM_OPERATION_IDENTITY:
break;
}
}
bool TransformOperation::ApproximatelyEqual(const TransformOperation& other,
SkMScalar tolerance) const {
DCHECK_LE(0, tolerance);
if (type != other.type)
return false;
switch (type) {
case TransformOperation::TRANSFORM_OPERATION_TRANSLATE:
return base::IsApproximatelyEqual(translate.x, other.translate.x,
tolerance) &&
base::IsApproximatelyEqual(translate.y, other.translate.y,
tolerance) &&
base::IsApproximatelyEqual(translate.z, other.translate.z,
tolerance);
case TransformOperation::TRANSFORM_OPERATION_ROTATE:
return base::IsApproximatelyEqual(rotate.axis.x, other.rotate.axis.x,
tolerance) &&
base::IsApproximatelyEqual(rotate.axis.y, other.rotate.axis.y,
tolerance) &&
base::IsApproximatelyEqual(rotate.axis.z, other.rotate.axis.z,
tolerance) &&
base::IsApproximatelyEqual(rotate.angle, other.rotate.angle,
tolerance);
case TransformOperation::TRANSFORM_OPERATION_SCALE:
return base::IsApproximatelyEqual(scale.x, other.scale.x, tolerance) &&
base::IsApproximatelyEqual(scale.y, other.scale.y, tolerance) &&
base::IsApproximatelyEqual(scale.z, other.scale.z, tolerance);
case TransformOperation::TRANSFORM_OPERATION_SKEW:
return base::IsApproximatelyEqual(skew.x, other.skew.x, tolerance) &&
base::IsApproximatelyEqual(skew.y, other.skew.y, tolerance);
case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE:
return base::IsApproximatelyEqual(perspective_depth,
other.perspective_depth, tolerance);
case TransformOperation::TRANSFORM_OPERATION_MATRIX:
// TODO(vollick): we could expose a tolerance on gfx::Transform, but it's
// complex since we need a different tolerance per component. Driving this
// with a single tolerance will take some care. For now, we will check
// exact equality where the tolerance is 0.0f, otherwise we will use the
// unparameterized version of gfx::Transform::ApproximatelyEqual.
if (tolerance == 0.0f)
return matrix == other.matrix;
else
return matrix.ApproximatelyEqual(other.matrix);
case TransformOperation::TRANSFORM_OPERATION_IDENTITY:
return other.matrix.IsIdentity();
}
NOTREACHED();
return false;
}
bool TransformOperation::BlendTransformOperations(
const TransformOperation* from,
const TransformOperation* to,
SkMScalar progress,
TransformOperation* result) {
if (IsOperationIdentity(from) && IsOperationIdentity(to))
return true;
TransformOperation::Type interpolation_type =
TransformOperation::TRANSFORM_OPERATION_IDENTITY;
if (IsOperationIdentity(to))
interpolation_type = from->type;
else
interpolation_type = to->type;
result->type = interpolation_type;
switch (interpolation_type) {
case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: {
SkMScalar from_x = IsOperationIdentity(from) ? 0 : from->translate.x;
SkMScalar from_y = IsOperationIdentity(from) ? 0 : from->translate.y;
SkMScalar from_z = IsOperationIdentity(from) ? 0 : from->translate.z;
SkMScalar to_x = IsOperationIdentity(to) ? 0 : to->translate.x;
SkMScalar to_y = IsOperationIdentity(to) ? 0 : to->translate.y;
SkMScalar to_z = IsOperationIdentity(to) ? 0 : to->translate.z;
result->translate.x = BlendSkMScalars(from_x, to_x, progress),
result->translate.y = BlendSkMScalars(from_y, to_y, progress),
result->translate.z = BlendSkMScalars(from_z, to_z, progress),
result->Bake();
break;
}
case TransformOperation::TRANSFORM_OPERATION_ROTATE: {
SkMScalar axis_x = 0;
SkMScalar axis_y = 0;
SkMScalar axis_z = 1;
SkMScalar from_angle = 0;
SkMScalar to_angle = IsOperationIdentity(to) ? 0 : to->rotate.angle;
if (ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle)) {
result->rotate.axis.x = axis_x;
result->rotate.axis.y = axis_y;
result->rotate.axis.z = axis_z;
result->rotate.angle = BlendSkMScalars(from_angle, to_angle, progress);
result->Bake();
} else {
if (!IsOperationIdentity(to))
result->matrix = to->matrix;
gfx::Transform from_matrix;
if (!IsOperationIdentity(from))
from_matrix = from->matrix;
if (!result->matrix.Blend(from_matrix, progress))
return false;
}
break;
}
case TransformOperation::TRANSFORM_OPERATION_SCALE: {
SkMScalar from_x = IsOperationIdentity(from) ? 1 : from->scale.x;
SkMScalar from_y = IsOperationIdentity(from) ? 1 : from->scale.y;
SkMScalar from_z = IsOperationIdentity(from) ? 1 : from->scale.z;
SkMScalar to_x = IsOperationIdentity(to) ? 1 : to->scale.x;
SkMScalar to_y = IsOperationIdentity(to) ? 1 : to->scale.y;
SkMScalar to_z = IsOperationIdentity(to) ? 1 : to->scale.z;
result->scale.x = BlendSkMScalars(from_x, to_x, progress);
result->scale.y = BlendSkMScalars(from_y, to_y, progress);
result->scale.z = BlendSkMScalars(from_z, to_z, progress);
result->Bake();
break;
}
case TransformOperation::TRANSFORM_OPERATION_SKEW: {
SkMScalar from_x = IsOperationIdentity(from) ? 0 : from->skew.x;
SkMScalar from_y = IsOperationIdentity(from) ? 0 : from->skew.y;
SkMScalar to_x = IsOperationIdentity(to) ? 0 : to->skew.x;
SkMScalar to_y = IsOperationIdentity(to) ? 0 : to->skew.y;
result->skew.x = BlendSkMScalars(from_x, to_x, progress);
result->skew.y = BlendSkMScalars(from_y, to_y, progress);
result->Bake();
break;
}
case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: {
SkMScalar from_perspective_depth =
IsOperationIdentity(from) ? std::numeric_limits<SkMScalar>::max()
: from->perspective_depth;
SkMScalar to_perspective_depth =
IsOperationIdentity(to) ? std::numeric_limits<SkMScalar>::max()
: to->perspective_depth;
if (from_perspective_depth == 0.f || to_perspective_depth == 0.f)
return false;
SkMScalar blended_perspective_depth = BlendSkMScalars(
1.f / from_perspective_depth, 1.f / to_perspective_depth, progress);
if (blended_perspective_depth == 0.f)
return false;
result->perspective_depth = 1.f / blended_perspective_depth;
result->Bake();
break;
}
case TransformOperation::TRANSFORM_OPERATION_MATRIX: {
if (!IsOperationIdentity(to))
result->matrix = to->matrix;
gfx::Transform from_matrix;
if (!IsOperationIdentity(from))
from_matrix = from->matrix;
if (!result->matrix.Blend(from_matrix, progress))
return false;
break;
}
case TransformOperation::TRANSFORM_OPERATION_IDENTITY:
// Do nothing.
break;
}
return true;
}
// If p = (px, py) is a point in the plane being rotated about (0, 0, nz), this
// function computes the angles we would have to rotate from p to get to
// (length(p), 0), (-length(p), 0), (0, length(p)), (0, -length(p)). If nz is
// negative, these angles will need to be reversed.
static void FindCandidatesInPlane(float px,
float py,
float nz,
double* candidates,
int* num_candidates) {
double phi = atan2(px, py);
*num_candidates = 4;
candidates[0] = phi;
for (int i = 1; i < *num_candidates; ++i)
candidates[i] = candidates[i - 1] + base::kPiDouble / 2;
if (nz < 0.f) {
for (int i = 0; i < *num_candidates; ++i)
candidates[i] *= -1.f;
}
}
static void BoundingBoxForArc(const gfx::Point3F& point,
const TransformOperation* from,
const TransformOperation* to,
SkMScalar min_progress,
SkMScalar max_progress,
gfx::BoxF* box) {
const TransformOperation* exemplar = from ? from : to;
gfx::Vector3dF axis(exemplar->rotate.axis.x,
exemplar->rotate.axis.y,
exemplar->rotate.axis.z);
const bool x_is_zero = axis.x() == 0.f;
const bool y_is_zero = axis.y() == 0.f;
const bool z_is_zero = axis.z() == 0.f;
// We will have at most 6 angles to test (excluding from->angle and
// to->angle).
static const int kMaxNumCandidates = 6;
double candidates[kMaxNumCandidates];
int num_candidates = kMaxNumCandidates;
if (x_is_zero && y_is_zero && z_is_zero)
return;
SkMScalar from_angle = from ? from->rotate.angle : 0.f;
SkMScalar to_angle = to ? to->rotate.angle : 0.f;
// If the axes of rotation are pointing in opposite directions, we need to
// flip one of the angles. Note, if both |from| and |to| exist, then axis will
// correspond to |from|.
if (from && to) {
gfx::Vector3dF other_axis(
to->rotate.axis.x, to->rotate.axis.y, to->rotate.axis.z);
if (gfx::DotProduct(axis, other_axis) < 0.f)
to_angle *= -1.f;
}
float min_degrees =
SkMScalarToFloat(BlendSkMScalars(from_angle, to_angle, min_progress));
float max_degrees =
SkMScalarToFloat(BlendSkMScalars(from_angle, to_angle, max_progress));
if (max_degrees < min_degrees)
std::swap(min_degrees, max_degrees);
gfx::Transform from_transform;
from_transform.RotateAbout(axis, min_degrees);
gfx::Transform to_transform;
to_transform.RotateAbout(axis, max_degrees);
*box = gfx::BoxF();
gfx::Point3F point_rotated_from = point;
from_transform.TransformPoint(&point_rotated_from);
gfx::Point3F point_rotated_to = point;
to_transform.TransformPoint(&point_rotated_to);
box->set_origin(point_rotated_from);
box->ExpandTo(point_rotated_to);
if (x_is_zero && y_is_zero) {
FindCandidatesInPlane(
point.x(), point.y(), axis.z(), candidates, &num_candidates);
} else if (x_is_zero && z_is_zero) {
FindCandidatesInPlane(
point.z(), point.x(), axis.y(), candidates, &num_candidates);
} else if (y_is_zero && z_is_zero) {
FindCandidatesInPlane(
point.y(), point.z(), axis.x(), candidates, &num_candidates);
} else {
gfx::Vector3dF normal = axis;
normal.Scale(1.f / normal.Length());
// First, find center of rotation.
gfx::Point3F origin;
gfx::Vector3dF to_point = point - origin;
gfx::Point3F center =
origin + gfx::ScaleVector3d(normal, gfx::DotProduct(to_point, normal));
// Now we need to find two vectors in the plane of rotation. One pointing
// towards point and another, perpendicular vector in the plane.
gfx::Vector3dF v1 = point - center;
float v1_length = v1.Length();
if (v1_length == 0.f)
return;
v1.Scale(1.f / v1_length);
gfx::Vector3dF v2 = gfx::CrossProduct(normal, v1);
// v1 is the basis vector in the direction of the point.
// i.e. with a rotation of 0, v1 is our +x vector.
// v2 is a perpenticular basis vector of our plane (+y).
// Take the parametric equation of a circle.
// x = r*cos(t); y = r*sin(t);
// We can treat that as a circle on the plane v1xv2.
// From that we get the parametric equations for a circle on the
// plane in 3d space of:
// x(t) = r*cos(t)*v1.x + r*sin(t)*v2.x + cx
// y(t) = r*cos(t)*v1.y + r*sin(t)*v2.y + cy
// z(t) = r*cos(t)*v1.z + r*sin(t)*v2.z + cz
// Taking the derivative of (x, y, z) and solving for 0 gives us our
// maximum/minimum x, y, z values.
// x'(t) = r*cos(t)*v2.x - r*sin(t)*v1.x = 0
// tan(t) = v2.x/v1.x
// t = atan2(v2.x, v1.x) + n*pi;
candidates[0] = atan2(v2.x(), v1.x());
candidates[1] = candidates[0] + base::kPiDouble;
candidates[2] = atan2(v2.y(), v1.y());
candidates[3] = candidates[2] + base::kPiDouble;
candidates[4] = atan2(v2.z(), v1.z());
candidates[5] = candidates[4] + base::kPiDouble;
}
double min_radians = gfx::DegToRad(min_degrees);
double max_radians = gfx::DegToRad(max_degrees);
for (int i = 0; i < num_candidates; ++i) {
double radians = candidates[i];
while (radians < min_radians)
radians += 2.0 * base::kPiDouble;
while (radians > max_radians)
radians -= 2.0 * base::kPiDouble;
if (radians < min_radians)
continue;
gfx::Transform rotation;
rotation.RotateAbout(axis, gfx::RadToDeg(radians));
gfx::Point3F rotated = point;
rotation.TransformPoint(&rotated);
box->ExpandTo(rotated);
}
}
bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box,
const TransformOperation* from,
const TransformOperation* to,
SkMScalar min_progress,
SkMScalar max_progress,
gfx::BoxF* bounds) {
bool is_identity_from = IsOperationIdentity(from);
bool is_identity_to = IsOperationIdentity(to);
if (is_identity_from && is_identity_to) {
*bounds = box;
return true;
}
TransformOperation::Type interpolation_type =
TransformOperation::TRANSFORM_OPERATION_IDENTITY;
if (is_identity_to)
interpolation_type = from->type;
else
interpolation_type = to->type;
switch (interpolation_type) {
case TransformOperation::TRANSFORM_OPERATION_IDENTITY:
*bounds = box;
return true;
case TransformOperation::TRANSFORM_OPERATION_TRANSLATE:
case TransformOperation::TRANSFORM_OPERATION_SKEW:
case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE:
case TransformOperation::TRANSFORM_OPERATION_SCALE: {
TransformOperation from_operation;
TransformOperation to_operation;
if (!BlendTransformOperations(from, to, min_progress, &from_operation) ||
!BlendTransformOperations(from, to, max_progress, &to_operation))
return false;
*bounds = box;
from_operation.matrix.TransformBox(bounds);
gfx::BoxF to_box = box;
to_operation.matrix.TransformBox(&to_box);
bounds->ExpandTo(to_box);
return true;
}
case TransformOperation::TRANSFORM_OPERATION_ROTATE: {
SkMScalar axis_x = 0;
SkMScalar axis_y = 0;
SkMScalar axis_z = 1;
SkMScalar from_angle = 0;
if (!ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle))
return false;
bool first_point = true;
for (int i = 0; i < 8; ++i) {
gfx::Point3F corner = box.origin();
corner += gfx::Vector3dF(i & 1 ? box.width() : 0.f,
i & 2 ? box.height() : 0.f,
i & 4 ? box.depth() : 0.f);
gfx::BoxF box_for_arc;
BoundingBoxForArc(
corner, from, to, min_progress, max_progress, &box_for_arc);
if (first_point)
*bounds = box_for_arc;
else
bounds->Union(box_for_arc);
first_point = false;
}
return true;
}
case TransformOperation::TRANSFORM_OPERATION_MATRIX:
return false;
}
NOTREACHED();
return false;
}
} // namespace cc