Source: types.js

/**
 * Models a point in 2D space
 */
class Point2D {
  /**
   * Creates a point
   *  @param {number} x - The x axis coordinate of the point
   *  @param {number} y - The y axis coordinate of the point
   */
  constructor(x, y) {
    this._x = x;
    this._y = y;
  }
  /**
   * Copies the values of another point to this one
   *  @param {Object} point - The point to copy to this one
   */
  copy(point) {
    this._x = point._x;
    this._y = point._y;
  }
  /**
   * Gets the x coordinate of this point
   *  @return {number} The x axis coordinate
   */
  get x() {
    return this._x;
  }
  /**
   * Gets the x coordinate of this point
   *  @param {number} val - The new value for the x axis coordinate
   */
  set x(val) {
    this._x = val;
  }
  /**
   * Gets the x coordinate of this point
   *  @return {number} The y axis coordinate
   */
  get y() {
    return this._y;
  }
  /**
   * Gets the y coordinate of this point
   *  @param {number} val - The new value for the y axis coordinate
   */
  set y(val) {
    this._y = val;
  }
  /**
   * Calculates the distance between two points
   *  @param {Object} point - The point to measure distance to
   *  @return {number} The distance between this point and the one passed
   */
  distanceTo(point) {
    const x = point._x - this._x;
    const y = point._y - this._y;
    return Math.sqrt( (x * x) + (y * y) );
  }
}

/**
 * Models a 2D vector
 *  @extends Point2D
 */
class Vector2D extends Point2D {
  /**
   * Creates a Vector
   *  @param {number} x - The x axis scalar of the vector
   *  @param {number} y - The y axis scalar of the vector
   */
  constructor(x, y) {
    super(x, y);
  }
  /**
   * Normaizes this vector. A normalized vector is one with a magnitude of 1.
   */
  normalize() {
    const mag = this.magnitude;
    this._x /= mag;
    this._y /= mag;
  }
  /**
   * Gets the euclidean distance (length) of this vector
   *  @return {number} The magintude of this vector
   */
  get magnitude() {
    return Math.sqrt(this._x * this._x + this._y * this._y);
  }
  /**
   * Rescales this vector to a new magnitude
   *  @param {number} val - the new magnitude of the vector
   */
  set magnitude(val) {
    // First reset the length of the vector to 1
    this.normalize();
    //Then multiply it by it's new magnitude
    this._x *= val;
    this._y *= val;
  }
  /**
   * Converts to this vector to angular vector notation
   *  @return {number} the angle of this vector in radians
   */
  get radian() {
    return Math.atan2(this._y, this._x);
  }
  /**
   * Converts this vector from angular vector notation
   *  @param {number} val - The new angular value
   */
  set radian(val) {
    this._x = Math.cos(val);
    this._y = Math.sin(val);
  }
  /**
   * Rotates this vector by another vector in a given direction
   *  @param {number} x - The x scalar of the vector to rotate by
   *  @param {number} y - The y scalar of the vector to rotate by
   *  @param {number} direction - 1 to rotate clockwise, -1 to rotate anti-clockwise
   */
  rotate(x, y, direction = -1) {
    const tX = this._x * x + (this._y * direction) * y;
    const tY = (this._x * y + (this._y * direction) * x) * direction;
    this._x = tX;
    this._y = tY;
  }
  // Addative rotation
  /**
   * Rotates this vector clockwise by an angle in radians
   *  @param {number} radians - The angle to rotate by in radians
   */
  rotateByRadians(radians) {
    this.rotate(
      Math.cos(radians),
      Math.sin(radians)
    );
  }
  /**
   * Rotates this vector clockwise by another vector
   *  @param {number} vector - The vector to rotate by
   */
  rotateByVector(vector) {
    this.rotate(vector._x, vector._y);
  }
  // Subtractive Rotation
  /**
   * Rotates this vector anti-clockwise by an angle in radians
   *  @param {number} radians - The angle to rotate by in radians
   */
  unrotateByRadians(radians) {
    this.rotate(
      Math.cos(radians),
      Math.sin(radians), 1
    );
  }
  /**
   * Rotates this vector anti-clockwise by another vector
   *  @param {number} vector - The vector to rotate by
   */
  unrotateByVector(vector) {
    this.rotate(vector._x, vector._y, 1);
  }
}

