physics/world/index.js

import { BoundingBox, Vector2 } from "../../math/index.js"
import { Broadphase, NaiveBroadphase } from "../broadphases/index.js"
import { SATNarrowphase, CollisionManifold, NarrowPhase } from "../narrowphase/index.js"
import { Settings } from "../settings.js"
import { deprecate } from "../../logger/index.js"
import { Body2D } from "../bodies/index.js"
import { Manager } from "../../ecs/index.js";
import { Movable } from "../../intergrator/movableComponent.js"
import { Transform } from "../../intergrator/index.js"

/**
 * Class responsible for updating bodies,constraints and composites.
 */
export class World2D {
  /**
   * The collision manifolds that have passed narrowphase and verified to be colliding.
   * 
   * @type {CollisionManifold<Entity>[]}
   */
  CLMDs = []
  /**
   * The collision manifolds that have passed broadphase and could be colliding
   * 
   * @deprecated
   * @type {CollisionPair[]}
   */
  contactList = []
  /**
   * The gravitational pull of the world.
   * 
   * @type {Vector2}
   */
  gravitationalAcceleration = new Vector2(0, 0)
  /**
   * Time in seconds that a single frame takes.This has more precedence than the first parameter of World2D.update(),set to this to zero if you want to use the latter as the delta time.
   * 
   * @type {number}
   */
  fixedFrameRate = Settings.fixedFrameRate
  /**
   * This is a cheap way of determining which pairs of bodies could be colliding.
   * 
   * @type {Broadphase}
   */
  broadphase
  /**
   * This accurately tests body pairs to check 
   * for collision and outputs a manifold for each body pair.
   * 
   * @type {NarrowPhase}
   */
  narrowphase
  constructor() {
    this.broadphase = new NaiveBroadphase()
    this.narrowphase = new SATNarrowphase()
  }

  /**
   * Gravitational pull of the world,will affect all bodies except static bodies.
   * 
   * @deprecated
   * @type { Vector2 }
   */
  get gravity() {
    return this.gravitationalAcceleration
  }

  set gravity(x) {
    if (typeof x === "object") {
      Vector2.copy(x, this.gravitationalAcceleration)
    } else {
      Vector2.set(this.gravitationalAcceleration, 0, x)
    }
  }

  /**
   * 
   * @param {any} manager
   * @param {World2D} world
   * @param {CollisionPair[]} contactList
   */
  static narrowPhase(manager, world, contactList) {
    return world.narrowphase.getCollisionPairs(manager, contactList)

  }
  /**
   * 
   * @param {World2D} world
   */
  static broadPhase(world) {
    return world.broadphase.getCollisionPairs([])
  }
  /**
   * 
   * @param {any} manager
   * @param {World2D} world
   */
  static collisionDetection(manager, world) {
    world.contactList = World2D.broadPhase(world)
    world.CLMDs = World2D.narrowPhase(manager, world, world.contactList)
  }
  /**
   * 
   * @param {number} dt
   * @param {Manager} manager
   * @param {World2D} world
   * @param {string | any[]} CLMDs
   */
  static collisionResponse(manager, world, CLMDs, dt) {
    const inv_dt = 1 / dt

    for (let i = 0; i < CLMDs.length; i++) {
      const manifold = CLMDs[i]
      const [transformA, movableA, bodyA] = manager.get(manifold.entityA, "transform", "movable", "body")
      const [transformB, movableB, bodyB] = manager.get(manifold.entityB, "transform", "movable", "body")

      if (Settings.warmStarting)
        CollisionManifold.warmstart(
          manifold,
          movableA,
          movableB,
          bodyA,
          bodyB
        )
      CollisionManifold.prepare(
        manifold,
        bodyA,
        bodyB,
        movableA,
        movableB,
        transformA.position,
        transformB.position,
        inv_dt
      )
    }
    for (let i = 0; i < Settings.velocitySolverIterations; i++) {
      for (let i = 0; i < CLMDs.length; i++) {
        const manifold = CLMDs[i]
        const [movableA, bodyA] = manager.get(manifold.entityA, "movable", "body")
        const [movableB, bodyB] = manager.get(manifold.entityB, "movable", "body")

        CollisionManifold.solve(
          manifold,
          movableA,
          movableB,
          bodyA,
          bodyB
        )
      }
    } /***/
  }
  /**
   * 
   * @param {Vector2} gravity
   * @param {Movable[][]} movable
   * @param {Body2D[][]} bodies
   */
  static applyGravity(gravity, movable, bodies) {
    for (var i = 0; i < bodies.length; i++) {
      for (let j = 0; j < bodies[i].length; j++) {
        if (bodies[i][j].inv_mass)
          Vector2.add(
            movable[i][j].acceleration,
            gravity,
            movable[i][j].acceleration
          )
      }
    }
  }
  /**
   * 
   * @param {Body2D[][]} bodies
   * @param {Transform[][]} transform
   * @param {BoundingBox[][]} bounds
   */
  static updateBodies(transform, bounds, bodies) {
    for (let i = 0; i < bodies.length; i++) {
      for (let j = 0; j < bodies[i].length; j++) {
        Body2D.update(
          bodies[i][j],
          transform[i][j].position,
          transform[i][j].orientation,
          transform[i][j].scale,
          bounds[i][j]
        )
      }
    }
  }
  /**
   * @param {World2D} world
   * @param {Body2D[][]} bodies
   * @param {Number} dt the time passed between the last call and this call.
   * @param {Manager} manager
   * @param {Entity[][]} entities
   * @param {Transform[][]} transform
   * @param {Movable[][]} movable
   * @param {BoundingBox[][]} bounds
   */
  static update(manager, world, entities, transform, movable, bounds, bodies, dt) {
    /** @type {CollisionManifold<Entity>[]}*/
    this.CLMDs = []
    World2D.applyGravity(world.gravitationalAcceleration, movable, bodies)
    World2D.updateBodies(transform, bounds, bodies)
    world.broadphase.update(entities, bounds)
    World2D.collisionDetection(manager, world)
    World2D.collisionResponse(manager, world, world.CLMDs, dt)
  }

  /**
   * Searches for objects in a given bounds and returns them.
   * 
   * @template {Entity} T
   * @param {Bounds} bound the region to search in
   * @param {T[]} [out = []] an array to store results in
   * @returns {T[]}
   */
  query(bound, out = []) {
    this.broadphase.query(bound, out)
    return out
  }
}

/**
 * Todo - Remove in version 1.0.0
 * @deprecated
 */
export class World extends World2D {
  /**
   * @inheritdoc
   */
  constructor() {
    deprecate("World()", "World2D()")
    // @ts-ignore
    super(...arguments)
  }
}