diff --git a/demo/dev.html b/demo/dev.html index 7f95753b..de17387e 100644 --- a/demo/dev.html +++ b/demo/dev.html @@ -61,10 +61,11 @@

Matter.js Demo (Dev. Build)

+
- \ No newline at end of file + diff --git a/demo/js/Demo.js b/demo/js/Demo.js index 85b13851..bd3bf943 100644 --- a/demo/js/Demo.js +++ b/demo/js/Demo.js @@ -448,12 +448,12 @@ Demo.chains = function() { var _world = _engine.world, - groupId = Body.nextGroupId(); + groupId = Body.nextNonCollidingGroupId(); Demo.reset(); var ropeA = Composites.stack(200, 100, 5, 2, 10, 10, function(x, y, column, row) { - return Bodies.rectangle(x, y, 50, 20, { groupId: groupId }); + return Bodies.rectangle(x, y, 50, 20, { collisionFilter: {group: groupId} }); }); Composites.chain(ropeA, 0.5, 0, -0.5, 0, { stiffness: 0.8, length: 2 }); @@ -466,10 +466,10 @@ World.add(_world, ropeA); - groupId = Body.nextGroupId(); + groupId = Body.nextNonCollidingGroupId(); var ropeB = Composites.stack(500, 100, 5, 2, 10, 10, function(x, y, column, row) { - return Bodies.circle(x, y, 20, { groupId: groupId }); + return Bodies.circle(x, y, 20, { collisionFilter: {group: groupId} }); }); Composites.chain(ropeB, 0.5, 0, -0.5, 0, { stiffness: 0.8, length: 2 }); @@ -485,12 +485,12 @@ Demo.bridge = function() { var _world = _engine.world, - groupId = Body.nextGroupId(); + groupId = Body.nextNonCollidingGroupId(); Demo.reset(); var bridge = Composites.stack(150, 300, 9, 1, 10, 10, function(x, y, column, row) { - return Bodies.rectangle(x, y, 50, 20, { groupId: groupId }); + return Bodies.rectangle(x, y, 50, 20, { collisionFilter: {group: groupId} }); }); Composites.chain(bridge, 0.5, 0, -0.5, 0, { stiffness: 0.9 }); @@ -937,8 +937,8 @@ Demo.reset(); - var groupId = Body.nextGroupId(), - particleOptions = { friction: 0.00001, groupId: groupId, render: { visible: false }}, + var groupId = Body.nextNonCollidingGroupId(), + particleOptions = { friction: 0.00001, collisionFilter: { group: groupId }, render: { visible: false }}, cloth = Composites.softBody(200, 200, 20, 12, 5, 5, false, 8, particleOptions); for (var i = 0; i < 20; i++) { @@ -1231,6 +1231,44 @@ var renderOptions = _engine.render.options; }; + Demo.collisionFiltering = function() { + var _world = _engine.world; + + Demo.reset(); + + var i; + + World.add(_world, + Composites.stack(275, 150, 5, 10, 10, 10, function(x, y, column, row) { + return Bodies.circle(x, y, 20, { + collisionFilter: { + category: row < 7 ? 2 : 4 + }, + render: { + strokeStyle: row < 7 ? 'red' : 'green', + fillStyle: 'transparent' + } + }); + }) + ); + + World.add(_world, + Bodies.circle(400, 40, 30, { + collisionFilter: { + mask: 5 + }, + render: { + fillStyle: 'blue' + } + }) + ); + + var renderOptions = _engine.render.options; + renderOptions.wireframes = false; + renderOptions.background = '#111'; + renderOptions.showCollisions = true; + }; + // the functions for the demo interface and controls below Demo.initControls = function() { @@ -1400,4 +1438,4 @@ renderOptions.showDebug = true; }; -})(); \ No newline at end of file +})(); diff --git a/src/body/Body.js b/src/body/Body.js index 5bd9578d..c7831d98 100644 --- a/src/body/Body.js +++ b/src/body/Body.js @@ -15,7 +15,8 @@ var Body = {}; Body._inertiaScale = 4; - var _nextGroupId = 1; + var _nextCollidingGroupId = 1, + _nextNonCollidingGroupId = -1; /** * Creates a new rigid body model. The options parameter is an object that specifies any properties you wish to override the defaults. @@ -49,7 +50,11 @@ var Body = {}; restitution: 0, friction: 0.1, frictionAir: 0.01, - groupId: 0, + collisionFilter: { + category: 1, + mask: 0xFFFFFFFF, + group: 0 + }, slop: 0.05, timeScale: 1, render: { @@ -70,12 +75,21 @@ var Body = {}; }; /** - * Returns the next unique groupID number. - * @method nextGroupId + * Returns the next unique groupID number for which bodies will collide. + * @method nextCollidingGroupId * @return {Number} Unique groupID */ - Body.nextGroupId = function() { - return _nextGroupId++; + Body.nextCollidingGroupId = function() { + return _nextCollidingGroupId++; + }; + + /** + * Returns the next collisionFilter.group value for which bodies will not collide. + * @method nextNonCollidingGroupId + * @return {Number} Unique groupID + */ + Body.nextNonCollidingGroupId = function() { + return _nextNonCollidingGroupId--; }; /** @@ -672,18 +686,56 @@ var Body = {}; */ /** - * An integer `Number` that specifies the collision group the body belongs to. - * Bodies with the same `groupId` are considered _as-one_ body and therefore do not interact. - * This allows for creation of segmented bodies that can self-intersect, such as a rope. - * The default value 0 means the body does not belong to a group, and can interact with all other bodies. + * An `Object` that specifies the collision filtering properties of this body. * - * @property groupId - * @type number + * Collisions between two bodies will obey the following rules: + * - If the two bodies have the same non-zero value of `collisionFilter.group`, + * they will always collide if the value is positive, and they will never collide + * if the value is negative. + * - If the two bodies have different values of `collisionFilter.group` or if one + * (or both) of the bodies has a value of 0, then the category/mask rules apply as follows: + * + * Each body belongs to a collision category, given by `collisionFilter.category`. This + * value is used as a bit field and the category should have only one bit set, meaning that + * the value of this property is a power of two in the range [1, 2^31]. Thus, there are 32 + * different collision categories available. + * Each body also defines a collision bitmask, given by `collisionFilter.mask` which specifies + * the categories it collides with (the value is the bitwise AND value of all these categories). + * + * Using the category/mask rules, two bodies `A` and `B` collide if each includes the other's + * category in its mask, i.e. `(categoryA & maskB) !== 0` and `(categoryB & maskA) !== 0` + * are both true. + * + * @property collisionFilter + * @type object + */ + + /** + * An Integer `Number`, see `collisionFilter` + * + * @property collisionFilter.group + * @type object * @default 0 */ /** - * A `Number` that specifies a tollerance on how far a body is allowed to 'sink' or rotate into other bodies. + * An Integer `Number`, see `collisionFilter` + * + * @property collisionFilter.category + * @type object + * @default 1 + */ + + /** + * An Integer `Number`, see `collisionFilter` + * + * @property collisionFilter.mask + * @type object + * @default -1 + */ + + /** + * A `Number` that specifies a tolerance on how far a body is allowed to 'sink' or rotate into other bodies. * Avoid changing this value unless you understand the purpose of `slop` in physics engines. * The default should generally suffice, although very large bodies may require larger values for stable stacking. * @@ -797,4 +849,4 @@ var Body = {}; * @type bounds */ -})(); \ No newline at end of file +})(); diff --git a/src/collision/Detector.js b/src/collision/Detector.js index e50a8eb6..879308e7 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -26,13 +26,14 @@ var Detector = {}; var bodyA = broadphasePairs[i][0], bodyB = broadphasePairs[i][1]; - // NOTE: could share a function for the below, but may drop performance? - - if (bodyA.groupId && bodyB.groupId && bodyA.groupId === bodyB.groupId) - continue; + var collisionFilterA = bodyA.collisionFilter, + collisionFilterB = bodyB.collisionFilter; if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping)) continue; + + if (!_pairCollides(collisionFilterA, collisionFilterB)) + continue; metrics.midphaseTests += 1; @@ -127,5 +128,12 @@ var Detector = {}; return collisions; }; + + var _pairCollides = function(filterA, filterB) { + if (filterA.group === filterB.group && filterA.group !== 0) + return filterA.group > 0; + + return ((filterA.mask & filterB.category) !== 0 && (filterB.mask & filterA.category) !== 0); + }; -})(); \ No newline at end of file +})(); diff --git a/src/factory/Composites.js b/src/factory/Composites.js index 67ace4b5..688d835f 100644 --- a/src/factory/Composites.js +++ b/src/factory/Composites.js @@ -226,7 +226,7 @@ var Composites = {}; * @return {composite} A new composite car body */ Composites.car = function(xx, yy, width, height, wheelSize) { - var groupId = Body.nextGroupId(), + var collisionFilterGroup = Body.nextNonCollidingGroupId(), wheelBase = -20, wheelAOffset = -width * 0.5 + wheelBase, wheelBOffset = width * 0.5 - wheelBase, @@ -234,7 +234,9 @@ var Composites = {}; var car = Composite.create({ label: 'Car' }), body = Bodies.trapezoid(xx, yy, width, height, 0.3, { - groupId: groupId, + collisionFilter: { + group: collisionFilterGroup + }, friction: 0.01, chamfer: { radius: 10 @@ -242,14 +244,18 @@ var Composites = {}; }); var wheelA = Bodies.circle(xx + wheelAOffset, yy + wheelYOffset, wheelSize, { - groupId: groupId, + collisionFilter: { + group: collisionFilterGroup + }, restitution: 0.5, friction: 0.9, density: 0.01 }); var wheelB = Bodies.circle(xx + wheelBOffset, yy + wheelYOffset, wheelSize, { - groupId: groupId, + collisionFilter: { + group: collisionFilterGroup + }, restitution: 0.5, friction: 0.9, density: 0.01 @@ -308,4 +314,4 @@ var Composites = {}; return softBody; }; -})(); \ No newline at end of file +})();