blob: 885376ad775a3ce27e69732457c2cd14bee74f52 [file] [log] [blame]
// Copyright 2014 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 "platform/animation/TimingFunction.h"
#include "platform/animation/CubicBezierControlPoints.h"
#include "wtf/MathExtras.h"
namespace blink {
String LinearTimingFunction::toString() const
{
return "linear";
}
double LinearTimingFunction::evaluate(double fraction, double) const
{
return fraction;
}
void LinearTimingFunction::range(double* minValue, double* maxValue) const
{
}
void LinearTimingFunction::partition(Vector<PartitionRegion>& regions) const
{
regions.append(PartitionRegion(RangeHalf::Lower, 0.0, 0.5));
regions.append(PartitionRegion(RangeHalf::Upper, 0.5, 1.0));
}
String CubicBezierTimingFunction::toString() const
{
switch (this->subType()) {
case CubicBezierTimingFunction::Ease:
return "ease";
case CubicBezierTimingFunction::EaseIn:
return "ease-in";
case CubicBezierTimingFunction::EaseOut:
return "ease-out";
case CubicBezierTimingFunction::EaseInOut:
return "ease-in-out";
case CubicBezierTimingFunction::Custom:
return "cubic-bezier(" + String::numberToStringECMAScript(this->x1()) + ", " +
String::numberToStringECMAScript(this->y1()) + ", " + String::numberToStringECMAScript(this->x2()) +
", " + String::numberToStringECMAScript(this->y2()) + ")";
default:
ASSERT_NOT_REACHED();
}
return "";
}
double CubicBezierTimingFunction::evaluate(double fraction, double accuracy) const
{
if (!m_bezier)
m_bezier = adoptPtr(new UnitBezier(m_x1, m_y1, m_x2, m_y2));
return m_bezier->solve(fraction, accuracy);
}
// This works by taking taking the derivative of the cubic bezier, on the y
// axis. We can then solve for where the derivative is zero to find the min
// and max distace along the line. We the have to solve those in terms of time
// rather than distance on the x-axis
void CubicBezierTimingFunction::range(double* minValue, double* maxValue) const
{
if (0 <= m_y1 && m_y2 < 1 && 0 <= m_y2 && m_y2 <= 1) {
return;
}
double a = 3.0 * (m_y1 - m_y2) + 1.0;
double b = 2.0 * (m_y2 - 2.0 * m_y1);
double c = m_y1;
if (std::abs(a) < std::numeric_limits<double>::epsilon()
&& std::abs(b) < std::numeric_limits<double>::epsilon()) {
return;
}
double t1 = 0.0;
double t2 = 0.0;
if (std::abs(a) < std::numeric_limits<double>::epsilon()) {
t1 = -c / b;
} else {
double discriminant = b * b - 4 * a * c;
if (discriminant < 0)
return;
double discriminantSqrt = sqrt(discriminant);
t1 = (-b + discriminantSqrt) / (2 * a);
t2 = (-b - discriminantSqrt) / (2 * a);
}
double solution1 = 0.0;
double solution2 = 0.0;
// If the solution is in the range [0,1] then we include it, otherwise we
// ignore it.
if (!m_bezier)
m_bezier = adoptPtr(new UnitBezier(m_x1, m_y1, m_x2, m_y2));
// An interesting fact about these beziers is that they are only
// actually evaluated in [0,1]. After that we take the tangent at that point
// and linearly project it out.
if (0 < t1 && t1 < 1)
solution1 = m_bezier->sampleCurveY(t1);
if (0 < t2 && t2 < 1)
solution2 = m_bezier->sampleCurveY(t2);
// Since our input values can be out of the range 0->1 so we must also
// consider the minimum and maximum points.
double solutionMin = m_bezier->solve(*minValue, std::numeric_limits<double>::epsilon());
double solutionMax = m_bezier->solve(*maxValue, std::numeric_limits<double>::epsilon());
*minValue = std::min(std::min(solutionMin, solutionMax), 0.0);
*maxValue = std::max(std::max(solutionMin, solutionMax), 1.0);
*minValue = std::min(std::min(*minValue, solution1), solution2);
*maxValue = std::max(std::max(*maxValue, solution1), solution2);
}
size_t CubicBezierTimingFunction::findIntersections(double intersectionY, double& solution1, double& solution2, double& solution3) const
{
size_t numberOfIntersections = 0;
// Divide the bezier into a number of monotonically
// increasing/decreasing segments, so each can intersect the
// horizontal line at most once.
Vector<CubicBezierControlPoints> monotonicSegments;
CubicBezierControlPoints initialSegment = CubicBezierControlPoints(0, 0, m_x1, m_y1, m_x2, m_y2, 1, 1);
// Find the curve's turning points, so we can split it into
// monotonically increasing/decreasing segments.
double turningPoint1 = 0.0;
double turningPoint2 = 0.0;
// Note the x values of each turning point, so we can discard
// intersections at these points (since they don't actually
// cross the horizontal line, but just touch it).
if (!m_bezier)
m_bezier = adoptPtr(new UnitBezier(m_x1, m_y1, m_x2, m_y2));
double turningX1 = 0.0;
double turningX2 = 0.0;
size_t numberOfTurningPoints = initialSegment.findTurningPoints(turningPoint1, turningPoint2);
switch (numberOfTurningPoints) {
case 2:
{
// Split into three segments.
CubicBezierControlPoints leftSegment = CubicBezierControlPoints();
CubicBezierControlPoints middleSegment = CubicBezierControlPoints();
CubicBezierControlPoints rightSegment = CubicBezierControlPoints();
CubicBezierControlPoints tmpSegment = CubicBezierControlPoints();
initialSegment.divide(turningPoint2, tmpSegment, rightSegment);
tmpSegment.divide(turningPoint1 / turningPoint2, leftSegment, middleSegment);
monotonicSegments.append(leftSegment);
monotonicSegments.append(middleSegment);
monotonicSegments.append(rightSegment);
turningX1 = m_bezier->sampleCurveX(turningPoint1);
turningX2 = m_bezier->sampleCurveX(turningPoint2);
break;
}
case 1:
{
// Split into two segments.
CubicBezierControlPoints leftSegment = CubicBezierControlPoints();
CubicBezierControlPoints rightSegment = CubicBezierControlPoints();
initialSegment.divide(turningPoint1, leftSegment, rightSegment);
monotonicSegments.append(leftSegment);
monotonicSegments.append(rightSegment);
turningX1 = m_bezier->sampleCurveX(turningPoint1);
break;
}
case 0:
monotonicSegments.append(initialSegment);
break;
default:
ASSERT_NOT_REACHED();
return 0;
}
double intersectionX = 0.0;
for (const auto& segment : monotonicSegments) {
if (segment.findIntersection(intersectionY, intersectionX)) {
// Ensure that this intersection isn't one of the turning
// points!
switch (numberOfTurningPoints) {
case 2:
if (std::abs(intersectionX - turningX2) < std::numeric_limits<double>::epsilon())
continue;
case 1:
if (std::abs(intersectionX - turningX1) < std::numeric_limits<double>::epsilon())
continue;
}
switch (numberOfIntersections) {
case 0:
solution1 = intersectionX;
break;
case 1:
solution2 = intersectionX;
break;
case 2:
solution3 = intersectionX;
break;
default:
ASSERT_NOT_REACHED();
}
numberOfIntersections++;
}
}
return numberOfIntersections;
}
void CubicBezierTimingFunction::partition(Vector<PartitionRegion>& regions) const
{
double solution1 = 0.0;
double solution2 = 0.0;
double solution3 = 0.0;
size_t numberOfIntersections = findIntersections(0.5, solution1, solution2, solution3);
// A valid cubic bezier should only cross the horizontal line
// 1 or 3 times.
switch (numberOfIntersections) {
case 1:
regions.append(PartitionRegion(TimingFunction::RangeHalf::Lower, 0.0, solution1));
regions.append(PartitionRegion(TimingFunction::RangeHalf::Upper, solution1, 1.0));
break;
case 3:
regions.append(PartitionRegion(TimingFunction::RangeHalf::Lower, 0.0, solution1));
regions.append(PartitionRegion(TimingFunction::RangeHalf::Upper, solution1, solution2));
regions.append(PartitionRegion(TimingFunction::RangeHalf::Lower, solution2, solution3));
regions.append(PartitionRegion(TimingFunction::RangeHalf::Upper, solution3, 1.0));
break;
default:
ASSERT_NOT_REACHED();
break;
}
}
String StepsTimingFunction::toString() const
{
const char* positionString = nullptr;
switch (getStepAtPosition()) {
case Start:
positionString = "start";
break;
case Middle:
positionString = "middle";
break;
case End:
positionString = "end";
break;
}
StringBuilder builder;
if (this->numberOfSteps() == 1) {
builder.append("step-");
builder.append(positionString);
} else {
builder.append("steps(" + String::numberToStringECMAScript(this->numberOfSteps()) + ", ");
builder.append(positionString);
builder.append(')');
}
return builder.toString();
}
void StepsTimingFunction::range(double* minValue, double* maxValue) const
{
*minValue = 0;
*maxValue = 1;
}
double StepsTimingFunction::evaluate(double fraction, double) const
{
double startOffset = 0;
switch (m_stepAtPosition) {
case Start:
startOffset = 1;
break;
case Middle:
startOffset = 0.5;
break;
case End:
startOffset = 0;
break;
default:
ASSERT_NOT_REACHED();
break;
}
return clampTo(floor((m_steps * fraction) + startOffset) / m_steps, 0.0, 1.0);
}
void StepsTimingFunction::partition(Vector<PartitionRegion>& regions) const
{
double split = 0.0;
if (m_steps % 2 == 0) {
switch (m_stepAtPosition) {
case Start:
split = 0.5 - (1.0 / m_steps);
break;
case Middle:
split = 0.5 - (0.5 / m_steps);
break;
case End:
split = 0.5;
break;
default:
ASSERT_NOT_REACHED();
return;
}
} else {
switch (m_stepAtPosition) {
case Start:
split = 0.5 - (0.5 / m_steps);
break;
case Middle:
split = 0.5;
break;
case End:
split = 0.5 + (0.5 / m_steps);
break;
default:
ASSERT_NOT_REACHED();
return;
}
}
regions.append(PartitionRegion(TimingFunction::RangeHalf::Lower, 0.0, split));
regions.append(PartitionRegion(TimingFunction::RangeHalf::Upper, split, 1.0));
}
// Equals operators
bool operator==(const LinearTimingFunction& lhs, const TimingFunction& rhs)
{
return rhs.type() == TimingFunction::kLinearFunction;
}
bool operator==(const CubicBezierTimingFunction& lhs, const TimingFunction& rhs)
{
if (rhs.type() != TimingFunction::kCubicBezierFunction)
return false;
const CubicBezierTimingFunction& ctf = toCubicBezierTimingFunction(rhs);
if ((lhs.subType() == CubicBezierTimingFunction::Custom) && (ctf.subType() == CubicBezierTimingFunction::Custom))
return (lhs.x1() == ctf.x1()) && (lhs.y1() == ctf.y1()) && (lhs.x2() == ctf.x2()) && (lhs.y2() == ctf.y2());
return lhs.subType() == ctf.subType();
}
bool operator==(const StepsTimingFunction& lhs, const TimingFunction& rhs)
{
if (rhs.type() != TimingFunction::kStepsFunction)
return false;
const StepsTimingFunction& stf = toStepsTimingFunction(rhs);
return (lhs.numberOfSteps() == stf.numberOfSteps()) && (lhs.getStepAtPosition() == stf.getStepAtPosition());
}
// The generic operator== *must* come after the
// non-generic operator== otherwise it will end up calling itself.
bool operator==(const TimingFunction& lhs, const TimingFunction& rhs)
{
switch (lhs.type()) {
case TimingFunction::kLinearFunction: {
const LinearTimingFunction& linear = toLinearTimingFunction(lhs);
return (linear == rhs);
}
case TimingFunction::kCubicBezierFunction: {
const CubicBezierTimingFunction& cubic = toCubicBezierTimingFunction(lhs);
return (cubic == rhs);
}
case TimingFunction::kStepsFunction: {
const StepsTimingFunction& step = toStepsTimingFunction(lhs);
return (step == rhs);
}
default:
ASSERT_NOT_REACHED();
}
return false;
}
// No need to define specific operator!= as they can all come via this function.
bool operator!=(const TimingFunction& lhs, const TimingFunction& rhs)
{
return !(lhs == rhs);
}
} // namespace blink