/**
 * @module Math
 */

import * as constants from '../core/constants';

/**
 * @private
 * This function is used by binary vector operations to prioritize shorter vectors,
 * and to emit a warning when lengths do not match.
 */
const prioritizeSmallerDimension = function (currentVectorDimension, args) {
  const resultDimension = Math.min(currentVectorDimension, args.length);
  if (Array.isArray(args) && currentVectorDimension !== args.length) {
    console.warn(
      'When working with two vectors of different sizes, the smaller dimension is used. In this operation, both vectors will be treated as ' + resultDimension + 'D vectors, and any additional values of the longer vector will be ignored.'
    );
  }
  return resultDimension;
};

/**
 * @private
 * In-place, shrinks an array to a dimension.
 */
const shrinkToDimension = function(arr, dim) {
  while (arr.length > dim) {
    arr.pop();
  }
}


class Vector {
  /**
   * The values of an N-dimensional vector.
   *
   * This array of numbers that represents the vector.
   * Each number in the array corresponds to a different component of the vector,
   * like its position in different directions (e.g., x, y, z).
   *
   * You can update the values of the entire vector to a new set of values.
   * You need to provide an array of numbers, where each number represents a component
   * of the vector (e.g., x, y, z). The length of the array will become the number of
   * dimensions of the vector.
   *
   * You can add (`add()`), multiply (`mult()`), divide (`div()`), and subtract (`sub()`)
   * vectors from each other, and calculate remainder (`rem()`). Only use these functions
   * on vectors when they are the same size: both 2-dimensional, or both 3-dimensional.
   * When an operation uses two vectors of different sizes, the smaller dimension will be
   * used, any additional values of the longer vector will be ignored.
   *
   * You can multiply, divide, or calculate remainder of a vector with a single number. Then,
   * the same operation will be done on each element of the vector.
   *
   * @type {Array<number>} The array of values representing the vector.
   * @throws Will throw an error if provided no arguments, or if the arguments
   *         are not all finity numbers
   */
  values = [];

  /**
   * @private
   * Check for disabled friendly errors.
   * This is overridden in the addon function to check the p5 instance.
   */
  static friendlyErrorsDisabled() {
    return true;
  }

  // This is how it comes in with createVector()
  // This check if the first argument is a function
  constructor(...args) {

    if (args.length === 0) {
      this._friendlyError(
        'Requires valid arguments.', 'p5.Vector'
      );
    }

    if (typeof args[0] === 'function') {
      this.isPInst = true;
      this._fromRadians = args.shift();
      this._toRadians = args.shift();
    }

    this.values = args;
    if (Array.isArray(args)) {
      for (let i = 0; i < args.length; i++) {
        const v = args[i];
        if (typeof v !== 'number' || !Number.isFinite(v)) {
          if (!Vector.friendlyErrorsDisabled()) {
            this._friendlyError(
              'Arguments contain non-finite numbers',
              'p5.Vector'
            );
          }
          this.values = [];
          break;
        }
      }
    } else {
      this.values = [];
    }

    // This property is here where duck typing (checking if obj.isVector) needs
    // to be used over more standard type checking (obj instanceof Vector). This
    // needs to happen where we are building multiple files, such as in p5.webgpu.js,
    // where if we `import { Vector }` directly, it will be a separate copy of the
    // Vector class from the one imported in the main p5.js bundle.
    this.isVector = true;
  }

  // This will get overwritten when exported as part of p5.
  _friendlyError(_e) {}


  /**
   * Gets how many dimensions the vector has.
   *
   * @returns {Number} The number of dimensions. Can be 1, 2, or 3.
   */
  get dimensions(){
    return this.values.length;
  }

  /**
   * Gets the x component of the vector.
   *
   * This method returns the value of the x component of the vector.
   * Think of the x component as the horizontal position or the first number in the vector.
   * If the x component is not defined, it will return 0.
   *
   * @returns {Number} The x component of the vector. Returns 0 if the value is not defined.
   */
  get x() {
    return this.values[0] || 0;
  }

  /**
   * Retrieves the value at the specified index from the vector.
   *
   * This method allows you to get the value of a specific component of the vector
   * by providing its index. Think of the vector as a list of numbers, where each
   * number represents a different direction (like x, y, or z). The index is just
   * the position of the number in that list.
   *
   * For example, if you have a vector with values 10, 20, 30 the index 0 would
   * give you the first value 10, index 1 would give you the second value 20,
   * and so on.
   *
   * @param {Number} index - The position of the value you want to get from the vector.
   * @returns {Number} The value at the specified position in the vector.
   * @throws Will throw an error if the index is out of bounds, meaning if you try to
   *          get a value from a position that doesn't exist in the vector.
   */
  getValue(index) {
    if (index < this.values.length) {
      return this.values[index];
    } else {
      this._friendlyError(
        'The index parameter is trying to set a value outside the bounds of the vector',
        'p5.Vector.setValue'
      );
    }
  }

  /**
   * Sets the value at the specified index of the vector.
   *
   * This method allows you to change a specific component of the vector by providing its index and the new value you want to set.
   * Think of the vector as a list of numbers, where each number represents a different direction (like x, y, or z).
   * The index is just the position of the number in that list.
   *
   * For example, if you have a vector with values [0, 20, 30], and you want to change the second value (20) to 50,
   * you would use this method with index 1 (since indexes start at 0) and value 50.
   *
   * @param {Number} index - The position in the vector where you want to set the new value.
   * @param {Number} value - The new value you want to set at the specified position.
   * @throws Will throw an error if the index is outside the bounds of the vector, meaning if you try to set a value at a position that doesn't exist in the vector.
   */
  setValue(index, value) {
    if (index < this.values.length) {
      this.values[index] = value;
    } else {
      this._friendlyError(
        'The index parameter is trying to set a value outside the bounds of the vector',
        'p5.Vector.setValue'
      );
    }
  }

  /**
   * Gets the y component of the vector.
   *
   * This method returns the value of the y component of the vector.
   * Think of the y component as the vertical position or the second number in the vector.
   * If the y component is not defined, it will return 0.
   *
   * @returns {Number} The y component of the vector. Returns 0 if the value is not defined.
   */
  get y() {
    return this.values[1] || 0;
  }

  /**
   * Gets the z component of the vector.
   *
   * This method returns the value of the z component of the vector.
   * Think of the z component as the depth or the third number in the vector.
   * If the z component is not defined, it will return 0.
   *
   * @returns {Number} The z component of the vector. Returns 0 if the value is not defined.
   */
  get z() {
    return this.values[2] || 0;
  }

  /**
   * Gets the w component of the vector.
   *
   * This method returns the value of the w component of the vector.
   * Think of the w component as the fourth number in the vector.
   * If the w component is not defined, it will return 0.
   *
   * @returns {Number} The w component of the vector. Returns 0 if the value is not defined.
   */
  get w() {
    return this.values[3] || 0;
  }

  /**
   * Sets the x component of the vector.
   *
   * This method allows you to change the x value of the vector.
   * The x value is the first number in the vector, representing the horizontal position.
   * By calling this method, you can update the x value to a new number.
   *
   * @param {Number} xVal - The new value for the x component.
   */
  set x(xVal) {
    if (this.values.length > 1) {
      this.values[0] = xVal;
    }
  }

  /**
   * Sets the y component of the vector.
   *
   * This method allows you to change the y value of the vector.
   * The y value is the second number in the vector, representing the vertical position.
   * By calling this method, you can update the y value to a new number.
   *
   * @param {Number} yVal - The new value for the y component.
   */
  set y(yVal) {
    if (this.values.length > 1) {
      this.values[1] = yVal;
    }
  }

  /**
   * Sets the z component of the vector.
   *
   * This method allows you to change the z value of the vector.
   * The z value is the third number in the vector, representing the depth or the third dimension.
   * By calling this method, you can update the z value to a new number.
   *
   * @param {Number} zVal - The new value for the z component.
   */
  set z(zVal) {
    if (this.values.length > 2) {
      this.values[2] = zVal;
    }
  }

  /**
   * Sets the w component of the vector.
   *
   * This method allows you to change the w value of the vector.
   * The w value is the fourth number in the vector, representing the fourth dimension.
   * By calling this method, you can update the w value to a new number.
   *
   * @param {Number} wVal - The new value for the w component.
   */
  set w(wVal) {
    if (this.values.length > 3) {
      this.values[3] = wVal;
    }
  }

  /**
   * Returns a string representation of a vector.
   *
   * Calling `toString()` is useful for printing vectors to the console while
   * debugging.
   *
   * @return {String} string representation of the vector.
   *
   * @example
   * // META:norender
   * function setup() {
   *   let v = createVector(20, 30);
   *
   *   // Prints 'vector[20, 30, 0]'.
   *   print(v.toString());
   * }
   */
  toString() {
    return `vector[${this.values.join(', ')}]`;
  }

  /**
   * Sets the the vector to a new value.
   *
   * `set()` can use separate numbers, as in `v.set(1, 2, 3)`, a
   * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.set(v2)`, or an
   * array of numbers, as in `v.set([1, 2, 3])`.
   *
   * @param {Number} [x] x component of the vector.
   * @param {Number} [y] y component of the vector.
   * @param {Number} [z] z component of the vector.
   * @chainable
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Top left.
   *   let pos = createVector(25, 25);
   *   point(pos);
   *
   *   // Top right.
   *   // set() with numbers.
   *   pos.set(75, 25);
   *   point(pos);
   *
   *   // Bottom right.
   *   // set() with a p5.Vector.
   *   let p2 = createVector(75, 75);
   *   pos.set(p2);
   *   point(pos);
   *
   *   // Bottom left.
   *   // set() with an array.
   *   let arr = [25, 75];
   *   pos.set(arr);
   *   point(pos);
   *
   *   describe('Four black dots arranged in a square on a gray background.');
   * }
   */
  /**
   * @param {p5.Vector|Number[]} value vector to set.
   * @chainable
   */
  set(...args) {
    if (args[0] instanceof Vector) {
      this.values = args[0].values.slice();
    } else if (Array.isArray(args[0])) {
      this.values = args[0].map(arg => arg || 0);
    } else {
      this.values = args.map(arg => arg || 0);
    }
    return this;
  }

  /**
   * Returns a copy of the <a href="#/p5.Vector">p5.Vector</a> object.
   *
   * @return {p5.Vector} copy of the <a href="#/p5.Vector">p5.Vector</a> object.
   *
   * @example
   * function setup() {
   *   createCanvas(100 ,100);
   *
   *   background(200);
   *
   *   // Create a p5.Vector object.
   *   let pos = createVector(50, 50);
   *
   *   // Make a copy.
   *   let pc = pos.copy();
   *
   *   // Draw the point.
   *   strokeWeight(5);
   *   point(pc);
   *
   *   describe('A black point drawn in the middle of a gray square.');
   * }
   */
  copy() {
    if (this.isPInst) {
      return new Vector(this._fromRadians, this._toRadians, ...this.values);
    } else {
      return new Vector(...this.values);
    }
  }

