/**
 * @module Shape
 * @submodule Curves
 * @for p5
 */

function curves(p5, fn){
  /**
   * Draws a Bézier curve.
   *
   * Bézier curves can form shapes and curves that slope gently. They're defined
   * by two anchor points and two control points. Bézier curves provide more
   * control than the spline curves created with the
   * <a href="#/p5/spline">spline()</a> function.
   *
   * The first two parameters, `x1` and `y1`, set the first anchor point. The
   * first anchor point is where the curve starts.
   *
   * The next four parameters, `x2`, `y2`, `x3`, and `y3`, set the two control
   * points. The control points "pull" the curve towards them.
   *
   * The seventh and eighth parameters, `x4` and `y4`, set the last anchor
   * point. The last anchor point is where the curve ends.
   *
   * Bézier curves can also be drawn in 3D using WebGL mode. The 3D version of
   * `bezier()` has twelve arguments because each point has x-, y-,
   * and z-coordinates.
   *
   * @method bezier
   * @param  {Number} x1 x-coordinate of the first anchor point.
   * @param  {Number} y1 y-coordinate of the first anchor point.
   * @param  {Number} x2 x-coordinate of the first control point.
   * @param  {Number} y2 y-coordinate of the first control point.
   * @param  {Number} x3 x-coordinate of the second control point.
   * @param  {Number} y3 y-coordinate of the second control point.
   * @param  {Number} x4 x-coordinate of the second anchor point.
   * @param  {Number} y4 y-coordinate of the second anchor point.
   * @chainable
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Draw the anchor points in black.
   *   stroke(0);
   *   strokeWeight(5);
   *   point(85, 20);
   *   point(15, 80);
   *
   *   // Draw the control points in red.
   *   stroke(255, 0, 0);
   *   point(10, 10);
   *   point(90, 90);
   *
   *   // Draw a black bezier curve.
   *   noFill();
   *   stroke(0);
   *   strokeWeight(1);
   *   bezier(85, 20, 10, 10, 90, 90, 15, 80);
   *
   *   // Draw red lines from the anchor points to the control points.
   *   stroke(255, 0, 0);
   *   line(85, 20, 10, 10);
   *   line(15, 80, 90, 90);
   *
   *   describe(
   *     'A gray square with three curves. A black s-curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
   *   );
   * }
   *
   * @example
   * // Click the mouse near the red dot in the top-left corner
   * // and drag to change the curve's shape.
   *
   * let x2 = 10;
   * let y2 = 10;
   * let isChanging = false;
   *
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe(
   *     'A gray square with three curves. A black s-curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
   *   );
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Draw the anchor points in black.
   *   stroke(0);
   *   strokeWeight(5);
   *   point(85, 20);
   *   point(15, 80);
   *
   *   // Draw the control points in red.
   *   stroke(255, 0, 0);
   *   point(x2, y2);
   *   point(90, 90);
   *
   *   // Draw a black bezier curve.
   *   noFill();
   *   stroke(0);
   *   strokeWeight(1);
   *   bezier(85, 20, x2, y2, 90, 90, 15, 80);
   *
   *   // Draw red lines from the anchor points to the control points.
   *   stroke(255, 0, 0);
   *   line(85, 20, x2, y2);
   *   line(15, 80, 90, 90);
   * }
   *
   * // Start changing the first control point if the user clicks near it.
   * function mousePressed() {
   *   if (dist(mouseX, mouseY, x2, y2) < 20) {
   *     isChanging = true;
   *   }
   * }
   *
   * // Stop changing the first control point when the user releases the mouse.
   * function mouseReleased() {
   *   isChanging = false;
   * }
   *
   * // Update the first control point while the user drags the mouse.
   * function mouseDragged() {
   *   if (isChanging === true) {
   *     x2 = mouseX;
   *     y2 = mouseY;
   *   }
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background('skyblue');
   *
   *   // Draw the red balloon.
   *   fill('red');
   *   bezier(50, 60, 5, 15, 95, 15, 50, 60);
   *
   *   // Draw the balloon string.
   *   line(50, 60, 50, 80);
   *
   *   describe('A red balloon in a blue sky.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100, WEBGL);
   *
   *   describe('A red balloon in a blue sky. The balloon rotates slowly, revealing that it is flat.');
   * }
   *
   * function draw() {
   *   background('skyblue');
   *
   *   // Rotate around the y-axis.
   *   rotateY(frameCount * 0.01);
   *
   *   // Draw the red balloon.
   *   fill('red');
   *   bezier(0, 0, 0, -45, -45, 0, 45, -45, 0, 0, 0, 0);
   *
   *   // Draw the balloon string.
   *   line(0, 0, 0, 0, 20, 0);
   * }
   */
  /**
   * @method bezier
   * @param  {Number} x1
   * @param  {Number} y1
   * @param  {Number} z1 z-coordinate of the first anchor point.
   * @param  {Number} x2
   * @param  {Number} y2
   * @param  {Number} z2 z-coordinate of the first control point.
   * @param  {Number} x3
   * @param  {Number} y3
   * @param  {Number} z3 z-coordinate of the second control point.
   * @param  {Number} x4
   * @param  {Number} y4
   * @param  {Number} z4 z-coordinate of the second anchor point.
   * @chainable
   */
  fn.bezier = function(...args) {
    // p5._validateParameters('bezier', args);

    // if the current stroke and fill settings wouldn't result in something
    // visible, exit immediately
    if (
      !this._renderer.states.strokeColor &&
      !this._renderer.states.fillColor
    ) {
      return this;
    }

    this._renderer.bezier(...args);

    return this;
  };

  /**
   * Calculates coordinates along a Bézier curve using interpolation.
   *
   * `bezierPoint()` calculates coordinates along a Bézier curve using the
   * anchor and control points. It expects points in the same order as the
   * <a href="#/p5/bezier">bezier()</a> function. `bezierPoint()` works one axis
   * at a time. Passing the anchor and control points' x-coordinates will
   * calculate the x-coordinate of a point on the curve. Passing the anchor and
   * control points' y-coordinates will calculate the y-coordinate of a point on
   * the curve.
   *
   * The first parameter, `a`, is the coordinate of the first anchor point.
   *
   * The second and third parameters, `b` and `c`, are the coordinates of the
   * control points.
   *
   * The fourth parameter, `d`, is the coordinate of the last anchor point.
   *
   * The fifth parameter, `t`, is the amount to interpolate along the curve. 0
   * is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
   * between them.
   *
   * @method bezierPoint
   * @param {Number} a coordinate of first anchor point.
   * @param {Number} b coordinate of first control point.
   * @param {Number} c coordinate of second control point.
   * @param {Number} d coordinate of second anchor point.
   * @param {Number} t amount to interpolate between 0 and 1.
   * @return {Number} coordinate of the point on the curve.
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Set the coordinates for the curve's anchor and control points.
   *   let x1 = 85;
   *   let x2 = 10;
   *   let x3 = 90;
   *   let x4 = 15;
   *   let y1 = 20;
   *   let y2 = 10;
   *   let y3 = 90;
   *   let y4 = 80;
   *
   *   // Style the curve.
   *   noFill();
   *
   *   // Draw the curve.
   *   bezier(x1, y1, x2, y2, x3, y3, x4, y4);
   *
   *   // Draw circles along the curve's path.
   *   fill(255);
   *
   *   // Top-right.
   *   let x = bezierPoint(x1, x2, x3, x4, 0);
   *   let y = bezierPoint(y1, y2, y3, y4, 0);
   *   circle(x, y, 5);
   *
   *   // Center.
   *   x = bezierPoint(x1, x2, x3, x4, 0.5);
   *   y = bezierPoint(y1, y2, y3, y4, 0.5);
   *   circle(x, y, 5);
   *
   *   // Bottom-left.
   *   x = bezierPoint(x1, x2, x3, x4, 1);
   *   y = bezierPoint(y1, y2, y3, y4, 1);
   *   circle(x, y, 5);
   *
   *   describe('A black s-curve on a gray square. The endpoints and center of the curve are marked with white circles.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('A black s-curve on a gray square. A white circle moves back and forth along the curve.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Set the coordinates for the curve's anchor and control points.
   *   let x1 = 85;
   *   let x2 = 10;
   *   let x3 = 90;
   *   let x4 = 15;
   *   let y1 = 20;
   *   let y2 = 10;
   *   let y3 = 90;
   *   let y4 = 80;
   *
   *   // Draw the curve.
   *   noFill();
   *   bezier(x1, y1, x2, y2, x3, y3, x4, y4);
   *
   *   // Calculate the circle's coordinates.
   *   let t = 0.5 * sin(frameCount * 0.01) + 0.5;
   *   let x = bezierPoint(x1, x2, x3, x4, t);
   *   let y = bezierPoint(y1, y2, y3, y4, t);
   *
   *   // Draw the circle.
   *   fill(255);
   *   circle(x, y, 5);
   * }
   */
  fn.bezierPoint = function(a, b, c, d, t) {
    // p5._validateParameters('bezierPoint', arguments);

    const adjustedT = 1 - t;
    return (
      Math.pow(adjustedT, 3) * a +
      3 * Math.pow(adjustedT, 2) * t * b +
      3 * adjustedT * Math.pow(t, 2) * c +
      Math.pow(t, 3) * d
    );
  };

  /**
   * Calculates coordinates along a line that's tangent to a Bézier curve.
   *
   * Tangent lines skim the surface of a curve. A tangent line's slope equals
   * the curve's slope at the point where it intersects.
   *
   * `bezierTangent()` calculates coordinates along a tangent line using the
   * Bézier curve's anchor and control points. It expects points in the same
   * order as the <a href="#/p5/bezier">bezier()</a> function. `bezierTangent()`
   * works one axis at a time. Passing the anchor and control points'
   * x-coordinates will calculate the x-coordinate of a point on the tangent
   * line. Passing the anchor and control points' y-coordinates will calculate
   * the y-coordinate of a point on the tangent line.
   *
   * The first parameter, `a`, is the coordinate of the first anchor point.
   *
   * The second and third parameters, `b` and `c`, are the coordinates of the
   * control points.
   *
   * The fourth parameter, `d`, is the coordinate of the last anchor point.
   *
   * The fifth parameter, `t`, is the amount to interpolate along the curve. 0
   * is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
   * between them.
   *
   * @method bezierTangent
   * @param {Number} a coordinate of first anchor point.
   * @param {Number} b coordinate of first control point.
   * @param {Number} c coordinate of second control point.
   * @param {Number} d coordinate of second anchor point.
   * @param {Number} t amount to interpolate between 0 and 1.
   * @return {Number} coordinate of a point on the tangent line.
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Set the coordinates for the curve's anchor and control points.
   *   let x1 = 85;
   *   let x2 = 10;
   *   let x3 = 90;
   *   let x4 = 15;
   *   let y1 = 20;
   *   let y2 = 10;
   *   let y3 = 90;
   *   let y4 = 80;
   *
   *   // Style the curve.
   *   noFill();
   *
   *   // Draw the curve.
   *   bezier(x1, y1, x2, y2, x3, y3, x4, y4);
   *
   *   // Draw tangents along the curve's path.
   *   fill(255);
   *
   *   // Top-right circle.
   *   stroke(0);
   *   let x = bezierPoint(x1, x2, x3, x4, 0);
   *   let y = bezierPoint(y1, y2, y3, y4, 0);
   *   circle(x, y, 5);
   *
   *   // Top-right tangent line.
   *   // Scale the tangent point to draw a shorter line.
   *   stroke(255, 0, 0);
   *   let tx = 0.1 * bezierTangent(x1, x2, x3, x4, 0);
   *   let ty = 0.1 * bezierTangent(y1, y2, y3, y4, 0);
   *   line(x + tx, y + ty, x - tx, y - ty);
   *
   *   // Center circle.
   *   stroke(0);
   *   x = bezierPoint(x1, x2, x3, x4, 0.5);
   *   y = bezierPoint(y1, y2, y3, y4, 0.5);
   *   circle(x, y, 5);
   *
   *   // Center tangent line.
   *   // Scale the tangent point to draw a shorter line.
   *   stroke(255, 0, 0);
   *   tx = 0.1 * bezierTangent(x1, x2, x3, x4, 0.5);
   *   ty = 0.1 * bezierTangent(y1, y2, y3, y4, 0.5);
   *   line(x + tx, y + ty, x - tx, y - ty);
   *
   *   // Bottom-left circle.
   *   stroke(0);
   *   x = bezierPoint(x1, x2, x3, x4, 1);
   *   y = bezierPoint(y1, y2, y3, y4, 1);
   *   circle(x, y, 5);
   *
   *   // Bottom-left tangent.
   *   // Scale the tangent point to draw a shorter line.
   *   stroke(255, 0, 0);
   *   tx = 0.1 * bezierTangent(x1, x2, x3, x4, 1);
   *   ty = 0.1 * bezierTangent(y1, y2, y3, y4, 1);
   *   line(x + tx, y + ty, x - tx, y - ty);
   *
   *   describe(
   *     'A black s-curve on a gray square. The endpoints and center of the curve are marked with white circles. Red tangent lines extend from the white circles.'
   *   );
   * }
   */
  fn.bezierTangent = function(a, b, c, d, t) {
    // p5._validateParameters('bezierTangent', arguments);

    const adjustedT = 1 - t;
    return (
      3 * d * Math.pow(t, 2) -
      3 * c * Math.pow(t, 2) +
      6 * c * adjustedT * t -
      6 * b * adjustedT * t +
      3 * b * Math.pow(adjustedT, 2) -
      3 * a * Math.pow(adjustedT, 2)
    );
  };

  /**
   * Draws a curve using a Catmull-Rom spline.
   *
   * Spline curves can form shapes and curves that slope gently. They’re like
   * cables that are attached to a set of points. By default (`ends: INCLUDE`),
   * the curve passes through all four points you provide, in order
   * `p0(x1,y1)` -> `p1(x2,y2)` -> `p2(x3,y3)` -> `p3(x4,y4)`. Think of them as
   * points on a curve. If you switch to `ends: EXCLUDE`, p0 and p3 act
   * like control points and only the middle span `p1->p2` is drawn.
   *
   * Spline curves can also be drawn in 3D using WebGL mode. The 3D version of
   * `spline()` has twelve arguments because each point has x-, y-, and
   * z-coordinates.
   *
   * @method spline
   * @param  {Number} x1 x-coordinate of point p0.
   * @param  {Number} y1 y-coordinate of point p0.
   * @param  {Number} x2 x-coordinate of point p1.
   * @param  {Number} y2 y-coordinate of point p1.
   * @param  {Number} x3 x-coordinate of point p2.
   * @param  {Number} y3 y-coordinate of point p2.
   * @param  {Number} x4 x-coordinate of point p3.
   * @param  {Number} y4 y-coordinate of point p3.
   * @chainable
   *
   * @example
   * function setup() {
   *   createCanvas(200, 200);
   *   background(240);
   *   noFill();
   *
   *   stroke(0);
   *   strokeWeight(2);
   *   spline(40, 60, 100, 40, 120, 120, 60, 140);
   *
   *   strokeWeight(5);
   *   point(40, 60);
   *   point(100, 40);
   *   point(120, 120);
   *   point(60, 140);
   *
   *   describe('A black spline passes smoothly through four points');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(200, 200);
   *   background(245);
   *
   *   // Ensure the curve includes both end spans p0->p1 and p2->p3
   *   splineProperty('ends', INCLUDE);
   *
   *   // Control / anchor points
   *   const p0 = createVector(30, 160);
   *   const p1 = createVector(60, 40);
   *   const p2 = createVector(140, 40);
   *   const p3 = createVector(170, 160);
   *
   *   // Draw the spline that passes through ALL four points (INCLUDE)
   *   noFill();
   *   stroke(0);
   *   strokeWeight(2);
   *   spline(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
   *
   *   // Draw markers + labels
   *   fill(255);
   *   stroke(0);
   *   const r = 6;
   *   circle(p0.x, p0.y, r);
   *   circle(p1.x, p1.y, r);
   *   circle(p2.x, p2.y, r);
   *   circle(p3.x, p3.y, r);
   *
   *   noStroke();
   *   fill(0);
   *   text('p0', p0.x - 14, p0.y + 14);
   *   text('p1', p1.x - 14, p1.y - 8);
   *   text('p2', p2.x + 4, p2.y - 8);
   *   text('p3', p3.x + 4, p3.y + 14);
   *
   *   describe('A black Catmull-Rom spline passes through p0, p1, p2, p3 with endpoints included.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Exclude the ends—skip the outer spans (p0→p1 and p2→p3) so only the middle span (p1→p2) is drawn.
   *   splineProperty('ends', EXCLUDE);
   *
   *   // Draw a black spline curve.
   *   noFill();
   *   strokeWeight(1);
   *   stroke(0);
   *   spline(5, 26, 73, 24, 73, 61, 15, 65);
   *
   *   // Draw red spline curves from the points.
   *   stroke(255, 0, 0);
   *   spline(5, 26, 5, 26, 73, 24, 73, 61);
   *   spline(73, 24, 73, 61, 15, 65, 15, 65);
   *
   *   // Draw the points in black.
   *   strokeWeight(5);
   *   stroke(0);
   *   point(73, 24);
   *   point(73, 61);
   *
   *   // Draw the points in red.
   *   stroke(255, 0, 0);
   *   point(5, 26);
   *   point(15, 65);
   *
   *   describe(
   *     'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
   *   );
   * }
   *
   * @example
   * let x1 = 5;
   * let y1 = 26;
   * let isChanging = false;
   *
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe(
   *     'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
   *   );
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Exclude the ends—skip the outer spans (p0→p1 and p2→p3) so only the middle span (p1→p2) is drawn.
   *   splineProperty('ends', EXCLUDE);
   *
   *   // Draw a black spline curve.
   *   noFill();
   *   strokeWeight(1);
   *   stroke(0);
   *   spline(x1, y1, 73, 24, 73, 61, 15, 65);
   *
   *   // Draw red spline curves from the points.
   *   stroke(255, 0, 0);
   *   spline(x1, y1, x1, y1, 73, 24, 73, 61);
   *   spline(73, 24, 73, 61, 15, 65, 15, 65);
   *
   *   // Draw the anchor points in black.
   *   strokeWeight(5);
   *   stroke(0);
   *   point(73, 24);
   *   point(73, 61);
   *
   *   // Draw the points in red.
   *   stroke(255, 0, 0);
   *   point(x1, y1);
   *   point(15, 65);
   * }
   *
   * // Start changing the first point if the user clicks near it.
   * function mousePressed() {
   *   if (dist(mouseX, mouseY, x1, y1) < 20) {
   *     isChanging = true;
   *   }
   * }
   *
   * // Stop changing the first point when the user releases the mouse.
   * function mouseReleased() {
   *   isChanging = false;
   * }
   *
   * // Update the first point while the user drags the mouse.
   * function mouseDragged() {
   *   if (isChanging === true) {
   *     x1 = mouseX;
   *     y1 = mouseY;
   *   }
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background('skyblue');
   *
   *   // Exclude the ends—skip the outer spans (p0→p1 and p2→p3) so only the middle span (p1→p2) is drawn.
   *   splineProperty('ends', EXCLUDE);
   *
   *   // Draw the red balloon.
   *   fill('red');
   *   spline(-150, 275, 50, 60, 50, 60, 250, 275);
   *
   *   // Draw the balloon string.
   *   line(50, 60, 50, 80);
   *
   *   describe('A red balloon in a blue sky.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100, WEBGL);
   *
   *   describe('A red balloon in a blue sky.');
   * }
   *
   * function draw() {
   *   background('skyblue');
   *
   *   // Exclude the ends—skip the outer spans (p0→p1 and p2→p3) so only the middle span (p1→p2) is drawn.
   *   splineProperty('ends', EXCLUDE);
   *
   *   // Rotate around the y-axis.
   *   rotateY(frameCount * 0.01);
   *
   *   // Draw the red balloon.
   *   fill('red');
   *   spline(-200, 225, 0, 0, 10, 0, 0, 10, 0, 200, 225, 0);
   *
   *   // Draw the balloon string.
   *   line(0, 10, 0, 0, 30, 0);
   * }
   */
  /**
   * @method spline
   * @param  {Number} x1
   * @param  {Number} y1
   * @param  {Number} z1 z-coordinate of point p0.
   * @param  {Number} x2
   * @param  {Number} y2
   * @param  {Number} z2 z-coordinate of point p1.
   * @param  {Number} x3
   * @param  {Number} y3
   * @param  {Number} z3 z-coordinate of point p2.
   * @param  {Number} x4
   * @param  {Number} y4
   * @param  {Number} z4 z-coordinate of point p3.
   * @chainable
   */
  fn.spline = function(...args) {
    if (
      !this._renderer.states.strokeColor &&
      !this._renderer.states.fillColor
    ) {
      return this;
    }
    this._renderer.spline(...args);

    return this;
  };

  /**
   * Calculates coordinates along a spline curve using interpolation.
   *
   * `splinePoint()` calculates coordinates along a spline curve using four
   * points p0, p1, p2, p3. It expects points in the same order as the
   * <a href="#/p5/spline">spline()</a> function. `splinePoint()` works one axis
   * at a time. Passing the points' x-coordinates will
   * calculate the x-coordinate of a point on the curve. Passing the
   * points' y-coordinates will calculate the y-coordinate of a point on
   * the curve.
   *
   * The first parameter, `a`, is the coordinate of point p0.
   *
   * The second and third parameters, `b` and `c`, are the coordinates of
   * points p1 and p2.
   *
   * The fourth parameter, `d`, is the coordinate of point p3.
   *
   * The fifth parameter, `t`, is the amount to interpolate along the span
   * from p1 to p2. `t = 0` is p1, `t = 1` is p2, and `t = 0.5` is halfway
   * between them.
   *
   * @method splinePoint
   * @param {Number} a coordinate of point p0.
   * @param {Number} b coordinate of point p1.
   * @param {Number} c coordinate of point p2.
   * @param {Number} d coordinate of point p3.
   * @param {Number} t amount to interpolate between 0 and 1.
   * @return {Number} coordinate of a point on the curve.
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *
   *   // Set the coordinates for the curve's four points (p0, p1, p2, p3).
   *   let x1 = 5;
   *   let y1 = 26;
   *   let x2 = 73;
   *   let y2 = 24;
   *   let x3 = 73;
   *   let y3 = 61;
   *   let x4 = 15;
   *   let y4 = 65;
   *
   *   // Draw the curve.
   *   noFill();
   *   spline(x1, y1, x2, y2, x3, y3, x4, y4);
   *
   *   // Draw circles along the curve's path.
   *   fill(255);
   *
   *   // Top.
   *   let x = splinePoint(x1, x2, x3, x4, 0);
   *   let y = splinePoint(y1, y2, y3, y4, 0);
   *   circle(x, y, 5);
   *
   *   // Center.
   *   x = splinePoint(x1, x2, x3, x4, 0.5);
   *   y = splinePoint(y1, y2, y3, y4, 0.5);
   *   circle(x, y, 5);
   *
   *   // Bottom.
   *   x = splinePoint(x1, x2, x3, x4, 1);
   *   y = splinePoint(y1, y2, y3, y4, 1);
   *   circle(x, y, 5);
   *
   *   describe('A black curve on a gray square. The endpoints and center of the curve are marked with white circles.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('A black curve on a gray square. A white circle moves back and forth along the curve.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Set the coordinates for the curve's four points (p0, p1, p2, p3).
   *   let x1 = 5;
   *   let y1 = 26;
   *   let x2 = 73;
   *   let y2 = 24;
   *   let x3 = 73;
   *   let y3 = 61;
   *   let x4 = 15;
   *   let y4 = 65;
   *
   *   // Draw the curve.
   *   noFill();
   *   spline(x1, y1, x2, y2, x3, y3, x4, y4);
   *
   *   // Calculate the circle's coordinates.
   *   let t = 0.5 * sin(frameCount * 0.01) + 0.5;
   *   let x = splinePoint(x1, x2, x3, x4, t);
   *   let y = splinePoint(y1, y2, y3, y4, t);
   *
   *   // Draw the circle.
   *   fill(255);
   *   circle(x, y, 5);
   * }
   *
   * @example
   * let p0, p1, p2, p3;
   *
   * function setup() {
   *   createCanvas(200, 200);
   *   splineProperty('ends', INCLUDE); // make endpoints part of the curve
   *
   *   // Four points forming a gentle arch
   *   p0 = createVector(30, 160);
   *   p1 = createVector(60, 50);
   *   p2 = createVector(140, 50);
   *   p3 = createVector(170, 160);
   *
   *   describe('Black spline through p0–p3. A red dot marks the location at parameter t on p1->p2 using splinePoint.');
   * }
   *
   * function draw() {
   *   background(245);
   *
   *   // Draw the spline for context
   *   noFill();
   *   stroke(0);
   *   strokeWeight(2);
   *   spline(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
   *
   *   // Map mouse X to t in [0, 1] (span p1->p2)
   *   let t = constrain(map(mouseX, 0, width, 0, 1), 0, 1);
   *
   *   // Evaluate the curve point by axis (splinePoint works one axis at a time)
   *   let x = splinePoint(p0.x, p1.x, p2.x, p3.x, t);
   *   let y = splinePoint(p0.y, p1.y, p2.y, p3.y, t);
   *
   *   // Marker at the evaluated position
   *   noStroke();
   *   fill('red');
   *   circle(x, y, 8);
   *
   *   // Draw control/anchor points
   *   stroke(0);
   *   strokeWeight(1);
   *   fill(255);
   *   const r = 6;
   *   circle(p0.x, p0.y, r);
   *   circle(p1.x, p1.y, r);
   *   circle(p2.x, p2.y, r);
   *   circle(p3.x, p3.y, r);
   *
   *   // Labels + UI hint
   *   noStroke();
   *   fill(20);
   *   textSize(10);
   *   text('p0', p0.x - 12, p0.y + 14);
   *   text('p1', p1.x - 12, p1.y - 8);
   *   text('p2', p2.x + 4, p2.y - 8);
   *   text('p3', p3.x + 4, p3.y + 14);
   *   text('t = ' + nf(t, 1, 2) + ' (p1→p2)', 8, 16);
   * }
   */
  fn.splinePoint = function(a, b, c, d, t) {
    const s = this._renderer.states.splineProperties.tightness,
      t3 = t * t * t,
      t2 = t * t,
      f1 = (s - 1) / 2 * t3 + (1 - s) * t2 + (s - 1) / 2 * t,
      f2 = (s + 3) / 2 * t3 + (-5 - s) / 2 * t2 + 1.0,
      f3 = (-3 - s) / 2 * t3 + (s + 2) * t2 + (1 - s) / 2 * t,
      f4 = (1 - s) / 2 * t3 + (s - 1) / 2 * t2;
    return a * f1 + b * f2 + c * f3 + d * f4;
  };

  /**
   * Calculates coordinates along a line that's tangent to a spline curve.
   *
   * Tangent lines skim the surface of a curve. A tangent line's slope equals
   * the curve's slope at the point where it intersects.
   *
   * `splineTangent()` calculates coordinates along a tangent line using four
   * points p0, p1, p2, p3. It expects points in the same order as the
   * <a href="#/p5/spline">spline()</a> function. `splineTangent()` works one
   * axis at a time. Passing the points' x-coordinates returns the x-component of
   * the tangent vector; passing the points' y-coordinates returns the y-component.
   * The first parameter, `a`, is the coordinate of point p0.
   *
   * The second and third parameters, `b` and `c`, are the coordinates of
   * points p1 and p2.
   *
   * The fourth parameter, `d`, is the coordinate of point p3.
   *
   * The fifth parameter, `t`, is the amount to interpolate along the span
   * from p1 to p2. `t = 0` is p1, `t = 1` is p2, and `t = 0.5` is halfway
   * between them.
   *
   * @method splineTangent
   * @param {Number} a coordinate of point p0.
   * @param {Number} b coordinate of point p1.
   * @param {Number} c coordinate of point p2.
   * @param {Number} d coordinate of point p3.
   * @param {Number} t amount to interpolate between 0 and 1.
   * @return {Number} coordinate of a point on the tangent line.
   *
   * @example
   * function setup() {
   *   createCanvas(120, 120);
   *   describe('A black spline on a gray canvas. A red dot moves along the curve on its own. A short line shows the tangent direction at the dot.');
   * }
   *
   * function draw() {
   *   background(240);
   *
   *   const x1 = 15, y1 = 40;
   *   const x2 = 90, y2 = 25;
   *   const x3 = 95, y3 = 95;
   *   const x4 = 30, y4 = 110;
   *
   *   noFill();
   *   stroke(0);
   *   strokeWeight(2);
   *   spline(x1, y1, x2, y2, x3, y3, x4, y4);
   *
   *   const t = 0.5 + 0.5 * sin(frameCount * 0.03);
   *
   *   const px = splinePoint(x1, x2, x3, x4, t);
   *   const py = splinePoint(y1, y2, y3, y4, t);
   *
   *   let tx = splineTangent(x1, x2, x3, x4, t);
   *   let ty = splineTangent(y1, y2, y3, y4, t);
   *
   *   const m = Math.hypot(tx, ty) || 1;
   *   tx = (tx / m) * 16;
   *   ty = (ty / m) * 16;
   *
   *   stroke(0);
   *   strokeWeight(2);
   *   line(px, py, px + tx, py + ty);
   *
   *   noStroke();
   *   fill('red');
   *   circle(px, py, 7);
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Set the coordinates for the curve's four points (p0, p1, p2, p3).
   *   let x1 = 5;
   *   let y1 = 26;
   *   let x2 = 73;
   *   let y2 = 24;
   *   let x3 = 73;
   *   let y3 = 61;
   *   let x4 = 15;
   *   let y4 = 65;
   *
   *   // Draw the curve.
   *   noFill();
   *   spline(x1, y1, x2, y2, x3, y3, x4, y4);
   *
   *   // Draw tangents along the curve's path.
   *   fill(255);
   *
   *   // Top circle.
   *   stroke(0);
   *   let x = splinePoint(x1, x2, x3, x4, 0);
   *   let y = splinePoint(y1, y2, y3, y4, 0);
   *   circle(x, y, 5);
   *
   *   // Top tangent line.
   *   // Scale the tangent point to draw a shorter line.
   *   stroke(255, 0, 0);
   *   let tx = 0.2 * splineTangent(x1, x2, x3, x4, 0);
   *   let ty = 0.2 * splineTangent(y1, y2, y3, y4, 0);
   *   line(x + tx, y + ty, x - tx, y - ty);
   *
   *   // Center circle.
   *   stroke(0);
   *   x = splinePoint(x1, x2, x3, x4, 0.5);
   *   y = splinePoint(y1, y2, y3, y4, 0.5);
   *   circle(x, y, 5);
   *
   *   // Center tangent line.
   *   // Scale the tangent point to draw a shorter line.
   *   stroke(255, 0, 0);
   *   tx = 0.2 * splineTangent(x1, x2, x3, x4, 0.5);
   *   ty = 0.2 * splineTangent(y1, y2, y3, y4, 0.5);
   *   line(x + tx, y + ty, x - tx, y - ty);
   *
   *   // Bottom circle.
   *   stroke(0);
   *   x = splinePoint(x1, x2, x3, x4, 1);
   *   y = splinePoint(y1, y2, y3, y4, 1);
   *   circle(x, y, 5);
   *
   *   // Bottom tangent line.
   *   // Scale the tangent point to draw a shorter line.
   *   stroke(255, 0, 0);
   *   tx = 0.2 * splineTangent(x1, x2, x3, x4, 1);
   *   ty = 0.2 * splineTangent(y1, y2, y3, y4, 1);
   *   line(x + tx, y + ty, x - tx, y - ty);
   *
   *   describe(
   *     'A black curve on a gray square. A white circle moves back and forth along the curve.'
   *   );
   * }
   */
  fn.splineTangent = function(a, b, c, d, t) {
    const s = this._renderer.states.splineProperties.tightness,
      tt3 = t * t * 3,
      t2 = t * 2,
      f1 = (s - 1) / 2 * tt3 + (1 - s) * t2 + (s - 1) / 2,
      f2 = (s + 3) / 2 * tt3 + (-5 - s) / 2 * t2,
      f3 = (-3 - s) / 2 * tt3 + (s + 2) * t2 + (1 - s) / 2,
      f4 = (1 - s) / 2 * tt3 + (s - 1) / 2 * t2;
    return a * f1 + b * f2 + c * f3 + d * f4;
  };
}

if(typeof p5 !== 'undefined'){
  curves(p5, p5.prototype);
}

export { curves as default };
