planck-js
Version:
2D physics engine for JavaScript/HTML5 game development
398 lines (343 loc) • 11 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 = Fixture;
var options = require('./util/options');
var AABB = require('./collision/AABB');
var Vec2 = require('./common/Vec2');
/**
* 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 {Fixture~Filter} filter Contact filtering data
* @prop userData
*/
function FixtureDef() {
this.userData = null;
this.friction = 0.2;
this.restitution = 0.0;
this.density = 0.0;
this.isSensor = false;
this.filter = new Filter();
};
FixtureDef.DEFAULTS = new FixtureDef();
/**
* 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 {Shape} The shape will be cloned, so you can reuse it.
* @param {def} density The shape density (set to zero for static bodies).
*/
function Fixture(body, shape, def) {
if (typeof def === 'number') {
def = {
density : def
};
}
def = options(def, FixtureDef.DEFAULTS);
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_filter = def.filter;
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;
};
/**
* 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(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(0 <= childIndex && childIndex < this.m_proxyCount);
return this.m_proxies[childIndex].aabb;
}
/**
* These support body activation/deactivation.
*/
Fixture.prototype.CreateProxies = function(broadPhase, xf) {
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_filter = filter;
this.Refilter();
}
/**
* 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);
}
}
/**
* @class Fixture~Filter
*
* This holds contact filtering data.
*
* Fixtures with same positive groupIndex always collide and fixtures with same
* negative groupIndex never collide.
*
* 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 groupIndex Zero, positive or negative collision group.
* @prop categoryBits Collision category bit or bits that this fixture belongs
* to.
* @prop maskBits Collision category bit or bits that this fixture accept for
* collision.
*/
function Filter() {
// TODO use hashmap/strings instead of bits?
this.categoryBits = 0x0001;
this.maskBits = 0xFFFF;
this.groupIndex = 0;
};
/**
* 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} fixtureA
* @param {Fixture} fixtureB
*/
Fixture.prototype.ShouldCollide = function(that) {
var filterA = that.m_filter;
var filterB = this.m_filter;
if (!filterA || !filterB) {
return false;
}
if (filterA.groupIndex == filterB.groupIndex && filterA.groupIndex != 0) {
return filterA.groupIndex > 0;
}
var collide = (filterA.maskBits & filterB.categoryBits) != 0
&& (filterA.categoryBits & filterB.maskBits) != 0;
return collide;
}