  /**
   * Adds to a vector's components.
   *
   * `add()` can use separate numbers, as in `v.add(1, 2, 3)`,
   * another <a href="#/p5.Vector">p5.Vector</a> object, as in `v.add(v2)`, or
   * an array of numbers, as in `v.add([1, 2, 3])`.
   *
   * You should add vectors only when they are the same size. When two vectors
   * of different sizes are added, the smaller dimension will be used, any
   * additional values of the longer vector will be ignored. For example,
   * adding `[1, 2, 3]` and `[4, 5]` will result in `[5, 7]`.
   *
   * Calling `add()` with no arguments, as in `v.add()`, has no effect.
   *
   * This method supports N-dimensional vectors.
   *
   * The static version of `add()`, as in `p5.Vector.add(v2, v1)`, returns a new
   * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
   * originals.
   *
   * @param  {Number|Array} x   x component of the vector to be added or an array of components.
   * @param  {Number} [y] y component of the vector to be added.
   * @param  {Number} [z] z component of the vector to be added.
   * @chainable
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Top left.
   *   let pos = createVector(25, 25);
   *   point(pos);
   *
   *   // Top right.
   *   // Add numbers.
   *   pos.add(50, 0);
   *   point(pos);
   *
   *   // Bottom right.
   *   // Add a p5.Vector.
   *   let p2 = createVector(0, 50);
   *   pos.add(p2);
   *   point(pos);
   *
   *   // Bottom left.
   *   // Add an array.
   *   let arr = [-50, 0];
   *   pos.add(arr);
   *   point(pos);
   *
   *   describe('Four black dots arranged in a square on a gray background.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Top left.
   *   let p1 = createVector(25, 25);
   *
   *   // Center.
   *   let p2 = createVector(50, 50);
   *
   *   // Bottom right.
   *   // Add p1 and p2.
   *   let p3 = p5.Vector.add(p1, p2);
   *
   *   // Draw the points.
   *   strokeWeight(5);
   *   point(p1);
   *   point(p2);
   *   point(p3);
   *
   *   describe('Three black dots in a diagonal line from top left to bottom right.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Three arrows drawn on a gray square. A red arrow extends from the top left corner to the center. A blue arrow extends from the tip of the red arrow. A purple arrow extends from the origin to the tip of the blue arrow.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   let origin = createVector(0, 0);
   *
   *   // Draw the red arrow.
   *   let v1 = createVector(50, 50);
   *   drawArrow(origin, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   let v2 = createVector(-30, 20);
   *   drawArrow(v1, v2, 'blue');
   *
   *   // Purple arrow.
   *   let v3 = p5.Vector.add(v1, v2);
   *   drawArrow(origin, v3, 'purple');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  /**
   * @param  {p5.Vector|Number[]} value The vector to add
   * @chainable
   */
  add(args) {
    const minDimension = prioritizeSmallerDimension(this.dimensions, args);
    shrinkToDimension(this.values, minDimension);

    for (let i = 0; i < this.values.length; i++) {
      this.values[i] += args[i];
    }

    return this;
  }

  /**
   * Performs modulo (remainder) division with a vector's components.
   *
   * `rem()` can use separate numbers, as in `v.rem(1, 2, 3)`,
   * another <a href="#/p5.Vector">p5.Vector</a> object, as in `v.rem(v2)`, or
   * an array of numbers, as in `v.rem([1, 2, 3])`.
   *
   * If only one value is provided, as in `v.rem(2)`, then all the components
   * will be set to their values modulo 2. Calling `rem()` with no
   * arguments, as in `v.rem()`, has no effect.
   *
   * You should modulo vectors only when they are the same size. When two
   * vectors of different sizes are used, the smaller dimension will be
   * used, any additional values of the longer vector will be ignored.
   * For example, taking `[3, 6, 9]` modulo `[2, 4]` will result in `[1, 2]`.
   *
   * The static version of `rem()`, as in `p5.Vector.rem(v2, v1)`, returns a
   * new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
   * originals.
   *
   * @param {Number} x x component of divisor vector.
   * @param {Number} y y component of divisor vector.
   * @param {Number} z z component of divisor vector.
   * @chainable
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(3, 4, 5);
   *
   *   // Divide numbers.
   *   v.rem(2);
   *
   *   // Prints 'p5.Vector Object : [1, 0, 1]'.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(3, 4, 5);
   *
   *   // Divide numbers.
   *   v.rem(2, 3);
   *
   *   // Prints 'p5.Vector Object : [1, 1, 5]'.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(3, 4, 5);
   *
   *   // Divide numbers.
   *   v.rem(2, 3, 4);
   *
   *   // Prints 'p5.Vector Object : [1, 1, 1]'.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v1 = createVector(3, 4, 5);
   *   let v2 = createVector(2, 3, 4);
   *
   *   // Divide a p5.Vector.
   *   v1.rem(v2);
   *
   *   // Prints 'p5.Vector Object : [1, 1, 1]'.
   *   print(v1.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(3, 4, 5);
   *
   *   // Divide an array.
   *   let arr = [2, 3, 4];
   *   v.rem(arr);
   *
   *   // Prints 'p5.Vector Object : [1, 1, 1]'.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v1 = createVector(3, 4, 5);
   *   let v2 = createVector(2, 3, 4);
   *
   *   // Divide without modifying the original vectors.
   *  let v3 = p5.Vector.rem(v1, v2);
   *
   *   // Prints 'p5.Vector Object : [1, 1, 1]'.
   *   print(v3.toString());
   * }
   */
  /**
   * @param {p5.Vector | Number[]}  value  divisor vector.
   * @chainable
   */
  rem(args) {
    const minDimension = prioritizeSmallerDimension(this.dimensions, args);

    shrinkToDimension(this.values, minDimension);

    if(Array.isArray(args)){
      for (let i = 0; i < this.values.length; i++) {
        if (args[i] > 0) {
          this.values[i] = this.values[i] % args[i];
        }
      }
    } else if(args > 0) {
      for (let i = 0; i < this.values.length; i++) {
        this.values[i] = this.values[i] % args;
      }
    }

    return this;
  }

  /**
   * Subtracts from a vector's components.
   *
   * `sub()` can use separate numbers, as in `v.sub(1, 2, 3)`, another
   * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.sub(v2)`, or an array
   * of numbers, as in `v.sub([1, 2, 3])`.
   *
   * Calling `sub()` with no arguments, as in `v.sub()`, has no effect.
   *
   * You should subtract vectors only when they are the same size. When two
   * vectors of different sizes are used, the smaller dimension will be
   * used, any additional values of the longer vector will be ignored.
   * For example, subtracting `[1, 2]` from `[3, 5, 7]` will result in `[2, 3]`.
   *
   * The static version of `sub()`, as in `p5.Vector.sub(v2, v1)`, returns a new
   * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
   * originals.
   *
   * @param  {Number} x   x component of the vector to subtract.
   * @param  {Number} [y] y component of the vector to subtract.
   * @param  {Number} [z] z component of the vector to subtract.
   * @chainable
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Bottom right.
   *   let pos = createVector(75, 75);
   *   point(pos);
   *
   *   // Top right.
   *   // Subtract numbers.
   *   pos.sub(0, 50);
   *   point(pos);
   *
   *   // Top left.
   *   // Subtract a p5.Vector.
   *   let p2 = createVector(50, 0);
   *   pos.sub(p2);
   *   point(pos);
   *
   *   // Bottom left.
   *   // Subtract an array.
   *   let arr = [0, -50];
   *   pos.sub(arr);
   *   point(pos);
   *
   *   describe('Four black dots arranged in a square on a gray background.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Create p5.Vector objects.
   *   let p1 = createVector(75, 75);
   *   let p2 = createVector(50, 50);
   *
   *   // Subtract with modifying the original vectors.
   *   let p3 = p5.Vector.sub(p1, p2);
   *
   *   // Draw the points.
   *   strokeWeight(5);
   *   point(p1);
   *   point(p2);
   *   point(p3);
   *
   *   describe('Three black dots in a diagonal line from top left to bottom right.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Three arrows drawn on a gray square. A red and a blue arrow extend from the top left. A purple arrow extends from the tip of the red arrow to the tip of the blue arrow.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   let origin = createVector(0, 0);
   *
   *   // Draw the red arrow.
   *   let v1 = createVector(50, 50);
   *   drawArrow(origin, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   let v2 = createVector(20, 70);
   *   drawArrow(origin, v2, 'blue');
   *
   *   // Purple arrow.
   *   let v3 = p5.Vector.sub(v2, v1);
   *   drawArrow(v1, v3, 'purple');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  /**
   * @param  {p5.Vector|Number[]} value the vector to subtract
   * @chainable
   */
  sub(args) {
    const minDimension = prioritizeSmallerDimension(this.dimensions, args);
    shrinkToDimension(this.values, minDimension);

    for (let i = 0; i < this.values.length; i++) {
      this.values[i] -= args[i];
    }

    return this;
  }

  /**
   * Multiplies a vector's components.
   *
   * `mult()` can use separate numbers, as in `v.mult(1, 2, 3)`, another
   * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.mult(v2)`, or an array
   * of numbers, as in `v.mult([1, 2, 3])`.
   *
   * If only one value is provided, as in `v.mult(2)`, then all the components
   * will be multiplied by 2. Calling `mult()` with no arguments, as in `v.mult()`, has
   * no effect.
   *
   * You should multiply vectors only when they are the same size. When two
   * vectors of different sizes are multiplied, the smaller dimension will be
   * used, any additional values of the longer vector will be ignored.
   * For example, multiplying `[1, 2, 3]` by `[4, 5]` will result in `[4, 10]`.
   *
   * The static version of `mult()`, as in `p5.Vector.mult(v, 2)`, returns a new
   * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
   * originals.
   *
   * @param  {Number} n The number to multiply with the vector
   * @chainable
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Top-left.
   *   let p = createVector(25, 25);
   *   point(p);
   *
   *   // Center.
   *   // Multiply all components by 2.
   *   p.mult(2);
   *   point(p);
   *
   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the center.');
   * }
   *
   * @example
   * function setup() {
   *   strokeWeight(5);
   *
   *   // Top-left.
   *   let p = createVector(25, 25);
   *   point(p);
   *
   *   // Bottom-right.
   *   // Multiply p.x * 2 and p.y * 3
   *   p.mult(2, 3);
   *   point(p);
   *
   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Top-left.
   *   let p = createVector(25, 25);
   *   point(p);
   *
   *   // Bottom-right.
   *   // Multiply p.x * 2 and p.y * 3
   *   let arr = [2, 3];
   *   p.mult(arr);
   *   point(p);
   *
   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Top-left.
   *   let p = createVector(25, 25);
   *   point(p);
   *
   *   // Bottom-right.
   *   // Multiply p.x * p2.x and p.y * p2.y
   *   let p2 = createVector(2, 3);
   *   p.mult(p2);
   *   point(p);
   *
   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Top-left.
   *   let p = createVector(25, 25);
   *   point(p);
   *
   *   // Bottom-right.
   *   // Create a new p5.Vector with
   *   // p3.x = p.x * p2.x
   *   // p3.y = p.y * p2.y
   *   let p2 = createVector(2, 3);
   *   let p3 = p5.Vector.mult(p, p2);
   *   point(p3);
   *
   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Two arrows extending from the top left corner. The blue arrow is twice the length of the red arrow.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   let origin = createVector(0, 0);
   *
   *   // Draw the red arrow.
   *   let v1 = createVector(25, 25);
   *   drawArrow(origin, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   let v2 = p5.Vector.mult(v1, 2);
   *   drawArrow(origin, v2, 'blue');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  /**
   * @param  {Number} x number to multiply with the x component of the vector.
   * @param  {Number} y number to multiply with the y component of the vector.
   * @param  {Number} [z] number to multiply with the z component of the vector.
   * @chainable
   */
  /**
   * @param  {Number[]} arr array to multiply with the components of the vector.
   * @chainable
   */
  /**
   * @param  {p5.Vector} v vector to multiply with the components of the original vector.
   * @chainable
   */
  mult(args) {
    const minDimension = prioritizeSmallerDimension(this.dimensions, args);
    shrinkToDimension(this.values, minDimension);

    if(Array.isArray(args)){
      for (let i = 0; i < this.values.length; i++) {
        this.values[i] *= args[i];
      }
    } else {
      for (let i = 0; i < this.values.length; i++) {
        this.values[i] *= args;
      }
    }

    return this;
  }

