Home Reference Source Test

src/vector.js

/* eslint-disable object-property-newline */
import {
  add,
  addAll,
  addScaled,
  sub,
  subAll,
  subScaled,
  scale,
  norm,
  dot,
  cross,
  cross2,
  dist,
  scalar,
  isNullVec,
} from './functions';
import { flattenList } from './utils';

/** mapping of additional accessors to array index */
const accessors = ({
  x: 0, r: 0, i: 0, s: 0,
  y: 1, g: 1, j: 1, t: 1,
  z: 2, b: 2, k: 2, u: 2,
  w: 3, a: 3, l: 3, v: 3,
});

/**
 * Simple extension to js Array to allow function chaining when doing vector operations.
 * A vector instance can be used the same way as a native Array instance. Note that length will
 * therefore return the number of vector components, and not its scalar value.
 * Use the scalar function if you want to know the vector's length.
 *
 * Instantiate a vector by its constructor or using one of the factory functions (vec2, vec3...)
 */

export class Vector extends Array {
  /**
   * Instantiate a vector from an array.
   * @param {number[]} arr array of numbers
   * @return {Vector}
   */
  static fromArray(arr) {
    return new Vector(...arr);
  }

  /**
   * Add one or more vectors to this vector
   * @param  {...number[]} vectors vectors to add to this
   * @return {Vector}
   */
  add(...vectors) {
    if (vectors.length === 1) {
      return add(this, vectors[0]);
    }
    return addAll(vectors, this);
  }

  /**
   * Add a scaled version of a vector to this vector
   * @param  {number[]} vector vectors to add to this
   * @param {number} factor scaling factor
   * @return {Vector}
   */
  addScaled(vector, factor) {
    return addScaled(this, vector, factor, this);
  }

  /**
   * Subtract one or more vectors from this vector
   * @param  {...number[]} vectors vectors to subtract from this vector
   * @return {Vector}
   */
  sub(...vectors) {
    if (vectors.length === 1) {
      return sub(this, vectors[0]);
    }
    return subAll(this, vectors);
  }


  /**
   * Subtract a scaled version of a vector from this vector
   * @param  {number[]} vector vector to subtract from this
   * @param {number} factor scaling factor
   * @return {Vector}
   */
  subScaled(vector, factor) {
    return subScaled(this, vector, factor, this);
  }

  /**
   * Calculate the scalar product (length) of this vector.
   * @return {number}
   */
  scalar() {
    return scalar(this);
  }

  /**
   * Scale this vector by a factor
   * @param {number} factor scaling factor
   * @param {Array/Vector} target optional array/vector to avoid mutating this vector
   * @return {Vector}
   */
  scale(factor, target = null) {
    return scale(this, factor, target || this);
  }

  /**
   * Negate all components of this vector
   * @param {Array/Vector} target optional array/vector to avoid mutating this vector
   * @return {Vector}
   */
  negate(target = null) {
    return this.scale(-1, target);
  }

  /**
   * Normalize this vector
   * @param {Array/Vector} target optional array/vector to avoid mutating this vector
   * @return {Vector}
   */
  normalize(target = null) {
    return norm(this, target || this);
  }

  /**
   * Calculate the dot product between this vector and the passed in argument
   * @param {number[]} other vector
   * @return {number} dot product
   */
  dot(other) {
    return dot(this, other);
  }

  /**
   * Find the cross product vector between this vector and the passed in argument.
   * Only for 3d vectors!
   * @param {number[]} other 3d vector
   * @param {Array/Vector} target optional array/vector to avoid mutating this vector
   * @return {Vector}
   */
  cross(other, target = null) {
    return cross(this, other, target);
  }

  /**
   * Calculate the psudo cross product between this vector and the passed in argument.
   * Only for 2d vectors!
   * @param {number[]} other 2d vector
   * @return {number} psudo cross product
   */
  cross2(other) {
    return cross2(this, other);
  }

  /**
   * Calculate the distance between this coordinates to the coordinates supplied in the argument
   * @param {number[]} other coordinates
   * @return {number} distance
   */
  distance(other) {
    return dist(this, other);
  }

  /**
   * Clone/copy vector
   * @return {Vector}
   */
  clone() {
    return new Vector(...this);
  }

  /**
   * Mimic the swizzle feature in glsl. Supply the swizzle pattern as a string, using the
   * defined accessors (xyzw, rgba, ijkl or stuv):
   * ex: vec.swizzle('xxyw') or vec.swizzle('rrra)
   * @param {string} pattern swizzle pattern
   * @param {Array/Vector} target optional array/vector to avoid mutating this vector
   * @return {Vector}
   */
  swizzle(pattern, target = null) {
    let values = this;
    if (!target) {
      target = this;
      values = [...this];
    }
    const l = target.length || pattern.length;
    for (let i = 0; i < l; i++) {
      const ai = accessors[pattern[i]];
      target[i] = values[ai];
    }
    return target;
  }

  /**
   * Test if this vector is a null vector
   * @param {number} epsilon epsilon
   */
  isNull(epsilon = 0) {
    return isNullVec(this, epsilon);
  }

  /**
   * Assign values to this vector
   * @param  {...number} v values to set
   * @return {Vector}
   */
  set(...v) {
    for (let i = 0; i < v.length; i++) {
      /** assign value */
      this[i] = v[i];
    }
    return this;
  }
}

// add getters and setters from accessors
Object.keys(accessors).forEach((a) => {
  Object.defineProperty(Vector.prototype, a, {
    get() {
      return this[accessors[a]];
    },
    set(v) {
      if (Number.isFinite(v)) {
        this[accessors[a]] = v;
      }
    },
  });
});

/**
 * Create a vector of specific dimension and optionally set its values.
 * If values contains only one element, the all components will be set to
 * this value. Elements in values exceeding the specified dimension will
 * be neglected.
 * @param {number} dim the number of components
 * @param {Array<number>} values optional initial values
 * @return {Vector}
 */
export function nvec(dim = 0, values = [0]) {
  if (values.length === 1 && Number.isFinite(values[0])) {
    return new Vector(dim).fill(values[0]);
  }
  const v = new Vector();
  flattenList(values, v, dim);
  for (let i = v.length; i < dim; i++) {
    v.push(0);
  }
  return v;
}

/**
 * Factory function for creating and assigning a 2d vector. If a single
 * argument is passed, then all components will be assigned with its value.
 * Missing initial values will be filled with zeros.
 * @param  {...number} args initial values
 * @return {Vector}
 */
export function vec2(...args) {
  return nvec(2, args);
}

/**
 * Factory function for creating and assigning a 3d vector. If a single
 * argument is passed, then all components will be assigned with its value.
 * Missing initial values will be filled with zeros.
 * @param  {...number} args initial values
 * @return {Vector}
 */
export function vec3(...args) {
  return nvec(3, args);
}

/**
 * Factory function for creating and assigning a 4d vector. If a single
 * argument is passed, then all components will be assigned with its value.
 * Missing initial values will be filled with zeros.
 * @param  {...number} args initial values
 * @return {Vector}
 */
export function vec4(...args) {
  return nvec(4, args);
}