Skip to content

Commit

Permalink
Merge branch 'GustavCarlson-collision-filters'
Browse files Browse the repository at this point in the history
  • Loading branch information
liabru committed Jul 29, 2014
2 parents 04955bb + 9f10f88 commit a58fe2a
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 41 deletions.
3 changes: 2 additions & 1 deletion demo/dev.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ <h1>Matter.js Demo (Dev. Build)</h1>
<option value="softBody">Basic Soft Bodies</option>
<option value="cloth">Cloth</option>
<option value="events">Events</option>
<option value="collisionFiltering">Collision Filtering</option>
<option value="chains">Chains</option>
<option value="ballPool">Ball Pool</option>
<option value="stack">Stack</option>
Expand All @@ -67,4 +68,4 @@ <h1>Matter.js Demo (Dev. Build)</h1>
<div id="canvas-container"></div>
</div>
</body>
</html>
</html>
109 changes: 100 additions & 9 deletions demo/js/Demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,12 +448,12 @@

Demo.chains = function() {
var _world = _engine.world,
groupId = Body.nextGroupId();
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, { groupId: 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 });
Expand All @@ -466,10 +466,10 @@

World.add(_world, ropeA);

groupId = Body.nextGroupId();
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, { groupId: groupId });
return Bodies.circle(x, y, 20, { collisionFilter: { group: group } });
});

Composites.chain(ropeB, 0.5, 0, -0.5, 0, { stiffness: 0.8, length: 2 });
Expand All @@ -485,12 +485,12 @@

Demo.bridge = function() {
var _world = _engine.world,
groupId = Body.nextGroupId();
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, { groupId: groupId });
return Bodies.rectangle(x, y, 50, 20, { collisionFilter: { group: group } });
});

Composites.chain(bridge, 0.5, 0, -0.5, 0, { stiffness: 0.9 });
Expand Down Expand Up @@ -937,8 +937,8 @@

Demo.reset();

var groupId = Body.nextGroupId(),
particleOptions = { friction: 0.00001, groupId: 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++) {
Expand Down Expand Up @@ -1231,6 +1231,97 @@
var renderOptions = _engine.render.options;
};

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();

// 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: category
},
render: {
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: defaultCategory | redCategory
},
render: {
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 = '#222';
renderOptions.showAngleIndicator = false;
};

// the functions for the demo interface and controls below

Demo.initControls = function() {
Expand Down Expand Up @@ -1400,4 +1491,4 @@
renderOptions.showDebug = true;
};

})();
})();
85 changes: 70 additions & 15 deletions src/body/Body.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -49,7 +50,11 @@ var Body = {};
restitution: 0,
friction: 0.1,
frictionAir: 0.01,
groupId: 0,
collisionFilter: {
category: 0x0001,
mask: 0xFFFFFFFF,
group: 0
},
slop: 0.05,
timeScale: 1,
render: {
Expand All @@ -70,12 +75,18 @@ var Body = {};
};

/**
* Returns the next unique groupID number.
* @method nextGroupId
* @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.nextGroupId = function() {
return _nextGroupId++;
Body.nextGroup = function(isNonColliding) {
if (isNonColliding)
return _nextNonCollidingGroupId--;

return _nextCollidingGroupId++;
};

/**
Expand Down Expand Up @@ -672,18 +683,62 @@ 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`, that specifies the collision group this body belongs to.
* See `body.collisionFilter` for more information.
*
* @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.
* 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
* @default 1
*/

/**
* A bit mask that specifies the collision categories this body may collide with.
* See `body.collisionFilter` for more information.
*
* @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.
*
Expand Down Expand Up @@ -797,4 +852,4 @@ var Body = {};
* @type bounds
*/

})();
})();
31 changes: 22 additions & 9 deletions src/collision/Detector.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,11 @@ 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;

if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping))
continue;

if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter))
continue;

metrics.midphaseTests += 1;

Expand Down Expand Up @@ -87,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;

Expand Down Expand Up @@ -128,4 +126,19 @@ var Detector = {};
return collisions;
};

})();
/**
* 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;
};

})();
19 changes: 17 additions & 2 deletions src/constraint/MouseConstraint.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ var MouseConstraint = {};
mouse: mouse,
dragBody: null,
dragPoint: null,
constraint: constraint
constraint: constraint,
collisionFilter: {
category: 0x0001,
mask: 0xFFFFFFFF,
group: 0
}
};

var mouseConstraint = Common.extend(defaults, options);
Expand Down Expand Up @@ -72,7 +77,8 @@ var MouseConstraint = {};
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (Bounds.contains(body.bounds, mouse.position)
&& Vertices.contains(body.vertices, mouse.position)) {
&& Vertices.contains(body.vertices, mouse.position)
&& Detector.canCollide(body.collisionFilter, mouseConstraint.collisionFilter)) {
constraint.pointA = mouse.position;
constraint.bodyB = body;
constraint.pointB = { x: mouse.position.x - body.position.x, y: mouse.position.y - body.position.y };
Expand Down Expand Up @@ -196,4 +202,13 @@ var MouseConstraint = {};
* @type constraint
*/

/**
* An `Object` that specifies the collision filter properties.
* The collision filter allows the user to define which types of body this mouse constraint can interact with.
* See `body.collisionFilter` for more information.
*
* @property collisionFilter
* @type object
*/

})();
Loading

0 comments on commit a58fe2a

Please sign in to comment.