UNPKG

planck-js

Version:

2D physics engine for JavaScript/HTML5 game development

398 lines (343 loc) 11 kB
/* * 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; }