diff --git a/examples/remove.js b/examples/remove.js index cd4db31..38ff103 100644 --- a/examples/remove.js +++ b/examples/remove.js @@ -13,8 +13,11 @@ Example.remove = function() { Events = Matter.Events; // create engine - var engine = Engine.create(), - world = engine.world; + var engine = Engine.create({ + enableSleeping: true + }); + + var world = engine.world; // create renderer var render = Render.create({ @@ -24,6 +27,7 @@ Example.remove = function() { width: 800, height: 600, showAngleIndicator: true, + showSleeping: true } }); @@ -33,9 +37,6 @@ Example.remove = function() { var runner = Runner.create(); Runner.run(runner, engine); - var stack = null, - lastTimestamp = 0; - var createStack = function() { return Composites.stack(20, 20, 10, 5, 0, 0, function(x, y) { var sides = Math.round(Common.random(1, 8)); @@ -61,15 +62,28 @@ Example.remove = function() { }); }; - // add and remove stacks every few updates + var stack = null, + bottomStack = createStack(), + lastTimestamp = 0; + + // add and remove bodies and composites every few updates Events.on(engine, 'afterUpdate', function(event) { // limit rate - if (stack && event.timestamp - lastTimestamp < 800) { + if (event.timestamp - lastTimestamp < 800) { return; } lastTimestamp = event.timestamp; + // remove an old body + Composite.remove(bottomStack, bottomStack.bodies[0]); + + // add a new body + Composite.add( + bottomStack, + Bodies.rectangle(Common.random(100, 500), 50, Common.random(25, 50), Common.random(25, 50)) + ); + // remove last stack if (stack) { Composite.remove(world, stack); @@ -82,10 +96,9 @@ Example.remove = function() { Composite.add(world, stack); }); - // add another stack that will not be removed - Composite.add(world, createStack()); - Composite.add(world, [ + bottomStack, + // walls Bodies.rectangle(400, 0, 800, 50, { isStatic: true }), Bodies.rectangle(400, 600, 800, 50, { isStatic: true }), diff --git a/src/body/Composite.js b/src/body/Composite.js index b814d7d..efbe6f3 100644 --- a/src/body/Composite.js +++ b/src/body/Composite.js @@ -192,8 +192,15 @@ var Body = require('./Body'); */ Composite.removeComposite = function(compositeA, compositeB, deep) { var position = Common.indexOf(compositeA.composites, compositeB); + if (position !== -1) { + var bodies = Composite.allBodies(compositeB); + Composite.removeCompositeAt(compositeA, position); + + for (var i = 0; i < bodies.length; i++) { + bodies[i].sleepCounter = 0; + } } if (deep) { @@ -244,8 +251,10 @@ var Body = require('./Body'); */ Composite.removeBody = function(composite, body, deep) { var position = Common.indexOf(composite.bodies, body); + if (position !== -1) { Composite.removeBodyAt(composite, position); + body.sleepCounter = 0; } if (deep) { @@ -296,6 +305,7 @@ var Body = require('./Body'); */ Composite.removeConstraint = function(composite, constraint, deep) { var position = Common.indexOf(composite.constraints, constraint); + if (position !== -1) { Composite.removeConstraintAt(composite, position); } diff --git a/src/collision/Collision.js b/src/collision/Collision.js index 40b6cdf..4aa5ca3 100644 --- a/src/collision/Collision.js +++ b/src/collision/Collision.js @@ -47,7 +47,8 @@ var Pair = require('./Pair'); normal: { x: 0, y: 0 }, tangent: { x: 0, y: 0 }, penetration: { x: 0, y: 0 }, - supports: [] + supports: [null, null], + supportCount: 0 }; }; @@ -99,27 +100,32 @@ var Pair = require('./Pair'); } var normal = collision.normal, + tangent = collision.tangent, + penetration = collision.penetration, supports = collision.supports, + depth = minOverlap.overlap, minAxis = minOverlap.axis, - minAxisX = minAxis.x, - minAxisY = minAxis.y; + normalX = minAxis.x, + normalY = minAxis.y, + deltaX = bodyB.position.x - bodyA.position.x, + deltaY = bodyB.position.y - bodyA.position.y; // ensure normal is facing away from bodyA - if (minAxisX * (bodyB.position.x - bodyA.position.x) + minAxisY * (bodyB.position.y - bodyA.position.y) < 0) { - normal.x = minAxisX; - normal.y = minAxisY; - } else { - normal.x = -minAxisX; - normal.y = -minAxisY; + if (normalX * deltaX + normalY * deltaY >= 0) { + normalX = -normalX; + normalY = -normalY; } + + normal.x = normalX; + normal.y = normalY; - collision.tangent.x = -normal.y; - collision.tangent.y = normal.x; + tangent.x = -normalY; + tangent.y = normalX; - collision.depth = minOverlap.overlap; + penetration.x = normalX * depth; + penetration.y = normalY * depth; - collision.penetration.x = normal.x * collision.depth; - collision.penetration.y = normal.y * collision.depth; + collision.depth = depth; // find support points, there is always either exactly one or two var supportsB = Collision._findSupports(bodyA, bodyB, normal, 1), @@ -152,8 +158,8 @@ var Pair = require('./Pair'); supports[supportCount++] = supportsB[0]; } - // update supports array size - supports.length = supportCount; + // update support count + collision.supportCount = supportCount; return collision; }; @@ -232,32 +238,6 @@ var Pair = require('./Pair'); result.overlap = overlapMin; }; - /** - * Projects vertices on an axis and returns an interval. - * @method _projectToAxis - * @private - * @param {} projection - * @param {} vertices - * @param {} axis - */ - Collision._projectToAxis = function(projection, vertices, axis) { - var min = vertices[0].x * axis.x + vertices[0].y * axis.y, - max = min; - - for (var i = 1; i < vertices.length; i += 1) { - var dot = vertices[i].x * axis.x + vertices[i].y * axis.y; - - if (dot > max) { - max = dot; - } else if (dot < min) { - min = dot; - } - } - - projection.min = min; - projection.max = max; - }; - /** * Finds supporting vertices given two bodies along a given direction using hill-climbing. * @method _findSupports @@ -275,15 +255,15 @@ var Pair = require('./Pair'); bodyAPositionY = bodyA.position.y, normalX = normal.x * direction, normalY = normal.y * direction, - nearestDistance = Number.MAX_VALUE, - vertexA, - vertexB, + vertexA = vertices[0], + vertexB = vertexA, + nearestDistance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y), vertexC, distance, j; // find deepest vertex relative to the axis - for (j = 0; j < verticesLength; j += 1) { + for (j = 1; j < verticesLength; j += 1) { vertexB = vertices[j]; distance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y); @@ -398,6 +378,10 @@ var Pair = require('./Pair'); /** * An array of body vertices that represent the support points in the collision. + * + * _Note:_ Only the first `collision.supportCount` items of `collision.supports` are active. + * Therefore use `collision.supportCount` instead of `collision.supports.length` when iterating the active supports. + * * These are the deepest vertices (along the collision normal) of each body that are contained by the other body's vertices. * * @property supports @@ -405,4 +389,15 @@ var Pair = require('./Pair'); * @default [] */ + /** + * The number of active supports for this collision found in `collision.supports`. + * + * _Note:_ Only the first `collision.supportCount` items of `collision.supports` are active. + * Therefore use `collision.supportCount` instead of `collision.supports.length` when iterating the active supports. + * + * @property supportCount + * @type number + * @default 0 + */ + })(); diff --git a/src/collision/Contact.js b/src/collision/Contact.js index 56f16cc..5ec9f0b 100644 --- a/src/collision/Contact.js +++ b/src/collision/Contact.js @@ -13,7 +13,7 @@ module.exports = Contact; /** * Creates a new contact. * @method create - * @param {vertex} vertex + * @param {vertex} [vertex] * @return {contact} A new contact */ Contact.create = function(vertex) { diff --git a/src/collision/Detector.js b/src/collision/Detector.js index a4626e2..c6059b0 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -22,6 +22,7 @@ var Collision = require('./Collision'); Detector.create = function(options) { var defaults = { bodies: [], + collisions: [], pairs: null }; @@ -45,6 +46,7 @@ var Collision = require('./Collision'); */ Detector.clear = function(detector) { detector.bodies = []; + detector.collisions = []; }; /** @@ -57,12 +59,13 @@ var Collision = require('./Collision'); * @return {collision[]} collisions */ Detector.collisions = function(detector) { - var collisions = [], - pairs = detector.pairs, + var pairs = detector.pairs, bodies = detector.bodies, bodiesLength = bodies.length, canCollide = Detector.canCollide, collides = Collision.collides, + collisions = detector.collisions, + collisionIndex = 0, i, j; @@ -104,7 +107,7 @@ var Collision = require('./Collision'); var collision = collides(bodyA, bodyB, pairs); if (collision) { - collisions.push(collision); + collisions[collisionIndex++] = collision; } } else { var partsAStart = partsALength > 1 ? 1 : 0, @@ -126,7 +129,7 @@ var Collision = require('./Collision'); var collision = collides(partA, partB, pairs); if (collision) { - collisions.push(collision); + collisions[collisionIndex++] = collision; } } } @@ -134,6 +137,10 @@ var Collision = require('./Collision'); } } + if (collisions.length !== collisionIndex) { + collisions.length = collisionIndex; + } + return collisions; }; @@ -180,6 +187,13 @@ var Collision = require('./Collision'); * @default [] */ + /** + * The array of `Matter.Collision` found in the last call to `Detector.collisions` on this detector. + * @property collisions + * @type collision[] + * @default [] + */ + /** * Optional. A `Matter.Pairs` object from which previous collision objects may be reused. Intended for internal `Matter.Engine` usage. * @property pairs diff --git a/src/collision/Pair.js b/src/collision/Pair.js index 8f7ed81..549ff36 100644 --- a/src/collision/Pair.js +++ b/src/collision/Pair.js @@ -28,11 +28,10 @@ var Contact = require('./Contact'); bodyA: bodyA, bodyB: bodyB, collision: collision, - contacts: [], - activeContacts: [], + contacts: [Contact.create(), Contact.create()], + contactCount: 0, separation: 0, isActive: true, - confirmedActive: true, isSensor: bodyA.isSensor || bodyB.isSensor, timeCreated: timestamp, timeUpdated: timestamp, @@ -56,12 +55,11 @@ var Contact = require('./Contact'); * @param {number} timestamp */ Pair.update = function(pair, collision, timestamp) { - var contacts = pair.contacts, - supports = collision.supports, - activeContacts = pair.activeContacts, + var supports = collision.supports, + supportCount = collision.supportCount, + contacts = pair.contacts, parentA = collision.parentA, - parentB = collision.parentB, - parentAVerticesLength = parentA.vertices.length; + parentB = collision.parentB; pair.isActive = true; pair.timeUpdated = timestamp; @@ -73,20 +71,24 @@ var Contact = require('./Contact'); pair.restitution = parentA.restitution > parentB.restitution ? parentA.restitution : parentB.restitution; pair.slop = parentA.slop > parentB.slop ? parentA.slop : parentB.slop; + pair.contactCount = supportCount; collision.pair = pair; - activeContacts.length = 0; - - for (var i = 0; i < supports.length; i++) { - var support = supports[i], - contactId = support.body === parentA ? support.index : parentAVerticesLength + support.index, - contact = contacts[contactId]; - if (contact) { - activeContacts.push(contact); - } else { - activeContacts.push(contacts[contactId] = Contact.create(support)); - } + var supportA = supports[0], + contactA = contacts[0], + supportB = supports[1], + contactB = contacts[1]; + + // match contacts to supports + if (contactB.vertex === supportA || contactA.vertex === supportB) { + contacts[1] = contactA; + contacts[0] = contactA = contactB; + contactB = contacts[1]; } + + // update contacts + contactA.vertex = supportA; + contactB.vertex = supportB; }; /** @@ -102,7 +104,7 @@ var Contact = require('./Contact'); pair.timeUpdated = timestamp; } else { pair.isActive = false; - pair.activeContacts.length = 0; + pair.contactCount = 0; } }; @@ -114,11 +116,8 @@ var Contact = require('./Contact'); * @return {string} Unique pairId */ Pair.id = function(bodyA, bodyB) { - if (bodyA.id < bodyB.id) { - return 'A' + bodyA.id + 'B' + bodyB.id; - } else { - return 'A' + bodyB.id + 'B' + bodyA.id; - } + return bodyA.id < bodyB.id ? bodyA.id.toString(36) + ':' + bodyB.id.toString(36) + : bodyB.id.toString(36) + ':' + bodyA.id.toString(36); }; })(); diff --git a/src/collision/Pairs.js b/src/collision/Pairs.js index 2c62cd3..ac00e82 100644 --- a/src/collision/Pairs.js +++ b/src/collision/Pairs.js @@ -37,27 +37,24 @@ var Common = require('../core/Common'); * @param {number} timestamp */ Pairs.update = function(pairs, collisions, timestamp) { - var pairsList = pairs.list, - pairsListLength = pairsList.length, + var pairUpdate = Pair.update, + pairCreate = Pair.create, + pairSetActive = Pair.setActive, pairsTable = pairs.table, - collisionsLength = collisions.length, + pairsList = pairs.list, + pairsListLength = pairsList.length, + pairsListIndex = pairsListLength, collisionStart = pairs.collisionStart, collisionEnd = pairs.collisionEnd, collisionActive = pairs.collisionActive, + collisionsLength = collisions.length, + collisionStartIndex = 0, + collisionEndIndex = 0, + collisionActiveIndex = 0, collision, - pairIndex, pair, i; - // clear collision state arrays, but maintain old reference - collisionStart.length = 0; - collisionEnd.length = 0; - collisionActive.length = 0; - - for (i = 0; i < pairsListLength; i++) { - pairsList[i].confirmedActive = false; - } - for (i = 0; i < collisionsLength; i++) { collision = collisions[i]; pair = collision.pair; @@ -66,49 +63,62 @@ var Common = require('../core/Common'); // pair already exists (but may or may not be active) if (pair.isActive) { // pair exists and is active - collisionActive.push(pair); - } else { - // pair exists but was inactive, so a collision has just started again - collisionStart.push(pair); + collisionActive[collisionActiveIndex++] = pair; } // update the pair - Pair.update(pair, collision, timestamp); - pair.confirmedActive = true; + pairUpdate(pair, collision, timestamp); } else { // pair did not exist, create a new pair - pair = Pair.create(collision, timestamp); + pair = pairCreate(collision, timestamp); pairsTable[pair.id] = pair; - // push the new pair - collisionStart.push(pair); - pairsList.push(pair); + // add the new pair + collisionStart[collisionStartIndex++] = pair; + pairsList[pairsListIndex++] = pair; } } // find pairs that are no longer active - var removePairIndex = []; + pairsListIndex = 0; pairsListLength = pairsList.length; for (i = 0; i < pairsListLength; i++) { pair = pairsList[i]; - if (!pair.confirmedActive) { - Pair.setActive(pair, false, timestamp); - collisionEnd.push(pair); + // pair is active if updated this timestep + if (pair.timeUpdated >= timestamp) { + // keep active pairs + pairsList[pairsListIndex++] = pair; + } else { + pairSetActive(pair, false, timestamp); - if (!pair.collision.bodyA.isSleeping && !pair.collision.bodyB.isSleeping) { - removePairIndex.push(i); + // keep inactive pairs if both bodies may be sleeping + if (pair.collision.bodyA.sleepCounter > 0 && pair.collision.bodyB.sleepCounter > 0) { + pairsList[pairsListIndex++] = pair; + } else { + // remove inactive pairs if either body awake + collisionEnd[collisionEndIndex++] = pair; + delete pairsTable[pair.id]; } } } - // remove inactive pairs - for (i = 0; i < removePairIndex.length; i++) { - pairIndex = removePairIndex[i] - i; - pair = pairsList[pairIndex]; - pairsList.splice(pairIndex, 1); - delete pairsTable[pair.id]; + // update array lengths if changed + if (pairsList.length !== pairsListIndex) { + pairsList.length = pairsListIndex; + } + + if (collisionStart.length !== collisionStartIndex) { + collisionStart.length = collisionStartIndex; + } + + if (collisionEnd.length !== collisionEndIndex) { + collisionEnd.length = collisionEndIndex; + } + + if (collisionActive.length !== collisionActiveIndex) { + collisionActive.length = collisionActiveIndex; } }; diff --git a/src/collision/Resolver.js b/src/collision/Resolver.js index 9fd7d39..db44fb8 100644 --- a/src/collision/Resolver.js +++ b/src/collision/Resolver.js @@ -29,7 +29,7 @@ var Bounds = require('../geometry/Bounds'); Resolver.preSolvePosition = function(pairs) { var i, pair, - activeCount, + contactCount, pairsLength = pairs.length; // find total contacts on each body @@ -39,9 +39,9 @@ var Bounds = require('../geometry/Bounds'); if (!pair.isActive) continue; - activeCount = pair.activeContacts.length; - pair.collision.parentA.totalContacts += activeCount; - pair.collision.parentB.totalContacts += activeCount; + contactCount = pair.contactCount; + pair.collision.parentA.totalContacts += contactCount; + pair.collision.parentB.totalContacts += contactCount; } }; @@ -79,8 +79,8 @@ var Bounds = require('../geometry/Bounds'); // get current separation between body edges involved in collision pair.separation = - normal.x * (bodyB.positionImpulse.x + collision.penetration.x - bodyA.positionImpulse.x) - + normal.y * (bodyB.positionImpulse.y + collision.penetration.y - bodyA.positionImpulse.y); + collision.depth + normal.x * (bodyB.positionImpulse.x - bodyA.positionImpulse.x) + + normal.y * (bodyB.positionImpulse.y - bodyA.positionImpulse.y); } for (i = 0; i < pairsLength; i++) { @@ -176,8 +176,8 @@ var Bounds = require('../geometry/Bounds'); if (!pair.isActive || pair.isSensor) continue; - var contacts = pair.activeContacts, - contactsLength = contacts.length, + var contacts = pair.contacts, + contactCount = pair.contactCount, collision = pair.collision, bodyA = collision.parentA, bodyB = collision.parentB, @@ -185,7 +185,7 @@ var Bounds = require('../geometry/Bounds'); tangent = collision.tangent; // resolve each contact - for (j = 0; j < contactsLength; j++) { + for (j = 0; j < contactCount; j++) { var contact = contacts[j], contactVertex = contact.vertex, normalImpulse = contact.normalImpulse, @@ -248,28 +248,26 @@ var Bounds = require('../geometry/Bounds'); var collision = pair.collision, bodyA = collision.parentA, bodyB = collision.parentB, - bodyAVelocity = bodyA.velocity, - bodyBVelocity = bodyB.velocity, normalX = collision.normal.x, normalY = collision.normal.y, tangentX = collision.tangent.x, tangentY = collision.tangent.y, - contacts = pair.activeContacts, - contactsLength = contacts.length, - contactShare = 1 / contactsLength, - inverseMassTotal = bodyA.inverseMass + bodyB.inverseMass, - friction = pair.friction * pair.frictionStatic * frictionNormalMultiplier; + inverseMassTotal = pair.inverseMass, + friction = pair.friction * pair.frictionStatic * frictionNormalMultiplier, + contacts = pair.contacts, + contactCount = pair.contactCount, + contactShare = 1 / contactCount; - // update body velocities - bodyAVelocity.x = bodyA.position.x - bodyA.positionPrev.x; - bodyAVelocity.y = bodyA.position.y - bodyA.positionPrev.y; - bodyBVelocity.x = bodyB.position.x - bodyB.positionPrev.x; - bodyBVelocity.y = bodyB.position.y - bodyB.positionPrev.y; - bodyA.angularVelocity = bodyA.angle - bodyA.anglePrev; - bodyB.angularVelocity = bodyB.angle - bodyB.anglePrev; + // get body velocities + var bodyAVelocityX = bodyA.position.x - bodyA.positionPrev.x, + bodyAVelocityY = bodyA.position.y - bodyA.positionPrev.y, + bodyAAngularVelocity = bodyA.angle - bodyA.anglePrev, + bodyBVelocityX = bodyB.position.x - bodyB.positionPrev.x, + bodyBVelocityY = bodyB.position.y - bodyB.positionPrev.y, + bodyBAngularVelocity = bodyB.angle - bodyB.anglePrev; // resolve each contact - for (j = 0; j < contactsLength; j++) { + for (j = 0; j < contactCount; j++) { var contact = contacts[j], contactVertex = contact.vertex; @@ -278,10 +276,10 @@ var Bounds = require('../geometry/Bounds'); offsetBX = contactVertex.x - bodyB.position.x, offsetBY = contactVertex.y - bodyB.position.y; - var velocityPointAX = bodyAVelocity.x - offsetAY * bodyA.angularVelocity, - velocityPointAY = bodyAVelocity.y + offsetAX * bodyA.angularVelocity, - velocityPointBX = bodyBVelocity.x - offsetBY * bodyB.angularVelocity, - velocityPointBY = bodyBVelocity.y + offsetBX * bodyB.angularVelocity; + var velocityPointAX = bodyAVelocityX - offsetAY * bodyAAngularVelocity, + velocityPointAY = bodyAVelocityY + offsetAX * bodyAAngularVelocity, + velocityPointBX = bodyBVelocityX - offsetBY * bodyBAngularVelocity, + velocityPointBY = bodyBVelocityY + offsetBX * bodyBAngularVelocity; var relativeVelocityX = velocityPointAX - velocityPointBX, relativeVelocityY = velocityPointAY - velocityPointBY; diff --git a/src/core/Engine.js b/src/core/Engine.js index 91b55ac..cdf03c4 100644 --- a/src/core/Engine.js +++ b/src/core/Engine.js @@ -60,6 +60,7 @@ var Body = require('../body/Body'); engine.world = options.world || Composite.create({ label: 'World' }); engine.pairs = options.pairs || Pairs.create(); engine.detector = options.detector || Detector.create(); + engine.detector.pairs = engine.pairs; // for temporary back compatibility only engine.grid = { buckets: [] }; @@ -138,7 +139,6 @@ var Body = require('../body/Body'); Constraint.postSolveAll(allBodies); // find all collisions - detector.pairs = engine.pairs; var collisions = Detector.collisions(detector); // update collision pairs diff --git a/src/render/Render.js b/src/render/Render.js index 9507049..3cfd80a 100644 --- a/src/render/Render.js +++ b/src/render/Render.js @@ -1209,8 +1209,8 @@ var Mouse = require('../core/Mouse'); continue; collision = pair.collision; - for (j = 0; j < pair.activeContacts.length; j++) { - var contact = pair.activeContacts[j], + for (j = 0; j < pair.contactCount; j++) { + var contact = pair.contacts[j], vertex = contact.vertex; c.rect(vertex.x - 1.5, vertex.y - 1.5, 3.5, 3.5); } @@ -1234,13 +1234,13 @@ var Mouse = require('../core/Mouse'); collision = pair.collision; - if (pair.activeContacts.length > 0) { - var normalPosX = pair.activeContacts[0].vertex.x, - normalPosY = pair.activeContacts[0].vertex.y; + if (pair.contactCount > 0) { + var normalPosX = pair.contacts[0].vertex.x, + normalPosY = pair.contacts[0].vertex.y; - if (pair.activeContacts.length === 2) { - normalPosX = (pair.activeContacts[0].vertex.x + pair.activeContacts[1].vertex.x) / 2; - normalPosY = (pair.activeContacts[0].vertex.y + pair.activeContacts[1].vertex.y) / 2; + if (pair.contactCount === 2) { + normalPosX = (pair.contacts[0].vertex.x + pair.contacts[1].vertex.x) / 2; + normalPosY = (pair.contacts[0].vertex.y + pair.contacts[1].vertex.y) / 2; } if (collision.bodyB === collision.supports[0].body || collision.bodyA.isStatic === true) {