  /**
   * Divides a vector's components.
   *
   * `div()` can use separate numbers, as in `v.div(1, 2, 3)`, another
   * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.div(v2)`, or an array
   * of numbers, as in `v.div([1, 2, 3])`.
   *
   * If only one value is provided, as in `v.div(2)`, then all the components
   * will be divided by 2. Calling `div()` with no arguments, as in `v.div()`, has
   * no effect.
   *
   * You should divide vectors only when they are the same size. When two
   * vectors of different sizes are divided, the smaller dimension will be
   * used, any additional values of the longer vector will be ignored.
   * For example, dividing `[8, 12, 21]` by `[2, 3]` will result in `[4, 4]`.
   *
   * The static version of `div()`, as in `p5.Vector.div(v, 2)`, returns a new
   * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
   * originals.
   *
   * @param  {Number}    n The number to divide the vector by
   * @chainable
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Center.
   *   let p = createVector(50, 50);
   *   point(p);
   *
   *   // Top-left.
   *   // Divide p.x / 2 and p.y / 2
   *   p.div(2);
   *   point(p);
   *
   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the center.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Bottom-right.
   *   let p = createVector(50, 75);
   *   point(p);
   *
   *   // Top-left.
   *   // Divide p.x / 2 and p.y / 3
   *   p.div(2, 3);
   *   point(p);
   *
   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Bottom-right.
   *   let p = createVector(50, 75);
   *   point(p);
   *
   *   // Top-left.
   *   // Divide p.x / 2 and p.y / 3
   *   let arr = [2, 3];
   *   p.div(arr);
   *   point(p);
   *
   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Bottom-right.
   *   let p = createVector(50, 75);
   *   point(p);
   *
   *   // Top-left.
   *   // Divide p.x / 2 and p.y / 3
   *   let p2 = createVector(2, 3);
   *   p.div(p2);
   *   point(p);
   *
   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Bottom-right.
   *   let p = createVector(50, 75);
   *   point(p);
   *
   *   // Top-left.
   *   // Create a new p5.Vector with
   *   // p3.x = p.x / p2.x
   *   // p3.y = p.y / p2.y
   *   let p2 = createVector(2, 3);
   *   let p3 = p5.Vector.div(p, p2);
   *   point(p3);
   *
   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
   * }
   *
   * @example
   * function draw() {
   *   background(200);
   *
   *   let origin = createVector(0, 0);
   *
   *   // Draw the red arrow.
   *   let v1 = createVector(50, 50);
   *   drawArrow(origin, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   let v2 = p5.Vector.div(v1, 2);
   *   drawArrow(origin, v2, 'blue');
   *
   *   describe('Two arrows extending from the top left corner. The blue arrow is half the length of the red arrow.');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  /**
   * @param  {Number} x number to divide with the x component of the vector.
   * @param  {Number} y number to divide with the y component of the vector.
   * @param  {Number} [z] number to divide with the z component of the vector.
   * @chainable
   */
  /**
   * @param  {Number[]} arr array to divide the components of the vector by.
   * @chainable
   */
  /**
   * @param  {p5.Vector} v vector to divide the components of the original vector by.
   * @chainable
   */
  div(args) {
    const minDimension = prioritizeSmallerDimension(this.dimensions, args);

    if (Array.isArray(args)) {
      for (let i = 0; i < minDimension; i++) {
        if ((typeof args[i] !== 'number' || args[i] === 0)) {
          if (!this.friendlyErrorsDisabled()) {
            console.warn(
              'p5.Vector.prototype.div',
              'Arguments contain components that are 0'
            );
          }
          return this;
        }
      }
    } else if(typeof args !== 'number' || args === 0) {
      if (!this.friendlyErrorsDisabled()) {
        console.warn(
          'p5.Vector.prototype.div',
          'Arguments contain components that are 0'
        );
      }
      return this;
    }

    shrinkToDimension(this.values, minDimension);

    if(Array.isArray(args)){
      for (let i = 0; i < this.values.length; i++) {
        this.values[i] /= args[i];
      }
    } else {
      for (let i = 0; i < this.values.length; i++) {
        this.values[i] /= args;
      }
    }

    return this;
  }

  /**
   * Calculates the magnitude (length) of the vector.
   *
   * Use <a href="#/p5/mag">mag()</a> to calculate the magnitude of a 2D vector
   * using components as in `mag(x, y)`.
   *
   * @return {Number} magnitude of the vector.
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Create a p5.Vector object.
   *   let p = createVector(30, 40);
   *
   *   // Draw a line from the origin.
   *   line(0, 0, p.x, p.y);
   *
   *   // Style the text.
   *   textAlign(CENTER);
   *   textSize(16);
   *
   *   // Display the vector's magnitude.
   *   let m = p.mag();
   *   text(m, p.x, p.y);
   *
   *   describe('A diagonal black line extends from the top left corner of a gray square. The number 50 is written at the end of the line.');
   * }
   */
  mag() {
    let sum = 0;
    for (let i = 0; i < this.values.length; i++) {
      const component = this.values[i];
      sum += component * component;
    }
    return Math.sqrt(sum);
  }

  /**
   * Calculates the magnitude (length) of the vector squared.
   *
   * @return {Number} squared magnitude of the vector.
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Create a p5.Vector object.
   *   let p = createVector(30, 40);
   *
   *   // Draw a line from th
   * e origin.
   *   line(0, 0, p.x, p.y);
   *
   *   // Style the text.
   *   textAlign(CENTER);
   *   textSize(16);
   *
   *   // Display the vector's magnitude squared.
   *   let m = p.magSq();
   *   text(m, p.x, p.y);
   *
   *   describe('A diagonal black line extends from the top left corner of a gray square. The number 2500 is written at the end of the line.');
   * }
   */
  magSq() {
    let sum = 0;
    for (let i = 0; i < this.values.length; i++) {
      const component = this.values[i];
      sum += component * component;
    }
    return sum;
  }

  /**
   * Calculates the dot product of two vectors.
   *
   * The dot product is a number that describes the overlap between two vectors.
   * Visually, the dot product can be thought of as the "shadow" one vector
   * casts on another. The dot product's magnitude is largest when two vectors
   * point in the same or opposite directions. Its magnitude is 0 when two
   * vectors form a right angle.
   *
   * The version of `dot()` with one parameter interprets it as another
   * <a href="#/p5.Vector">p5.Vector</a> object.
   *
   * The version of `dot()` with multiple parameters interprets them as the
   * `x`, `y`, and `z` components of another vector.
   *
   * The static version of `dot()`, as in `p5.Vector.dot(v1, v2)`, is the same
   * as calling `v1.dot(v2)`.
   *
   * @param  {Number} x   x component of the vector.
   * @param  {Number} [y] y component of the vector.
   * @param  {Number} [z] z component of the vector.
   * @return {Number}     dot product.
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v1 = createVector(3, 4);
   *   let v2 = createVector(3, 0);
   *
   *   // Calculate the dot product.
   *   let dp = v1.dot(v2);
   *
   *   // Prints "9" to the console.
   *   print(dp);
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v1 = createVector(1, 0);
   *   let v2 = createVector(0, 1);
   *
   *   // Calculate the dot product.
   *   let dp = p5.Vector.dot(v1, v2);
   *
   *   // Prints "0" to the console.
   *   print(dp);
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Two arrows drawn on a gray square. A black arrow points to the right and a red arrow follows the mouse. The text "v1 • v2 = something" changes as the mouse moves.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Center.
   *   let v0 = createVector(50, 50);
   *
   *   // Draw the black arrow.
   *   let v1 = createVector(30, 0);
   *   drawArrow(v0, v1, 'black');
   *
   *   // Draw the red arrow.
   *   let v2 = createVector(mouseX - 50, mouseY - 50);
   *   drawArrow(v0, v2, 'red');
   *
   *   // Display the dot product.
   *   let dp = v2.dot(v1);
   *   text(`v2 • v1 = ${dp}`, 10, 20);
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  /**
   * @param  {p5.Vector} v <a href="#/p5.Vector">p5.Vector</a> to be dotted.
   * @return {Number}
   */
  dot(...args) {
    let vals = args;
    if (args[0] instanceof Vector) {
      vals = args[0].values;
    }
    const minDimension = prioritizeSmallerDimension(this.dimensions, vals);
    let sum = 0;
    for (let i = 0; i < minDimension; i++) {
      sum += this.values[i] * vals[i];
    }
    return sum;
  }

  /**
   * Calculates the cross product of two vectors.
   *
   * The cross product is a vector that points straight out of the plane created
   * by two vectors. The cross product's magnitude is the area of the parallelogram
   * formed by the original two vectors.
   *
   * The cross product is defined on 3-dimensional vectors, and will use the `x`, `y`,
   * and `z` components. This method should only be used with 3D vectors.
   *
   * The static version of `cross()`, as in `p5.Vector.cross(v1, v2)`, is the same
   * as calling `v1.cross(v2)`.
   *
   * @param  {p5.Vector} v <a href="#/p5.Vector">p5.Vector</a> to be crossed.
   * @return {p5.Vector}   cross product as a <a href="#/p5.Vector">p5.Vector</a>.
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v1 = createVector(1, 0);
   *   let v2 = createVector(3, 4);
   *
   *   // Calculate the cross product.
   *   let cp = v1.cross(v2);
   *
   *   // Prints "p5.Vector Object : [0, 0, 4]" to the console.
   *   print(cp.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v1 = createVector(1, 0);
   *   let v2 = createVector(3, 4);
   *
   *   // Calculate the cross product.
   *   let cp = p5.Vector.cross(v1, v2);
   *
   *   // Prints "p5.Vector Object : [0, 0, 4]" to the console.
   *   print(cp.toString());
   * }
   */
  cross(v) {
    const x = this.y * v.z - this.z * v.y;
    const y = this.z * v.x - this.x * v.z;
    const z = this.x * v.y - this.y * v.x;
    if (this.isPInst) {
      return new Vector(this._fromRadians, this._toRadians, x, y, z);
    } else {
      return new Vector(x, y, z);
    }
  }

