// 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 "flutter/sky/engine/platform/animation/TimingFunction.h" #include "flutter/sky/engine/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 {} 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::epsilon() && std::abs(b) < std::numeric_limits::epsilon()) { return; } double t1 = 0.0; double t2 = 0.0; if (std::abs(a) < std::numeric_limits::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::epsilon()); double solutionMax = m_bezier->solve(*maxValue, std::numeric_limits::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); } String StepsTimingFunction::toString() const { StringBuilder builder; switch (this->subType()) { case StepsTimingFunction::Start: return "step-start"; case StepsTimingFunction::Middle: return "step-middle"; case StepsTimingFunction::End: return "step-end"; case StepsTimingFunction::Custom: builder.append("steps(" + String::numberToStringECMAScript(this->numberOfSteps()) + ", "); if (this->stepAtPosition() == StepsTimingFunction::StepAtStart) builder.appendLiteral("start"); else if (this->stepAtPosition() == StepsTimingFunction::StepAtMiddle) builder.appendLiteral("middle"); else if (this->stepAtPosition() == StepsTimingFunction::StepAtEnd) builder.appendLiteral("end"); else ASSERT_NOT_REACHED(); builder.append(')'); break; default: ASSERT_NOT_REACHED(); } 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 StepAtStart: startOffset = 1; break; case StepAtMiddle: startOffset = 0.5; break; case StepAtEnd: startOffset = 0; break; default: ASSERT_NOT_REACHED(); break; } return clampTo(floor((m_steps * fraction) + startOffset) / m_steps, 0.0, 1.0); } // Equals operators bool operator==(const LinearTimingFunction& lhs, const TimingFunction& rhs) { return rhs.type() == TimingFunction::LinearFunction; } bool operator==(const CubicBezierTimingFunction& lhs, const TimingFunction& rhs) { if (rhs.type() != TimingFunction::CubicBezierFunction) 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::StepsFunction) return false; const StepsTimingFunction& stf = toStepsTimingFunction(rhs); if ((lhs.subType() == StepsTimingFunction::Custom) && (stf.subType() == StepsTimingFunction::Custom)) return (lhs.numberOfSteps() == stf.numberOfSteps()) && (lhs.stepAtPosition() == stf.stepAtPosition()); return lhs.subType() == stf.subType(); } // 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::LinearFunction: { const LinearTimingFunction& linear = toLinearTimingFunction(lhs); return (linear == rhs); } case TimingFunction::CubicBezierFunction: { const CubicBezierTimingFunction& cubic = toCubicBezierTimingFunction(lhs); return (cubic == rhs); } case TimingFunction::StepsFunction: { 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