UNPKG

planck-js

Version:

2D JavaScript/TypeScript physics engine for cross-platform HTML5 game development

212 lines (178 loc) 6.27 kB
/* * Planck.js * * Copyright (c) Erin Catto, Ali Shakiba * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import { Vec2Value } from "../common/Vec2"; import { AABB, AABBValue, RayCastCallback, RayCastInput } from "./AABB"; import { DynamicTree, DynamicTreeQueryCallback } from "./DynamicTree"; import { FixtureProxy } from "../dynamics/Fixture"; /** @internal */ const _ASSERT = typeof ASSERT === "undefined" ? false : ASSERT; /** @internal */ const math_max = Math.max; /** @internal */ const math_min = Math.min; /** * The broad-phase wraps and extends a dynamic-tree to keep track of moved * objects and query them on update. */ export class BroadPhase { m_tree: DynamicTree<FixtureProxy> = new DynamicTree<FixtureProxy>(); m_moveBuffer: number[] = []; m_callback: (userDataA: any, userDataB: any) => void; m_queryProxyId: number; /** * Get user data from a proxy. Returns null if the id is invalid. */ getUserData(proxyId: number): FixtureProxy { return this.m_tree.getUserData(proxyId); } /** * Test overlap of fat AABBs. */ testOverlap(proxyIdA: number, proxyIdB: number): boolean { const aabbA = this.m_tree.getFatAABB(proxyIdA); const aabbB = this.m_tree.getFatAABB(proxyIdB); return AABB.testOverlap(aabbA, aabbB); } /** * Get the fat AABB for a proxy. */ getFatAABB(proxyId: number): AABB { return this.m_tree.getFatAABB(proxyId); } /** * Get the number of proxies. */ getProxyCount(): number { return this.m_moveBuffer.length; } /** * Get the height of the embedded tree. */ getTreeHeight(): number { return this.m_tree.getHeight(); } /** * Get the balance (integer) of the embedded tree. */ getTreeBalance(): number { return this.m_tree.getMaxBalance(); } /** * Get the quality metric of the embedded tree. */ getTreeQuality(): number { return this.m_tree.getAreaRatio(); } /** * Query an AABB for overlapping proxies. The callback class is called for each * proxy that overlaps the supplied AABB. */ query = (aabb: AABBValue, queryCallback: DynamicTreeQueryCallback): void => { this.m_tree.query(aabb, queryCallback); }; /** * Ray-cast against the proxies in the tree. This relies on the callback to * perform a exact ray-cast in the case were the proxy contains a shape. The * callback also performs the any collision filtering. This has performance * roughly equal to k * log(n), where k is the number of collisions and n is the * number of proxies in the tree. * * @param input The ray-cast input data. The ray extends from `p1` to `p1 + maxFraction * (p2 - p1)`. * @param rayCastCallback A function that is called for each proxy that is hit by the ray. If the return value is a positive number it will update the maxFraction of the ray cast input, and if it is zero it will terminate they ray cast. */ rayCast(input: RayCastInput, rayCastCallback: RayCastCallback): void { this.m_tree.rayCast(input, rayCastCallback); } /** * Shift the world origin. Useful for large worlds. The shift formula is: * position -= newOrigin * * @param newOrigin The new origin with respect to the old origin */ shiftOrigin(newOrigin: Vec2Value): void { this.m_tree.shiftOrigin(newOrigin); } /** * Create a proxy with an initial AABB. Pairs are not reported until UpdatePairs * is called. */ createProxy(aabb: AABBValue, userData: FixtureProxy): number { if (_ASSERT) console.assert(AABB.isValid(aabb)); const proxyId = this.m_tree.createProxy(aabb, userData); this.bufferMove(proxyId); return proxyId; } /** * Destroy a proxy. It is up to the client to remove any pairs. */ destroyProxy(proxyId: number): void { this.unbufferMove(proxyId); this.m_tree.destroyProxy(proxyId); } /** * Call moveProxy as many times as you like, then when you are done call * UpdatePairs to finalized the proxy pairs (for your time step). */ moveProxy(proxyId: number, aabb: AABB, displacement: Vec2Value): void { if (_ASSERT) console.assert(AABB.isValid(aabb)); const changed = this.m_tree.moveProxy(proxyId, aabb, displacement); if (changed) { this.bufferMove(proxyId); } } /** * Call to trigger a re-processing of it's pairs on the next call to * UpdatePairs. */ touchProxy(proxyId: number): void { this.bufferMove(proxyId); } bufferMove(proxyId: number): void { this.m_moveBuffer.push(proxyId); } unbufferMove(proxyId: number): void { for (let i = 0; i < this.m_moveBuffer.length; ++i) { if (this.m_moveBuffer[i] === proxyId) { this.m_moveBuffer[i] = null; } } } /** * Update the pairs. This results in pair callbacks. This can only add pairs. */ updatePairs(addPairCallback: (userDataA: FixtureProxy, userDataB: FixtureProxy) => void): void { if (_ASSERT) console.assert(typeof addPairCallback === "function"); this.m_callback = addPairCallback; // Perform tree queries for all moving proxies. while (this.m_moveBuffer.length > 0) { this.m_queryProxyId = this.m_moveBuffer.pop(); if (this.m_queryProxyId === null) { continue; } // We have to query the tree with the fat AABB so that // we don't fail to create a pair that may touch later. const fatAABB = this.m_tree.getFatAABB(this.m_queryProxyId); // Query tree, create pairs and add them pair buffer. this.m_tree.query(fatAABB, this.queryCallback); } // Try to keep the tree balanced. // this.m_tree.rebalance(4); } queryCallback = (proxyId: number): boolean => { // A proxy cannot form a pair with itself. if (proxyId === this.m_queryProxyId) { return true; } const proxyIdA = math_min(proxyId, this.m_queryProxyId); const proxyIdB = math_max(proxyId, this.m_queryProxyId); // TODO: Skip any duplicate pairs. const userDataA = this.m_tree.getUserData(proxyIdA); const userDataB = this.m_tree.getUserData(proxyIdB); // Send the pairs back to the client. this.m_callback(userDataA, userDataB); return true; }; }