easing.ts

import { between } from "@daybrush/utils";
import { EasingFunction } from "./types";

function cubic(y1: number, y2: number, t: number) {
  const t2 = 1 - t;

  // Bezier Curve Formula
  return t * t * t + 3 * t * t * t2 * y2 + 3 * t * t2 * t2 * y1;
}
function solveFromX(x1: number, x2: number, x: number) {
  // x  0 ~ 1
  // t 0 ~ 1
  let t = x;
  let solveX = x;
  let dx = 1;

  while (Math.abs(dx) > 1 / 1000) {
    // 예상 t초에 의한 _x값
    solveX = cubic(x1, x2, t);
    dx = solveX - x;
    // 차이가 미세하면 그 값을 t로 지정
    if (Math.abs(dx) < 1 / 1000) {
      return t;
    }
    t -= dx / 2;
  }
  return t;
}
/**
 * @namespace easing
 */
/**
* Cubic Bezier curve.
* @memberof easing
* @func bezier
* @param {number} [x1] - point1's x
* @param {number} [y1] - point1's y
* @param {number} [x2] - point2's x
* @param {number} [y2] - point2's y
* @return {function} the curve function
* @example
import {bezier} from "scenejs";
Scene.bezier(0, 0, 1, 1) // LINEAR
Scene.bezier(0.25, 0.1, 0.25, 1) // EASE
*/
export function bezier(x1: number, y1: number, x2: number, y2: number) {
  /*
		x = f(t)
		calculate inverse function by x
		t = f-1(x)
	*/
  const func: EasingFunction = (x: number) => {
    const t = solveFromX(x1, x2, between(x, 0, 1));

    return cubic(y1, y2, t);
  };

  func.easingName = `cubic-bezier(${x1},${y1},${x2},${y2})`;
  return func;
}
/**
* Specifies a stepping function
* @see {@link https://www.w3schools.com/cssref/css3_pr_animation-timing-function.asp|CSS3 Timing Function}
* @memberof easing
* @func steps
* @param {number} count - point1's x
* @param {"start" | "end"} postion - point1's y
* @return {function} the curve function
* @example
import {steps} from "scenejs";
Scene.steps(1, "start") // Scene.STEP_START
Scene.steps(1, "end") // Scene.STEP_END
*/
export function steps(count: number, position: "start" | "end") {
  const func: EasingFunction = (time: number) => {
    const level = 1 / count;

    if (time >= 1) {
      return 1;
    }
    return (position === "start" ? level : 0) + Math.floor(time / level) * level;
  };

  func.easingName = `steps(${count}, ${position})`;

  return func;
}

/**
* Equivalent to steps(1, start)
* @memberof easing
* @name STEP_START
* @static
* @type {function}
* @example
import {STEP_START} from "scenejs";
Scene.STEP_START // steps(1, start)
*/
export const STEP_START = /*#__PURE__#*/steps(1, "start");
/**
* Equivalent to steps(1, end)
* @memberof easing
* @name STEP_END
* @static
* @type {function}
* @example
import {STEP_END} from "scenejs";
Scene.STEP_END // steps(1, end)
*/
export const STEP_END = /*#__PURE__#*/steps(1, "end");
/**
* Linear Speed (0, 0, 1, 1)
* @memberof easing
* @name LINEAR
* @static
* @type {function}
* @example
import {LINEAR} from "scenejs";
Scene.LINEAR
*/
export const LINEAR = /*#__PURE__#*/bezier(0, 0, 1, 1);
/**
* Ease Speed (0.25, 0.1, 0.25, 1)
* @memberof easing
* @name EASE
* @static
* @type {function}
* @example
import {EASE} from "scenejs";
Scene.EASE
*/
export const EASE = /*#__PURE__#*/bezier(0.25, 0.1, 0.25, 1);
/**
* Ease In Speed (0.42, 0, 1, 1)
* @memberof easing
* @name EASE_IN
* @static
* @type {function}
* @example
import {EASE_IN} from "scenejs";
Scene.EASE_IN
*/
export const EASE_IN = /*#__PURE__#*/bezier(0.42, 0, 1, 1);
/**
* Ease Out Speed (0, 0, 0.58, 1)
* @memberof easing
* @name EASE_OUT
* @static
* @type {function}
* @example
import {EASE_OUT} from "scenejs";
Scene.EASE_OUT
*/
export const EASE_OUT = /*#__PURE__#*/bezier(0, 0, 0.58, 1);
/**
* Ease In Out Speed (0.42, 0, 0.58, 1)
* @memberof easing
* @name EASE_IN_OUT
* @static
* @type {function}
* @example
import {EASE_IN_OUT} from "scenejs";
Scene.EASE_IN_OUT
*/
export const EASE_IN_OUT = /*#__PURE__#*/bezier(0.42, 0, 0.58, 1);