Source: player/Timing.js

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/** Timing functions for transitions.
 *
 * This module is based on the article by Gaëtan Renaudeau, with code released under an MIT license.
 *
 * @module
 * @see {@link http://greweb.me/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/ Bézier Curve based easing functions – from concept to implementation}
 */

/** Helper function to compute a Bézier curve.
 *
 * @param {number} xy1 - X or Y coordinate of the first control point.
 * @param {number} xy2 - X or Y coordinate of the second control point.
 * @returns {number} - A linear combination of the arguments.
 */
function A(xy1, xy2) {
    return 1.0 - 3.0 * xy2 + 3.0 * xy1;
}

/** Helper function to compute a Bézier curve.
 *
 * @param {number} xy1 - X or Y coordinate of the first control point.
 * @param {number} xy2 - X or Y coordinate of the second control point.
 * @returns {number} - A linear combination of the arguments.
 */
function B(xy1, xy2) {
    return 3.0 * xy2 - 6.0 * xy1;
}

/** Helper function to compute a Bézier curve.
 *
 * @param {number} xy1 - X or Y coordinate of the first control point.
 * @returns {number} - A linear combination of the arguments.
 */
function C(xy1) {
    return 3.0 * xy1;
}

/** Compute a coordinate of a point of the Bézier curve.
 *
 * @param {number} t - The location of the point along the curve.
 * @param {number} a - The output of function `A` for the control points of the curve.
 * @param {number} b - The output of function `B` for the control points of the curve.
 * @param {number} c - The output of function `C` for the control points of the curve.
 * @returns {number} - The X or Y coordinate of a point of the Bézier curve.
 */
function bezier(t, a, b, c) {
    return ((a * t + b) * t + c) * t;
}

/** Compute the derivative of a coordinate at a point of the Bézier curve.
 *
 * @param {number} t - The location of the point along the curve.
 * @param {number} a - The output of function `A` for the control points of the curve.
 * @param {number} b - The output of function `B` for the control points of the curve.
 * @param {number} c - The output of function `C` for the control points of the curve.
 * @returns {number} - The derivative dX/dt or dY/dt of a coordinate at a point of the Bézier curve.
 */
function bezierSlope(t, a, b, c) {
    return (3.0 * a * t + 2.0 * b) * t + c;
}

/** Create a Bézier curve with the given control points.
 *
 * @param {number} x1 - The X coordinate of the first control point.
 * @param {number} y1 - The Y coordinate of the first control point.
 * @param {number} x2 - The X coordinate of the second control point.
 * @param {number} y2 - The Y coordinate of the second control point.
 * @returns {Function} - A function that takes a coordinates X and computes the Y coordinate of the corresponding point of the Bézier curve.
 */
export function makeBezier(x1, y1, x2, y2) {
    const ax = A(x1, x2), bx = B(x1, x2), cx = C(x1);
    const ay = A(y1, y2), by = B(y1, y2), cy = C(y1);

    if (x1 === y1 && x2 === y2) {
        // Linear
        return function (x) {
            return x;
        };
    }

    return function(x) {
        // Newton raphson iteration
        let t = x;
        for (let i = 0; i < 4; i++) {
            const currentSlope = bezierSlope(t, ax, bx, cx);
            if (currentSlope === 0.0) {
                break;
            }
            const currentX = bezier(t, ax, bx, cx) - x;
            t -= currentX / currentSlope;
        }
        return bezier(t, ay, by, cy);
    };
}

/** Create a staircase function.
 *
 * @param {number} n - The number of steps.
 * @param {boolean} start - Step at the beginning or at the end of each interval?
 * @returns {Function} - A function that takes a coordinates X and computes the Y coordinate of the corresponding point of the step function.
 */
export function makeSteps(n, start) {
    const trunc = start ? Math.ceil : Math.floor;
    return function (x) {
        return trunc(n * x) / n;
    };
}

/** A linear timing function.
 *
 * @param {number} t - The current time, between 0 and 1.
 * @returns {number} - The actual progress, between 0 and 1.
 */
export const linear = makeBezier(0.0,  0.0, 1.0,  1.0);
linear.reverse = linear;

/** An easing timing function.
 *
 * @param {number} t - The current time, between 0 and 1.
 * @returns {number} - The actual progress, between 0 and 1.
 */
export const ease = makeBezier(0.25, 0.1, 0.25, 1.0);
ease.reverse = ease;

/** An ease-in timing function.
 *
 * @param {number} t - The current time, between 0 and 1.
 * @returns {number} - The actual progress, between 0 and 1.
 */
export const easeIn = makeBezier(0.42, 0.0, 1.0,  1.0);

/** An ease-out timing function.
 *
 * @param {number} t - The current time, between 0 and 1.
 * @returns {number} - The actual progress, between 0 and 1.
 */
export const easeOut = makeBezier(0.0,  0.0, 0.58, 1.0);

easeIn.reverse = easeOut;
easeOut.reverse = easeIn;

/** A timing function with ease-in and ease-out.
 *
 * @param {number} t - The current time, between 0 and 1.
 * @returns {number} - The actual progress, between 0 and 1.
 */
export const easeInOut = makeBezier(0.42, 0.0, 0.58, 1.0);
easeInOut.reverse = easeInOut;

/** A single immediate step timing function.
 *
 * @param {number} t - The current time, between 0 and 1.
 * @returns {number} - The actual progress, between 0 and 1.
 */
export const stepStart = makeSteps(1, true);

/** A single final step timing function.
 *
 * @param {number} t - The current time, between 0 and 1.
 * @returns {number} - The actual progress, between 0 and 1.
 */
export const stepEnd = makeSteps(1, false);

stepStart.reverse = stepEnd;
stepEnd.reverse = stepStart;

/** A middle step timing function.
 *
 * @param {number} t - The current time, between 0 and 1.
 * @returns {number} - The actual progress, between 0 and 1.
 */
export function stepMiddle(t) {
    return t >= 0.5 ? 1 : 0;
}
stepMiddle.reverse = stepMiddle;