@box2d/debug-draw
Version:
Debug drawing helper for @box2d
369 lines (368 loc) • 13.2 kB
JavaScript
"use strict";
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.b2Contact = exports.b2ContactEdge = exports.b2MixRestitutionThreshold = exports.b2MixRestitution = exports.b2MixFriction = void 0;
// Copyright (c) 2019 Erin Catto
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
const b2_common_1 = require("../common/b2_common");
const b2_collision_1 = require("../collision/b2_collision");
/**
* Friction mixing law. The idea is to allow either fixture to drive the friction to zero.
* For example, anything slides on ice.
*/
function b2MixFriction(friction1, friction2) {
return Math.sqrt(friction1 * friction2);
}
exports.b2MixFriction = b2MixFriction;
/**
* Restitution mixing law. The idea is allow for anything to bounce off an inelastic surface.
* For example, a superball bounces on anything.
*/
function b2MixRestitution(restitution1, restitution2) {
return restitution1 > restitution2 ? restitution1 : restitution2;
}
exports.b2MixRestitution = b2MixRestitution;
/**
* Restitution mixing law. This picks the lowest value.
*/
function b2MixRestitutionThreshold(threshold1, threshold2) {
return threshold1 < threshold2 ? threshold1 : threshold2;
}
exports.b2MixRestitutionThreshold = b2MixRestitutionThreshold;
/**
* A contact edge is used to connect bodies and contacts together
* in a contact graph where each body is a node and each contact
* is an edge. A contact edge belongs to a doubly linked list
* maintained in each attached body. Each contact has two contact
* nodes, one for each attached body.
*/
class b2ContactEdge {
get other() {
(0, b2_common_1.b2Assert)(this.m_other !== null);
return this.m_other;
}
set other(value) {
(0, b2_common_1.b2Assert)(this.m_other === null);
this.m_other = value;
}
constructor(contact) {
/** Provides quick access to the other body attached. */
this.m_other = null;
/** The previous contact edge in the body's contact list */
this.prev = null;
/** The next contact edge in the body's contact list */
this.next = null;
this.contact = contact;
}
Reset() {
this.m_other = null;
this.prev = null;
this.next = null;
}
}
exports.b2ContactEdge = b2ContactEdge;
/**
* The class manages contact between two shapes. A contact exists for each overlapping
* AABB in the broad-phase (except if filtered). Therefore a contact object may exist
* that has no contact points.
*/
class b2Contact {
constructor() {
/**
* Used when crawling contact graph when forming islands.
*
* @internal protected
*/
this.m_islandFlag = false;
/**
* Set when the shapes are touching.
*
* @internal protected
*/
this.m_touchingFlag = false;
/**
* This contact can be disabled (by user)
*
* @internal protected
*/
this.m_enabledFlag = false;
/**
* This contact needs filtering because a fixture filter was changed.
*
* @internal protected
*/
this.m_filterFlag = false;
/**
* This bullet contact had a TOI event
*
* @internal protected
*/
this.m_bulletHitFlag = false;
/**
* This contact has a valid TOI in m_toi
*
* @internal protected
*/
this.m_toiFlag = false;
/**
* World pool and list pointers.
*
* @internal protected
*/
this.m_prev = null;
/** @internal protected */
this.m_next = null;
/**
* Nodes for connecting bodies.
*
* @internal protected
*/
this.m_nodeA = new b2ContactEdge(this);
/** @internal protected */
this.m_nodeB = new b2ContactEdge(this);
/** @internal protected */
this.m_indexA = 0;
/** @internal protected */
this.m_indexB = 0;
/** @internal protected */
this.m_manifold = new b2_collision_1.b2Manifold(); // TODO: readonly
/** @internal protected */
this.m_toiCount = 0;
/** @internal protected */
this.m_toi = 0;
/** @internal protected */
this.m_friction = 0;
/** @internal protected */
this.m_restitution = 0;
/** @internal protected */
this.m_restitutionThreshold = 0;
/** @internal protected */
this.m_tangentSpeed = 0;
this.m_oldManifold = new b2_collision_1.b2Manifold(); // TODO: readonly
}
/**
* Get the contact manifold.
* Do not modify the manifold unless you understand the internals of Box2D.
*/
GetManifold() {
return this.m_manifold;
}
/** Get the world manifold. */
GetWorldManifold(worldManifold) {
const bodyA = this.m_fixtureA.GetBody();
const bodyB = this.m_fixtureB.GetBody();
const shapeA = this.GetShapeA();
const shapeB = this.GetShapeB();
worldManifold.Initialize(this.m_manifold, bodyA.GetTransform(), shapeA.m_radius, bodyB.GetTransform(), shapeB.m_radius);
}
/** Is this contact touching? */
IsTouching() {
return this.m_touchingFlag;
}
/**
* Enable/disable this contact. This can be used inside the pre-solve
* contact listener. The contact is only disabled for the current
* time step (or sub-step in continuous collisions).
*/
SetEnabled(flag) {
this.m_enabledFlag = flag;
}
/** Has this contact been disabled? */
IsEnabled() {
return this.m_enabledFlag;
}
/** Get the next contact in the world's contact list. */
GetNext() {
return this.m_next;
}
/** Get fixture A in this contact. */
GetFixtureA() {
return this.m_fixtureA;
}
/** Get the child primitive index for fixture A. */
GetChildIndexA() {
return this.m_indexA;
}
/** Get fixture A in this contact. */
GetShapeA() {
return this.m_fixtureA.GetShape();
}
/** Get fixture B in this contact. */
GetFixtureB() {
return this.m_fixtureB;
}
/** Get the child primitive index for fixture B. */
GetChildIndexB() {
return this.m_indexB;
}
GetShapeB() {
return this.m_fixtureB.GetShape();
}
/**
* Flag this contact for filtering. Filtering will occur the next time step.
*
* @internal protected
*/
FlagForFiltering() {
this.m_filterFlag = true;
}
/**
* Override the default friction mixture.
* You can call this in b2ContactListener::PreSolve.
* This value persists until set or reset.
*/
SetFriction(friction) {
this.m_friction = friction;
}
/** Get the friction. */
GetFriction() {
return this.m_friction;
}
/** Reset the friction mixture to the default value. */
ResetFriction() {
this.m_friction = b2MixFriction(this.m_fixtureA.m_friction, this.m_fixtureB.m_friction);
}
/**
* Override the default restitution mixture.
* You can call this in b2ContactListener::PreSolve.
* The value persists until you set or reset.
*/
SetRestitution(restitution) {
this.m_restitution = restitution;
}
/** Get the restitution. */
GetRestitution() {
return this.m_restitution;
}
/** Reset the restitution to the default value. */
ResetRestitution() {
this.m_restitution = b2MixRestitution(this.m_fixtureA.m_restitution, this.m_fixtureB.m_restitution);
}
/**
* Override the default restitution velocity threshold mixture. You can call this in b2ContactListener::PreSolve.
* The value persists until you set or reset.
*/
SetRestitutionThreshold(threshold) {
this.m_restitutionThreshold = threshold;
}
/**
* Get the restitution threshold.
*/
GetRestitutionThreshold() {
return this.m_restitutionThreshold;
}
/**
* Reset the restitution threshold to the default value.
*/
ResetRestitutionThreshold() {
this.m_restitutionThreshold = b2MixRestitutionThreshold(this.m_fixtureA.m_restitutionThreshold, this.m_fixtureB.m_restitutionThreshold);
}
/** Set the desired tangent speed for a conveyor belt behavior. In meters per second. */
SetTangentSpeed(speed) {
this.m_tangentSpeed = speed;
}
/** Get the desired tangent speed. In meters per second. */
GetTangentSpeed() {
return this.m_tangentSpeed;
}
Reset(fixtureA, indexA, fixtureB, indexB) {
this.m_islandFlag = false;
this.m_touchingFlag = false;
this.m_enabledFlag = true;
this.m_filterFlag = false;
this.m_bulletHitFlag = false;
this.m_toiFlag = false;
this.m_fixtureA = fixtureA;
this.m_fixtureB = fixtureB;
this.m_indexA = indexA;
this.m_indexB = indexB;
this.m_manifold.pointCount = 0;
this.m_prev = null;
this.m_next = null;
this.m_nodeA.Reset();
this.m_nodeB.Reset();
this.m_toiCount = 0;
this.m_friction = b2MixFriction(this.m_fixtureA.m_friction, this.m_fixtureB.m_friction);
this.m_restitution = b2MixRestitution(this.m_fixtureA.m_restitution, this.m_fixtureB.m_restitution);
this.m_restitutionThreshold = b2MixRestitutionThreshold(this.m_fixtureA.m_restitutionThreshold, this.m_fixtureB.m_restitutionThreshold);
}
/**
* Update the contact manifold and touching status.
* Note: do not assume the fixture AABBs are overlapping or are valid.
*
* @internal protected
*/
Update(listener) {
const tManifold = this.m_oldManifold;
this.m_oldManifold = this.m_manifold;
this.m_manifold = tManifold;
// Re-enable this contact.
this.m_enabledFlag = true;
let touching = false;
const wasTouching = this.m_touchingFlag;
const sensorA = this.m_fixtureA.IsSensor();
const sensorB = this.m_fixtureB.IsSensor();
const sensor = sensorA || sensorB;
const bodyA = this.m_fixtureA.GetBody();
const bodyB = this.m_fixtureB.GetBody();
const xfA = bodyA.GetTransform();
const xfB = bodyB.GetTransform();
// Is this contact a sensor?
if (sensor) {
const shapeA = this.GetShapeA();
const shapeB = this.GetShapeB();
touching = (0, b2_collision_1.b2TestOverlap)(shapeA, this.m_indexA, shapeB, this.m_indexB, xfA, xfB);
// Sensors don't generate manifolds.
this.m_manifold.pointCount = 0;
}
else {
this.Evaluate(this.m_manifold, xfA, xfB);
touching = this.m_manifold.pointCount > 0;
// Match old contact ids to new contact ids and copy the
// stored impulses to warm start the solver.
for (let i = 0; i < this.m_manifold.pointCount; ++i) {
const mp2 = this.m_manifold.points[i];
mp2.normalImpulse = 0;
mp2.tangentImpulse = 0;
const id2 = mp2.id;
for (let j = 0; j < this.m_oldManifold.pointCount; ++j) {
const mp1 = this.m_oldManifold.points[j];
if (mp1.id.key === id2.key) {
mp2.normalImpulse = mp1.normalImpulse;
mp2.tangentImpulse = mp1.tangentImpulse;
break;
}
}
}
if (touching !== wasTouching) {
bodyA.SetAwake(true);
bodyB.SetAwake(true);
}
}
this.m_touchingFlag = touching;
if (!wasTouching && touching && listener) {
listener.BeginContact(this);
}
if (wasTouching && !touching && listener) {
listener.EndContact(this);
}
if (!sensor && touching && listener) {
listener.PreSolve(this, this.m_oldManifold);
}
}
}
exports.b2Contact = b2Contact;