constructor(x1, y1, x2, y2, functionName) {
    // pre-calculate the polynomial coefficients
    // First and last control points are implied to be (0.0, 0.0) and (1.0, 1.0)
    const p1x = x1 || 0;
    const p1y = y1 || 0;
    const p2x = x2 || 1;
    const p2y = y2 || 1;
  
    /** @type {number} */
    this.cx = 3 * p1x;
  
    /** @type {number} */
    this.bx = 3 * (p2x - p1x) - this.cx;

    /** @type {number} */
    this.ax = 1 - this.cx - this.bx;
    
    /** @type {number} */
    this.cy = 3 * p1y;
  
    /** @type {number} */
    this.by = 3 * (p2y - p1y) - this.cy;
  
    /** @type {number} */
    this.ay = 1 - this.cy - this.by;
    
    /** @type {(t: number) => number} */
    const BezierEasing = (t) => this.sampleCurveY(this.solveCurveX(t));

    // this function needs a name
    Object.defineProperty(BezierEasing, 'name', { writable: true });
    BezierEasing.name = functionName || `cubic-bezier(${[p1x, p1y, p2x, p2y]})`;

    return BezierEasing;
  }
@constructor
@param {number} x1 - first point horizontal position
@param {number} y1 - first point vertical position
@param {number} x2 - second point horizontal position
@param {number} y2 - second point vertical position
@param {string=} functionName - an optional function name
@returns {(t: number) => number} a new CubicBezier easing function