  /**
   * Calculates the distance between two points represented by vectors.
   *
   * A point's coordinates can be represented by the components of a vector
   * that extends from the origin to the point.
   *
   * The static version of `dist()`, as in `p5.Vector.dist(v1, v2)`, is the same
   * as calling `v1.dist(v2)`.
   *
   * Use <a href="#/p5/dist">dist()</a> to calculate the distance between points
   * using coordinates as in `dist(x1, y1, x2, y2)`.
   *
   * @submodule p5.Vector
   * @param  {p5.Vector} v x, y, and z coordinates of a <a href="#/p5.Vector">p5.Vector</a>.
   * @return {Number}      distance.
   *
   * @example
   * // META:norender
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Create p5.Vector objects.
   *   let v1 = createVector(1, 0);
   *   let v2 = createVector(0, 1);
   *
   *   // Calculate the distance between them.
   *   let d = v1.dist(v2);
   *
   *   // Prints "1.414..." to the console.
   *   print(d);
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Create p5.Vector objects.
   *   let v1 = createVector(1, 0);
   *   let v2 = createVector(0, 1);
   *
   *   // Calculate the distance between them.
   *   let d = p5.Vector.dist(v1, v2);
   *
   *   // Prints "1.414..." to the console.
   *   print(d);
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Three arrows drawn on a gray square. A red and a blue arrow extend from the top left. A purple arrow extends from the tip of the red arrow to the tip of the blue arrow. The number 36 is written in black near the purple arrow.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   let origin = createVector(0, 0);
   *
   *   // Draw the red arrow.
   *   let v1 = createVector(50, 50);
   *   drawArrow(origin, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   let v2 = createVector(20, 70);
   *   drawArrow(origin, v2, 'blue');
   *
   *   // Purple arrow.
   *   let v3 = p5.Vector.sub(v2, v1);
   *   drawArrow(v1, v3, 'purple');
   *
   *   // Style the text.
   *   textAlign(CENTER);
   *
   *   // Display the magnitude. The same as floor(v3.mag());
   *   let m = floor(p5.Vector.dist(v1, v2));
   *   text(m, 50, 75);
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  dist(v) {
    const minDimension = prioritizeSmallerDimension(this.dimensions, v.values);
    let sum = 0;
    for (let i = 0; i < minDimension; i++) {
      const component = this.values[i] - v.values[i];
      sum += component * component;
    }
    return Math.sqrt(sum);
  }

  /**
   * Scales the components of a <a href="#/p5.Vector">p5.Vector</a> object so
   * that its magnitude is 1.
   *
   * The static version of `normalize()`,  as in `p5.Vector.normalize(v)`,
   * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
   * the original.
   *
   * @return {p5.Vector} normalized <a href="#/p5.Vector">p5.Vector</a>.
   *
   * @example
   * // META:norender
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Create a p5.Vector.
   *   let v = createVector(10, 20, 2);
   *
   *   // Normalize.
   *   v.normalize();
   *
   *   // Prints "p5.Vector Object : [0.445..., 0.890..., 0.089...]" to the console.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Create a p5.Vector.
   *   let v0 = createVector(10, 20, 2);
   *
   *   // Create a normalized copy.
   *   let v1 = p5.Vector.normalize(v0);
   *
   *   // Prints "p5.Vector Object : [10, 20, 2]" to the console.
   *   print(v0.toString());
   *   // Prints "p5.Vector Object : [0.445..., 0.890..., 0.089...]" to the console.
   *   print(v1.toString());
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe("A red and blue arrow extend from the center of a circle. Both arrows follow the mouse, but the blue arrow's length is fixed to the circle's radius.");
   * }
   *
   * function draw() {
   *   background(240);
   *
   *   // Vector to the center.
   *   let v0 = createVector(50, 50);
   *
   *   // Vector from the center to the mouse.
   *   let v1 = createVector(mouseX - 50, mouseY - 50);
   *
   *   // Circle's radius.
   *   let r = 25;
   *
   *   // Draw the red arrow.
   *   drawArrow(v0, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   v1.normalize();
   *   drawArrow(v0, v1.mult(r), 'blue');
   *
   *   // Draw the circle.
   *   noFill();
   *   circle(50, 50, r * 2);
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  normalize() {
    const len = this.mag();
    // here we multiply by the reciprocal instead of calling 'div()'
    // since div duplicates this zero check.
    if (len !== 0) this.mult(1 / len);
    return this;
  }

  /**
   * Limits a vector's magnitude to a maximum value.
   *
   * The static version of `limit()`, as in `p5.Vector.limit(v, 5)`, returns a
   * new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
   * original.
   *
   * @param  {Number}    max maximum magnitude for the vector.
   * @chainable
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(10, 20, 2);
   *
   *   // Limit its magnitude.
   *   v.limit(5);
   *
   *   // Prints "p5.Vector Object : [2.227..., 4.454..., 0.445...]" to the console.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v0 = createVector(10, 20, 2);
   *
   *   // Create a copy an limit its magintude.
   *   let v1 = p5.Vector.limit(v0, 5);
   *
   *   // Prints "p5.Vector Object : [2.227..., 4.454..., 0.445...]" to the console.
   *   print(v1.toString());
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe("A red and blue arrow extend from the center of a circle. Both arrows follow the mouse, but the blue arrow never crosses the circle's edge.");
   * }
   * function draw() {
   *   background(240);
   *
   *   // Vector to the center.
   *   let v0 = createVector(50, 50);
   *
   *   // Vector from the center to the mouse.
   *   let v1 = createVector(mouseX - 50, mouseY - 50);
   *
   *   // Circle's radius.
   *   let r = 25;
   *
   *   // Draw the red arrow.
   *   drawArrow(v0, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   drawArrow(v0, v1.limit(r), 'blue');
   *
   *   // Draw the circle.
   *   noFill();
   *   circle(50, 50, r * 2);
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  limit(max) {
    const mSq = this.magSq();
    if (mSq > max * max) {
      this.div(Math.sqrt(mSq)) //normalize it
        .mult(max);
    }
    return this;
  }

  /**
   * Sets a vector's magnitude to a given value.
   *
   * The static version of `setMag()`, as in `p5.Vector.setMag(v, 10)`, returns
   * a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
   * original.
   *
   * @param  {Number}    len new length for this vector.
   * @chainable
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(3, 4, 0);
   *
   *   // Prints "5" to the console.
   *   print(v.mag());
   *
   *   // Set its magnitude to 10.
   *   v.setMag(10);
   *
   *   // Prints "p5.Vector Object : [6, 8, 0]" to the console.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v0 = createVector(3, 4, 0);
   *
   *   // Create a copy with a magnitude of 10.
   *   let v1 = p5.Vector.setMag(v0, 10);
   *
   *   // Prints "5" to the console.
   *   print(v0.mag());
   *
   *   // Prints "p5.Vector Object : [6, 8, 0]" to the console.
   *   print(v1.toString());
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Two arrows extend from the top left corner of a square toward its center. The red arrow reaches the center and the blue arrow only extends part of the way.');
   * }
   *
   * function draw() {
   *   background(240);
   *
   *   let origin = createVector(0, 0);
   *   let v = createVector(50, 50);
   *
   *   // Draw the red arrow.
   *   drawArrow(origin, v, 'red');
   *
   *   // Set v's magnitude to 30.
   *   v.setMag(30);
   *
   *   // Draw the blue arrow.
   *   drawArrow(origin, v, 'blue');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  setMag(n) {
    return this.normalize().mult(n);
  }

  /**
   * Calculates the angle a 2D vector makes with the positive x-axis.
   *
   * By convention, the positive x-axis has an angle of 0. Angles increase in
   * the clockwise direction.
   *
   * If the vector was created with
   * <a href="#/p5/createVector">createVector()</a>, `heading()` returns angles
   * in the units of the current <a href="#/p5/angleMode">angleMode()</a>.
   *
   * The static version of `heading()`, as in `p5.Vector.heading(v)`, works the
   * same way.
   *
   * @return {Number} angle of rotation.
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(1, 1);
   *
   *   // Prints "0.785..." to the console.
   *   print(v.heading());
   *
   *   // Use degrees.
   *   angleMode(DEGREES);
   *
   *   // Prints "45" to the console.
   *   print(v.heading());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(1, 1);
   *
   *   // Prints "0.785..." to the console.
   *   print(p5.Vector.heading(v));
   *
   *   // Use degrees.
   *   angleMode(DEGREES);
   *
   *   // Prints "45" to the console.
   *   print(p5.Vector.heading(v));
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('A black arrow extends from the top left of a square to its center. The text "Radians: 0.79" and "Degrees: 45" is written near the tip of the arrow.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   let origin = createVector(0, 0);
   *   let v = createVector(50, 50);
   *
   *   // Draw the black arrow.
   *   drawArrow(origin, v, 'black');
   *
   *   // Use radians.
   *   angleMode(RADIANS);
   *
   *   // Display the heading in radians.
   *   let h = round(v.heading(), 2);
   *   text(`Radians: ${h}`, 20, 70);
   *
   *   // Use degrees.
   *   angleMode(DEGREES);
   *
   *   // Display the heading in degrees.
   *   h = v.heading();
   *   text(`Degrees: ${h}`, 20, 85);
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  heading() {
    const h = Math.atan2(this.y, this.x);
    if (this.isPInst) return this._fromRadians(h);
    return h;
  }

  /**
   * Rotates a 2D vector to a specific angle without changing its magnitude.
   *
   * By convention, the positive x-axis has an angle of 0. Angles increase in
   * the clockwise direction.
   *
   * If the vector was created with
   * <a href="#/p5/createVector">createVector()</a>, `setHeading()` uses
   * the units of the current <a href="#/p5/angleMode">angleMode()</a>.
   *
   * @param  {Number}    angle angle of rotation.
   * @chainable
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(0, 1);
   *
   *   // Prints "1.570..." to the console.
   *   print(v.heading());
   *
   *   // Point to the left.
   *   v.setHeading(PI);
   *
   *   // Prints "3.141..." to the console.
   *   print(v.heading());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Use degrees.
   *   angleMode(DEGREES);
   *
   *   // Create a p5.Vector object.
   *   let v = createVector(0, 1);
   *
   *   // Prints "90" to the console.
   *   print(v.heading());
   *
   *   // Point to the left.
   *   v.setHeading(180);
   *
   *   // Prints "180" to the console.
   *   print(v.heading());
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Two arrows extend from the center of a gray square. The red arrow points to the right and the blue arrow points down.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Create p5.Vector objects.
   *   let v0 = createVector(50, 50);
   *   let v1 = createVector(30, 0);
   *
   *   // Draw the red arrow.
   *   drawArrow(v0, v1, 'red');
   *
   *   // Point down.
   *   v1.setHeading(HALF_PI);
   *
   *   // Draw the blue arrow.
   *   drawArrow(v0, v1, 'blue');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  setHeading(a) {
    if (this.dimensions < 2 || (
      this._values instanceof Array && this._values.slice(2).some(v => v !== 0))
    ) {
      p5._friendlyError(
        'p5.Vector.setHeading() only supports 2D vectors (z === 0). ' +
        'For 3D or higher-dimensional vectors, use rotate() or another ' +
        'appropriate method instead.',
        'p5.Vector.setHeading'
      );
      return this;
    }
    const m = this.mag();
    this.x = m * Math.cos(a);
    this.y = m * Math.sin(a);
    return this;
  }

  /**
   * Rotates a 2D vector by an angle without changing its magnitude.
   *
   * By convention, the positive x-axis has an angle of 0. Angles increase in
   * the clockwise direction.
   *
   * If the vector was created with
   * <a href="#/p5/createVector">createVector()</a>, `rotate()` uses
   * the units of the current <a href="#/p5/angleMode">angleMode()</a>.
   *
   * The static version of `rotate()`, as in `p5.Vector.rotate(v, PI)`,
   * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
   * the original.
   *
   * @param  {Number}    angle angle of rotation.
   * @chainable
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(1, 0);
   *
   *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
   *   print(v.toString());
   *
   *   // Rotate a quarter turn.
   *   v.rotate(HALF_PI);
   *
   *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Use degrees.
   *   angleMode(DEGREES);
   *
   *   // Create a p5.Vector object.
   *   let v = createVector(1, 0);
   *
   *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
   *   print(v.toString());
   *
   *   // Rotate a quarter turn.
   *   v.rotate(90);
   *
   *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v0 = createVector(1, 0);
   *
   *   // Create a rotated copy.
   *   let v1 = p5.Vector.rotate(v0, HALF_PI);
   *
   *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
   *   print(v0.toString());
   *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
   *   print(v1.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Use degrees.
   *   angleMode(DEGREES);
   *
   *   // Create a p5.Vector object.
   *   let v0 = createVector(1, 0);
   *
   *   // Create a rotated copy.
   *   let v1 = p5.Vector.rotate(v0, 90);
   *
   *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
   *   print(v0.toString());
   *
   *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
   *   print(v1.toString());
   * }
   *
   * @example
   * let v0;
   * let v1;
   *
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   // Create p5.Vector objects.
   *   v0 = createVector(50, 50);
   *   v1 = createVector(30, 0);
   *
   *   describe('A black arrow extends from the center of a gray square. The arrow rotates clockwise.');
   * }
   *
   * function draw() {
   *   background(240);
   *
   *   // Rotate v1.
   *   v1.rotate(0.01);
   *
   *   // Draw the black arrow.
   *   drawArrow(v0, v1, 'black');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  rotate(a) {
    let newHeading = this.heading() + a;
    if (this.isPInst) newHeading = this._toRadians(newHeading);
    const mag = this.mag();
    this.x = Math.cos(newHeading) * mag;
    this.y = Math.sin(newHeading) * mag;
    return this;
  }

  /**
   * Calculates the angle between two vectors.
   *
   * The angles returned are signed, which means that
   * `v1.angleBetween(v2) === -v2.angleBetween(v1)`.
   *
   * If the vector was created with
   * <a href="#/p5/createVector">createVector()</a>, `angleBetween()` returns
   * angles in the units of the current
   * <a href="#/p5/angleMode">angleMode()</a>.
   *
   * @param  {p5.Vector}    value x, y, and z components of a <a href="#/p5.Vector">p5.Vector</a>.
   * @return {Number}       angle between the vectors.
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v0 = createVector(1, 0);
   *   let v1 = createVector(0, 1);
   *
   *   // Prints "1.570..." to the console.
   *   print(v0.angleBetween(v1));
   *
   *   // Prints "-1.570..." to the console.
   *   print(v1.angleBetween(v0));
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Use degrees.
   *   angleMode(DEGREES);
   *   // Create p5.Vector objects.
   *   let v0 = createVector(1, 0);
   *   let v1 = createVector(0, 1);
   *
   *   // Prints "90" to the console.
   *   print(v0.angleBetween(v1));
   *
   *   // Prints "-90" to the console.
   *   print(v1.angleBetween(v0));
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v0 = createVector(1, 0);
   *   let v1 = createVector(0, 1);
   *
   *   // Prints "1.570..." to the console.
   *   print(p5.Vector.angleBetween(v0, v1));
   *
   *   // Prints "-1.570..." to the console.
   *   print(p5.Vector.angleBetween(v1, v0));
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Use degrees.
   *   angleMode(DEGREES);
   *
   *   // Create p5.Vector objects.
   *   let v0 = createVector(1, 0);
   *   let v1 = createVector(0, 1);
   *
   *   // Prints "90" to the console.
   *   print(p5.Vector.angleBetween(v0, v1));
   *
   *   // Prints "-90" to the console.
   *   print(p5.Vector.angleBetween(v1, v0));
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Two arrows extend from the center of a gray square. A red arrow points to the right and a blue arrow points down. The text "Radians: 1.57" and "Degrees: 90" is written above the arrows.');
   * }
   * function draw() {
   *   background(200);
   *
   *   // Create p5.Vector objects.
   *   let v0 = createVector(50, 50);
   *   let v1 = createVector(30, 0);
   *   let v2 = createVector(0, 30);
   *
   *   // Draw the red arrow.
   *   drawArrow(v0, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   drawArrow(v0, v2, 'blue');
   *
   *   // Use radians.
   *   angleMode(RADIANS);
   *
   *   // Display the angle in radians.
   *   let angle = round(v1.angleBetween(v2), 2);
   *   text(`Radians: ${angle}`, 20, 20);
   *
   *   // Use degrees.
   *   angleMode(DEGREES);
   *
   *   // Display the angle in degrees.
   *   angle = round(v1.angleBetween(v2), 2);
   *   text(`Degrees: ${angle}`, 20, 35);
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  angleBetween(v) {
    const magSqMult = this.magSq() * v.magSq();
    // Returns NaN if either vector is the zero vector.
    if (magSqMult === 0) {
      return NaN;
    }
    const u = this.cross(v);
    // The dot product computes the cos value, and the cross product computes
    // the sin value. Find the angle based on them. In addition, in the case of
    // 2D vectors, a sign is added according to the direction of the vector.
    let angle = Math.atan2(u.mag(), this.dot(v)) * Math.sign(u.z || 1);
    if (this.isPInst) {
      angle = this._fromRadians(angle);
    }
    return angle;
  }

  /**
   * Calculates new `x`, `y`, and `z` components that are proportionally the
   * same distance between two vectors.
   *
   * The `amt` parameter is the amount to interpolate between the old vector and
   * the new vector. 0.0 keeps all components equal to the old vector's, 0.5 is
   * halfway between, and 1.0 sets all components equal to the new vector's.
   *
   * The static version of `lerp()`, as in `p5.Vector.lerp(v0, v1, 0.5)`,
   * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
   * the original.
   *
   * @param  {Number}    x   x component.
   * @param  {Number}    y   y component.
   * @param  {Number}    z   z component.
   * @param  {Number}    amt amount of interpolation between 0.0 (old vector)
   *                         and 1.0 (new vector). 0.5 is halfway between.
   * @chainable
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v0 = createVector(1, 1, 1);
   *   let v1 = createVector(3, 3, 3);
   *
   *   // Interpolate.
   *   v0.lerp(v1, 0.5);
   *
   *   // Prints "p5.Vector Object : [2, 2, 2]" to the console.
   *   print(v0.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(1, 1, 1);
   *
   *   // Interpolate.
   *   v.lerp(3, 3, 3, 0.5);
   *
   *   // Prints "p5.Vector Object : [2, 2, 2]" to the console.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v0 = createVector(1, 1, 1);
   *   let v1 = createVector(3, 3, 3);
   *
   *   // Interpolate.
   *   let v2 = p5.Vector.lerp(v0, v1, 0.5);
   *
   *   // Prints "p5.Vector Object : [2, 2, 2]" to the console.
   *   print(v2.toString());
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Three arrows extend from the center of a gray square. A red arrow points to the right, a blue arrow points down, and a purple arrow points to the bottom right.');
   * }
   * function draw() {
   *   background(200);
   *
   *   // Create p5.Vector objects.
   *   let v0 = createVector(50, 50);
   *   let v1 = createVector(30, 0);
   *   let v2 = createVector(0, 30);
   *
   *   // Interpolate.
   *   let v3 = p5.Vector.lerp(v1, v2, 0.5);
   *
   *   // Draw the red arrow.
   *   drawArrow(v0, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   drawArrow(v0, v2, 'blue');
   *
   *   // Draw the purple arrow.
   *   drawArrow(v0, v3, 'purple');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  /**
   * @param  {p5.Vector} v  <a href="#/p5.Vector">p5.Vector</a> to lerp toward.
   * @param  {Number}    amt
   * @chainable
   */
  lerp(x, y, z, amt) {
    if (x instanceof Vector) {
      return this.lerp(x.x, x.y, x.z, y);
    }
    this.x += (x - this.x) * amt || 0;
    this.y += (y - this.y) * amt || 0;
    this.z += (z - this.z) * amt || 0;
    return this;
  }

