planck-js
Version:
2D JavaScript physics engine for cross-platform HTML5 game development
462 lines (393 loc) • 13 kB
JavaScript
/*
* Planck.js
* The MIT License
* Copyright (c) 2021 Erin Catto, Ali Shakiba
*
* 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.
*/
var _DEBUG = typeof DEBUG === 'undefined' ? false : DEBUG;
var _ASSERT = typeof ASSERT === 'undefined' ? false : ASSERT;
module.exports = Fixture;
var common = require('./util/common');
var options = require('./util/options');
var Math = require('./common/Math');
var Vec2 = require('./common/Vec2');
var AABB = require('./collision/AABB');
var Shape = require('./Shape');
/**
* @typedef {Object} FixtureDef
*
* A fixture definition is used to create a fixture. This class defines an
* abstract fixture definition. You can reuse fixture definitions safely.
*
* @prop friction The friction coefficient, usually in the range [0,1]
* @prop restitution The restitution (elasticity) usually in the range [0,1]
* @prop density The density, usually in kg/m^2
* @prop isSensor A sensor shape collects contact information but never
* generates a collision response
* @prop userData
* @prop filterGroupIndex Zero, positive or negative collision group. Fixtures with same positive groupIndex always collide and fixtures with same
* negative groupIndex never collide.
* @prop filterCategoryBits Collision category bit or bits that this fixture belongs
* to. If groupIndex is zero or not matching, then at least one bit in this fixture
* categoryBits should match other fixture maskBits and vice versa.
* @prop filterMaskBits Collision category bit or bits that this fixture accept for
* collision.
*/
var FixtureDef = {
userData : null,
friction : 0.2,
restitution : 0.0,
density : 0.0,
isSensor : false,
filterGroupIndex : 0,
filterCategoryBits : 0x0001,
filterMaskBits : 0xFFFF
};
/**
* This proxy is used internally to connect shape children to the broad-phase.
*/
function FixtureProxy(fixture, childIndex) {
this.aabb = new AABB();
this.fixture = fixture;
this.childIndex = childIndex;
this.proxyId;
};
/**
* A fixture is used to attach a shape to a body for collision detection. A
* fixture inherits its transform from its parent. Fixtures hold additional
* non-geometric data such as friction, collision filters, etc. Fixtures are
* created via Body.createFixture.
*
* @param {Body} body
* @param {Shape|FixtureDef} shape Shape of fixture definition.
* @param {FixtureDef|number} def Fixture definition or number.
*/
function Fixture(body, shape, def) {
if (shape.shape) {
def = shape;
shape = shape.shape;
} else if (typeof def === 'number') {
def = {density : def};
}
def = options(def, FixtureDef);
this.m_body = body;
this.m_friction = def.friction;
this.m_restitution = def.restitution;
this.m_density = def.density;
this.m_isSensor = def.isSensor;
this.m_filterGroupIndex = def.filterGroupIndex;
this.m_filterCategoryBits = def.filterCategoryBits;
this.m_filterMaskBits = def.filterMaskBits;
// TODO validate shape
this.m_shape = shape; //.clone();
this.m_next = null;
this.m_proxies = [];
this.m_proxyCount = 0;
var childCount = this.m_shape.getChildCount();
for (var i = 0; i < childCount; ++i) {
this.m_proxies[i] = new FixtureProxy(this, i);
}
this.m_userData = def.userData;
};
/**
* Re-setup fixture.
* @private
*/
Fixture.prototype._reset = function() {
var body = this.getBody();
var broadPhase = body.m_world.m_broadPhase;
this.destroyProxies(broadPhase);
if (this.m_shape._reset) {
this.m_shape._reset();
}
var childCount = this.m_shape.getChildCount();
for (var i = 0; i < childCount; ++i) {
this.m_proxies[i] = new FixtureProxy(this, i);
}
this.createProxies(broadPhase, body.m_xf);
body.resetMassData();
};
Fixture.prototype._serialize = function() {
return {
friction: this.m_friction,
restitution: this.m_restitution,
density: this.m_density,
isSensor: this.m_isSensor,
filterGroupIndex: this.m_filterGroupIndex,
filterCategoryBits: this.m_filterCategoryBits,
filterMaskBits: this.m_filterMaskBits,
shape: this.m_shape,
};
};
Fixture._deserialize = function(data, body, restore) {
var shape = restore(Shape, data.shape);
var fixture = shape && new Fixture(body, shape, data);
return fixture;
};
/**
* Get the type of the child shape. You can use this to down cast to the
* concrete shape.
*/
Fixture.prototype.getType = function() {
return this.m_shape.getType();
}
/**
* Get the child shape. You can modify the child shape, however you should not
* change the number of vertices because this will crash some collision caching
* mechanisms. Manipulating the shape may lead to non-physical behavior.
*/
Fixture.prototype.getShape = function() {
return this.m_shape;
}
/**
* A sensor shape collects contact information but never generates a collision
* response.
*/
Fixture.prototype.isSensor = function() {
return this.m_isSensor;
}
/**
* Set if this fixture is a sensor.
*/
Fixture.prototype.setSensor = function(sensor) {
if (sensor != this.m_isSensor) {
this.m_body.setAwake(true);
this.m_isSensor = sensor;
}
}
/**
* Get the contact filtering data.
*/
// Fixture.prototype.getFilterData = function() {
// return this.m_filter;
// }
/**
* Get the user data that was assigned in the fixture definition. Use this to
* store your application specific data.
*/
Fixture.prototype.getUserData = function() {
return this.m_userData;
}
/**
* Set the user data. Use this to store your application specific data.
*/
Fixture.prototype.setUserData = function(data) {
this.m_userData = data;
}
/**
* Get the parent body of this fixture. This is null if the fixture is not
* attached.
*/
Fixture.prototype.getBody = function() {
return this.m_body;
}
/**
* Get the next fixture in the parent body's fixture list.
*/
Fixture.prototype.getNext = function() {
return this.m_next;
}
/**
* Get the density of this fixture.
*/
Fixture.prototype.getDensity = function() {
return this.m_density;
}
/**
* Set the density of this fixture. This will _not_ automatically adjust the
* mass of the body. You must call Body.resetMassData to update the body's mass.
*/
Fixture.prototype.setDensity = function(density) {
_ASSERT && common.assert(Math.isFinite(density) && density >= 0.0);
this.m_density = density;
}
/**
* Get the coefficient of friction, usually in the range [0,1].
*/
Fixture.prototype.getFriction = function() {
return this.m_friction;
}
/**
* Set the coefficient of friction. This will not change the friction of
* existing contacts.
*/
Fixture.prototype.setFriction = function(friction) {
this.m_friction = friction;
}
/**
* Get the coefficient of restitution.
*/
Fixture.prototype.getRestitution = function() {
return this.m_restitution;
}
/**
* Set the coefficient of restitution. This will not change the restitution of
* existing contacts.
*/
Fixture.prototype.setRestitution = function(restitution) {
this.m_restitution = restitution;
}
/**
* Test a point in world coordinates for containment in this fixture.
*/
Fixture.prototype.testPoint = function(p) {
return this.m_shape.testPoint(this.m_body.getTransform(), p);
}
/**
* Cast a ray against this shape.
*/
Fixture.prototype.rayCast = function(output, input, childIndex) {
return this.m_shape.rayCast(output, input, this.m_body.getTransform(), childIndex);
}
/**
* Get the mass data for this fixture. The mass data is based on the density and
* the shape. The rotational inertia is about the shape's origin. This operation
* may be expensive.
*/
Fixture.prototype.getMassData = function(massData) {
this.m_shape.computeMass(massData, this.m_density);
}
/**
* Get the fixture's AABB. This AABB may be enlarge and/or stale. If you need a
* more accurate AABB, compute it using the shape and the body transform.
*/
Fixture.prototype.getAABB = function(childIndex) {
_ASSERT && common.assert(0 <= childIndex && childIndex < this.m_proxyCount);
return this.m_proxies[childIndex].aabb;
}
/**
* These support body activation/deactivation.
*/
Fixture.prototype.createProxies = function(broadPhase, xf) {
_ASSERT && common.assert(this.m_proxyCount == 0);
// Create proxies in the broad-phase.
this.m_proxyCount = this.m_shape.getChildCount();
for (var i = 0; i < this.m_proxyCount; ++i) {
var proxy = this.m_proxies[i];
this.m_shape.computeAABB(proxy.aabb, xf, i);
proxy.proxyId = broadPhase.createProxy(proxy.aabb, proxy);
}
}
Fixture.prototype.destroyProxies = function(broadPhase) {
// Destroy proxies in the broad-phase.
for (var i = 0; i < this.m_proxyCount; ++i) {
var proxy = this.m_proxies[i];
broadPhase.destroyProxy(proxy.proxyId);
proxy.proxyId = null;
}
this.m_proxyCount = 0;
}
/**
* Updates this fixture proxy in broad-phase (with combined AABB of current and
* next transformation).
*/
Fixture.prototype.synchronize = function(broadPhase, xf1, xf2) {
for (var i = 0; i < this.m_proxyCount; ++i) {
var proxy = this.m_proxies[i];
// Compute an AABB that covers the swept shape (may miss some rotation
// effect).
var aabb1 = new AABB();
var aabb2 = new AABB();
this.m_shape.computeAABB(aabb1, xf1, proxy.childIndex);
this.m_shape.computeAABB(aabb2, xf2, proxy.childIndex);
proxy.aabb.combine(aabb1, aabb2);
var displacement = Vec2.sub(xf2.p, xf1.p);
broadPhase.moveProxy(proxy.proxyId, proxy.aabb, displacement);
}
}
/**
* Set the contact filtering data. This will not update contacts until the next
* time step when either parent body is active and awake. This automatically
* calls refilter.
*/
Fixture.prototype.setFilterData = function(filter) {
this.m_filterGroupIndex = filter.groupIndex;
this.m_filterCategoryBits = filter.categoryBits;
this.m_filterMaskBits = filter.maskBits;
this.refilter();
}
Fixture.prototype.getFilterGroupIndex = function() {
return this.m_filterGroupIndex;
}
Fixture.prototype.setFilterGroupIndex = function(groupIndex) {
return this.m_filterGroupIndex = groupIndex;
}
Fixture.prototype.getFilterCategoryBits = function() {
return this.m_filterCategoryBits;
}
Fixture.prototype.setFilterCategoryBits = function(categoryBits) {
this.m_filterCategoryBits = categoryBits;
}
Fixture.prototype.getFilterMaskBits = function() {
return this.m_filterMaskBits;
}
Fixture.prototype.setFilterMaskBits = function(maskBits) {
this.m_filterMaskBits = maskBits;
}
/**
* Call this if you want to establish collision that was previously disabled by
* ContactFilter.
*/
Fixture.prototype.refilter = function() {
if (this.m_body == null) {
return;
}
// Flag associated contacts for filtering.
var edge = this.m_body.getContactList();
while (edge) {
var contact = edge.contact;
var fixtureA = contact.getFixtureA();
var fixtureB = contact.getFixtureB();
if (fixtureA == this || fixtureB == this) {
contact.flagForFiltering();
}
edge = edge.next;
}
var world = this.m_body.getWorld();
if (world == null) {
return;
}
// Touch each proxy so that new pairs may be created
var broadPhase = world.m_broadPhase;
for (var i = 0; i < this.m_proxyCount; ++i) {
broadPhase.touchProxy(this.m_proxies[i].proxyId);
}
}
/**
* Implement this method to provide collision filtering, if you want finer
* control over contact creation.
*
* Return true if contact calculations should be performed between these two
* fixtures.
*
* Warning: for performance reasons this is only called when the AABBs begin to
* overlap.
*
* @param {Fixture} that
*/
Fixture.prototype.shouldCollide = function(that) {
if (that.m_filterGroupIndex === this.m_filterGroupIndex && that.m_filterGroupIndex !== 0) {
return that.m_filterGroupIndex > 0;
}
var collideA = (that.m_filterMaskBits & this.m_filterCategoryBits) !== 0;
var collideB = (that.m_filterCategoryBits & this.m_filterMaskBits) !== 0;
var collide = collideA && collideB;
return collide;
}