From 5060c7626acff8a7c20226916352c6557d34c3f0 Mon Sep 17 00:00:00 2001 From: liabru Date: Tue, 29 Jul 2014 13:14:31 +0100 Subject: [PATCH] tweaks to collision filtering --- demo/dev.html | 2 +- demo/js/Demo.js | 83 ++++++++++++++++++++++++++++++++------- src/body/Body.js | 35 +++++++++-------- src/collision/Detector.js | 25 +++++++----- src/factory/Composites.js | 8 ++-- 5 files changed, 106 insertions(+), 47 deletions(-) diff --git a/demo/dev.html b/demo/dev.html index de17387..63bcb67 100644 --- a/demo/dev.html +++ b/demo/dev.html @@ -49,6 +49,7 @@ + @@ -61,7 +62,6 @@ - diff --git a/demo/js/Demo.js b/demo/js/Demo.js index bd3bf94..fc0304d 100644 --- a/demo/js/Demo.js +++ b/demo/js/Demo.js @@ -448,12 +448,12 @@ Demo.chains = function() { var _world = _engine.world, - groupId = Body.nextNonCollidingGroupId(); + group = Body.nextGroup(true); Demo.reset(); var ropeA = Composites.stack(200, 100, 5, 2, 10, 10, function(x, y, column, row) { - return Bodies.rectangle(x, y, 50, 20, { collisionFilter: {group: groupId} }); + return Bodies.rectangle(x, y, 50, 20, { collisionFilter: { group: group } }); }); Composites.chain(ropeA, 0.5, 0, -0.5, 0, { stiffness: 0.8, length: 2 }); @@ -466,10 +466,10 @@ World.add(_world, ropeA); - groupId = Body.nextNonCollidingGroupId(); + group = Body.nextGroup(true); var ropeB = Composites.stack(500, 100, 5, 2, 10, 10, function(x, y, column, row) { - return Bodies.circle(x, y, 20, { collisionFilter: {group: groupId} }); + return Bodies.circle(x, y, 20, { collisionFilter: { group: group } }); }); 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.nextNonCollidingGroupId(); + group = Body.nextGroup(true); Demo.reset(); var bridge = Composites.stack(150, 300, 9, 1, 10, 10, function(x, y, column, row) { - return Bodies.rectangle(x, y, 50, 20, { collisionFilter: {group: groupId} }); + return Bodies.rectangle(x, y, 50, 20, { collisionFilter: { group: group } }); }); Composites.chain(bridge, 0.5, 0, -0.5, 0, { stiffness: 0.9 }); @@ -937,8 +937,8 @@ Demo.reset(); - var groupId = Body.nextNonCollidingGroupId(), - particleOptions = { friction: 0.00001, collisionFilter: { group: groupId }, render: { visible: false }}, + var group = Body.nextGroup(true), + particleOptions = { friction: 0.00001, collisionFilter: { group: group }, render: { visible: false }}, cloth = Composites.softBody(200, 200, 20, 12, 5, 5, false, 8, particleOptions); for (var i = 0; i < 20; i++) { @@ -1234,39 +1234,92 @@ Demo.collisionFiltering = function() { var _world = _engine.world; + // define our categories (as bit fields, there are up to 32 available) + + var defaultCategory = 0x0001, + redCategory = 0x0002, + greenCategory = 0x0004, + blueCategory = 0x0008; + + var redColor = '#C44D58', + blueColor = '#4ECDC4', + greenColor = '#C7F464'; + Demo.reset(); - var i; + // create a stack with varying body categories (but these bodies can all collide with each other) World.add(_world, Composites.stack(275, 150, 5, 10, 10, 10, function(x, y, column, row) { + var category = redCategory, + color = redColor; + + if (row > 5) { + category = blueCategory; + color = blueColor; + } else if (row > 2) { + category = greenCategory; + color = greenColor; + } + return Bodies.circle(x, y, 20, { collisionFilter: { - category: row < 7 ? 2 : 4 + category: category }, render: { - strokeStyle: row < 7 ? 'red' : 'green', + strokeStyle: color, fillStyle: 'transparent' } }); }) ); + // this body will only collide with the walls and the green bodies + + World.add(_world, + Bodies.circle(310, 40, 30, { + collisionFilter: { + mask: defaultCategory | greenCategory + }, + render: { + strokeStyle: Common.shadeColor(greenColor, -20), + fillStyle: greenColor + } + }) + ); + + // this body will only collide with the walls and the red bodies + World.add(_world, Bodies.circle(400, 40, 30, { collisionFilter: { - mask: 5 + mask: defaultCategory | redCategory }, render: { - fillStyle: 'blue' + strokeStyle: Common.shadeColor(redColor, -20), + fillStyle: redColor + } + }) + ); + + // this body will only collide with the walls and the blue bodies + + World.add(_world, + Bodies.circle(480, 40, 30, { + collisionFilter: { + mask: defaultCategory | blueCategory + }, + render: { + strokeStyle: Common.shadeColor(blueColor, -20), + fillStyle: blueColor } }) ); var renderOptions = _engine.render.options; renderOptions.wireframes = false; - renderOptions.background = '#111'; - renderOptions.showCollisions = true; + renderOptions.background = '#222'; + renderOptions.showAngleIndicator = false; }; // the functions for the demo interface and controls below diff --git a/src/body/Body.js b/src/body/Body.js index c7831d9..4cbe2ff 100644 --- a/src/body/Body.js +++ b/src/body/Body.js @@ -51,7 +51,7 @@ var Body = {}; friction: 0.1, frictionAir: 0.01, collisionFilter: { - category: 1, + category: 0x0001, mask: 0xFFFFFFFF, group: 0 }, @@ -75,21 +75,18 @@ var Body = {}; }; /** - * Returns the next unique groupID number for which bodies will collide. - * @method nextCollidingGroupId - * @return {Number} Unique groupID + * Returns the next unique group index for which bodies will collide. + * If `isNonColliding` is `true`, returns the next unique group index for which bodies will _not_ collide. + * See `body.collisionFilter` for more information. + * @method nextGroup + * @param {bool} [isNonColliding=false] + * @return {Number} Unique group index */ - Body.nextCollidingGroupId = function() { - return _nextCollidingGroupId++; - }; + Body.nextGroup = function(isNonColliding) { + if (isNonColliding) + return _nextNonCollidingGroupId--; - /** - * Returns the next collisionFilter.group value for which bodies will not collide. - * @method nextNonCollidingGroupId - * @return {Number} Unique groupID - */ - Body.nextNonCollidingGroupId = function() { - return _nextNonCollidingGroupId--; + return _nextCollidingGroupId++; }; /** @@ -699,6 +696,7 @@ var Body = {}; * 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). * @@ -711,7 +709,7 @@ var Body = {}; */ /** - * An Integer `Number`, see `collisionFilter` + * An Integer `Number`, see `body.collisionFilter` for more information. * * @property collisionFilter.group * @type object @@ -719,7 +717,10 @@ var Body = {}; */ /** - * An Integer `Number`, see `collisionFilter` + * A bit field that specifies the collision category this body belongs to. + * The category value should have only one bit set, for example `0x0001`. + * This means there are up to 32 unique collision categories available. + * See `body.collisionFilter` for more information. * * @property collisionFilter.category * @type object @@ -727,7 +728,7 @@ var Body = {}; */ /** - * An Integer `Number`, see `collisionFilter` + * An Integer `Number`, see `body.collisionFilter` for more information. * * @property collisionFilter.mask * @type object diff --git a/src/collision/Detector.js b/src/collision/Detector.js index 879308e..aa46820 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -26,13 +26,10 @@ var Detector = {}; var bodyA = broadphasePairs[i][0], bodyB = broadphasePairs[i][1]; - var collisionFilterA = bodyA.collisionFilter, - collisionFilterB = bodyB.collisionFilter; - if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping)) continue; - if (!_pairCollides(collisionFilterA, collisionFilterB)) + if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) continue; metrics.midphaseTests += 1; @@ -88,11 +85,11 @@ var Detector = {}; // NOTE: could share a function for the below, but may drop performance? - if (bodyA.groupId && bodyB.groupId && bodyA.groupId === bodyB.groupId) - continue; - if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping)) continue; + + if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) + continue; metrics.midphaseTests += 1; @@ -128,12 +125,20 @@ var Detector = {}; return collisions; }; - - var _pairCollides = function(filterA, filterB) { + + /** + * Returns `true` if both supplied collision filters will allow a collision to occur. + * See `body.collisionFilter` for more information. + * @method canCollide + * @param {} filterA + * @param {} filterB + * @return {bool} `true` if collision can occur + */ + Detector.canCollide = 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); + return (filterA.mask & filterB.category) !== 0 && (filterB.mask & filterA.category) !== 0; }; })(); diff --git a/src/factory/Composites.js b/src/factory/Composites.js index 688d835..fea5eb7 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 collisionFilterGroup = Body.nextNonCollidingGroupId(), + var group = Body.nextGroup(true), wheelBase = -20, wheelAOffset = -width * 0.5 + wheelBase, wheelBOffset = width * 0.5 - wheelBase, @@ -235,7 +235,7 @@ var Composites = {}; var car = Composite.create({ label: 'Car' }), body = Bodies.trapezoid(xx, yy, width, height, 0.3, { collisionFilter: { - group: collisionFilterGroup + group: group }, friction: 0.01, chamfer: { @@ -245,7 +245,7 @@ var Composites = {}; var wheelA = Bodies.circle(xx + wheelAOffset, yy + wheelYOffset, wheelSize, { collisionFilter: { - group: collisionFilterGroup + group: group }, restitution: 0.5, friction: 0.9, @@ -254,7 +254,7 @@ var Composites = {}; var wheelB = Bodies.circle(xx + wheelBOffset, yy + wheelYOffset, wheelSize, { collisionFilter: { - group: collisionFilterGroup + group: group }, restitution: 0.5, friction: 0.9,