  /**
   * Calculates a new heading and magnitude that are between two vectors.
   *
   * The `amt` parameter is the amount to interpolate between the old vector and
   * the new vector. 0.0 keeps the heading and magnitude equal to the old
   * vector's, 0.5 sets them halfway between, and 1.0 sets the heading and
   * magnitude equal to the new vector's.
   *
   * `slerp()` differs from <a href="#/p5.Vector/lerp">lerp()</a> because
   * it interpolates magnitude. Calling `v0.slerp(v1, 0.5)` sets `v0`'s
   * magnitude to a value halfway between its original magnitude and `v1`'s.
   * Calling `v0.lerp(v1, 0.5)` makes no such guarantee.
   *
   * The static version of `slerp()`, as in `p5.Vector.slerp(v0, v1, 0.5)`,
   * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
   * the original.
   *
   * @param {p5.Vector} v <a href="#/p5.Vector">p5.Vector</a> to slerp toward.
   * @param {Number} amt  amount of interpolation between 0.0 (old vector)
   *                      and 1.0 (new vector). 0.5 is halfway between.
   * @return {p5.Vector}
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v0 = createVector(3, 0);
   *
   *   // Prints "3" to the console.
   *   print(v0.mag());
   *
   *   // Prints "0" to the console.
   *   print(v0.heading());
   *
   *   // Create a p5.Vector object.
   *   let v1 = createVector(0, 1);
   *
   *   // Prints "1" to the console.
   *   print(v1.mag());
   *
   *   // Prints "1.570..." to the console.
   *   print(v1.heading());
   *
   *   // Interpolate halfway between v0 and v1.
   *   v0.slerp(v1, 0.5);
   *
   *   // Prints "2" to the console.
   *   print(v0.mag());
   *
   *   // Prints "0.785..." to the console.
   *   print(v0.heading());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v0 = createVector(3, 0);
   *
   *   // Prints "3" to the console.
   *   print(v0.mag());
   *
   *   // Prints "0" to the console.
   *   print(v0.heading());
   *
   *   // Create a p5.Vector object.
   *   let v1 = createVector(0, 1);
   *
   *   // Prints "1" to the console.
   *   print(v1.mag());
   *
   *   // Prints "1.570..." to the console.
   *   print(v1.heading());
   *
   *   // Create a p5.Vector that's halfway between v0 and v1.
   *   let v3 = p5.Vector.slerp(v0, v1, 0.5);
   *
   *   // Prints "2" to the console.
   *   print(v3.mag());
   *
   *   // Prints "0.785..." to the console.
   *   print(v3.heading());
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Three arrows extend from the center of a gray square. A red arrow points to the right, a blue arrow points to the left, and a purple arrow points down.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Create p5.Vector objects.
   *   let v0 = createVector(50, 50);
   *   let v1 = createVector(20, 0);
   *   let v2 = createVector(-40, 0);
   *
   *   // Create a p5.Vector that's halfway between v1 and v2.
   *   let v3 = p5.Vector.slerp(v1, v2, 0.5);
   *
   *   // Draw the red arrow.
   *   drawArrow(v0, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   drawArrow(v0, v2, 'blue');
   *
   *   // Draw the purple arrow.
   *   drawArrow(v0, v3, 'purple');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  slerp(v, amt) {
    // edge cases.
    if (amt === 0) {
      return this;
    }
    if (amt === 1) {
      return this.set(v);
    }

    // calculate magnitudes
    const selfMag = this.mag();
    const vMag = v.mag();
    const magmag = selfMag * vMag;
    // if either is a zero vector, linearly interpolate by these vectors
    if (magmag === 0) {
      this.mult(1 - amt).add(v.x * amt, v.y * amt, v.z * amt);
      return this;
    }
    // the cross product of 'this' and 'v' is the axis of rotation
    const axis = this.cross(v);
    const axisMag = axis.mag();
    // Calculates the angle between 'this' and 'v'
    const theta = Math.atan2(axisMag, this.dot(v));

    // However, if the norm of axis is 0, normalization cannot be performed,
    // so we will divide the cases
    if (axisMag > 0) {
      axis.x /= axisMag;
      axis.y /= axisMag;
      axis.z /= axisMag;
    } else if (theta < Math.PI * 0.5) {
      // if the norm is 0 and the angle is less than PI/2,
      // the angle is very close to 0, so do linear interpolation.
      this.mult(1 - amt).add(v.x * amt, v.y * amt, v.z * amt);
      return this;
    } else {
      // If the norm is 0 and the angle is more than PI/2, the angle is
      // very close to PI.
      // In this case v can be regarded as '-this', so take any vector
      // that is orthogonal to 'this' and use that as the axis.
      if (this.z === 0 && v.z === 0) {
        // if both this and v are 2D vectors, use (0,0,1)
        // this makes the result also a 2D vector.
        axis.set(0, 0, 1);
      } else if (this.x !== 0) {
        // if the x components is not 0, use (y, -x, 0)
        axis.set(this.y, -this.x, 0).normalize();
      } else {
        // if the x components is 0, use (1,0,0)
        axis.set(1, 0, 0);
      }
    }

    // Since 'axis' is a unit vector, ey is a vector of the same length as 'this'.
    const ey = axis.cross(this);
    // interpolate the length with 'this' and 'v'.
    const lerpedMagFactor = 1 - amt + (amt * vMag) / selfMag;
    // imagine a situation where 'axis', 'this', and 'ey' are pointing
    // along the z, x, and y axes, respectively.
    // rotates 'this' around 'axis' by amt * theta towards 'ey'.
    const cosMultiplier = lerpedMagFactor * Math.cos(amt * theta);
    const sinMultiplier = lerpedMagFactor * Math.sin(amt * theta);
    // then, calculate 'result'.
    this.x = this.x * cosMultiplier + ey.x * sinMultiplier;
    this.y = this.y * cosMultiplier + ey.y * sinMultiplier;
    this.z = this.z * cosMultiplier + ey.z * sinMultiplier;

    return this;
  }

  /**
   * Reflects a vector about a line in 2D or a plane in 3D.
   *
   * The orientation of the line or plane is described by a normal vector that
   * points away from the shape.
   *
   * The static version of `reflect()`, as in `p5.Vector.reflect(v, n)`,
   * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
   * the original.
   *
   * @param  {p5.Vector} surfaceNormal  <a href="#/p5.Vector">p5.Vector</a>
   *                                    to reflect about.
   * @chainable
   * @example
   * // META:norender
   * function setup() {
   *   // Create a normal vector.
   *   let n = createVector(0, 1);
   *   // Create a vector to reflect.
   *   let v = createVector(4, 6);
   *
   *   // Reflect v about n.
   *   v.reflect(n);
   *
   *   // Prints "p5.Vector Object : [4, -6, 0]" to the console.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a normal vector.
   *   let n = createVector(0, 1);
   *
   *   // Create a vector to reflect.
   *   let v0 = createVector(4, 6);
   *
   *   // Create a reflected vector.
   *   let v1 = p5.Vector.reflect(v0, n);
   *
   *   // Prints "p5.Vector Object : [4, -6, 0]" to the console.
   *   print(v1.toString());
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('Three arrows extend from the center of a gray square with a vertical line down its middle. A black arrow points to the right, a blue arrow points to the bottom left, and a red arrow points to the bottom right.');
   * }
   * function draw() {
   *   background(200);
   *
   *   // Draw a vertical line.
   *   line(50, 0, 50, 100);
   *
   *   // Create a normal vector.
   *   let n = createVector(1, 0);
   *
   *   // Center.
   *   let v0 = createVector(50, 50);
   *
   *   // Create a vector to reflect.
   *   let v1 = createVector(30, 40);
   *
   *   // Create a reflected vector.
   *   let v2 = p5.Vector.reflect(v1, n);
   *
   *   // Scale the normal vector for drawing.
   *   n.setMag(30);
   *
   *   // Draw the black arrow.
   *   drawArrow(v0, n, 'black');
   *
   *   // Draw the red arrow.
   *   drawArrow(v0, v1, 'red');
   *
   *   // Draw the blue arrow.
   *   drawArrow(v0, v2, 'blue');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  reflect(surfaceNormal) {
    const surfaceNormalCopy = Vector.normalize(surfaceNormal);
    return this.sub(surfaceNormalCopy.mult(2 * this.dot(surfaceNormalCopy)));
  }

  /**
   * Returns the vector's components as an array of numbers.
   *
   * @return {Number[]} array with the vector's components.
   * @deprecated To retrieve vector components, use `v.values`
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = createVector(20, 30);
   *
   *   // Prints "[20, 30, 0]" to the console.
   *   print(v.array());
   * }
   */
  array() {
    return [this.x || 0, this.y || 0, this.z || 0];
  }