/**
 * Defines a rectanglular region in 2D space that can collide and detect collision
 * Implements an Axis Aligned Bounding Box (AABB)
 *  @extends Point2D
 */
class BoundingBox extends Point2D {
  /**
   * Creates a new bounding box
   *  @param {number} x - The x position of the top left corner
   *  @param {number} y - The y position of the top left corner
   *  @param {number} width - The width of the box
   *  @param {number} height - The height of the box
   */
  constructor(x, y, width, height) {
    super(x, y);
    this._w = width;
    this._h = height;
  }
  /**
   * Copies the values of another BoundingBox to this one
   *  @param {Object} box - the BoundingBox to copy
   */
  copy(box) {
    this._x = box._x;
    this._y = box._y;
    this._w = box._w;
    this._h = box._h;
  }
  /**
   * Get the width value
   *  @return {number} The width value
   */
  get width() {
    return this._w;
  }
  set width(val) {
    this._w = val;
  }
  /**
   * Get the height value
   *  @return {number} The height value
   */
  get height() {
    return this._h;
  }
  set height(val) {
    this._h = val;
  }
  /**
   * Checks whether the point is within the box boundary
   *  @param {Object} point - The Point2D cordinates to check
   *  @return {boolean} true if the point is inside this box, otherwise false
   */
  pointInBounds(point) {
    return this.inBounds(point._x, point._y);
  }
  /**
   * Checks whether the x,y coordinate passed is within the box boundary
   *  @param {number} x - The x coordinate
   *  @param {number} y - The y coordinate
   *  @return {boolean} true if the point is inside this box, otherwise false
   */
  inBounds(x, y) {
    if (x >= this._x && y >= this._y &&
        x <= (this._x + this._w) &&
        y <= (this._y + this._h)) {
      return true;
    }
    return false;
  }
  /**
   * Performs a simple collision check on another boundingBox.
   *  @param {Object} box - The BoundingBox to check for intersection
   *  @return {boolean} true if colliding
   */
  collides(box) {
    if (this._x < box._x + box._w &&
        this._x + this._w > box._x &&
        this._y < box._y + box._h &&
        this._y + this._h > box._y) {
      return true;
    }
    return false;
  }
  /**
   * Performs a comprehensive collision test that checks where the two boxes are
   * overlapping and indicates the closest point to move them out of collision.
   *  @param {Object} box - The BoundingBox to check for intersection
   *  @return {Object} a dictionary with side: axis of intersection and
   *                     pos: closest point of non-intersection
   */
  intersects(box) {
    // Find the amount of intersection for the left and right sides
    const x1 = (box._x + box._w) - this._x;
    const x2 = (this._x + this._w) - box._x;
    let x = 0, xPos = 0;
    // Find the closest side
    if (x1 < x2) {
      x = x1;
      xPos = this._x - (box._w + 1);
    } else {
      x = x2;
      xPos = this._x + this._w + 1;
    }
    // If x is negative there's no collision in x which means
    // the boxes aren't intersecting
    if (x < 0) return null;

    // Find the amount of intersection for the top and bottom sides
    const y1 = (box._y + box._h) - this._y;
    const y2 = (this._y + this._h) - box._y;
    let y = 0, yPos = 0;
    if (y1 < y2) {
      y = y1;
      yPos = this._y - (box._h + 1);
    } else {
      y = y2;
      yPos = this._y + this._h + 1;
    }
    if (y < 0) return null;

    // Find the closest axis
    if (x < y) {
      // x collision
      return {side: 'x', pos: xPos};
    } else {
      return {side: 'y', pos: yPos};
    }
  }

  /**
   * Checks the entire boundingBox passed is inside this one.
   *  @param {Object} box - The box to check
   *  @return {boolean} True/False whether the box passed is contained within this one
   */
  contains(box) {
    if (this._x < box._x && this._y < box._y &&
        this._x + this._w > box._x + box._w &&
        this._y + this._h > box._y + box._h) {
      return true;
    }
    return false;
  }
}