planck-js
Version:
2D physics engine for JavaScript/HTML5 game development
1,013 lines (865 loc) • 26.6 kB
JavaScript
/*
* Copyright (c) 2016 Ali Shakiba http://shakiba.me/planck.js
* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
module.exports = World;
var options = require('./util/options');
var Timer = require('./util/Timer');
var Vec2 = require('./common/Vec2');
var BroadPhase = require('./collision/BroadPhase');
var Solver = require('./Solver');
var Body = require('./Body');
var Contact = require('./Contact');
/**
* @prop {Vec2} [gravity = { x : 0, y : 0}]
* @prop {boolean} [allowSleep = true]
* @prop {boolean} [warmStarting = false]
* @prop {boolean} [continuousPhysics = false]
* @prop {boolean} [subStepping = false]
* @prop {boolean} [blockSolve = true]
* @prop {int} [velocityIterations = 8] For the velocity constraint solver.
* @prop {int} [positionIterations = 3] For the position constraint solver.
*/
function WorldDef() {
this.gravity = new Vec2();
this.allowSleep = true;
this.warmStarting = false;
this.continuousPhysics = true;
this.subStepping = true;
this.blockSolve = true;
this.velocityIterations = 8;
this.positionIterations = 3;
}
WorldDef.DEFAULTS = new WorldDef();
/**
* @param {WordDef|Vec2} def World definition or gravity vector.
*/
function World(def) {
if (!(this instanceof World)) {
return new World(def);
}
if (def && Vec2.IsValid(def)) {
def = {
gravity : def
};
}
def = options(def, WorldDef.DEFAULTS);
this.m_solver = new Solver(this);
this.m_broadPhase = new BroadPhase();
this.m_contactList = null;
this.m_contactCount = 0;
this.m_bodyList = null;
this.m_bodyCount = 0;
this.m_jointList = null;
this.m_jointCount = 0;
this.m_stepComplete = true;
this.m_allowSleep = def.allowSleep;
this.m_gravity = Vec2.Clone(def.gravity);
this.m_clearForces = true;
this.m_newFixture = false;
this.m_locked = false;
// These are for debugging the solver.
this.m_warmStarting = def.warmStarting;
this.m_continuousPhysics = def.continuousPhysics;
this.m_subStepping = def.subStepping;
this.m_blockSolve = def.blockSolve;
this.m_velocityIterations = def.velocityIterations;
this.m_positionIterations = def.positionIterations;
this.m_t = 0;
}
/**
* Get the world body list. With the returned body, use Body.GetNext to get the
* next body in the world list. A null body indicates the end of the list.
*
* @return the head of the world body list.
*/
World.prototype.GetBodyList = function() {
return this.m_bodyList;
}
/**
* Get the world joint list. With the returned joint, use Joint.GetNext to get
* the next joint in the world list. A null joint indicates the end of the list.
*
* @return the head of the world joint list.
*/
World.prototype.GetJointList = function() {
return this.m_jointList;
}
/**
* Get the world contact list. With the returned contact, use Contact.GetNext to
* get the next contact in the world list. A null contact indicates the end of
* the list.
*
* @return the head of the world contact list. Warning: contacts are created and
* destroyed in the middle of a time step. Use ContactListener to avoid
* missing contacts.
*/
World.prototype.GetContactList = function() {
return this.m_contactList;
}
World.prototype.GetBodyCount = function() {
return this.m_bodyCount;
}
World.prototype.GetJointCount = function() {
return this.m_jointCount;
}
/**
* Get the number of contacts (each may have 0 or more contact points).
*/
World.prototype.GetContactCount = function() {
return this.m_contactCount;
}
/**
* Change the global gravity vector.
*/
World.prototype.SetGravity = function(gravity) {
this.m_gravity = gravity;
}
/**
* Get the global gravity vector.
*/
World.prototype.GetGravity = function() {
return this.m_gravity;
}
/**
* Is the world locked (in the middle of a time step).
*/
World.prototype.IsLocked = function() {
return this.m_locked;
}
/**
* Enable/disable sleep.
*/
World.prototype.SetAllowSleeping = function(flag) {
if (flag == this.m_allowSleep) {
return;
}
this.m_allowSleep = flag;
if (this.m_allowSleep == false) {
for (var b = this.m_bodyList; b; b = b.m_next) {
b.SetAwake(true);
}
}
}
World.prototype.GetAllowSleeping = function() {
return this.m_allowSleep;
}
/**
* Enable/disable warm starting. For testing.
*/
World.prototype.SetWarmStarting = function(flag) {
this.m_warmStarting = flag;
}
World.prototype.GetWarmStarting = function() {
return this.m_warmStarting;
}
/**
* Enable/disable continuous physics. For testing.
*/
World.prototype.SetContinuousPhysics = function(flag) {
this.m_continuousPhysics = flag;
}
World.prototype.GetContinuousPhysics = function() {
return this.m_continuousPhysics;
}
/**
* Enable/disable single stepped continuous physics. For testing.
*/
World.prototype.SetSubStepping = function(flag) {
this.m_subStepping = flag;
}
World.prototype.GetSubStepping = function() {
return this.m_subStepping;
}
/**
* Set flag to control automatic clearing of forces after each time step.
*/
World.prototype.SetAutoClearForces = function(flag) {
this.m_clearForces = flag;
}
/**
* Get the flag that controls automatic clearing of forces after each time step.
*/
World.prototype.GetAutoClearForces = function() {
return this.m_clearForces;
}
/**
* Manually clear the force buffer on all bodies. By default, forces are cleared
* automatically after each call to Step. The default behavior is modified by
* calling SetAutoClearForces. The purpose of this function is to support
* sub-stepping. Sub-stepping is often used to maintain a fixed sized time step
* under a variable frame-rate. When you perform sub-stepping you will disable
* auto clearing of forces and instead call ClearForces after all sub-steps are
* complete in one pass of your game loop.
*
* @see SetAutoClearForces
*/
World.prototype.ClearForces = function() {
for (var body = this.m_bodyList; body; body = body.GetNext()) {
body.m_force.SetZero();
body.m_torque = 0.0;
}
}
function WorldQueryWrapper() {
this.broadPhase;
this.callback;
};
WorldQueryWrapper.prototype.QueryCallback = function(proxyId) {
var proxy = this.broadPhase.GetUserData(proxyId); // FixtureProxy
return this.callback.ReportFixture(proxy.fixture);
}
/**
* @function World~RayCastCallback
*
* @param fixture
*/
/**
* Query the world for all fixtures that potentially overlap the provided AABB.
*
* @param {World~QueryCallback} callback.QueryCallback Called for each fixture
* found in the query AABB. It may return `false` to terminate the
* query.
*
* @param aabb The query box.
*/
World.prototype.QueryAABB = function(callback, aabb) {
var wrapper = new WorldQueryWrapper(callback);
wrapper.broadPhase = this.m_broadPhase;
wrapper.callback = callback;
this.m_broadPhase.Query(wrapper, aabb);
}
function WorldRayCastWrapper() {
this.broadPhase;
this.callback;
};
WorldRayCastWrapper.prototype.RayCastCallback = function(input, proxyId) {
var proxy = this.broadPhase.GetUserData(proxyId); // FixtureProxy
var fixture = proxy.fixture;
var index = proxy.childIndex;
var output = new RayCastOutput();
var hit = fixture.RayCast(output, input, index);
if (hit) {
var fraction = output.fraction;
var point = Add(Mul((1.0 - fraction), input.p1), Mul(fraction, input.p2));
return this.callback.ReportFixture(fixture, point, output.normal, fraction);
}
return input.maxFraction;
}
/**
* @function World~RayCastCallback
*
* Callback class for ray casts. See World.RayCast
*
* Called for each fixture found in the query. You control how the ray cast
* proceeds by returning a float: return -1: ignore this fixture and continue
* return 0: terminate the ray cast return fraction: clip the ray to this point
* return 1: don't clip the ray and continue
*
* @param fixture The fixture hit by the ray
* @param point The point of initial intersection
* @param normal The normal vector at the point of intersection
* @param fraction
*
* @return {float} -1 to filter, 0 to terminate, fraction to clip the ray for
* closest hit, 1 to continue
*/
/**
*
* Ray-cast the world for all fixtures in the path of the ray. Your callback
* controls whether you get the closest point, any point, or n-points. The
* ray-cast ignores shapes that contain the starting point.
*
* @param {World~RayCastCallback} callback.ReportFixture A user implemented
* callback function.
* @param point1 The ray starting point
* @param point2 The ray ending point
*/
World.prototype.RayCast = function(callback, point1, point2) {
var wrapper = new WorldRayCastWrapper();
wrapper.broadPhase = this.m_broadPhase;
wrapper.callback = callback;
var input = new RayCastInput();
input.maxFraction = 1.0;
input.p1 = point1;
input.p2 = point2;
this.m_broadPhase.RayCast(wrapper, input);
}
/**
* Get the number of broad-phase proxies.
*/
World.prototype.GetProxyCount = function() {
return this.m_broadPhase.GetProxyCount();
}
/**
* Get the height of broad-phase dynamic tree.
*/
World.prototype.GetTreeHeight = function() {
return this.m_broadPhase.GetTreeHeight();
}
/**
* Get the balance of broad-phase dynamic tree.
*
* @returns {int}
*/
World.prototype.GetTreeBalance = function() {
return this.m_broadPhase.GetTreeBalance();
}
/**
* Get the quality metric of broad-phase dynamic tree. The smaller the better.
* The minimum is 1.
*
* @returns {float}
*/
World.prototype.GetTreeQuality = function() {
return this.m_broadPhase.GetTreeQuality();
}
/**
* Shift the world origin. Useful for large worlds. The body shift formula is:
* position -= newOrigin
*
* @param {Vec2} newOrigin The new origin with respect to the old origin
*/
World.prototype.ShiftOrigin = function(newOrigin) {
Assert(this.m_locked == false);
if (this.m_locked) {
return;
}
for (var b = this.m_bodyList; b; b = b.m_next) {
b.m_xf.p.Sub(newOrigin);
b.m_sweep.c0.Sub(newOrigin);
b.m_sweep.c.Sub(newOrigin);
}
for (var j = this.m_jointList; j; j = j.m_next) {
j.ShiftOrigin(newOrigin);
}
this.m_broadPhase.ShiftOrigin(newOrigin);
}
/**
* Joints and fixtures are destroyed when their associated body is destroyed.
* Register a destruction listener so that you may nullify references to these
* joints and shapes.
*
* `listener.SayGoodbye(object)` is called when any joint or fixture is about to
* be destroyed due to the destruction of one of its attached or parent bodies.
*/
// World.prototype.SetDestructionListener = function(listener) {
// }
/**
* Register a contact filter to provide specific control over collision.
* Otherwise the default filter is used (defaultFilter). The listener is owned
* by you and must remain in scope.
*
* @param {ContactFilter} filter
*/
// World.prototype.SetContactFilter = function(filter) {
// }
/**
* Register a contact event listener. The listener is owned by you and must
* remain in scope.
*
* @param {ContactFilter} listener
*/
// World.prototype.SetContactListener = function(listener) {
// }
/**
* Create a rigid body given a definition. No reference to the definition is
* retained.
*
* Warning: This function is locked during callbacks.
*
* @param {BodyDef|Vec2} def Body definition or position.
* @param {float} angle Body angle if def is position.
*/
World.prototype.CreateBody = function(def, angle) {
Assert(this.IsLocked() == false);
if (this.IsLocked()) {
return null;
}
if (def && Vec2.IsValid(def)) {
def = {
position : def,
angle : angle
};
}
var body = new Body(this, def);
// Add to world doubly linked list.
body.m_prev = null;
body.m_next = this.m_bodyList;
if (this.m_bodyList) {
this.m_bodyList.m_prev = body;
}
this.m_bodyList = body;
++this.m_bodyCount;
return body;
}
World.prototype.CreateDynamicBody = function(def, angle) {
if (!def) {
def = {
type : 'dynamic'
};
} else if (Vec2.IsValid(def)) {
def = {
type : 'dynamic',
position : def,
angle : angle
};
} else {
def.type = 'dynamic';
}
return this.CreateBody(def);
}
/**
* Destroy a rigid body given a definition. No reference to the definition is
* retained.
*
* Warning: This automatically deletes all associated shapes and joints.
*
* Warning: This function is locked during callbacks.
*
* @param {Body} b
*/
World.prototype.DestroyBody = function(b) {
Assert(this.m_bodyCount > 0);
Assert(this.IsLocked() == false);
if (this.IsLocked()) {
return;
}
// Delete the attached joints.
var je = b.m_jointList;
while (je) {
var je0 = je;
je = je.next;
this.SayGoodbye(je0.joint);
DestroyJoint(je0.joint);
b.m_jointList = je;
}
b.m_jointList = null;
// Delete the attached contacts.
var ce = b.m_contactList;
while (ce) {
var ce0 = ce;
ce = ce.next;
this.DestroyContact(ce0.contact);
}
b.m_contactList = null;
// Delete the attached fixtures. This destroys broad-phase proxies.
var /* Fixture */f = b.m_fixtureList;
while (f) {
var f0 = f;
f = f.m_next;
this.SayGoodbye(f0);
f0.DestroyProxies(this.m_broadPhase);
f0.Destroy();
b.m_fixtureList = f;
b.m_fixtureCount -= 1;
}
b.m_fixtureList = null;
b.m_fixtureCount = 0;
// Remove world body list.
if (b.m_prev) {
b.m_prev.m_next = b.m_next;
}
if (b.m_next) {
b.m_next.m_prev = b.m_prev;
}
if (b == this.m_bodyList) {
this.m_bodyList = b.m_next;
}
--this.m_bodyCount;
}
/**
* Create a joint to constrain bodies together. No reference to the definition
* is retained. This may cause the connected bodies to cease colliding.
*
* Warning: This function is locked during callbacks.
*
* @param {Joint} join
* @param {Body} bodyB
* @param {Body} bodyA
*/
World.prototype.CreateJoint = function(joint) {
Assert(!!joint.m_bodyA);
Assert(!!joint.m_bodyB);
Assert(this.IsLocked() == false);
if (this.IsLocked()) {
return null;
}
// Connect to the world list.
joint.m_prev = null;
joint.m_next = this.m_jointList;
if (this.m_jointList) {
this.m_jointList.m_prev = joint;
}
this.m_jointList = joint;
++this.m_jointCount;
// Connect to the bodies' doubly linked lists.
joint.m_edgeA.joint = joint;
joint.m_edgeA.other = joint.m_bodyB;
joint.m_edgeA.prev = null;
joint.m_edgeA.next = joint.m_bodyA.m_jointList;
if (joint.m_bodyA.m_jointList)
joint.m_bodyA.m_jointList.prev = joint.m_edgeA;
joint.m_bodyA.m_jointList = joint.m_edgeA;
joint.m_edgeB.joint = joint;
joint.m_edgeB.other = joint.m_bodyA;
joint.m_edgeB.prev = null;
joint.m_edgeB.next = joint.m_bodyB.m_jointList;
if (joint.m_bodyB.m_jointList)
joint.m_bodyB.m_jointList.prev = joint.m_edgeB;
joint.m_bodyB.m_jointList = joint.m_edgeB;
// If the joint prevents collisions, then flag any contacts for filtering.
if (joint.m_collideConnected == false) {
for (var edge = joint.m_bodyB.GetContactList(); edge; edge = edge.next) {
if (edge.other == joint.m_bodyA) {
// Flag the contact for filtering at the next time step (where either
// body is awake).
edge.contact.FlagForFiltering();
}
}
}
// Note: creating a joint doesn't wake the bodies.
return joint;
}
/**
* Destroy a joint. This may cause the connected bodies to begin colliding.
* Warning: This function is locked during callbacks.
*
* @param {Joint} join
*/
World.prototype.DestroyJoint = function(joint) {
Assert(this.IsLocked() == false);
if (this.IsLocked()) {
return;
}
// Remove from the doubly linked list.
if (joint.m_prev) {
joint.m_prev.m_next = joint.m_next;
}
if (joint.m_next) {
joint.m_next.m_prev = joint.m_prev;
}
if (joint == this.m_jointList) {
this.m_jointList = joint.m_next;
}
// Disconnect from bodies.
var bodyA = joint.m_bodyA;
var bodyB = joint.m_bodyB;
// Wake up connected bodies.
bodyA.SetAwake(true);
bodyB.SetAwake(true);
// Remove from body 1.
if (joint.m_edgeA.prev) {
joint.m_edgeA.prev.next = joint.m_edgeA.next;
}
if (joint.m_edgeA.next) {
joint.m_edgeA.next.prev = joint.m_edgeA.prev;
}
if (joint.m_edgeA == bodyA.m_jointList) {
bodyA.m_jointList = joint.m_edgeA.next;
}
joint.m_edgeA.prev = null;
joint.m_edgeA.next = null;
// Remove from body 2
if (joint.m_edgeB.prev) {
joint.m_edgeB.prev.next = joint.m_edgeB.next;
}
if (joint.m_edgeB.next) {
joint.m_edgeB.next.prev = joint.m_edgeB.prev;
}
if (joint.m_edgeB == bodyB.m_jointList) {
bodyB.m_jointList = joint.m_edgeB.next;
}
joint.m_edgeB.prev = null;
joint.m_edgeB.next = null;
Assert(this.m_jointCount > 0);
--this.m_jointCount;
// If the joint prevents collisions, then flag any contacts for filtering.
if (joint.m_collideConnected == false) {
var edge = bodyB.GetContactList();
while (edge) {
if (edge.other == bodyA) {
// Flag the contact for filtering at the next time step (where either
// body is awake).
edge.contact.FlagForFiltering();
}
edge = edge.next;
}
}
}
var s_step = new Solver.TimeStep(); // reuse
/**
* Take a time step. This performs collision detection, integration, and
* constraint solution.
*
* Broad-phase, narrow-phase, solve and solve time of impacts.
*
* @param {float} ts Time step, this should not vary.
* @param {float} dt Elapsed time, since last call.
*/
World.prototype.Step = function(ts, dt) {
if (typeof dt === 'number') {
this.m_t += dt;
while (this.m_t > ts) {
this.Step(ts);
this.m_t -= ts;
}
}
// If new fixtures were added, we need to find the new contacts.
if (this.m_newFixture) {
this.FindNewContacts();
this.m_newFixture = false;
}
this.m_locked = true;
s_step.Reset(ts);
s_step.velocityIterations = this.m_velocityIterations;
s_step.positionIterations = this.m_positionIterations;
s_step.warmStarting = this.m_warmStarting;
s_step.blockSolve = this.m_blockSolve;
// Update contacts. This is where some contacts are destroyed.
this.UpdateContacts();
// Integrate velocities, solve velocity constraints, and integrate positions.
if (this.m_stepComplete && ts > 0.0) {
this.m_solver.SolveWorld(s_step);
// Synchronize fixtures, check for out of range bodies.
for (var b = this.m_bodyList; b; b = b.GetNext()) {
// If a body was not in an island then it did not move.
if (b.m_islandFlag == false) {
continue;
}
if (b.IsStatic()) {
continue;
}
// Update fixtures (for broad-phase).
b.SynchronizeFixtures();
}
// Look for new contacts.
this.FindNewContacts();
}
// Handle TOI events.
if (this.m_continuousPhysics && ts > 0.0) {
this.m_solver.SolveWorldTOI(s_step);
}
if (this.m_clearForces) {
this.ClearForces();
}
this.m_locked = false;
}
/**
* Call this method to find new contacts.
*/
World.prototype.FindNewContacts = function() {
this.m_broadPhase.UpdatePairs(this);
}
/**
* Broad-phase callback.
*
* @private
*
* @param {FixtureProxy} proxyA
* @param {FixtureProxy} proxyB
*/
World.prototype.AddPair = function(proxyA, proxyB) {
return this.CreateContact(proxyA, proxyB);
}
/**
* @private
*
* @param {FixtureProxy} proxyA
* @param {FixtureProxy} proxyB
*/
World.prototype.CreateContact = function(proxyA, proxyB) {
var fixtureA = proxyA.fixture;
var fixtureB = proxyB.fixture;
var indexA = proxyA.childIndex;
var indexB = proxyB.childIndex;
var bodyA = fixtureA.GetBody();
var bodyB = fixtureB.GetBody();
// Are the fixtures on the same body?
if (bodyA == bodyB) {
return;
}
// TODO_ERIN use a hash table to remove a potential bottleneck when both
// bodies have a lot of contacts.
// Does a contact already exist?
var edge = bodyB.GetContactList(); // ContactEdge
while (edge) {
if (edge.other == bodyA) {
var fA = edge.contact.GetFixtureA();
var fB = edge.contact.GetFixtureB();
var iA = edge.contact.GetChildIndexA();
var iB = edge.contact.GetChildIndexB();
if (fA == fixtureA && fB == fixtureB && iA == indexA && iB == indexB) {
// A contact already exists.
return;
}
if (fA == fixtureB && fB == fixtureA && iA == indexB && iB == indexA) {
// A contact already exists.
return;
}
}
edge = edge.next;
}
if (bodyB.ShouldCollide(bodyA) == false) {
return;
}
if (fixtureB.ShouldCollide(fixtureA) == false) {
return;
}
// Call the factory.
var contact = Contact.Create(fixtureA, indexA, fixtureB, indexB);
if (contact == null) {
return;
}
// Insert into the world.
contact.m_prev = null;
if (this.m_contactList != null) {
contact.m_next = this.m_contactList;
this.m_contactList.m_prev = contact;
}
this.m_contactList = contact;
++this.m_contactCount;
}
/**
* Removes old non-overlapping contacts, applies filters and updates contacts.
*/
World.prototype.UpdateContacts = function() {
// Update awake contacts.
var c, next_c = this.m_contactList;
while (c = next_c) {
next_c = c.GetNext()
var fixtureA = c.GetFixtureA();
var fixtureB = c.GetFixtureB();
var indexA = c.GetChildIndexA();
var indexB = c.GetChildIndexB();
var bodyA = fixtureA.GetBody();
var bodyB = fixtureB.GetBody();
// Is this contact flagged for filtering?
if (c.m_filterFlag) {
if (bodyB.ShouldCollide(bodyA) == false) {
this.DestroyContact(c);
continue;
}
if (fixtureB.ShouldCollide(fixtureA) == false) {
this.DestroyContact(c);
continue;
}
// Clear the filtering flag.
c.m_filterFlag = false;
}
var activeA = bodyA.IsAwake() && !bodyA.IsStatic();
var activeB = bodyB.IsAwake() && !bodyB.IsStatic();
// At least one body must be awake and it must be dynamic or kinematic.
if (activeA == false && activeB == false) {
continue;
}
var proxyIdA = fixtureA.m_proxies[indexA].proxyId;
var proxyIdB = fixtureB.m_proxies[indexB].proxyId;
var overlap = this.m_broadPhase.TestOverlap(proxyIdA, proxyIdB);
// Here we destroy contacts that cease to overlap in the broad-phase.
if (overlap == false) {
this.DestroyContact(c);
continue;
}
// The contact persists.
c.Update(this);
}
}
/**
* @param {Contact} contact
*/
World.prototype.DestroyContact = function(contact) {
Contact.Destroy(contact, this);
// Remove from the world.
if (contact.m_prev) {
contact.m_prev.m_next = contact.m_next;
}
if (contact.m_next) {
contact.m_next.m_prev = contact.m_prev;
}
if (contact == this.m_contactList) {
this.m_contactList = contact.m_next;
}
--this.m_contactCount;
}
/**
* Implement contact callbacks to get contact information. You can use these
* results for things like sounds and game logic. You can also get contact
* results by traversing the contact lists after the time step. However, you
* might miss some contacts because continuous physics leads to sub-stepping.
* Additionally you may receive multiple callbacks for the same contact in a
* single time step. You should strive to make your callbacks efficient because
* there may be many callbacks per time step.
*/
/**
* Called when two fixtures begin to touch.
*
* Warning: You cannot create/destroy world entities inside these callbacks.
*
* @param {Contact} contact
*/
World.prototype.BeginContact = function(contact) {
};
/**
* Called when two fixtures cease to touch.
*
* Warning: You cannot create/destroy world entities inside these callbacks.
*
* @param {Contact} contact
*/
World.prototype.EndContact = function(contact) {
};
/**
* This is called after a contact is updated. This allows you to inspect a
* contact before it goes to the solver. If you are careful, you can modify the
* contact manifold (e.g. disable contact). A copy of the old manifold is
* provided so that you can detect changes. Note: this is called only for awake
* bodies. Note: this is called even when the number of contact points is zero.
* Note: this is not called for sensors. Note: if you set the number of contact
* points to zero, you will not get an EndContact callback. However, you may get
* a BeginContact callback the next step.
*
* Warning: You cannot create/destroy world entities inside these callbacks.
*
* @param {Contact} contact
* @param {Manifold} oldManifold
*/
World.prototype.PreSolve = function(contact, oldManifold) {
};
/**
* This lets you inspect a contact after the solver is finished. This is useful
* for inspecting impulses. Note: the contact manifold does not include time of
* impact impulses, which can be arbitrarily large if the sub-step is small.
* Hence the impulse is provided explicitly in a separate data structure. Note:
* this is only called for contacts that are touching, solid, and awake.
*
* Warning: You cannot create/destroy world entities inside these callbacks.
*
* @param {Contact} contact
* @param {ContactImpulse} impulse
*/
World.prototype.PostSolve = function(contact, impulse) {
};
/**
* Joints and fixtures are destroyed when their associated body is destroyed.
* Register a destruction listener so that you may nullify references to these
* joints and shapes.
*
* This method is called when any joint or fixture is about to be destroyed due
* to the destruction of one of its attached or parent bodies.
*/
World.prototype.SayGoodbye = function(object) {
};