  /**
   * Checks whether all the vector's components are equal to another vector's.
   *
   * `equals()` returns `true` if the vector's components are all the same as another
   * vector's and `false` if not.
   *
   * The version of `equals()` with one parameter interprets it as another
   * <a href="#/p5.Vector">p5.Vector</a> object.
   *
   * The version of `equals()` with multiple parameters interprets them as the
   * components of another vector.
   *
   * If the two vectors have different lengths, a warning is logged and only
   * the components up to the shorter length are compared.
   *
   * The static version of `equals()`, as in `p5.Vector.equals(v0, v1)`,
   * interprets both parameters as <a href="#/p5.Vector">p5.Vector</a> objects.
   *
   * @param {Number} [x] x component of the vector.
   * @param {Number} [y] y component of the vector.
   * @param {Number} [z] z component of the vector.
   * @return {Boolean} whether the vectors are equal.
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v0 = createVector(10, 20, 30);
   *   let v1 = createVector(10, 20, 30);
   *   let v2 = createVector(0, 0, 0);
   *
   *   // Prints "true" to the console.
   *   print(v0.equals(v1));
   *
   *   // Prints "false" to the console.
   *   print(v0.equals(v2));
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v0 = createVector(5, 10, 20);
   *   let v1 = createVector(5, 10, 20);
   *   let v2 = createVector(13, 10, 19);
   *
   *   // Prints "true" to the console.
   *   print(v0.equals(v1.x, v1.y, v1.z));
   *
   *   // Prints "false" to the console.
   *   print(v0.equals(v2.x, v2.y, v2.z));
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create p5.Vector objects.
   *   let v0 = createVector(10, 20, 30);
   *   let v1 = createVector(10, 20, 30);
   *   let v2 = createVector(0, 0, 0);
   *
   *   // Prints "true" to the console.
   *   print(p5.Vector.equals(v0, v1));
   *
   *   // Prints "false" to the console.
   *   print(p5.Vector.equals(v0, v2));
   * }
   */
  /**
   * @param {p5.Vector|Array} value vector to compare.
   * @return {Boolean}
   */
  equals(...args) {
    let values;
    if (args[0] instanceof Vector) {
      values = args[0].values;
    } else if (Array.isArray(args[0])) {
      values = args[0];
    } else {
      values = args;
    }

    const minDimension = prioritizeSmallerDimension(this.values.length, values);

    for (let i = 0; i < minDimension; i++) {
      if (this.values[i] !== values[i]) {
        return false;
      }
    }
    return true;
  }

  /**
   * Replaces the components of a <a href="#/p5.Vector">p5.Vector</a> that are very close to zero with zero.
   *
   * In computers, handling numbers with decimals can give slightly imprecise answers due to the way those numbers are represented.
   * This can make it hard to check if a number is zero, as it may be close but not exactly zero.
   * This method rounds very close numbers to zero to make those checks easier
   *
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON
   *
   * @return {p5.Vector} with components very close to zero replaced with zero.
   * @chainable
   */
  clampToZero() {
    for (let i = 0; i < this.values.length; i++) {
      this.values[i] = this._clampToZero(this.values[i]);
    }
    return this;
  }

  /**
   * Helper function for clampToZero
   * @private
   */
  _clampToZero(val) {
    return Math.abs((val || 0) - 0) <= Number.EPSILON ? 0 : val;
  }

  // Static Methods

