physics/bodies/body.js

import { Vector2 } from "../../math/index.js"
import { Utils } from "../../utils/index.js"
import { BoundingBox } from "../../math/index.js"
import { Settings } from "../settings.js"
import { Shape } from "../shapes/index.js"
import { deprecate } from "../../logger/index.js"

/**
 * Holds information needed for collision detection and response.
 * 
 */
export class Body2D {
  /**
   * Unique identification of a body.
   * 
   * @readonly
   * @type {number}
   */
  id = Utils.generateID()
  /**
   * Inverse mass of the body.
   * 
   * @type {number}
   */
  inv_mass = 0
  /**
   * Inverse inertia of the body.
   * 
   * @type {number}
   */
  inv_inertia = 0
  /**
   * The bounciness of the body between 0 and 1.
   * 
   * @type {number}
   * @default Settings.restitution
   */
  restitution = Settings.restitution
  /**
   * The friction of the body between 0 and 1 that affects it before it moves.
   * 
   * @type {number}
   * @default Settings.staticFriction
   */
  staticFriction = Settings.staticFriction
  /**
   * The friction of the body between 0 and 1that affects it after it moves.
   * 
   * @type {number}
   * @default Settings.kineticFriction
   */
  kineticFriction = Settings.kineticFriction
  /**
   * The padding of the body's bounds.
   * 
   * @type {number}
   * @default Settings.boundPadding
   */
  boundPadding = Settings.boundPadding
  /**
   * Used to describe how bodies will collide with each other.
   * Bodies in the same layer or layer 0 will always collide with each other unless they are in different groups.
   * Bodies in the same group will not collied with each other.
   * 
   * @type {{layer:number, group: number}}
   * @default -1
   */
  mask = {
    layer: 0,
    group: 0
  }
  /**
   * The shape of the body.
   * 
   * @readonly
   * @type {Shape}
   */
  shape
  /**
   * Whether the body should sleep when at rest or not.
   * 
   * @type {boolean}
   * @default Settings.allowSleep
   */
  allowSleep = Settings.allowSleep
  /**
   * If the body is asleep or not.
   * 
   * @type {boolean}
   */
  sleeping = false
  /**
   * Whether the body should detect collisions with bounds only.If true,no collision response will occur.Precollision event only will be fired.
   * 
   * @type {boolean}
   * @default Settings.aabbDetectionOnly
   */
  aabbDetectionOnly = Settings.aabbDetectionOnly
  /**
   * Whether the body should respond to collisions.If false,no collision response will occur but collision events will still be fired.
   * 
   * @type {boolean}
   * @default Settings.collisionResponse
   */
  collisionResponse = Settings.collisionResponse
  /**
   * Whether or not the bounds should be automatically updated.
   * 
   * @type {boolean}
   * @default Settings.autoUpdateBound
   */
  autoUpdateBound = Settings.autoUpdateBound
  /**
   * @param {Shape} shape
   */
  constructor(shape) {
    Body2D.setType(this,Settings.type)
    this.shape = shape
    Body2D.setMass(this,1)
  }
  /**
   * Type of a body.It includes the static and dynamic for now.
   * Static bodies do not move and do not react to collisions.
   * Dynamic bodies move and respond to collisions.
   * Kinematic bodies move but do not respond to collisions.
   * 
   * @deprecated
   * @example
   * @type {number}
   * let body = new Body2D()
   * body.type = Body2D.STATIC
   * 
   */
  set type(x) {
    deprecate("Body2D().type","Body2D.setType()")
    if (x === Body2D.STATIC) this.mass = 0
  }
  get type() {
    deprecate("Body2D().type")
    return 0
  }
  /**
   * @deprecated
   * @type {number} 
   */
  set mass(x) {
    deprecate("Body2D().mass")
    this.inv_mass = x === 0 ? 0 : 1 / x
    this.inv_inertia = 1 / Shape.calcInertia(this.shape,this.mass)
  }
  get mass() {
    deprecate("Body2D().mass")
    return 1 / this.inv_mass
  }
  /**
   * Density of a body.
   *
   * @deprecated
   * @type {number}
   */
  set density(x) {
    deprecate("Body2D().density")
    const area = Shape.getArea(this.shape)
    this.inv_mass = 1 / (x * area)
  }
  get density() {
    deprecate("Body2D().density")
    const area = Shape.getArea(this.shape)
    return this.inv_mass * 1 / area
  }
  /**
   * Rotational inertia of a body.
   * 
   * @deprecated
   * @type number
   */
  set inertia(x) {
    deprecate("Body2D().inertia")
    this.inv_inertia = x == 0 ? 0 : 1 / x
  }
  get inertia() {
    deprecate("Body2D().inertia")
    return 1 / this.inv_inertia
  }
  /**
   * Sets an anchor that is relative to the center of the body into it.The anchor's world coordinates will be updated when the body too is updated.
   * 
   * @deprecated
   * @param { Vector2} v The anchor arm
   * @returns {number}
   */
  setAnchor(v) {
    deprecate("Ball().setAnchor()")
    return 0
  }
  /**
   * Gets an anchor in its local space coordinate form.
   * Treat the returned value as read-only.
   * 
   * @deprecated
   * @param {number} index the position of the
   * @returns { Vector2}
   */
  getAnchor(index) {
    deprecate("Ball().getAnchor()")
    return new Vector2()
  }
  /**
   * Returns a rotated anchor relative to the body.
   * 
   * @deprecated
   * @param {number} index The position of the anchor.
   * @param {Vector2} [target] Vector2 to store results in.
   * @returns {Vector2}
   * @param {number} angle
   */
  getLocalAnchor(index,angle,target = new Vector2()) {
    deprecate("Ball().getLocalAnchor()")
    return target
  }
  /**
   * Calculates the bounds of the body
   * 
   * @param {BoundingBox} bound
   * @param {Body2D} body Body2D to calculate max and min from
   * @param {Number} padding increases the size of the bounds
   */
  static calculateBounds(body,bound,padding = 0) {
    let minX = Number.MAX_SAFE_INTEGER,
      minY = Number.MAX_SAFE_INTEGER,
      maxX = -Number.MAX_SAFE_INTEGER,
      maxY = -Number.MAX_SAFE_INTEGER

    const shape = body.shape
    if (shape.type == Shape.CIRCLE) {
      const position = shape.vertices[0]
      const radius = shape.vertices[1].x
      const idx = position.x - radius,
        idy = position.y - radius,
        mdx = position.x + radius,
        mdy = position.y + radius
      if (!minX || idx < minX) minX = idx
      if (!maxX || mdx > maxX) maxX = mdx
      if (!minY || idy < minY) minY = idy
      if (!maxY || mdy > maxY) maxY = mdy
    } else {
      for (let j = 0; j < shape.vertices.length; j++) {
        let vertex = shape.vertices[j]
        if (vertex.x < minX) minX = vertex.x
        if (vertex.x > maxX) maxX = vertex.x
        if (vertex.y < minY) minY = vertex.y
        if (vertex.y > maxY) maxY = vertex.y
      }
    }
    bound.min.x = minX - padding
    bound.max.x = maxX + padding
    bound.min.y = minY - padding
    bound.max.y = maxY + padding
  }
  /**
   * This updates the world coordinates of shape and bounds.
   * @param {Body2D} body
   * @param {Vector2} position
   * @param {number} orientation
   * @param {Vector2} scale
   * @param {BoundingBox} bounds
   */
  static update(body,position,orientation,scale,bounds) {
    Shape.update(body.shape,position,orientation,scale)
    if (body.autoUpdateBound)
      Body2D.calculateBounds(body,bounds)
  }
  /**
   * @param {Body2D} body
   * @param {number} mass
   */
  static setMass(body,mass) {
    body.inv_mass = mass === 0 ? 0 : 1 / mass
    Body2D.setInertia(body,Shape.calcInertia(body.shape,mass))
  }
  /**
   * @param {Body2D} body
   * @param {number} inertia
   */
  static setInertia(body,inertia) {
    body.inv_inertia = inertia === 0 ? 0 : 1 / inertia
  }
  /**
   * @param {Body2D} body
   * @param {number} type
   */
  static setType(body,type) {
    if (type !== Body2D.STATIC) return
    body.inv_mass = 0
    body.inv_inertia = 0
  }
  /**
   * @param {Body2D} body
   * @param {number} density
   */
  static setDensity(body,density) {
    const area = Shape.getArea(body.shape)
    Body2D.setMass(body,density * area)
  }
  /**
   *Body2D type that dictates a body cannot move nor respond to collisions.
   * 
   * @readonly
   * @type {number}
   */
  static STATIC = 0
  /**
   * Body2D type that dictates a body can move and respond to collisions.
   * 
   * @readonly
   * @type {number}
   */
  static DYNAMIC = 2
}

/**
 * Todo - Remove in version 1.0.0
 * @deprecated
 */
export class Body extends Body2D {
  /**
   * @inheritdoc
   * @param {Shape} shape
   */
  constructor(shape) {
    deprecate("Body()","Body2D()")
    super(shape)
  }
}