  /**
   * Creates a new 2D vector from an angle.
   *
   * @static
   * @param {Number}     angle desired angle, in radians. Unaffected by <a href="#/p5/angleMode">angleMode()</a>.
   * @param {Number}     [length] length of the new vector (defaults to 1).
   * @return {p5.Vector}       new <a href="#/p5.Vector">p5.Vector</a> object.
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = p5.Vector.fromAngle(0);
   *
   *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
   *   print(v.toString());
   * }
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = p5.Vector.fromAngle(0, 30);
   *
   *   // Prints "p5.Vector Object : [30, 0, 0]" to the console.
   *   print(v.toString());
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('A black arrow extends from the center of a gray square. It points to the right.');
   * }
   * function draw() {
   *   background(200);
   *
   *   // Create a p5.Vector to the center.
   *   let v0 = createVector(50, 50);
   *
   *   // Create a p5.Vector with an angle 0 and magnitude 30.
   *   let v1 = p5.Vector.fromAngle(0, 30);
   *
   *   // Draw the black arrow.
   *   drawArrow(v0, v1, 'black');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  static fromAngle(angle, length) {
    if (typeof length === 'undefined') {
      length = 1;
    }
    return new Vector(length * Math.cos(angle), length * Math.sin(angle));
  }

  /**
   * Creates a new 3D vector from a pair of ISO spherical angles.
   *
   * @static
   * @param {Number}     theta    polar angle in radians (zero is up).
   * @param {Number}     phi      azimuthal angle in radians
   *                               (zero is out of the screen).
   * @param {Number}     [length] length of the new vector (defaults to 1).
   * @return {p5.Vector}          new <a href="#/p5.Vector">p5.Vector</a> object.
   *
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = p5.Vector.fromAngles(0, 0);
   *
   *   // Prints "p5.Vector Object : [0, -1, 0]" to the console.
   *   print(v.toString());
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100, WEBGL);
   *
   *   describe('A light shines on a pink sphere as it orbits.');
   * }
   *
   * function draw() {
   *   background(0);
   *
   *   // Calculate the ISO angles.
   *   let theta = frameCount *  0.05;
   *   let phi = 0;
   *
   *   // Create a p5.Vector object.
   *   let v = p5.Vector.fromAngles(theta, phi, 100);
   *
   *   // Create a point light using the p5.Vector.
   *   let c = color('deeppink');
   *   pointLight(c, v);
   *
   *   // Style the sphere.
   *   fill(255);
   *   noStroke();
   *
   *   // Draw the sphere.
   *   sphere(35);
   * }
   */
  static fromAngles(theta, phi, length) {
    if (typeof length === 'undefined') {
      length = 1;
    }
    const cosPhi = Math.cos(phi);
    const sinPhi = Math.sin(phi);
    const cosTheta = Math.cos(theta);
    const sinTheta = Math.sin(theta);

    return new Vector(
      length * sinTheta * sinPhi,
      -length * cosTheta,
      length * sinTheta * cosPhi
    );
  }

  /**
   * Creates a new 2D unit vector with a random heading.
   *
   * @static
   * @return {p5.Vector} new <a href="#/p5.Vector">p5.Vector</a> object.
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = p5.Vector.random2D();
   *
   *   // Prints "p5.Vector Object : [x, y]" to the console
   *   // where x and y are small random numbers.
   *   print(v.toString());
   * }
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   // Slow the frame rate.
   *   frameRate(1);
   *
   *   describe('A black arrow in extends from the center of a gray square. It changes direction once per second.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Create a p5.Vector to the center.
   *   let v0 = createVector(50, 50);
   *
   *   // Create a random p5.Vector.
   *   let v1 = p5.Vector.random2D();
   *
   *   // Scale v1 for drawing.
   *   v1.mult(30);
   *
   *   // Draw the black arrow.
   *   drawArrow(v0, v1, 'black');
   * }
   *
   * // Draws an arrow between two vectors.
   * function drawArrow(base, vec, myColor) {
   *   push();
   *   stroke(myColor);
   *   strokeWeight(3);
   *   fill(myColor);
   *   translate(base.x, base.y);
   *   line(0, 0, vec.x, vec.y);
   *   rotate(vec.heading());
   *   let arrowSize = 7;
   *   translate(vec.mag() - arrowSize, 0);
   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
   *   pop();
   * }
   */
  static random2D() {
    return this.fromAngle(Math.random() * constants.TWO_PI);
  }

  /**
   * Creates a new 3D unit vector with a random heading.
   *
   * @static
   * @return {p5.Vector} new <a href="#/p5.Vector">p5.Vector</a> object.
   * @example
   * // META:norender
   * function setup() {
   *   // Create a p5.Vector object.
   *   let v = p5.Vector.random3D();
   *
   *   // Prints "p5.Vector Object : [x, y, z]" to the console
   *   // where x, y, and z are small random numbers.
   *   print(v.toString());
   * }
   */
  static random3D() {
    const angle = Math.random() * constants.TWO_PI;
    const vz = Math.random() * 2 - 1;
    const vzBase = Math.sqrt(1 - vz * vz);
    const vx = vzBase * Math.cos(angle);
    const vy = vzBase * Math.sin(angle);
    return new Vector(vx, vy, vz);
  }

  // Returns a copy of a vector.
  /**
   * @static
   * @param  {p5.Vector} v the <a href="#/p5.Vector">p5.Vector</a> to create a copy of
   * @return {p5.Vector} the copy of the <a href="#/p5.Vector">p5.Vector</a> object
   */
  static copy(v) {
    return v.copy(v);
  }

  // Adds two vectors together and returns a new one.
  /**
   * @static
   * @param  {p5.Vector} v1 A <a href="#/p5.Vector">p5.Vector</a> to add
   * @param  {p5.Vector} v2 A <a href="#/p5.Vector">p5.Vector</a> to add
   * @param  {p5.Vector} [target] vector to receive the result.
   * @return {p5.Vector} resulting <a href="#/p5.Vector">p5.Vector</a>.
   */
  static add(v1, v2, target) {
    if (!target) {
      target = v1.copy();
      if (arguments.length === 3) {
        this._friendlyError(
          'The target parameter is undefined, it should be of type p5.Vector',
          'p5.Vector.add'
        );
      }
    } else {
      target.set(v1);
    }
    target.add(v2);
    return target;
  }

  // Returns a vector remainder when it is divided by another vector
  /**
   * @static
   * @param  {p5.Vector} v1 The dividend <a href="#/p5.Vector">p5.Vector</a>
   * @param  {p5.Vector} v2 The divisor <a href="#/p5.Vector">p5.Vector</a>
   */
  /**
   * @static
   * @param  {p5.Vector} v1
   * @param  {p5.Vector} v2
   * @return {p5.Vector} The resulting <a href="#/p5.Vector">p5.Vector</a>
   */
  static rem(v1, v2) {
    if (v1 instanceof Vector && v2 instanceof Vector) {
      let target = v1.copy();
      target.rem(v2);
      return target;
    }
  }

  /*
   * Subtracts one <a href="#/p5.Vector">p5.Vector</a> from another and returns a new one.  The second
   * vector (`v2`) is subtracted from the first (`v1`), resulting in `v1-v2`.
   */
  /**
   * @static
   * @param  {p5.Vector} v1 A <a href="#/p5.Vector">p5.Vector</a> to subtract from
   * @param  {p5.Vector} v2 A <a href="#/p5.Vector">p5.Vector</a> to subtract
   * @param  {p5.Vector} [target] vector to receive the result.
   * @return {p5.Vector} The resulting <a href="#/p5.Vector">p5.Vector</a>
   */
  static sub(v1, v2, target) {
    if (!target) {
      target = v1.copy();
      if (arguments.length === 3) {
        this._friendlyError(
          'The target parameter is undefined, it should be of type p5.Vector',
          'p5.Vector.sub'
        );
      }
    } else {
      target.set(v1);
    }
    target.sub(v2);
    return target;
  }

  /**
   * Multiplies a vector by a scalar and returns a new vector.
   */
  /**
   * @static
   * @param  {Number} x
   * @param  {Number} y
   * @param  {Number} [z]
   * @return {p5.Vector} resulting new <a href="#/p5.Vector">p5.Vector</a>.
   */
  /**
   * @static
   * @param  {p5.Vector} v
   * @param  {Number}  n
   * @param  {p5.Vector} [target] vector to receive the result.
   */
  /**
   * @static
   * @param  {p5.Vector} v0
   * @param  {p5.Vector} v1
   * @param  {p5.Vector} [target]
   */
  /**
   * @static
   * @param  {p5.Vector} v0
   * @param  {Number[]} arr
   * @param  {p5.Vector} [target]
   */
  static mult(v, n, target) {
    if (!target) {
      target = v.copy();
      if (arguments.length === 3) {
        this._friendlyError(
          'The target parameter is undefined, it should be of type p5.Vector',
          'p5.Vector.mult'
        );
      }
    } else {
      target.set(v);
    }
    target.mult(n);
    return target;
  }

  /**
   * Rotates the vector (only 2D vectors) by the given angle; magnitude remains the same. Returns a new vector.
   */
  /**
   * @static
   * @param  {p5.Vector} v
   * @param  {Number} angle
   * @param  {p5.Vector} [target] The vector to receive the result
   */
  static rotate(v, a, target) {
    if (arguments.length === 2) {
      target = v.copy();
    } else {
      if (!(target instanceof Vector)) {
        this._friendlyError(
          'The target parameter should be of type p5.Vector',
          'p5.Vector.rotate'
        );
      }
      target.set(v);
    }
    target.rotate(a);
    return target;
  }

  /**
   * Divides a vector by a scalar and returns a new vector.
   */
  /**
   * @static
   * @param  {Number} x
   * @param  {Number} y
   * @param  {Number} [z]
   * @return {p5.Vector} The resulting new <a href="#/p5.Vector">p5.Vector</a>
   */
  /**
   * @static
   * @param  {p5.Vector} v
   * @param  {Number}  n
   * @param  {p5.Vector} [target] The vector to receive the result
   */
  /**
   * @static
   * @param  {p5.Vector} v0
   * @param  {p5.Vector} v1
   * @param  {p5.Vector} [target]
   */
  /**
   * @static
   * @param  {p5.Vector} v0
   * @param  {Number[]} arr
   * @param  {p5.Vector} [target]
   */
  static div(v, n, target) {
    if (!target) {
      target = v.copy();

      if (arguments.length === 3) {
        this._friendlyError(
          'The target parameter is undefined, it should be of type p5.Vector',
          'p5.Vector.div'
        );
      }
    } else {
      target.set(v);
    }
    target.div(n);
    return target;
  }

  /**
   * Calculates the dot product of two vectors.
   */
  /**
   * @static
   * @param  {p5.Vector} v1 first <a href="#/p5.Vector">p5.Vector</a>.
   * @param  {p5.Vector} v2 second <a href="#/p5.Vector">p5.Vector</a>.
   * @return {Number}     dot product.
   */
  static dot(v1, v2) {
    return v1.dot(v2);
  }

  /**
   * Calculates the cross product of two vectors.
   */
  /**
   * @static
   * @param  {p5.Vector} v1 first <a href="#/p5.Vector">p5.Vector</a>.
   * @param  {p5.Vector} v2 second <a href="#/p5.Vector">p5.Vector</a>.
   * @return {p5.Vector}     cross product.
   */
  static cross(v1, v2) {
    return v1.cross(v2);
  }

  /**
   * Calculates the Euclidean distance between two points (considering a
   * point as a vector object).
   */
  /**
   * @static
   * @param  {p5.Vector} v1 The first <a href="#/p5.Vector">p5.Vector</a>
   * @param  {p5.Vector} v2 The second <a href="#/p5.Vector">p5.Vector</a>
   * @return {Number}     The distance
   */
  static dist(v1, v2) {
    return v1.dist(v2);
  }

  /**
   * Linear interpolate a vector to another vector and return the result as a
   * new vector.
   */
  /**
   * @static
   * @param {p5.Vector} v1
   * @param {p5.Vector} v2
   * @param {Number} amt
   * @param {p5.Vector} [target] The vector to receive the result
   * @return {p5.Vector}      The lerped value
   */
  static lerp(v1, v2, amt, target) {
    if (!target) {
      target = v1.copy();
      if (arguments.length === 4) {
        this._friendlyError(
          'The target parameter is undefined, it should be of type p5.Vector',
          'p5.Vector.lerp'
        );
      }
    } else {
      target.set(v1);
    }
    target.lerp(v2, amt);
    return target;
  }

  /**
   * Performs spherical linear interpolation with the other vector
   * and returns the resulting vector.
   * This works in both 3D and 2D. As for 2D, the result of slerping
   * between 2D vectors is always a 2D vector.
   */
  /**
   * @static
   * @param {p5.Vector} v1 old vector.
   * @param {p5.Vector} v2 new vector.
   * @param {Number} amt
   * @param {p5.Vector} [target] vector to receive the result.
   * @return {p5.Vector} slerped vector between v1 and v2
   */
  static slerp(v1, v2, amt, target) {
    if (!target) {
      target = v1.copy();
      if (arguments.length === 4) {
        this._friendlyError(
          'The target parameter is undefined, it should be of type p5.Vector',
          'p5.Vector.slerp'
        );
      }
    } else {
      target.set(v1);
    }
    target.slerp(v2, amt);
    return target;
  }

  /**
   * Calculates the magnitude (length) of the vector and returns the result as
   * a float (this is simply the equation `sqrt(x*x + y*y + z*z)`.)
   */
  /**
   * @static
   * @param {p5.Vector} vecT The vector to return the magnitude of
   * @return {Number}        The magnitude of vecT
   */
  static mag(vecT) {
    return vecT.mag();
  }

  /**
   * Calculates the squared magnitude of the vector and returns the result
   * as a float (this is simply the equation <em>(x\*x + y\*y + z\*z)</em>.)
   * Faster if the real length is not required in the
   * case of comparing vectors, etc.
   */
  /**
   * @static
   * @param {p5.Vector} vecT the vector to return the squared magnitude of
   * @return {Number}        the squared magnitude of vecT
   */
  static magSq(vecT) {
    return vecT.magSq();
  }

  /**
   * Normalize the vector to length 1 (make it a unit vector).
   */
  /**
   * @static
   * @param {p5.Vector} v  The vector to normalize
   * @param {p5.Vector} [target] The vector to receive the result
   * @return {p5.Vector}   The vector v, normalized to a length of 1
   */
  static normalize(v, target) {
    if (arguments.length < 2) {
      target = v.copy();
    } else {
      if (!(target instanceof Vector)) {
        this._friendlyError(
          'The target parameter should be of type p5.Vector',
          'p5.Vector.normalize'
        );
      }
      target.set(v);
    }
    return target.normalize();
  }

  /**
   * Limit the magnitude of the vector to the value used for the <b>max</b>
   * parameter.
   */
  /**
   * @static
   * @param {p5.Vector} v  the vector to limit
   * @param {Number}    max
   * @param {p5.Vector} [target] the vector to receive the result (Optional)
   * @return {p5.Vector} v with a magnitude limited to max
   */
  static limit(v, max, target) {
    if (arguments.length < 3) {
      target = v.copy();
    } else {
      if (!(target instanceof Vector)) {
        this._friendlyError(
          'The target parameter should be of type p5.Vector',
          'p5.Vector.limit'
        );
      }
      target.set(v);
    }
    return target.limit(max);
  }

  /**
   * Set the magnitude of the vector to the value used for the <b>len</b>
   * parameter.
   */
  /**
   * @static
   * @param {p5.Vector} v  the vector to set the magnitude of
   * @param {Number}    len
   * @param {p5.Vector} [target] the vector to receive the result (Optional)
   * @return {p5.Vector} v with a magnitude set to len
   */
  static setMag(v, len, target) {
    if (arguments.length < 3) {
      target = v.copy();
    } else {
      if (!(target instanceof Vector)) {
        this._friendlyError(
          'The target parameter should be of type p5.Vector',
          'p5.Vector.setMag'
        );
      }
      target.set(v);
    }
    return target.setMag(len);
  }

  /**
   * Calculate the angle of rotation for this vector (only 2D vectors).
   * p5.Vectors created using <a href="#/p5/createVector">createVector()</a>
   * will take the current <a href="#/p5/angleMode">angleMode</a> into
   * consideration, and give the angle in radians or degrees accordingly.
   */
  /**
   * @static
   * @param {p5.Vector} v the vector to find the angle of
   * @return {Number} the angle of rotation
   */
  static heading(v) {
    return v.heading();
  }

  /**
   * Calculates and returns the angle between two vectors. This function will take
   * the <a href="#/p5/angleMode">angleMode</a> on v1 into consideration, and
   * give the angle in radians or degrees accordingly.
   */
  /**
   * @static
   * @param  {p5.Vector}    v1 the first vector.
   * @param  {p5.Vector}    v2 the second vector.
   * @return {Number}       angle between the two vectors.
   */
  static angleBetween(v1, v2) {
    return v1.angleBetween(v2);
  }

  /**
   * Reflect a vector about a normal to a line in 2D, or about a normal to a
   * plane in 3D.
   */
  /**
   * @static
   * @param  {p5.Vector} incidentVector vector to be reflected.
   * @param  {p5.Vector} surfaceNormal
   * @param  {p5.Vector} [target] vector to receive the result.
   * @return {p5.Vector} the reflected vector
   */
  static reflect(incidentVector, surfaceNormal, target) {
    if (arguments.length < 3) {
      target = incidentVector.copy();
    } else {
      if (!(target instanceof Vector)) {
        this._friendlyError(
          'The target parameter should be of type p5.Vector',
          'p5.Vector.reflect'
        );
      }
      target.set(incidentVector);
    }
    return target.reflect(surfaceNormal);
  }

  /**
   * Return a representation of this vector as a float array. This is only
   * for temporary use. If used in any other fashion, the contents should be
   * copied by using the <b>p5.Vector.<a href="#/p5.Vector/copy">copy()</a></b>
   * method to copy into your own vector.
   */
  /**
   * @static
   * @param  {p5.Vector} v the vector to convert to an array
   * @return {Number[]} an Array with the 3 values
   */
  static array(v) {
    return v.array();
  }

  /**
   * Equality check against a <a href="#/p5.Vector">p5.Vector</a>
   */
  /**
   * @static
   * @param {p5.Vector|Array} v1 the first vector to compare
   * @param {p5.Vector|Array} v2 the second vector to compare
   * @return {Boolean}
   */
  static equals(v1, v2) {
    let v;
    if (v1 instanceof Vector) {
      v = v1;
    } else if (v1 instanceof Array) {
      v = new Vector().set(v1);
    } else {
      this._friendlyError(
        'The v1 parameter should be of type Array or p5.Vector',
        'p5.Vector.equals'
      );
    }
    return v.equals(v2);
  }
}

function vector(p5, fn) {
  /**
   * A class to describe a two or three-dimensional vector.
   *
   * A vector can be thought of in different ways. In one view, a vector is like
   * an arrow pointing in space. Vectors have both magnitude (length) and
   * direction.
   *
   * `p5.Vector` objects are often used to program motion because they simplify
   * the math. For example, a moving ball has a position and a velocity.
   * Position describes where the ball is in space. The ball's position vector
   * extends from the origin to the ball's center. Velocity describes the ball's
   * speed and the direction it's moving. If the ball is moving straight up, its
   * velocity vector points straight up. Adding the ball's velocity vector to
   * its position vector moves it, as in `pos.add(vel)`. Vector math relies on
   * methods inside the `p5.Vector` class.
   *
   * Note: <a href="#/p5/createVector">createVector()</a> is the recommended way
   * to make an instance of this class.
   *
   * @class p5.Vector
   * @param {Number} [x] x component of the vector.
   * @param {Number} [y] y component of the vector.
   * @param {Number} [z] z component of the vector.
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   background(200);
   *
   *   // Create p5.Vector objects.
   *   let p1 = createVector(25, 25);
   *   let p2 = createVector(75, 75);
   *
   *   // Style the points.
   *   strokeWeight(5);
   *
   *   // Draw the first point using a p5.Vector.
   *   point(p1);
   *
   *   // Draw the second point using a p5.Vector's components.
   *   point(p2.x, p2.y);
   *
   *   describe('Two black dots on a gray square, one at the top left and the other at the bottom right.');
   * }
   *
   * @example
   * let pos;
   * let vel;
   *
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   // Create p5.Vector objects.
   *   pos = createVector(50, 100);
   *   vel = createVector(0, -1);
   *
   *   describe('A black dot moves from bottom to top on a gray square. The dot reappears at the bottom when it reaches the top.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Add velocity to position.
   *   pos.add(vel);
   *
   *   // If the dot reaches the top of the canvas,
   *   // restart from the bottom.
   *   if (pos.y < 0) {
   *     pos.y = 100;
   *   }
   *
   *   // Draw the dot.
   *   strokeWeight(5);
   *   point(pos);
   * }
   */
  p5.Vector = Vector;

  Vector.prototype._friendlyError = p5._friendlyError;
  Vector.prototype.friendlyErrorsDisabled = function() {
    return p5.disableFriendlyErrors;
  };

  /**
   * The x component of the vector
   * @type {Number}
   * @for p5.Vector
   * @property x
   * @name x
   */

  /**
   * The y component of the vector
   * @type {Number}
   * @for p5.Vector
   * @property y
   * @name y
   */

  /**
   * The z component of the vector
   * @type {Number}
   * @for p5.Vector
   * @property z
   * @name z
   */

  /**
   * The dimensions of the vector
   * @type {Number}
   * @for p5.Vector
   * @property dimensions
   * @name dimensions
   */
}

export default vector;
export { Vector };

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