From 792ae2ead45eb38ea4813179bc96febac1364e6c Mon Sep 17 00:00:00 2001 From: liabru Date: Mon, 26 Apr 2021 23:36:09 +0100 Subject: [PATCH 01/62] optimised Vertices.translate --- src/geometry/Vertices.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/geometry/Vertices.js b/src/geometry/Vertices.js index 8e487de..285eb07 100644 --- a/src/geometry/Vertices.js +++ b/src/geometry/Vertices.js @@ -169,17 +169,16 @@ var Common = require('../core/Common'); * @param {number} scalar */ Vertices.translate = function(vertices, vector, scalar) { - var i; - if (scalar) { - for (i = 0; i < vertices.length; i++) { - vertices[i].x += vector.x * scalar; - vertices[i].y += vector.y * scalar; - } - } else { - for (i = 0; i < vertices.length; i++) { - vertices[i].x += vector.x; - vertices[i].y += vector.y; - } + scalar = typeof scalar !== 'undefined' ? scalar : 1; + + var verticesLength = vertices.length, + translateX = vector.x * scalar, + translateY = vector.y * scalar, + i; + + for (i = 0; i < verticesLength; i++) { + vertices[i].x += translateX; + vertices[i].y += translateY; } return vertices; From 6883d0d98a41c5afc9750eb2c4927140dddb7c12 Mon Sep 17 00:00:00 2001 From: liabru Date: Mon, 26 Apr 2021 23:43:21 +0100 Subject: [PATCH 02/62] optimised Vertices.rotate --- src/geometry/Vertices.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/geometry/Vertices.js b/src/geometry/Vertices.js index 285eb07..ae0b279 100644 --- a/src/geometry/Vertices.js +++ b/src/geometry/Vertices.js @@ -196,15 +196,21 @@ var Common = require('../core/Common'); return; var cos = Math.cos(angle), - sin = Math.sin(angle); + sin = Math.sin(angle), + pointX = point.x, + pointY = point.y, + verticesLength = vertices.length, + vertex, + dx, + dy, + i; - for (var i = 0; i < vertices.length; i++) { - var vertice = vertices[i], - dx = vertice.x - point.x, - dy = vertice.y - point.y; - - vertice.x = point.x + (dx * cos - dy * sin); - vertice.y = point.y + (dx * sin + dy * cos); + for (i = 0; i < verticesLength; i++) { + vertex = vertices[i]; + dx = vertex.x - pointX; + dy = vertex.y - pointY; + vertex.x = pointX + (dx * cos - dy * sin); + vertex.y = pointY + (dx * sin + dy * cos); } return vertices; From c1988783db2d97bda610254fa07dc95f5b1d59ea Mon Sep 17 00:00:00 2001 From: liabru Date: Mon, 26 Apr 2021 23:48:00 +0100 Subject: [PATCH 03/62] optimised Vertices.contains --- src/geometry/Vertices.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/geometry/Vertices.js b/src/geometry/Vertices.js index ae0b279..f28d7a9 100644 --- a/src/geometry/Vertices.js +++ b/src/geometry/Vertices.js @@ -224,12 +224,21 @@ var Common = require('../core/Common'); * @return {boolean} True if the vertices contains point, otherwise false */ Vertices.contains = function(vertices, point) { - for (var i = 0; i < vertices.length; i++) { - var vertice = vertices[i], - nextVertice = vertices[(i + 1) % vertices.length]; - if ((point.x - vertice.x) * (nextVertice.y - vertice.y) + (point.y - vertice.y) * (vertice.x - nextVertice.x) > 0) { + var pointX = point.x, + pointY = point.y, + verticesLength = vertices.length, + vertex = vertices[verticesLength - 1], + nextVertex; + + for (var i = 0; i < verticesLength; i++) { + nextVertex = vertices[i]; + + if ((pointX - vertex.x) * (nextVertex.y - vertex.y) + + (pointY - vertex.y) * (vertex.x - nextVertex.x) > 0) { return false; } + + vertex = nextVertex; } return true; From 48673db79f894f2ceb3695be9986e9fa112d58d5 Mon Sep 17 00:00:00 2001 From: liabru Date: Mon, 26 Apr 2021 23:52:35 +0100 Subject: [PATCH 04/62] optimised Pair.create --- src/collision/Pair.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/collision/Pair.js b/src/collision/Pair.js index ccfd088..049a857 100644 --- a/src/collision/Pair.js +++ b/src/collision/Pair.js @@ -38,10 +38,10 @@ var Contact = require('./Contact'); timeCreated: timestamp, timeUpdated: timestamp, inverseMass: parentA.inverseMass + parentB.inverseMass, - friction: Math.min(parentA.friction, parentB.friction), - frictionStatic: Math.max(parentA.frictionStatic, parentB.frictionStatic), - restitution: Math.max(parentA.restitution, parentB.restitution), - slop: Math.max(parentA.slop, parentB.slop) + friction: parentA.friction < parentB.friction ? parentA.friction : parentB.friction, + frictionStatic: parentA.frictionStatic > parentB.frictionStatic ? parentA.frictionStatic : parentB.frictionStatic, + restitution: parentA.restitution > parentB.restitution ? parentA.restitution : parentB.restitution, + slop: parentA.slop > parentB.slop ? parentA.slop : parentB.slop }; Pair.update(pair, collision, timestamp); From 1073ddeecf1a28d42570455bd93152f8faaddf2c Mon Sep 17 00:00:00 2001 From: liabru Date: Mon, 26 Apr 2021 23:53:18 +0100 Subject: [PATCH 05/62] optimised Pair.update --- src/collision/Pair.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/collision/Pair.js b/src/collision/Pair.js index 049a857..a48754b 100644 --- a/src/collision/Pair.js +++ b/src/collision/Pair.js @@ -65,10 +65,10 @@ var Contact = require('./Contact'); pair.collision = collision; pair.inverseMass = parentA.inverseMass + parentB.inverseMass; - pair.friction = Math.min(parentA.friction, parentB.friction); - pair.frictionStatic = Math.max(parentA.frictionStatic, parentB.frictionStatic); - pair.restitution = Math.max(parentA.restitution, parentB.restitution); - pair.slop = Math.max(parentA.slop, parentB.slop); + pair.friction = parentA.friction < parentB.friction ? parentA.friction : parentB.friction; + pair.frictionStatic = parentA.frictionStatic > parentB.frictionStatic ? parentA.frictionStatic : parentB.frictionStatic; + pair.restitution = parentA.restitution > parentB.restitution ? parentA.restitution : parentB.restitution; + pair.slop = parentA.slop > parentB.slop ? parentA.slop : parentB.slop; activeContacts.length = 0; if (collision.collided) { From a882a74cd54ce5a070b6ac5589e0bba6ce5859dd Mon Sep 17 00:00:00 2001 From: liabru Date: Mon, 26 Apr 2021 23:54:48 +0100 Subject: [PATCH 06/62] optimised Pairs.removeOld --- src/collision/Pairs.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/collision/Pairs.js b/src/collision/Pairs.js index b17904b..3f8cadd 100644 --- a/src/collision/Pairs.js +++ b/src/collision/Pairs.js @@ -111,6 +111,7 @@ var Common = require('../core/Common'); var pairsList = pairs.list, pairsTable = pairs.table, indexesToRemove = [], + pairMaxIdleLife = Pairs._pairMaxIdleLife, pair, collision, pairIndex, @@ -127,7 +128,7 @@ var Common = require('../core/Common'); } // if pair is inactive for too long, mark it to be removed - if (timestamp - pair.timeUpdated > Pairs._pairMaxIdleLife) { + if (timestamp - pair.timeUpdated > pairMaxIdleLife) { indexesToRemove.push(i); } } From 3cf65e80519199b91a3809b1813da58a5e4b3ef5 Mon Sep 17 00:00:00 2001 From: liabru Date: Tue, 27 Apr 2021 00:30:53 +0100 Subject: [PATCH 07/62] optimised Resolver.solvePosition --- src/collision/Resolver.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/collision/Resolver.js b/src/collision/Resolver.js index 45376da..503a2f4 100644 --- a/src/collision/Resolver.js +++ b/src/collision/Resolver.js @@ -29,10 +29,11 @@ var Bounds = require('../geometry/Bounds'); Resolver.preSolvePosition = function(pairs) { var i, pair, - activeCount; + activeCount, + pairsLength = pairs.length; // find total contacts on each body - for (i = 0; i < pairs.length; i++) { + for (i = 0; i < pairsLength; i++) { pair = pairs[i]; if (!pair.isActive) @@ -57,17 +58,13 @@ var Bounds = require('../geometry/Bounds'); bodyA, bodyB, normal, - bodyBtoA, contactShare, positionImpulse, - contactCount = {}, - tempA = Vector._temp[0], - tempB = Vector._temp[1], - tempC = Vector._temp[2], - tempD = Vector._temp[3]; + positionDampen = Resolver._positionDampen, + pairsLength = pairs.length; // find impulses required to resolve penetration - for (i = 0; i < pairs.length; i++) { + for (i = 0; i < pairsLength; i++) { pair = pairs[i]; if (!pair.isActive || pair.isSensor) @@ -78,15 +75,18 @@ var Bounds = require('../geometry/Bounds'); bodyB = collision.parentB; normal = collision.normal; + // TODO: behaviour change: replace with fully simplified version // get current separation between body edges involved in collision - bodyBtoA = Vector.sub(Vector.add(bodyB.positionImpulse, bodyB.position, tempA), - Vector.add(bodyA.positionImpulse, - Vector.sub(bodyB.position, collision.penetration, tempB), tempC), tempD); - - pair.separation = Vector.dot(normal, bodyBtoA); + pair.separation = + normal.x * ( + (bodyB.positionImpulse.x + bodyB.position.x) - (bodyA.positionImpulse.x + (bodyB.position.x - collision.penetration.x)) + ) + + normal.y * ( + (bodyB.positionImpulse.y + bodyB.position.y) - (bodyA.positionImpulse.y + (bodyB.position.y - collision.penetration.y)) + ); } - for (i = 0; i < pairs.length; i++) { + for (i = 0; i < pairsLength; i++) { pair = pairs[i]; if (!pair.isActive || pair.isSensor) @@ -102,13 +102,13 @@ var Bounds = require('../geometry/Bounds'); positionImpulse *= 2; if (!(bodyA.isStatic || bodyA.isSleeping)) { - contactShare = Resolver._positionDampen / bodyA.totalContacts; + contactShare = positionDampen / bodyA.totalContacts; bodyA.positionImpulse.x += normal.x * positionImpulse * contactShare; bodyA.positionImpulse.y += normal.y * positionImpulse * contactShare; } if (!(bodyB.isStatic || bodyB.isSleeping)) { - contactShare = Resolver._positionDampen / bodyB.totalContacts; + contactShare = positionDampen / bodyB.totalContacts; bodyB.positionImpulse.x -= normal.x * positionImpulse * contactShare; bodyB.positionImpulse.y -= normal.y * positionImpulse * contactShare; } From 49fbfba5115ec3349b23931d01c6a5eaf16645b2 Mon Sep 17 00:00:00 2001 From: liabru Date: Tue, 27 Apr 2021 00:31:34 +0100 Subject: [PATCH 08/62] optimised Resolver.postSolvePosition --- src/collision/Resolver.js | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/collision/Resolver.js b/src/collision/Resolver.js index 503a2f4..2f26713 100644 --- a/src/collision/Resolver.js +++ b/src/collision/Resolver.js @@ -121,34 +121,40 @@ var Bounds = require('../geometry/Bounds'); * @param {body[]} bodies */ Resolver.postSolvePosition = function(bodies) { + var positionWarming = Resolver._positionWarming; + for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; + var body = bodies[i], + positionImpulse = body.positionImpulse, + positionImpulseX = positionImpulse.x, + positionImpulseY = positionImpulse.y, + velocity = body.velocity; // reset contact count body.totalContacts = 0; - if (body.positionImpulse.x !== 0 || body.positionImpulse.y !== 0) { + if (positionImpulseX !== 0 || positionImpulseY !== 0) { // update body geometry for (var j = 0; j < body.parts.length; j++) { var part = body.parts[j]; - Vertices.translate(part.vertices, body.positionImpulse); - Bounds.update(part.bounds, part.vertices, body.velocity); - part.position.x += body.positionImpulse.x; - part.position.y += body.positionImpulse.y; + Vertices.translate(part.vertices, positionImpulse); + Bounds.update(part.bounds, part.vertices, velocity); + part.position.x += positionImpulseX; + part.position.y += positionImpulseY; } // move the body without changing velocity - body.positionPrev.x += body.positionImpulse.x; - body.positionPrev.y += body.positionImpulse.y; + body.positionPrev.x += positionImpulseX; + body.positionPrev.y += positionImpulseY; - if (Vector.dot(body.positionImpulse, body.velocity) < 0) { + if (positionImpulseX * velocity.x + positionImpulseY * velocity.y < 0) { // reset cached impulse if the body has velocity along it - body.positionImpulse.x = 0; - body.positionImpulse.y = 0; + positionImpulse.x = 0; + positionImpulse.y = 0; } else { // warm the next iteration - body.positionImpulse.x *= Resolver._positionWarming; - body.positionImpulse.y *= Resolver._positionWarming; + positionImpulse.x *= positionWarming; + positionImpulse.y *= positionWarming; } } } From c7cec16eb7f8fc42600ca7e72c25324874994642 Mon Sep 17 00:00:00 2001 From: liabru Date: Tue, 27 Apr 2021 23:59:29 +0100 Subject: [PATCH 09/62] optimise Detector.collisions --- src/collision/Detector.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/collision/Detector.js b/src/collision/Detector.js index 041ccbd..18cf0ab 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -39,26 +39,22 @@ var Bounds = require('../geometry/Bounds'); // mid phase if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) { - for (var j = bodyA.parts.length > 1 ? 1 : 0; j < bodyA.parts.length; j++) { + var partsALength = bodyA.parts.length, + partsBLength = bodyB.parts.length; + + for (var j = partsALength > 1 ? 1 : 0; j < partsALength; j++) { var partA = bodyA.parts[j]; - for (var k = bodyB.parts.length > 1 ? 1 : 0; k < bodyB.parts.length; k++) { + for (var k = partsBLength > 1 ? 1 : 0; k < partsBLength; k++) { var partB = bodyB.parts[k]; if ((partA === bodyA && partB === bodyB) || Bounds.overlaps(partA.bounds, partB.bounds)) { // find a previous collision we could reuse var pairId = Pair.id(partA, partB), - pair = pairsTable[pairId], - previousCollision; - - if (pair && pair.isActive) { - previousCollision = pair.collision; - } else { - previousCollision = null; - } + pair = pairsTable[pairId]; // narrow phase - var collision = SAT.collides(partA, partB, previousCollision); + var collision = SAT.collides(partA, partB, pair && pair.isActive ? pair.collision : null); if (collision.collided) { collisions.push(collision); From fceb0cab68ad479d23e2651b9fa7a512f42a2520 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 28 Apr 2021 23:47:59 +0100 Subject: [PATCH 10/62] optimised Resolver.postSolvePosition --- src/collision/Resolver.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/collision/Resolver.js b/src/collision/Resolver.js index 2f26713..20e2aed 100644 --- a/src/collision/Resolver.js +++ b/src/collision/Resolver.js @@ -121,9 +121,12 @@ var Bounds = require('../geometry/Bounds'); * @param {body[]} bodies */ Resolver.postSolvePosition = function(bodies) { - var positionWarming = Resolver._positionWarming; + var positionWarming = Resolver._positionWarming, + bodiesLength = bodies.length, + verticesTranslate = Vertices.translate, + boundsUpdate = Bounds.update; - for (var i = 0; i < bodies.length; i++) { + for (var i = 0; i < bodiesLength; i++) { var body = bodies[i], positionImpulse = body.positionImpulse, positionImpulseX = positionImpulse.x, @@ -137,8 +140,8 @@ var Bounds = require('../geometry/Bounds'); // update body geometry for (var j = 0; j < body.parts.length; j++) { var part = body.parts[j]; - Vertices.translate(part.vertices, positionImpulse); - Bounds.update(part.bounds, part.vertices, velocity); + verticesTranslate(part.vertices, positionImpulse); + boundsUpdate(part.bounds, part.vertices, velocity); part.position.x += positionImpulseX; part.position.y += positionImpulseY; } From 0b07a319442b6f73f7944c82302c13c14ae4b0cd Mon Sep 17 00:00:00 2001 From: liabru Date: Sat, 1 May 2021 14:16:11 +0100 Subject: [PATCH 11/62] optimised Resolver.preSolveVelocity --- src/collision/Resolver.js | 80 +++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/src/collision/Resolver.js b/src/collision/Resolver.js index 20e2aed..95ea21c 100644 --- a/src/collision/Resolver.js +++ b/src/collision/Resolver.js @@ -9,8 +9,6 @@ var Resolver = {}; module.exports = Resolver; var Vertices = require('../geometry/Vertices'); -var Vector = require('../geometry/Vector'); -var Common = require('../core/Common'); var Bounds = require('../geometry/Bounds'); (function() { @@ -169,61 +167,53 @@ var Bounds = require('../geometry/Bounds'); * @param {pair[]} pairs */ Resolver.preSolveVelocity = function(pairs) { - var i, - j, - pair, - contacts, - collision, - bodyA, - bodyB, - normal, - tangent, - contact, - contactVertex, - normalImpulse, - tangentImpulse, - offset, - impulse = Vector._temp[0], - tempA = Vector._temp[1]; + var pairsLength = pairs.length, + i, + j; - for (i = 0; i < pairs.length; i++) { - pair = pairs[i]; + for (i = 0; i < pairsLength; i++) { + var pair = pairs[i]; if (!pair.isActive || pair.isSensor) continue; - contacts = pair.activeContacts; - collision = pair.collision; - bodyA = collision.parentA; - bodyB = collision.parentB; - normal = collision.normal; - tangent = collision.tangent; - + var contacts = pair.activeContacts, + contactsLength = contacts.length, + collision = pair.collision, + bodyA = collision.parentA, + bodyB = collision.parentB, + normal = collision.normal, + tangent = collision.tangent; + // resolve each contact - for (j = 0; j < contacts.length; j++) { - contact = contacts[j]; - contactVertex = contact.vertex; - normalImpulse = contact.normalImpulse; - tangentImpulse = contact.tangentImpulse; - + for (j = 0; j < contactsLength; j++) { + var contact = contacts[j], + contactVertex = contact.vertex, + normalImpulse = contact.normalImpulse, + tangentImpulse = contact.tangentImpulse; + if (normalImpulse !== 0 || tangentImpulse !== 0) { // total impulse from contact - impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse); - impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse); + var impulseX = normal.x * normalImpulse + tangent.x * tangentImpulse, + impulseY = normal.y * normalImpulse + tangent.y * tangentImpulse; // apply impulse from contact if (!(bodyA.isStatic || bodyA.isSleeping)) { - offset = Vector.sub(contactVertex, bodyA.position, tempA); - bodyA.positionPrev.x += impulse.x * bodyA.inverseMass; - bodyA.positionPrev.y += impulse.y * bodyA.inverseMass; - bodyA.anglePrev += Vector.cross(offset, impulse) * bodyA.inverseInertia; + bodyA.positionPrev.x += impulseX * bodyA.inverseMass; + bodyA.positionPrev.y += impulseY * bodyA.inverseMass; + bodyA.anglePrev += bodyA.inverseInertia * ( + (contactVertex.x - bodyA.position.x) * impulseY + - (contactVertex.y - bodyA.position.y) * impulseX + ); } - + if (!(bodyB.isStatic || bodyB.isSleeping)) { - offset = Vector.sub(contactVertex, bodyB.position, tempA); - bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass; - bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass; - bodyB.anglePrev -= Vector.cross(offset, impulse) * bodyB.inverseInertia; + bodyB.positionPrev.x -= impulseX * bodyB.inverseMass; + bodyB.positionPrev.y -= impulseY * bodyB.inverseMass; + bodyB.anglePrev -= bodyB.inverseInertia * ( + (contactVertex.x - bodyB.position.x) * impulseY + - (contactVertex.y - bodyB.position.y) * impulseX + ); } } } @@ -281,7 +271,7 @@ var Bounds = require('../geometry/Bounds'); var tangentVelocity = Vector.dot(tangent, relativeVelocity), tangentSpeed = Math.abs(tangentVelocity), tangentVelocityDirection = Common.sign(tangentVelocity); - + // raw impulses var normalImpulse = (1 + pair.restitution) * normalVelocity, normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1) * Resolver._frictionNormalMultiplier; From e4b35d3961f9424c294efe258e30ca99d48c8006 Mon Sep 17 00:00:00 2001 From: liabru Date: Sat, 1 May 2021 14:16:39 +0100 Subject: [PATCH 12/62] optimised Resolver.solveVelocity --- src/collision/Resolver.js | 133 ++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 54 deletions(-) diff --git a/src/collision/Resolver.js b/src/collision/Resolver.js index 95ea21c..835c1fb 100644 --- a/src/collision/Resolver.js +++ b/src/collision/Resolver.js @@ -228,14 +228,17 @@ var Bounds = require('../geometry/Bounds'); */ Resolver.solveVelocity = function(pairs, timeScale) { var timeScaleSquared = timeScale * timeScale, - impulse = Vector._temp[0], - tempA = Vector._temp[1], - tempB = Vector._temp[2], - tempC = Vector._temp[3], - tempD = Vector._temp[4], - tempE = Vector._temp[5]; - - for (var i = 0; i < pairs.length; i++) { + restingThresh = Resolver._restingThresh * timeScaleSquared, + frictionNormalMultiplier = Resolver._frictionNormalMultiplier, + restingThreshTangent = Resolver._restingThreshTangent * timeScaleSquared, + NumberMaxValue = Number.MAX_VALUE, + pairsLength = pairs.length, + tangentImpulse, + maxFriction, + i, + j; + + for (i = 0; i < pairsLength; i++) { var pair = pairs[i]; if (!pair.isActive || pair.isSensor) @@ -244,97 +247,119 @@ var Bounds = require('../geometry/Bounds'); var collision = pair.collision, bodyA = collision.parentA, bodyB = collision.parentB, - normal = collision.normal, - tangent = collision.tangent, + 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, - contactShare = 1 / contacts.length; + contactsLength = contacts.length, + contactShare = 1 / contactsLength, + inverseMassTotal = bodyA.inverseMass + bodyB.inverseMass, + friction = pair.friction * pair.frictionStatic * frictionNormalMultiplier * timeScaleSquared; // update body velocities - bodyA.velocity.x = bodyA.position.x - bodyA.positionPrev.x; - bodyA.velocity.y = bodyA.position.y - bodyA.positionPrev.y; - bodyB.velocity.x = bodyB.position.x - bodyB.positionPrev.x; - bodyB.velocity.y = bodyB.position.y - bodyB.positionPrev.y; + 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; // resolve each contact - for (var j = 0; j < contacts.length; j++) { + for (j = 0; j < contactsLength; j++) { var contact = contacts[j], - contactVertex = contact.vertex, - offsetA = Vector.sub(contactVertex, bodyA.position, tempA), - offsetB = Vector.sub(contactVertex, bodyB.position, tempB), - velocityPointA = Vector.add(bodyA.velocity, Vector.mult(Vector.perp(offsetA), bodyA.angularVelocity), tempC), - velocityPointB = Vector.add(bodyB.velocity, Vector.mult(Vector.perp(offsetB), bodyB.angularVelocity), tempD), - relativeVelocity = Vector.sub(velocityPointA, velocityPointB, tempE), - normalVelocity = Vector.dot(normal, relativeVelocity); + contactVertex = contact.vertex; - var tangentVelocity = Vector.dot(tangent, relativeVelocity), - tangentSpeed = Math.abs(tangentVelocity), - tangentVelocityDirection = Common.sign(tangentVelocity); + var offsetAX = contactVertex.x - bodyA.position.x, + offsetAY = contactVertex.y - bodyA.position.y, + offsetBX = contactVertex.x - bodyB.position.x, + offsetBY = contactVertex.y - bodyB.position.y; - // raw impulses - var normalImpulse = (1 + pair.restitution) * normalVelocity, - normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1) * Resolver._frictionNormalMultiplier; + 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 relativeVelocityX = velocityPointAX - velocityPointBX, + relativeVelocityY = velocityPointAY - velocityPointBY; + + var normalVelocity = normalX * relativeVelocityX + normalY * relativeVelocityY, + tangentVelocity = tangentX * relativeVelocityX + tangentY * relativeVelocityY; // coulomb friction - var tangentImpulse = tangentVelocity, - maxFriction = Infinity; + var normalOverlap = pair.separation + normalVelocity; + var normalForce = normalOverlap > 1 ? 1 : normalOverlap; + normalForce = normalOverlap < 0 ? 0 : normalForce; + + var frictionLimit = normalForce * friction; - if (tangentSpeed > pair.friction * pair.frictionStatic * normalForce * timeScaleSquared) { - maxFriction = tangentSpeed; - tangentImpulse = Common.clamp( - pair.friction * tangentVelocityDirection * timeScaleSquared, - -maxFriction, maxFriction - ); + if (tangentVelocity > frictionLimit || -tangentVelocity > frictionLimit) { + maxFriction = tangentVelocity > 0 ? tangentVelocity : -tangentVelocity; + tangentImpulse = pair.friction * (tangentVelocity > 0 ? 1 : -1) * timeScaleSquared; + + if (tangentImpulse < -maxFriction) { + tangentImpulse = -maxFriction; + } else if (tangentImpulse > maxFriction) { + tangentImpulse = maxFriction; + } + } else { + tangentImpulse = tangentVelocity; + maxFriction = NumberMaxValue; } - // modify impulses accounting for mass, inertia and offset - var oAcN = Vector.cross(offsetA, normal), - oBcN = Vector.cross(offsetB, normal), - share = contactShare / (bodyA.inverseMass + bodyB.inverseMass + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN); + // account for mass, inertia and contact offset + var oAcN = offsetAX * normalY - offsetAY * normalX, + oBcN = offsetBX * normalY - offsetBY * normalX, + share = contactShare / (inverseMassTotal + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN); - normalImpulse *= share; + // raw impulses + var normalImpulse = (1 + pair.restitution) * normalVelocity * share; tangentImpulse *= share; // handle high velocity and resting collisions separately - if (normalVelocity < 0 && normalVelocity * normalVelocity > Resolver._restingThresh * timeScaleSquared) { + if (normalVelocity * normalVelocity > restingThresh && normalVelocity < 0) { // high normal velocity so clear cached contact normal impulse contact.normalImpulse = 0; } else { // solve resting collision constraints using Erin Catto's method (GDC08) // impulse constraint tends to 0 var contactNormalImpulse = contact.normalImpulse; - contact.normalImpulse = Math.min(contact.normalImpulse + normalImpulse, 0); + contact.normalImpulse += normalImpulse; + contact.normalImpulse = contact.normalImpulse < 0 ? contact.normalImpulse : 0; normalImpulse = contact.normalImpulse - contactNormalImpulse; } // handle high velocity and resting collisions separately - if (tangentVelocity * tangentVelocity > Resolver._restingThreshTangent * timeScaleSquared) { + if (tangentVelocity * tangentVelocity > restingThreshTangent) { // high tangent velocity so clear cached contact tangent impulse contact.tangentImpulse = 0; } else { // solve resting collision constraints using Erin Catto's method (GDC08) // tangent impulse tends to -tangentSpeed or +tangentSpeed var contactTangentImpulse = contact.tangentImpulse; - contact.tangentImpulse = Common.clamp(contact.tangentImpulse + tangentImpulse, -maxFriction, maxFriction); + contact.tangentImpulse += tangentImpulse; + if (contact.tangentImpulse < -maxFriction) contact.tangentImpulse = -maxFriction; + if (contact.tangentImpulse > maxFriction) contact.tangentImpulse = maxFriction; tangentImpulse = contact.tangentImpulse - contactTangentImpulse; } // total impulse from contact - impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse); - impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse); + var impulseX = normalX * normalImpulse + tangentX * tangentImpulse, + impulseY = normalY * normalImpulse + tangentY * tangentImpulse; // apply impulse from contact if (!(bodyA.isStatic || bodyA.isSleeping)) { - bodyA.positionPrev.x += impulse.x * bodyA.inverseMass; - bodyA.positionPrev.y += impulse.y * bodyA.inverseMass; - bodyA.anglePrev += Vector.cross(offsetA, impulse) * bodyA.inverseInertia; + bodyA.positionPrev.x += impulseX * bodyA.inverseMass; + bodyA.positionPrev.y += impulseY * bodyA.inverseMass; + bodyA.anglePrev += (offsetAX * impulseY - offsetAY * impulseX) * bodyA.inverseInertia; } if (!(bodyB.isStatic || bodyB.isSleeping)) { - bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass; - bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass; - bodyB.anglePrev -= Vector.cross(offsetB, impulse) * bodyB.inverseInertia; + bodyB.positionPrev.x -= impulseX * bodyB.inverseMass; + bodyB.positionPrev.y -= impulseY * bodyB.inverseMass; + bodyB.anglePrev -= (offsetBX * impulseY - offsetBY * impulseX) * bodyB.inverseInertia; } } } From efede6e22d010507af4e2ffa0ce20b722df4721b Mon Sep 17 00:00:00 2001 From: liabru Date: Sat, 1 May 2021 14:31:08 +0100 Subject: [PATCH 13/62] optimised Detector.collisions --- src/collision/Detector.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/collision/Detector.js b/src/collision/Detector.js index 18cf0ab..1327eb0 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -25,20 +25,26 @@ var Bounds = require('../geometry/Bounds'); */ Detector.collisions = function(broadphasePairs, engine) { var collisions = [], - pairsTable = engine.pairs.table; + pairsTable = engine.pairs.table, + broadphasePairsLength = broadphasePairs.length, + canCollide = Detector.canCollide, + overlaps = Bounds.overlaps, + collides = SAT.collides, + pairId = Pair.id, + i; - for (var i = 0; i < broadphasePairs.length; i++) { + for (i = 0; i < broadphasePairsLength; i++) { var bodyA = broadphasePairs[i][0], bodyB = broadphasePairs[i][1]; if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping)) continue; - if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) + if (!canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) continue; // mid phase - if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) { + if (overlaps(bodyA.bounds, bodyB.bounds)) { var partsALength = bodyA.parts.length, partsBLength = bodyB.parts.length; @@ -48,13 +54,12 @@ var Bounds = require('../geometry/Bounds'); for (var k = partsBLength > 1 ? 1 : 0; k < partsBLength; k++) { var partB = bodyB.parts[k]; - if ((partA === bodyA && partB === bodyB) || Bounds.overlaps(partA.bounds, partB.bounds)) { + if ((partA === bodyA && partB === bodyB) || overlaps(partA.bounds, partB.bounds)) { // find a previous collision we could reuse - var pairId = Pair.id(partA, partB), - pair = pairsTable[pairId]; + var pair = pairsTable[pairId(partA, partB)]; // narrow phase - var collision = SAT.collides(partA, partB, pair && pair.isActive ? pair.collision : null); + var collision = collides(partA, partB, pair && pair.isActive ? pair.collision : null); if (collision.collided) { collisions.push(collision); From 0d90a17f2d3c1884a76df53f0238832f35cad448 Mon Sep 17 00:00:00 2001 From: liabru Date: Sat, 1 May 2021 15:56:35 +0100 Subject: [PATCH 14/62] optimised SAT._findSupports --- src/collision/SAT.js | 73 +++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/src/collision/SAT.js b/src/collision/SAT.js index 29a58c3..a06f7ef 100644 --- a/src/collision/SAT.js +++ b/src/collision/SAT.js @@ -120,7 +120,7 @@ var Vector = require('../geometry/Vector'); collision.penetration.y = collision.normal.y * collision.depth; // find support points, there is always either exactly one or two - var verticesB = SAT._findSupports(bodyA, bodyB, collision.normal), + var verticesB = SAT._findSupports(bodyA, bodyB, collision.normal, 1), supports = []; // find the supports from bodyB that are inside bodyA @@ -132,8 +132,8 @@ var Vector = require('../geometry/Vector'); // find the supports from bodyA that are inside bodyB if (supports.length < 2) { - var verticesA = SAT._findSupports(bodyB, bodyA, Vector.neg(collision.normal)); - + var verticesA = SAT._findSupports(bodyB, bodyA, collision.normal, -1); + if (Vertices.contains(bodyB.vertices, verticesA[0])) supports.push(verticesA[0]); @@ -183,7 +183,7 @@ var Vector = require('../geometry/Vector'); result.overlap = overlap; result.axis = axis; result.axisNumber = i; - } + } } return result; @@ -214,57 +214,54 @@ var Vector = require('../geometry/Vector'); projection.min = min; projection.max = max; }; - + /** * Finds supporting vertices given two bodies along a given direction using hill-climbing. * @method _findSupports * @private - * @param {} bodyA - * @param {} bodyB - * @param {} normal + * @param {body} bodyA + * @param {body} bodyB + * @param {vector} normal + * @param {number} direction * @return [vector] */ - SAT._findSupports = function(bodyA, bodyB, normal) { - var nearestDistance = Number.MAX_VALUE, - vertexToBody = Vector._temp[0], - vertices = bodyB.vertices, - bodyAPosition = bodyA.position, - distance, - vertex, + SAT._findSupports = function(bodyA, bodyB, normal, direction) { + var vertices = bodyB.vertices, + verticesLength = vertices.length, + bodyAPositionX = bodyA.position.x, + bodyAPositionY = bodyA.position.y, + normalX = normal.x * direction, + normalY = normal.y * direction, + nearestDistance = Infinity, vertexA, - vertexB; + vertexB, + vertexC, + distance, + j; - // find closest vertex on bodyB - for (var i = 0; i < vertices.length; i++) { - vertex = vertices[i]; - vertexToBody.x = vertex.x - bodyAPosition.x; - vertexToBody.y = vertex.y - bodyAPosition.y; - distance = -Vector.dot(normal, vertexToBody); + // find deepest vertex relative to the axis + for (j = 0; j < verticesLength; j += 1) { + vertexB = vertices[j]; + distance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y); + // convex hill-climbing if (distance < nearestDistance) { nearestDistance = distance; - vertexA = vertex; + vertexA = vertexB; } } - // find next closest vertex using the two connected to it - var prevIndex = vertexA.index - 1 >= 0 ? vertexA.index - 1 : vertices.length - 1; - vertex = vertices[prevIndex]; - vertexToBody.x = vertex.x - bodyAPosition.x; - vertexToBody.y = vertex.y - bodyAPosition.y; - nearestDistance = -Vector.dot(normal, vertexToBody); - vertexB = vertex; + // measure next vertex + vertexC = vertices[(verticesLength + vertexA.index - 1) % verticesLength]; + nearestDistance = normalX * (bodyAPositionX - vertexC.x) + normalY * (bodyAPositionY - vertexC.y); - var nextIndex = (vertexA.index + 1) % vertices.length; - vertex = vertices[nextIndex]; - vertexToBody.x = vertex.x - bodyAPosition.x; - vertexToBody.y = vertex.y - bodyAPosition.y; - distance = -Vector.dot(normal, vertexToBody); - if (distance < nearestDistance) { - vertexB = vertex; + // compare with previous vertex + vertexB = vertices[(vertexA.index + 1) % verticesLength]; + if (normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y) < nearestDistance) { + return [vertexA, vertexB]; } - return [vertexA, vertexB]; + return [vertexA, vertexC]; }; })(); From 2096961846afebb9da8d8a206aecb6d250b22a3e Mon Sep 17 00:00:00 2001 From: liabru Date: Sat, 1 May 2021 16:20:27 +0100 Subject: [PATCH 15/62] optimised SAT._overlapAxes --- src/collision/SAT.js | 102 ++++++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 30 deletions(-) diff --git a/src/collision/SAT.js b/src/collision/SAT.js index a06f7ef..6491743 100644 --- a/src/collision/SAT.js +++ b/src/collision/SAT.js @@ -15,6 +15,18 @@ var Vector = require('../geometry/Vector'); (function() { + var _overlapAB = { + overlap: 0, + axis: null, + axisNumber: 0 + }; + + var _overlapBA = { + overlap: 0, + axis: null, + axisNumber: 0 + }; + /** * Detect collision between two bodies using the Separating Axis Theorem. * @method collides @@ -24,9 +36,7 @@ var Vector = require('../geometry/Vector'); * @return {collision} collision */ SAT.collides = function(bodyA, bodyB, previousCollision) { - var overlapAB, - overlapBA, - minOverlap, + var minOverlap, collision, canReusePrevCol = false; @@ -54,35 +64,37 @@ var Vector = require('../geometry/Vector'); axisBodyB = axisBodyA === bodyA ? bodyB : bodyA, axes = [axisBodyA.axes[previousCollision.axisNumber]]; - minOverlap = SAT._overlapAxes(axisBodyA.vertices, axisBodyB.vertices, axes); + SAT._overlapAxes(_overlapAB, axisBodyA.vertices, axisBodyB.vertices, axes); collision.reused = true; - if (minOverlap.overlap <= 0) { + if (_overlapAB.overlap <= 0) { collision.collided = false; return collision; } + + minOverlap = _overlapAB; } else { // if we can't reuse a result, perform a full SAT test - overlapAB = SAT._overlapAxes(bodyA.vertices, bodyB.vertices, bodyA.axes); + SAT._overlapAxes(_overlapAB, bodyA.vertices, bodyB.vertices, bodyA.axes); - if (overlapAB.overlap <= 0) { + if (_overlapAB.overlap <= 0) { collision.collided = false; return collision; } - overlapBA = SAT._overlapAxes(bodyB.vertices, bodyA.vertices, bodyB.axes); + SAT._overlapAxes(_overlapBA, bodyB.vertices, bodyA.vertices, bodyB.axes); - if (overlapBA.overlap <= 0) { + if (_overlapBA.overlap <= 0) { collision.collided = false; return collision; } - if (overlapAB.overlap < overlapBA.overlap) { - minOverlap = overlapAB; + if (_overlapAB.overlap < _overlapBA.overlap) { + minOverlap = _overlapAB; collision.axisBody = bodyA; } else { - minOverlap = overlapBA; + minOverlap = _overlapBA; collision.axisBody = bodyB; } @@ -154,29 +166,61 @@ var Vector = require('../geometry/Vector'); * Find the overlap between two sets of vertices. * @method _overlapAxes * @private - * @param {} verticesA - * @param {} verticesB - * @param {} axes - * @return result + * @param {object} result + * @param {vertices} verticesA + * @param {vertices} verticesB + * @param {axes} axes */ - SAT._overlapAxes = function(verticesA, verticesB, axes) { - var projectionA = Vector._temp[0], - projectionB = Vector._temp[1], - result = { overlap: Number.MAX_VALUE }, + SAT._overlapAxes = function(result, verticesA, verticesB, axes) { + var verticesALength = verticesA.length, + verticesBLength = verticesB.length, + axesLength = axes.length, + dot, overlap, - axis; + overlapAB, + overlapBA, + i, + j; - for (var i = 0; i < axes.length; i++) { - axis = axes[i]; + result.overlap = Number.MAX_VALUE; - SAT._projectToAxis(projectionA, verticesA, axis); - SAT._projectToAxis(projectionB, verticesB, axis); + for (i = 0; i < axesLength; i++) { + var axis = axes[i], + axisX = axis.x, + axisY = axis.y, + minA = verticesA[0].x * axisX + verticesA[0].y * axisY, + minB = verticesB[0].x * axisX + verticesB[0].y * axisY, + maxA = minA, + maxB = minB; + + for (j = 1; j < verticesALength; j += 1) { + dot = verticesA[j].x * axisX + verticesA[j].y * axisY; - overlap = Math.min(projectionA.max - projectionB.min, projectionB.max - projectionA.min); + if (dot > maxA) { + maxA = dot; + } else if (dot < minA) { + minA = dot; + } + } + + for (j = 1; j < verticesBLength; j += 1) { + dot = verticesB[j].x * axisX + verticesB[j].y * axisY; + + if (dot > maxB) { + maxB = dot; + } else if (dot < minB) { + minB = dot; + } + } + + overlapAB = maxA - minB; + overlapBA = maxB - minA; + overlap = overlapAB < overlapBA ? overlapAB : overlapBA; if (overlap <= 0) { result.overlap = overlap; - return result; + result.axisNumber = i; + return; } if (overlap < result.overlap) { @@ -185,8 +229,6 @@ var Vector = require('../geometry/Vector'); result.axisNumber = i; } } - - return result; }; /** @@ -232,7 +274,7 @@ var Vector = require('../geometry/Vector'); bodyAPositionY = bodyA.position.y, normalX = normal.x * direction, normalY = normal.y * direction, - nearestDistance = Infinity, + nearestDistance = Number.MAX_VALUE, vertexA, vertexB, vertexC, From 0af144c78bd0942c7014ba48da7cf1864d3a2e54 Mon Sep 17 00:00:00 2001 From: liabru Date: Sat, 1 May 2021 21:56:22 +0100 Subject: [PATCH 16/62] optimised SAT.collides --- src/collision/Detector.js | 6 +- src/collision/SAT.js | 146 +++++++++++++++++++++++--------------- 2 files changed, 89 insertions(+), 63 deletions(-) diff --git a/src/collision/Detector.js b/src/collision/Detector.js index 1327eb0..9b4e1bc 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -55,11 +55,9 @@ var Bounds = require('../geometry/Bounds'); var partB = bodyB.parts[k]; if ((partA === bodyA && partB === bodyB) || overlaps(partA.bounds, partB.bounds)) { - // find a previous collision we could reuse - var pair = pairsTable[pairId(partA, partB)]; - // narrow phase - var collision = collides(partA, partB, pair && pair.isActive ? pair.collision : null); + var pair = pairsTable[pairId(partA, partB)]; + var collision = collides(partA, partB, pair && pair.collision, pair && pair.isActive); if (collision.collided) { collisions.push(collision); diff --git a/src/collision/SAT.js b/src/collision/SAT.js index 6491743..2ac56c6 100644 --- a/src/collision/SAT.js +++ b/src/collision/SAT.js @@ -27,20 +27,47 @@ var Vector = require('../geometry/Vector'); axisNumber: 0 }; + /** + * Creates a new collision record. + * @private + * @method create + * @param {body} bodyA + * @param {body} bodyB + * @return {collision} A new collision + */ + SAT.create = function(bodyA, bodyB) { + return { + collided: false, + bodyA: bodyA, + bodyB: bodyB, + parentA: bodyA.parent, + parentB: bodyB.parent, + axisBodyA: bodyA, + axisBodyB: bodyB, + axisNumber: 0, + depth: 0, + normal: { x: 0, y: 0 }, + tangent: { x: 0, y: 0 }, + penetration: { x: 0, y: 0 }, + supports: [] + }; + }; + /** * Detect collision between two bodies using the Separating Axis Theorem. * @method collides * @param {body} bodyA * @param {body} bodyB - * @param {collision} previousCollision + * @param {?collision} previousCollision + * @param {?boolean} [previousPairActive=false] * @return {collision} collision */ - SAT.collides = function(bodyA, bodyB, previousCollision) { + SAT.collides = function(bodyA, bodyB, previousCollision, pairActive) { var minOverlap, - collision, - canReusePrevCol = false; + collision = previousCollision || SAT.create(bodyA, bodyB), + canReusePrevCol; - if (previousCollision) { + if (pairActive && previousCollision.collided) { // estimate total motion var parentA = bodyA.parent, parentB = bodyB.parent, @@ -49,24 +76,18 @@ var Vector = require('../geometry/Vector'); // we may be able to (partially) reuse collision result // but only safe if collision was resting - canReusePrevCol = previousCollision && previousCollision.collided && motion < 0.2; - - // reuse collision object - collision = previousCollision; - } else { - collision = { collided: false, bodyA: bodyA, bodyB: bodyB }; + canReusePrevCol = motion < 0.2; } - if (previousCollision && canReusePrevCol) { + if (canReusePrevCol) { // if we can reuse the collision result // we only need to test the previously found axis - var axisBodyA = collision.axisBody, - axisBodyB = axisBodyA === bodyA ? bodyB : bodyA, + var axisBodyA = previousCollision.axisBodyA, + axisBodyB = previousCollision.axisBodyB, axes = [axisBodyA.axes[previousCollision.axisNumber]]; SAT._overlapAxes(_overlapAB, axisBodyA.vertices, axisBodyB.vertices, axes); - collision.reused = true; - + if (_overlapAB.overlap <= 0) { collision.collided = false; return collision; @@ -92,10 +113,12 @@ var Vector = require('../geometry/Vector'); if (_overlapAB.overlap < _overlapBA.overlap) { minOverlap = _overlapAB; - collision.axisBody = bodyA; + collision.axisBodyA = bodyA; + collision.axisBodyB = bodyB; } else { minOverlap = _overlapBA; - collision.axisBody = bodyB; + collision.axisBodyA = bodyB; + collision.axisBodyB = bodyA; } // important for reuse later @@ -112,52 +135,51 @@ var Vector = require('../geometry/Vector'); bodyA = collision.bodyA; bodyB = collision.bodyB; + var normal = collision.normal, + supports = collision.supports; + // ensure normal is facing away from bodyA if (Vector.dot(minOverlap.axis, Vector.sub(bodyB.position, bodyA.position)) < 0) { - collision.normal = { - x: minOverlap.axis.x, - y: minOverlap.axis.y - }; + normal.x = minOverlap.axis.x; + normal.y = minOverlap.axis.y; } else { - collision.normal = { - x: -minOverlap.axis.x, - y: -minOverlap.axis.y - }; + normal.x = -minOverlap.axis.x; + normal.y = -minOverlap.axis.y; } - collision.tangent = Vector.perp(collision.normal); + collision.tangent.x = -normal.y; + collision.tangent.y = normal.x; - collision.penetration = collision.penetration || {}; - collision.penetration.x = collision.normal.x * collision.depth; - collision.penetration.y = collision.normal.y * collision.depth; + collision.penetration.x = normal.x * collision.depth; + collision.penetration.y = normal.y * collision.depth; // find support points, there is always either exactly one or two - var verticesB = SAT._findSupports(bodyA, bodyB, collision.normal, 1), - supports = []; + var supportsB = SAT._findSupports(bodyA, bodyB, collision.normal, 1); + + // clear supports + supports.length = 0; // find the supports from bodyB that are inside bodyA - if (Vertices.contains(bodyA.vertices, verticesB[0])) - supports.push(verticesB[0]); + if (Vertices.contains(bodyA.vertices, supportsB[0])) + supports.push(supportsB[0]); - if (Vertices.contains(bodyA.vertices, verticesB[1])) - supports.push(verticesB[1]); + if (Vertices.contains(bodyA.vertices, supportsB[1])) + supports.push(supportsB[1]); // find the supports from bodyA that are inside bodyB if (supports.length < 2) { - var verticesA = SAT._findSupports(bodyB, bodyA, collision.normal, -1); + var supportsA = SAT._findSupports(bodyB, bodyA, collision.normal, -1); - if (Vertices.contains(bodyB.vertices, verticesA[0])) - supports.push(verticesA[0]); + if (Vertices.contains(bodyB.vertices, supportsA[0])) + supports.push(supportsA[0]); - if (supports.length < 2 && Vertices.contains(bodyB.vertices, verticesA[1])) - supports.push(verticesA[1]); + if (supports.length < 2 && Vertices.contains(bodyB.vertices, supportsA[1])) + supports.push(supportsA[1]); } // account for the edge case of overlapping but no vertex containment - if (supports.length < 1) - supports = [verticesB[0]]; - - collision.supports = supports; + if (supports.length === 0) + supports.push(supportsB[0]); return collision; }; @@ -174,22 +196,26 @@ var Vector = require('../geometry/Vector'); SAT._overlapAxes = function(result, verticesA, verticesB, axes) { var verticesALength = verticesA.length, verticesBLength = verticesB.length, + verticesAX = verticesA[0].x, + verticesAY = verticesA[0].y, + verticesBX = verticesB[0].x, + verticesBY = verticesB[0].y, axesLength = axes.length, - dot, + overlapMin = Number.MAX_VALUE, + overlapAxisNumber = 0, overlap, overlapAB, overlapBA, + dot, i, j; - result.overlap = Number.MAX_VALUE; - for (i = 0; i < axesLength; i++) { var axis = axes[i], axisX = axis.x, axisY = axis.y, - minA = verticesA[0].x * axisX + verticesA[0].y * axisY, - minB = verticesB[0].x * axisX + verticesB[0].y * axisY, + minA = verticesAX * axisX + verticesAY * axisY, + minB = verticesBX * axisX + verticesBY * axisY, maxA = minA, maxB = minB; @@ -217,18 +243,20 @@ var Vector = require('../geometry/Vector'); overlapBA = maxB - minA; overlap = overlapAB < overlapBA ? overlapAB : overlapBA; - if (overlap <= 0) { - result.overlap = overlap; - result.axisNumber = i; - return; - } + if (overlap < overlapMin) { + overlapMin = overlap; + overlapAxisNumber = i; - if (overlap < result.overlap) { - result.overlap = overlap; - result.axis = axis; - result.axisNumber = i; + if (overlap <= 0) { + // can not be intersecting + break; + } } } + + result.axis = axes[overlapAxisNumber]; + result.axisNumber = overlapAxisNumber; + result.overlap = overlapMin; }; /** From 5e3c629b90b8ab3cc88d5c0571c41a3346a0d0aa Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 2 May 2021 23:05:21 +0100 Subject: [PATCH 17/62] optimised Grid._bucketAddBody --- src/collision/Grid.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/collision/Grid.js b/src/collision/Grid.js index 55b62ef..f7fce43 100644 --- a/src/collision/Grid.js +++ b/src/collision/Grid.js @@ -227,8 +227,13 @@ var Common = require('../core/Common'); * @param {} body */ Grid._bucketAddBody = function(grid, bucket, body) { + var gridPairs = grid.pairs, + pairId = Pair.id, + bucketLength = bucket.length, + i; + // add new pairs - for (var i = 0; i < bucket.length; i++) { + for (i = 0; i < bucketLength; i++) { var bodyB = bucket[i]; if (body.id === bodyB.id || (body.isStatic && bodyB.isStatic)) @@ -236,13 +241,13 @@ var Common = require('../core/Common'); // keep track of the number of buckets the pair exists in // important for Grid.update to work - var pairId = Pair.id(body, bodyB), - pair = grid.pairs[pairId]; + var id = pairId(body, bodyB), + pair = gridPairs[id]; if (pair) { pair[2] += 1; } else { - grid.pairs[pairId] = [body, bodyB, 1]; + gridPairs[id] = [body, bodyB, 1]; } } From 84136de6a472f1a7f3ee87e1c5d448a07d925fcb Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 2 May 2021 23:05:43 +0100 Subject: [PATCH 18/62] optimised Grid._bucketRemoveBody --- src/collision/Grid.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/collision/Grid.js b/src/collision/Grid.js index f7fce43..d3a523b 100644 --- a/src/collision/Grid.js +++ b/src/collision/Grid.js @@ -264,16 +264,20 @@ var Common = require('../core/Common'); * @param {} body */ Grid._bucketRemoveBody = function(grid, bucket, body) { + var gridPairs = grid.pairs, + pairId = Pair.id, + i; + // remove from bucket bucket.splice(Common.indexOf(bucket, body), 1); + var bucketLength = bucket.length; + // update pair counts - for (var i = 0; i < bucket.length; i++) { + for (i = 0; i < bucketLength; i++) { // keep track of the number of buckets the pair exists in // important for _createActivePairsList to work - var bodyB = bucket[i], - pairId = Pair.id(body, bodyB), - pair = grid.pairs[pairId]; + var pair = gridPairs[pairId(body, bucket[i])]; if (pair) pair[2] -= 1; From e4b3bcd69e71666acfb62681e6400c276aaedeaa Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 2 May 2021 23:06:21 +0100 Subject: [PATCH 19/62] optimised Grid._createActivePairsList --- src/collision/Grid.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/collision/Grid.js b/src/collision/Grid.js index d3a523b..6bcc1c5 100644 --- a/src/collision/Grid.js +++ b/src/collision/Grid.js @@ -292,23 +292,23 @@ var Common = require('../core/Common'); * @return [] pairs */ Grid._createActivePairsList = function(grid) { - var pairKeys, - pair, - pairs = []; - - // grid.pairs is used as a hashmap - pairKeys = Common.keys(grid.pairs); + var pair, + gridPairs = grid.pairs, + pairKeys = Common.keys(gridPairs), + pairKeysLength = pairKeys.length, + pairs = [], + k; // iterate over grid.pairs - for (var k = 0; k < pairKeys.length; k++) { - pair = grid.pairs[pairKeys[k]]; + for (k = 0; k < pairKeysLength; k++) { + pair = gridPairs[pairKeys[k]]; // if pair exists in at least one bucket // it is a pair that needs further collision testing so push it if (pair[2] > 0) { pairs.push(pair); } else { - delete grid.pairs[pairKeys[k]]; + delete gridPairs[pairKeys[k]]; } } From 30b899c86263ac717e18005e493d23ebe047cb08 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 2 May 2021 23:06:50 +0100 Subject: [PATCH 20/62] optimised Resolver.solveVelocity --- src/collision/Resolver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/collision/Resolver.js b/src/collision/Resolver.js index 835c1fb..00302a5 100644 --- a/src/collision/Resolver.js +++ b/src/collision/Resolver.js @@ -290,7 +290,7 @@ var Bounds = require('../geometry/Bounds'); // coulomb friction var normalOverlap = pair.separation + normalVelocity; - var normalForce = normalOverlap > 1 ? 1 : normalOverlap; + var normalForce = Math.min(normalOverlap, 1); normalForce = normalOverlap < 0 ? 0 : normalForce; var frictionLimit = normalForce * friction; @@ -327,7 +327,7 @@ var Bounds = require('../geometry/Bounds'); // impulse constraint tends to 0 var contactNormalImpulse = contact.normalImpulse; contact.normalImpulse += normalImpulse; - contact.normalImpulse = contact.normalImpulse < 0 ? contact.normalImpulse : 0; + contact.normalImpulse = Math.min(contact.normalImpulse, 0); normalImpulse = contact.normalImpulse - contactNormalImpulse; } From cd289ec279b421500d39fc7beff45639c01a3ec6 Mon Sep 17 00:00:00 2001 From: liabru Date: Fri, 19 Nov 2021 20:30:12 +0000 Subject: [PATCH 21/62] improve performance measurement accuracy in tests --- package.json | 4 ++-- test/ExampleWorker.js | 2 ++ test/Examples.spec.js | 3 ++- test/TestTools.js | 7 +++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 2bdb97d..0a5c29a 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "test-all": "jest --no-cache", "test-save": "SAVE=true npm run test-node", "test-watch": "npm run test-node -- --watch", - "test-node": "jest --no-cache ./test/Examples.spec.js", - "test-browser": "jest --no-cache ./test/Browser.spec.js", + "test-node": "node --expose-gc node_modules/.bin/jest --no-cache --runInBand ./test/Examples.spec.js", + "test-browser": "node --expose-gc node_modules/.bin/jest --no-cache --runInBand ./test/Browser.spec.js", "changelog": "conventional-changelog -i CHANGELOG.md -s -r", "release": "npm version --no-git-tag-version", "preversion": "git checkout master && npm run lint && SAVE=true npm run test-all", diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index ba13118..64285c2 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -30,6 +30,8 @@ const runExample = options => { const consoleOriginal = global.console; const logs = []; + global.gc(); + mock('matter-js', Matter); global.Matter = Matter; diff --git a/test/Examples.spec.js b/test/Examples.spec.js index dcc7e41..b20a0c9 100644 --- a/test/Examples.spec.js +++ b/test/Examples.spec.js @@ -30,7 +30,8 @@ const examples = Object.keys(Example).filter(key => { const runExamples = async useDev => { const worker = new Worker(require.resolve('./ExampleWorker'), { - enableWorkerThreads: true + enableWorkerThreads: true, + numWorkers: 1 }); const result = await Promise.all(examples.map(name => worker.runExample({ diff --git a/test/TestTools.js b/test/TestTools.js index 129d70b..1b21767 100644 --- a/test/TestTools.js +++ b/test/TestTools.js @@ -256,9 +256,8 @@ const comparisonReport = (capturesDev, capturesBuild, buildVersion, save) => { let perfChange = 1 - (totalTimeDev / totalTimeBuild); - const perfChangeThreshold = 0.075; - const perfChangeLarge = Math.abs(perfChange) > perfChangeThreshold; - perfChange = perfChangeLarge ? perfChange : 0; + const perfChangeThreshold = 0.01; + perfChange = Math.abs(perfChange) > perfChangeThreshold ? perfChange : 0; let similarityAvg = 0; similarityEntries.forEach(([_, similarity]) => { @@ -288,7 +287,7 @@ const comparisonReport = (capturesDev, capturesBuild, buildVersion, save) => { } return output; }, '\n\n'), - `\nwhere · no change ● extrinsics changed ◆ intrinsics changed\n`, + `\n\nwhere · no change ● extrinsics changed ◆ intrinsics changed\n`, similarityAvg < 1 ? `\n${format('▶', colors.White)} ${format(compareCommand + '=' + 120 + '#' + similarityEntries[0][0], colors.BrightCyan)}` : '', intrinsicChangeCount > 0 ? `\n${format('▶', colors.White)} ${format((save ? diffCommand : diffSaveCommand), colors.BrightCyan)}` : '' ].join(' '); From bedf84cacb7d6efbe46c249dc0a1525cabb009b2 Mon Sep 17 00:00:00 2001 From: liabru Date: Sat, 20 Nov 2021 12:27:14 +0000 Subject: [PATCH 22/62] added memory comparison to tests --- test/ExampleWorker.js | 10 +++++++--- test/TestTools.js | 25 +++++++++++++++++++------ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 64285c2..9017132 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -30,8 +30,6 @@ const runExample = options => { const consoleOriginal = global.console; const logs = []; - global.gc(); - mock('matter-js', Matter); global.Matter = Matter; @@ -47,6 +45,7 @@ const runExample = options => { const example = Example[options.name](); const engine = example.engine; + let totalMemory = 0; let totalDuration = 0; let overlapTotal = 0; let overlapCount = 0; @@ -64,13 +63,17 @@ const runExample = options => { } } + global.gc(); + for (let i = 0; i < options.totalUpdates; i += 1) { const startTime = process.hrtime(); - + totalMemory += process.memoryUsage().heapUsed; + Matter.Engine.update(engine, 1000 / 60); const duration = process.hrtime(startTime); totalDuration += duration[0] * 1e9 + duration[1]; + totalMemory += process.memoryUsage().heapUsed; for (let p = 0; p < engine.pairs.list.length; p += 1) { const pair = engine.pairs.list[p]; @@ -93,6 +96,7 @@ const runExample = options => { name: options.name, duration: totalDuration, overlap: overlapTotal / (overlapCount || 1), + memory: totalMemory, logs, ...engineCapture(engine) }; diff --git a/test/TestTools.js b/test/TestTools.js index 1b21767..4f13f41 100644 --- a/test/TestTools.js +++ b/test/TestTools.js @@ -32,6 +32,13 @@ const colors = { Red: 31, Green: 32, Yellow: 33, White: 37, BrightWhite: 90, Bri const color = (text, number) => number ? `\x1b[${number}m${text}\x1b[0m` : text; const limit = (val, precision=3) => parseFloat(val.toPrecision(precision)); const toPercent = val => (100 * val).toPrecision(3); +const toPercentRound = val => Math.round(100 * val); + +const noiseThreshold = (val, threshold) => { + const sign = val < 0 ? -1 : 1; + const magnitude = Math.abs(val); + return sign * Math.max(0, magnitude - threshold) / (1 - threshold); +}; const engineCapture = (engine) => ({ timestamp: limit(engine.timing.timestamp), @@ -231,13 +238,19 @@ const comparisonReport = (capturesDev, capturesBuild, buildVersion, save) => { let totalTimeDev = 0; let totalOverlapBuild = 0; let totalOverlapDev = 0; + let totalMemoryBuild = 0; + let totalMemoryDev = 0; const capturePerformance = Object.entries(capturesDev).map(([name]) => { totalTimeBuild += capturesBuild[name].duration; totalTimeDev += capturesDev[name].duration; + totalOverlapBuild += capturesBuild[name].overlap; totalOverlapDev += capturesDev[name].overlap; + totalMemoryBuild += capturesBuild[name].memory; + totalMemoryDev += capturesDev[name].memory; + const changedIntrinsics = !equals(capturesDev[name].intrinsic, capturesBuild[name].intrinsic); if (changedIntrinsics) { capturesDev[name].changedIntrinsics = true; @@ -254,10 +267,8 @@ const comparisonReport = (capturesDev, capturesBuild, buildVersion, save) => { capturePerformance.sort((a, b) => a.name.localeCompare(b.name)); similarityEntries.sort((a, b) => a[1] - b[1]); - let perfChange = 1 - (totalTimeDev / totalTimeBuild); - - const perfChangeThreshold = 0.01; - perfChange = Math.abs(perfChange) > perfChangeThreshold ? perfChange : 0; + let perfChange = noiseThreshold(1 - (totalTimeDev / totalTimeBuild), 0.01); + let memoryChange = noiseThreshold((totalMemoryDev / totalMemoryBuild) - 1, 0.01); let similarityAvg = 0; similarityEntries.forEach(([_, similarity]) => { @@ -275,9 +286,11 @@ const comparisonReport = (capturesDev, capturesBuild, buildVersion, save) => { `\n\n${format('Similarity', colors.White)}`, `${format(toPercent(similarityAvg), similarityAvg === 1 ? colors.Green : colors.Yellow)}%`, `${format('Performance', colors.White)}`, - `${format((perfChange >= 0 ? '+' : '') + toPercent(perfChange), perfChange >= 0 ? colors.Green : colors.Red)}%`, + `${format((perfChange >= 0 ? '+' : '-') + toPercentRound(Math.abs(perfChange)), perfChange >= 0 ? colors.Green : colors.Red)}%`, + `${format('Memory', colors.White)}`, + `${format((memoryChange >= 0 ? '+' : '-') + toPercentRound(Math.abs(memoryChange)), memoryChange <= 0 ? colors.Green : colors.Red)}%`, `${format('Overlap', colors.White)}`, - `${format((overlapChange >= 0 ? '+' : '') + toPercent(overlapChange), overlapChange > 0 ? colors.Red : colors.Green)}%`, + `${format((overlapChange >= 0 ? '+' : '-') + toPercent(Math.abs(overlapChange)), overlapChange <= 0 ? colors.Green : colors.Red)}%`, capturePerformance.reduce((output, p, i) => { output += `${p.name} `; output += `${similarityRatings(similaritys[p.name])} `; From 8bfaff0cce8f9fceb32c74f38c466b2ffae9fd04 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 21 Nov 2021 16:51:19 +0000 Subject: [PATCH 23/62] added support for build metadata in Plugin.versionParse --- src/core/Plugin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/Plugin.js b/src/core/Plugin.js index d3265c7..4513cee 100644 --- a/src/core/Plugin.js +++ b/src/core/Plugin.js @@ -240,7 +240,7 @@ var Common = require('./Common'); */ Plugin.dependencyParse = function(dependency) { if (Common.isString(dependency)) { - var pattern = /^[\w-]+(@(\*|[\^~]?\d+\.\d+\.\d+(-[0-9A-Za-z-]+)?))?$/; + var pattern = /^[\w-]+(@(\*|[\^~]?\d+\.\d+\.\d+(-[0-9A-Za-z-+]+)?))?$/; if (!pattern.test(dependency)) { Common.warn('Plugin.dependencyParse:', dependency, 'is not a valid dependency string.'); @@ -275,7 +275,7 @@ var Common = require('./Common'); * @return {object} The version range parsed into its components. */ Plugin.versionParse = function(range) { - var pattern = /^(\*)|(\^|~|>=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-]+)?$/; + var pattern = /^(\*)|(\^|~|>=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-+]+)?$/; if (!pattern.test(range)) { Common.warn('Plugin.versionParse:', range, 'is not a valid version or range.'); From 55feb89f95883bd07457dec67af75656d09fdb0b Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 21 Nov 2021 16:58:29 +0000 Subject: [PATCH 24/62] changed tests to use a production build rather than source --- .gitignore | 2 ++ package.json | 5 +++-- test/ExampleWorker.js | 2 +- test/TestTools.js | 2 +- webpack.config.js | 9 ++++----- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index b5b01b1..98d9f7f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ docs matter-doc-theme build/matter-dev.js build/matter-dev.min.js +build/matter.dev.js +build/matter.dev.min.js demo/js/lib/matter-dev.js demo/js/Examples.min.js examples/build diff --git a/package.json b/package.json index 0a5c29a..eadf1d5 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "serve": "webpack-dev-server --no-cache --mode development --config webpack.demo.config.js", "watch": "nodemon --watch webpack.demo.config.js --exec \"npm run serve\"", "build": "webpack --mode=production --no-hot --no-watch & webpack --mode=production --no-hot --no-watch --env.MINIMIZE", - "build-alpha": "webpack --mode=production --env.ALPHA & webpack --mode=production --env.MINIMIZE --env.ALPHA", + "build-alpha": "webpack --mode=production --no-hot --no-watch --env.KIND=alpha & webpack --mode=production --no-hot --no-watch --env.MINIMIZE --env.KIND=alpha", + "build-dev": "webpack --mode=production --no-hot --no-watch --env.KIND=dev & webpack --mode=production --no-hot --no-watch --env.MINIMIZE --env.KIND=dev", "build-demo": "rm -rf ./demo/js && webpack --no-hot --no-watch --config webpack.demo.config.js --mode=production && webpack --no-hot --no-watch --config webpack.demo.config.js --mode=production --env.MINIMIZE", "lint": "eslint 'src/**/*.js' 'demo/src/**/*.js' 'examples/*.js' 'webpack.*.js'", "doc": "yuidoc --config yuidoc.json --project-version $npm_package_version", @@ -53,7 +54,7 @@ "test-all": "jest --no-cache", "test-save": "SAVE=true npm run test-node", "test-watch": "npm run test-node -- --watch", - "test-node": "node --expose-gc node_modules/.bin/jest --no-cache --runInBand ./test/Examples.spec.js", + "test-node": "npm run build-dev && node --expose-gc node_modules/.bin/jest --no-cache --runInBand ./test/Examples.spec.js", "test-browser": "node --expose-gc node_modules/.bin/jest --no-cache --runInBand ./test/Browser.spec.js", "changelog": "conventional-changelog -i CHANGELOG.md -s -r", "release": "npm version --no-git-tag-version", diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 9017132..8a9b074 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -21,7 +21,7 @@ const reset = M => { const mock = require('mock-require'); const { engineCapture } = require('./TestTools'); -const MatterDev = stubBrowserFeatures(require('../src/module/main')); +const MatterDev = stubBrowserFeatures(require('../build/matter.dev')); const MatterBuild = stubBrowserFeatures(require('../build/matter')); const Example = require('../examples/index'); diff --git a/test/TestTools.js b/test/TestTools.js index 4f13f41..67ce103 100644 --- a/test/TestTools.js +++ b/test/TestTools.js @@ -224,7 +224,7 @@ const logReport = (captures, version) => { } } - return `Output logs from ${color(version, colors.Yellow)} version on last run\n\n` + return `Output logs from ${color(version, colors.Yellow)} build on last run\n\n` + (report ? report : ' None\n'); }; diff --git a/webpack.config.js b/webpack.config.js index 3f8764c..885c8f1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -9,18 +9,17 @@ const execSync = require('child_process').execSync; module.exports = (env = {}) => { const minimize = env.MINIMIZE || false; - const alpha = env.ALPHA || false; + const kind = env.KIND || null; const sizeThreshold = minimize ? 100 * 1024 : 512 * 1024; const commitHash = execSync('git rev-parse --short HEAD').toString().trim(); - const version = !alpha ? pkg.version : `${pkg.version}-alpha+${commitHash}`; + const version = !kind ? pkg.version : `${pkg.version}-${kind}+${commitHash}`; const license = fs.readFileSync('LICENSE', 'utf8'); const resolve = relativePath => path.resolve(__dirname, relativePath); - const alphaInfo = 'Experimental pre-release build.\n '; const banner = `${pkg.name} ${version} by @liabru -${alpha ? alphaInfo : ''}${pkg.homepage} +${kind ? 'Experimental pre-release build.\n ' : ''}${pkg.homepage} License ${pkg.license}${!minimize ? '\n\n' + license : ''}`; return { @@ -32,7 +31,7 @@ License ${pkg.license}${!minimize ? '\n\n' + license : ''}`; umdNamedDefine: true, globalObject: 'this', path: resolve('./build'), - filename: `[name]${alpha ? '.alpha' : ''}${minimize ? '.min' : ''}.js` + filename: `[name]${kind ? '.' + kind : ''}${minimize ? '.min' : ''}.js` }, optimization: { minimize }, performance: { From b3a8aa3cae79742dc4530990777119efe8313b8a Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 21 Nov 2021 17:00:53 +0000 Subject: [PATCH 25/62] added filesize to test comparison report --- test/ExampleWorker.js | 7 +++++-- test/Examples.spec.js | 9 +++++++-- test/TestTools.js | 23 +++++++++++++---------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 8a9b074..791c808 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -75,8 +75,11 @@ const runExample = options => { totalDuration += duration[0] * 1e9 + duration[1]; totalMemory += process.memoryUsage().heapUsed; - for (let p = 0; p < engine.pairs.list.length; p += 1) { - const pair = engine.pairs.list[p]; + const pairsList = engine.pairs.list; + const pairsListLength = engine.pairs.list.length; + + for (let p = 0; p < pairsListLength; p += 1) { + const pair = pairsList[p]; const separation = pair.separation - pair.slop; if (pair.isActive && !pair.isSensor) { diff --git a/test/Examples.spec.js b/test/Examples.spec.js index b20a0c9..efb2dc9 100644 --- a/test/Examples.spec.js +++ b/test/Examples.spec.js @@ -3,6 +3,8 @@ jest.setTimeout(30 * 1000); +const fs = require('fs'); + const { comparisonReport, logReport, @@ -54,11 +56,14 @@ afterAll(async () => { const dev = await capturesDev; const build = await capturesBuild; + const buildSize = fs.statSync('./build/matter.min.js').size; + const devSize = fs.statSync('./build/matter.dev.min.js').size; + console.log( - 'Examples ran against previous release and current version\n\n' + 'Examples ran against previous release and current build\n\n' + logReport(build, `release`) + '\n' + logReport(dev, `current`) + '\n' - + comparisonReport(dev, build, MatterBuild.version, saveComparison) + + comparisonReport(dev, build, devSize, buildSize, MatterBuild.version, saveComparison) ); }); diff --git a/test/TestTools.js b/test/TestTools.js index 67ce103..8504adf 100644 --- a/test/TestTools.js +++ b/test/TestTools.js @@ -228,7 +228,7 @@ const logReport = (captures, version) => { + (report ? report : ' None\n'); }; -const comparisonReport = (capturesDev, capturesBuild, buildVersion, save) => { +const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildVersion, save) => { const similaritys = captureSimilarityExtrinsic(capturesDev, capturesBuild); const similarityEntries = Object.entries(similaritys); const devIntrinsicsChanged = {}; @@ -267,8 +267,10 @@ const comparisonReport = (capturesDev, capturesBuild, buildVersion, save) => { capturePerformance.sort((a, b) => a.name.localeCompare(b.name)); similarityEntries.sort((a, b) => a[1] - b[1]); - let perfChange = noiseThreshold(1 - (totalTimeDev / totalTimeBuild), 0.01); - let memoryChange = noiseThreshold((totalMemoryDev / totalMemoryBuild) - 1, 0.01); + const perfChange = noiseThreshold(1 - (totalTimeDev / totalTimeBuild), 0.01); + const memoryChange = noiseThreshold((totalMemoryDev / totalMemoryBuild) - 1, 0.01); + const overlapChange = (totalOverlapDev / (totalOverlapBuild || 1)) - 1; + const filesizeChange = (devSize / buildSize) - 1; let similarityAvg = 0; similarityEntries.forEach(([_, similarity]) => { @@ -277,20 +279,21 @@ const comparisonReport = (capturesDev, capturesBuild, buildVersion, save) => { similarityAvg /= similarityEntries.length; - const overlapChange = (totalOverlapDev / (totalOverlapBuild || 1)) - 1; - const report = (breakEvery, format) => [ [`Output comparison of ${similarityEntries.length}`, `examples against previous release ${format('matter-js@' + buildVersion, colors.Yellow)}` ].join(' '), `\n\n${format('Similarity', colors.White)}`, `${format(toPercent(similarityAvg), similarityAvg === 1 ? colors.Green : colors.Yellow)}%`, - `${format('Performance', colors.White)}`, - `${format((perfChange >= 0 ? '+' : '-') + toPercentRound(Math.abs(perfChange)), perfChange >= 0 ? colors.Green : colors.Red)}%`, - `${format('Memory', colors.White)}`, - `${format((memoryChange >= 0 ? '+' : '-') + toPercentRound(Math.abs(memoryChange)), memoryChange <= 0 ? colors.Green : colors.Red)}%`, `${format('Overlap', colors.White)}`, - `${format((overlapChange >= 0 ? '+' : '-') + toPercent(Math.abs(overlapChange)), overlapChange <= 0 ? colors.Green : colors.Red)}%`, + `${format((overlapChange >= 0 ? '+' : '-') + toPercent(Math.abs(overlapChange)), overlapChange <= 0 ? colors.Green : colors.Yellow)}%`, + `${format('Performance', colors.White)}`, + `${format((perfChange >= 0 ? '+' : '-') + toPercentRound(Math.abs(perfChange)), perfChange >= 0 ? colors.Green : colors.Yellow)}%`, + `${format('Memory', colors.White)}`, + `${format((memoryChange >= 0 ? '+' : '-') + toPercentRound(Math.abs(memoryChange)), memoryChange <= 0 ? colors.Green : colors.Yellow)}%`, + `${format('Filesize', colors.White)}`, + `${format((filesizeChange >= 0 ? '+' : '-') + toPercent(Math.abs(filesizeChange)), filesizeChange <= 0 ? colors.Green : colors.Yellow)}%`, + `${format(`${(devSize / 1024).toPrecision(4)} KB`, colors.White)}`, capturePerformance.reduce((output, p, i) => { output += `${p.name} `; output += `${similarityRatings(similaritys[p.name])} `; From caeb07e2d242ebad4207b41fd7dd63e81a410e5c Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 21 Nov 2021 23:52:13 +0000 Subject: [PATCH 26/62] optimised Detector.collisions --- src/collision/Detector.js | 58 ++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/collision/Detector.js b/src/collision/Detector.js index 9b4e1bc..d1affe5 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -12,7 +12,6 @@ module.exports = Detector; var SAT = require('./SAT'); var Pair = require('./Pair'); -var Bounds = require('../geometry/Bounds'); (function() { @@ -28,14 +27,14 @@ var Bounds = require('../geometry/Bounds'); pairsTable = engine.pairs.table, broadphasePairsLength = broadphasePairs.length, canCollide = Detector.canCollide, - overlaps = Bounds.overlaps, collides = SAT.collides, pairId = Pair.id, i; for (i = 0; i < broadphasePairsLength; i++) { - var bodyA = broadphasePairs[i][0], - bodyB = broadphasePairs[i][1]; + var broadphasePair = broadphasePairs[i], + bodyA = broadphasePair[0], + bodyB = broadphasePair[1]; if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping)) continue; @@ -43,25 +42,46 @@ var Bounds = require('../geometry/Bounds'); if (!canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) continue; - // mid phase - if (overlaps(bodyA.bounds, bodyB.bounds)) { - var partsALength = bodyA.parts.length, - partsBLength = bodyB.parts.length; + var boundsA = bodyA.bounds, + boundsB = bodyB.bounds; + + if (boundsA.min.x > boundsB.max.x || boundsA.max.x < boundsB.min.x + || boundsA.max.y < boundsB.min.y || boundsA.min.y > boundsB.max.y) { + continue; + } + + var partsALength = bodyA.parts.length, + partsBLength = bodyB.parts.length; + + if (partsALength === 1 && partsBLength === 1) { + var pair = pairsTable[pairId(bodyA, bodyB)]; + var collision = collides(bodyA, bodyB, pair && pair.collision, pair && pair.isActive); + + if (collision.collided) { + collisions.push(collision); + } + } else { + var partsAStart = partsALength > 1 ? 1 : 0, + partsBStart = partsBLength > 1 ? 1 : 0; - for (var j = partsALength > 1 ? 1 : 0; j < partsALength; j++) { - var partA = bodyA.parts[j]; + for (var j = partsAStart; j < partsALength; j++) { + var partA = bodyA.parts[j], + boundsA = partA.bounds; - for (var k = partsBLength > 1 ? 1 : 0; k < partsBLength; k++) { - var partB = bodyB.parts[k]; + for (var k = partsBStart; k < partsBLength; k++) { + var partB = bodyB.parts[k], + boundsB = partB.bounds; - if ((partA === bodyA && partB === bodyB) || overlaps(partA.bounds, partB.bounds)) { - // narrow phase - var pair = pairsTable[pairId(partA, partB)]; - var collision = collides(partA, partB, pair && pair.collision, pair && pair.isActive); + if (boundsA.min.x > boundsB.max.x || boundsA.max.x < boundsB.min.x + || boundsA.max.y < boundsB.min.y || boundsA.min.y > boundsB.max.y) { + continue; + } - if (collision.collided) { - collisions.push(collision); - } + var pair = pairsTable[pairId(partA, partB)]; + var collision = collides(partA, partB, pair && pair.collision, pair && pair.isActive); + + if (collision.collided) { + collisions.push(collision); } } } From ca2fe752d33fce63cf9d9502d3828d8a983a7d76 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 28 Nov 2021 21:25:11 +0000 Subject: [PATCH 27/62] refactor test worker and prevent test cache --- test/ExampleWorker.js | 66 +++++++++++++++++++++++-------------------- test/Examples.spec.js | 15 +++++----- test/TestTools.js | 9 +++++- 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 791c808..2b058f8 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -2,37 +2,34 @@ /* eslint no-global-assign: 0 */ "use strict"; -const stubBrowserFeatures = M => { - const noop = () => ({ collisionFilter: {}, mouse: {} }); - M.Render.create = () => ({ options: {}, bounds: { min: { x: 0, y: 0 }, max: { x: 800, y: 600 }}}); - M.Render.run = M.Render.lookAt = noop; - M.Runner.create = M.Runner.run = noop; - M.MouseConstraint.create = M.Mouse.create = noop; - M.Common.info = M.Common.warn = M.Common.log; - return M; -}; - -const reset = M => { - M.Common._nextId = M.Common._seed = 0; - M.Body._nextCollidingGroupId = 1; - M.Body._nextNonCollidingGroupId = -1; - M.Body._nextCategory = 0x0001; -}; - const mock = require('mock-require'); -const { engineCapture } = require('./TestTools'); -const MatterDev = stubBrowserFeatures(require('../build/matter.dev')); -const MatterBuild = stubBrowserFeatures(require('../build/matter')); +const { requireUncached, engineCapture } = require('./TestTools'); const Example = require('../examples/index'); +const consoleOriginal = global.console; -const runExample = options => { - const Matter = options.useDev ? MatterDev : MatterBuild; - const consoleOriginal = global.console; - const logs = []; +const prepareMatter = (options) => { + const Matter = requireUncached(options.useDev ? '../build/matter.dev' : '../build/matter'); + if (Matter.Common._nextId !== 0) { + throw 'Matter instance has already been used.'; + } + + const noop = () => ({ collisionFilter: {}, mouse: {} }); + + Matter.Render.create = () => ({ options: {}, bounds: { min: { x: 0, y: 0 }, max: { x: 800, y: 600 }}}); + Matter.Render.run = Matter.Render.lookAt = noop; + Matter.Runner.create = Matter.Runner.run = noop; + Matter.MouseConstraint.create = Matter.Mouse.create = noop; + Matter.Common.info = Matter.Common.warn = Matter.Common.log; + + return Matter; +}; + +const prepareEnvironment = Matter => { mock('matter-js', Matter); global.Matter = Matter; + const logs = []; global.document = global.window = { addEventListener: () => {} }; global.console = { log: (...args) => { @@ -40,7 +37,20 @@ const runExample = options => { } }; - reset(Matter); + return logs; +}; + +const resetEnvironment = () => { + global.console = consoleOriginal; + global.window = undefined; + global.document = undefined; + global.Matter = undefined; + mock.stopAll(); +}; + +const runExample = options => { + const Matter = prepareMatter(options); + const logs = prepareEnvironment(Matter); const example = Example[options.name](); const engine = example.engine; @@ -89,11 +99,7 @@ const runExample = options => { } } - global.console = consoleOriginal; - global.window = undefined; - global.document = undefined; - global.Matter = undefined; - mock.stopAll(); + resetEnvironment(); return { name: options.name, diff --git a/test/Examples.spec.js b/test/Examples.spec.js index efb2dc9..00a85dc 100644 --- a/test/Examples.spec.js +++ b/test/Examples.spec.js @@ -5,16 +5,17 @@ jest.setTimeout(30 * 1000); const fs = require('fs'); -const { +const { + requireUncached, comparisonReport, logReport, toMatchExtrinsics, toMatchIntrinsics } = require('./TestTools'); -const Example = require('../examples/index'); -const MatterBuild = require('../build/matter'); -const { versionSatisfies } = require('../src/core/Plugin'); +const Example = requireUncached('../examples/index'); +const MatterBuild = requireUncached('../build/matter'); +const { versionSatisfies } = requireUncached('../src/core/Plugin'); const Worker = require('jest-worker').default; const testComparison = process.env.COMPARE === 'true'; @@ -30,7 +31,7 @@ const examples = Object.keys(Example).filter(key => { return !excluded && supported; }); -const runExamples = async useDev => { +const captureExamples = async useDev => { const worker = new Worker(require.resolve('./ExampleWorker'), { enableWorkerThreads: true, numWorkers: 1 @@ -48,8 +49,8 @@ const runExamples = async useDev => { return result.reduce((out, capture) => (out[capture.name] = capture, out), {}); }; -const capturesDev = runExamples(true); -const capturesBuild = runExamples(false); +const capturesDev = captureExamples(true); +const capturesBuild = captureExamples(false); afterAll(async () => { // Report experimental capture comparison. diff --git a/test/TestTools.js b/test/TestTools.js index 8504adf..525e547 100644 --- a/test/TestTools.js +++ b/test/TestTools.js @@ -34,6 +34,13 @@ const limit = (val, precision=3) => parseFloat(val.toPrecision(precision)); const toPercent = val => (100 * val).toPrecision(3); const toPercentRound = val => Math.round(100 * val); +const requireUncached = path => { + delete require.cache[require.resolve(path)]; + const module = require(path); + delete require.cache[require.resolve(path)]; + return module; +}; + const noiseThreshold = (val, threshold) => { const sign = val < 0 ? -1 : 1; const magnitude = Math.abs(val); @@ -318,6 +325,6 @@ const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildV }; module.exports = { - engineCapture, comparisonReport, logReport, + requireUncached, engineCapture, comparisonReport, logReport, toMatchExtrinsics, toMatchIntrinsics }; \ No newline at end of file From de04c00278aa19819ec46131ac58592e5c837fee Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 28 Nov 2021 21:26:39 +0000 Subject: [PATCH 28/62] improve test comparison report --- test/TestTools.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/TestTools.js b/test/TestTools.js index 525e547..44d045f 100644 --- a/test/TestTools.js +++ b/test/TestTools.js @@ -31,7 +31,7 @@ const intrinsicProps = [ const colors = { Red: 31, Green: 32, Yellow: 33, White: 37, BrightWhite: 90, BrightCyan: 36 }; const color = (text, number) => number ? `\x1b[${number}m${text}\x1b[0m` : text; const limit = (val, precision=3) => parseFloat(val.toPrecision(precision)); -const toPercent = val => (100 * val).toPrecision(3); +const toPercent = val => (100 * val).toFixed(3); const toPercentRound = val => Math.round(100 * val); const requireUncached = path => { @@ -294,13 +294,10 @@ const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildV `${format(toPercent(similarityAvg), similarityAvg === 1 ? colors.Green : colors.Yellow)}%`, `${format('Overlap', colors.White)}`, `${format((overlapChange >= 0 ? '+' : '-') + toPercent(Math.abs(overlapChange)), overlapChange <= 0 ? colors.Green : colors.Yellow)}%`, - `${format('Performance', colors.White)}`, + `${format('Performance ~', colors.White)}`, `${format((perfChange >= 0 ? '+' : '-') + toPercentRound(Math.abs(perfChange)), perfChange >= 0 ? colors.Green : colors.Yellow)}%`, - `${format('Memory', colors.White)}`, + `${format('Memory ~', colors.White)}`, `${format((memoryChange >= 0 ? '+' : '-') + toPercentRound(Math.abs(memoryChange)), memoryChange <= 0 ? colors.Green : colors.Yellow)}%`, - `${format('Filesize', colors.White)}`, - `${format((filesizeChange >= 0 ? '+' : '-') + toPercent(Math.abs(filesizeChange)), filesizeChange <= 0 ? colors.Green : colors.Yellow)}%`, - `${format(`${(devSize / 1024).toPrecision(4)} KB`, colors.White)}`, capturePerformance.reduce((output, p, i) => { output += `${p.name} `; output += `${similarityRatings(similaritys[p.name])} `; @@ -312,7 +309,10 @@ const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildV }, '\n\n'), `\n\nwhere · no change ● extrinsics changed ◆ intrinsics changed\n`, similarityAvg < 1 ? `\n${format('▶', colors.White)} ${format(compareCommand + '=' + 120 + '#' + similarityEntries[0][0], colors.BrightCyan)}` : '', - intrinsicChangeCount > 0 ? `\n${format('▶', colors.White)} ${format((save ? diffCommand : diffSaveCommand), colors.BrightCyan)}` : '' + intrinsicChangeCount > 0 ? `\n${format('▶', colors.White)} ${format((save ? diffCommand : diffSaveCommand), colors.BrightCyan)}` : '', + `\n\n${format('Filesize', colors.White)}`, + `${format((filesizeChange >= 0 ? '+' : '-') + toPercent(Math.abs(filesizeChange)), filesizeChange <= 0 ? colors.Green : colors.Yellow)}%`, + `${format(`${(devSize / 1024).toPrecision(4)} KB`, colors.White)}`, ].join(' '); if (save) { From ea3c11b1fee370f603bf7d6e3547ec34fa80102a Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 28 Nov 2021 21:39:11 +0000 Subject: [PATCH 29/62] add test capture sort to improve comparison --- test/ExampleWorker.js | 112 +++++++++++++++++++++++++++++++++++++++++- test/TestTools.js | 105 +-------------------------------------- 2 files changed, 111 insertions(+), 106 deletions(-) diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 2b058f8..3075f92 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -3,10 +3,27 @@ "use strict"; const mock = require('mock-require'); -const { requireUncached, engineCapture } = require('./TestTools'); const Example = require('../examples/index'); +const { requireUncached } = require('./TestTools'); const consoleOriginal = global.console; +const intrinsicProps = [ + // Common + 'id', 'label', + + // Constraint + 'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness', + + // Body + 'area', 'axes', 'collisionFilter', 'category', 'mask', + 'group', 'density', 'friction', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', 'isSensor', + 'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution', 'sleepThreshold', 'slop', + 'timeScale', 'vertices', + + // Composite + 'bodies', 'constraints', 'composites' +]; + const prepareMatter = (options) => { const Matter = requireUncached(options.useDev ? '../build/matter.dev' : '../build/matter'); @@ -48,6 +65,97 @@ const resetEnvironment = () => { mock.stopAll(); }; +const limitPrecision = (val, precision=3) => parseFloat(val.toPrecision(precision)); + +const sortById = (objs) => { + objs.sort((objA, objB) => objA.id - objB.id); + return objs; +}; + +const engineCapture = (engine, Matter) => ({ + timestamp: limitPrecision(engine.timing.timestamp), + extrinsic: worldCaptureExtrinsic(engine.world, Matter), + intrinsic: worldCaptureIntrinsic(engine.world, Matter) +}); + +const worldCaptureExtrinsic = (world, Matter) => ({ + bodies: sortById(Matter.Composite.allBodies(world)).reduce((bodies, body) => { + bodies[body.id] = [ + body.position.x, + body.position.y, + body.positionPrev.x, + body.positionPrev.y, + body.angle, + body.anglePrev, + ...body.vertices.reduce((flat, vertex) => (flat.push(vertex.x, vertex.y), flat), []) + ]; + + return bodies; + }, {}), + constraints: sortById(Matter.Composite.allConstraints(world)).reduce((constraints, constraint) => { + const positionA = Matter.Constraint.pointAWorld(constraint); + const positionB = Matter.Constraint.pointBWorld(constraint); + + constraints[constraint.id] = [ + positionA.x, + positionA.y, + positionB.x, + positionB.y + ]; + + return constraints; + }, {}) +}); + +const worldCaptureIntrinsic = (world, Matter) => worldCaptureIntrinsicBase({ + bodies: sortById(Matter.Composite.allBodies(world)).reduce((bodies, body) => { + bodies[body.id] = body; + return bodies; + }, {}), + constraints: sortById(Matter.Composite.allConstraints(world)).reduce((constraints, constraint) => { + constraints[constraint.id] = constraint; + return constraints; + }, {}), + composites: sortById(Matter.Composite.allComposites(world)).reduce((composites, composite) => { + composites[composite.id] = { + bodies: sortById(Matter.Composite.allBodies(composite)).map(body => body.id), + constraints: sortById(Matter.Composite.allConstraints(composite)).map(constraint => constraint.id), + composites: sortById(Matter.Composite.allComposites(composite)).map(composite => composite.id) + }; + return composites; + }, {}) +}); + +const worldCaptureIntrinsicBase = (obj, depth=0) => { + if (obj === Infinity) { + return 'Infinity'; + } else if (typeof obj === 'number') { + return limitPrecision(obj); + } else if (Array.isArray(obj)) { + return obj.map(item => worldCaptureIntrinsicBase(item, depth + 1)); + } else if (typeof obj !== 'object') { + return obj; + } + + const result = Object.entries(obj) + .filter(([key]) => depth <= 1 || intrinsicProps.includes(key)) + .reduce((cleaned, [key, val]) => { + if (val && val.id && String(val.id) !== key) { + val = val.id; + } + + if (Array.isArray(val) && !['composites', 'constraints', 'bodies'].includes(key)) { + val = `[${val.length}]`; + } + + cleaned[key] = worldCaptureIntrinsicBase(val, depth + 1); + return cleaned; + }, {}); + + return Object.keys(result).sort() + .reduce((sorted, key) => (sorted[key] = result[key], sorted), {}); +}; + const runExample = options => { const Matter = prepareMatter(options); const logs = prepareEnvironment(Matter); @@ -107,7 +215,7 @@ const runExample = options => { overlap: overlapTotal / (overlapCount || 1), memory: totalMemory, logs, - ...engineCapture(engine) + ...engineCapture(engine, Matter) }; }; diff --git a/test/TestTools.js b/test/TestTools.js index 44d045f..7e41739 100644 --- a/test/TestTools.js +++ b/test/TestTools.js @@ -3,7 +3,6 @@ const fs = require('fs'); const compactStringify = require('json-stringify-pretty-compact'); -const { Composite, Constraint } = require('../src/module/main'); const comparePath = './test/__compare__'; const compareCommand = 'open http://localhost:8000/?compare'; @@ -11,26 +10,8 @@ const diffSaveCommand = 'npm run test-save'; const diffCommand = 'code -n -d test/__compare__/examples-build.json test/__compare__/examples-dev.json'; const equalityThreshold = 0.99999; -const intrinsicProps = [ - // Common - 'id', 'label', - - // Constraint - 'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness', - - // Body - 'area', 'axes', 'collisionFilter', 'category', 'mask', - 'group', 'density', 'friction', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', 'isSensor', - 'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution', 'sleepThreshold', 'slop', - 'timeScale', 'vertices', - - // Composite - 'bodies', 'constraints', 'composites' -]; - const colors = { Red: 31, Green: 32, Yellow: 33, White: 37, BrightWhite: 90, BrightCyan: 36 }; const color = (text, number) => number ? `\x1b[${number}m${text}\x1b[0m` : text; -const limit = (val, precision=3) => parseFloat(val.toPrecision(precision)); const toPercent = val => (100 * val).toFixed(3); const toPercentRound = val => Math.round(100 * val); @@ -47,90 +28,6 @@ const noiseThreshold = (val, threshold) => { return sign * Math.max(0, magnitude - threshold) / (1 - threshold); }; -const engineCapture = (engine) => ({ - timestamp: limit(engine.timing.timestamp), - extrinsic: worldCaptureExtrinsic(engine.world), - intrinsic: worldCaptureIntrinsic(engine.world) -}); - -const worldCaptureExtrinsic = world => ({ - bodies: Composite.allBodies(world).reduce((bodies, body) => { - bodies[body.id] = [ - body.position.x, - body.position.y, - body.positionPrev.x, - body.positionPrev.y, - body.angle, - body.anglePrev, - ...body.vertices.reduce((flat, vertex) => (flat.push(vertex.x, vertex.y), flat), []) - ]; - - return bodies; - }, {}), - constraints: Composite.allConstraints(world).reduce((constraints, constraint) => { - const positionA = Constraint.pointAWorld(constraint); - const positionB = Constraint.pointBWorld(constraint); - - constraints[constraint.id] = [ - positionA.x, - positionA.y, - positionB.x, - positionB.y - ]; - - return constraints; - }, {}) -}); - -const worldCaptureIntrinsic = world => worldCaptureIntrinsicBase({ - bodies: Composite.allBodies(world).reduce((bodies, body) => { - bodies[body.id] = body; - return bodies; - }, {}), - constraints: Composite.allConstraints(world).reduce((constraints, constraint) => { - constraints[constraint.id] = constraint; - return constraints; - }, {}), - composites: Composite.allComposites(world).reduce((composites, composite) => { - composites[composite.id] = { - bodies: Composite.allBodies(composite).map(body => body.id), - constraints: Composite.allConstraints(composite).map(constraint => constraint.id), - composites: Composite.allComposites(composite).map(composite => composite.id) - }; - return composites; - }, {}) -}); - -const worldCaptureIntrinsicBase = (obj, depth=0) => { - if (obj === Infinity) { - return 'Infinity'; - } else if (typeof obj === 'number') { - return limit(obj); - } else if (Array.isArray(obj)) { - return obj.map(item => worldCaptureIntrinsicBase(item, depth + 1)); - } else if (typeof obj !== 'object') { - return obj; - } - - const result = Object.entries(obj) - .filter(([key]) => depth <= 1 || intrinsicProps.includes(key)) - .reduce((cleaned, [key, val]) => { - if (val && val.id && String(val.id) !== key) { - val = val.id; - } - - if (Array.isArray(val) && !['composites', 'constraints', 'bodies'].includes(key)) { - val = `[${val.length}]`; - } - - cleaned[key] = worldCaptureIntrinsicBase(val, depth + 1); - return cleaned; - }, {}); - - return Object.keys(result).sort() - .reduce((sorted, key) => (sorted[key] = result[key], sorted), {}); -}; - const similarity = (a, b) => { const distance = Math.sqrt(a.reduce( (sum, _val, i) => sum + Math.pow((a[i] || 0) - (b[i] || 0), 2), 0) @@ -325,6 +222,6 @@ const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildV }; module.exports = { - requireUncached, engineCapture, comparisonReport, logReport, + requireUncached, comparisonReport, logReport, toMatchExtrinsics, toMatchIntrinsics }; \ No newline at end of file From bcc31682d7b90c03047edc8331dd22f98b0bcde4 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 28 Nov 2021 21:39:59 +0000 Subject: [PATCH 30/62] prevent test example cache --- test/ExampleWorker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 3075f92..9ba242a 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -3,7 +3,6 @@ "use strict"; const mock = require('mock-require'); -const Example = require('../examples/index'); const { requireUncached } = require('./TestTools'); const consoleOriginal = global.console; @@ -160,7 +159,8 @@ const runExample = options => { const Matter = prepareMatter(options); const logs = prepareEnvironment(Matter); - const example = Example[options.name](); + const Examples = requireUncached('../examples/index'); + const example = Examples[options.name](); const engine = example.engine; let totalMemory = 0; From 81dd2fb6950a6d280b74c8fac9218527114d3fc2 Mon Sep 17 00:00:00 2001 From: liabru Date: Tue, 30 Nov 2021 23:16:20 +0000 Subject: [PATCH 31/62] added stable sorting to test worker and refactor --- test/ExampleWorker.js | 345 ++++++++++++++++++++++-------------------- 1 file changed, 180 insertions(+), 165 deletions(-) diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 9ba242a..2794563 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -6,155 +6,6 @@ const mock = require('mock-require'); const { requireUncached } = require('./TestTools'); const consoleOriginal = global.console; -const intrinsicProps = [ - // Common - 'id', 'label', - - // Constraint - 'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness', - - // Body - 'area', 'axes', 'collisionFilter', 'category', 'mask', - 'group', 'density', 'friction', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', 'isSensor', - 'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution', 'sleepThreshold', 'slop', - 'timeScale', 'vertices', - - // Composite - 'bodies', 'constraints', 'composites' -]; - -const prepareMatter = (options) => { - const Matter = requireUncached(options.useDev ? '../build/matter.dev' : '../build/matter'); - - if (Matter.Common._nextId !== 0) { - throw 'Matter instance has already been used.'; - } - - const noop = () => ({ collisionFilter: {}, mouse: {} }); - - Matter.Render.create = () => ({ options: {}, bounds: { min: { x: 0, y: 0 }, max: { x: 800, y: 600 }}}); - Matter.Render.run = Matter.Render.lookAt = noop; - Matter.Runner.create = Matter.Runner.run = noop; - Matter.MouseConstraint.create = Matter.Mouse.create = noop; - Matter.Common.info = Matter.Common.warn = Matter.Common.log; - - return Matter; -}; - -const prepareEnvironment = Matter => { - mock('matter-js', Matter); - global.Matter = Matter; - - const logs = []; - global.document = global.window = { addEventListener: () => {} }; - global.console = { - log: (...args) => { - logs.push(args.join(' ')); - } - }; - - return logs; -}; - -const resetEnvironment = () => { - global.console = consoleOriginal; - global.window = undefined; - global.document = undefined; - global.Matter = undefined; - mock.stopAll(); -}; - -const limitPrecision = (val, precision=3) => parseFloat(val.toPrecision(precision)); - -const sortById = (objs) => { - objs.sort((objA, objB) => objA.id - objB.id); - return objs; -}; - -const engineCapture = (engine, Matter) => ({ - timestamp: limitPrecision(engine.timing.timestamp), - extrinsic: worldCaptureExtrinsic(engine.world, Matter), - intrinsic: worldCaptureIntrinsic(engine.world, Matter) -}); - -const worldCaptureExtrinsic = (world, Matter) => ({ - bodies: sortById(Matter.Composite.allBodies(world)).reduce((bodies, body) => { - bodies[body.id] = [ - body.position.x, - body.position.y, - body.positionPrev.x, - body.positionPrev.y, - body.angle, - body.anglePrev, - ...body.vertices.reduce((flat, vertex) => (flat.push(vertex.x, vertex.y), flat), []) - ]; - - return bodies; - }, {}), - constraints: sortById(Matter.Composite.allConstraints(world)).reduce((constraints, constraint) => { - const positionA = Matter.Constraint.pointAWorld(constraint); - const positionB = Matter.Constraint.pointBWorld(constraint); - - constraints[constraint.id] = [ - positionA.x, - positionA.y, - positionB.x, - positionB.y - ]; - - return constraints; - }, {}) -}); - -const worldCaptureIntrinsic = (world, Matter) => worldCaptureIntrinsicBase({ - bodies: sortById(Matter.Composite.allBodies(world)).reduce((bodies, body) => { - bodies[body.id] = body; - return bodies; - }, {}), - constraints: sortById(Matter.Composite.allConstraints(world)).reduce((constraints, constraint) => { - constraints[constraint.id] = constraint; - return constraints; - }, {}), - composites: sortById(Matter.Composite.allComposites(world)).reduce((composites, composite) => { - composites[composite.id] = { - bodies: sortById(Matter.Composite.allBodies(composite)).map(body => body.id), - constraints: sortById(Matter.Composite.allConstraints(composite)).map(constraint => constraint.id), - composites: sortById(Matter.Composite.allComposites(composite)).map(composite => composite.id) - }; - return composites; - }, {}) -}); - -const worldCaptureIntrinsicBase = (obj, depth=0) => { - if (obj === Infinity) { - return 'Infinity'; - } else if (typeof obj === 'number') { - return limitPrecision(obj); - } else if (Array.isArray(obj)) { - return obj.map(item => worldCaptureIntrinsicBase(item, depth + 1)); - } else if (typeof obj !== 'object') { - return obj; - } - - const result = Object.entries(obj) - .filter(([key]) => depth <= 1 || intrinsicProps.includes(key)) - .reduce((cleaned, [key, val]) => { - if (val && val.id && String(val.id) !== key) { - val = val.id; - } - - if (Array.isArray(val) && !['composites', 'constraints', 'bodies'].includes(key)) { - val = `[${val.length}]`; - } - - cleaned[key] = worldCaptureIntrinsicBase(val, depth + 1); - return cleaned; - }, {}); - - return Object.keys(result).sort() - .reduce((sorted, key) => (sorted[key] = result[key], sorted), {}); -}; - const runExample = options => { const Matter = prepareMatter(options); const logs = prepareEnvironment(Matter); @@ -168,22 +19,9 @@ const runExample = options => { let overlapTotal = 0; let overlapCount = 0; - const bodies = Matter.Composite.allBodies(engine.world); - - if (options.jitter) { - for (let i = 0; i < bodies.length; i += 1) { - const body = bodies[i]; - - Matter.Body.applyForce(body, body.position, { - x: Math.cos(i * i) * options.jitter * body.mass, - y: Math.sin(i * i) * options.jitter * body.mass - }); - } - } - global.gc(); - for (let i = 0; i < options.totalUpdates; i += 1) { + for (let i = 0; i < options.updates; i += 1) { const startTime = process.hrtime(); totalMemory += process.memoryUsage().heapUsed; @@ -214,9 +52,186 @@ const runExample = options => { duration: totalDuration, overlap: overlapTotal / (overlapCount || 1), memory: totalMemory, - logs, - ...engineCapture(engine, Matter) + logs: logs, + extrinsic: captureExtrinsics(engine, Matter), + intrinsic: captureIntrinsics(engine, Matter), }; }; +const prepareMatter = (options) => { + const Matter = requireUncached(options.useDev ? '../build/matter.dev' : '../build/matter'); + + if (Matter.Common._nextId !== 0) { + throw 'Matter instance has already been used.'; + } + + const noop = () => ({ collisionFilter: {}, mouse: {} }); + + Matter.Render.create = () => ({ options: {}, bounds: { min: { x: 0, y: 0 }, max: { x: 800, y: 600 }}}); + Matter.Render.run = Matter.Render.lookAt = noop; + Matter.Runner.create = Matter.Runner.run = noop; + Matter.MouseConstraint.create = Matter.Mouse.create = noop; + Matter.Common.info = Matter.Common.warn = Matter.Common.log; + + if (options.stableSort) { + const MatterSATCollides = Matter.SAT.collides; + Matter.SAT.collides = function(bodyA, bodyB, previousCollision, pairActive) { + const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; + const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; + return MatterSATCollides(_bodyA, _bodyB, previousCollision, pairActive); + }; + + Matter.after('Detector.collisions', function() { this.sort(collisionCompareId); }); + Matter.after('Composite.allBodies', function() { sortById(this); }); + Matter.after('Composite.allConstraints', function() { sortById(this); }); + Matter.after('Composite.allComposites', function() { sortById(this); }); + + Matter.before('Pairs.update', function(pairs) { + pairs.list.sort((pairA, pairB) => collisionCompareId(pairA.collision, pairB.collision)); + }); + + Matter.after('Pairs.update', function(pairs) { + pairs.list.sort((pairA, pairB) => collisionCompareId(pairA.collision, pairB.collision)); + }); + } + + if (options.jitter) { + Matter.after('Body.create', function() { + Matter.Body.applyForce(this, this.position, { + x: Math.cos(this.id * this.id) * options.jitter * this.mass, + y: Math.sin(this.id * this.id) * options.jitter * this.mass + }); + }); + } + + return Matter; +}; + +const prepareEnvironment = Matter => { + mock('matter-js', Matter); + global.Matter = Matter; + + const logs = []; + global.document = global.window = { addEventListener: () => {} }; + global.console = { + log: (...args) => { + logs.push(args.join(' ')); + } + }; + + return logs; +}; + +const resetEnvironment = () => { + global.console = consoleOriginal; + global.window = undefined; + global.document = undefined; + global.Matter = undefined; + mock.stopAll(); +}; + +const captureExtrinsics = ({ world }, Matter) => ({ + bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => { + bodies[body.id] = [ + body.position.x, + body.position.y, + body.positionPrev.x, + body.positionPrev.y, + body.angle, + body.anglePrev, + ...body.vertices.reduce((flat, vertex) => (flat.push(vertex.x, vertex.y), flat), []) + ]; + + return bodies; + }, {}), + constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => { + const positionA = Matter.Constraint.pointAWorld(constraint); + const positionB = Matter.Constraint.pointBWorld(constraint); + + constraints[constraint.id] = [ + positionA.x, + positionA.y, + positionB.x, + positionB.y + ]; + + return constraints; + }, {}) +}); + +const captureIntrinsics = ({ world }, Matter) => formatIntrinsics({ + bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => { + bodies[body.id] = body; + return bodies; + }, {}), + constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => { + constraints[constraint.id] = constraint; + return constraints; + }, {}), + composites: Matter.Composite.allComposites(world).reduce((composites, composite) => { + composites[composite.id] = { + bodies: Matter.Composite.allBodies(composite).map(body => body.id), + constraints: Matter.Composite.allConstraints(composite).map(constraint => constraint.id), + composites: Matter.Composite.allComposites(composite).map(composite => composite.id) + }; + return composites; + }, {}) +}); + +const formatIntrinsics = (obj, depth=0) => { + if (obj === Infinity) { + return 'Infinity'; + } else if (typeof obj === 'number') { + return limitPrecision(obj); + } else if (Array.isArray(obj)) { + return obj.map(item => formatIntrinsics(item, depth + 1)); + } else if (typeof obj !== 'object') { + return obj; + } + + const result = Object.entries(obj) + .filter(([key]) => depth <= 1 || intrinsicProperties.includes(key)) + .reduce((cleaned, [key, val]) => { + if (val && val.id && String(val.id) !== key) { + val = val.id; + } + + if (Array.isArray(val) && !['composites', 'constraints', 'bodies'].includes(key)) { + val = `[${val.length}]`; + } + + cleaned[key] = formatIntrinsics(val, depth + 1); + return cleaned; + }, {}); + + return Object.keys(result).sort() + .reduce((sorted, key) => (sorted[key] = result[key], sorted), {}); +}; + +const intrinsicProperties = [ + // Common + 'id', 'label', + + // Constraint + 'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness', + + // Body + 'area', 'axes', 'collisionFilter', 'category', 'mask', + 'group', 'density', 'friction', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', 'isSensor', + 'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution', 'sleepThreshold', 'slop', + 'timeScale', 'vertices', + + // Composite + 'bodies', 'constraints', 'composites' +]; + +const collisionId = (collision) => + Math.min(collision.bodyA.id, collision.bodyB.id) + Math.max(collision.bodyA.id, collision.bodyB.id) * 10000; + +const collisionCompareId = (collisionA, collisionB) => collisionId(collisionA) - collisionId(collisionB); + +const sortById = (objs) => objs.sort((objA, objB) => objA.id - objB.id); + +const limitPrecision = (val, precision=3) => parseFloat(val.toPrecision(precision)); + module.exports = { runExample }; \ No newline at end of file From 81259663fd1af20d3f1fe0f2afc6202dd1997f67 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 1 Dec 2021 23:34:41 +0000 Subject: [PATCH 32/62] added behaviour metric to tests and refactor tests --- examples/sleeping.js | 3 + test/Examples.spec.js | 56 +++++++- test/TestTools.js | 302 ++++++++++++++++++++++-------------------- 3 files changed, 216 insertions(+), 145 deletions(-) diff --git a/examples/sleeping.js b/examples/sleeping.js index 8d78648..bdb6a86 100644 --- a/examples/sleeping.js +++ b/examples/sleeping.js @@ -61,12 +61,15 @@ Example.sleeping = function() { Composite.add(world, stack); + /* + // sleep events for (var i = 0; i < stack.bodies.length; i++) { Events.on(stack.bodies[i], 'sleepStart sleepEnd', function(event) { var body = this; console.log('body id', body.id, 'sleeping:', body.isSleeping); }); } + */ // add mouse control var mouse = Mouse.create(render.canvas), diff --git a/test/Examples.spec.js b/test/Examples.spec.js index 00a85dc..a40bb42 100644 --- a/test/Examples.spec.js +++ b/test/Examples.spec.js @@ -32,21 +32,67 @@ const examples = Object.keys(Example).filter(key => { }); const captureExamples = async useDev => { - const worker = new Worker(require.resolve('./ExampleWorker'), { + const multiThreadWorker = new Worker(require.resolve('./ExampleWorker'), { + enableWorkerThreads: true + }); + + const overlapRuns = await Promise.all(examples.map(name => multiThreadWorker.runExample({ + name, + useDev, + updates: 1, + stableSort: true, + jitter: excludeJitter.includes(name) ? 0 : 1e-10 + }))); + + const behaviourRuns = await Promise.all(examples.map(name => multiThreadWorker.runExample({ + name, + useDev, + updates: 2, + stableSort: true, + jitter: excludeJitter.includes(name) ? 0 : 1e-10 + }))); + + const similarityRuns = await Promise.all(examples.map(name => multiThreadWorker.runExample({ + name, + useDev, + updates: 2, + stableSort: false, + jitter: excludeJitter.includes(name) ? 0 : 1e-10 + }))); + + await multiThreadWorker.end(); + + const singleThreadWorker = new Worker(require.resolve('./ExampleWorker'), { enableWorkerThreads: true, numWorkers: 1 }); - const result = await Promise.all(examples.map(name => worker.runExample({ + const completeRuns = await Promise.all(examples.map(name => singleThreadWorker.runExample({ name, useDev, - totalUpdates: 120, + updates: 150, + stableSort: false, jitter: excludeJitter.includes(name) ? 0 : 1e-10 }))); - await worker.end(); + await singleThreadWorker.end(); - return result.reduce((out, capture) => (out[capture.name] = capture, out), {}); + const capture = {}; + + for (const completeRun of completeRuns) { + const behaviourRun = behaviourRuns.find(({ name }) => name === completeRun.name); + const similarityRun = similarityRuns.find(({ name }) => name === completeRun.name); + const overlapRun = overlapRuns.find(({ name }) => name === completeRun.name); + + capture[overlapRun.name] = { + ...completeRun, + behaviourExtrinsic: behaviourRun.extrinsic, + similarityExtrinsic: similarityRun.extrinsic, + overlap: overlapRun.overlap + }; + } + + return capture; }; const capturesDev = captureExamples(true); diff --git a/test/TestTools.js b/test/TestTools.js index 7e41739..f0da95c 100644 --- a/test/TestTools.js +++ b/test/TestTools.js @@ -9,23 +9,92 @@ const compareCommand = 'open http://localhost:8000/?compare'; const diffSaveCommand = 'npm run test-save'; const diffCommand = 'code -n -d test/__compare__/examples-build.json test/__compare__/examples-dev.json'; const equalityThreshold = 0.99999; - const colors = { Red: 31, Green: 32, Yellow: 33, White: 37, BrightWhite: 90, BrightCyan: 36 }; -const color = (text, number) => number ? `\x1b[${number}m${text}\x1b[0m` : text; -const toPercent = val => (100 * val).toFixed(3); -const toPercentRound = val => Math.round(100 * val); -const requireUncached = path => { - delete require.cache[require.resolve(path)]; - const module = require(path); - delete require.cache[require.resolve(path)]; - return module; -}; +const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildVersion, save) => { + const performanceDev = capturePerformanceTotals(capturesDev); + const performanceBuild = capturePerformanceTotals(capturesBuild); -const noiseThreshold = (val, threshold) => { - const sign = val < 0 ? -1 : 1; - const magnitude = Math.abs(val); - return sign * Math.max(0, magnitude - threshold) / (1 - threshold); + const perfChange = noiseThreshold(1 - (performanceDev.duration / performanceBuild.duration), 0.01); + const memoryChange = noiseThreshold((performanceDev.memory / performanceBuild.memory) - 1, 0.01); + const overlapChange = (performanceDev.overlap / (performanceBuild.overlap || 1)) - 1; + const filesizeChange = (devSize / buildSize) - 1; + + const behaviourSimilaritys = extrinsicSimilarity(capturesDev, capturesBuild, 'behaviourExtrinsic'); + const behaviourSimilarityAverage = extrinsicSimilarityAverage(behaviourSimilaritys); + const behaviourSimilarityEntries = Object.entries(behaviourSimilaritys); + behaviourSimilarityEntries.sort((a, b) => a[1] - b[1]); + + const similaritys = extrinsicSimilarity(capturesDev, capturesBuild, 'similarityExtrinsic'); + const similarityAverage = extrinsicSimilarityAverage(similaritys); + + const devIntrinsicsChanged = {}; + const buildIntrinsicsChanged = {}; + let intrinsicChangeCount = 0; + + const captureSummary = Object.entries(capturesDev) + .map(([name]) => { + const changedIntrinsics = !equals(capturesDev[name].intrinsic, capturesBuild[name].intrinsic); + if (changedIntrinsics) { + capturesDev[name].changedIntrinsics = true; + if (intrinsicChangeCount < 2) { + devIntrinsicsChanged[name] = capturesDev[name].intrinsic; + buildIntrinsicsChanged[name] = capturesBuild[name].intrinsic; + intrinsicChangeCount += 1; + } + } + + return { name }; + }) + .sort((a, b) => a.name.localeCompare(b.name)); + + const report = (breakEvery, format) => [ + [`Output comparison of ${behaviourSimilarityEntries.length}`, + `examples against previous release ${format('matter-js@' + buildVersion, colors.Yellow)}` + ].join(' '), + + `\n\n${format('Behaviour ', colors.White)}`, + `${format(formatPercent(behaviourSimilarityAverage), behaviourSimilarityAverage === 1 ? colors.Green : colors.Yellow)}%`, + + ` ${format('Similarity', colors.White)}`, + `${format(formatPercent(similarityAverage), similarityAverage === 1 ? colors.Green : colors.Yellow)}%`, + + ` ${format('Overlap', colors.White)}`, + ` ${format((overlapChange >= 0 ? '+' : '-') + formatPercent(overlapChange, true), overlapChange <= 0 ? colors.Green : colors.Yellow)}%`, + + `\n${format('Performance', colors.White)}`, + `${format((perfChange >= 0 ? '+' : '-') + formatPercent(perfChange, true), perfChange >= 0 ? colors.Green : colors.Yellow)}%`, + + ` ${format('Memory', colors.White)}`, + ` ${format((memoryChange >= 0 ? '+' : '-') + formatPercent(memoryChange, true), memoryChange <= 0 ? colors.Green : colors.Yellow)}%`, + + ` ${format('Filesize', colors.White)}`, + `${format((filesizeChange >= 0 ? '+' : '-') + formatPercent(filesizeChange, true), filesizeChange <= 0 ? colors.Green : colors.Yellow)}%`, + `${format(`${(devSize / 1024).toPrecision(4)} KB`, colors.White)}`, + + captureSummary.reduce((output, p, i) => { + output += `${p.name} `; + output += `${similarityRatings(behaviourSimilaritys[p.name])} `; + output += `${changeRatings(capturesDev[p.name].changedIntrinsics)} `; + if (i > 0 && i < captureSummary.length && breakEvery > 0 && i % breakEvery === 0) { + output += '\n'; + } + return output; + }, '\n\n'), + + `\n\nwhere · no change ● extrinsics changed ◆ intrinsics changed\n`, + + behaviourSimilarityAverage < 1 ? `\n${format('▶', colors.White)} ${format(compareCommand + '=' + 150 + '#' + behaviourSimilarityEntries[0][0], colors.BrightCyan)}` : '', + intrinsicChangeCount > 0 ? `\n${format('▶', colors.White)} ${format((save ? diffCommand : diffSaveCommand), colors.BrightCyan)}` : '' + ].join(' '); + + if (save) { + writeResult('examples-dev', devIntrinsicsChanged); + writeResult('examples-build', buildIntrinsicsChanged); + writeResult('examples-report', report(5, s => s)); + } + + return report(5, color); }; const similarity = (a, b) => { @@ -35,18 +104,56 @@ const similarity = (a, b) => { return 1 / (1 + (distance / a.length)); }; -const captureSimilarityExtrinsic = (currentCaptures, referenceCaptures) => { +const similarityRatings = similarity => similarity < equalityThreshold ? color('●', colors.Yellow) : '·'; +const changeRatings = isChanged => isChanged ? color('◆', colors.White) : '·'; +const color = (text, number) => number ? `\x1b[${number}m${text}\x1b[0m` : text; +const formatPercent = (val, abs) => (100 * (abs ? Math.abs(val) : val)).toFixed(2); + +const noiseThreshold = (val, threshold) => { + const sign = val < 0 ? -1 : 1; + const magnitude = Math.abs(val); + return sign * Math.max(0, magnitude - threshold) / (1 - threshold); +}; + +const equals = (a, b) => { + try { + expect(a).toEqual(b); + } catch (e) { + return false; + } + return true; +}; + +const capturePerformanceTotals = (captures) => { + const totals = { + duration: 0, + overlap: 0, + memory: 0 + }; + + for (const [ name ] of Object.entries(captures)) { + totals.duration += captures[name].duration; + totals.overlap += captures[name].overlap; + totals.memory += captures[name].memory; + }; + + return totals; +}; + +const extrinsicSimilarity = (currentCaptures, referenceCaptures, key) => { const result = {}; Object.entries(currentCaptures).forEach(([name, current]) => { const reference = referenceCaptures[name]; const worldVector = []; const worldVectorRef = []; + const currentExtrinsic = current[key]; + const referenceExtrinsic = reference[key]; - Object.keys(current.extrinsic).forEach(objectType => { - Object.keys(current.extrinsic[objectType]).forEach(objectId => { - worldVector.push(...current.extrinsic[objectType][objectId]); - worldVectorRef.push(...reference.extrinsic[objectType][objectId]); + Object.keys(currentExtrinsic).forEach(objectType => { + Object.keys(currentExtrinsic[objectType]).forEach(objectId => { + worldVector.push(...currentExtrinsic[objectType][objectId]); + worldVectorRef.push(...referenceExtrinsic[objectType][objectId]); }); }); @@ -56,6 +163,15 @@ const captureSimilarityExtrinsic = (currentCaptures, referenceCaptures) => { return result; }; +const extrinsicSimilarityAverage = (similaritys) => { + const entries = Object.entries(similaritys); + let average = 0; + + entries.forEach(([_, similarity]) => average += similarity); + + return average /= entries.length; +}; + const writeResult = (name, obj) => { try { fs.mkdirSync(comparePath, { recursive: true }); @@ -70,9 +186,35 @@ const writeResult = (name, obj) => { } }; +const logReport = (captures, version) => { + let report = ''; + + for (const capture of Object.values(captures)) { + if (!capture.logs.length) { + continue; + } + + report += ` ${capture.name}\n`; + + for (const log of capture.logs) { + report += ` ${log}\n`; + } + } + + return `Output logs from ${color(version, colors.Yellow)} build on last run\n\n` + + (report ? report : ' None\n'); +}; + +const requireUncached = path => { + delete require.cache[require.resolve(path)]; + const module = require(path); + delete require.cache[require.resolve(path)]; + return module; +}; + const toMatchExtrinsics = { toMatchExtrinsics(received, value) { - const similaritys = captureSimilarityExtrinsic(received, value); + const similaritys = extrinsicSimilarity(received, value, 'extrinsic'); const pass = Object.values(similaritys).every(similarity => similarity >= equalityThreshold); return { @@ -101,126 +243,6 @@ const toMatchIntrinsics = { } }; -const similarityRatings = similarity => similarity < equalityThreshold ? color('●', colors.Yellow) : '·'; -const changeRatings = isChanged => isChanged ? color('◆', colors.White) : '·'; - -const equals = (a, b) => { - try { - expect(a).toEqual(b); - } catch (e) { - return false; - } - return true; -}; - -const logReport = (captures, version) => { - let report = ''; - - for (const capture of Object.values(captures)) { - if (!capture.logs.length) { - continue; - } - - report += ` ${capture.name}\n`; - - for (const log of capture.logs) { - report += ` ${log}\n`; - } - } - - return `Output logs from ${color(version, colors.Yellow)} build on last run\n\n` - + (report ? report : ' None\n'); -}; - -const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildVersion, save) => { - const similaritys = captureSimilarityExtrinsic(capturesDev, capturesBuild); - const similarityEntries = Object.entries(similaritys); - const devIntrinsicsChanged = {}; - const buildIntrinsicsChanged = {}; - let intrinsicChangeCount = 0; - let totalTimeBuild = 0; - let totalTimeDev = 0; - let totalOverlapBuild = 0; - let totalOverlapDev = 0; - let totalMemoryBuild = 0; - let totalMemoryDev = 0; - - const capturePerformance = Object.entries(capturesDev).map(([name]) => { - totalTimeBuild += capturesBuild[name].duration; - totalTimeDev += capturesDev[name].duration; - - totalOverlapBuild += capturesBuild[name].overlap; - totalOverlapDev += capturesDev[name].overlap; - - totalMemoryBuild += capturesBuild[name].memory; - totalMemoryDev += capturesDev[name].memory; - - const changedIntrinsics = !equals(capturesDev[name].intrinsic, capturesBuild[name].intrinsic); - if (changedIntrinsics) { - capturesDev[name].changedIntrinsics = true; - if (intrinsicChangeCount < 2) { - devIntrinsicsChanged[name] = capturesDev[name].intrinsic; - buildIntrinsicsChanged[name] = capturesBuild[name].intrinsic; - intrinsicChangeCount += 1; - } - } - - return { name }; - }); - - capturePerformance.sort((a, b) => a.name.localeCompare(b.name)); - similarityEntries.sort((a, b) => a[1] - b[1]); - - const perfChange = noiseThreshold(1 - (totalTimeDev / totalTimeBuild), 0.01); - const memoryChange = noiseThreshold((totalMemoryDev / totalMemoryBuild) - 1, 0.01); - const overlapChange = (totalOverlapDev / (totalOverlapBuild || 1)) - 1; - const filesizeChange = (devSize / buildSize) - 1; - - let similarityAvg = 0; - similarityEntries.forEach(([_, similarity]) => { - similarityAvg += similarity; - }); - - similarityAvg /= similarityEntries.length; - - const report = (breakEvery, format) => [ - [`Output comparison of ${similarityEntries.length}`, - `examples against previous release ${format('matter-js@' + buildVersion, colors.Yellow)}` - ].join(' '), - `\n\n${format('Similarity', colors.White)}`, - `${format(toPercent(similarityAvg), similarityAvg === 1 ? colors.Green : colors.Yellow)}%`, - `${format('Overlap', colors.White)}`, - `${format((overlapChange >= 0 ? '+' : '-') + toPercent(Math.abs(overlapChange)), overlapChange <= 0 ? colors.Green : colors.Yellow)}%`, - `${format('Performance ~', colors.White)}`, - `${format((perfChange >= 0 ? '+' : '-') + toPercentRound(Math.abs(perfChange)), perfChange >= 0 ? colors.Green : colors.Yellow)}%`, - `${format('Memory ~', colors.White)}`, - `${format((memoryChange >= 0 ? '+' : '-') + toPercentRound(Math.abs(memoryChange)), memoryChange <= 0 ? colors.Green : colors.Yellow)}%`, - capturePerformance.reduce((output, p, i) => { - output += `${p.name} `; - output += `${similarityRatings(similaritys[p.name])} `; - output += `${changeRatings(capturesDev[p.name].changedIntrinsics)} `; - if (i > 0 && i < capturePerformance.length && breakEvery > 0 && i % breakEvery === 0) { - output += '\n'; - } - return output; - }, '\n\n'), - `\n\nwhere · no change ● extrinsics changed ◆ intrinsics changed\n`, - similarityAvg < 1 ? `\n${format('▶', colors.White)} ${format(compareCommand + '=' + 120 + '#' + similarityEntries[0][0], colors.BrightCyan)}` : '', - intrinsicChangeCount > 0 ? `\n${format('▶', colors.White)} ${format((save ? diffCommand : diffSaveCommand), colors.BrightCyan)}` : '', - `\n\n${format('Filesize', colors.White)}`, - `${format((filesizeChange >= 0 ? '+' : '-') + toPercent(Math.abs(filesizeChange)), filesizeChange <= 0 ? colors.Green : colors.Yellow)}%`, - `${format(`${(devSize / 1024).toPrecision(4)} KB`, colors.White)}`, - ].join(' '); - - if (save) { - writeResult('examples-dev', devIntrinsicsChanged); - writeResult('examples-build', buildIntrinsicsChanged); - writeResult('examples-report', report(5, s => s)); - } - - return report(5, color); -}; - module.exports = { requireUncached, comparisonReport, logReport, toMatchExtrinsics, toMatchIntrinsics From d8a6380899e68e93314097a48f38346cab8972d2 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 5 Dec 2021 16:56:41 +0000 Subject: [PATCH 33/62] optimised Matter.Pair --- src/collision/Contact.js | 11 ---------- src/collision/Pair.js | 47 ++++++++++++++++++---------------------- 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/src/collision/Contact.js b/src/collision/Contact.js index aeaa30c..56f16cc 100644 --- a/src/collision/Contact.js +++ b/src/collision/Contact.js @@ -18,21 +18,10 @@ module.exports = Contact; */ Contact.create = function(vertex) { return { - id: Contact.id(vertex), vertex: vertex, normalImpulse: 0, tangentImpulse: 0 }; }; - - /** - * Generates a contact id. - * @method id - * @param {vertex} vertex - * @return {string} Unique contactID - */ - Contact.id = function(vertex) { - return vertex.body.id + '_' + vertex.index; - }; })(); diff --git a/src/collision/Pair.js b/src/collision/Pair.js index a48754b..f4d367e 100644 --- a/src/collision/Pair.js +++ b/src/collision/Pair.js @@ -21,15 +21,13 @@ var Contact = require('./Contact'); */ Pair.create = function(collision, timestamp) { var bodyA = collision.bodyA, - bodyB = collision.bodyB, - parentA = collision.parentA, - parentB = collision.parentB; + bodyB = collision.bodyB; var pair = { id: Pair.id(bodyA, bodyB), bodyA: bodyA, bodyB: bodyB, - contacts: {}, + contacts: [], activeContacts: [], separation: 0, isActive: true, @@ -37,11 +35,11 @@ var Contact = require('./Contact'); isSensor: bodyA.isSensor || bodyB.isSensor, timeCreated: timestamp, timeUpdated: timestamp, - inverseMass: parentA.inverseMass + parentB.inverseMass, - friction: parentA.friction < parentB.friction ? parentA.friction : parentB.friction, - frictionStatic: parentA.frictionStatic > parentB.frictionStatic ? parentA.frictionStatic : parentB.frictionStatic, - restitution: parentA.restitution > parentB.restitution ? parentA.restitution : parentB.restitution, - slop: parentA.slop > parentB.slop ? parentA.slop : parentB.slop + inverseMass: 0, + friction: 0, + frictionStatic: 0, + restitution: 0, + slop: 0 }; Pair.update(pair, collision, timestamp); @@ -61,34 +59,31 @@ var Contact = require('./Contact'); supports = collision.supports, activeContacts = pair.activeContacts, parentA = collision.parentA, - parentB = collision.parentB; + parentB = collision.parentB, + parentAVerticesLength = parentA.vertices.length; + pair.isActive = true; + pair.timeUpdated = timestamp; pair.collision = collision; + pair.separation = collision.depth; pair.inverseMass = parentA.inverseMass + parentB.inverseMass; pair.friction = parentA.friction < parentB.friction ? parentA.friction : parentB.friction; pair.frictionStatic = parentA.frictionStatic > parentB.frictionStatic ? parentA.frictionStatic : parentB.frictionStatic; pair.restitution = parentA.restitution > parentB.restitution ? parentA.restitution : parentB.restitution; pair.slop = parentA.slop > parentB.slop ? parentA.slop : parentB.slop; + activeContacts.length = 0; - if (collision.collided) { - for (var i = 0; i < supports.length; i++) { - var support = supports[i], - contactId = Contact.id(support), - contact = contacts[contactId]; + 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)); - } + if (contact) { + activeContacts.push(contact); + } else { + activeContacts.push(contacts[contactId] = Contact.create(support)); } - - pair.separation = collision.depth; - Pair.setActive(pair, true, timestamp); - } else { - if (pair.isActive === true) - Pair.setActive(pair, false, timestamp); } }; From 52e797791b8b45f6605e01e753215497c2e5994d Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 5 Dec 2021 19:11:29 +0000 Subject: [PATCH 34/62] optimised Matter.Composite --- src/body/Composite.js | 55 +++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/src/body/Composite.js b/src/body/Composite.js index c56d679..496437a 100644 --- a/src/body/Composite.js +++ b/src/body/Composite.js @@ -39,7 +39,12 @@ var Body = require('./Body'); constraints: [], composites: [], label: 'Composite', - plugin: {} + plugin: {}, + cache: { + allBodies: null, + allConstraints: null, + allComposites: null + } }, options); }; @@ -47,6 +52,7 @@ var Body = require('./Body'); * Sets the composite's `isModified` flag. * If `updateParents` is true, all parents will be set (default: false). * If `updateChildren` is true, all children will be set (default: false). + * @private * @method setModified * @param {composite} composite * @param {boolean} isModified @@ -56,6 +62,12 @@ var Body = require('./Body'); Composite.setModified = function(composite, isModified, updateParents, updateChildren) { composite.isModified = isModified; + if (isModified) { + composite.cache.allBodies = null; + composite.cache.allConstraints = null; + composite.cache.allComposites = null; + } + if (updateParents && composite.parent) { Composite.setModified(composite.parent, isModified, updateParents, updateChildren); } @@ -182,7 +194,6 @@ var Body = require('./Body'); var position = Common.indexOf(compositeA.composites, compositeB); if (position !== -1) { Composite.removeCompositeAt(compositeA, position); - Composite.setModified(compositeA, true, true, false); } if (deep) { @@ -235,7 +246,6 @@ var Body = require('./Body'); var position = Common.indexOf(composite.bodies, body); if (position !== -1) { Composite.removeBodyAt(composite, position); - Composite.setModified(composite, true, true, false); } if (deep) { @@ -336,6 +346,7 @@ var Body = require('./Body'); composite.constraints.length = 0; composite.composites.length = 0; + Composite.setModified(composite, true, true, false); return composite; @@ -348,11 +359,17 @@ var Body = require('./Body'); * @return {body[]} All the bodies */ Composite.allBodies = function(composite) { + if (composite.cache.allBodies) { + return composite.cache.allBodies; + } + var bodies = [].concat(composite.bodies); for (var i = 0; i < composite.composites.length; i++) bodies = bodies.concat(Composite.allBodies(composite.composites[i])); + composite.cache.allBodies = bodies; + return bodies; }; @@ -363,11 +380,17 @@ var Body = require('./Body'); * @return {constraint[]} All the constraints */ Composite.allConstraints = function(composite) { + if (composite.cache.allConstraints) { + return composite.cache.allConstraints; + } + var constraints = [].concat(composite.constraints); for (var i = 0; i < composite.composites.length; i++) constraints = constraints.concat(Composite.allConstraints(composite.composites[i])); + composite.cache.allConstraints = constraints; + return constraints; }; @@ -378,11 +401,17 @@ var Body = require('./Body'); * @return {composite[]} All the composites */ Composite.allComposites = function(composite) { + if (composite.cache.allComposites) { + return composite.cache.allComposites; + } + var composites = [].concat(composite.composites); for (var i = 0; i < composite.composites.length; i++) composites = composites.concat(Composite.allComposites(composite.composites[i])); + composite.cache.allComposites = composites; + return composites; }; @@ -449,8 +478,6 @@ var Body = require('./Body'); objects[i].id = Common.nextId(); } - Composite.setModified(composite, true, true, false); - return composite; }; @@ -469,8 +496,6 @@ var Body = require('./Body'); Body.translate(bodies[i], translation); } - Composite.setModified(composite, true, true, false); - return composite; }; @@ -500,8 +525,6 @@ var Body = require('./Body'); Body.rotate(body, rotation); } - Composite.setModified(composite, true, true, false); - return composite; }; @@ -530,8 +553,6 @@ var Body = require('./Body'); Body.scale(body, scaleX, scaleY); } - Composite.setModified(composite, true, true, false); - return composite; }; @@ -631,8 +652,7 @@ var Body = require('./Body'); /** * A flag that specifies whether the composite has been modified during the current step. - * Most `Matter.Composite` methods will automatically set this flag to `true` to inform the engine of changes to be handled. - * If you need to change it manually, you should use the `Composite.setModified` method. + * This is automatically managed when bodies, constraints or composites are added or removed. * * @property isModified * @type boolean @@ -684,4 +704,13 @@ var Body = require('./Body'); * @type {} */ + /** + * An object used for storing cached results for performance reasons. + * This is used internally only and is automatically managed. + * + * @private + * @property cache + * @type {} + */ + })(); From f847f4c83e1e605e858e4c6e95cb327ceffd5ccb Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 5 Dec 2021 19:30:35 +0000 Subject: [PATCH 35/62] optimised Resolver.solvePosition --- src/collision/Resolver.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/collision/Resolver.js b/src/collision/Resolver.js index 00302a5..ba69d26 100644 --- a/src/collision/Resolver.js +++ b/src/collision/Resolver.js @@ -73,15 +73,10 @@ var Bounds = require('../geometry/Bounds'); bodyB = collision.parentB; normal = collision.normal; - // TODO: behaviour change: replace with fully simplified version // get current separation between body edges involved in collision pair.separation = - normal.x * ( - (bodyB.positionImpulse.x + bodyB.position.x) - (bodyA.positionImpulse.x + (bodyB.position.x - collision.penetration.x)) - ) - + normal.y * ( - (bodyB.positionImpulse.y + bodyB.position.y) - (bodyA.positionImpulse.y + (bodyB.position.y - collision.penetration.y)) - ); + normal.x * (bodyB.positionImpulse.x + collision.penetration.x - bodyA.positionImpulse.x) + + normal.y * (bodyB.positionImpulse.y + collision.penetration.y - bodyA.positionImpulse.y); } for (i = 0; i < pairsLength; i++) { From a30707fd87cd3e98495cc2c5125fecca7911f996 Mon Sep 17 00:00:00 2001 From: liabru Date: Mon, 6 Dec 2021 23:57:09 +0000 Subject: [PATCH 36/62] optimised Matter.Pairs --- examples/manipulation.js | 2 +- src/collision/Pairs.js | 120 +++++++++++++++------------------------ src/core/Engine.js | 1 - 3 files changed, 47 insertions(+), 76 deletions(-) diff --git a/examples/manipulation.js b/examples/manipulation.js index efbeb76..7cdccc4 100644 --- a/examples/manipulation.js +++ b/examples/manipulation.js @@ -62,7 +62,7 @@ Example.manipulation = function() { var counter = 0, scaleFactor = 1.01; - Events.on(engine, 'beforeUpdate', function(event) { + Events.on(runner, 'afterTick', function(event) { counter += 1; if (counter === 40) diff --git a/src/collision/Pairs.js b/src/collision/Pairs.js index 3f8cadd..4685783 100644 --- a/src/collision/Pairs.js +++ b/src/collision/Pairs.js @@ -12,8 +12,6 @@ var Pair = require('./Pair'); var Common = require('../core/Common'); (function() { - - Pairs._pairMaxIdleLife = 1000; /** * Creates a new pairs structure. @@ -40,11 +38,14 @@ var Common = require('../core/Common'); */ Pairs.update = function(pairs, collisions, timestamp) { var pairsList = pairs.list, + pairsListLength = pairsList.length, pairsTable = pairs.table, + collisionsLength = collisions.length, collisionStart = pairs.collisionStart, collisionEnd = pairs.collisionEnd, collisionActive = pairs.collisionActive, collision, + pairIndex, pairId, pair, i; @@ -54,91 +55,62 @@ var Common = require('../core/Common'); collisionEnd.length = 0; collisionActive.length = 0; - for (i = 0; i < pairsList.length; i++) { + for (i = 0; i < pairsListLength; i++) { pairsList[i].confirmedActive = false; } - for (i = 0; i < collisions.length; i++) { + for (i = 0; i < collisionsLength; i++) { collision = collisions[i]; + pairId = Pair.id(collision.bodyA, collision.bodyB); + pair = pairsTable[pairId]; - if (collision.collided) { - pairId = Pair.id(collision.bodyA, collision.bodyB); - - pair = pairsTable[pairId]; - - if (pair) { - // 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); - } - - // update the pair - Pair.update(pair, collision, timestamp); - pair.confirmedActive = true; + if (pair) { + // pair already exists (but may or may not be active) + if (pair.isActive) { + // pair exists and is active + collisionActive.push(pair); } else { - // pair did not exist, create a new pair - pair = Pair.create(collision, timestamp); - pairsTable[pairId] = pair; - - // push the new pair + // pair exists but was inactive, so a collision has just started again collisionStart.push(pair); - pairsList.push(pair); + } + + // update the pair + Pair.update(pair, collision, timestamp); + pair.confirmedActive = true; + } else { + // pair did not exist, create a new pair + pair = Pair.create(collision, timestamp); + pairsTable[pairId] = pair; + + // push the new pair + collisionStart.push(pair); + pairsList.push(pair); + } + } + + // find pairs that are no longer active + var removePairIndex = []; + pairsListLength = pairsList.length; + + for (i = 0; i < pairsListLength; i++) { + pair = pairsList[i]; + + if (!pair.confirmedActive) { + Pair.setActive(pair, false, timestamp); + collisionEnd.push(pair); + + if (!pair.collision.bodyA.isSleeping && !pair.collision.bodyB.isSleeping) { + removePairIndex.push(i); } } } - // deactivate previously active pairs that are now inactive - for (i = 0; i < pairsList.length; i++) { - pair = pairsList[i]; - if (pair.isActive && !pair.confirmedActive) { - Pair.setActive(pair, false, timestamp); - collisionEnd.push(pair); - } - } - }; - - /** - * Finds and removes pairs that have been inactive for a set amount of time. - * @method removeOld - * @param {object} pairs - * @param {number} timestamp - */ - Pairs.removeOld = function(pairs, timestamp) { - var pairsList = pairs.list, - pairsTable = pairs.table, - indexesToRemove = [], - pairMaxIdleLife = Pairs._pairMaxIdleLife, - pair, - collision, - pairIndex, - i; - - for (i = 0; i < pairsList.length; i++) { - pair = pairsList[i]; - collision = pair.collision; - - // never remove sleeping pairs - if (collision.bodyA.isSleeping || collision.bodyB.isSleeping) { - pair.timeUpdated = timestamp; - continue; - } - - // if pair is inactive for too long, mark it to be removed - if (timestamp - pair.timeUpdated > pairMaxIdleLife) { - indexesToRemove.push(i); - } - } - - // remove marked pairs - for (i = 0; i < indexesToRemove.length; i++) { - pairIndex = indexesToRemove[i] - i; + // remove inactive pairs + for (i = 0; i < removePairIndex.length; i++) { + pairIndex = removePairIndex[i] - i; pair = pairsList[pairIndex]; - delete pairsTable[pair.id]; pairsList.splice(pairIndex, 1); + delete pairsTable[pair.id]; } }; diff --git a/src/core/Engine.js b/src/core/Engine.js index a1c4d60..bb98b55 100644 --- a/src/core/Engine.js +++ b/src/core/Engine.js @@ -152,7 +152,6 @@ var Body = require('../body/Body'); var pairs = engine.pairs, timestamp = timing.timestamp; Pairs.update(pairs, collisions, timestamp); - Pairs.removeOld(pairs, timestamp); // wake up bodies involved in collisions if (engine.enableSleeping) From 2581595e6e22695265ede43809abf83fc6eb272a Mon Sep 17 00:00:00 2001 From: liabru Date: Tue, 7 Dec 2021 23:23:34 +0000 Subject: [PATCH 37/62] use Matter.Runner in test worker --- test/ExampleWorker.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 2794563..81b25eb 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -12,7 +12,12 @@ const runExample = options => { const Examples = requireUncached('../examples/index'); const example = Examples[options.name](); + const engine = example.engine; + const runner = example.runner; + + runner.delta = 1000 / 60; + runner.isFixed = true; let totalMemory = 0; let totalDuration = 0; @@ -24,8 +29,8 @@ const runExample = options => { for (let i = 0; i < options.updates; i += 1) { const startTime = process.hrtime(); totalMemory += process.memoryUsage().heapUsed; - - Matter.Engine.update(engine, 1000 / 60); + + Matter.Runner.tick(runner, engine, i * runner.delta); const duration = process.hrtime(startTime); totalDuration += duration[0] * 1e9 + duration[1]; From 9037f36f3127519c0517b5b4e459512467a06d79 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 8 Dec 2021 23:36:13 +0000 Subject: [PATCH 38/62] added Matter.Collision --- src/collision/Collision.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/collision/Collision.js diff --git a/src/collision/Collision.js b/src/collision/Collision.js new file mode 100644 index 0000000..ef53a7d --- /dev/null +++ b/src/collision/Collision.js @@ -0,0 +1,36 @@ +/** +* The `Matter.Collision` module contains methods for managing collision records. +* +* @class Collision +*/ + +var Collision = {}; + +module.exports = Collision; + +(function() { + + /** + * Creates a new collision record. + * @method create + * @param {body} bodyA + * @param {body} bodyB + * @return {collision} A new collision record + */ + Collision.create = function(bodyA, bodyB) { + return { + pair: null, + collided: false, + bodyA: bodyA, + bodyB: bodyB, + parentA: bodyA.parent, + parentB: bodyB.parent, + depth: 0, + normal: { x: 0, y: 0 }, + tangent: { x: 0, y: 0 }, + penetration: { x: 0, y: 0 }, + supports: [] + }; + }; + +})(); From fd1a70ec7ae044e8602496abe87c5c826c071e91 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 8 Dec 2021 23:38:13 +0000 Subject: [PATCH 39/62] optimised collisions --- src/collision/Detector.js | 13 +-- src/collision/Pair.js | 2 + src/collision/Pairs.js | 6 +- src/collision/Query.js | 22 ++-- src/collision/SAT.js | 209 +++++++++++++++----------------------- 5 files changed, 104 insertions(+), 148 deletions(-) diff --git a/src/collision/Detector.js b/src/collision/Detector.js index d1affe5..7b47c9f 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -24,11 +24,10 @@ var Pair = require('./Pair'); */ Detector.collisions = function(broadphasePairs, engine) { var collisions = [], - pairsTable = engine.pairs.table, + pairs = engine.pairs, broadphasePairsLength = broadphasePairs.length, canCollide = Detector.canCollide, collides = SAT.collides, - pairId = Pair.id, i; for (i = 0; i < broadphasePairsLength; i++) { @@ -54,10 +53,9 @@ var Pair = require('./Pair'); partsBLength = bodyB.parts.length; if (partsALength === 1 && partsBLength === 1) { - var pair = pairsTable[pairId(bodyA, bodyB)]; - var collision = collides(bodyA, bodyB, pair && pair.collision, pair && pair.isActive); + var collision = collides(bodyA, bodyB, pairs); - if (collision.collided) { + if (collision) { collisions.push(collision); } } else { @@ -77,10 +75,9 @@ var Pair = require('./Pair'); continue; } - var pair = pairsTable[pairId(partA, partB)]; - var collision = collides(partA, partB, pair && pair.collision, pair && pair.isActive); + var collision = collides(partA, partB, pairs); - if (collision.collided) { + if (collision) { collisions.push(collision); } } diff --git a/src/collision/Pair.js b/src/collision/Pair.js index f4d367e..8f7ed81 100644 --- a/src/collision/Pair.js +++ b/src/collision/Pair.js @@ -27,6 +27,7 @@ var Contact = require('./Contact'); id: Pair.id(bodyA, bodyB), bodyA: bodyA, bodyB: bodyB, + collision: collision, contacts: [], activeContacts: [], separation: 0, @@ -72,6 +73,7 @@ var Contact = require('./Contact'); pair.restitution = parentA.restitution > parentB.restitution ? parentA.restitution : parentB.restitution; pair.slop = parentA.slop > parentB.slop ? parentA.slop : parentB.slop; + collision.pair = pair; activeContacts.length = 0; for (var i = 0; i < supports.length; i++) { diff --git a/src/collision/Pairs.js b/src/collision/Pairs.js index 4685783..2c62cd3 100644 --- a/src/collision/Pairs.js +++ b/src/collision/Pairs.js @@ -46,7 +46,6 @@ var Common = require('../core/Common'); collisionActive = pairs.collisionActive, collision, pairIndex, - pairId, pair, i; @@ -61,8 +60,7 @@ var Common = require('../core/Common'); for (i = 0; i < collisionsLength; i++) { collision = collisions[i]; - pairId = Pair.id(collision.bodyA, collision.bodyB); - pair = pairsTable[pairId]; + pair = collision.pair; if (pair) { // pair already exists (but may or may not be active) @@ -80,7 +78,7 @@ var Common = require('../core/Common'); } else { // pair did not exist, create a new pair pair = Pair.create(collision, timestamp); - pairsTable[pairId] = pair; + pairsTable[pair.id] = pair; // push the new pair collisionStart.push(pair); diff --git a/src/collision/Query.js b/src/collision/Query.js index 7166478..cfc6877 100644 --- a/src/collision/Query.js +++ b/src/collision/Query.js @@ -26,19 +26,25 @@ var Vertices = require('../geometry/Vertices'); * @return {object[]} Collisions */ Query.collides = function(body, bodies) { - var collisions = []; + var collisions = [], + bodiesLength = bodies.length, + bounds = body.bounds, + collides = SAT.collides, + overlaps = Bounds.overlaps; - for (var i = 0; i < bodies.length; i++) { - var bodyA = bodies[i]; + for (var i = 0; i < bodiesLength; i++) { + var bodyA = bodies[i], + partsALength = bodyA.parts.length, + partsAStart = partsALength === 1 ? 0 : 1; - if (Bounds.overlaps(bodyA.bounds, body.bounds)) { - for (var j = bodyA.parts.length === 1 ? 0 : 1; j < bodyA.parts.length; j++) { + if (overlaps(bodyA.bounds, bounds)) { + for (var j = partsAStart; j < partsALength; j++) { var part = bodyA.parts[j]; - if (Bounds.overlaps(part.bounds, body.bounds)) { - var collision = SAT.collides(part, body); + if (overlaps(part.bounds, bounds)) { + var collision = collides(part, body); - if (collision.collided) { + if (collision) { collisions.push(collision); break; } diff --git a/src/collision/SAT.js b/src/collision/SAT.js index 2ac56c6..3a3a611 100644 --- a/src/collision/SAT.js +++ b/src/collision/SAT.js @@ -4,53 +4,25 @@ * @class SAT */ -// TODO: true circles and curves - var SAT = {}; module.exports = SAT; var Vertices = require('../geometry/Vertices'); -var Vector = require('../geometry/Vector'); +var Pair = require('../collision/Pair'); +var Collision = require('./Collision'); (function() { + var _supports = []; var _overlapAB = { overlap: 0, - axis: null, - axisNumber: 0 + axis: null }; var _overlapBA = { overlap: 0, - axis: null, - axisNumber: 0 - }; - - /** - * Creates a new collision record. - * @private - * @method create - * @param {body} bodyA - * @param {body} bodyB - * @return {collision} A new collision - */ - SAT.create = function(bodyA, bodyB) { - return { - collided: false, - bodyA: bodyA, - bodyB: bodyB, - parentA: bodyA.parent, - parentB: bodyB.parent, - axisBodyA: bodyA, - axisBodyB: bodyB, - axisNumber: 0, - depth: 0, - normal: { x: 0, y: 0 }, - tangent: { x: 0, y: 0 }, - penetration: { x: 0, y: 0 }, - supports: [] - }; + axis: null }; /** @@ -58,128 +30,104 @@ var Vector = require('../geometry/Vector'); * @method collides * @param {body} bodyA * @param {body} bodyB - * @param {?collision} previousCollision - * @param {?boolean} [previousPairActive=false] - * @return {collision} collision + * @param {pairs} [pairs] Optionally reuse collision objects from existing pairs. + * @return {collision|null} A collision object if found, otherwise null */ - SAT.collides = function(bodyA, bodyB, previousCollision, pairActive) { - var minOverlap, - collision = previousCollision || SAT.create(bodyA, bodyB), - canReusePrevCol; + SAT.collides = function(bodyA, bodyB, pairs) { + SAT._overlapAxes(_overlapAB, bodyA.vertices, bodyB.vertices, bodyA.axes); - if (pairActive && previousCollision.collided) { - // estimate total motion - var parentA = bodyA.parent, - parentB = bodyB.parent, - motion = parentA.speed * parentA.speed + parentA.angularSpeed * parentA.angularSpeed - + parentB.speed * parentB.speed + parentB.angularSpeed * parentB.angularSpeed; - - // we may be able to (partially) reuse collision result - // but only safe if collision was resting - canReusePrevCol = motion < 0.2; + if (_overlapAB.overlap <= 0) { + return null; } - if (canReusePrevCol) { - // if we can reuse the collision result - // we only need to test the previously found axis - var axisBodyA = previousCollision.axisBodyA, - axisBodyB = previousCollision.axisBodyB, - axes = [axisBodyA.axes[previousCollision.axisNumber]]; + SAT._overlapAxes(_overlapBA, bodyB.vertices, bodyA.vertices, bodyB.axes); - SAT._overlapAxes(_overlapAB, axisBodyA.vertices, axisBodyB.vertices, axes); - - if (_overlapAB.overlap <= 0) { - collision.collided = false; - return collision; - } + if (_overlapBA.overlap <= 0) { + return null; + } - minOverlap = _overlapAB; + // reuse collision objects for gc efficiency + var pair = pairs && pairs.table[Pair.id(bodyA, bodyB)], + collision; + + if (!pair) { + collision = Collision.create(bodyA, bodyB); + collision.collided = true; + collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; + collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; + collision.parentA = collision.bodyA.parent; + collision.parentB = collision.bodyB.parent; } else { - // if we can't reuse a result, perform a full SAT test - - SAT._overlapAxes(_overlapAB, bodyA.vertices, bodyB.vertices, bodyA.axes); - - if (_overlapAB.overlap <= 0) { - collision.collided = false; - return collision; - } - - SAT._overlapAxes(_overlapBA, bodyB.vertices, bodyA.vertices, bodyB.axes); - - if (_overlapBA.overlap <= 0) { - collision.collided = false; - return collision; - } - - if (_overlapAB.overlap < _overlapBA.overlap) { - minOverlap = _overlapAB; - collision.axisBodyA = bodyA; - collision.axisBodyB = bodyB; - } else { - minOverlap = _overlapBA; - collision.axisBodyA = bodyB; - collision.axisBodyB = bodyA; - } - - // important for reuse later - collision.axisNumber = minOverlap.axisNumber; + collision = pair.collision; } - collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; - collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; - collision.collided = true; - collision.depth = minOverlap.overlap; - collision.parentA = collision.bodyA.parent; - collision.parentB = collision.bodyB.parent; - bodyA = collision.bodyA; bodyB = collision.bodyB; - var normal = collision.normal, - supports = collision.supports; + var minOverlap; - // ensure normal is facing away from bodyA - if (Vector.dot(minOverlap.axis, Vector.sub(bodyB.position, bodyA.position)) < 0) { - normal.x = minOverlap.axis.x; - normal.y = minOverlap.axis.y; + if (_overlapAB.overlap < _overlapBA.overlap) { + minOverlap = _overlapAB; } else { - normal.x = -minOverlap.axis.x; - normal.y = -minOverlap.axis.y; + minOverlap = _overlapBA; } + var normal = collision.normal, + supports = collision.supports, + minAxis = minOverlap.axis, + minAxisX = minAxis.x, + minAxisY = minAxis.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; + } + collision.tangent.x = -normal.y; collision.tangent.y = normal.x; + collision.depth = minOverlap.overlap; + collision.penetration.x = normal.x * collision.depth; - collision.penetration.y = normal.y * collision.depth; + collision.penetration.y = normal.y * collision.depth; // find support points, there is always either exactly one or two - var supportsB = SAT._findSupports(bodyA, bodyB, collision.normal, 1); - - // clear supports - supports.length = 0; + var supportsB = SAT._findSupports(bodyA, bodyB, normal, 1), + supportCount = 0; // find the supports from bodyB that are inside bodyA - if (Vertices.contains(bodyA.vertices, supportsB[0])) - supports.push(supportsB[0]); + if (Vertices.contains(bodyA.vertices, supportsB[0])) { + supports[supportCount++] = supportsB[0]; + } - if (Vertices.contains(bodyA.vertices, supportsB[1])) - supports.push(supportsB[1]); + if (Vertices.contains(bodyA.vertices, supportsB[1])) { + supports[supportCount++] = supportsB[1]; + } // find the supports from bodyA that are inside bodyB - if (supports.length < 2) { - var supportsA = SAT._findSupports(bodyB, bodyA, collision.normal, -1); + if (supportCount < 2) { + var supportsA = SAT._findSupports(bodyB, bodyA, normal, -1); - if (Vertices.contains(bodyB.vertices, supportsA[0])) - supports.push(supportsA[0]); + if (Vertices.contains(bodyB.vertices, supportsA[0])) { + supports[supportCount++] = supportsA[0]; + } - if (supports.length < 2 && Vertices.contains(bodyB.vertices, supportsA[1])) - supports.push(supportsA[1]); + if (supportCount < 2 && Vertices.contains(bodyB.vertices, supportsA[1])) { + supports[supportCount++] = supportsA[1]; + } } // account for the edge case of overlapping but no vertex containment - if (supports.length === 0) - supports.push(supportsB[0]); + if (supportCount === 0) { + supports[supportCount++] = supportsB[0]; + } + + // update supports array size + supports.length = supportCount; return collision; }; @@ -255,7 +203,6 @@ var Vector = require('../geometry/Vector'); } result.axis = axes[overlapAxisNumber]; - result.axisNumber = overlapAxisNumber; result.overlap = overlapMin; }; @@ -268,11 +215,11 @@ var Vector = require('../geometry/Vector'); * @param {} axis */ SAT._projectToAxis = function(projection, vertices, axis) { - var min = Vector.dot(vertices[0], 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 = Vector.dot(vertices[i], axis); + var dot = vertices[i].x * axis.x + vertices[i].y * axis.y; if (dot > max) { max = dot; @@ -328,10 +275,16 @@ var Vector = require('../geometry/Vector'); // compare with previous vertex vertexB = vertices[(vertexA.index + 1) % verticesLength]; if (normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y) < nearestDistance) { - return [vertexA, vertexB]; + _supports[0] = vertexA; + _supports[1] = vertexB; + + return _supports; } - return [vertexA, vertexC]; + _supports[0] = vertexA; + _supports[1] = vertexC; + + return _supports; }; })(); From 10afaea8bc6623111048cc12fe3fe83229cf295c Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 8 Dec 2021 23:53:12 +0000 Subject: [PATCH 40/62] change raycasting example events to enable use in tests --- examples/raycasting.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/raycasting.js b/examples/raycasting.js index 4e512c9..347f4de 100644 --- a/examples/raycasting.js +++ b/examples/raycasting.js @@ -68,14 +68,21 @@ Example.raycasting = function() { Bodies.rectangle(0, 300, 50, 600, { isStatic: true }) ]); + var collisions, + startPoint = { x: 400, y: 100 }; + + Events.on(engine, 'afterUpdate', function() { + var mouse = mouseConstraint.mouse, + bodies = Composite.allBodies(engine.world), + endPoint = mouse.position || { x: 100, y: 600 }; + + collisions = Query.ray(bodies, startPoint, endPoint); + }); + Events.on(render, 'afterRender', function() { var mouse = mouseConstraint.mouse, context = render.context, - bodies = Composite.allBodies(engine.world), - startPoint = { x: 400, y: 100 }, - endPoint = mouse.position; - - var collisions = Query.ray(bodies, startPoint, endPoint); + endPoint = mouse.position || { x: 100, y: 600 }; Render.startViewTransform(render); From 32fd2852d1dd37558624bd308d1f14500165e9b5 Mon Sep 17 00:00:00 2001 From: liabru Date: Thu, 9 Dec 2021 23:23:55 +0000 Subject: [PATCH 41/62] added cache checks to Matter.Composite --- src/body/Composite.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/body/Composite.js b/src/body/Composite.js index 496437a..b814d7d 100644 --- a/src/body/Composite.js +++ b/src/body/Composite.js @@ -62,7 +62,7 @@ var Body = require('./Body'); Composite.setModified = function(composite, isModified, updateParents, updateChildren) { composite.isModified = isModified; - if (isModified) { + if (isModified && composite.cache) { composite.cache.allBodies = null; composite.cache.allConstraints = null; composite.cache.allComposites = null; @@ -73,7 +73,7 @@ var Body = require('./Body'); } if (updateChildren) { - for(var i = 0; i < composite.composites.length; i++) { + for (var i = 0; i < composite.composites.length; i++) { var childComposite = composite.composites[i]; Composite.setModified(childComposite, isModified, updateParents, updateChildren); } @@ -359,7 +359,7 @@ var Body = require('./Body'); * @return {body[]} All the bodies */ Composite.allBodies = function(composite) { - if (composite.cache.allBodies) { + if (composite.cache && composite.cache.allBodies) { return composite.cache.allBodies; } @@ -368,7 +368,9 @@ var Body = require('./Body'); for (var i = 0; i < composite.composites.length; i++) bodies = bodies.concat(Composite.allBodies(composite.composites[i])); - composite.cache.allBodies = bodies; + if (composite.cache) { + composite.cache.allBodies = bodies; + } return bodies; }; @@ -380,7 +382,7 @@ var Body = require('./Body'); * @return {constraint[]} All the constraints */ Composite.allConstraints = function(composite) { - if (composite.cache.allConstraints) { + if (composite.cache && composite.cache.allConstraints) { return composite.cache.allConstraints; } @@ -389,7 +391,9 @@ var Body = require('./Body'); for (var i = 0; i < composite.composites.length; i++) constraints = constraints.concat(Composite.allConstraints(composite.composites[i])); - composite.cache.allConstraints = constraints; + if (composite.cache) { + composite.cache.allConstraints = constraints; + } return constraints; }; @@ -401,7 +405,7 @@ var Body = require('./Body'); * @return {composite[]} All the composites */ Composite.allComposites = function(composite) { - if (composite.cache.allComposites) { + if (composite.cache && composite.cache.allComposites) { return composite.cache.allComposites; } @@ -410,7 +414,9 @@ var Body = require('./Body'); for (var i = 0; i < composite.composites.length; i++) composites = composites.concat(Composite.allComposites(composite.composites[i])); - composite.cache.allComposites = composites; + if (composite.cache) { + composite.cache.allComposites = composites; + } return composites; }; From 8adf8102fbb3cef2febb62a1548d352c76699230 Mon Sep 17 00:00:00 2001 From: liabru Date: Fri, 10 Dec 2021 23:01:04 +0000 Subject: [PATCH 42/62] use force exit in tests --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index eadf1d5..0ac01da 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,8 @@ "test-all": "jest --no-cache", "test-save": "SAVE=true npm run test-node", "test-watch": "npm run test-node -- --watch", - "test-node": "npm run build-dev && node --expose-gc node_modules/.bin/jest --no-cache --runInBand ./test/Examples.spec.js", - "test-browser": "node --expose-gc node_modules/.bin/jest --no-cache --runInBand ./test/Browser.spec.js", + "test-node": "npm run build-dev && node --expose-gc node_modules/.bin/jest --force-exit --no-cache --runInBand ./test/Examples.spec.js", + "test-browser": "node --expose-gc node_modules/.bin/jest --force-exit --no-cache --runInBand ./test/Browser.spec.js", "changelog": "conventional-changelog -i CHANGELOG.md -s -r", "release": "npm version --no-git-tag-version", "preversion": "git checkout master && npm run lint && SAVE=true npm run test-all", From b9e7d9dd8ba7bfb80efab84d837d0571fa2d3c28 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 12 Dec 2021 10:45:50 +0000 Subject: [PATCH 43/62] replaced Matter.SAT with Matter.Collision --- src/collision/Collision.js | 378 ++++++++++++++++++++++++++++++++++++- src/collision/Query.js | 8 +- src/collision/SAT.js | 279 ++------------------------- src/module/main.js | 1 + test/ExampleWorker.js | 21 ++- 5 files changed, 408 insertions(+), 279 deletions(-) diff --git a/src/collision/Collision.js b/src/collision/Collision.js index ef53a7d..40b6cdf 100644 --- a/src/collision/Collision.js +++ b/src/collision/Collision.js @@ -1,5 +1,9 @@ /** -* The `Matter.Collision` module contains methods for managing collision records. +* The `Matter.Collision` module contains methods for detecting collisions between a given pair of bodies. +* +* For efficient detection between a list of bodies, see `Matter.Detector` and `Matter.Query`. +* +* See `Matter.Engine` for collision events. * * @class Collision */ @@ -8,13 +12,27 @@ var Collision = {}; module.exports = Collision; +var Vertices = require('../geometry/Vertices'); +var Pair = require('./Pair'); + (function() { + var _supports = []; + + var _overlapAB = { + overlap: 0, + axis: null + }; + + var _overlapBA = { + overlap: 0, + axis: null + }; /** * Creates a new collision record. * @method create - * @param {body} bodyA - * @param {body} bodyB + * @param {body} bodyA The first body part represented by the collision record + * @param {body} bodyB The second body part represented by the collision record * @return {collision} A new collision record */ Collision.create = function(bodyA, bodyB) { @@ -33,4 +51,358 @@ module.exports = Collision; }; }; + /** + * Detect collision between two bodies. + * @method collides + * @param {body} bodyA + * @param {body} bodyB + * @param {pairs} [pairs] Optionally reuse collision records from existing pairs. + * @return {collision|null} A collision record if detected, otherwise null + */ + Collision.collides = function(bodyA, bodyB, pairs) { + Collision._overlapAxes(_overlapAB, bodyA.vertices, bodyB.vertices, bodyA.axes); + + if (_overlapAB.overlap <= 0) { + return null; + } + + Collision._overlapAxes(_overlapBA, bodyB.vertices, bodyA.vertices, bodyB.axes); + + if (_overlapBA.overlap <= 0) { + return null; + } + + // reuse collision records for gc efficiency + var pair = pairs && pairs.table[Pair.id(bodyA, bodyB)], + collision; + + if (!pair) { + collision = Collision.create(bodyA, bodyB); + collision.collided = true; + collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; + collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; + collision.parentA = collision.bodyA.parent; + collision.parentB = collision.bodyB.parent; + } else { + collision = pair.collision; + } + + bodyA = collision.bodyA; + bodyB = collision.bodyB; + + var minOverlap; + + if (_overlapAB.overlap < _overlapBA.overlap) { + minOverlap = _overlapAB; + } else { + minOverlap = _overlapBA; + } + + var normal = collision.normal, + supports = collision.supports, + minAxis = minOverlap.axis, + minAxisX = minAxis.x, + minAxisY = minAxis.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; + } + + collision.tangent.x = -normal.y; + collision.tangent.y = normal.x; + + collision.depth = minOverlap.overlap; + + collision.penetration.x = normal.x * collision.depth; + collision.penetration.y = normal.y * collision.depth; + + // find support points, there is always either exactly one or two + var supportsB = Collision._findSupports(bodyA, bodyB, normal, 1), + supportCount = 0; + + // find the supports from bodyB that are inside bodyA + if (Vertices.contains(bodyA.vertices, supportsB[0])) { + supports[supportCount++] = supportsB[0]; + } + + if (Vertices.contains(bodyA.vertices, supportsB[1])) { + supports[supportCount++] = supportsB[1]; + } + + // find the supports from bodyA that are inside bodyB + if (supportCount < 2) { + var supportsA = Collision._findSupports(bodyB, bodyA, normal, -1); + + if (Vertices.contains(bodyB.vertices, supportsA[0])) { + supports[supportCount++] = supportsA[0]; + } + + if (supportCount < 2 && Vertices.contains(bodyB.vertices, supportsA[1])) { + supports[supportCount++] = supportsA[1]; + } + } + + // account for the edge case of overlapping but no vertex containment + if (supportCount === 0) { + supports[supportCount++] = supportsB[0]; + } + + // update supports array size + supports.length = supportCount; + + return collision; + }; + + /** + * Find the overlap between two sets of vertices. + * @method _overlapAxes + * @private + * @param {object} result + * @param {vertices} verticesA + * @param {vertices} verticesB + * @param {axes} axes + */ + Collision._overlapAxes = function(result, verticesA, verticesB, axes) { + var verticesALength = verticesA.length, + verticesBLength = verticesB.length, + verticesAX = verticesA[0].x, + verticesAY = verticesA[0].y, + verticesBX = verticesB[0].x, + verticesBY = verticesB[0].y, + axesLength = axes.length, + overlapMin = Number.MAX_VALUE, + overlapAxisNumber = 0, + overlap, + overlapAB, + overlapBA, + dot, + i, + j; + + for (i = 0; i < axesLength; i++) { + var axis = axes[i], + axisX = axis.x, + axisY = axis.y, + minA = verticesAX * axisX + verticesAY * axisY, + minB = verticesBX * axisX + verticesBY * axisY, + maxA = minA, + maxB = minB; + + for (j = 1; j < verticesALength; j += 1) { + dot = verticesA[j].x * axisX + verticesA[j].y * axisY; + + if (dot > maxA) { + maxA = dot; + } else if (dot < minA) { + minA = dot; + } + } + + for (j = 1; j < verticesBLength; j += 1) { + dot = verticesB[j].x * axisX + verticesB[j].y * axisY; + + if (dot > maxB) { + maxB = dot; + } else if (dot < minB) { + minB = dot; + } + } + + overlapAB = maxA - minB; + overlapBA = maxB - minA; + overlap = overlapAB < overlapBA ? overlapAB : overlapBA; + + if (overlap < overlapMin) { + overlapMin = overlap; + overlapAxisNumber = i; + + if (overlap <= 0) { + // can not be intersecting + break; + } + } + } + + result.axis = axes[overlapAxisNumber]; + 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 + * @private + * @param {body} bodyA + * @param {body} bodyB + * @param {vector} normal + * @param {number} direction + * @return [vector] + */ + Collision._findSupports = function(bodyA, bodyB, normal, direction) { + var vertices = bodyB.vertices, + verticesLength = vertices.length, + bodyAPositionX = bodyA.position.x, + bodyAPositionY = bodyA.position.y, + normalX = normal.x * direction, + normalY = normal.y * direction, + nearestDistance = Number.MAX_VALUE, + vertexA, + vertexB, + vertexC, + distance, + j; + + // find deepest vertex relative to the axis + for (j = 0; j < verticesLength; j += 1) { + vertexB = vertices[j]; + distance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y); + + // convex hill-climbing + if (distance < nearestDistance) { + nearestDistance = distance; + vertexA = vertexB; + } + } + + // measure next vertex + vertexC = vertices[(verticesLength + vertexA.index - 1) % verticesLength]; + nearestDistance = normalX * (bodyAPositionX - vertexC.x) + normalY * (bodyAPositionY - vertexC.y); + + // compare with previous vertex + vertexB = vertices[(vertexA.index + 1) % verticesLength]; + if (normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y) < nearestDistance) { + _supports[0] = vertexA; + _supports[1] = vertexB; + + return _supports; + } + + _supports[0] = vertexA; + _supports[1] = vertexC; + + return _supports; + }; + + /* + * + * Properties Documentation + * + */ + + /** + * A reference to the pair using this collision record, if there is one. + * + * @property pair + * @type {pair|null} + * @default null + */ + + /** + * A flag that indicates if the bodies were colliding when the collision was last updated. + * + * @property collided + * @type boolean + * @default false + */ + + /** + * The first body part represented by the collision (see also `collision.parentA`). + * + * @property bodyA + * @type body + */ + + /** + * The second body part represented by the collision (see also `collision.parentB`). + * + * @property bodyB + * @type body + */ + + /** + * The first body represented by the collision (i.e. `collision.bodyA.parent`). + * + * @property parentA + * @type body + */ + + /** + * The second body represented by the collision (i.e. `collision.bodyB.parent`). + * + * @property parentB + * @type body + */ + + /** + * A `Number` that represents the minimum separating distance between the bodies along the collision normal. + * + * @readOnly + * @property depth + * @type number + * @default 0 + */ + + /** + * A normalised `Vector` that represents the direction between the bodies that provides the minimum separating distance. + * + * @property normal + * @type vector + * @default { x: 0, y: 0 } + */ + + /** + * A normalised `Vector` that is the tangent direction to the collision normal. + * + * @property tangent + * @type vector + * @default { x: 0, y: 0 } + */ + + /** + * A `Vector` that represents the direction and depth of the collision. + * + * @property penetration + * @type vector + * @default { x: 0, y: 0 } + */ + + /** + * An array of body vertices that represent the support points in the collision. + * These are the deepest vertices (along the collision normal) of each body that are contained by the other body's vertices. + * + * @property supports + * @type vector[] + * @default [] + */ + })(); diff --git a/src/collision/Query.js b/src/collision/Query.js index cfc6877..4f45afb 100644 --- a/src/collision/Query.js +++ b/src/collision/Query.js @@ -11,7 +11,7 @@ var Query = {}; module.exports = Query; var Vector = require('../geometry/Vector'); -var SAT = require('./SAT'); +var Collision = require('./Collision'); var Bounds = require('../geometry/Bounds'); var Bodies = require('../factory/Bodies'); var Vertices = require('../geometry/Vertices'); @@ -23,13 +23,13 @@ var Vertices = require('../geometry/Vertices'); * @method collides * @param {body} body * @param {body[]} bodies - * @return {object[]} Collisions + * @return {collision[]} Collisions */ Query.collides = function(body, bodies) { var collisions = [], bodiesLength = bodies.length, bounds = body.bounds, - collides = SAT.collides, + collides = Collision.collides, overlaps = Bounds.overlaps; for (var i = 0; i < bodiesLength; i++) { @@ -63,7 +63,7 @@ var Vertices = require('../geometry/Vertices'); * @param {vector} startPoint * @param {vector} endPoint * @param {number} [rayWidth] - * @return {object[]} Collisions + * @return {collision[]} Collisions */ Query.ray = function(bodies, startPoint, endPoint, rayWidth) { rayWidth = rayWidth || 1e-100; diff --git a/src/collision/SAT.js b/src/collision/SAT.js index 3a3a611..cc0e8c5 100644 --- a/src/collision/SAT.js +++ b/src/collision/SAT.js @@ -1,290 +1,37 @@ /** +* This module has now been replaced by `Matter.Collision`. +* +* All usage should be migrated to `Matter.Collision`. +* For back-compatibility purposes this module will remain for a short term and then later removed in a future release. +* * The `Matter.SAT` module contains methods for detecting collisions using the Separating Axis Theorem. * * @class SAT +* @deprecated */ var SAT = {}; module.exports = SAT; -var Vertices = require('../geometry/Vertices'); -var Pair = require('../collision/Pair'); var Collision = require('./Collision'); +var Common = require('../core/Common'); +var deprecated = Common.deprecated; (function() { - var _supports = []; - - var _overlapAB = { - overlap: 0, - axis: null - }; - - var _overlapBA = { - overlap: 0, - axis: null - }; /** * Detect collision between two bodies using the Separating Axis Theorem. + * @deprecated replaced by Collision.collides * @method collides * @param {body} bodyA * @param {body} bodyB - * @param {pairs} [pairs] Optionally reuse collision objects from existing pairs. - * @return {collision|null} A collision object if found, otherwise null + * @return {collision} collision */ - SAT.collides = function(bodyA, bodyB, pairs) { - SAT._overlapAxes(_overlapAB, bodyA.vertices, bodyB.vertices, bodyA.axes); - - if (_overlapAB.overlap <= 0) { - return null; - } - - SAT._overlapAxes(_overlapBA, bodyB.vertices, bodyA.vertices, bodyB.axes); - - if (_overlapBA.overlap <= 0) { - return null; - } - - // reuse collision objects for gc efficiency - var pair = pairs && pairs.table[Pair.id(bodyA, bodyB)], - collision; - - if (!pair) { - collision = Collision.create(bodyA, bodyB); - collision.collided = true; - collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; - collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; - collision.parentA = collision.bodyA.parent; - collision.parentB = collision.bodyB.parent; - } else { - collision = pair.collision; - } - - bodyA = collision.bodyA; - bodyB = collision.bodyB; - - var minOverlap; - - if (_overlapAB.overlap < _overlapBA.overlap) { - minOverlap = _overlapAB; - } else { - minOverlap = _overlapBA; - } - - var normal = collision.normal, - supports = collision.supports, - minAxis = minOverlap.axis, - minAxisX = minAxis.x, - minAxisY = minAxis.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; - } - - collision.tangent.x = -normal.y; - collision.tangent.y = normal.x; - - collision.depth = minOverlap.overlap; - - collision.penetration.x = normal.x * collision.depth; - collision.penetration.y = normal.y * collision.depth; - - // find support points, there is always either exactly one or two - var supportsB = SAT._findSupports(bodyA, bodyB, normal, 1), - supportCount = 0; - - // find the supports from bodyB that are inside bodyA - if (Vertices.contains(bodyA.vertices, supportsB[0])) { - supports[supportCount++] = supportsB[0]; - } - - if (Vertices.contains(bodyA.vertices, supportsB[1])) { - supports[supportCount++] = supportsB[1]; - } - - // find the supports from bodyA that are inside bodyB - if (supportCount < 2) { - var supportsA = SAT._findSupports(bodyB, bodyA, normal, -1); - - if (Vertices.contains(bodyB.vertices, supportsA[0])) { - supports[supportCount++] = supportsA[0]; - } - - if (supportCount < 2 && Vertices.contains(bodyB.vertices, supportsA[1])) { - supports[supportCount++] = supportsA[1]; - } - } - - // account for the edge case of overlapping but no vertex containment - if (supportCount === 0) { - supports[supportCount++] = supportsB[0]; - } - - // update supports array size - supports.length = supportCount; - - return collision; + SAT.collides = function(bodyA, bodyB) { + return Collision.collides(bodyA, bodyB); }; - /** - * Find the overlap between two sets of vertices. - * @method _overlapAxes - * @private - * @param {object} result - * @param {vertices} verticesA - * @param {vertices} verticesB - * @param {axes} axes - */ - SAT._overlapAxes = function(result, verticesA, verticesB, axes) { - var verticesALength = verticesA.length, - verticesBLength = verticesB.length, - verticesAX = verticesA[0].x, - verticesAY = verticesA[0].y, - verticesBX = verticesB[0].x, - verticesBY = verticesB[0].y, - axesLength = axes.length, - overlapMin = Number.MAX_VALUE, - overlapAxisNumber = 0, - overlap, - overlapAB, - overlapBA, - dot, - i, - j; - - for (i = 0; i < axesLength; i++) { - var axis = axes[i], - axisX = axis.x, - axisY = axis.y, - minA = verticesAX * axisX + verticesAY * axisY, - minB = verticesBX * axisX + verticesBY * axisY, - maxA = minA, - maxB = minB; - - for (j = 1; j < verticesALength; j += 1) { - dot = verticesA[j].x * axisX + verticesA[j].y * axisY; - - if (dot > maxA) { - maxA = dot; - } else if (dot < minA) { - minA = dot; - } - } - - for (j = 1; j < verticesBLength; j += 1) { - dot = verticesB[j].x * axisX + verticesB[j].y * axisY; - - if (dot > maxB) { - maxB = dot; - } else if (dot < minB) { - minB = dot; - } - } - - overlapAB = maxA - minB; - overlapBA = maxB - minA; - overlap = overlapAB < overlapBA ? overlapAB : overlapBA; - - if (overlap < overlapMin) { - overlapMin = overlap; - overlapAxisNumber = i; - - if (overlap <= 0) { - // can not be intersecting - break; - } - } - } - - result.axis = axes[overlapAxisNumber]; - result.overlap = overlapMin; - }; - - /** - * Projects vertices on an axis and returns an interval. - * @method _projectToAxis - * @private - * @param {} projection - * @param {} vertices - * @param {} axis - */ - SAT._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 - * @private - * @param {body} bodyA - * @param {body} bodyB - * @param {vector} normal - * @param {number} direction - * @return [vector] - */ - SAT._findSupports = function(bodyA, bodyB, normal, direction) { - var vertices = bodyB.vertices, - verticesLength = vertices.length, - bodyAPositionX = bodyA.position.x, - bodyAPositionY = bodyA.position.y, - normalX = normal.x * direction, - normalY = normal.y * direction, - nearestDistance = Number.MAX_VALUE, - vertexA, - vertexB, - vertexC, - distance, - j; - - // find deepest vertex relative to the axis - for (j = 0; j < verticesLength; j += 1) { - vertexB = vertices[j]; - distance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y); - - // convex hill-climbing - if (distance < nearestDistance) { - nearestDistance = distance; - vertexA = vertexB; - } - } - - // measure next vertex - vertexC = vertices[(verticesLength + vertexA.index - 1) % verticesLength]; - nearestDistance = normalX * (bodyAPositionX - vertexC.x) + normalY * (bodyAPositionY - vertexC.y); - - // compare with previous vertex - vertexB = vertices[(vertexA.index + 1) % verticesLength]; - if (normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y) < nearestDistance) { - _supports[0] = vertexA; - _supports[1] = vertexB; - - return _supports; - } - - _supports[0] = vertexA; - _supports[1] = vertexC; - - return _supports; - }; + deprecated(SAT, 'collides', 'SAT.collides ➤ replaced by Collision.collides'); })(); diff --git a/src/module/main.js b/src/module/main.js index 869ce38..fa0b57c 100644 --- a/src/module/main.js +++ b/src/module/main.js @@ -4,6 +4,7 @@ Matter.Axes = require('../geometry/Axes'); Matter.Bodies = require('../factory/Bodies'); Matter.Body = require('../body/Body'); Matter.Bounds = require('../geometry/Bounds'); +Matter.Collision = require('../collision/Collision'); Matter.Common = require('../core/Common'); Matter.Composite = require('../body/Composite'); Matter.Composites = require('../factory/Composites'); diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 81b25eb..7b8b43d 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -79,12 +79,21 @@ const prepareMatter = (options) => { Matter.Common.info = Matter.Common.warn = Matter.Common.log; if (options.stableSort) { - const MatterSATCollides = Matter.SAT.collides; - Matter.SAT.collides = function(bodyA, bodyB, previousCollision, pairActive) { - const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; - const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; - return MatterSATCollides(_bodyA, _bodyB, previousCollision, pairActive); - }; + if (Matter.Collision) { + const MatterCollisionCollides = Matter.Collision.collides; + Matter.Collision.collides = function(bodyA, bodyB, pairs) { + const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; + const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; + return MatterCollisionCollides(_bodyA, _bodyB, pairs); + }; + } else { + const MatterSATCollides = Matter.SAT.collides; + Matter.SAT.collides = function(bodyA, bodyB, previousCollision, pairActive) { + const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; + const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; + return MatterSATCollides(_bodyA, _bodyB, previousCollision, pairActive); + }; + } Matter.after('Detector.collisions', function() { this.sort(collisionCompareId); }); Matter.after('Composite.allBodies', function() { sortById(this); }); From a6b5e7d8497c92a801af47732308b3acd4cc699b Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 12 Dec 2021 11:25:57 +0000 Subject: [PATCH 44/62] added broadphase to Matter.Detector --- src/collision/Detector.js | 196 +++++++++++++++++++++++++++----------- src/core/Engine.js | 107 +++++++++++---------- 2 files changed, 193 insertions(+), 110 deletions(-) diff --git a/src/collision/Detector.js b/src/collision/Detector.js index 7b47c9f..a4626e2 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -1,84 +1,133 @@ /** -* The `Matter.Detector` module contains methods for detecting collisions given a set of pairs. +* The `Matter.Detector` module contains methods for efficiently detecting collisions between a list of bodies using a broadphase algorithm. * * @class Detector */ -// TODO: speculative contacts - var Detector = {}; module.exports = Detector; -var SAT = require('./SAT'); -var Pair = require('./Pair'); +var Common = require('../core/Common'); +var Collision = require('./Collision'); (function() { /** - * Finds all collisions given a list of pairs. - * @method collisions - * @param {pair[]} broadphasePairs - * @param {engine} engine - * @return {array} collisions + * Creates a new collision detector. + * @method create + * @param {} options + * @return {detector} A new collision detector */ - Detector.collisions = function(broadphasePairs, engine) { + Detector.create = function(options) { + var defaults = { + bodies: [], + pairs: null + }; + + return Common.extend(defaults, options); + }; + + /** + * Sets the list of bodies in the detector. + * @method setBodies + * @param {detector} detector + * @param {body[]} bodies + */ + Detector.setBodies = function(detector, bodies) { + detector.bodies = bodies.slice(0); + }; + + /** + * Clears the detector including its list of bodies. + * @method clear + * @param {detector} detector + */ + Detector.clear = function(detector) { + detector.bodies = []; + }; + + /** + * Efficiently finds all collisions among all the bodies in `detector.bodies` using a broadphase algorithm. + * + * _Note:_ The specific ordering of collisions returned is not guaranteed between releases and may change for performance reasons. + * If a specific ordering is required then apply a sort to the resulting array. + * @method collisions + * @param {detector} detector + * @return {collision[]} collisions + */ + Detector.collisions = function(detector) { var collisions = [], - pairs = engine.pairs, - broadphasePairsLength = broadphasePairs.length, + pairs = detector.pairs, + bodies = detector.bodies, + bodiesLength = bodies.length, canCollide = Detector.canCollide, - collides = SAT.collides, - i; + collides = Collision.collides, + i, + j; - for (i = 0; i < broadphasePairsLength; i++) { - var broadphasePair = broadphasePairs[i], - bodyA = broadphasePair[0], - bodyB = broadphasePair[1]; + bodies.sort(Detector._compareBoundsX); - if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping)) - continue; - - if (!canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) - continue; + for (i = 0; i < bodiesLength; i++) { + var bodyA = bodies[i], + boundsA = bodyA.bounds, + boundXMax = bodyA.bounds.max.x, + boundYMax = bodyA.bounds.max.y, + boundYMin = bodyA.bounds.min.y, + bodyAStatic = bodyA.isStatic || bodyA.isSleeping, + partsALength = bodyA.parts.length, + partsASingle = partsALength === 1; - var boundsA = bodyA.bounds, - boundsB = bodyB.bounds; + for (j = i + 1; j < bodiesLength; j++) { + var bodyB = bodies[j], + boundsB = bodyB.bounds; - if (boundsA.min.x > boundsB.max.x || boundsA.max.x < boundsB.min.x - || boundsA.max.y < boundsB.min.y || boundsA.min.y > boundsB.max.y) { - continue; - } - - var partsALength = bodyA.parts.length, - partsBLength = bodyB.parts.length; - - if (partsALength === 1 && partsBLength === 1) { - var collision = collides(bodyA, bodyB, pairs); - - if (collision) { - collisions.push(collision); + if (boundsB.min.x > boundXMax) { + break; } - } else { - var partsAStart = partsALength > 1 ? 1 : 0, - partsBStart = partsBLength > 1 ? 1 : 0; - - for (var j = partsAStart; j < partsALength; j++) { - var partA = bodyA.parts[j], - boundsA = partA.bounds; - for (var k = partsBStart; k < partsBLength; k++) { - var partB = bodyB.parts[k], - boundsB = partB.bounds; + if (boundYMax < boundsB.min.y || boundYMin > boundsB.max.y) { + continue; + } - if (boundsA.min.x > boundsB.max.x || boundsA.max.x < boundsB.min.x - || boundsA.max.y < boundsB.min.y || boundsA.min.y > boundsB.max.y) { - continue; - } + if (bodyAStatic && (bodyB.isStatic || bodyB.isSleeping)) { + continue; + } - var collision = collides(partA, partB, pairs); + if (!canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) { + continue; + } - if (collision) { - collisions.push(collision); + var partsBLength = bodyB.parts.length; + + if (partsASingle && partsBLength === 1) { + var collision = collides(bodyA, bodyB, pairs); + + if (collision) { + collisions.push(collision); + } + } else { + var partsAStart = partsALength > 1 ? 1 : 0, + partsBStart = partsBLength > 1 ? 1 : 0; + + for (var k = partsAStart; k < partsALength; k++) { + var partA = bodyA.parts[k], + boundsA = partA.bounds; + + for (var z = partsBStart; z < partsBLength; z++) { + var partB = bodyB.parts[z], + boundsB = partB.bounds; + + if (boundsA.min.x > boundsB.max.x || boundsA.max.x < boundsB.min.x + || boundsA.max.y < boundsB.min.y || boundsA.min.y > boundsB.max.y) { + continue; + } + + var collision = collides(partA, partB, pairs); + + if (collision) { + collisions.push(collision); + } } } } @@ -103,4 +152,39 @@ var Pair = require('./Pair'); return (filterA.mask & filterB.category) !== 0 && (filterB.mask & filterA.category) !== 0; }; + /** + * The comparison function used in the broadphase algorithm. + * Returns the signed delta of the bodies bounds on the x-axis. + * @private + * @method _sortCompare + * @param {body} bodyA + * @param {body} bodyB + * @return {number} The signed delta used for sorting + */ + Detector._compareBoundsX = function(bodyA, bodyB) { + return bodyA.bounds.min.x - bodyB.bounds.min.x; + }; + + /* + * + * Properties Documentation + * + */ + + /** + * The array of `Matter.Body` between which the detector finds collisions. + * + * _Note:_ The order of bodies in this array _is not fixed_ and will be continually managed by the detector. + * @property bodies + * @type body[] + * @default [] + */ + + /** + * Optional. A `Matter.Pairs` object from which previous collision objects may be reused. Intended for internal `Matter.Engine` usage. + * @property pairs + * @type {pairs|null} + * @default null + */ + })(); diff --git a/src/core/Engine.js b/src/core/Engine.js index bb98b55..7d15e4c 100644 --- a/src/core/Engine.js +++ b/src/core/Engine.js @@ -16,7 +16,6 @@ var Sleeping = require('./Sleeping'); var Resolver = require('../collision/Resolver'); var Detector = require('../collision/Detector'); var Pairs = require('../collision/Pairs'); -var Grid = require('../collision/Grid'); var Events = require('./Events'); var Composite = require('../body/Composite'); var Constraint = require('../constraint/Constraint'); @@ -43,7 +42,6 @@ var Body = require('../body/Body'); enableSleeping: false, events: [], plugin: {}, - grid: null, gravity: { x: 0, y: 1, @@ -60,10 +58,11 @@ var Body = require('../body/Body'); var engine = Common.extend(defaults, options); engine.world = options.world || Composite.create({ label: 'World' }); - engine.grid = Grid.create(options.grid || options.broadphase); - engine.pairs = Pairs.create(); + engine.pairs = options.pairs || Pairs.create(); + engine.detector = options.detector || Detector.create(); - // temporary back compatibility + // for temporary back compatibility only + engine.grid = { buckets: [] }; engine.world.gravity = engine.gravity; engine.broadphase = engine.grid; engine.metrics = {}; @@ -93,9 +92,10 @@ var Body = require('../body/Body'); correction = correction || 1; var world = engine.world, + detector = engine.detector, + pairs = engine.pairs, timing = engine.timing, - grid = engine.grid, - gridPairs = [], + timestamp = timing.timestamp, i; // increment timestamp @@ -109,15 +109,25 @@ var Body = require('../body/Body'); Events.trigger(engine, 'beforeUpdate', event); - // get lists of all bodies and constraints, no matter what composites they are in + // get all bodies and all constraints in the world var allBodies = Composite.allBodies(world), allConstraints = Composite.allConstraints(world); - // if sleeping enabled, call the sleeping controller + // update the detector bodies if they have changed + if (world.isModified) { + Detector.setBodies(detector, allBodies); + } + + // reset all composite modified flags + if (world.isModified) { + Composite.setModified(world, false, false, true); + } + + // update sleeping if enabled if (engine.enableSleeping) Sleeping.update(allBodies, timing.timeScale); - // applies gravity to all bodies + // apply gravity to all bodies Engine._bodiesApplyGravity(allBodies, engine.gravity); // update all body position and rotation by integration @@ -130,27 +140,11 @@ var Body = require('../body/Body'); } Constraint.postSolveAll(allBodies); - // broadphase pass: find potential collision pairs - - // if world is dirty, we must flush the whole grid - if (world.isModified) - Grid.clear(grid); - - // update the grid buckets based on current bodies - Grid.update(grid, allBodies, engine, world.isModified); - gridPairs = grid.pairsList; - - // clear all composite modified flags - if (world.isModified) { - Composite.setModified(world, false, false, true); - } - - // narrowphase pass: find actual collisions, then create or update collision pairs - var collisions = Detector.collisions(gridPairs, engine); + // find all collisions + detector.pairs = engine.pairs; + var collisions = Detector.collisions(detector); // update collision pairs - var pairs = engine.pairs, - timestamp = timing.timestamp; Pairs.update(pairs, collisions, timestamp); // wake up bodies involved in collisions @@ -224,17 +218,13 @@ var Body = require('../body/Body'); }; /** - * Clears the engine including the world, pairs and broadphase. + * Clears the engine pairs and detector. * @method clear * @param {engine} engine */ Engine.clear = function(engine) { - var world = engine.world, - bodies = Composite.allBodies(world); - Pairs.clear(engine.pairs); - Grid.clear(engine.grid); - Grid.update(engine.grid, bodies, engine, true); + Detector.clear(engine.detector); }; /** @@ -314,53 +304,53 @@ var Body = require('../body/Body'); * Fired just before an update * * @event beforeUpdate - * @param {} event An event object + * @param {object} event An event object * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update and all collision events * * @event afterUpdate - * @param {} event An event object + * @param {object} event An event object * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that have started to collide in the current tick (if any) * * @event collisionStart - * @param {} event An event object - * @param {} event.pairs List of affected pairs + * @param {object} event An event object + * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that are colliding in the current tick (if any) * * @event collisionActive - * @param {} event An event object - * @param {} event.pairs List of affected pairs + * @param {object} event An event object + * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that have ended collision in the current tick (if any) * * @event collisionEnd - * @param {} event An event object - * @param {} event.pairs List of affected pairs + * @param {object} event An event object + * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /* @@ -452,9 +442,18 @@ var Body = require('../body/Body'); * @default 0 */ + /** + * A `Matter.Detector` instance. + * + * @property detector + * @type detector + * @default a Matter.Detector instance + */ + /** * A `Matter.Grid` instance. * + * @deprecated replaced by `engine.detector` * @property grid * @type grid * @default a Matter.Grid instance @@ -463,7 +462,7 @@ var Body = require('../body/Body'); /** * Replaced by and now alias for `engine.grid`. * - * @deprecated use `engine.grid` + * @deprecated replaced by `engine.detector` * @property broadphase * @type grid * @default a Matter.Grid instance From e366d0e9923e629a38e973bdf840fb96927ce26c Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 12 Dec 2021 12:22:21 +0000 Subject: [PATCH 45/62] deprecated Matter.Grid --- src/collision/Grid.js | 22 ++++++++++++++++++++ src/render/Render.js | 47 ++++--------------------------------------- 2 files changed, 26 insertions(+), 43 deletions(-) diff --git a/src/collision/Grid.js b/src/collision/Grid.js index 6bcc1c5..e01ca01 100644 --- a/src/collision/Grid.js +++ b/src/collision/Grid.js @@ -1,7 +1,13 @@ /** +* This module has now been replaced by `Matter.Detector`. +* +* All usage should be migrated to `Matter.Detector` or another alternative. +* For back-compatibility purposes this module will remain for a short term and then later removed in a future release. +* * The `Matter.Grid` module contains methods for creating and manipulating collision broadphase grid structures. * * @class Grid +* @deprecated */ var Grid = {}; @@ -10,11 +16,13 @@ module.exports = Grid; var Pair = require('./Pair'); var Common = require('../core/Common'); +var deprecated = Common.deprecated; (function() { /** * Creates a new grid. + * @deprecated replaced by Matter.Detector * @method create * @param {} options * @return {grid} A new grid @@ -49,6 +57,7 @@ var Common = require('../core/Common'); /** * Updates the grid. + * @deprecated replaced by Matter.Detector * @method update * @param {grid} grid * @param {body[]} bodies @@ -127,8 +136,11 @@ var Common = require('../core/Common'); grid.pairsList = Grid._createActivePairsList(grid); }; + deprecated(Grid, 'update', 'Grid.update ➤ replaced by Matter.Detector'); + /** * Clears the grid. + * @deprecated replaced by Matter.Detector * @method clear * @param {grid} grid */ @@ -138,9 +150,12 @@ var Common = require('../core/Common'); grid.pairsList = []; }; + deprecated(Grid, 'clear', 'Grid.clear ➤ replaced by Matter.Detector'); + /** * Finds the union of two regions. * @method _regionUnion + * @deprecated replaced by Matter.Detector * @private * @param {} regionA * @param {} regionB @@ -158,6 +173,7 @@ var Common = require('../core/Common'); /** * Gets the region a given body falls in for a given grid. * @method _getRegion + * @deprecated replaced by Matter.Detector * @private * @param {} grid * @param {} body @@ -176,6 +192,7 @@ var Common = require('../core/Common'); /** * Creates a region. * @method _createRegion + * @deprecated replaced by Matter.Detector * @private * @param {} startCol * @param {} endCol @@ -196,6 +213,7 @@ var Common = require('../core/Common'); /** * Gets the bucket id at the given position. * @method _getBucketId + * @deprecated replaced by Matter.Detector * @private * @param {} column * @param {} row @@ -208,6 +226,7 @@ var Common = require('../core/Common'); /** * Creates a bucket. * @method _createBucket + * @deprecated replaced by Matter.Detector * @private * @param {} buckets * @param {} bucketId @@ -221,6 +240,7 @@ var Common = require('../core/Common'); /** * Adds a body to a bucket. * @method _bucketAddBody + * @deprecated replaced by Matter.Detector * @private * @param {} grid * @param {} bucket @@ -258,6 +278,7 @@ var Common = require('../core/Common'); /** * Removes a body from a bucket. * @method _bucketRemoveBody + * @deprecated replaced by Matter.Detector * @private * @param {} grid * @param {} bucket @@ -287,6 +308,7 @@ var Common = require('../core/Common'); /** * Generates a list of the active pairs in the grid. * @method _createActivePairsList + * @deprecated replaced by Matter.Detector * @private * @param {} grid * @return [] pairs diff --git a/src/render/Render.js b/src/render/Render.js index 6ee0aaf..7888226 100644 --- a/src/render/Render.js +++ b/src/render/Render.js @@ -76,7 +76,6 @@ var Mouse = require('../core/Mouse'); showDebug: false, showStats: false, showPerformance: false, - showBroadphase: false, showBounds: false, showVelocity: false, showCollisions: false, @@ -116,6 +115,9 @@ var Mouse = require('../core/Mouse'); } }; + // for temporary back compatibility only + render.options.showBroadphase = false; + if (render.options.pixelRatio !== 1) { Render.setPixelRatio(render, render.options.pixelRatio); } @@ -436,9 +438,6 @@ var Mouse = require('../core/Mouse'); Render.constraints(constraints, context); - if (options.showBroadphase) - Render.grid(render, engine.grid, context); - if (options.hasBounds) { // revert view transforms Render.endViewTransform(render); @@ -1285,45 +1284,6 @@ var Mouse = require('../core/Mouse'); c.stroke(); }; - /** - * Description - * @private - * @method grid - * @param {render} render - * @param {grid} grid - * @param {RenderingContext} context - */ - Render.grid = function(render, grid, context) { - var c = context, - options = render.options; - - if (options.wireframes) { - c.strokeStyle = 'rgba(255,180,0,0.1)'; - } else { - c.strokeStyle = 'rgba(255,180,0,0.5)'; - } - - c.beginPath(); - - var bucketKeys = Common.keys(grid.buckets); - - for (var i = 0; i < bucketKeys.length; i++) { - var bucketId = bucketKeys[i]; - - if (grid.buckets[bucketId].length < 2) - continue; - - var region = bucketId.split(/C|R/); - c.rect(0.5 + parseInt(region[1], 10) * grid.bucketWidth, - 0.5 + parseInt(region[2], 10) * grid.bucketHeight, - grid.bucketWidth, - grid.bucketHeight); - } - - c.lineWidth = 1; - c.stroke(); - }; - /** * Description * @private @@ -1765,6 +1725,7 @@ var Mouse = require('../core/Mouse'); /** * A flag to enable or disable the collision broadphase debug overlay. * + * @deprecated no longer implemented * @property options.showBroadphase * @type boolean * @default false From c80ed5c6a79ca193330fac5242fda7a227fd9853 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 12 Dec 2021 12:50:10 +0000 Subject: [PATCH 46/62] updated examples --- README.md | 1 - examples/broadphase.js | 103 ----------------------------------------- examples/index.js | 1 - examples/raycasting.js | 2 +- 4 files changed, 1 insertion(+), 106 deletions(-) delete mode 100644 examples/broadphase.js diff --git a/README.md b/README.md index b5745b5..2c3b129 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,6 @@
  • Air Friction
  • Static Friction
  • Sleeping
  • -
  • Grid Broadphase
  • Beach Balls
  • Stress 1
  • Stress 2
  • diff --git a/examples/broadphase.js b/examples/broadphase.js deleted file mode 100644 index deb7963..0000000 --- a/examples/broadphase.js +++ /dev/null @@ -1,103 +0,0 @@ -var Example = Example || {}; - -Example.broadphase = function() { - var Engine = Matter.Engine, - Render = Matter.Render, - Runner = Matter.Runner, - Composites = Matter.Composites, - Common = Matter.Common, - MouseConstraint = Matter.MouseConstraint, - Mouse = Matter.Mouse, - Composite = Matter.Composite, - Bodies = Matter.Bodies; - - // create engine - var engine = Engine.create(), - world = engine.world; - - // create renderer - var render = Render.create({ - element: document.body, - engine: engine, - options: { - width: 800, - height: 600, - showAngleIndicator: true, - showBroadphase: true - } - }); - - Render.run(render); - - // create runner - var runner = Runner.create(); - Runner.run(runner, engine); - - // add bodies - Composite.add(world, [ - // walls - Bodies.rectangle(400, 0, 800, 50, { isStatic: true }), - Bodies.rectangle(400, 600, 800, 50, { isStatic: true }), - Bodies.rectangle(800, 300, 50, 600, { isStatic: true }), - Bodies.rectangle(0, 300, 50, 600, { isStatic: true }) - ]); - - var stack = Composites.stack(20, 20, 12, 5, 0, 0, function(x, y) { - switch (Math.round(Common.random(0, 1))) { - - case 0: - if (Common.random() < 0.8) { - return Bodies.rectangle(x, y, Common.random(20, 50), Common.random(20, 50)); - } else { - return Bodies.rectangle(x, y, Common.random(80, 120), Common.random(20, 30)); - } - case 1: - return Bodies.polygon(x, y, Math.round(Common.random(1, 8)), Common.random(20, 50)); - - } - }); - - Composite.add(world, stack); - - // add mouse control - var mouse = Mouse.create(render.canvas), - mouseConstraint = MouseConstraint.create(engine, { - mouse: mouse, - constraint: { - stiffness: 0.2, - render: { - visible: false - } - } - }); - - Composite.add(world, mouseConstraint); - - // keep the mouse in sync with rendering - render.mouse = mouse; - - // fit the render viewport to the scene - Render.lookAt(render, { - min: { x: 0, y: 0 }, - max: { x: 800, y: 600 } - }); - - // context for MatterTools.Demo - return { - engine: engine, - runner: runner, - render: render, - canvas: render.canvas, - stop: function() { - Matter.Render.stop(render); - Matter.Runner.stop(runner); - } - }; -}; - -Example.broadphase.title = 'Broadphase'; -Example.broadphase.for = '>=0.14.2'; - -if (typeof module !== 'undefined') { - module.exports = Example.broadphase; -} diff --git a/examples/index.js b/examples/index.js index 3205a4f..e11d4e9 100644 --- a/examples/index.js +++ b/examples/index.js @@ -3,7 +3,6 @@ module.exports = { avalanche: require('./avalanche.js'), ballPool: require('./ballPool.js'), bridge: require('./bridge.js'), - broadphase: require('./broadphase.js'), car: require('./car.js'), catapult: require('./catapult.js'), chains: require('./chains.js'), diff --git a/examples/raycasting.js b/examples/raycasting.js index 347f4de..4348c36 100644 --- a/examples/raycasting.js +++ b/examples/raycasting.js @@ -68,7 +68,7 @@ Example.raycasting = function() { Bodies.rectangle(0, 300, 50, 600, { isStatic: true }) ]); - var collisions, + var collisions = [], startPoint = { x: 400, y: 100 }; Events.on(engine, 'afterUpdate', function() { From bc07f56a94c70b6ce78cd8d8dc43dd7922678519 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 12 Dec 2021 13:19:37 +0000 Subject: [PATCH 47/62] added example for Composite.remove --- examples/index.js | 1 + examples/remove.js | 138 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 examples/remove.js diff --git a/examples/index.js b/examples/index.js index e11d4e9..bee420f 100644 --- a/examples/index.js +++ b/examples/index.js @@ -27,6 +27,7 @@ module.exports = { raycasting: require('./raycasting.js'), restitution: require('./restitution.js'), rounded: require('./rounded.js'), + remove: require('./remove.js'), sensors: require('./sensors.js'), sleeping: require('./sleeping.js'), slingshot: require('./slingshot.js'), diff --git a/examples/remove.js b/examples/remove.js new file mode 100644 index 0000000..6fac386 --- /dev/null +++ b/examples/remove.js @@ -0,0 +1,138 @@ +var Example = Example || {}; + +Example.remove = function() { + var Engine = Matter.Engine, + Render = Matter.Render, + Runner = Matter.Runner, + Composites = Matter.Composites, + Common = Matter.Common, + MouseConstraint = Matter.MouseConstraint, + Mouse = Matter.Mouse, + Composite = Matter.Composite, + Bodies = Matter.Bodies, + Events = Matter.Events; + + // create engine + var engine = Engine.create(), + world = engine.world; + + // create renderer + var render = Render.create({ + element: document.body, + engine: engine, + options: { + width: 800, + height: 600, + showAngleIndicator: true, + } + }); + + Render.run(render); + + // create runner + var runner = Runner.create(); + Runner.run(runner, engine); + + var stack = null, + updateCount = 0; + + var createStack = function() { + return Composites.stack(20, 20, 10, 5, 0, 0, function(x, y) { + var sides = Math.round(Common.random(1, 8)); + + // round the edges of some bodies + var chamfer = null; + if (sides > 2 && Common.random() > 0.7) { + chamfer = { + radius: 10 + }; + } + + switch (Math.round(Common.random(0, 1))) { + case 0: + if (Common.random() < 0.8) { + return Bodies.rectangle(x, y, Common.random(25, 50), Common.random(25, 50), { chamfer: chamfer }); + } else { + return Bodies.rectangle(x, y, Common.random(80, 120), Common.random(25, 30), { chamfer: chamfer }); + } + case 1: + return Bodies.polygon(x, y, sides, Common.random(25, 50), { chamfer: chamfer }); + } + }); + }; + + // add and remove stacks every few updates + Events.on(engine, 'afterUpdate', function() { + // limit rate + if (stack && updateCount <= 50) { + updateCount += 1; + return; + } + + updateCount = 0; + + // remove last stack + if (stack) { + Composite.remove(world, stack); + } + + // create a new stack + stack = createStack(); + + // add the new stack + Composite.add(world, stack); + }); + + // add another stack that will not be removed + Composite.add(world, createStack()); + + Composite.add(world, [ + // walls + Bodies.rectangle(400, 0, 800, 50, { isStatic: true }), + Bodies.rectangle(400, 600, 800, 50, { isStatic: true }), + Bodies.rectangle(800, 300, 50, 600, { isStatic: true }), + Bodies.rectangle(0, 300, 50, 600, { isStatic: true }) + ]); + + // add mouse control + var mouse = Mouse.create(render.canvas), + mouseConstraint = MouseConstraint.create(engine, { + mouse: mouse, + constraint: { + stiffness: 0.2, + render: { + visible: false + } + } + }); + + Composite.add(world, mouseConstraint); + + // keep the mouse in sync with rendering + render.mouse = mouse; + + // fit the render viewport to the scene + Render.lookAt(render, { + min: { x: 0, y: 0 }, + max: { x: 800, y: 600 } + }); + + // context for MatterTools.Demo + return { + engine: engine, + runner: runner, + render: render, + canvas: render.canvas, + stop: function() { + Matter.Render.stop(render); + Matter.Runner.stop(runner); + } + }; +}; + +Example.remove.title = 'Composite Remove'; +Example.remove.for = '>=0.14.2'; + +if (typeof module !== 'undefined') { + module.exports = Example.remove; +} From b116f642256a12655f0404930442e3be39bddfc3 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 12 Dec 2021 13:20:15 +0000 Subject: [PATCH 48/62] add triangles to mixed bodies example --- examples/gyro.js | 3 --- examples/mixed.js | 3 --- 2 files changed, 6 deletions(-) diff --git a/examples/gyro.js b/examples/gyro.js index fadac72..dc51c37 100644 --- a/examples/gyro.js +++ b/examples/gyro.js @@ -36,9 +36,6 @@ Example.gyro = function() { var stack = Composites.stack(20, 20, 10, 5, 0, 0, function(x, y) { var sides = Math.round(Common.random(1, 8)); - // triangles can be a little unstable, so avoid until fixed - sides = (sides === 3) ? 4 : sides; - // round the edges of some bodies var chamfer = null; if (sides > 2 && Common.random() > 0.7) { diff --git a/examples/mixed.js b/examples/mixed.js index 447c9b0..3e022b3 100644 --- a/examples/mixed.js +++ b/examples/mixed.js @@ -36,9 +36,6 @@ Example.mixed = function() { var stack = Composites.stack(20, 20, 10, 5, 0, 0, function(x, y) { var sides = Math.round(Common.random(1, 8)); - // triangles can be a little unstable, so avoid until fixed - sides = (sides === 3) ? 4 : sides; - // round the edges of some bodies var chamfer = null; if (sides > 2 && Common.random() > 0.7) { From 5c59bef62e05d175d93c1391c913e304466f68dd Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 15 Dec 2021 09:57:51 +0000 Subject: [PATCH 49/62] increase iterations on Example.stress3 --- examples/stress3.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/stress3.js b/examples/stress3.js index ea2f9b5..3def2e5 100644 --- a/examples/stress3.js +++ b/examples/stress3.js @@ -12,8 +12,12 @@ Example.stress3 = function() { Bodies = Matter.Bodies; // create engine - var engine = Engine.create(), - world = engine.world; + var engine = Engine.create({ + positionIterations: 10, + velocityIterations: 10 + }); + + var world = engine.world; // create renderer var render = Render.create({ From 7f34c4506e3456083eb927beb4a5abb9c8bfc539 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 15 Dec 2021 09:58:31 +0000 Subject: [PATCH 50/62] added benchmark test command --- .travis.yml | 1 + package.json | 1 + test/Examples.spec.js | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3d03717..fdb6065 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ install: - npm ci script: - npm run lint + - npm run benchmark - npm run test - npm run build - npm run build-demo \ No newline at end of file diff --git a/package.json b/package.json index 0ac01da..f3c1c77 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "lint": "eslint 'src/**/*.js' 'demo/src/**/*.js' 'examples/*.js' 'webpack.*.js'", "doc": "yuidoc --config yuidoc.json --project-version $npm_package_version", "doc-watch": "nodemon --delay 3 --watch 'matter-doc-theme' --watch src -e 'js,html,css,handlebars' --exec 'npm run doc'", + "benchmark": "EXAMPLES=stress3 npm run test-node", "test": "npm run test-node", "test-all": "jest --no-cache", "test-save": "SAVE=true npm run test-node", diff --git a/test/Examples.spec.js b/test/Examples.spec.js index a40bb42..fd30ca9 100644 --- a/test/Examples.spec.js +++ b/test/Examples.spec.js @@ -18,12 +18,14 @@ const MatterBuild = requireUncached('../build/matter'); const { versionSatisfies } = requireUncached('../src/core/Plugin'); const Worker = require('jest-worker').default; +const specificExamples = process.env.EXAMPLES ? process.env.EXAMPLES.split(' ') : null; const testComparison = process.env.COMPARE === 'true'; const saveComparison = process.env.SAVE === 'true'; + const excludeExamples = ['svg', 'terrain']; const excludeJitter = ['stack', 'circleStack', 'restitution', 'staticFriction', 'friction', 'newtonsCradle', 'catapult']; -const examples = Object.keys(Example).filter(key => { +const examples = (specificExamples || Object.keys(Example)).filter(key => { const excluded = excludeExamples.includes(key); const buildVersion = MatterBuild.version; const exampleFor = Example[key].for; From 80cf76b40b307a8d45c2028cec32a435d8f0a972 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 15 Dec 2021 10:49:00 +0000 Subject: [PATCH 51/62] added note about webpack performance to readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2c3b129..732f8f4 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,10 @@ Alternatively you can download a [stable release](https://github.com/liabru/matt +### Webpack + +Some [webpack](https://webpack.js.org/) configs including the default may impact your project's performance during development, for a solution see [issue](https://github.com/liabru/matter-js/issues/1001). + ### Usage Visit the [Getting started](https://github.com/liabru/matter-js/wiki/Getting-started) wiki page for a minimal usage example which should work in both browsers and Node.js. From afa467aad657ffdff3e3724e63412d568ae4ea39 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 15 Dec 2021 10:59:11 +0000 Subject: [PATCH 52/62] updated test scripts --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f3c1c77..2e3d9dd 100644 --- a/package.json +++ b/package.json @@ -52,11 +52,11 @@ "doc-watch": "nodemon --delay 3 --watch 'matter-doc-theme' --watch src -e 'js,html,css,handlebars' --exec 'npm run doc'", "benchmark": "EXAMPLES=stress3 npm run test-node", "test": "npm run test-node", - "test-all": "jest --no-cache", - "test-save": "SAVE=true npm run test-node", - "test-watch": "npm run test-node -- --watch", "test-node": "npm run build-dev && node --expose-gc node_modules/.bin/jest --force-exit --no-cache --runInBand ./test/Examples.spec.js", "test-browser": "node --expose-gc node_modules/.bin/jest --force-exit --no-cache --runInBand ./test/Browser.spec.js", + "test-all": "npm run test-node && npm run test-browser", + "test-save": "SAVE=true npm run test-node", + "test-watch": "npm run test-node -- --watch", "changelog": "conventional-changelog -i CHANGELOG.md -s -r", "release": "npm version --no-git-tag-version", "preversion": "git checkout master && npm run lint && SAVE=true npm run test-all", From f1ba9b583ba92a56d307d63c850a0330d26bbd5e Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 15 Dec 2021 17:29:18 +0000 Subject: [PATCH 53/62] prevent source map in demo builds --- .gitignore | 3 ++- webpack.demo.config.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 98d9f7f..4ce65a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +*.map node_modules npm-debug.log docs @@ -15,4 +16,4 @@ test/browser/refs test/node/diffs test/node/refs __snapshots__ -__compare__ \ No newline at end of file +__compare__ diff --git a/webpack.demo.config.js b/webpack.demo.config.js index 8d055cb..a46cb82 100644 --- a/webpack.demo.config.js +++ b/webpack.demo.config.js @@ -29,7 +29,7 @@ License ${pkg.license}`; return { entry: { [name]: './demo/src/index.js' }, node: false, - devtool: 'source-map', + devtool: devServer ? 'source-map' : 'none', output: { library: 'MatterDemo', libraryTarget: 'umd', From 039212a56e5a0d353a078b270accf9b347c3e421 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 15 Dec 2021 17:33:44 +0000 Subject: [PATCH 54/62] release 0.18.0 --- CHANGELOG.md | 41 + RELEASE.md | 58 + build/matter.js | 2593 +++++++++-------- build/matter.min.js | 4 +- demo/index.html | 12 +- demo/js/matter-demo.a280d3.min.js | 6 + demo/js/matter-demo.e7da73.min.js | 6 - ....min.js => matter-demo.main.5754e1.min.js} | 2 +- ...=> matter-demo.matter-tools.97f38a.min.js} | 2 +- ... => matter-demo.matter-wrap.dbda1f.min.js} | 2 +- ...n.js => matter-demo.pathseg.cf21c2.min.js} | 2 +- ... => matter-demo.poly-decomp.c3d015.min.js} | 2 +- package-lock.json | 4 +- package.json | 2 +- 14 files changed, 1569 insertions(+), 1167 deletions(-) create mode 100644 demo/js/matter-demo.a280d3.min.js delete mode 100644 demo/js/matter-demo.e7da73.min.js rename demo/js/{matter-demo.main.5a504f.min.js => matter-demo.main.5754e1.min.js} (97%) rename demo/js/{matter-demo.matter-tools.5e580e.min.js => matter-demo.matter-tools.97f38a.min.js} (99%) rename demo/js/{matter-demo.matter-wrap.b3a896.min.js => matter-demo.matter-wrap.dbda1f.min.js} (98%) rename demo/js/{matter-demo.pathseg.067c95.min.js => matter-demo.pathseg.cf21c2.min.js} (99%) rename demo/js/{matter-demo.poly-decomp.59954b.min.js => matter-demo.poly-decomp.c3d015.min.js} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c8d9c8..3119f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,44 @@ +## 0.18.0 (2021-12-15) + +* added test capture sort to improve comparison ([ea3c11b](https://github.com/liabru/matter-js/commit/ea3c11b)) +* added triangles to mixed bodies example ([b116f64](https://github.com/liabru/matter-js/commit/b116f64)) +* added behaviour metric to tests and refactor tests ([8125966](https://github.com/liabru/matter-js/commit/8125966)) +* added benchmark test command ([7f34c45](https://github.com/liabru/matter-js/commit/7f34c45)) +* added broadphase to Matter.Detector ([a6b5e7d](https://github.com/liabru/matter-js/commit/a6b5e7d)) +* added cache checks to Matter.Composite ([32fd285](https://github.com/liabru/matter-js/commit/32fd285)) +* added example for Composite.remove ([bc07f56](https://github.com/liabru/matter-js/commit/bc07f56)) +* added example stress 3 ([d0ee246](https://github.com/liabru/matter-js/commit/d0ee246)) +* added filesize to test comparison report ([b3a8aa3](https://github.com/liabru/matter-js/commit/b3a8aa3)) +* added Matter.Collision ([9037f36](https://github.com/liabru/matter-js/commit/9037f36)) +* added memory comparison to tests ([bedf84c](https://github.com/liabru/matter-js/commit/bedf84c)) +* added note about webpack performance to readme ([80cf76b](https://github.com/liabru/matter-js/commit/80cf76b)) +* added stable sorting to test worker and refactor ([81dd2fb](https://github.com/liabru/matter-js/commit/81dd2fb)) +* added support for build metadata in Plugin.versionParse ([8bfaff0](https://github.com/liabru/matter-js/commit/8bfaff0)) +* changed raycasting example events to enable use in tests ([10afaea](https://github.com/liabru/matter-js/commit/10afaea)) +* changed build config to 'source-map' devtool ([e909b04](https://github.com/liabru/matter-js/commit/e909b04)) +* changed tests to use a production build rather than source ([55feb89](https://github.com/liabru/matter-js/commit/55feb89)) +* deprecated Matter.Grid ([e366d0e](https://github.com/liabru/matter-js/commit/e366d0e)) +* fixed sync issues on demo compare tool ([826ed46](https://github.com/liabru/matter-js/commit/826ed46)) +* improved performance measurement accuracy in tests ([cd289ec](https://github.com/liabru/matter-js/commit/cd289ec)) +* improved test comparison report ([de04c00](https://github.com/liabru/matter-js/commit/de04c00)) +* optimised Matter.Detector ([c7cec16](https://github.com/liabru/matter-js/commit/c7cec16)), ([fd1a70e](https://github.com/liabru/matter-js/commit/fd1a70e)), ([caeb07e](https://github.com/liabru/matter-js/commit/caeb07e)), ([efede6e](https://github.com/liabru/matter-js/commit/efede6e)) +* optimised Matter.Composite ([52e7977](https://github.com/liabru/matter-js/commit/52e7977)) +* optimised Matter.Pair ([d8a6380](https://github.com/liabru/matter-js/commit/d8a6380)), ([48673db](https://github.com/liabru/matter-js/commit/48673db)), ([1073dde](https://github.com/liabru/matter-js/commit/1073dde)) +* optimised Matter.Pairs ([a30707f](https://github.com/liabru/matter-js/commit/a30707f)), ([a882a74](https://github.com/liabru/matter-js/commit/a882a74)) +* optimised Matter.Resolver ([fceb0ca](https://github.com/liabru/matter-js/commit/fceb0ca)), ([49fbfba](https://github.com/liabru/matter-js/commit/49fbfba)), ([0b07a31](https://github.com/liabru/matter-js/commit/0b07a31)), ([f847f4c](https://github.com/liabru/matter-js/commit/f847f4c)), ([3cf65e8](https://github.com/liabru/matter-js/commit/3cf65e8)), ([30b899c](https://github.com/liabru/matter-js/commit/30b899c)), ([e4b35d3](https://github.com/liabru/matter-js/commit/e4b35d3)) +* optimised Matter.SAT ([0d90a17](https://github.com/liabru/matter-js/commit/0d90a17)), ([2096961](https://github.com/liabru/matter-js/commit/2096961)), ([0af144c](https://github.com/liabru/matter-js/commit/0af144c)) +* optimised Matter.Vertices ([c198878](https://github.com/liabru/matter-js/commit/c198878)), ([6883d0d](https://github.com/liabru/matter-js/commit/6883d0d)), ([792ae2e](https://github.com/liabru/matter-js/commit/792ae2e)) +* refactor test worker and prevent test cache ([ca2fe75](https://github.com/liabru/matter-js/commit/ca2fe75)), ([bcc3168](https://github.com/liabru/matter-js/commit/bcc3168)) +* replaced Matter.SAT with Matter.Collision ([b9e7d9d](https://github.com/liabru/matter-js/commit/b9e7d9d)) +* show debug stats in dev demo ([2f14ec5](https://github.com/liabru/matter-js/commit/2f14ec5)) +* updated dev dependencies ([c5028d5](https://github.com/liabru/matter-js/commit/c5028d5)) +* updated examples ([c80ed5c](https://github.com/liabru/matter-js/commit/c80ed5c)) +* updated test scripts ([afa467a](https://github.com/liabru/matter-js/commit/afa467a)) +* use force exit in tests ([8adf810](https://github.com/liabru/matter-js/commit/8adf810)) +* use Matter.Runner in test worker ([2581595](https://github.com/liabru/matter-js/commit/2581595)) + + + ## 0.17.1 (2021-04-14) * deprecate Engine.run alias replaced by Runner.run ([5817046](https://github.com/liabru/matter-js/commit/5817046)) diff --git a/RELEASE.md b/RELEASE.md index 3ecfb0e..069318a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,61 @@ +## ▲.● matter.js `0.18.0` + +Release notes for `0.18.0`. See the release [readme](https://github.com/liabru/matter-js/blob/0.18.0/README.md) for further information. + +### Highlights ✺ + +- **Up to ~40% performance improvement (on average measured over all examples, in Node on a Mac Air M1)** +- Replaces `Matter.Grid` with a faster and more efficient broadphase in `Matter.Detector` +- Reduced memory usage and garbage collection +- Resolves issues in `Matter.SAT` related to collision reuse +- Removes performance issues from `Matter.Grid` +- Improved collision accuracy +- Improved API and docs for collision modules +- Improved [documentation](https://brm.io/matter-js/docs/) pages +- Added note about avoiding Webpack [performance problems](https://github.com/liabru/matter-js/blob/master/README.md#install) + +### Changes ✲ + +See the release [compare page](https://github.com/liabru/matter-js/compare/0.17.1...0.18.0) and the [changelog](https://github.com/liabru/matter-js/blob/0.18.0/CHANGELOG.md) for a detailed list of changes. + +### Migration ⌲ + +- Behaviour and similarity is in practice the same (a fractional difference from improved accuracy) +- API is the same other than: + - [Matter.Detector](https://brm.io/matter-js/docs/classes/Detector.html) replaces [Matter.Grid](https://brm.io/matter-js/docs/classes/Grid.html) (which is now deprecated but will remain for a short term) + - [render.options.showBroadphase](https://brm.io/matter-js/docs/classes/Render.html#property_options.showBroadphase) is deprecated (no longer implemented) + - [Matter.SAT](https://brm.io/matter-js/docs/classes/SAT.html) is renamed [Matter.Collision](https://brm.io/matter-js/docs/classes/Collision.html) + - [Matter.SAT.collides](https://brm.io/matter-js/docs/classes/SAT.html#method_collides) is now [Matter.Collision.collides](https://brm.io/matter-js/docs/classes/Collision.html#method_collides) with a slightly different set of arguments + +### Comparison ⎄ + +Differences in behaviour, quality and performance against the previous release `0.17.1`. For more information see [comparison method](https://github.com/liabru/matter-js/pull/794). + +```ocaml +Output comparison of 43 examples against previous release matter-js@0.17.1 + +Behaviour 99.99% Similarity 99.98% Overlap -0.00% +Performance +40.62% Memory -6.18% Filesize -0.16% 77.73 KB + +airFriction · · avalanche ● · ballPool · · bridge · · car · · catapult · · +chains · · circleStack · · cloth · · collisionFiltering · · compositeManipulation ● · +compound · · compoundStack · · concave · · constraints ● · doublePendulum · · +events · · friction · · gravity · · gyro · · manipulation · ◆ +mixed · · newtonsCradle · · pyramid · · ragdoll · · raycasting · · +remove · · restitution · · rounded · · sensors · · sleeping · ◆ +slingshot · · softBody · · sprites · · stack · · staticFriction · · +stats · · stress · · stress2 · · stress3 · · timescale · · +views · · wreckingBall · · + +where · no change ● extrinsics changed ◆ intrinsics changed +``` + +### Contributors ♥︎ + +Many thanks to the [contributors](https://github.com/liabru/matter-js/compare/0.17.1...0.18.0) of this release, [past contributors](https://github.com/liabru/matter-js/graphs/contributors) as well those involved in the [community](https://github.com/liabru/matter-js/issues) for your input and support. + +--- + ## ▲.● matter.js `0.17.0` Release notes for `0.17.0`. See the release [readme](https://github.com/liabru/matter-js/blob/0.17.0/README.md) for further information. diff --git a/build/matter.js b/build/matter.js index b44b5db..ae3bc3d 100644 --- a/build/matter.js +++ b/build/matter.js @@ -1,5 +1,5 @@ /*! - * matter-js 0.17.1 by @liabru + * matter-js 0.18.0 by @liabru * http://brm.io/matter-js/ * License MIT * @@ -118,7 +118,7 @@ return /******/ (function(modules) { // webpackBootstrap /******/ /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 22); +/******/ return __webpack_require__(__webpack_require__.s = 21); /******/ }) /************************************************************************/ /******/ ([ @@ -1283,17 +1283,16 @@ var Common = __webpack_require__(0); * @param {number} scalar */ Vertices.translate = function(vertices, vector, scalar) { - var i; - if (scalar) { - for (i = 0; i < vertices.length; i++) { - vertices[i].x += vector.x * scalar; - vertices[i].y += vector.y * scalar; - } - } else { - for (i = 0; i < vertices.length; i++) { - vertices[i].x += vector.x; - vertices[i].y += vector.y; - } + scalar = typeof scalar !== 'undefined' ? scalar : 1; + + var verticesLength = vertices.length, + translateX = vector.x * scalar, + translateY = vector.y * scalar, + i; + + for (i = 0; i < verticesLength; i++) { + vertices[i].x += translateX; + vertices[i].y += translateY; } return vertices; @@ -1311,15 +1310,21 @@ var Common = __webpack_require__(0); return; var cos = Math.cos(angle), - sin = Math.sin(angle); + sin = Math.sin(angle), + pointX = point.x, + pointY = point.y, + verticesLength = vertices.length, + vertex, + dx, + dy, + i; - for (var i = 0; i < vertices.length; i++) { - var vertice = vertices[i], - dx = vertice.x - point.x, - dy = vertice.y - point.y; - - vertice.x = point.x + (dx * cos - dy * sin); - vertice.y = point.y + (dx * sin + dy * cos); + for (i = 0; i < verticesLength; i++) { + vertex = vertices[i]; + dx = vertex.x - pointX; + dy = vertex.y - pointY; + vertex.x = pointX + (dx * cos - dy * sin); + vertex.y = pointY + (dx * sin + dy * cos); } return vertices; @@ -1333,12 +1338,21 @@ var Common = __webpack_require__(0); * @return {boolean} True if the vertices contains point, otherwise false */ Vertices.contains = function(vertices, point) { - for (var i = 0; i < vertices.length; i++) { - var vertice = vertices[i], - nextVertice = vertices[(i + 1) % vertices.length]; - if ((point.x - vertice.x) * (nextVertice.y - vertice.y) + (point.y - vertice.y) * (vertice.x - nextVertice.x) > 0) { + var pointX = point.x, + pointY = point.y, + verticesLength = vertices.length, + vertex = vertices[verticesLength - 1], + nextVertex; + + for (var i = 0; i < verticesLength; i++) { + nextVertex = vertices[i]; + + if ((pointX - vertex.x) * (nextVertex.y - vertex.y) + + (pointY - vertex.y) * (vertex.x - nextVertex.x) > 0) { return false; } + + vertex = nextVertex; } return true; @@ -1724,7 +1738,12 @@ var Body = __webpack_require__(6); constraints: [], composites: [], label: 'Composite', - plugin: {} + plugin: {}, + cache: { + allBodies: null, + allConstraints: null, + allComposites: null + } }, options); }; @@ -1732,6 +1751,7 @@ var Body = __webpack_require__(6); * Sets the composite's `isModified` flag. * If `updateParents` is true, all parents will be set (default: false). * If `updateChildren` is true, all children will be set (default: false). + * @private * @method setModified * @param {composite} composite * @param {boolean} isModified @@ -1741,12 +1761,18 @@ var Body = __webpack_require__(6); Composite.setModified = function(composite, isModified, updateParents, updateChildren) { composite.isModified = isModified; + if (isModified && composite.cache) { + composite.cache.allBodies = null; + composite.cache.allConstraints = null; + composite.cache.allComposites = null; + } + if (updateParents && composite.parent) { Composite.setModified(composite.parent, isModified, updateParents, updateChildren); } if (updateChildren) { - for(var i = 0; i < composite.composites.length; i++) { + for (var i = 0; i < composite.composites.length; i++) { var childComposite = composite.composites[i]; Composite.setModified(childComposite, isModified, updateParents, updateChildren); } @@ -1867,7 +1893,6 @@ var Body = __webpack_require__(6); var position = Common.indexOf(compositeA.composites, compositeB); if (position !== -1) { Composite.removeCompositeAt(compositeA, position); - Composite.setModified(compositeA, true, true, false); } if (deep) { @@ -1920,7 +1945,6 @@ var Body = __webpack_require__(6); var position = Common.indexOf(composite.bodies, body); if (position !== -1) { Composite.removeBodyAt(composite, position); - Composite.setModified(composite, true, true, false); } if (deep) { @@ -2021,6 +2045,7 @@ var Body = __webpack_require__(6); composite.constraints.length = 0; composite.composites.length = 0; + Composite.setModified(composite, true, true, false); return composite; @@ -2033,11 +2058,19 @@ var Body = __webpack_require__(6); * @return {body[]} All the bodies */ Composite.allBodies = function(composite) { + if (composite.cache && composite.cache.allBodies) { + return composite.cache.allBodies; + } + var bodies = [].concat(composite.bodies); for (var i = 0; i < composite.composites.length; i++) bodies = bodies.concat(Composite.allBodies(composite.composites[i])); + if (composite.cache) { + composite.cache.allBodies = bodies; + } + return bodies; }; @@ -2048,11 +2081,19 @@ var Body = __webpack_require__(6); * @return {constraint[]} All the constraints */ Composite.allConstraints = function(composite) { + if (composite.cache && composite.cache.allConstraints) { + return composite.cache.allConstraints; + } + var constraints = [].concat(composite.constraints); for (var i = 0; i < composite.composites.length; i++) constraints = constraints.concat(Composite.allConstraints(composite.composites[i])); + if (composite.cache) { + composite.cache.allConstraints = constraints; + } + return constraints; }; @@ -2063,11 +2104,19 @@ var Body = __webpack_require__(6); * @return {composite[]} All the composites */ Composite.allComposites = function(composite) { + if (composite.cache && composite.cache.allComposites) { + return composite.cache.allComposites; + } + var composites = [].concat(composite.composites); for (var i = 0; i < composite.composites.length; i++) composites = composites.concat(Composite.allComposites(composite.composites[i])); + if (composite.cache) { + composite.cache.allComposites = composites; + } + return composites; }; @@ -2134,8 +2183,6 @@ var Body = __webpack_require__(6); objects[i].id = Common.nextId(); } - Composite.setModified(composite, true, true, false); - return composite; }; @@ -2154,8 +2201,6 @@ var Body = __webpack_require__(6); Body.translate(bodies[i], translation); } - Composite.setModified(composite, true, true, false); - return composite; }; @@ -2185,8 +2230,6 @@ var Body = __webpack_require__(6); Body.rotate(body, rotation); } - Composite.setModified(composite, true, true, false); - return composite; }; @@ -2215,8 +2258,6 @@ var Body = __webpack_require__(6); Body.scale(body, scaleX, scaleY); } - Composite.setModified(composite, true, true, false); - return composite; }; @@ -2316,8 +2357,7 @@ var Body = __webpack_require__(6); /** * A flag that specifies whether the composite has been modified during the current step. - * Most `Matter.Composite` methods will automatically set this flag to `true` to inform the engine of changes to be handled. - * If you need to change it manually, you should use the `Composite.setModified` method. + * This is automatically managed when bodies, constraints or composites are added or removed. * * @property isModified * @type boolean @@ -2369,6 +2409,15 @@ var Body = __webpack_require__(6); * @type {} */ + /** + * An object used for storing cached results for performance reasons. + * This is used internally only and is automatically managed. + * + * @private + * @property cache + * @type {} + */ + })(); @@ -2396,7 +2445,7 @@ var Sleeping = __webpack_require__(7); var Render = __webpack_require__(16); var Common = __webpack_require__(0); var Bounds = __webpack_require__(1); -var Axes = __webpack_require__(10); +var Axes = __webpack_require__(11); (function() { @@ -3752,6 +3801,550 @@ var Events = __webpack_require__(4); /* 8 */ /***/ (function(module, exports, __webpack_require__) { +/** +* The `Matter.Collision` module contains methods for detecting collisions between a given pair of bodies. +* +* For efficient detection between a list of bodies, see `Matter.Detector` and `Matter.Query`. +* +* See `Matter.Engine` for collision events. +* +* @class Collision +*/ + +var Collision = {}; + +module.exports = Collision; + +var Vertices = __webpack_require__(3); +var Pair = __webpack_require__(9); + +(function() { + var _supports = []; + + var _overlapAB = { + overlap: 0, + axis: null + }; + + var _overlapBA = { + overlap: 0, + axis: null + }; + + /** + * Creates a new collision record. + * @method create + * @param {body} bodyA The first body part represented by the collision record + * @param {body} bodyB The second body part represented by the collision record + * @return {collision} A new collision record + */ + Collision.create = function(bodyA, bodyB) { + return { + pair: null, + collided: false, + bodyA: bodyA, + bodyB: bodyB, + parentA: bodyA.parent, + parentB: bodyB.parent, + depth: 0, + normal: { x: 0, y: 0 }, + tangent: { x: 0, y: 0 }, + penetration: { x: 0, y: 0 }, + supports: [] + }; + }; + + /** + * Detect collision between two bodies. + * @method collides + * @param {body} bodyA + * @param {body} bodyB + * @param {pairs} [pairs] Optionally reuse collision records from existing pairs. + * @return {collision|null} A collision record if detected, otherwise null + */ + Collision.collides = function(bodyA, bodyB, pairs) { + Collision._overlapAxes(_overlapAB, bodyA.vertices, bodyB.vertices, bodyA.axes); + + if (_overlapAB.overlap <= 0) { + return null; + } + + Collision._overlapAxes(_overlapBA, bodyB.vertices, bodyA.vertices, bodyB.axes); + + if (_overlapBA.overlap <= 0) { + return null; + } + + // reuse collision records for gc efficiency + var pair = pairs && pairs.table[Pair.id(bodyA, bodyB)], + collision; + + if (!pair) { + collision = Collision.create(bodyA, bodyB); + collision.collided = true; + collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; + collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; + collision.parentA = collision.bodyA.parent; + collision.parentB = collision.bodyB.parent; + } else { + collision = pair.collision; + } + + bodyA = collision.bodyA; + bodyB = collision.bodyB; + + var minOverlap; + + if (_overlapAB.overlap < _overlapBA.overlap) { + minOverlap = _overlapAB; + } else { + minOverlap = _overlapBA; + } + + var normal = collision.normal, + supports = collision.supports, + minAxis = minOverlap.axis, + minAxisX = minAxis.x, + minAxisY = minAxis.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; + } + + collision.tangent.x = -normal.y; + collision.tangent.y = normal.x; + + collision.depth = minOverlap.overlap; + + collision.penetration.x = normal.x * collision.depth; + collision.penetration.y = normal.y * collision.depth; + + // find support points, there is always either exactly one or two + var supportsB = Collision._findSupports(bodyA, bodyB, normal, 1), + supportCount = 0; + + // find the supports from bodyB that are inside bodyA + if (Vertices.contains(bodyA.vertices, supportsB[0])) { + supports[supportCount++] = supportsB[0]; + } + + if (Vertices.contains(bodyA.vertices, supportsB[1])) { + supports[supportCount++] = supportsB[1]; + } + + // find the supports from bodyA that are inside bodyB + if (supportCount < 2) { + var supportsA = Collision._findSupports(bodyB, bodyA, normal, -1); + + if (Vertices.contains(bodyB.vertices, supportsA[0])) { + supports[supportCount++] = supportsA[0]; + } + + if (supportCount < 2 && Vertices.contains(bodyB.vertices, supportsA[1])) { + supports[supportCount++] = supportsA[1]; + } + } + + // account for the edge case of overlapping but no vertex containment + if (supportCount === 0) { + supports[supportCount++] = supportsB[0]; + } + + // update supports array size + supports.length = supportCount; + + return collision; + }; + + /** + * Find the overlap between two sets of vertices. + * @method _overlapAxes + * @private + * @param {object} result + * @param {vertices} verticesA + * @param {vertices} verticesB + * @param {axes} axes + */ + Collision._overlapAxes = function(result, verticesA, verticesB, axes) { + var verticesALength = verticesA.length, + verticesBLength = verticesB.length, + verticesAX = verticesA[0].x, + verticesAY = verticesA[0].y, + verticesBX = verticesB[0].x, + verticesBY = verticesB[0].y, + axesLength = axes.length, + overlapMin = Number.MAX_VALUE, + overlapAxisNumber = 0, + overlap, + overlapAB, + overlapBA, + dot, + i, + j; + + for (i = 0; i < axesLength; i++) { + var axis = axes[i], + axisX = axis.x, + axisY = axis.y, + minA = verticesAX * axisX + verticesAY * axisY, + minB = verticesBX * axisX + verticesBY * axisY, + maxA = minA, + maxB = minB; + + for (j = 1; j < verticesALength; j += 1) { + dot = verticesA[j].x * axisX + verticesA[j].y * axisY; + + if (dot > maxA) { + maxA = dot; + } else if (dot < minA) { + minA = dot; + } + } + + for (j = 1; j < verticesBLength; j += 1) { + dot = verticesB[j].x * axisX + verticesB[j].y * axisY; + + if (dot > maxB) { + maxB = dot; + } else if (dot < minB) { + minB = dot; + } + } + + overlapAB = maxA - minB; + overlapBA = maxB - minA; + overlap = overlapAB < overlapBA ? overlapAB : overlapBA; + + if (overlap < overlapMin) { + overlapMin = overlap; + overlapAxisNumber = i; + + if (overlap <= 0) { + // can not be intersecting + break; + } + } + } + + result.axis = axes[overlapAxisNumber]; + 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 + * @private + * @param {body} bodyA + * @param {body} bodyB + * @param {vector} normal + * @param {number} direction + * @return [vector] + */ + Collision._findSupports = function(bodyA, bodyB, normal, direction) { + var vertices = bodyB.vertices, + verticesLength = vertices.length, + bodyAPositionX = bodyA.position.x, + bodyAPositionY = bodyA.position.y, + normalX = normal.x * direction, + normalY = normal.y * direction, + nearestDistance = Number.MAX_VALUE, + vertexA, + vertexB, + vertexC, + distance, + j; + + // find deepest vertex relative to the axis + for (j = 0; j < verticesLength; j += 1) { + vertexB = vertices[j]; + distance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y); + + // convex hill-climbing + if (distance < nearestDistance) { + nearestDistance = distance; + vertexA = vertexB; + } + } + + // measure next vertex + vertexC = vertices[(verticesLength + vertexA.index - 1) % verticesLength]; + nearestDistance = normalX * (bodyAPositionX - vertexC.x) + normalY * (bodyAPositionY - vertexC.y); + + // compare with previous vertex + vertexB = vertices[(vertexA.index + 1) % verticesLength]; + if (normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y) < nearestDistance) { + _supports[0] = vertexA; + _supports[1] = vertexB; + + return _supports; + } + + _supports[0] = vertexA; + _supports[1] = vertexC; + + return _supports; + }; + + /* + * + * Properties Documentation + * + */ + + /** + * A reference to the pair using this collision record, if there is one. + * + * @property pair + * @type {pair|null} + * @default null + */ + + /** + * A flag that indicates if the bodies were colliding when the collision was last updated. + * + * @property collided + * @type boolean + * @default false + */ + + /** + * The first body part represented by the collision (see also `collision.parentA`). + * + * @property bodyA + * @type body + */ + + /** + * The second body part represented by the collision (see also `collision.parentB`). + * + * @property bodyB + * @type body + */ + + /** + * The first body represented by the collision (i.e. `collision.bodyA.parent`). + * + * @property parentA + * @type body + */ + + /** + * The second body represented by the collision (i.e. `collision.bodyB.parent`). + * + * @property parentB + * @type body + */ + + /** + * A `Number` that represents the minimum separating distance between the bodies along the collision normal. + * + * @readOnly + * @property depth + * @type number + * @default 0 + */ + + /** + * A normalised `Vector` that represents the direction between the bodies that provides the minimum separating distance. + * + * @property normal + * @type vector + * @default { x: 0, y: 0 } + */ + + /** + * A normalised `Vector` that is the tangent direction to the collision normal. + * + * @property tangent + * @type vector + * @default { x: 0, y: 0 } + */ + + /** + * A `Vector` that represents the direction and depth of the collision. + * + * @property penetration + * @type vector + * @default { x: 0, y: 0 } + */ + + /** + * An array of body vertices that represent the support points in the collision. + * These are the deepest vertices (along the collision normal) of each body that are contained by the other body's vertices. + * + * @property supports + * @type vector[] + * @default [] + */ + +})(); + + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +/** +* The `Matter.Pair` module contains methods for creating and manipulating collision pairs. +* +* @class Pair +*/ + +var Pair = {}; + +module.exports = Pair; + +var Contact = __webpack_require__(17); + +(function() { + + /** + * Creates a pair. + * @method create + * @param {collision} collision + * @param {number} timestamp + * @return {pair} A new pair + */ + Pair.create = function(collision, timestamp) { + var bodyA = collision.bodyA, + bodyB = collision.bodyB; + + var pair = { + id: Pair.id(bodyA, bodyB), + bodyA: bodyA, + bodyB: bodyB, + collision: collision, + contacts: [], + activeContacts: [], + separation: 0, + isActive: true, + confirmedActive: true, + isSensor: bodyA.isSensor || bodyB.isSensor, + timeCreated: timestamp, + timeUpdated: timestamp, + inverseMass: 0, + friction: 0, + frictionStatic: 0, + restitution: 0, + slop: 0 + }; + + Pair.update(pair, collision, timestamp); + + return pair; + }; + + /** + * Updates a pair given a collision. + * @method update + * @param {pair} pair + * @param {collision} collision + * @param {number} timestamp + */ + Pair.update = function(pair, collision, timestamp) { + var contacts = pair.contacts, + supports = collision.supports, + activeContacts = pair.activeContacts, + parentA = collision.parentA, + parentB = collision.parentB, + parentAVerticesLength = parentA.vertices.length; + + pair.isActive = true; + pair.timeUpdated = timestamp; + pair.collision = collision; + pair.separation = collision.depth; + pair.inverseMass = parentA.inverseMass + parentB.inverseMass; + pair.friction = parentA.friction < parentB.friction ? parentA.friction : parentB.friction; + pair.frictionStatic = parentA.frictionStatic > parentB.frictionStatic ? parentA.frictionStatic : parentB.frictionStatic; + pair.restitution = parentA.restitution > parentB.restitution ? parentA.restitution : parentB.restitution; + pair.slop = parentA.slop > parentB.slop ? parentA.slop : parentB.slop; + + 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)); + } + } + }; + + /** + * Set a pair as active or inactive. + * @method setActive + * @param {pair} pair + * @param {bool} isActive + * @param {number} timestamp + */ + Pair.setActive = function(pair, isActive, timestamp) { + if (isActive) { + pair.isActive = true; + pair.timeUpdated = timestamp; + } else { + pair.isActive = false; + pair.activeContacts.length = 0; + } + }; + + /** + * Get the id for the given pair. + * @method id + * @param {body} bodyA + * @param {body} bodyB + * @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; + } + }; + +})(); + + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + /** * The `Matter.Constraint` module contains methods for creating and manipulating constraints. * Constraints are used for specifying that a fixed distance must be maintained between two bodies (or a body and a fixed world-space position). @@ -3770,7 +4363,7 @@ var Vertices = __webpack_require__(3); var Vector = __webpack_require__(2); var Sleeping = __webpack_require__(7); var Bounds = __webpack_require__(1); -var Axes = __webpack_require__(10); +var Axes = __webpack_require__(11); var Common = __webpack_require__(0); (function() { @@ -4234,140 +4827,7 @@ var Common = __webpack_require__(0); /***/ }), -/* 9 */ -/***/ (function(module, exports, __webpack_require__) { - -/** -* The `Matter.Pair` module contains methods for creating and manipulating collision pairs. -* -* @class Pair -*/ - -var Pair = {}; - -module.exports = Pair; - -var Contact = __webpack_require__(17); - -(function() { - - /** - * Creates a pair. - * @method create - * @param {collision} collision - * @param {number} timestamp - * @return {pair} A new pair - */ - Pair.create = function(collision, timestamp) { - var bodyA = collision.bodyA, - bodyB = collision.bodyB, - parentA = collision.parentA, - parentB = collision.parentB; - - var pair = { - id: Pair.id(bodyA, bodyB), - bodyA: bodyA, - bodyB: bodyB, - contacts: {}, - activeContacts: [], - separation: 0, - isActive: true, - confirmedActive: true, - isSensor: bodyA.isSensor || bodyB.isSensor, - timeCreated: timestamp, - timeUpdated: timestamp, - inverseMass: parentA.inverseMass + parentB.inverseMass, - friction: Math.min(parentA.friction, parentB.friction), - frictionStatic: Math.max(parentA.frictionStatic, parentB.frictionStatic), - restitution: Math.max(parentA.restitution, parentB.restitution), - slop: Math.max(parentA.slop, parentB.slop) - }; - - Pair.update(pair, collision, timestamp); - - return pair; - }; - - /** - * Updates a pair given a collision. - * @method update - * @param {pair} pair - * @param {collision} collision - * @param {number} timestamp - */ - Pair.update = function(pair, collision, timestamp) { - var contacts = pair.contacts, - supports = collision.supports, - activeContacts = pair.activeContacts, - parentA = collision.parentA, - parentB = collision.parentB; - - pair.collision = collision; - pair.inverseMass = parentA.inverseMass + parentB.inverseMass; - pair.friction = Math.min(parentA.friction, parentB.friction); - pair.frictionStatic = Math.max(parentA.frictionStatic, parentB.frictionStatic); - pair.restitution = Math.max(parentA.restitution, parentB.restitution); - pair.slop = Math.max(parentA.slop, parentB.slop); - activeContacts.length = 0; - - if (collision.collided) { - for (var i = 0; i < supports.length; i++) { - var support = supports[i], - contactId = Contact.id(support), - contact = contacts[contactId]; - - if (contact) { - activeContacts.push(contact); - } else { - activeContacts.push(contacts[contactId] = Contact.create(support)); - } - } - - pair.separation = collision.depth; - Pair.setActive(pair, true, timestamp); - } else { - if (pair.isActive === true) - Pair.setActive(pair, false, timestamp); - } - }; - - /** - * Set a pair as active or inactive. - * @method setActive - * @param {pair} pair - * @param {bool} isActive - * @param {number} timestamp - */ - Pair.setActive = function(pair, isActive, timestamp) { - if (isActive) { - pair.isActive = true; - pair.timeUpdated = timestamp; - } else { - pair.isActive = false; - pair.activeContacts.length = 0; - } - }; - - /** - * Get the id for the given pair. - * @method id - * @param {body} bodyA - * @param {body} bodyB - * @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; - } - }; - -})(); - - -/***/ }), -/* 10 */ +/* 11 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -4437,7 +4897,7 @@ var Common = __webpack_require__(0); /***/ }), -/* 11 */ +/* 12 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -4801,7 +5261,7 @@ var Vector = __webpack_require__(2); /***/ }), -/* 12 */ +/* 13 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -5008,72 +5468,137 @@ var Common = __webpack_require__(0); /***/ }), -/* 13 */ +/* 14 */ /***/ (function(module, exports, __webpack_require__) { /** -* The `Matter.Detector` module contains methods for detecting collisions given a set of pairs. +* The `Matter.Detector` module contains methods for efficiently detecting collisions between a list of bodies using a broadphase algorithm. * * @class Detector */ -// TODO: speculative contacts - var Detector = {}; module.exports = Detector; -var SAT = __webpack_require__(14); -var Pair = __webpack_require__(9); -var Bounds = __webpack_require__(1); +var Common = __webpack_require__(0); +var Collision = __webpack_require__(8); (function() { /** - * Finds all collisions given a list of pairs. - * @method collisions - * @param {pair[]} broadphasePairs - * @param {engine} engine - * @return {array} collisions + * Creates a new collision detector. + * @method create + * @param {} options + * @return {detector} A new collision detector */ - Detector.collisions = function(broadphasePairs, engine) { + Detector.create = function(options) { + var defaults = { + bodies: [], + pairs: null + }; + + return Common.extend(defaults, options); + }; + + /** + * Sets the list of bodies in the detector. + * @method setBodies + * @param {detector} detector + * @param {body[]} bodies + */ + Detector.setBodies = function(detector, bodies) { + detector.bodies = bodies.slice(0); + }; + + /** + * Clears the detector including its list of bodies. + * @method clear + * @param {detector} detector + */ + Detector.clear = function(detector) { + detector.bodies = []; + }; + + /** + * Efficiently finds all collisions among all the bodies in `detector.bodies` using a broadphase algorithm. + * + * _Note:_ The specific ordering of collisions returned is not guaranteed between releases and may change for performance reasons. + * If a specific ordering is required then apply a sort to the resulting array. + * @method collisions + * @param {detector} detector + * @return {collision[]} collisions + */ + Detector.collisions = function(detector) { var collisions = [], - pairsTable = engine.pairs.table; + pairs = detector.pairs, + bodies = detector.bodies, + bodiesLength = bodies.length, + canCollide = Detector.canCollide, + collides = Collision.collides, + i, + j; - for (var i = 0; i < broadphasePairs.length; i++) { - var bodyA = broadphasePairs[i][0], - bodyB = broadphasePairs[i][1]; + bodies.sort(Detector._compareBoundsX); - if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping)) - continue; - - if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) - continue; + for (i = 0; i < bodiesLength; i++) { + var bodyA = bodies[i], + boundsA = bodyA.bounds, + boundXMax = bodyA.bounds.max.x, + boundYMax = bodyA.bounds.max.y, + boundYMin = bodyA.bounds.min.y, + bodyAStatic = bodyA.isStatic || bodyA.isSleeping, + partsALength = bodyA.parts.length, + partsASingle = partsALength === 1; - // mid phase - if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) { - for (var j = bodyA.parts.length > 1 ? 1 : 0; j < bodyA.parts.length; j++) { - var partA = bodyA.parts[j]; + for (j = i + 1; j < bodiesLength; j++) { + var bodyB = bodies[j], + boundsB = bodyB.bounds; - for (var k = bodyB.parts.length > 1 ? 1 : 0; k < bodyB.parts.length; k++) { - var partB = bodyB.parts[k]; + if (boundsB.min.x > boundXMax) { + break; + } - if ((partA === bodyA && partB === bodyB) || Bounds.overlaps(partA.bounds, partB.bounds)) { - // find a previous collision we could reuse - var pairId = Pair.id(partA, partB), - pair = pairsTable[pairId], - previousCollision; + if (boundYMax < boundsB.min.y || boundYMin > boundsB.max.y) { + continue; + } - if (pair && pair.isActive) { - previousCollision = pair.collision; - } else { - previousCollision = null; + if (bodyAStatic && (bodyB.isStatic || bodyB.isSleeping)) { + continue; + } + + if (!canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) { + continue; + } + + var partsBLength = bodyB.parts.length; + + if (partsASingle && partsBLength === 1) { + var collision = collides(bodyA, bodyB, pairs); + + if (collision) { + collisions.push(collision); + } + } else { + var partsAStart = partsALength > 1 ? 1 : 0, + partsBStart = partsBLength > 1 ? 1 : 0; + + for (var k = partsAStart; k < partsALength; k++) { + var partA = bodyA.parts[k], + boundsA = partA.bounds; + + for (var z = partsBStart; z < partsBLength; z++) { + var partB = bodyB.parts[z], + boundsB = partB.bounds; + + if (boundsA.min.x > boundsB.max.x || boundsA.max.x < boundsB.min.x + || boundsA.max.y < boundsB.min.y || boundsA.min.y > boundsB.max.y) { + continue; } - // narrow phase - var collision = SAT.collides(partA, partB, previousCollision); + var collision = collides(partA, partB, pairs); - if (collision.collided) { + if (collision) { collisions.push(collision); } } @@ -5100,281 +5625,40 @@ var Bounds = __webpack_require__(1); return (filterA.mask & filterB.category) !== 0 && (filterB.mask & filterA.category) !== 0; }; -})(); - - -/***/ }), -/* 14 */ -/***/ (function(module, exports, __webpack_require__) { - -/** -* The `Matter.SAT` module contains methods for detecting collisions using the Separating Axis Theorem. -* -* @class SAT -*/ - -// TODO: true circles and curves - -var SAT = {}; - -module.exports = SAT; - -var Vertices = __webpack_require__(3); -var Vector = __webpack_require__(2); - -(function() { - /** - * Detect collision between two bodies using the Separating Axis Theorem. - * @method collides + * The comparison function used in the broadphase algorithm. + * Returns the signed delta of the bodies bounds on the x-axis. + * @private + * @method _sortCompare * @param {body} bodyA * @param {body} bodyB - * @param {collision} previousCollision - * @return {collision} collision + * @return {number} The signed delta used for sorting */ - SAT.collides = function(bodyA, bodyB, previousCollision) { - var overlapAB, - overlapBA, - minOverlap, - collision, - canReusePrevCol = false; - - if (previousCollision) { - // estimate total motion - var parentA = bodyA.parent, - parentB = bodyB.parent, - motion = parentA.speed * parentA.speed + parentA.angularSpeed * parentA.angularSpeed - + parentB.speed * parentB.speed + parentB.angularSpeed * parentB.angularSpeed; - - // we may be able to (partially) reuse collision result - // but only safe if collision was resting - canReusePrevCol = previousCollision && previousCollision.collided && motion < 0.2; - - // reuse collision object - collision = previousCollision; - } else { - collision = { collided: false, bodyA: bodyA, bodyB: bodyB }; - } - - if (previousCollision && canReusePrevCol) { - // if we can reuse the collision result - // we only need to test the previously found axis - var axisBodyA = collision.axisBody, - axisBodyB = axisBodyA === bodyA ? bodyB : bodyA, - axes = [axisBodyA.axes[previousCollision.axisNumber]]; - - minOverlap = SAT._overlapAxes(axisBodyA.vertices, axisBodyB.vertices, axes); - collision.reused = true; - - if (minOverlap.overlap <= 0) { - collision.collided = false; - return collision; - } - } else { - // if we can't reuse a result, perform a full SAT test - - overlapAB = SAT._overlapAxes(bodyA.vertices, bodyB.vertices, bodyA.axes); - - if (overlapAB.overlap <= 0) { - collision.collided = false; - return collision; - } - - overlapBA = SAT._overlapAxes(bodyB.vertices, bodyA.vertices, bodyB.axes); - - if (overlapBA.overlap <= 0) { - collision.collided = false; - return collision; - } - - if (overlapAB.overlap < overlapBA.overlap) { - minOverlap = overlapAB; - collision.axisBody = bodyA; - } else { - minOverlap = overlapBA; - collision.axisBody = bodyB; - } - - // important for reuse later - collision.axisNumber = minOverlap.axisNumber; - } - - collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB; - collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA; - collision.collided = true; - collision.depth = minOverlap.overlap; - collision.parentA = collision.bodyA.parent; - collision.parentB = collision.bodyB.parent; - - bodyA = collision.bodyA; - bodyB = collision.bodyB; - - // ensure normal is facing away from bodyA - if (Vector.dot(minOverlap.axis, Vector.sub(bodyB.position, bodyA.position)) < 0) { - collision.normal = { - x: minOverlap.axis.x, - y: minOverlap.axis.y - }; - } else { - collision.normal = { - x: -minOverlap.axis.x, - y: -minOverlap.axis.y - }; - } - - collision.tangent = Vector.perp(collision.normal); - - collision.penetration = collision.penetration || {}; - collision.penetration.x = collision.normal.x * collision.depth; - collision.penetration.y = collision.normal.y * collision.depth; - - // find support points, there is always either exactly one or two - var verticesB = SAT._findSupports(bodyA, bodyB, collision.normal), - supports = []; - - // find the supports from bodyB that are inside bodyA - if (Vertices.contains(bodyA.vertices, verticesB[0])) - supports.push(verticesB[0]); - - if (Vertices.contains(bodyA.vertices, verticesB[1])) - supports.push(verticesB[1]); - - // find the supports from bodyA that are inside bodyB - if (supports.length < 2) { - var verticesA = SAT._findSupports(bodyB, bodyA, Vector.neg(collision.normal)); - - if (Vertices.contains(bodyB.vertices, verticesA[0])) - supports.push(verticesA[0]); - - if (supports.length < 2 && Vertices.contains(bodyB.vertices, verticesA[1])) - supports.push(verticesA[1]); - } - - // account for the edge case of overlapping but no vertex containment - if (supports.length < 1) - supports = [verticesB[0]]; - - collision.supports = supports; - - return collision; + Detector._compareBoundsX = function(bodyA, bodyB) { + return bodyA.bounds.min.x - bodyB.bounds.min.x; }; + /* + * + * Properties Documentation + * + */ + /** - * Find the overlap between two sets of vertices. - * @method _overlapAxes - * @private - * @param {} verticesA - * @param {} verticesB - * @param {} axes - * @return result + * The array of `Matter.Body` between which the detector finds collisions. + * + * _Note:_ The order of bodies in this array _is not fixed_ and will be continually managed by the detector. + * @property bodies + * @type body[] + * @default [] */ - SAT._overlapAxes = function(verticesA, verticesB, axes) { - var projectionA = Vector._temp[0], - projectionB = Vector._temp[1], - result = { overlap: Number.MAX_VALUE }, - overlap, - axis; - - for (var i = 0; i < axes.length; i++) { - axis = axes[i]; - - SAT._projectToAxis(projectionA, verticesA, axis); - SAT._projectToAxis(projectionB, verticesB, axis); - - overlap = Math.min(projectionA.max - projectionB.min, projectionB.max - projectionA.min); - - if (overlap <= 0) { - result.overlap = overlap; - return result; - } - - if (overlap < result.overlap) { - result.overlap = overlap; - result.axis = axis; - result.axisNumber = i; - } - } - - return result; - }; /** - * Projects vertices on an axis and returns an interval. - * @method _projectToAxis - * @private - * @param {} projection - * @param {} vertices - * @param {} axis + * Optional. A `Matter.Pairs` object from which previous collision objects may be reused. Intended for internal `Matter.Engine` usage. + * @property pairs + * @type {pairs|null} + * @default null */ - SAT._projectToAxis = function(projection, vertices, axis) { - var min = Vector.dot(vertices[0], axis), - max = min; - - for (var i = 1; i < vertices.length; i += 1) { - var dot = Vector.dot(vertices[i], axis); - - 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 - * @private - * @param {} bodyA - * @param {} bodyB - * @param {} normal - * @return [vector] - */ - SAT._findSupports = function(bodyA, bodyB, normal) { - var nearestDistance = Number.MAX_VALUE, - vertexToBody = Vector._temp[0], - vertices = bodyB.vertices, - bodyAPosition = bodyA.position, - distance, - vertex, - vertexA, - vertexB; - - // find closest vertex on bodyB - for (var i = 0; i < vertices.length; i++) { - vertex = vertices[i]; - vertexToBody.x = vertex.x - bodyAPosition.x; - vertexToBody.y = vertex.y - bodyAPosition.y; - distance = -Vector.dot(normal, vertexToBody); - - if (distance < nearestDistance) { - nearestDistance = distance; - vertexA = vertex; - } - } - - // find next closest vertex using the two connected to it - var prevIndex = vertexA.index - 1 >= 0 ? vertexA.index - 1 : vertices.length - 1; - vertex = vertices[prevIndex]; - vertexToBody.x = vertex.x - bodyAPosition.x; - vertexToBody.y = vertex.y - bodyAPosition.y; - nearestDistance = -Vector.dot(normal, vertexToBody); - vertexB = vertex; - - var nextIndex = (vertexA.index + 1) % vertices.length; - vertex = vertices[nextIndex]; - vertexToBody.x = vertex.x - bodyAPosition.x; - vertexToBody.y = vertex.y - bodyAPosition.y; - distance = -Vector.dot(normal, vertexToBody); - if (distance < nearestDistance) { - vertexB = vertex; - } - - return [vertexA, vertexB]; - }; })(); @@ -5625,7 +5909,7 @@ var Common = __webpack_require__(0); */ Plugin.dependencyParse = function(dependency) { if (Common.isString(dependency)) { - var pattern = /^[\w-]+(@(\*|[\^~]?\d+\.\d+\.\d+(-[0-9A-Za-z-]+)?))?$/; + var pattern = /^[\w-]+(@(\*|[\^~]?\d+\.\d+\.\d+(-[0-9A-Za-z-+]+)?))?$/; if (!pattern.test(dependency)) { Common.warn('Plugin.dependencyParse:', dependency, 'is not a valid dependency string.'); @@ -5660,7 +5944,7 @@ var Common = __webpack_require__(0); * @return {object} The version range parsed into its components. */ Plugin.versionParse = function(range) { - var pattern = /^(\*)|(\^|~|>=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-]+)?$/; + var pattern = /^(\*)|(\^|~|>=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-+]+)?$/; if (!pattern.test(range)) { Common.warn('Plugin.versionParse:', range, 'is not a valid version or range.'); @@ -5757,7 +6041,7 @@ var Composite = __webpack_require__(5); var Bounds = __webpack_require__(1); var Events = __webpack_require__(4); var Vector = __webpack_require__(2); -var Mouse = __webpack_require__(12); +var Mouse = __webpack_require__(13); (function() { @@ -5818,7 +6102,6 @@ var Mouse = __webpack_require__(12); showDebug: false, showStats: false, showPerformance: false, - showBroadphase: false, showBounds: false, showVelocity: false, showCollisions: false, @@ -5858,6 +6141,9 @@ var Mouse = __webpack_require__(12); } }; + // for temporary back compatibility only + render.options.showBroadphase = false; + if (render.options.pixelRatio !== 1) { Render.setPixelRatio(render, render.options.pixelRatio); } @@ -6178,9 +6464,6 @@ var Mouse = __webpack_require__(12); Render.constraints(constraints, context); - if (options.showBroadphase) - Render.grid(render, engine.grid, context); - if (options.hasBounds) { // revert view transforms Render.endViewTransform(render); @@ -7027,45 +7310,6 @@ var Mouse = __webpack_require__(12); c.stroke(); }; - /** - * Description - * @private - * @method grid - * @param {render} render - * @param {grid} grid - * @param {RenderingContext} context - */ - Render.grid = function(render, grid, context) { - var c = context, - options = render.options; - - if (options.wireframes) { - c.strokeStyle = 'rgba(255,180,0,0.1)'; - } else { - c.strokeStyle = 'rgba(255,180,0,0.5)'; - } - - c.beginPath(); - - var bucketKeys = Common.keys(grid.buckets); - - for (var i = 0; i < bucketKeys.length; i++) { - var bucketId = bucketKeys[i]; - - if (grid.buckets[bucketId].length < 2) - continue; - - var region = bucketId.split(/C|R/); - c.rect(0.5 + parseInt(region[1], 10) * grid.bucketWidth, - 0.5 + parseInt(region[2], 10) * grid.bucketHeight, - grid.bucketWidth, - grid.bucketHeight); - } - - c.lineWidth = 1; - c.stroke(); - }; - /** * Description * @private @@ -7507,6 +7751,7 @@ var Mouse = __webpack_require__(12); /** * A flag to enable or disable the collision broadphase debug overlay. * + * @deprecated no longer implemented * @property options.showBroadphase * @type boolean * @default false @@ -7635,22 +7880,11 @@ module.exports = Contact; */ Contact.create = function(vertex) { return { - id: Contact.id(vertex), vertex: vertex, normalImpulse: 0, tangentImpulse: 0 }; }; - - /** - * Generates a contact id. - * @method id - * @param {vertex} vertex - * @return {string} Unique contactID - */ - Contact.id = function(vertex) { - return vertex.body.id + '_' + vertex.index; - }; })(); @@ -7675,12 +7909,11 @@ module.exports = Engine; var Sleeping = __webpack_require__(7); var Resolver = __webpack_require__(19); -var Detector = __webpack_require__(13); +var Detector = __webpack_require__(14); var Pairs = __webpack_require__(20); -var Grid = __webpack_require__(21); var Events = __webpack_require__(4); var Composite = __webpack_require__(5); -var Constraint = __webpack_require__(8); +var Constraint = __webpack_require__(10); var Common = __webpack_require__(0); var Body = __webpack_require__(6); @@ -7704,7 +7937,6 @@ var Body = __webpack_require__(6); enableSleeping: false, events: [], plugin: {}, - grid: null, gravity: { x: 0, y: 1, @@ -7721,10 +7953,11 @@ var Body = __webpack_require__(6); var engine = Common.extend(defaults, options); engine.world = options.world || Composite.create({ label: 'World' }); - engine.grid = Grid.create(options.grid || options.broadphase); - engine.pairs = Pairs.create(); + engine.pairs = options.pairs || Pairs.create(); + engine.detector = options.detector || Detector.create(); - // temporary back compatibility + // for temporary back compatibility only + engine.grid = { buckets: [] }; engine.world.gravity = engine.gravity; engine.broadphase = engine.grid; engine.metrics = {}; @@ -7754,9 +7987,10 @@ var Body = __webpack_require__(6); correction = correction || 1; var world = engine.world, + detector = engine.detector, + pairs = engine.pairs, timing = engine.timing, - grid = engine.grid, - gridPairs = [], + timestamp = timing.timestamp, i; // increment timestamp @@ -7770,15 +8004,25 @@ var Body = __webpack_require__(6); Events.trigger(engine, 'beforeUpdate', event); - // get lists of all bodies and constraints, no matter what composites they are in + // get all bodies and all constraints in the world var allBodies = Composite.allBodies(world), allConstraints = Composite.allConstraints(world); - // if sleeping enabled, call the sleeping controller + // update the detector bodies if they have changed + if (world.isModified) { + Detector.setBodies(detector, allBodies); + } + + // reset all composite modified flags + if (world.isModified) { + Composite.setModified(world, false, false, true); + } + + // update sleeping if enabled if (engine.enableSleeping) Sleeping.update(allBodies, timing.timeScale); - // applies gravity to all bodies + // apply gravity to all bodies Engine._bodiesApplyGravity(allBodies, engine.gravity); // update all body position and rotation by integration @@ -7791,29 +8035,12 @@ var Body = __webpack_require__(6); } Constraint.postSolveAll(allBodies); - // broadphase pass: find potential collision pairs - - // if world is dirty, we must flush the whole grid - if (world.isModified) - Grid.clear(grid); - - // update the grid buckets based on current bodies - Grid.update(grid, allBodies, engine, world.isModified); - gridPairs = grid.pairsList; - - // clear all composite modified flags - if (world.isModified) { - Composite.setModified(world, false, false, true); - } - - // narrowphase pass: find actual collisions, then create or update collision pairs - var collisions = Detector.collisions(gridPairs, engine); + // find all collisions + detector.pairs = engine.pairs; + var collisions = Detector.collisions(detector); // update collision pairs - var pairs = engine.pairs, - timestamp = timing.timestamp; Pairs.update(pairs, collisions, timestamp); - Pairs.removeOld(pairs, timestamp); // wake up bodies involved in collisions if (engine.enableSleeping) @@ -7886,17 +8113,13 @@ var Body = __webpack_require__(6); }; /** - * Clears the engine including the world, pairs and broadphase. + * Clears the engine pairs and detector. * @method clear * @param {engine} engine */ Engine.clear = function(engine) { - var world = engine.world, - bodies = Composite.allBodies(world); - Pairs.clear(engine.pairs); - Grid.clear(engine.grid); - Grid.update(engine.grid, bodies, engine, true); + Detector.clear(engine.detector); }; /** @@ -7976,53 +8199,53 @@ var Body = __webpack_require__(6); * Fired just before an update * * @event beforeUpdate - * @param {} event An event object + * @param {object} event An event object * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update and all collision events * * @event afterUpdate - * @param {} event An event object + * @param {object} event An event object * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that have started to collide in the current tick (if any) * * @event collisionStart - * @param {} event An event object - * @param {} event.pairs List of affected pairs + * @param {object} event An event object + * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that are colliding in the current tick (if any) * * @event collisionActive - * @param {} event An event object - * @param {} event.pairs List of affected pairs + * @param {object} event An event object + * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that have ended collision in the current tick (if any) * * @event collisionEnd - * @param {} event An event object - * @param {} event.pairs List of affected pairs + * @param {object} event An event object + * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /* @@ -8114,9 +8337,18 @@ var Body = __webpack_require__(6); * @default 0 */ + /** + * A `Matter.Detector` instance. + * + * @property detector + * @type detector + * @default a Matter.Detector instance + */ + /** * A `Matter.Grid` instance. * + * @deprecated replaced by `engine.detector` * @property grid * @type grid * @default a Matter.Grid instance @@ -8125,7 +8357,7 @@ var Body = __webpack_require__(6); /** * Replaced by and now alias for `engine.grid`. * - * @deprecated use `engine.grid` + * @deprecated replaced by `engine.detector` * @property broadphase * @type grid * @default a Matter.Grid instance @@ -8195,8 +8427,6 @@ var Resolver = {}; module.exports = Resolver; var Vertices = __webpack_require__(3); -var Vector = __webpack_require__(2); -var Common = __webpack_require__(0); var Bounds = __webpack_require__(1); (function() { @@ -8215,10 +8445,11 @@ var Bounds = __webpack_require__(1); Resolver.preSolvePosition = function(pairs) { var i, pair, - activeCount; + activeCount, + pairsLength = pairs.length; // find total contacts on each body - for (i = 0; i < pairs.length; i++) { + for (i = 0; i < pairsLength; i++) { pair = pairs[i]; if (!pair.isActive) @@ -8243,17 +8474,13 @@ var Bounds = __webpack_require__(1); bodyA, bodyB, normal, - bodyBtoA, contactShare, positionImpulse, - contactCount = {}, - tempA = Vector._temp[0], - tempB = Vector._temp[1], - tempC = Vector._temp[2], - tempD = Vector._temp[3]; + positionDampen = Resolver._positionDampen, + pairsLength = pairs.length; // find impulses required to resolve penetration - for (i = 0; i < pairs.length; i++) { + for (i = 0; i < pairsLength; i++) { pair = pairs[i]; if (!pair.isActive || pair.isSensor) @@ -8265,14 +8492,12 @@ var Bounds = __webpack_require__(1); normal = collision.normal; // get current separation between body edges involved in collision - bodyBtoA = Vector.sub(Vector.add(bodyB.positionImpulse, bodyB.position, tempA), - Vector.add(bodyA.positionImpulse, - Vector.sub(bodyB.position, collision.penetration, tempB), tempC), tempD); - - pair.separation = Vector.dot(normal, bodyBtoA); + pair.separation = + normal.x * (bodyB.positionImpulse.x + collision.penetration.x - bodyA.positionImpulse.x) + + normal.y * (bodyB.positionImpulse.y + collision.penetration.y - bodyA.positionImpulse.y); } - for (i = 0; i < pairs.length; i++) { + for (i = 0; i < pairsLength; i++) { pair = pairs[i]; if (!pair.isActive || pair.isSensor) @@ -8288,13 +8513,13 @@ var Bounds = __webpack_require__(1); positionImpulse *= 2; if (!(bodyA.isStatic || bodyA.isSleeping)) { - contactShare = Resolver._positionDampen / bodyA.totalContacts; + contactShare = positionDampen / bodyA.totalContacts; bodyA.positionImpulse.x += normal.x * positionImpulse * contactShare; bodyA.positionImpulse.y += normal.y * positionImpulse * contactShare; } if (!(bodyB.isStatic || bodyB.isSleeping)) { - contactShare = Resolver._positionDampen / bodyB.totalContacts; + contactShare = positionDampen / bodyB.totalContacts; bodyB.positionImpulse.x -= normal.x * positionImpulse * contactShare; bodyB.positionImpulse.y -= normal.y * positionImpulse * contactShare; } @@ -8307,34 +8532,43 @@ var Bounds = __webpack_require__(1); * @param {body[]} bodies */ Resolver.postSolvePosition = function(bodies) { - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; + var positionWarming = Resolver._positionWarming, + bodiesLength = bodies.length, + verticesTranslate = Vertices.translate, + boundsUpdate = Bounds.update; + + for (var i = 0; i < bodiesLength; i++) { + var body = bodies[i], + positionImpulse = body.positionImpulse, + positionImpulseX = positionImpulse.x, + positionImpulseY = positionImpulse.y, + velocity = body.velocity; // reset contact count body.totalContacts = 0; - if (body.positionImpulse.x !== 0 || body.positionImpulse.y !== 0) { + if (positionImpulseX !== 0 || positionImpulseY !== 0) { // update body geometry for (var j = 0; j < body.parts.length; j++) { var part = body.parts[j]; - Vertices.translate(part.vertices, body.positionImpulse); - Bounds.update(part.bounds, part.vertices, body.velocity); - part.position.x += body.positionImpulse.x; - part.position.y += body.positionImpulse.y; + verticesTranslate(part.vertices, positionImpulse); + boundsUpdate(part.bounds, part.vertices, velocity); + part.position.x += positionImpulseX; + part.position.y += positionImpulseY; } // move the body without changing velocity - body.positionPrev.x += body.positionImpulse.x; - body.positionPrev.y += body.positionImpulse.y; + body.positionPrev.x += positionImpulseX; + body.positionPrev.y += positionImpulseY; - if (Vector.dot(body.positionImpulse, body.velocity) < 0) { + if (positionImpulseX * velocity.x + positionImpulseY * velocity.y < 0) { // reset cached impulse if the body has velocity along it - body.positionImpulse.x = 0; - body.positionImpulse.y = 0; + positionImpulse.x = 0; + positionImpulse.y = 0; } else { // warm the next iteration - body.positionImpulse.x *= Resolver._positionWarming; - body.positionImpulse.y *= Resolver._positionWarming; + positionImpulse.x *= positionWarming; + positionImpulse.y *= positionWarming; } } } @@ -8346,61 +8580,53 @@ var Bounds = __webpack_require__(1); * @param {pair[]} pairs */ Resolver.preSolveVelocity = function(pairs) { - var i, - j, - pair, - contacts, - collision, - bodyA, - bodyB, - normal, - tangent, - contact, - contactVertex, - normalImpulse, - tangentImpulse, - offset, - impulse = Vector._temp[0], - tempA = Vector._temp[1]; + var pairsLength = pairs.length, + i, + j; - for (i = 0; i < pairs.length; i++) { - pair = pairs[i]; + for (i = 0; i < pairsLength; i++) { + var pair = pairs[i]; if (!pair.isActive || pair.isSensor) continue; - contacts = pair.activeContacts; - collision = pair.collision; - bodyA = collision.parentA; - bodyB = collision.parentB; - normal = collision.normal; - tangent = collision.tangent; - + var contacts = pair.activeContacts, + contactsLength = contacts.length, + collision = pair.collision, + bodyA = collision.parentA, + bodyB = collision.parentB, + normal = collision.normal, + tangent = collision.tangent; + // resolve each contact - for (j = 0; j < contacts.length; j++) { - contact = contacts[j]; - contactVertex = contact.vertex; - normalImpulse = contact.normalImpulse; - tangentImpulse = contact.tangentImpulse; - + for (j = 0; j < contactsLength; j++) { + var contact = contacts[j], + contactVertex = contact.vertex, + normalImpulse = contact.normalImpulse, + tangentImpulse = contact.tangentImpulse; + if (normalImpulse !== 0 || tangentImpulse !== 0) { // total impulse from contact - impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse); - impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse); + var impulseX = normal.x * normalImpulse + tangent.x * tangentImpulse, + impulseY = normal.y * normalImpulse + tangent.y * tangentImpulse; // apply impulse from contact if (!(bodyA.isStatic || bodyA.isSleeping)) { - offset = Vector.sub(contactVertex, bodyA.position, tempA); - bodyA.positionPrev.x += impulse.x * bodyA.inverseMass; - bodyA.positionPrev.y += impulse.y * bodyA.inverseMass; - bodyA.anglePrev += Vector.cross(offset, impulse) * bodyA.inverseInertia; + bodyA.positionPrev.x += impulseX * bodyA.inverseMass; + bodyA.positionPrev.y += impulseY * bodyA.inverseMass; + bodyA.anglePrev += bodyA.inverseInertia * ( + (contactVertex.x - bodyA.position.x) * impulseY + - (contactVertex.y - bodyA.position.y) * impulseX + ); } - + if (!(bodyB.isStatic || bodyB.isSleeping)) { - offset = Vector.sub(contactVertex, bodyB.position, tempA); - bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass; - bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass; - bodyB.anglePrev -= Vector.cross(offset, impulse) * bodyB.inverseInertia; + bodyB.positionPrev.x -= impulseX * bodyB.inverseMass; + bodyB.positionPrev.y -= impulseY * bodyB.inverseMass; + bodyB.anglePrev -= bodyB.inverseInertia * ( + (contactVertex.x - bodyB.position.x) * impulseY + - (contactVertex.y - bodyB.position.y) * impulseX + ); } } } @@ -8415,14 +8641,17 @@ var Bounds = __webpack_require__(1); */ Resolver.solveVelocity = function(pairs, timeScale) { var timeScaleSquared = timeScale * timeScale, - impulse = Vector._temp[0], - tempA = Vector._temp[1], - tempB = Vector._temp[2], - tempC = Vector._temp[3], - tempD = Vector._temp[4], - tempE = Vector._temp[5]; - - for (var i = 0; i < pairs.length; i++) { + restingThresh = Resolver._restingThresh * timeScaleSquared, + frictionNormalMultiplier = Resolver._frictionNormalMultiplier, + restingThreshTangent = Resolver._restingThreshTangent * timeScaleSquared, + NumberMaxValue = Number.MAX_VALUE, + pairsLength = pairs.length, + tangentImpulse, + maxFriction, + i, + j; + + for (i = 0; i < pairsLength; i++) { var pair = pairs[i]; if (!pair.isActive || pair.isSensor) @@ -8431,97 +8660,119 @@ var Bounds = __webpack_require__(1); var collision = pair.collision, bodyA = collision.parentA, bodyB = collision.parentB, - normal = collision.normal, - tangent = collision.tangent, + 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, - contactShare = 1 / contacts.length; + contactsLength = contacts.length, + contactShare = 1 / contactsLength, + inverseMassTotal = bodyA.inverseMass + bodyB.inverseMass, + friction = pair.friction * pair.frictionStatic * frictionNormalMultiplier * timeScaleSquared; // update body velocities - bodyA.velocity.x = bodyA.position.x - bodyA.positionPrev.x; - bodyA.velocity.y = bodyA.position.y - bodyA.positionPrev.y; - bodyB.velocity.x = bodyB.position.x - bodyB.positionPrev.x; - bodyB.velocity.y = bodyB.position.y - bodyB.positionPrev.y; + 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; // resolve each contact - for (var j = 0; j < contacts.length; j++) { + for (j = 0; j < contactsLength; j++) { var contact = contacts[j], - contactVertex = contact.vertex, - offsetA = Vector.sub(contactVertex, bodyA.position, tempA), - offsetB = Vector.sub(contactVertex, bodyB.position, tempB), - velocityPointA = Vector.add(bodyA.velocity, Vector.mult(Vector.perp(offsetA), bodyA.angularVelocity), tempC), - velocityPointB = Vector.add(bodyB.velocity, Vector.mult(Vector.perp(offsetB), bodyB.angularVelocity), tempD), - relativeVelocity = Vector.sub(velocityPointA, velocityPointB, tempE), - normalVelocity = Vector.dot(normal, relativeVelocity); + contactVertex = contact.vertex; - var tangentVelocity = Vector.dot(tangent, relativeVelocity), - tangentSpeed = Math.abs(tangentVelocity), - tangentVelocityDirection = Common.sign(tangentVelocity); + var offsetAX = contactVertex.x - bodyA.position.x, + offsetAY = contactVertex.y - bodyA.position.y, + 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; - // raw impulses - var normalImpulse = (1 + pair.restitution) * normalVelocity, - normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1) * Resolver._frictionNormalMultiplier; + var relativeVelocityX = velocityPointAX - velocityPointBX, + relativeVelocityY = velocityPointAY - velocityPointBY; + + var normalVelocity = normalX * relativeVelocityX + normalY * relativeVelocityY, + tangentVelocity = tangentX * relativeVelocityX + tangentY * relativeVelocityY; // coulomb friction - var tangentImpulse = tangentVelocity, - maxFriction = Infinity; + var normalOverlap = pair.separation + normalVelocity; + var normalForce = Math.min(normalOverlap, 1); + normalForce = normalOverlap < 0 ? 0 : normalForce; + + var frictionLimit = normalForce * friction; - if (tangentSpeed > pair.friction * pair.frictionStatic * normalForce * timeScaleSquared) { - maxFriction = tangentSpeed; - tangentImpulse = Common.clamp( - pair.friction * tangentVelocityDirection * timeScaleSquared, - -maxFriction, maxFriction - ); + if (tangentVelocity > frictionLimit || -tangentVelocity > frictionLimit) { + maxFriction = tangentVelocity > 0 ? tangentVelocity : -tangentVelocity; + tangentImpulse = pair.friction * (tangentVelocity > 0 ? 1 : -1) * timeScaleSquared; + + if (tangentImpulse < -maxFriction) { + tangentImpulse = -maxFriction; + } else if (tangentImpulse > maxFriction) { + tangentImpulse = maxFriction; + } + } else { + tangentImpulse = tangentVelocity; + maxFriction = NumberMaxValue; } - // modify impulses accounting for mass, inertia and offset - var oAcN = Vector.cross(offsetA, normal), - oBcN = Vector.cross(offsetB, normal), - share = contactShare / (bodyA.inverseMass + bodyB.inverseMass + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN); + // account for mass, inertia and contact offset + var oAcN = offsetAX * normalY - offsetAY * normalX, + oBcN = offsetBX * normalY - offsetBY * normalX, + share = contactShare / (inverseMassTotal + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN); - normalImpulse *= share; + // raw impulses + var normalImpulse = (1 + pair.restitution) * normalVelocity * share; tangentImpulse *= share; // handle high velocity and resting collisions separately - if (normalVelocity < 0 && normalVelocity * normalVelocity > Resolver._restingThresh * timeScaleSquared) { + if (normalVelocity * normalVelocity > restingThresh && normalVelocity < 0) { // high normal velocity so clear cached contact normal impulse contact.normalImpulse = 0; } else { // solve resting collision constraints using Erin Catto's method (GDC08) // impulse constraint tends to 0 var contactNormalImpulse = contact.normalImpulse; - contact.normalImpulse = Math.min(contact.normalImpulse + normalImpulse, 0); + contact.normalImpulse += normalImpulse; + contact.normalImpulse = Math.min(contact.normalImpulse, 0); normalImpulse = contact.normalImpulse - contactNormalImpulse; } // handle high velocity and resting collisions separately - if (tangentVelocity * tangentVelocity > Resolver._restingThreshTangent * timeScaleSquared) { + if (tangentVelocity * tangentVelocity > restingThreshTangent) { // high tangent velocity so clear cached contact tangent impulse contact.tangentImpulse = 0; } else { // solve resting collision constraints using Erin Catto's method (GDC08) // tangent impulse tends to -tangentSpeed or +tangentSpeed var contactTangentImpulse = contact.tangentImpulse; - contact.tangentImpulse = Common.clamp(contact.tangentImpulse + tangentImpulse, -maxFriction, maxFriction); + contact.tangentImpulse += tangentImpulse; + if (contact.tangentImpulse < -maxFriction) contact.tangentImpulse = -maxFriction; + if (contact.tangentImpulse > maxFriction) contact.tangentImpulse = maxFriction; tangentImpulse = contact.tangentImpulse - contactTangentImpulse; } // total impulse from contact - impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse); - impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse); + var impulseX = normalX * normalImpulse + tangentX * tangentImpulse, + impulseY = normalY * normalImpulse + tangentY * tangentImpulse; // apply impulse from contact if (!(bodyA.isStatic || bodyA.isSleeping)) { - bodyA.positionPrev.x += impulse.x * bodyA.inverseMass; - bodyA.positionPrev.y += impulse.y * bodyA.inverseMass; - bodyA.anglePrev += Vector.cross(offsetA, impulse) * bodyA.inverseInertia; + bodyA.positionPrev.x += impulseX * bodyA.inverseMass; + bodyA.positionPrev.y += impulseY * bodyA.inverseMass; + bodyA.anglePrev += (offsetAX * impulseY - offsetAY * impulseX) * bodyA.inverseInertia; } if (!(bodyB.isStatic || bodyB.isSleeping)) { - bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass; - bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass; - bodyB.anglePrev -= Vector.cross(offsetB, impulse) * bodyB.inverseInertia; + bodyB.positionPrev.x -= impulseX * bodyB.inverseMass; + bodyB.positionPrev.y -= impulseY * bodyB.inverseMass; + bodyB.anglePrev -= (offsetBX * impulseY - offsetBY * impulseX) * bodyB.inverseInertia; } } } @@ -8548,8 +8799,6 @@ var Pair = __webpack_require__(9); var Common = __webpack_require__(0); (function() { - - Pairs._pairMaxIdleLife = 1000; /** * Creates a new pairs structure. @@ -8576,12 +8825,14 @@ var Common = __webpack_require__(0); */ Pairs.update = function(pairs, collisions, timestamp) { var pairsList = pairs.list, + pairsListLength = pairsList.length, pairsTable = pairs.table, + collisionsLength = collisions.length, collisionStart = pairs.collisionStart, collisionEnd = pairs.collisionEnd, collisionActive = pairs.collisionActive, collision, - pairId, + pairIndex, pair, i; @@ -8590,90 +8841,61 @@ var Common = __webpack_require__(0); collisionEnd.length = 0; collisionActive.length = 0; - for (i = 0; i < pairsList.length; i++) { + for (i = 0; i < pairsListLength; i++) { pairsList[i].confirmedActive = false; } - for (i = 0; i < collisions.length; i++) { + for (i = 0; i < collisionsLength; i++) { collision = collisions[i]; + pair = collision.pair; - if (collision.collided) { - pairId = Pair.id(collision.bodyA, collision.bodyB); - - pair = pairsTable[pairId]; - - if (pair) { - // 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); - } - - // update the pair - Pair.update(pair, collision, timestamp); - pair.confirmedActive = true; + if (pair) { + // pair already exists (but may or may not be active) + if (pair.isActive) { + // pair exists and is active + collisionActive.push(pair); } else { - // pair did not exist, create a new pair - pair = Pair.create(collision, timestamp); - pairsTable[pairId] = pair; - - // push the new pair + // pair exists but was inactive, so a collision has just started again collisionStart.push(pair); - pairsList.push(pair); + } + + // update the pair + Pair.update(pair, collision, timestamp); + pair.confirmedActive = true; + } else { + // pair did not exist, create a new pair + pair = Pair.create(collision, timestamp); + pairsTable[pair.id] = pair; + + // push the new pair + collisionStart.push(pair); + pairsList.push(pair); + } + } + + // find pairs that are no longer active + var removePairIndex = []; + pairsListLength = pairsList.length; + + for (i = 0; i < pairsListLength; i++) { + pair = pairsList[i]; + + if (!pair.confirmedActive) { + Pair.setActive(pair, false, timestamp); + collisionEnd.push(pair); + + if (!pair.collision.bodyA.isSleeping && !pair.collision.bodyB.isSleeping) { + removePairIndex.push(i); } } } - // deactivate previously active pairs that are now inactive - for (i = 0; i < pairsList.length; i++) { - pair = pairsList[i]; - if (pair.isActive && !pair.confirmedActive) { - Pair.setActive(pair, false, timestamp); - collisionEnd.push(pair); - } - } - }; - - /** - * Finds and removes pairs that have been inactive for a set amount of time. - * @method removeOld - * @param {object} pairs - * @param {number} timestamp - */ - Pairs.removeOld = function(pairs, timestamp) { - var pairsList = pairs.list, - pairsTable = pairs.table, - indexesToRemove = [], - pair, - collision, - pairIndex, - i; - - for (i = 0; i < pairsList.length; i++) { - pair = pairsList[i]; - collision = pair.collision; - - // never remove sleeping pairs - if (collision.bodyA.isSleeping || collision.bodyB.isSleeping) { - pair.timeUpdated = timestamp; - continue; - } - - // if pair is inactive for too long, mark it to be removed - if (timestamp - pair.timeUpdated > Pairs._pairMaxIdleLife) { - indexesToRemove.push(i); - } - } - - // remove marked pairs - for (i = 0; i < indexesToRemove.length; i++) { - pairIndex = indexesToRemove[i] - i; + // remove inactive pairs + for (i = 0; i < removePairIndex.length; i++) { + pairIndex = removePairIndex[i] - i; pair = pairsList[pairIndex]; - delete pairsTable[pair.id]; pairsList.splice(pairIndex, 1); + delete pairsTable[pair.id]; } }; @@ -8699,337 +8921,23 @@ var Common = __webpack_require__(0); /* 21 */ /***/ (function(module, exports, __webpack_require__) { -/** -* The `Matter.Grid` module contains methods for creating and manipulating collision broadphase grid structures. -* -* @class Grid -*/ - -var Grid = {}; - -module.exports = Grid; - -var Pair = __webpack_require__(9); -var Common = __webpack_require__(0); - -(function() { - - /** - * Creates a new grid. - * @method create - * @param {} options - * @return {grid} A new grid - */ - Grid.create = function(options) { - var defaults = { - buckets: {}, - pairs: {}, - pairsList: [], - bucketWidth: 48, - bucketHeight: 48 - }; - - return Common.extend(defaults, options); - }; - - /** - * The width of a single grid bucket. - * - * @property bucketWidth - * @type number - * @default 48 - */ - - /** - * The height of a single grid bucket. - * - * @property bucketHeight - * @type number - * @default 48 - */ - - /** - * Updates the grid. - * @method update - * @param {grid} grid - * @param {body[]} bodies - * @param {engine} engine - * @param {boolean} forceUpdate - */ - Grid.update = function(grid, bodies, engine, forceUpdate) { - var i, col, row, - world = engine.world, - buckets = grid.buckets, - bucket, - bucketId, - gridChanged = false; - - for (i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - if (body.isSleeping && !forceUpdate) - continue; - - // temporary back compatibility bounds check - if (world.bounds && (body.bounds.max.x < world.bounds.min.x || body.bounds.min.x > world.bounds.max.x - || body.bounds.max.y < world.bounds.min.y || body.bounds.min.y > world.bounds.max.y)) - continue; - - var newRegion = Grid._getRegion(grid, body); - - // if the body has changed grid region - if (!body.region || newRegion.id !== body.region.id || forceUpdate) { - - if (!body.region || forceUpdate) - body.region = newRegion; - - var union = Grid._regionUnion(newRegion, body.region); - - // update grid buckets affected by region change - // iterate over the union of both regions - for (col = union.startCol; col <= union.endCol; col++) { - for (row = union.startRow; row <= union.endRow; row++) { - bucketId = Grid._getBucketId(col, row); - bucket = buckets[bucketId]; - - var isInsideNewRegion = (col >= newRegion.startCol && col <= newRegion.endCol - && row >= newRegion.startRow && row <= newRegion.endRow); - - var isInsideOldRegion = (col >= body.region.startCol && col <= body.region.endCol - && row >= body.region.startRow && row <= body.region.endRow); - - // remove from old region buckets - if (!isInsideNewRegion && isInsideOldRegion) { - if (isInsideOldRegion) { - if (bucket) - Grid._bucketRemoveBody(grid, bucket, body); - } - } - - // add to new region buckets - if (body.region === newRegion || (isInsideNewRegion && !isInsideOldRegion) || forceUpdate) { - if (!bucket) - bucket = Grid._createBucket(buckets, bucketId); - Grid._bucketAddBody(grid, bucket, body); - } - } - } - - // set the new region - body.region = newRegion; - - // flag changes so we can update pairs - gridChanged = true; - } - } - - // update pairs list only if pairs changed (i.e. a body changed region) - if (gridChanged) - grid.pairsList = Grid._createActivePairsList(grid); - }; - - /** - * Clears the grid. - * @method clear - * @param {grid} grid - */ - Grid.clear = function(grid) { - grid.buckets = {}; - grid.pairs = {}; - grid.pairsList = []; - }; - - /** - * Finds the union of two regions. - * @method _regionUnion - * @private - * @param {} regionA - * @param {} regionB - * @return {} region - */ - Grid._regionUnion = function(regionA, regionB) { - var startCol = Math.min(regionA.startCol, regionB.startCol), - endCol = Math.max(regionA.endCol, regionB.endCol), - startRow = Math.min(regionA.startRow, regionB.startRow), - endRow = Math.max(regionA.endRow, regionB.endRow); - - return Grid._createRegion(startCol, endCol, startRow, endRow); - }; - - /** - * Gets the region a given body falls in for a given grid. - * @method _getRegion - * @private - * @param {} grid - * @param {} body - * @return {} region - */ - Grid._getRegion = function(grid, body) { - var bounds = body.bounds, - startCol = Math.floor(bounds.min.x / grid.bucketWidth), - endCol = Math.floor(bounds.max.x / grid.bucketWidth), - startRow = Math.floor(bounds.min.y / grid.bucketHeight), - endRow = Math.floor(bounds.max.y / grid.bucketHeight); - - return Grid._createRegion(startCol, endCol, startRow, endRow); - }; - - /** - * Creates a region. - * @method _createRegion - * @private - * @param {} startCol - * @param {} endCol - * @param {} startRow - * @param {} endRow - * @return {} region - */ - Grid._createRegion = function(startCol, endCol, startRow, endRow) { - return { - id: startCol + ',' + endCol + ',' + startRow + ',' + endRow, - startCol: startCol, - endCol: endCol, - startRow: startRow, - endRow: endRow - }; - }; - - /** - * Gets the bucket id at the given position. - * @method _getBucketId - * @private - * @param {} column - * @param {} row - * @return {string} bucket id - */ - Grid._getBucketId = function(column, row) { - return 'C' + column + 'R' + row; - }; - - /** - * Creates a bucket. - * @method _createBucket - * @private - * @param {} buckets - * @param {} bucketId - * @return {} bucket - */ - Grid._createBucket = function(buckets, bucketId) { - var bucket = buckets[bucketId] = []; - return bucket; - }; - - /** - * Adds a body to a bucket. - * @method _bucketAddBody - * @private - * @param {} grid - * @param {} bucket - * @param {} body - */ - Grid._bucketAddBody = function(grid, bucket, body) { - // add new pairs - for (var i = 0; i < bucket.length; i++) { - var bodyB = bucket[i]; - - if (body.id === bodyB.id || (body.isStatic && bodyB.isStatic)) - continue; - - // keep track of the number of buckets the pair exists in - // important for Grid.update to work - var pairId = Pair.id(body, bodyB), - pair = grid.pairs[pairId]; - - if (pair) { - pair[2] += 1; - } else { - grid.pairs[pairId] = [body, bodyB, 1]; - } - } - - // add to bodies (after pairs, otherwise pairs with self) - bucket.push(body); - }; - - /** - * Removes a body from a bucket. - * @method _bucketRemoveBody - * @private - * @param {} grid - * @param {} bucket - * @param {} body - */ - Grid._bucketRemoveBody = function(grid, bucket, body) { - // remove from bucket - bucket.splice(Common.indexOf(bucket, body), 1); - - // update pair counts - for (var i = 0; i < bucket.length; i++) { - // keep track of the number of buckets the pair exists in - // important for _createActivePairsList to work - var bodyB = bucket[i], - pairId = Pair.id(body, bodyB), - pair = grid.pairs[pairId]; - - if (pair) - pair[2] -= 1; - } - }; - - /** - * Generates a list of the active pairs in the grid. - * @method _createActivePairsList - * @private - * @param {} grid - * @return [] pairs - */ - Grid._createActivePairsList = function(grid) { - var pairKeys, - pair, - pairs = []; - - // grid.pairs is used as a hashmap - pairKeys = Common.keys(grid.pairs); - - // iterate over grid.pairs - for (var k = 0; k < pairKeys.length; k++) { - pair = grid.pairs[pairKeys[k]]; - - // if pair exists in at least one bucket - // it is a pair that needs further collision testing so push it - if (pair[2] > 0) { - pairs.push(pair); - } else { - delete grid.pairs[pairKeys[k]]; - } - } - - return pairs; - }; - -})(); - - -/***/ }), -/* 22 */ -/***/ (function(module, exports, __webpack_require__) { - -var Matter = module.exports = __webpack_require__(23); +var Matter = module.exports = __webpack_require__(22); -Matter.Axes = __webpack_require__(10); -Matter.Bodies = __webpack_require__(11); +Matter.Axes = __webpack_require__(11); +Matter.Bodies = __webpack_require__(12); Matter.Body = __webpack_require__(6); Matter.Bounds = __webpack_require__(1); +Matter.Collision = __webpack_require__(8); Matter.Common = __webpack_require__(0); Matter.Composite = __webpack_require__(5); -Matter.Composites = __webpack_require__(24); -Matter.Constraint = __webpack_require__(8); +Matter.Composites = __webpack_require__(23); +Matter.Constraint = __webpack_require__(10); Matter.Contact = __webpack_require__(17); -Matter.Detector = __webpack_require__(13); +Matter.Detector = __webpack_require__(14); Matter.Engine = __webpack_require__(18); Matter.Events = __webpack_require__(4); -Matter.Grid = __webpack_require__(21); -Matter.Mouse = __webpack_require__(12); +Matter.Grid = __webpack_require__(24); +Matter.Mouse = __webpack_require__(13); Matter.MouseConstraint = __webpack_require__(25); Matter.Pair = __webpack_require__(9); Matter.Pairs = __webpack_require__(20); @@ -9038,12 +8946,12 @@ Matter.Query = __webpack_require__(26); Matter.Render = __webpack_require__(16); Matter.Resolver = __webpack_require__(19); Matter.Runner = __webpack_require__(27); -Matter.SAT = __webpack_require__(14); +Matter.SAT = __webpack_require__(28); Matter.Sleeping = __webpack_require__(7); -Matter.Svg = __webpack_require__(28); +Matter.Svg = __webpack_require__(29); Matter.Vector = __webpack_require__(2); Matter.Vertices = __webpack_require__(3); -Matter.World = __webpack_require__(29); +Matter.World = __webpack_require__(30); // temporary back compatibility Matter.Engine.run = Matter.Runner.run; @@ -9051,7 +8959,7 @@ Matter.Common.deprecated(Matter.Engine, 'run', 'Engine.run ➤ use Matter.Runner /***/ }), -/* 23 */ +/* 22 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -9083,7 +8991,7 @@ var Common = __webpack_require__(0); * @readOnly * @type {String} */ - Matter.version = true ? "0.17.1" : undefined; + Matter.version = true ? "0.18.0" : undefined; /** * A list of plugin dependencies to be installed. These are normally set and installed through `Matter.use`. @@ -9143,7 +9051,7 @@ var Common = __webpack_require__(0); /***/ }), -/* 24 */ +/* 23 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -9160,10 +9068,10 @@ var Composites = {}; module.exports = Composites; var Composite = __webpack_require__(5); -var Constraint = __webpack_require__(8); +var Constraint = __webpack_require__(10); var Common = __webpack_require__(0); var Body = __webpack_require__(6); -var Bodies = __webpack_require__(11); +var Bodies = __webpack_require__(12); var deprecated = Common.deprecated; (function() { @@ -9485,6 +9393,352 @@ var deprecated = Common.deprecated; })(); +/***/ }), +/* 24 */ +/***/ (function(module, exports, __webpack_require__) { + +/** +* This module has now been replaced by `Matter.Detector`. +* +* All usage should be migrated to `Matter.Detector` or another alternative. +* For back-compatibility purposes this module will remain for a short term and then later removed in a future release. +* +* The `Matter.Grid` module contains methods for creating and manipulating collision broadphase grid structures. +* +* @class Grid +* @deprecated +*/ + +var Grid = {}; + +module.exports = Grid; + +var Pair = __webpack_require__(9); +var Common = __webpack_require__(0); +var deprecated = Common.deprecated; + +(function() { + + /** + * Creates a new grid. + * @deprecated replaced by Matter.Detector + * @method create + * @param {} options + * @return {grid} A new grid + */ + Grid.create = function(options) { + var defaults = { + buckets: {}, + pairs: {}, + pairsList: [], + bucketWidth: 48, + bucketHeight: 48 + }; + + return Common.extend(defaults, options); + }; + + /** + * The width of a single grid bucket. + * + * @property bucketWidth + * @type number + * @default 48 + */ + + /** + * The height of a single grid bucket. + * + * @property bucketHeight + * @type number + * @default 48 + */ + + /** + * Updates the grid. + * @deprecated replaced by Matter.Detector + * @method update + * @param {grid} grid + * @param {body[]} bodies + * @param {engine} engine + * @param {boolean} forceUpdate + */ + Grid.update = function(grid, bodies, engine, forceUpdate) { + var i, col, row, + world = engine.world, + buckets = grid.buckets, + bucket, + bucketId, + gridChanged = false; + + for (i = 0; i < bodies.length; i++) { + var body = bodies[i]; + + if (body.isSleeping && !forceUpdate) + continue; + + // temporary back compatibility bounds check + if (world.bounds && (body.bounds.max.x < world.bounds.min.x || body.bounds.min.x > world.bounds.max.x + || body.bounds.max.y < world.bounds.min.y || body.bounds.min.y > world.bounds.max.y)) + continue; + + var newRegion = Grid._getRegion(grid, body); + + // if the body has changed grid region + if (!body.region || newRegion.id !== body.region.id || forceUpdate) { + + if (!body.region || forceUpdate) + body.region = newRegion; + + var union = Grid._regionUnion(newRegion, body.region); + + // update grid buckets affected by region change + // iterate over the union of both regions + for (col = union.startCol; col <= union.endCol; col++) { + for (row = union.startRow; row <= union.endRow; row++) { + bucketId = Grid._getBucketId(col, row); + bucket = buckets[bucketId]; + + var isInsideNewRegion = (col >= newRegion.startCol && col <= newRegion.endCol + && row >= newRegion.startRow && row <= newRegion.endRow); + + var isInsideOldRegion = (col >= body.region.startCol && col <= body.region.endCol + && row >= body.region.startRow && row <= body.region.endRow); + + // remove from old region buckets + if (!isInsideNewRegion && isInsideOldRegion) { + if (isInsideOldRegion) { + if (bucket) + Grid._bucketRemoveBody(grid, bucket, body); + } + } + + // add to new region buckets + if (body.region === newRegion || (isInsideNewRegion && !isInsideOldRegion) || forceUpdate) { + if (!bucket) + bucket = Grid._createBucket(buckets, bucketId); + Grid._bucketAddBody(grid, bucket, body); + } + } + } + + // set the new region + body.region = newRegion; + + // flag changes so we can update pairs + gridChanged = true; + } + } + + // update pairs list only if pairs changed (i.e. a body changed region) + if (gridChanged) + grid.pairsList = Grid._createActivePairsList(grid); + }; + + deprecated(Grid, 'update', 'Grid.update ➤ replaced by Matter.Detector'); + + /** + * Clears the grid. + * @deprecated replaced by Matter.Detector + * @method clear + * @param {grid} grid + */ + Grid.clear = function(grid) { + grid.buckets = {}; + grid.pairs = {}; + grid.pairsList = []; + }; + + deprecated(Grid, 'clear', 'Grid.clear ➤ replaced by Matter.Detector'); + + /** + * Finds the union of two regions. + * @method _regionUnion + * @deprecated replaced by Matter.Detector + * @private + * @param {} regionA + * @param {} regionB + * @return {} region + */ + Grid._regionUnion = function(regionA, regionB) { + var startCol = Math.min(regionA.startCol, regionB.startCol), + endCol = Math.max(regionA.endCol, regionB.endCol), + startRow = Math.min(regionA.startRow, regionB.startRow), + endRow = Math.max(regionA.endRow, regionB.endRow); + + return Grid._createRegion(startCol, endCol, startRow, endRow); + }; + + /** + * Gets the region a given body falls in for a given grid. + * @method _getRegion + * @deprecated replaced by Matter.Detector + * @private + * @param {} grid + * @param {} body + * @return {} region + */ + Grid._getRegion = function(grid, body) { + var bounds = body.bounds, + startCol = Math.floor(bounds.min.x / grid.bucketWidth), + endCol = Math.floor(bounds.max.x / grid.bucketWidth), + startRow = Math.floor(bounds.min.y / grid.bucketHeight), + endRow = Math.floor(bounds.max.y / grid.bucketHeight); + + return Grid._createRegion(startCol, endCol, startRow, endRow); + }; + + /** + * Creates a region. + * @method _createRegion + * @deprecated replaced by Matter.Detector + * @private + * @param {} startCol + * @param {} endCol + * @param {} startRow + * @param {} endRow + * @return {} region + */ + Grid._createRegion = function(startCol, endCol, startRow, endRow) { + return { + id: startCol + ',' + endCol + ',' + startRow + ',' + endRow, + startCol: startCol, + endCol: endCol, + startRow: startRow, + endRow: endRow + }; + }; + + /** + * Gets the bucket id at the given position. + * @method _getBucketId + * @deprecated replaced by Matter.Detector + * @private + * @param {} column + * @param {} row + * @return {string} bucket id + */ + Grid._getBucketId = function(column, row) { + return 'C' + column + 'R' + row; + }; + + /** + * Creates a bucket. + * @method _createBucket + * @deprecated replaced by Matter.Detector + * @private + * @param {} buckets + * @param {} bucketId + * @return {} bucket + */ + Grid._createBucket = function(buckets, bucketId) { + var bucket = buckets[bucketId] = []; + return bucket; + }; + + /** + * Adds a body to a bucket. + * @method _bucketAddBody + * @deprecated replaced by Matter.Detector + * @private + * @param {} grid + * @param {} bucket + * @param {} body + */ + Grid._bucketAddBody = function(grid, bucket, body) { + var gridPairs = grid.pairs, + pairId = Pair.id, + bucketLength = bucket.length, + i; + + // add new pairs + for (i = 0; i < bucketLength; i++) { + var bodyB = bucket[i]; + + if (body.id === bodyB.id || (body.isStatic && bodyB.isStatic)) + continue; + + // keep track of the number of buckets the pair exists in + // important for Grid.update to work + var id = pairId(body, bodyB), + pair = gridPairs[id]; + + if (pair) { + pair[2] += 1; + } else { + gridPairs[id] = [body, bodyB, 1]; + } + } + + // add to bodies (after pairs, otherwise pairs with self) + bucket.push(body); + }; + + /** + * Removes a body from a bucket. + * @method _bucketRemoveBody + * @deprecated replaced by Matter.Detector + * @private + * @param {} grid + * @param {} bucket + * @param {} body + */ + Grid._bucketRemoveBody = function(grid, bucket, body) { + var gridPairs = grid.pairs, + pairId = Pair.id, + i; + + // remove from bucket + bucket.splice(Common.indexOf(bucket, body), 1); + + var bucketLength = bucket.length; + + // update pair counts + for (i = 0; i < bucketLength; i++) { + // keep track of the number of buckets the pair exists in + // important for _createActivePairsList to work + var pair = gridPairs[pairId(body, bucket[i])]; + + if (pair) + pair[2] -= 1; + } + }; + + /** + * Generates a list of the active pairs in the grid. + * @method _createActivePairsList + * @deprecated replaced by Matter.Detector + * @private + * @param {} grid + * @return [] pairs + */ + Grid._createActivePairsList = function(grid) { + var pair, + gridPairs = grid.pairs, + pairKeys = Common.keys(gridPairs), + pairKeysLength = pairKeys.length, + pairs = [], + k; + + // iterate over grid.pairs + for (k = 0; k < pairKeysLength; k++) { + pair = gridPairs[pairKeys[k]]; + + // if pair exists in at least one bucket + // it is a pair that needs further collision testing so push it + if (pair[2] > 0) { + pairs.push(pair); + } else { + delete gridPairs[pairKeys[k]]; + } + } + + return pairs; + }; + +})(); + + /***/ }), /* 25 */ /***/ (function(module, exports, __webpack_require__) { @@ -9504,10 +9758,10 @@ module.exports = MouseConstraint; var Vertices = __webpack_require__(3); var Sleeping = __webpack_require__(7); -var Mouse = __webpack_require__(12); +var Mouse = __webpack_require__(13); var Events = __webpack_require__(4); -var Detector = __webpack_require__(13); -var Constraint = __webpack_require__(8); +var Detector = __webpack_require__(14); +var Constraint = __webpack_require__(10); var Composite = __webpack_require__(5); var Common = __webpack_require__(0); var Bounds = __webpack_require__(1); @@ -9769,9 +10023,9 @@ var Query = {}; module.exports = Query; var Vector = __webpack_require__(2); -var SAT = __webpack_require__(14); +var Collision = __webpack_require__(8); var Bounds = __webpack_require__(1); -var Bodies = __webpack_require__(11); +var Bodies = __webpack_require__(12); var Vertices = __webpack_require__(3); (function() { @@ -9781,22 +10035,28 @@ var Vertices = __webpack_require__(3); * @method collides * @param {body} body * @param {body[]} bodies - * @return {object[]} Collisions + * @return {collision[]} Collisions */ Query.collides = function(body, bodies) { - var collisions = []; + var collisions = [], + bodiesLength = bodies.length, + bounds = body.bounds, + collides = Collision.collides, + overlaps = Bounds.overlaps; - for (var i = 0; i < bodies.length; i++) { - var bodyA = bodies[i]; + for (var i = 0; i < bodiesLength; i++) { + var bodyA = bodies[i], + partsALength = bodyA.parts.length, + partsAStart = partsALength === 1 ? 0 : 1; - if (Bounds.overlaps(bodyA.bounds, body.bounds)) { - for (var j = bodyA.parts.length === 1 ? 0 : 1; j < bodyA.parts.length; j++) { + if (overlaps(bodyA.bounds, bounds)) { + for (var j = partsAStart; j < partsALength; j++) { var part = bodyA.parts[j]; - if (Bounds.overlaps(part.bounds, body.bounds)) { - var collision = SAT.collides(part, body); + if (overlaps(part.bounds, bounds)) { + var collision = collides(part, body); - if (collision.collided) { + if (collision) { collisions.push(collision); break; } @@ -9815,7 +10075,7 @@ var Vertices = __webpack_require__(3); * @param {vector} startPoint * @param {vector} endPoint * @param {number} [rayWidth] - * @return {object[]} Collisions + * @return {collision[]} Collisions */ Query.ray = function(bodies, startPoint, endPoint, rayWidth) { rayWidth = rayWidth || 1e-100; @@ -10182,6 +10442,49 @@ var Common = __webpack_require__(0); /* 28 */ /***/ (function(module, exports, __webpack_require__) { +/** +* This module has now been replaced by `Matter.Collision`. +* +* All usage should be migrated to `Matter.Collision`. +* For back-compatibility purposes this module will remain for a short term and then later removed in a future release. +* +* The `Matter.SAT` module contains methods for detecting collisions using the Separating Axis Theorem. +* +* @class SAT +* @deprecated +*/ + +var SAT = {}; + +module.exports = SAT; + +var Collision = __webpack_require__(8); +var Common = __webpack_require__(0); +var deprecated = Common.deprecated; + +(function() { + + /** + * Detect collision between two bodies using the Separating Axis Theorem. + * @deprecated replaced by Collision.collides + * @method collides + * @param {body} bodyA + * @param {body} bodyB + * @return {collision} collision + */ + SAT.collides = function(bodyA, bodyB) { + return Collision.collides(bodyA, bodyB); + }; + + deprecated(SAT, 'collides', 'SAT.collides ➤ replaced by Collision.collides'); + +})(); + + +/***/ }), +/* 29 */ +/***/ (function(module, exports, __webpack_require__) { + /** * The `Matter.Svg` module contains methods for converting SVG images into an array of vector points. * @@ -10410,7 +10713,7 @@ var Common = __webpack_require__(0); })(); /***/ }), -/* 29 */ +/* 30 */ /***/ (function(module, exports, __webpack_require__) { /** diff --git a/build/matter.min.js b/build/matter.min.js index da8f1e1..29d25db 100644 --- a/build/matter.min.js +++ b/build/matter.min.js @@ -1,6 +1,6 @@ /*! - * matter-js 0.17.1 by @liabru + * matter-js 0.18.0 by @liabru * http://brm.io/matter-js/ * License MIT */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Matter",[],t):"object"==typeof exports?exports.Matter=t():e.Matter=t()}(this,(function(){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=22)}([function(e,t){var n={};e.exports=n,function(){n._nextId=0,n._seed=0,n._nowStartTime=+new Date,n._warnedOnce={},n._decomp=null,n.extend=function(e,t){var i,o;"boolean"==typeof t?(i=2,o=t):(i=1,o=!0);for(var r=i;r0;t--){var i=Math.floor(n.random()*(t+1)),o=e[t];e[t]=e[i],e[i]=o}return e},n.choose=function(e){return e[Math.floor(n.random()*e.length)]},n.isElement=function(e){return"undefined"!=typeof HTMLElement?e instanceof HTMLElement:!!(e&&e.nodeType&&e.nodeName)},n.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)},n.isFunction=function(e){return"function"==typeof e},n.isPlainObject=function(e){return"object"==typeof e&&e.constructor===Object},n.isString=function(e){return"[object String]"===toString.call(e)},n.clamp=function(e,t,n){return en?n:e},n.sign=function(e){return e<0?-1:1},n.now=function(){if("undefined"!=typeof window&&window.performance){if(window.performance.now)return window.performance.now();if(window.performance.webkitNow)return window.performance.webkitNow()}return Date.now?Date.now():new Date-n._nowStartTime},n.random=function(t,n){return n=void 0!==n?n:1,(t=void 0!==t?t:0)+e()*(n-t)};var e=function(){return n._seed=(9301*n._seed+49297)%233280,n._seed/233280};n.colorToNumber=function(e){return 3==(e=e.replace("#","")).length&&(e=e.charAt(0)+e.charAt(0)+e.charAt(1)+e.charAt(1)+e.charAt(2)+e.charAt(2)),parseInt(e,16)},n.logLevel=1,n.log=function(){console&&n.logLevel>0&&n.logLevel<=3&&console.log.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.info=function(){console&&n.logLevel>0&&n.logLevel<=2&&console.info.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.warn=function(){console&&n.logLevel>0&&n.logLevel<=3&&console.warn.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.warnOnce=function(){var e=Array.prototype.slice.call(arguments).join(" ");n._warnedOnce[e]||(n.warn(e),n._warnedOnce[e]=!0)},n.deprecated=function(e,t,i){e[t]=n.chain((function(){n.warnOnce("🔅 deprecated 🔅",i)}),e[t])},n.nextId=function(){return n._nextId++},n.indexOf=function(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0;ne.max.x&&(e.max.x=o.x),o.xe.max.y&&(e.max.y=o.y),o.y0?e.max.x+=n.x:e.min.x+=n.x,n.y>0?e.max.y+=n.y:e.min.y+=n.y)},n.contains=function(e,t){return t.x>=e.min.x&&t.x<=e.max.x&&t.y>=e.min.y&&t.y<=e.max.y},n.overlaps=function(e,t){return e.min.x<=t.max.x&&e.max.x>=t.min.x&&e.max.y>=t.min.y&&e.min.y<=t.max.y},n.translate=function(e,t){e.min.x+=t.x,e.max.x+=t.x,e.min.y+=t.y,e.max.y+=t.y},n.shift=function(e,t){var n=e.max.x-e.min.x,i=e.max.y-e.min.y;e.min.x=t.x,e.max.x=t.x+n,e.min.y=t.y,e.max.y=t.y+i}},function(e,t){var n={};e.exports=n,n.create=function(e,t){return{x:e||0,y:t||0}},n.clone=function(e){return{x:e.x,y:e.y}},n.magnitude=function(e){return Math.sqrt(e.x*e.x+e.y*e.y)},n.magnitudeSquared=function(e){return e.x*e.x+e.y*e.y},n.rotate=function(e,t,n){var i=Math.cos(t),o=Math.sin(t);n||(n={});var r=e.x*i-e.y*o;return n.y=e.x*o+e.y*i,n.x=r,n},n.rotateAbout=function(e,t,n,i){var o=Math.cos(t),r=Math.sin(t);i||(i={});var a=n.x+((e.x-n.x)*o-(e.y-n.y)*r);return i.y=n.y+((e.x-n.x)*r+(e.y-n.y)*o),i.x=a,i},n.normalise=function(e){var t=n.magnitude(e);return 0===t?{x:0,y:0}:{x:e.x/t,y:e.y/t}},n.dot=function(e,t){return e.x*t.x+e.y*t.y},n.cross=function(e,t){return e.x*t.y-e.y*t.x},n.cross3=function(e,t,n){return(t.x-e.x)*(n.y-e.y)-(t.y-e.y)*(n.x-e.x)},n.add=function(e,t,n){return n||(n={}),n.x=e.x+t.x,n.y=e.y+t.y,n},n.sub=function(e,t,n){return n||(n={}),n.x=e.x-t.x,n.y=e.y-t.y,n},n.mult=function(e,t){return{x:e.x*t,y:e.y*t}},n.div=function(e,t){return{x:e.x/t,y:e.y/t}},n.perp=function(e,t){return{x:(t=!0===t?-1:1)*-e.y,y:t*e.x}},n.neg=function(e){return{x:-e.x,y:-e.y}},n.angle=function(e,t){return Math.atan2(t.y-e.y,t.x-e.x)},n._temp=[n.create(),n.create(),n.create(),n.create(),n.create(),n.create()]},function(e,t,n){var i={};e.exports=i;var o=n(2),r=n(0);i.create=function(e,t){for(var n=[],i=0;i0)return!1}return!0},i.scale=function(e,t,n,r){if(1===t&&1===n)return e;var a,s;r=r||i.centre(e);for(var l=0;l=0?l-1:e.length-1],u=e[l],d=e[(l+1)%e.length],p=t[l0&&(r|=2),3===r)return!1;return 0!==r||null},i.hull=function(e){var t,n,i=[],r=[];for((e=e.slice(0)).sort((function(e,t){var n=e.x-t.x;return 0!==n?n:e.y-t.y})),n=0;n=2&&o.cross3(r[r.length-2],r[r.length-1],t)<=0;)r.pop();r.push(t)}for(n=e.length-1;n>=0;n-=1){for(t=e[n];i.length>=2&&o.cross3(i[i.length-2],i[i.length-1],t)<=0;)i.pop();i.push(t)}return i.pop(),r.pop(),i.concat(r)}},function(e,t,n){var i={};e.exports=i;var o=n(0);i.on=function(e,t,n){for(var i,o=t.split(" "),r=0;r0){n||(n={}),i=t.split(" ");for(var c=0;c0&&r.rotateAbout(a.position,n,e.position,a.position)}},i.setVelocity=function(e,t){e.positionPrev.x=e.position.x-t.x,e.positionPrev.y=e.position.y-t.y,e.velocity.x=t.x,e.velocity.y=t.y,e.speed=r.magnitude(e.velocity)},i.setAngularVelocity=function(e,t){e.anglePrev=e.angle-t,e.angularVelocity=t,e.angularSpeed=Math.abs(e.angularVelocity)},i.translate=function(e,t){i.setPosition(e,r.add(e.position,t))},i.rotate=function(e,t,n){if(n){var o=Math.cos(t),r=Math.sin(t),a=e.position.x-n.x,s=e.position.y-n.y;i.setPosition(e,{x:n.x+(a*o-s*r),y:n.y+(a*r+s*o)}),i.setAngle(e,e.angle+t)}else i.setAngle(e,e.angle+t)},i.scale=function(e,t,n,r){var a=0,s=0;r=r||e.position;for(var u=0;u0&&(a+=d.area,s+=d.inertia),d.position.x=r.x+(d.position.x-r.x)*t,d.position.y=r.y+(d.position.y-r.y)*n,l.update(d.bounds,d.vertices,e.velocity)}e.parts.length>1&&(e.area=a,e.isStatic||(i.setMass(e,e.density*a),i.setInertia(e,s))),e.circleRadius&&(t===n?e.circleRadius*=t:e.circleRadius=null)},i.update=function(e,t,n,i){var a=Math.pow(t*n*e.timeScale,2),s=1-e.frictionAir*n*e.timeScale,u=e.position.x-e.positionPrev.x,d=e.position.y-e.positionPrev.y;e.velocity.x=u*s*i+e.force.x/e.mass*a,e.velocity.y=d*s*i+e.force.y/e.mass*a,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.position.x+=e.velocity.x,e.position.y+=e.velocity.y,e.angularVelocity=(e.angle-e.anglePrev)*s*i+e.torque/e.inertia*a,e.anglePrev=e.angle,e.angle+=e.angularVelocity,e.speed=r.magnitude(e.velocity),e.angularSpeed=Math.abs(e.angularVelocity);for(var p=0;p0&&(f.position.x+=e.velocity.x,f.position.y+=e.velocity.y),0!==e.angularVelocity&&(o.rotate(f.vertices,e.angularVelocity,e.position),c.rotate(f.axes,e.angularVelocity),p>0&&r.rotateAbout(f.position,e.angularVelocity,e.position,f.position)),l.update(f.bounds,f.vertices,e.velocity)}},i.applyForce=function(e,t,n){e.force.x+=n.x,e.force.y+=n.y;var i=t.x-e.position.x,o=t.y-e.position.y;e.torque+=i*n.y-o*n.x},i._totalProperties=function(e){for(var t={mass:0,area:0,inertia:0,centre:{x:0,y:0}},n=1===e.parts.length?0:1;n0&&r.motion=r.sleepThreshold&&i.set(r,!0)):r.sleepCounter>0&&(r.sleepCounter-=1)}else i.set(r,!1)}},i.afterCollisions=function(e,t){for(var n=t*t*t,o=0;oi._motionWakeThreshold*n&&i.set(c,!1)}}}},i.set=function(e,t){var n=e.isSleeping;t?(e.isSleeping=!0,e.sleepCounter=e.sleepThreshold,e.positionImpulse.x=0,e.positionImpulse.y=0,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.anglePrev=e.angle,e.speed=0,e.angularSpeed=0,e.motion=0,n||o.trigger(e,"sleepStart")):(e.isSleeping=!1,e.sleepCounter=0,n&&o.trigger(e,"sleepEnd"))}},function(e,t,n){var i={};e.exports=i;var o=n(3),r=n(2),a=n(7),s=n(1),l=n(10),c=n(0);i._warming=.4,i._torqueDampen=1,i._minLength=1e-6,i.create=function(e){var t=e;t.bodyA&&!t.pointA&&(t.pointA={x:0,y:0}),t.bodyB&&!t.pointB&&(t.pointB={x:0,y:0});var n=t.bodyA?r.add(t.bodyA.position,t.pointA):t.pointA,i=t.bodyB?r.add(t.bodyB.position,t.pointB):t.pointB,o=r.magnitude(r.sub(n,i));t.length=void 0!==t.length?t.length:o,t.id=t.id||c.nextId(),t.label=t.label||"Constraint",t.type="constraint",t.stiffness=t.stiffness||(t.length>0?1:.7),t.damping=t.damping||0,t.angularStiffness=t.angularStiffness||0,t.angleA=t.bodyA?t.bodyA.angle:t.angleA,t.angleB=t.bodyB?t.bodyB.angle:t.angleB,t.plugin={};var a={visible:!0,lineWidth:2,strokeStyle:"#ffffff",type:"line",anchors:!0};return 0===t.length&&t.stiffness>.1?(a.type="pin",a.anchors=!1):t.stiffness<.9&&(a.type="spring"),t.render=c.extend(a,t.render),t},i.preSolveAll=function(e){for(var t=0;t0&&(d.position.x+=c.x,d.position.y+=c.y),0!==c.angle&&(o.rotate(d.vertices,c.angle,n.position),l.rotate(d.axes,c.angle),u>0&&r.rotateAbout(d.position,c.angle,n.position,d.position)),s.update(d.bounds,d.vertices,n.velocity)}c.angle*=i._warming,c.x*=i._warming,c.y*=i._warming}}},i.pointAWorld=function(e){return{x:(e.bodyA?e.bodyA.position.x:0)+e.pointA.x,y:(e.bodyA?e.bodyA.position.y:0)+e.pointA.y}},i.pointBWorld=function(e){return{x:(e.bodyB?e.bodyB.position.x:0)+e.pointB.x,y:(e.bodyB?e.bodyB.position.y:0)+e.pointB.y}}},function(e,t,n){var i={};e.exports=i;var o=n(17);i.create=function(e,t){var n=e.bodyA,o=e.bodyB,r=e.parentA,a=e.parentB,s={id:i.id(n,o),bodyA:n,bodyB:o,contacts:{},activeContacts:[],separation:0,isActive:!0,confirmedActive:!0,isSensor:n.isSensor||o.isSensor,timeCreated:t,timeUpdated:t,inverseMass:r.inverseMass+a.inverseMass,friction:Math.min(r.friction,a.friction),frictionStatic:Math.max(r.frictionStatic,a.frictionStatic),restitution:Math.max(r.restitution,a.restitution),slop:Math.max(r.slop,a.slop)};return i.update(s,e,t),s},i.update=function(e,t,n){var r=e.contacts,a=t.supports,s=e.activeContacts,l=t.parentA,c=t.parentB;if(e.collision=t,e.inverseMass=l.inverseMass+c.inverseMass,e.friction=Math.min(l.friction,c.friction),e.frictionStatic=Math.max(l.frictionStatic,c.frictionStatic),e.restitution=Math.max(l.restitution,c.restitution),e.slop=Math.max(l.slop,c.slop),s.length=0,t.collided){for(var u=0;u0&&o.area(C)1?(v=a.create(r.extend({parts:m.slice(0)},i)),a.setPosition(v,{x:e,y:t}),v):m[0]}},function(e,t,n){var i={};e.exports=i;var o=n(0);i.create=function(e){var t={};return e||o.log("Mouse.create: element was undefined, defaulting to document.body","warn"),t.element=e||document.body,t.absolute={x:0,y:0},t.position={x:0,y:0},t.mousedownPosition={x:0,y:0},t.mouseupPosition={x:0,y:0},t.offset={x:0,y:0},t.scale={x:1,y:1},t.wheelDelta=0,t.button=-1,t.pixelRatio=parseInt(t.element.getAttribute("data-pixel-ratio"),10)||1,t.sourceEvents={mousemove:null,mousedown:null,mouseup:null,mousewheel:null},t.mousemove=function(e){var n=i._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches&&(t.button=0,e.preventDefault()),t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.sourceEvents.mousemove=e},t.mousedown=function(e){var n=i._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches?(t.button=0,e.preventDefault()):t.button=e.button,t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.mousedownPosition.x=t.position.x,t.mousedownPosition.y=t.position.y,t.sourceEvents.mousedown=e},t.mouseup=function(e){var n=i._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches&&e.preventDefault(),t.button=-1,t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.mouseupPosition.x=t.position.x,t.mouseupPosition.y=t.position.y,t.sourceEvents.mouseup=e},t.mousewheel=function(e){t.wheelDelta=Math.max(-1,Math.min(1,e.wheelDelta||-e.detail)),e.preventDefault()},i.setElement(t,t.element),t},i.setElement=function(e,t){e.element=t,t.addEventListener("mousemove",e.mousemove),t.addEventListener("mousedown",e.mousedown),t.addEventListener("mouseup",e.mouseup),t.addEventListener("mousewheel",e.mousewheel),t.addEventListener("DOMMouseScroll",e.mousewheel),t.addEventListener("touchmove",e.mousemove),t.addEventListener("touchstart",e.mousedown),t.addEventListener("touchend",e.mouseup)},i.clearSourceEvents=function(e){e.sourceEvents.mousemove=null,e.sourceEvents.mousedown=null,e.sourceEvents.mouseup=null,e.sourceEvents.mousewheel=null,e.wheelDelta=0},i.setOffset=function(e,t){e.offset.x=t.x,e.offset.y=t.y,e.position.x=e.absolute.x*e.scale.x+e.offset.x,e.position.y=e.absolute.y*e.scale.y+e.offset.y},i.setScale=function(e,t){e.scale.x=t.x,e.scale.y=t.y,e.position.x=e.absolute.x*e.scale.x+e.offset.x,e.position.y=e.absolute.y*e.scale.y+e.offset.y},i._getRelativeMousePosition=function(e,t,n){var i,o,r=t.getBoundingClientRect(),a=document.documentElement||document.body.parentNode||document.body,s=void 0!==window.pageXOffset?window.pageXOffset:a.scrollLeft,l=void 0!==window.pageYOffset?window.pageYOffset:a.scrollTop,c=e.changedTouches;return c?(i=c[0].pageX-r.left-s,o=c[0].pageY-r.top-l):(i=e.pageX-r.left-s,o=e.pageY-r.top-l),{x:i/(t.clientWidth/(t.width||t.clientWidth)*n),y:o/(t.clientHeight/(t.height||t.clientHeight)*n)}}},function(e,t,n){var i={};e.exports=i;var o=n(14),r=n(9),a=n(1);i.collisions=function(e,t){for(var n=[],s=t.pairs.table,l=0;l1?1:0;d1?1:0;f0:0!=(e.mask&t.category)&&0!=(t.mask&e.category)}},function(e,t,n){var i={};e.exports=i;var o=n(3),r=n(2);i.collides=function(e,t,n){var a,s,l,c,u=!1;if(n){var d=e.parent,p=t.parent,f=d.speed*d.speed+d.angularSpeed*d.angularSpeed+p.speed*p.speed+p.angularSpeed*p.angularSpeed;u=n&&n.collided&&f<.2,c=n}else c={collided:!1,bodyA:e,bodyB:t};if(n&&u){var v=c.axisBody,m=v===e?t:e,y=[v.axes[n.axisNumber]];if(l=i._overlapAxes(v.vertices,m.vertices,y),c.reused=!0,l.overlap<=0)return c.collided=!1,c}else{if((a=i._overlapAxes(e.vertices,t.vertices,e.axes)).overlap<=0)return c.collided=!1,c;if((s=i._overlapAxes(t.vertices,e.vertices,t.axes)).overlap<=0)return c.collided=!1,c;a.overlapo?o=s:s=0?a.index-1:u.length-1],c.x=o.x-d.x,c.y=o.y-d.y,l=-r.dot(n,c),s=o,o=u[(a.index+1)%u.length],c.x=o.x-d.x,c.y=o.y-d.y,(i=-r.dot(n,c))r?(o.warn("Plugin.register:",i.toString(t),"was upgraded to",i.toString(e)),i._registry[e.name]=e):n-1},i.isFor=function(e,t){var n=e.for&&i.dependencyParse(e.for);return!e.for||t.name===n.name&&i.versionSatisfies(t.version,n.range)},i.use=function(e,t){if(e.uses=(e.uses||[]).concat(t||[]),0!==e.uses.length){for(var n=i.dependencies(e),r=o.topologicalSort(n),a=[],s=0;s0&&o.info(a.join(" "))}else o.warn("Plugin.use:",i.toString(e),"does not specify any dependencies to install.")},i.dependencies=function(e,t){var n=i.dependencyParse(e),r=n.name;if(!(r in(t=t||{}))){e=i.resolve(e)||e,t[r]=o.map(e.uses||[],(function(t){i.isPlugin(t)&&i.register(t);var r=i.dependencyParse(t),a=i.resolve(t);return a&&!i.versionSatisfies(a.version,r.range)?(o.warn("Plugin.dependencies:",i.toString(a),"does not satisfy",i.toString(r),"used by",i.toString(n)+"."),a._warned=!0,e._warned=!0):a||(o.warn("Plugin.dependencies:",i.toString(t),"used by",i.toString(n),"could not be resolved."),e._warned=!0),r.name}));for(var a=0;a=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-]+)?$/;t.test(e)||o.warn("Plugin.versionParse:",e,"is not a valid version or range.");var n=t.exec(e),i=Number(n[4]),r=Number(n[5]),a=Number(n[6]);return{isRange:Boolean(n[1]||n[2]),version:n[3],range:e,operator:n[1]||n[2]||"",major:i,minor:r,patch:a,parts:[i,r,a],prerelease:n[7],number:1e8*i+1e4*r+a}},i.versionSatisfies=function(e,t){t=t||"*";var n=i.versionParse(t),o=i.versionParse(e);if(n.isRange){if("*"===n.operator||"*"===e)return!0;if(">"===n.operator)return o.number>n.number;if(">="===n.operator)return o.number>=n.number;if("~"===n.operator)return o.major===n.major&&o.minor===n.minor&&o.patch>=n.patch;if("^"===n.operator)return n.major>0?o.major===n.major&&o.number>=n.number:n.minor>0?o.minor===n.minor&&o.patch>=n.patch:o.patch===n.patch}return e===t||"*"===e}},function(e,t,n){var i={};e.exports=i;var o=n(0),r=n(5),a=n(1),s=n(4),l=n(2),c=n(12);!function(){var e,t;"undefined"!=typeof window&&(e=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout((function(){e(o.now())}),1e3/60)},t=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame),i._goodFps=30,i._goodDelta=1e3/60,i.create=function(e){var t={controller:i,engine:null,element:null,canvas:null,mouse:null,frameRequestId:null,timing:{historySize:60,delta:0,deltaHistory:[],lastTime:0,lastTimestamp:0,lastElapsed:0,timestampElapsed:0,timestampElapsedHistory:[],engineDeltaHistory:[],engineElapsedHistory:[],elapsedHistory:[]},options:{width:800,height:600,pixelRatio:1,background:"#14151f",wireframeBackground:"#14151f",hasBounds:!!e.bounds,enabled:!0,wireframes:!0,showSleeping:!0,showDebug:!1,showStats:!1,showPerformance:!1,showBroadphase:!1,showBounds:!1,showVelocity:!1,showCollisions:!1,showSeparations:!1,showAxes:!1,showPositions:!1,showAngleIndicator:!1,showIds:!1,showVertexNumbers:!1,showConvexHulls:!1,showInternalEdges:!1,showMousePosition:!1}},n=o.extend(t,e);return n.canvas&&(n.canvas.width=n.options.width||n.canvas.width,n.canvas.height=n.options.height||n.canvas.height),n.mouse=e.mouse,n.engine=e.engine,n.canvas=n.canvas||d(n.options.width,n.options.height),n.context=n.canvas.getContext("2d"),n.textures={},n.bounds=n.bounds||{min:{x:0,y:0},max:{x:n.canvas.width,y:n.canvas.height}},1!==n.options.pixelRatio&&i.setPixelRatio(n,n.options.pixelRatio),o.isElement(n.element)?n.element.appendChild(n.canvas):n.canvas.parentNode||o.log("Render.create: options.element was undefined, render.canvas was created but not appended","warn"),n},i.run=function(t){!function o(r){t.frameRequestId=e(o),n(t,r),i.world(t,r),(t.options.showStats||t.options.showDebug)&&i.stats(t,t.context,r),(t.options.showPerformance||t.options.showDebug)&&i.performance(t,t.context,r)}()},i.stop=function(e){t(e.frameRequestId)},i.setPixelRatio=function(e,t){var n=e.options,i=e.canvas;"auto"===t&&(t=p(i)),n.pixelRatio=t,i.setAttribute("data-pixel-ratio",t),i.width=n.width*t,i.height=n.height*t,i.style.width=n.width+"px",i.style.height=n.height+"px"},i.lookAt=function(e,t,n,i){i=void 0===i||i,t=o.isArray(t)?t:[t],n=n||{x:0,y:0};for(var r={min:{x:1/0,y:1/0},max:{x:-1/0,y:-1/0}},a=0;ar.max.x&&(r.max.x=u.x),l.yr.max.y&&(r.max.y=u.y))}var d=r.max.x-r.min.x+2*n.x,p=r.max.y-r.min.y+2*n.y,f=e.canvas.height,v=e.canvas.width/f,m=d/p,y=1,g=1;m>v?g=m/v:y=v/m,e.options.hasBounds=!0,e.bounds.min.x=r.min.x,e.bounds.max.x=r.min.x+d*y,e.bounds.min.y=r.min.y,e.bounds.max.y=r.min.y+p*g,i&&(e.bounds.min.x+=.5*d-d*y*.5,e.bounds.max.x+=.5*d-d*y*.5,e.bounds.min.y+=.5*p-p*g*.5,e.bounds.max.y+=.5*p-p*g*.5),e.bounds.min.x-=n.x,e.bounds.max.x-=n.x,e.bounds.min.y-=n.y,e.bounds.max.y-=n.y,e.mouse&&(c.setScale(e.mouse,{x:(e.bounds.max.x-e.bounds.min.x)/e.canvas.width,y:(e.bounds.max.y-e.bounds.min.y)/e.canvas.height}),c.setOffset(e.mouse,e.bounds.min))},i.startViewTransform=function(e){var t=e.bounds.max.x-e.bounds.min.x,n=e.bounds.max.y-e.bounds.min.y,i=t/e.options.width,o=n/e.options.height;e.context.setTransform(e.options.pixelRatio/i,0,0,e.options.pixelRatio/o,0,0),e.context.translate(-e.bounds.min.x,-e.bounds.min.y)},i.endViewTransform=function(e){e.context.setTransform(e.options.pixelRatio,0,0,e.options.pixelRatio,0,0)},i.world=function(e,t){var n,u=o.now(),d=e.engine,p=d.world,f=e.canvas,m=e.context,y=e.options,g=e.timing,x=r.allBodies(p),h=r.allConstraints(p),b=y.wireframes?y.wireframeBackground:y.background,S=[],w=[],A={timestamp:d.timing.timestamp};if(s.trigger(e,"beforeRender",A),e.currentBackground!==b&&v(e,b),m.globalCompositeOperation="source-in",m.fillStyle="transparent",m.fillRect(0,0,f.width,f.height),m.globalCompositeOperation="source-over",y.hasBounds){for(n=0;n1?1:0;a1?1:0;s1?1:0;r1?1:0;s1?1:0;r1?1:0;r1?1:0;o0)){var u=i.activeContacts[0].vertex.x,d=i.activeContacts[0].vertex.y;2===i.activeContacts.length&&(u=(i.activeContacts[0].vertex.x+i.activeContacts[1].vertex.x)/2,d=(i.activeContacts[0].vertex.y+i.activeContacts[1].vertex.y)/2),o.bodyB===o.supports[0].body||!0===o.bodyA.isStatic?s.moveTo(u-8*o.normal.x,d-8*o.normal.y):s.moveTo(u+8*o.normal.x,d+8*o.normal.y),s.lineTo(u,d)}l.wireframes?s.strokeStyle="rgba(255,165,0,0.7)":s.strokeStyle="orange",s.lineWidth=1,s.stroke()},i.separations=function(e,t,n){var i,o,r,a,s,l=n,c=e.options;for(l.beginPath(),s=0;s0&&c.trigger(e,"collisionStart",{pairs:A.collisionStart}),r.preSolvePosition(A.list),m=0;m0&&c.trigger(e,"collisionActive",{pairs:A.collisionActive}),A.collisionEnd.length>0&&c.trigger(e,"collisionEnd",{pairs:A.collisionEnd}),i._bodiesClearForces(b),c.trigger(e,"afterUpdate",h),e.timing.lastElapsed=p.now()-f,e},i.merge=function(e,t){if(p.extend(e,t),t.world){e.world=t.world,i.clear(e);for(var n=u.allBodies(e.world),r=0;rf.friction*f.frictionStatic*L*n&&(O=T,V=a.clamp(f.friction*R*n,-O,O));var F=r.cross(P,g),D=r.cross(M,g),H=b/(m.inverseMass+y.inverseMass+m.inverseInertia*F*F+y.inverseInertia*D*D);if(E*=H,V*=H,k<0&&k*k>i._restingThresh*n)w.normalImpulse=0;else{var j=w.normalImpulse;w.normalImpulse=Math.min(w.normalImpulse+E,0),E=w.normalImpulse-j}if(I*I>i._restingThreshTangent*n)w.tangentImpulse=0;else{var W=w.tangentImpulse;w.tangentImpulse=a.clamp(w.tangentImpulse+V,-O,O),V=w.tangentImpulse-W}o.x=g.x*E+x.x*V,o.y=g.y*E+x.y*V,m.isStatic||m.isSleeping||(m.positionPrev.x+=o.x*m.inverseMass,m.positionPrev.y+=o.y*m.inverseMass,m.anglePrev+=r.cross(P,o)*m.inverseInertia),y.isStatic||y.isSleeping||(y.positionPrev.x-=o.x*y.inverseMass,y.positionPrev.y-=o.y*y.inverseMass,y.anglePrev-=r.cross(M,o)*y.inverseInertia)}}}}},function(e,t,n){var i={};e.exports=i;var o=n(9),r=n(0);i._pairMaxIdleLife=1e3,i.create=function(e){return r.extend({table:{},list:[],collisionStart:[],collisionActive:[],collisionEnd:[]},e)},i.update=function(e,t,n){var i,r,a,s,l=e.list,c=e.table,u=e.collisionStart,d=e.collisionEnd,p=e.collisionActive;for(u.length=0,d.length=0,p.length=0,s=0;si._pairMaxIdleLife&&c.push(a);for(a=0;au.bounds.max.x||f.bounds.max.yu.bounds.max.y))){var v=i._getRegion(e,f);if(!f.region||v.id!==f.region.id||o){f.region&&!o||(f.region=v);var m=i._regionUnion(v,f.region);for(a=m.startCol;a<=m.endCol;a++)for(s=m.startRow;s<=m.endRow;s++){l=d[c=i._getBucketId(a,s)];var y=a>=v.startCol&&a<=v.endCol&&s>=v.startRow&&s<=v.endRow,g=a>=f.region.startCol&&a<=f.region.endCol&&s>=f.region.startRow&&s<=f.region.endRow;!y&&g&&g&&l&&i._bucketRemoveBody(e,l,f),(f.region===v||y&&!g||o)&&(l||(l=i._createBucket(d,c)),i._bucketAddBody(e,l,f))}f.region=v,p=!0}}}p&&(e.pairsList=i._createActivePairsList(e))},i.clear=function(e){e.buckets={},e.pairs={},e.pairsList=[]},i._regionUnion=function(e,t){var n=Math.min(e.startCol,t.startCol),o=Math.max(e.endCol,t.endCol),r=Math.min(e.startRow,t.startRow),a=Math.max(e.endRow,t.endRow);return i._createRegion(n,o,r,a)},i._getRegion=function(e,t){var n=t.bounds,o=Math.floor(n.min.x/e.bucketWidth),r=Math.floor(n.max.x/e.bucketWidth),a=Math.floor(n.min.y/e.bucketHeight),s=Math.floor(n.max.y/e.bucketHeight);return i._createRegion(o,r,a,s)},i._createRegion=function(e,t,n,i){return{id:e+","+t+","+n+","+i,startCol:e,endCol:t,startRow:n,endRow:i}},i._getBucketId=function(e,t){return"C"+e+"R"+t},i._createBucket=function(e,t){return e[t]=[]},i._bucketAddBody=function(e,t,n){for(var i=0;i0?i.push(n):delete e.pairs[t[o]];return i}},function(e,t,n){var i=e.exports=n(23);i.Axes=n(10),i.Bodies=n(11),i.Body=n(6),i.Bounds=n(1),i.Common=n(0),i.Composite=n(5),i.Composites=n(24),i.Constraint=n(8),i.Contact=n(17),i.Detector=n(13),i.Engine=n(18),i.Events=n(4),i.Grid=n(21),i.Mouse=n(12),i.MouseConstraint=n(25),i.Pair=n(9),i.Pairs=n(20),i.Plugin=n(15),i.Query=n(26),i.Render=n(16),i.Resolver=n(19),i.Runner=n(27),i.SAT=n(14),i.Sleeping=n(7),i.Svg=n(28),i.Vector=n(2),i.Vertices=n(3),i.World=n(29),i.Engine.run=i.Runner.run,i.Common.deprecated(i.Engine,"run","Engine.run ➤ use Matter.Runner.run(engine) instead")},function(e,t,n){var i={};e.exports=i;var o=n(15),r=n(0);i.name="matter-js",i.version="0.17.1",i.uses=[],i.used=[],i.use=function(){o.use(i,Array.prototype.slice.call(arguments))},i.before=function(e,t){return e=e.replace(/^Matter./,""),r.chainPathBefore(i,e,t)},i.after=function(e,t){return e=e.replace(/^Matter./,""),r.chainPathAfter(i,e,t)}},function(e,t,n){var i={};e.exports=i;var o=n(5),r=n(8),a=n(0),s=n(6),l=n(11),c=a.deprecated;i.stack=function(e,t,n,i,r,a,l){for(var c,u=o.create({label:"Stack"}),d=e,p=t,f=0,v=0;vm&&(m=x),s.translate(g,{x:.5*h,y:.5*x}),d=g.bounds.max.x+r,o.addBody(u,g),c=g,f+=1}else d+=r}p+=m+a,d=e}return u},i.chain=function(e,t,n,i,s,l){for(var c=e.bodies,u=1;u0)for(c=0;c0&&(p=f[c-1+(l-1)*t],o.addConstraint(e,r.create(a.extend({bodyA:p,bodyB:d},s)))),i&&cp||a<(c=p-c)||a>n-1-c))return 1===d&&s.translate(u,{x:(a+(n%2==1?1:-1))*f,y:0}),l(e+(u?a*f:0)+a*r,i,a,c,u,d)}))},i.newtonsCradle=function(e,t,n,i,a){for(var s=o.create({label:"Newtons Cradle"}),c=0;c1?1:0;ue.deltaMax?e.deltaMax:i)/e.delta,e.delta=i),0!==e.timeScalePrev&&(s*=a.timeScale/e.timeScalePrev),0===a.timeScale&&(s=0),e.timeScalePrev=a.timeScale,e.correction=s,e.frameCounter+=1,n-e.counterTimestamp>=1e3&&(e.fps=e.frameCounter*((n-e.counterTimestamp)/1e3),e.counterTimestamp=n,e.frameCounter=0),o.trigger(e,"tick",l),o.trigger(e,"beforeUpdate",l),r.update(t,i,s),o.trigger(e,"afterUpdate",l),o.trigger(e,"afterTick",l)},i.stop=function(e){t(e.frameRequestId)},i.start=function(e,t){i.run(e,t)}}()},function(e,t,n){var i={};e.exports=i;n(1);var o=n(0);i.pathToVertices=function(e,t){"undefined"==typeof window||"SVGPathSeg"in window||o.warn("Svg.pathToVertices: SVGPathSeg not defined, a polyfill is required.");var n,r,a,s,l,c,u,d,p,f,v,m=[],y=0,g=0,x=0;t=t||15;var h=function(e,t,n){var i=n%2==1&&n>1;if(!p||e!=p.x||t!=p.y){p&&i?(f=p.x,v=p.y):(f=0,v=0);var o={x:f+e,y:v+t};!i&&p||(p=o),m.push(o),g=f+e,x=v+t}},b=function(e){var t=e.pathSegTypeAsLetter.toUpperCase();if("Z"!==t){switch(t){case"M":case"L":case"T":case"C":case"S":case"Q":g=e.x,x=e.y;break;case"H":g=e.x;break;case"V":x=e.y}h(g,x,e.pathSegType)}};for(i._svgPathToAbsolute(e),a=e.getTotalLength(),c=[],n=0;n0;t--){var i=Math.floor(n.random()*(t+1)),o=e[t];e[t]=e[i],e[i]=o}return e},n.choose=function(e){return e[Math.floor(n.random()*e.length)]},n.isElement=function(e){return"undefined"!=typeof HTMLElement?e instanceof HTMLElement:!!(e&&e.nodeType&&e.nodeName)},n.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)},n.isFunction=function(e){return"function"==typeof e},n.isPlainObject=function(e){return"object"==typeof e&&e.constructor===Object},n.isString=function(e){return"[object String]"===toString.call(e)},n.clamp=function(e,t,n){return en?n:e},n.sign=function(e){return e<0?-1:1},n.now=function(){if("undefined"!=typeof window&&window.performance){if(window.performance.now)return window.performance.now();if(window.performance.webkitNow)return window.performance.webkitNow()}return Date.now?Date.now():new Date-n._nowStartTime},n.random=function(t,n){return n=void 0!==n?n:1,(t=void 0!==t?t:0)+e()*(n-t)};var e=function(){return n._seed=(9301*n._seed+49297)%233280,n._seed/233280};n.colorToNumber=function(e){return 3==(e=e.replace("#","")).length&&(e=e.charAt(0)+e.charAt(0)+e.charAt(1)+e.charAt(1)+e.charAt(2)+e.charAt(2)),parseInt(e,16)},n.logLevel=1,n.log=function(){console&&n.logLevel>0&&n.logLevel<=3&&console.log.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.info=function(){console&&n.logLevel>0&&n.logLevel<=2&&console.info.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.warn=function(){console&&n.logLevel>0&&n.logLevel<=3&&console.warn.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.warnOnce=function(){var e=Array.prototype.slice.call(arguments).join(" ");n._warnedOnce[e]||(n.warn(e),n._warnedOnce[e]=!0)},n.deprecated=function(e,t,i){e[t]=n.chain((function(){n.warnOnce("🔅 deprecated 🔅",i)}),e[t])},n.nextId=function(){return n._nextId++},n.indexOf=function(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0;ne.max.x&&(e.max.x=o.x),o.xe.max.y&&(e.max.y=o.y),o.y0?e.max.x+=n.x:e.min.x+=n.x,n.y>0?e.max.y+=n.y:e.min.y+=n.y)},n.contains=function(e,t){return t.x>=e.min.x&&t.x<=e.max.x&&t.y>=e.min.y&&t.y<=e.max.y},n.overlaps=function(e,t){return e.min.x<=t.max.x&&e.max.x>=t.min.x&&e.max.y>=t.min.y&&e.min.y<=t.max.y},n.translate=function(e,t){e.min.x+=t.x,e.max.x+=t.x,e.min.y+=t.y,e.max.y+=t.y},n.shift=function(e,t){var n=e.max.x-e.min.x,i=e.max.y-e.min.y;e.min.x=t.x,e.max.x=t.x+n,e.min.y=t.y,e.max.y=t.y+i}},function(e,t){var n={};e.exports=n,n.create=function(e,t){return{x:e||0,y:t||0}},n.clone=function(e){return{x:e.x,y:e.y}},n.magnitude=function(e){return Math.sqrt(e.x*e.x+e.y*e.y)},n.magnitudeSquared=function(e){return e.x*e.x+e.y*e.y},n.rotate=function(e,t,n){var i=Math.cos(t),o=Math.sin(t);n||(n={});var r=e.x*i-e.y*o;return n.y=e.x*o+e.y*i,n.x=r,n},n.rotateAbout=function(e,t,n,i){var o=Math.cos(t),r=Math.sin(t);i||(i={});var a=n.x+((e.x-n.x)*o-(e.y-n.y)*r);return i.y=n.y+((e.x-n.x)*r+(e.y-n.y)*o),i.x=a,i},n.normalise=function(e){var t=n.magnitude(e);return 0===t?{x:0,y:0}:{x:e.x/t,y:e.y/t}},n.dot=function(e,t){return e.x*t.x+e.y*t.y},n.cross=function(e,t){return e.x*t.y-e.y*t.x},n.cross3=function(e,t,n){return(t.x-e.x)*(n.y-e.y)-(t.y-e.y)*(n.x-e.x)},n.add=function(e,t,n){return n||(n={}),n.x=e.x+t.x,n.y=e.y+t.y,n},n.sub=function(e,t,n){return n||(n={}),n.x=e.x-t.x,n.y=e.y-t.y,n},n.mult=function(e,t){return{x:e.x*t,y:e.y*t}},n.div=function(e,t){return{x:e.x/t,y:e.y/t}},n.perp=function(e,t){return{x:(t=!0===t?-1:1)*-e.y,y:t*e.x}},n.neg=function(e){return{x:-e.x,y:-e.y}},n.angle=function(e,t){return Math.atan2(t.y-e.y,t.x-e.x)},n._temp=[n.create(),n.create(),n.create(),n.create(),n.create(),n.create()]},function(e,t,n){var i={};e.exports=i;var o=n(2),r=n(0);i.create=function(e,t){for(var n=[],i=0;i0)return!1;a=n}return!0},i.scale=function(e,t,n,r){if(1===t&&1===n)return e;var a,s;r=r||i.centre(e);for(var l=0;l=0?l-1:e.length-1],u=e[l],d=e[(l+1)%e.length],p=t[l0&&(r|=2),3===r)return!1;return 0!==r||null},i.hull=function(e){var t,n,i=[],r=[];for((e=e.slice(0)).sort((function(e,t){var n=e.x-t.x;return 0!==n?n:e.y-t.y})),n=0;n=2&&o.cross3(r[r.length-2],r[r.length-1],t)<=0;)r.pop();r.push(t)}for(n=e.length-1;n>=0;n-=1){for(t=e[n];i.length>=2&&o.cross3(i[i.length-2],i[i.length-1],t)<=0;)i.pop();i.push(t)}return i.pop(),r.pop(),i.concat(r)}},function(e,t,n){var i={};e.exports=i;var o=n(0);i.on=function(e,t,n){for(var i,o=t.split(" "),r=0;r0){n||(n={}),i=t.split(" ");for(var c=0;c0&&r.rotateAbout(a.position,n,e.position,a.position)}},i.setVelocity=function(e,t){e.positionPrev.x=e.position.x-t.x,e.positionPrev.y=e.position.y-t.y,e.velocity.x=t.x,e.velocity.y=t.y,e.speed=r.magnitude(e.velocity)},i.setAngularVelocity=function(e,t){e.anglePrev=e.angle-t,e.angularVelocity=t,e.angularSpeed=Math.abs(e.angularVelocity)},i.translate=function(e,t){i.setPosition(e,r.add(e.position,t))},i.rotate=function(e,t,n){if(n){var o=Math.cos(t),r=Math.sin(t),a=e.position.x-n.x,s=e.position.y-n.y;i.setPosition(e,{x:n.x+(a*o-s*r),y:n.y+(a*r+s*o)}),i.setAngle(e,e.angle+t)}else i.setAngle(e,e.angle+t)},i.scale=function(e,t,n,r){var a=0,s=0;r=r||e.position;for(var u=0;u0&&(a+=d.area,s+=d.inertia),d.position.x=r.x+(d.position.x-r.x)*t,d.position.y=r.y+(d.position.y-r.y)*n,l.update(d.bounds,d.vertices,e.velocity)}e.parts.length>1&&(e.area=a,e.isStatic||(i.setMass(e,e.density*a),i.setInertia(e,s))),e.circleRadius&&(t===n?e.circleRadius*=t:e.circleRadius=null)},i.update=function(e,t,n,i){var a=Math.pow(t*n*e.timeScale,2),s=1-e.frictionAir*n*e.timeScale,u=e.position.x-e.positionPrev.x,d=e.position.y-e.positionPrev.y;e.velocity.x=u*s*i+e.force.x/e.mass*a,e.velocity.y=d*s*i+e.force.y/e.mass*a,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.position.x+=e.velocity.x,e.position.y+=e.velocity.y,e.angularVelocity=(e.angle-e.anglePrev)*s*i+e.torque/e.inertia*a,e.anglePrev=e.angle,e.angle+=e.angularVelocity,e.speed=r.magnitude(e.velocity),e.angularSpeed=Math.abs(e.angularVelocity);for(var p=0;p0&&(f.position.x+=e.velocity.x,f.position.y+=e.velocity.y),0!==e.angularVelocity&&(o.rotate(f.vertices,e.angularVelocity,e.position),c.rotate(f.axes,e.angularVelocity),p>0&&r.rotateAbout(f.position,e.angularVelocity,e.position,f.position)),l.update(f.bounds,f.vertices,e.velocity)}},i.applyForce=function(e,t,n){e.force.x+=n.x,e.force.y+=n.y;var i=t.x-e.position.x,o=t.y-e.position.y;e.torque+=i*n.y-o*n.x},i._totalProperties=function(e){for(var t={mass:0,area:0,inertia:0,centre:{x:0,y:0}},n=1===e.parts.length?0:1;n0&&r.motion=r.sleepThreshold&&i.set(r,!0)):r.sleepCounter>0&&(r.sleepCounter-=1)}else i.set(r,!1)}},i.afterCollisions=function(e,t){for(var n=t*t*t,o=0;oi._motionWakeThreshold*n&&i.set(c,!1)}}}},i.set=function(e,t){var n=e.isSleeping;t?(e.isSleeping=!0,e.sleepCounter=e.sleepThreshold,e.positionImpulse.x=0,e.positionImpulse.y=0,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.anglePrev=e.angle,e.speed=0,e.angularSpeed=0,e.motion=0,n||o.trigger(e,"sleepStart")):(e.isSleeping=!1,e.sleepCounter=0,n&&o.trigger(e,"sleepEnd"))}},function(e,t,n){var i={};e.exports=i;var o,r,a,s=n(3),l=n(9);o=[],r={overlap:0,axis:null},a={overlap:0,axis:null},i.create=function(e,t){return{pair:null,collided:!1,bodyA:e,bodyB:t,parentA:e.parent,parentB:t.parent,depth:0,normal:{x:0,y:0},tangent:{x:0,y:0},penetration:{x:0,y:0},supports:[]}},i.collides=function(e,t,n){if(i._overlapAxes(r,e.vertices,t.vertices,e.axes),r.overlap<=0)return null;if(i._overlapAxes(a,t.vertices,e.vertices,t.axes),a.overlap<=0)return null;var o,c,u=n&&n.table[l.id(e,t)];u?o=u.collision:((o=i.create(e,t)).collided=!0,o.bodyA=e.idP?P=s:sC?C=s:so?o=a:al.frictionStatic?s.frictionStatic:l.frictionStatic,e.restitution=s.restitution>l.restitution?s.restitution:l.restitution,e.slop=s.slop>l.slop?s.slop:l.slop,t.pair=e,a.length=0;for(var u=0;u0?1:.7),t.damping=t.damping||0,t.angularStiffness=t.angularStiffness||0,t.angleA=t.bodyA?t.bodyA.angle:t.angleA,t.angleB=t.bodyB?t.bodyB.angle:t.angleB,t.plugin={};var a={visible:!0,lineWidth:2,strokeStyle:"#ffffff",type:"line",anchors:!0};return 0===t.length&&t.stiffness>.1?(a.type="pin",a.anchors=!1):t.stiffness<.9&&(a.type="spring"),t.render=c.extend(a,t.render),t},i.preSolveAll=function(e){for(var t=0;t0&&(d.position.x+=c.x,d.position.y+=c.y),0!==c.angle&&(o.rotate(d.vertices,c.angle,n.position),l.rotate(d.axes,c.angle),u>0&&r.rotateAbout(d.position,c.angle,n.position,d.position)),s.update(d.bounds,d.vertices,n.velocity)}c.angle*=i._warming,c.x*=i._warming,c.y*=i._warming}}},i.pointAWorld=function(e){return{x:(e.bodyA?e.bodyA.position.x:0)+e.pointA.x,y:(e.bodyA?e.bodyA.position.y:0)+e.pointA.y}},i.pointBWorld=function(e){return{x:(e.bodyB?e.bodyB.position.x:0)+e.pointB.x,y:(e.bodyB?e.bodyB.position.y:0)+e.pointB.y}}},function(e,t,n){var i={};e.exports=i;var o=n(2),r=n(0);i.fromVertices=function(e){for(var t={},n=0;n0&&o.area(M)1?(v=a.create(r.extend({parts:y.slice(0)},i)),a.setPosition(v,{x:e,y:t}),v):y[0]}},function(e,t,n){var i={};e.exports=i;var o=n(0);i.create=function(e){var t={};return e||o.log("Mouse.create: element was undefined, defaulting to document.body","warn"),t.element=e||document.body,t.absolute={x:0,y:0},t.position={x:0,y:0},t.mousedownPosition={x:0,y:0},t.mouseupPosition={x:0,y:0},t.offset={x:0,y:0},t.scale={x:1,y:1},t.wheelDelta=0,t.button=-1,t.pixelRatio=parseInt(t.element.getAttribute("data-pixel-ratio"),10)||1,t.sourceEvents={mousemove:null,mousedown:null,mouseup:null,mousewheel:null},t.mousemove=function(e){var n=i._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches&&(t.button=0,e.preventDefault()),t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.sourceEvents.mousemove=e},t.mousedown=function(e){var n=i._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches?(t.button=0,e.preventDefault()):t.button=e.button,t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.mousedownPosition.x=t.position.x,t.mousedownPosition.y=t.position.y,t.sourceEvents.mousedown=e},t.mouseup=function(e){var n=i._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches&&e.preventDefault(),t.button=-1,t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.mouseupPosition.x=t.position.x,t.mouseupPosition.y=t.position.y,t.sourceEvents.mouseup=e},t.mousewheel=function(e){t.wheelDelta=Math.max(-1,Math.min(1,e.wheelDelta||-e.detail)),e.preventDefault()},i.setElement(t,t.element),t},i.setElement=function(e,t){e.element=t,t.addEventListener("mousemove",e.mousemove),t.addEventListener("mousedown",e.mousedown),t.addEventListener("mouseup",e.mouseup),t.addEventListener("mousewheel",e.mousewheel),t.addEventListener("DOMMouseScroll",e.mousewheel),t.addEventListener("touchmove",e.mousemove),t.addEventListener("touchstart",e.mousedown),t.addEventListener("touchend",e.mouseup)},i.clearSourceEvents=function(e){e.sourceEvents.mousemove=null,e.sourceEvents.mousedown=null,e.sourceEvents.mouseup=null,e.sourceEvents.mousewheel=null,e.wheelDelta=0},i.setOffset=function(e,t){e.offset.x=t.x,e.offset.y=t.y,e.position.x=e.absolute.x*e.scale.x+e.offset.x,e.position.y=e.absolute.y*e.scale.y+e.offset.y},i.setScale=function(e,t){e.scale.x=t.x,e.scale.y=t.y,e.position.x=e.absolute.x*e.scale.x+e.offset.x,e.position.y=e.absolute.y*e.scale.y+e.offset.y},i._getRelativeMousePosition=function(e,t,n){var i,o,r=t.getBoundingClientRect(),a=document.documentElement||document.body.parentNode||document.body,s=void 0!==window.pageXOffset?window.pageXOffset:a.scrollLeft,l=void 0!==window.pageYOffset?window.pageYOffset:a.scrollTop,c=e.changedTouches;return c?(i=c[0].pageX-r.left-s,o=c[0].pageY-r.top-l):(i=e.pageX-r.left-s,o=e.pageY-r.top-l),{x:i/(t.clientWidth/(t.width||t.clientWidth)*n),y:o/(t.clientHeight/(t.height||t.clientHeight)*n)}}},function(e,t,n){var i={};e.exports=i;var o=n(0),r=n(8);i.create=function(e){return o.extend({bodies:[],pairs:null},e)},i.setBodies=function(e,t){e.bodies=t.slice(0)},i.clear=function(e){e.bodies=[]},i.collisions=function(e){var t,n,o=[],a=e.pairs,s=e.bodies,l=s.length,c=i.canCollide,u=r.collides;for(s.sort(i._compareBoundsX),t=0;tf)break;if(!(vB.max.y)&&(!m||!h.isStatic&&!h.isSleeping)&&c(d.collisionFilter,h.collisionFilter)){var b=h.parts.length;if(x&&1===b)(C=u(d,h,a))&&o.push(C);else for(var S=b>1?1:0,w=g>1?1:0;wB.max.x||p.max.xB.max.y||(C=u(A,M,a))&&o.push(C)}}}}return o},i.canCollide=function(e,t){return e.group===t.group&&0!==e.group?e.group>0:0!=(e.mask&t.category)&&0!=(t.mask&e.category)},i._compareBoundsX=function(e,t){return e.bounds.min.x-t.bounds.min.x}},function(e,t,n){var i={};e.exports=i;var o=n(0);i._registry={},i.register=function(e){if(i.isPlugin(e)||o.warn("Plugin.register:",i.toString(e),"does not implement all required fields."),e.name in i._registry){var t=i._registry[e.name],n=i.versionParse(e.version).number,r=i.versionParse(t.version).number;n>r?(o.warn("Plugin.register:",i.toString(t),"was upgraded to",i.toString(e)),i._registry[e.name]=e):n-1},i.isFor=function(e,t){var n=e.for&&i.dependencyParse(e.for);return!e.for||t.name===n.name&&i.versionSatisfies(t.version,n.range)},i.use=function(e,t){if(e.uses=(e.uses||[]).concat(t||[]),0!==e.uses.length){for(var n=i.dependencies(e),r=o.topologicalSort(n),a=[],s=0;s0&&o.info(a.join(" "))}else o.warn("Plugin.use:",i.toString(e),"does not specify any dependencies to install.")},i.dependencies=function(e,t){var n=i.dependencyParse(e),r=n.name;if(!(r in(t=t||{}))){e=i.resolve(e)||e,t[r]=o.map(e.uses||[],(function(t){i.isPlugin(t)&&i.register(t);var r=i.dependencyParse(t),a=i.resolve(t);return a&&!i.versionSatisfies(a.version,r.range)?(o.warn("Plugin.dependencies:",i.toString(a),"does not satisfy",i.toString(r),"used by",i.toString(n)+"."),a._warned=!0,e._warned=!0):a||(o.warn("Plugin.dependencies:",i.toString(t),"used by",i.toString(n),"could not be resolved."),e._warned=!0),r.name}));for(var a=0;a=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-+]+)?$/;t.test(e)||o.warn("Plugin.versionParse:",e,"is not a valid version or range.");var n=t.exec(e),i=Number(n[4]),r=Number(n[5]),a=Number(n[6]);return{isRange:Boolean(n[1]||n[2]),version:n[3],range:e,operator:n[1]||n[2]||"",major:i,minor:r,patch:a,parts:[i,r,a],prerelease:n[7],number:1e8*i+1e4*r+a}},i.versionSatisfies=function(e,t){t=t||"*";var n=i.versionParse(t),o=i.versionParse(e);if(n.isRange){if("*"===n.operator||"*"===e)return!0;if(">"===n.operator)return o.number>n.number;if(">="===n.operator)return o.number>=n.number;if("~"===n.operator)return o.major===n.major&&o.minor===n.minor&&o.patch>=n.patch;if("^"===n.operator)return n.major>0?o.major===n.major&&o.number>=n.number:n.minor>0?o.minor===n.minor&&o.patch>=n.patch:o.patch===n.patch}return e===t||"*"===e}},function(e,t,n){var i={};e.exports=i;var o=n(0),r=n(5),a=n(1),s=n(4),l=n(2),c=n(13);!function(){var e,t;"undefined"!=typeof window&&(e=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout((function(){e(o.now())}),1e3/60)},t=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame),i._goodFps=30,i._goodDelta=1e3/60,i.create=function(e){var t={controller:i,engine:null,element:null,canvas:null,mouse:null,frameRequestId:null,timing:{historySize:60,delta:0,deltaHistory:[],lastTime:0,lastTimestamp:0,lastElapsed:0,timestampElapsed:0,timestampElapsedHistory:[],engineDeltaHistory:[],engineElapsedHistory:[],elapsedHistory:[]},options:{width:800,height:600,pixelRatio:1,background:"#14151f",wireframeBackground:"#14151f",hasBounds:!!e.bounds,enabled:!0,wireframes:!0,showSleeping:!0,showDebug:!1,showStats:!1,showPerformance:!1,showBounds:!1,showVelocity:!1,showCollisions:!1,showSeparations:!1,showAxes:!1,showPositions:!1,showAngleIndicator:!1,showIds:!1,showVertexNumbers:!1,showConvexHulls:!1,showInternalEdges:!1,showMousePosition:!1}},n=o.extend(t,e);return n.canvas&&(n.canvas.width=n.options.width||n.canvas.width,n.canvas.height=n.options.height||n.canvas.height),n.mouse=e.mouse,n.engine=e.engine,n.canvas=n.canvas||d(n.options.width,n.options.height),n.context=n.canvas.getContext("2d"),n.textures={},n.bounds=n.bounds||{min:{x:0,y:0},max:{x:n.canvas.width,y:n.canvas.height}},n.options.showBroadphase=!1,1!==n.options.pixelRatio&&i.setPixelRatio(n,n.options.pixelRatio),o.isElement(n.element)?n.element.appendChild(n.canvas):n.canvas.parentNode||o.log("Render.create: options.element was undefined, render.canvas was created but not appended","warn"),n},i.run=function(t){!function o(r){t.frameRequestId=e(o),n(t,r),i.world(t,r),(t.options.showStats||t.options.showDebug)&&i.stats(t,t.context,r),(t.options.showPerformance||t.options.showDebug)&&i.performance(t,t.context,r)}()},i.stop=function(e){t(e.frameRequestId)},i.setPixelRatio=function(e,t){var n=e.options,i=e.canvas;"auto"===t&&(t=p(i)),n.pixelRatio=t,i.setAttribute("data-pixel-ratio",t),i.width=n.width*t,i.height=n.height*t,i.style.width=n.width+"px",i.style.height=n.height+"px"},i.lookAt=function(e,t,n,i){i=void 0===i||i,t=o.isArray(t)?t:[t],n=n||{x:0,y:0};for(var r={min:{x:1/0,y:1/0},max:{x:-1/0,y:-1/0}},a=0;ar.max.x&&(r.max.x=u.x),l.yr.max.y&&(r.max.y=u.y))}var d=r.max.x-r.min.x+2*n.x,p=r.max.y-r.min.y+2*n.y,f=e.canvas.height,v=e.canvas.width/f,y=d/p,m=1,g=1;y>v?g=y/v:m=v/y,e.options.hasBounds=!0,e.bounds.min.x=r.min.x,e.bounds.max.x=r.min.x+d*m,e.bounds.min.y=r.min.y,e.bounds.max.y=r.min.y+p*g,i&&(e.bounds.min.x+=.5*d-d*m*.5,e.bounds.max.x+=.5*d-d*m*.5,e.bounds.min.y+=.5*p-p*g*.5,e.bounds.max.y+=.5*p-p*g*.5),e.bounds.min.x-=n.x,e.bounds.max.x-=n.x,e.bounds.min.y-=n.y,e.bounds.max.y-=n.y,e.mouse&&(c.setScale(e.mouse,{x:(e.bounds.max.x-e.bounds.min.x)/e.canvas.width,y:(e.bounds.max.y-e.bounds.min.y)/e.canvas.height}),c.setOffset(e.mouse,e.bounds.min))},i.startViewTransform=function(e){var t=e.bounds.max.x-e.bounds.min.x,n=e.bounds.max.y-e.bounds.min.y,i=t/e.options.width,o=n/e.options.height;e.context.setTransform(e.options.pixelRatio/i,0,0,e.options.pixelRatio/o,0,0),e.context.translate(-e.bounds.min.x,-e.bounds.min.y)},i.endViewTransform=function(e){e.context.setTransform(e.options.pixelRatio,0,0,e.options.pixelRatio,0,0)},i.world=function(e,t){var n,u=o.now(),d=e.engine,p=d.world,f=e.canvas,y=e.context,m=e.options,g=e.timing,x=r.allBodies(p),h=r.allConstraints(p),b=m.wireframes?m.wireframeBackground:m.background,S=[],w=[],A={timestamp:d.timing.timestamp};if(s.trigger(e,"beforeRender",A),e.currentBackground!==b&&v(e,b),y.globalCompositeOperation="source-in",y.fillStyle="transparent",y.fillRect(0,0,f.width,f.height),y.globalCompositeOperation="source-over",m.hasBounds){for(n=0;n1?1:0;a1?1:0;s1?1:0;r1?1:0;s1?1:0;r1?1:0;r1?1:0;o0)){var u=i.activeContacts[0].vertex.x,d=i.activeContacts[0].vertex.y;2===i.activeContacts.length&&(u=(i.activeContacts[0].vertex.x+i.activeContacts[1].vertex.x)/2,d=(i.activeContacts[0].vertex.y+i.activeContacts[1].vertex.y)/2),o.bodyB===o.supports[0].body||!0===o.bodyA.isStatic?s.moveTo(u-8*o.normal.x,d-8*o.normal.y):s.moveTo(u+8*o.normal.x,d+8*o.normal.y),s.lineTo(u,d)}l.wireframes?s.strokeStyle="rgba(255,165,0,0.7)":s.strokeStyle="orange",s.lineWidth=1,s.stroke()},i.separations=function(e,t,n){var i,o,r,a,s,l=n,c=e.options;for(l.beginPath(),s=0;s0&&l.trigger(e,"collisionStart",{pairs:m.collisionStart}),r.preSolvePosition(m.list),f=0;f0&&l.trigger(e,"collisionActive",{pairs:m.collisionActive}),m.collisionEnd.length>0&&l.trigger(e,"collisionEnd",{pairs:m.collisionEnd}),i._bodiesClearForces(b),l.trigger(e,"afterUpdate",h),e.timing.lastElapsed=d.now()-p,e},i.merge=function(e,t){if(d.extend(e,t),t.world){e.world=t.world,i.clear(e);for(var n=c.allBodies(e.world),r=0;rW||-H>W?(o=H>0?H:-H,(n=f.friction*(H>0?1:-1)*s)<-o?n=-o:n>o&&(n=o)):(n=H,o=d);var G=I*b-T*h,N=R*b-E*h,U=C/(M+y.inverseInertia*G*G+m.inverseInertia*N*N),z=(1+f.restitution)*F*U;if(n*=U,F*F>l&&F<0)k.normalImpulse=0;else{var X=k.normalImpulse;k.normalImpulse+=z,k.normalImpulse=Math.min(k.normalImpulse,0),z=k.normalImpulse-X}if(H*H>u)k.tangentImpulse=0;else{var Q=k.tangentImpulse;k.tangentImpulse+=n,k.tangentImpulse<-o&&(k.tangentImpulse=-o),k.tangentImpulse>o&&(k.tangentImpulse=o),n=k.tangentImpulse-Q}var Y=h*z+S*n,Z=b*z+w*n;y.isStatic||y.isSleeping||(y.positionPrev.x+=Y*y.inverseMass,y.positionPrev.y+=Z*y.inverseMass,y.anglePrev+=(I*Z-T*Y)*y.inverseInertia),m.isStatic||m.isSleeping||(m.positionPrev.x-=Y*m.inverseMass,m.positionPrev.y-=Z*m.inverseMass,m.anglePrev-=(R*Z-E*Y)*m.inverseInertia)}}}}},function(e,t,n){var i={};e.exports=i;var o=n(9),r=n(0);i.create=function(e){return r.extend({table:{},list:[],collisionStart:[],collisionActive:[],collisionEnd:[]},e)},i.update=function(e,t,n){var i,r,a,s,l=e.list,c=l.length,u=e.table,d=t.length,p=e.collisionStart,f=e.collisionEnd,v=e.collisionActive;for(p.length=0,f.length=0,v.length=0,s=0;sy&&(y=x),s.translate(g,{x:.5*h,y:.5*x}),d=g.bounds.max.x+r,o.addBody(u,g),c=g,f+=1}else d+=r}p+=y+a,d=e}return u},i.chain=function(e,t,n,i,s,l){for(var c=e.bodies,u=1;u0)for(c=0;c0&&(p=f[c-1+(l-1)*t],o.addConstraint(e,r.create(a.extend({bodyA:p,bodyB:d},s)))),i&&cp||a<(c=p-c)||a>n-1-c))return 1===d&&s.translate(u,{x:(a+(n%2==1?1:-1))*f,y:0}),l(e+(u?a*f:0)+a*r,i,a,c,u,d)}))},i.newtonsCradle=function(e,t,n,i,a){for(var s=o.create({label:"Newtons Cradle"}),c=0;cu.bounds.max.x||f.bounds.max.yu.bounds.max.y))){var v=i._getRegion(e,f);if(!f.region||v.id!==f.region.id||o){f.region&&!o||(f.region=v);var y=i._regionUnion(v,f.region);for(a=y.startCol;a<=y.endCol;a++)for(s=y.startRow;s<=y.endRow;s++){l=d[c=i._getBucketId(a,s)];var m=a>=v.startCol&&a<=v.endCol&&s>=v.startRow&&s<=v.endRow,g=a>=f.region.startCol&&a<=f.region.endCol&&s>=f.region.startRow&&s<=f.region.endRow;!m&&g&&g&&l&&i._bucketRemoveBody(e,l,f),(f.region===v||m&&!g||o)&&(l||(l=i._createBucket(d,c)),i._bucketAddBody(e,l,f))}f.region=v,p=!0}}}p&&(e.pairsList=i._createActivePairsList(e))},a(i,"update","Grid.update ➤ replaced by Matter.Detector"),i.clear=function(e){e.buckets={},e.pairs={},e.pairsList=[]},a(i,"clear","Grid.clear ➤ replaced by Matter.Detector"),i._regionUnion=function(e,t){var n=Math.min(e.startCol,t.startCol),o=Math.max(e.endCol,t.endCol),r=Math.min(e.startRow,t.startRow),a=Math.max(e.endRow,t.endRow);return i._createRegion(n,o,r,a)},i._getRegion=function(e,t){var n=t.bounds,o=Math.floor(n.min.x/e.bucketWidth),r=Math.floor(n.max.x/e.bucketWidth),a=Math.floor(n.min.y/e.bucketHeight),s=Math.floor(n.max.y/e.bucketHeight);return i._createRegion(o,r,a,s)},i._createRegion=function(e,t,n,i){return{id:e+","+t+","+n+","+i,startCol:e,endCol:t,startRow:n,endRow:i}},i._getBucketId=function(e,t){return"C"+e+"R"+t},i._createBucket=function(e,t){return e[t]=[]},i._bucketAddBody=function(e,t,n){var i,r=e.pairs,a=o.id,s=t.length;for(i=0;i0?s.push(t):delete i[o[n]];return s}},function(e,t,n){var i={};e.exports=i;var o=n(3),r=n(7),a=n(13),s=n(4),l=n(14),c=n(10),u=n(5),d=n(0),p=n(1);i.create=function(e,t){var n=(e?e.mouse:null)||(t?t.mouse:null);n||(e&&e.render&&e.render.canvas?n=a.create(e.render.canvas):t&&t.element?n=a.create(t.element):(n=a.create(),d.warn("MouseConstraint.create: options.mouse was undefined, options.element was undefined, may not function as expected")));var o={type:"mouseConstraint",mouse:n,element:null,body:null,constraint:c.create({label:"Mouse Constraint",pointA:n.position,pointB:{x:0,y:0},length:.01,stiffness:.1,angularStiffness:1,render:{strokeStyle:"#90EE90",lineWidth:3}}),collisionFilter:{category:1,mask:4294967295,group:0}},r=d.extend(o,t);return s.on(e,"beforeUpdate",(function(){var t=u.allBodies(e.world);i.update(r,t),i._triggerEvents(r)})),r},i.update=function(e,t){var n=e.mouse,i=e.constraint,a=e.body;if(0===n.button){if(i.bodyB)r.set(i.bodyB,!1),i.pointA=n.position;else for(var c=0;c1?1:0;ue.deltaMax?e.deltaMax:i)/e.delta,e.delta=i),0!==e.timeScalePrev&&(s*=a.timeScale/e.timeScalePrev),0===a.timeScale&&(s=0),e.timeScalePrev=a.timeScale,e.correction=s,e.frameCounter+=1,n-e.counterTimestamp>=1e3&&(e.fps=e.frameCounter*((n-e.counterTimestamp)/1e3),e.counterTimestamp=n,e.frameCounter=0),o.trigger(e,"tick",l),o.trigger(e,"beforeUpdate",l),r.update(t,i,s),o.trigger(e,"afterUpdate",l),o.trigger(e,"afterTick",l)},i.stop=function(e){t(e.frameRequestId)},i.start=function(e,t){i.run(e,t)}}()},function(e,t,n){var i={};e.exports=i;var o=n(8),r=n(0).deprecated;i.collides=function(e,t){return o.collides(e,t)},r(i,"collides","SAT.collides ➤ replaced by Collision.collides")},function(e,t,n){var i={};e.exports=i;n(1);var o=n(0);i.pathToVertices=function(e,t){"undefined"==typeof window||"SVGPathSeg"in window||o.warn("Svg.pathToVertices: SVGPathSeg not defined, a polyfill is required.");var n,r,a,s,l,c,u,d,p,f,v,y=[],m=0,g=0,x=0;t=t||15;var h=function(e,t,n){var i=n%2==1&&n>1;if(!p||e!=p.x||t!=p.y){p&&i?(f=p.x,v=p.y):(f=0,v=0);var o={x:f+e,y:v+t};!i&&p||(p=o),y.push(o),g=f+e,x=v+t}},b=function(e){var t=e.pathSegTypeAsLetter.toUpperCase();if("Z"!==t){switch(t){case"M":case"L":case"T":case"C":case"S":case"Q":g=e.x,x=e.y;break;case"H":g=e.x;break;case"V":x=e.y}h(g,x,e.pathSegType)}};for(i._svgPathToAbsolute(e),a=e.getTotalLength(),c=[],n=0;n - - - - - - + + + + + + \ No newline at end of file diff --git a/demo/js/matter-demo.a280d3.min.js b/demo/js/matter-demo.a280d3.min.js new file mode 100644 index 0000000..584548f --- /dev/null +++ b/demo/js/matter-demo.a280d3.min.js @@ -0,0 +1,6 @@ +/*! + * matter-demo bundle 0.18.0 by @liabru + * http://brm.io/matter-js/ + * License MIT + */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("MatterDemo",[],t):"object"==typeof exports?exports.MatterDemo=t():e.MatterDemo=t()}(this,(function(){return(this.webpackJsonpMatterDemo=this.webpackJsonpMatterDemo||[]).push([[0],{"+QOk":function(e,t,n){var r=r||{};r.timescale=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Events,i=Matter.Composite,a=Matter.Composites,s=Matter.Common,c=Matter.MouseConstraint,l=Matter.Mouse,d=Matter.Bodies,u=e.create(),p=u.world,m=t.create({element:document.body,engine:u,options:{width:800,height:600,showAngleIndicator:!0}});t.run(m);var f=n.create();n.run(f,u),i.add(p,[d.rectangle(400,0,800,50,{isStatic:!0}),d.rectangle(400,600,800,50,{isStatic:!0}),d.rectangle(800,300,50,600,{isStatic:!0}),d.rectangle(0,300,50,600,{isStatic:!0})]);var v=1,g=0;o.on(u,"afterUpdate",(function(e){u.timing.timeScale+=.05*(v-u.timing.timeScale),(g+=1)>=90&&(v=v<1?1:.05,function(e){for(var t=i.allBodies(e.world),n=0;n=500){var a=.05*o.mass;r.applyForce(o,o.position,{x:(a+s.random()*a)*s.choose([1,-1]),y:-a+s.random()*-a})}}}(u),g=0)}));var y={frictionAir:0,friction:1e-4,restitution:.8};i.add(p,a.stack(20,100,15,3,20,40,(function(e,t){return d.circle(e,t,s.random(10,20),y)}))),i.add(p,a.stack(50,50,8,3,0,0,(function(e,t){switch(Math.round(s.random(0,1))){case 0:return s.random()<.8?d.rectangle(e,t,s.random(20,50),s.random(20,50),y):d.rectangle(e,t,s.random(80,120),s.random(20,30),y);case 1:return d.polygon(e,t,Math.round(s.random(4,8)),s.random(20,50),y)}})));var x=l.create(m.canvas),h=c.create(u,{mouse:x,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(p,h),m.mouse=x,t.lookAt(m,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:u,runner:f,render:m,canvas:m.canvas,stop:function(){Matter.Render.stop(m),Matter.Runner.stop(f)}}},r.timescale.title="Time Scaling",r.timescale.for=">=0.14.2",e.exports=r.timescale},"+jwT":function(e,t,n){var r=r||{};r.constraints=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=(Matter.Composites,Matter.Common,Matter.Constraint),o=Matter.MouseConstraint,i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showAngleIndicator:!0}});t.run(d);var u=n.create();n.run(u,c);var p=s.polygon(150,200,5,30),m=r.create({pointA:{x:150,y:100},bodyB:p,pointB:{x:-10,y:-10}});a.add(l,[p,m]);p=s.polygon(280,100,3,30),m=r.create({pointA:{x:280,y:120},bodyB:p,pointB:{x:-10,y:-7},stiffness:.001});a.add(l,[p,m]);p=s.polygon(400,100,4,30),m=r.create({pointA:{x:400,y:120},bodyB:p,pointB:{x:-10,y:-10},stiffness:.001,damping:.05});a.add(l,[p,m]);p=s.rectangle(600,200,200,20);var f=s.circle(550,150,20);m=r.create({pointA:{x:600,y:200},bodyB:p,length:0});a.add(l,[p,f,m]);p=s.rectangle(500,400,100,20,{collisionFilter:{group:-1}}),f=s.circle(600,400,20,{collisionFilter:{group:-1}}),m=r.create({bodyA:p,bodyB:f});a.add(l,[p,f,m]);var v=s.polygon(100,400,6,20),g=s.polygon(200,400,1,50);m=r.create({bodyA:v,pointA:{x:-10,y:-10},bodyB:g,pointB:{x:-10,y:-10}});a.add(l,[v,g,m]);v=s.polygon(300,400,4,20),g=s.polygon(400,400,3,30),m=r.create({bodyA:v,pointA:{x:-10,y:-10},bodyB:g,pointB:{x:-10,y:-7},stiffness:.001});a.add(l,[v,g,m]);v=s.polygon(500,400,6,30),g=s.polygon(600,400,7,60),m=r.create({bodyA:v,pointA:{x:-10,y:-10},bodyB:g,pointB:{x:-10,y:-10},stiffness:.001,damping:.1});a.add(l,[v,g,m]),a.add(l,[s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(400,600,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0})]);var y=i.create(d.canvas),x=o.create(c,{mouse:y,constraint:{angularStiffness:0,render:{visible:!1}}});return a.add(l,x),d.mouse=y,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.constraints.title="Constraints",r.constraints.for=">=0.14.2",e.exports=r.constraints},"/iAh":function(e,t,n){var r=r||{};r.svg=function(){var e=Matter.Engine,t=Matter.Render,r=Matter.Runner,o=Matter.Common,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Vertices,l=Matter.Svg,d=Matter.Bodies;o.setDecomp(n("Dded"));var u=e.create(),p=u.world,m=t.create({element:document.body,engine:u,options:{width:800,height:600}});t.run(m);var f=r.create();if(r.run(f,u),"undefined"!=typeof fetch){var v=function(e,t){return Array.prototype.slice.call(e.querySelectorAll(t))},g=function(e){return fetch(e).then((function(e){return e.text()})).then((function(e){return(new window.DOMParser).parseFromString(e,"image/svg+xml")}))};["./svg/iconmonstr-check-mark-8-icon.svg","./svg/iconmonstr-paperclip-2-icon.svg","./svg/iconmonstr-puzzle-icon.svg","./svg/iconmonstr-user-icon.svg"].forEach((function(e,t){g(e).then((function(e){var n=o.choose(["#f19648","#f5d259","#f55a3c","#063e7b","#ececd1"]),r=v(e,"path").map((function(e){return c.scale(l.pathToVertices(e,30),.4,.4)}));s.add(p,d.fromVertices(100+150*t,200+50*t,r,{render:{fillStyle:n,strokeStyle:n,lineWidth:1}},!0))}))})),g("./svg/svg.svg").then((function(e){var t=o.choose(["#f19648","#f5d259","#f55a3c","#063e7b","#ececd1"]),n=v(e,"path").map((function(e){return l.pathToVertices(e,30)}));s.add(p,d.fromVertices(400,80,n,{render:{fillStyle:t,strokeStyle:t,lineWidth:1}},!0))}))}else o.warn("Fetch is not available. Could not load SVG.");s.add(p,[d.rectangle(400,0,800,50,{isStatic:!0}),d.rectangle(400,600,800,50,{isStatic:!0}),d.rectangle(800,300,50,600,{isStatic:!0}),d.rectangle(0,300,50,600,{isStatic:!0})]);var y=a.create(m.canvas),x=i.create(u,{mouse:y,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(p,x),m.mouse=y,t.lookAt(m,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:u,runner:f,render:m,canvas:m.canvas,stop:function(){Matter.Render.stop(m),Matter.Runner.stop(f)}}},r.svg.title="Concave SVG Paths",r.svg.for=">0.16.1",e.exports=r.svg},"02te":function(e,t,n){var r=r||{};r.ballPool=function(){try{"undefined"!=typeof MatterWrap?Matter.use("matter-wrap"):Matter.use(n("OPlj"))}catch(e){}var e=Matter.Engine,t=Matter.Render,r=Matter.Runner,o=Matter.Composite,i=Matter.Composites,a=Matter.Common,s=Matter.MouseConstraint,c=Matter.Mouse,l=Matter.Bodies,d=e.create(),u=d.world,p=t.create({element:document.body,engine:d,options:{width:800,height:600,showAngleIndicator:!0}});t.run(p);var m=r.create();r.run(m,d),o.add(u,[l.rectangle(400,600,1200,50.5,{isStatic:!0,render:{fillStyle:"#060a19"}})]);var f=i.stack(100,0,10,8,10,10,(function(e,t){return l.circle(e,t,a.random(15,30),{restitution:.6,friction:.1})}));o.add(u,[f,l.polygon(200,460,3,60),l.polygon(400,460,5,60),l.rectangle(600,460,80,80)]);var v=c.create(p.canvas),g=s.create(d,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});o.add(u,g),p.mouse=v,t.lookAt(p,{min:{x:0,y:0},max:{x:800,y:600}});for(var y=o.allBodies(u),x=0;x1;if(!p||e!=p.x||t!=p.y){p&&r?(m=p.x,f=p.y):(m=0,f=0);var o={x:m+e,y:f+t};!r&&p||(p=o),v.push(o),y=m+e,x=f+t}},M=function(e){var t=e.pathSegTypeAsLetter.toUpperCase();if("Z"!==t){switch(t){case"M":case"L":case"T":case"C":case"S":case"Q":y=e.x,x=e.y;break;case"H":y=e.x;break;case"V":x=e.y}h(y,x,e.pathSegType)}};for(r._svgPathToAbsolute(e),a=e.getTotalLength(),l=[],n=0;n0)return!1;a=n}return!0},r.scale=function(e,t,n,i){if(1===t&&1===n)return e;var a,s;i=i||r.centre(e);for(var c=0;c=0?c-1:e.length-1],d=e[c],u=e[(c+1)%e.length],p=t[c0&&(i|=2),3===i)return!1;return 0!==i||null},r.hull=function(e){var t,n,r=[],i=[];for((e=e.slice(0)).sort((function(e,t){var n=e.x-t.x;return 0!==n?n:e.y-t.y})),n=0;n=2&&o.cross3(i[i.length-2],i[i.length-1],t)<=0;)i.pop();i.push(t)}for(n=e.length-1;n>=0;n-=1){for(t=e[n];r.length>=2&&o.cross3(r[r.length-2],r[r.length-1],t)<=0;)r.pop();r.push(t)}return r.pop(),i.pop(),r.concat(i)}},"0mtl":function(e,t,n){var r=r||{};r.catapult=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Constraint,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=Matter.Body,d=Matter.Vector,u=e.create(),p=u.world,m=t.create({element:document.body,engine:u,options:{width:800,height:600,showAngleIndicator:!0,showCollisions:!0,showVelocity:!0}});t.run(m);var f=n.create();n.run(f,u);var v=l.nextGroup(!0),g=r.stack(250,255,1,6,0,0,(function(e,t){return c.rectangle(e,t,30,30)})),y=c.rectangle(400,520,320,20,{collisionFilter:{group:v}});s.add(p,[g,y,c.rectangle(400,600,800,50.5,{isStatic:!0,render:{fillStyle:"#060a19"}}),c.rectangle(250,555,20,50,{isStatic:!0,render:{fillStyle:"#060a19"}}),c.rectangle(400,535,20,80,{isStatic:!0,collisionFilter:{group:v},render:{fillStyle:"#060a19"}}),c.circle(560,100,50,{density:.005}),o.create({bodyA:y,pointB:d.clone(y.position),stiffness:1,length:0})]);var x=a.create(m.canvas),h=i.create(u,{mouse:x,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(p,h),m.mouse=x,t.lookAt(m,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:u,runner:f,render:m,canvas:m.canvas,stop:function(){Matter.Render.stop(m),Matter.Runner.stop(f)}}},r.catapult.title="Catapult",r.catapult.for=">=0.14.2",e.exports=r.catapult},"136C":function(e,t,n){var r=r||{};r.slingshot=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Events,i=Matter.Constraint,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Composite,l=Matter.Bodies,d=e.create(),u=d.world,p=t.create({element:document.body,engine:d,options:{width:800,height:600,showAngleIndicator:!0}});t.run(p);var m=n.create();n.run(m,d);var f=l.rectangle(395,600,815,50,{isStatic:!0,render:{fillStyle:"#060a19"}}),v={density:.004},g=l.polygon(170,450,8,20,v),y=i.create({pointA:{x:170,y:450},bodyB:g,stiffness:.05}),x=r.pyramid(500,300,9,10,0,0,(function(e,t){return l.rectangle(e,t,25,40)})),h=l.rectangle(610,250,200,20,{isStatic:!0,render:{fillStyle:"#060a19"}}),M=r.pyramid(550,0,5,10,0,0,(function(e,t){return l.rectangle(e,t,25,40)}));c.add(d.world,[f,x,h,M,g,y]),o.on(d,"afterUpdate",(function(){-1===S.mouse.button&&(g.position.x>190||g.position.y<430)&&(g=l.polygon(170,450,7,20,v),c.add(d.world,g),y.bodyB=g)}));var b=s.create(p.canvas),S=a.create(d,{mouse:b,constraint:{stiffness:.2,render:{visible:!1}}});return c.add(u,S),p.mouse=b,t.lookAt(p,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:d,runner:m,render:p,canvas:p.canvas,stop:function(){Matter.Render.stop(p),Matter.Runner.stop(m)}}},r.slingshot.title="Slingshot",r.slingshot.for=">=0.14.2",e.exports=r.slingshot},"2Og8":function(e,t,n){var r={};e.exports=r;var o=n("571F");r._registry={},r.register=function(e){if(r.isPlugin(e)||o.warn("Plugin.register:",r.toString(e),"does not implement all required fields."),e.name in r._registry){var t=r._registry[e.name],n=r.versionParse(e.version).number,i=r.versionParse(t.version).number;n>i?(o.warn("Plugin.register:",r.toString(t),"was upgraded to",r.toString(e)),r._registry[e.name]=e):n-1},r.isFor=function(e,t){var n=e.for&&r.dependencyParse(e.for);return!e.for||t.name===n.name&&r.versionSatisfies(t.version,n.range)},r.use=function(e,t){if(e.uses=(e.uses||[]).concat(t||[]),0!==e.uses.length){for(var n=r.dependencies(e),i=o.topologicalSort(n),a=[],s=0;s0&&o.info(a.join(" "))}else o.warn("Plugin.use:",r.toString(e),"does not specify any dependencies to install.")},r.dependencies=function(e,t){var n=r.dependencyParse(e),i=n.name;if(!(i in(t=t||{}))){e=r.resolve(e)||e,t[i]=o.map(e.uses||[],(function(t){r.isPlugin(t)&&r.register(t);var i=r.dependencyParse(t),a=r.resolve(t);return a&&!r.versionSatisfies(a.version,i.range)?(o.warn("Plugin.dependencies:",r.toString(a),"does not satisfy",r.toString(i),"used by",r.toString(n)+"."),a._warned=!0,e._warned=!0):a||(o.warn("Plugin.dependencies:",r.toString(t),"used by",r.toString(n),"could not be resolved."),e._warned=!0),i.name}));for(var a=0;a=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-+]+)?$/;t.test(e)||o.warn("Plugin.versionParse:",e,"is not a valid version or range.");var n=t.exec(e),r=Number(n[4]),i=Number(n[5]),a=Number(n[6]);return{isRange:Boolean(n[1]||n[2]),version:n[3],range:e,operator:n[1]||n[2]||"",major:r,minor:i,patch:a,parts:[r,i,a],prerelease:n[7],number:1e8*r+1e4*i+a}},r.versionSatisfies=function(e,t){t=t||"*";var n=r.versionParse(t),o=r.versionParse(e);if(n.isRange){if("*"===n.operator||"*"===e)return!0;if(">"===n.operator)return o.number>n.number;if(">="===n.operator)return o.number>=n.number;if("~"===n.operator)return o.major===n.major&&o.minor===n.minor&&o.patch>=n.patch;if("^"===n.operator)return n.major>0?o.major===n.major&&o.number>=n.number:n.minor>0?o.minor===n.minor&&o.patch>=n.patch:o.patch===n.patch}return e===t||"*"===e}},"2oV2":function(e,t,n){var r=r||{};r.raycasting=function(){var e=Matter.Engine,t=Matter.Render,r=Matter.Runner,o=Matter.Composite,i=Matter.Composites,a=Matter.Common,s=Matter.Query,c=Matter.MouseConstraint,l=Matter.Mouse,d=Matter.Events,u=Matter.Vertices,p=Matter.Bodies,m=e.create(),f=m.world,v=t.create({element:document.body,engine:m,options:{width:800,height:600,showAngleIndicator:!0}});t.run(v);var g=r.create();r.run(g,m);var y=i.stack(20,20,12,4,0,0,(function(e,t){switch(Math.round(a.random(0,1))){case 0:return a.random()<.8?p.rectangle(e,t,a.random(20,50),a.random(20,50)):p.rectangle(e,t,a.random(80,120),a.random(20,30));case 1:var n=Math.round(a.random(1,8));return n=3===n?4:n,p.polygon(e,t,n,a.random(20,50))}}));a.setDecomp(n("Dded"));var x=u.fromPath("50 0 63 38 100 38 69 59 82 100 50 75 18 100 31 59 0 38 37 38"),h=p.fromVertices(200,200,x);o.add(f,[y,h,p.rectangle(400,0,800,50,{isStatic:!0}),p.rectangle(400,600,800,50,{isStatic:!0}),p.rectangle(800,300,50,600,{isStatic:!0}),p.rectangle(0,300,50,600,{isStatic:!0})]);var M=[],b={x:400,y:100};d.on(m,"afterUpdate",(function(){var e=w.mouse,t=o.allBodies(m.world),n=e.position||{x:100,y:600};M=s.ray(t,b,n)})),d.on(v,"afterRender",(function(){var e=w.mouse,n=v.context,r=e.position||{x:100,y:600};t.startViewTransform(v),n.beginPath(),n.moveTo(b.x,b.y),n.lineTo(r.x,r.y),M.length>0?n.strokeStyle="#fff":n.strokeStyle="#555",n.lineWidth=.5,n.stroke();for(var o=0;o0.16.1",e.exports=r.raycasting},"3Slt":function(e,t,n){var r={};e.exports=r;var o=n("0kzT"),i=n("Tgw/");r._restingThresh=4,r._restingThreshTangent=6,r._positionDampen=.9,r._positionWarming=.8,r._frictionNormalMultiplier=5,r.preSolvePosition=function(e){var t,n,r,o=e.length;for(t=0;tH||-W>H?(o=W>0?W:-W,(n=m.friction*(W>0?1:-1)*s)<-o?n=-o:n>o&&(n=o)):(n=W,o=u);var G=I*M-E*h,U=_*M-T*h,N=A/(B+v.inverseInertia*G*G+g.inverseInertia*U*U),z=(1+m.restitution)*O*N;if(n*=N,O*O>c&&O<0)R.normalImpulse=0;else{var Z=R.normalImpulse;R.normalImpulse+=z,R.normalImpulse=Math.min(R.normalImpulse,0),z=R.normalImpulse-Z}if(W*W>d)R.tangentImpulse=0;else{var Q=R.tangentImpulse;R.tangentImpulse+=n,R.tangentImpulse<-o&&(R.tangentImpulse=-o),R.tangentImpulse>o&&(R.tangentImpulse=o),n=R.tangentImpulse-Q}var X=h*z+b*n,Y=M*z+S*n;v.isStatic||v.isSleeping||(v.positionPrev.x+=X*v.inverseMass,v.positionPrev.y+=Y*v.inverseMass,v.anglePrev+=(I*Y-E*X)*v.inverseInertia),g.isStatic||g.isSleeping||(g.positionPrev.x-=X*g.inverseMass,g.positionPrev.y-=Y*g.inverseMass,g.anglePrev-=(_*Y-T*X)*g.inverseInertia)}}}}},"4XQC":function(e,t,n){var r=r||{};r.staticFriction=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Composites,i=Matter.Events,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Composite,l=Matter.Bodies,d=e.create(),u=d.world,p=t.create({element:document.body,engine:d,options:{width:800,height:600,showVelocity:!0}});t.run(p);var m=n.create();n.run(m,d);var f=l.rectangle(400,500,200,60,{isStatic:!0,chamfer:10,render:{fillStyle:"#060a19"}}),v=-1,g=o.stack(350,170,1,6,0,0,(function(e,t){return l.rectangle(e,t,100,50,{slop:.5,friction:1,frictionStatic:1/0})}));c.add(u,[f,g,l.rectangle(400,0,800,50,{isStatic:!0}),l.rectangle(400,600,800,50,{isStatic:!0}),l.rectangle(800,300,50,600,{isStatic:!0}),l.rectangle(0,300,50,600,{isStatic:!0})]),i.on(d,"beforeUpdate",(function(e){if(!((v+=.014)<0)){var t=400+100*Math.sin(v);r.setVelocity(f,{x:t-f.position.x,y:0}),r.setPosition(f,{x:t,y:f.position.y})}}));var y=s.create(p.canvas),x=a.create(d,{mouse:y,constraint:{stiffness:.2,render:{visible:!1}}});return c.add(u,x),p.mouse=y,t.lookAt(p,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:d,runner:m,render:p,canvas:p.canvas,stop:function(){Matter.Render.stop(p),Matter.Runner.stop(m)}}},r.staticFriction.title="Static Friction",r.staticFriction.for=">=0.14.2",e.exports=r.staticFriction},"4d8i":function(e,t,n){var r={};e.exports=r;var o=n("yTB+"),i=n("nIFq"),a=n("571F");!function(){var e,t,n;("undefined"!=typeof window&&(e=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame,t=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame),e)||(e=function(e){n=setTimeout((function(){e(a.now())}),1e3/60)},t=function(){clearTimeout(n)});r.create=function(e){var t=a.extend({fps:60,correction:1,deltaSampleSize:60,counterTimestamp:0,frameCounter:0,deltaHistory:[],timePrev:null,timeScalePrev:1,frameRequestId:null,isFixed:!1,enabled:!0},e);return t.delta=t.delta||1e3/t.fps,t.deltaMin=t.deltaMin||1e3/t.fps,t.deltaMax=t.deltaMax||1e3/(.5*t.fps),t.fps=1e3/t.delta,t},r.run=function(t,n){return void 0!==t.positionIterations&&(n=t,t=r.create()),function o(i){t.frameRequestId=e(o),i&&t.enabled&&r.tick(t,n,i)}(),t},r.tick=function(e,t,n){var r,a=t.timing,s=1,c={timestamp:a.timestamp};o.trigger(e,"beforeTick",c),e.isFixed?r=e.delta:(r=n-e.timePrev||e.delta,e.timePrev=n,e.deltaHistory.push(r),e.deltaHistory=e.deltaHistory.slice(-e.deltaSampleSize),s=(r=(r=(r=Math.min.apply(null,e.deltaHistory))e.deltaMax?e.deltaMax:r)/e.delta,e.delta=r),0!==e.timeScalePrev&&(s*=a.timeScale/e.timeScalePrev),0===a.timeScale&&(s=0),e.timeScalePrev=a.timeScale,e.correction=s,e.frameCounter+=1,n-e.counterTimestamp>=1e3&&(e.fps=e.frameCounter*((n-e.counterTimestamp)/1e3),e.counterTimestamp=n,e.frameCounter=0),o.trigger(e,"tick",c),o.trigger(e,"beforeUpdate",c),i.update(t,r,s),o.trigger(e,"afterUpdate",c),o.trigger(e,"afterTick",c)},r.stop=function(e){t(e.frameRequestId)},r.start=function(e,t){r.run(e,t)}}()},"52dP":function(e,t,n){var r=r||{};r.terrain=function(){var e=Matter.Engine,t=Matter.Render,r=Matter.Runner,o=Matter.Composites,i=Matter.Common,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Composite,l=Matter.Query,d=Matter.Svg,u=Matter.Bodies;i.setDecomp(n("Dded"));var p=e.create(),m=p.world,f=t.create({element:document.body,engine:p,options:{width:800,height:600}});t.run(f);var v,g=r.create();if(r.run(g,p),"undefined"!=typeof fetch){(v="./svg/terrain.svg",fetch(v).then((function(e){return e.text()})).then((function(e){return(new window.DOMParser).parseFromString(e,"image/svg+xml")}))).then((function(e){var t=function(e,t){return Array.prototype.slice.call(e.querySelectorAll(t))}(e,"path").map((function(e){return d.pathToVertices(e,30)})),n=u.fromVertices(400,350,t,{isStatic:!0,render:{fillStyle:"#060a19",strokeStyle:"#060a19",lineWidth:1}},!0);c.add(m,n);var r={frictionAir:0,friction:1e-4,restitution:.6};c.add(m,o.stack(80,100,20,20,10,10,(function(e,t){if(0===l.point([n],{x:e,y:t}).length)return u.polygon(e,t,5,12,r)})))}))}else i.warn("Fetch is not available. Could not load SVG.");var y=s.create(f.canvas),x=a.create(p,{mouse:y,constraint:{stiffness:.2,render:{visible:!1}}});return c.add(m,x),f.mouse=y,t.lookAt(f,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:p,runner:g,render:f,canvas:f.canvas,stop:function(){Matter.Render.stop(f),Matter.Runner.stop(g)}}},r.terrain.title="Terrain",r.terrain.for=">0.16.1",e.exports=r.terrain},"571F":function(e,t){var n={};e.exports=n,function(){n._nextId=0,n._seed=0,n._nowStartTime=+new Date,n._warnedOnce={},n._decomp=null,n.extend=function(e,t){var r,o;"boolean"==typeof t?(r=2,o=t):(r=1,o=!0);for(var i=r;i0;t--){var r=Math.floor(n.random()*(t+1)),o=e[t];e[t]=e[r],e[r]=o}return e},n.choose=function(e){return e[Math.floor(n.random()*e.length)]},n.isElement=function(e){return"undefined"!=typeof HTMLElement?e instanceof HTMLElement:!!(e&&e.nodeType&&e.nodeName)},n.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)},n.isFunction=function(e){return"function"==typeof e},n.isPlainObject=function(e){return"object"==typeof e&&e.constructor===Object},n.isString=function(e){return"[object String]"===toString.call(e)},n.clamp=function(e,t,n){return en?n:e},n.sign=function(e){return e<0?-1:1},n.now=function(){if("undefined"!=typeof window&&window.performance){if(window.performance.now)return window.performance.now();if(window.performance.webkitNow)return window.performance.webkitNow()}return Date.now?Date.now():new Date-n._nowStartTime},n.random=function(t,n){return n=void 0!==n?n:1,(t=void 0!==t?t:0)+e()*(n-t)};var e=function(){return n._seed=(9301*n._seed+49297)%233280,n._seed/233280};n.colorToNumber=function(e){return 3==(e=e.replace("#","")).length&&(e=e.charAt(0)+e.charAt(0)+e.charAt(1)+e.charAt(1)+e.charAt(2)+e.charAt(2)),parseInt(e,16)},n.logLevel=1,n.log=function(){console&&n.logLevel>0&&n.logLevel<=3&&console.log.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.info=function(){console&&n.logLevel>0&&n.logLevel<=2&&console.info.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.warn=function(){console&&n.logLevel>0&&n.logLevel<=3&&console.warn.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.warnOnce=function(){var e=Array.prototype.slice.call(arguments).join(" ");n._warnedOnce[e]||(n.warn(e),n._warnedOnce[e]=!0)},n.deprecated=function(e,t,r){e[t]=n.chain((function(){n.warnOnce("🔅 deprecated 🔅",r)}),e[t])},n.nextId=function(){return n._nextId++},n.indexOf=function(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0;n=0.14.2",e.exports=r.pyramid},"6IAi":function(e,t,n){var r=r||{};r.airFriction=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.MouseConstraint,o=Matter.Mouse,i=Matter.Composite,a=Matter.Bodies,s=e.create(),c=s.world,l=t.create({element:document.body,engine:s,options:{width:800,height:600,showVelocity:!0}});t.run(l);var d=n.create();n.run(d,s),i.add(c,[a.rectangle(200,100,60,60,{frictionAir:.001}),a.rectangle(400,100,60,60,{frictionAir:.05}),a.rectangle(600,100,60,60,{frictionAir:.1}),a.rectangle(400,0,800,50,{isStatic:!0}),a.rectangle(400,600,800,50,{isStatic:!0}),a.rectangle(800,300,50,600,{isStatic:!0}),a.rectangle(0,300,50,600,{isStatic:!0})]);var u=o.create(l.canvas),p=r.create(s,{mouse:u,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(c,p),l.mouse=u,t.lookAt(l,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:s,runner:d,render:l,canvas:l.canvas,stop:function(){Matter.Render.stop(l),Matter.Runner.stop(d)}}},r.airFriction.title="Air Friction",r.airFriction.for=">=0.14.2",e.exports=r.airFriction},"71Xj":function(e,t,n){var r=n("akk5");e.exports={demo:function(e,t){var n=r.Demo.create({toolbar:{title:"matter-js"+(t?" ・ dev":""),url:"https://github.com/liabru/matter-js",reset:!0,source:!0,inspector:!0,tools:!0,fullscreen:!0,exampleSelect:!0},tools:{inspector:!0,gui:!0},inline:!1,preventZoom:!0,resetOnOrientation:!0,routing:!0,startExample:"mixed",examples:e});if(window.MatterDemoInstance=n,document.body.appendChild(n.dom.root),document.title="Matter.js Demo"+(t?" ・ Dev":""),t){var o=n.dom.buttonSource,i=o.cloneNode(!0);i.textContent="⎄",i.title="Compare",i.href="?compare",i.target="",i.className="matter-btn matter-btn-compare",i.addEventListener("click",(function(e){window.location="?compare#"+n.example.id,e.preventDefault()})),o.parentNode.insertBefore(i,o.nextSibling),Matter.before("Render.create",(function(e){e.options.showDebug=!0}))}r.Demo.start(n)}}},"74MQ":function(e,t,n){var r=r||{};r.doublePendulum=function(){var e=Matter.Engine,t=Matter.Events,n=Matter.Render,r=Matter.Runner,o=Matter.Body,i=Matter.Composite,a=Matter.Composites,s=Matter.Constraint,c=Matter.MouseConstraint,l=Matter.Mouse,d=Matter.Bodies,u=Matter.Vector,p=e.create(),m=p.world,f=n.create({element:document.body,engine:p,options:{width:800,height:600,wireframes:!1}});n.run(f);var v=r.create();r.run(v,p);var g=o.nextGroup(!0),y=a.stack(350,160,2,1,-20,0,(function(e,t){return d.rectangle(e,t,200,25,{collisionFilter:{group:g},frictionAir:0,chamfer:5,render:{fillStyle:"transparent",lineWidth:1}})}));p.gravity.scale=.002,a.chain(y,.45,0,-.45,0,{stiffness:.9,length:0,angularStiffness:.7,render:{strokeStyle:"#4a485b"}}),i.add(y,s.create({bodyB:y.bodies[0],pointB:{x:-84,y:0},pointA:{x:y.bodies[0].position.x-84,y:y.bodies[0].position.y},stiffness:.9,length:0,render:{strokeStyle:"#4a485b"}}));var x=y.bodies[1];o.rotate(x,.3*-Math.PI,{x:x.position.x-100,y:x.position.y}),i.add(m,y);var h=[];t.on(f,"afterRender",(function(){h.unshift({position:u.clone(x.position),speed:x.speed}),n.startViewTransform(f),f.context.globalAlpha=.7;for(var e=0;e2e3&&h.pop()}));var M=l.create(f.canvas),b=c.create(p,{mouse:M,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(m,b),f.mouse=M,n.lookAt(f,{min:{x:0,y:0},max:{x:700,y:600}}),{engine:p,runner:v,render:f,canvas:f.canvas,stop:function(){Matter.Render.stop(f),Matter.Runner.stop(v)}}},r.doublePendulum.title="Double Pendulum",r.doublePendulum.for=">0.16.1",e.exports=r.doublePendulum},"7MJU":function(e,t,n){var r=r||{};r.gravity=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Common,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showVelocity:!0,showAngleIndicator:!0}});t.run(u);var p=n.create();n.run(p,l),s.add(d,[c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50.5,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]),l.gravity.y=-1;var m=r.stack(50,120,11,5,0,0,(function(e,t){switch(Math.round(o.random(0,1))){case 0:return o.random()<.8?c.rectangle(e,t,o.random(20,50),o.random(20,50)):c.rectangle(e,t,o.random(80,120),o.random(20,30));case 1:return c.polygon(e,t,Math.round(o.random(1,8)),o.random(20,50))}}));s.add(d,m);var f=a.create(u.canvas),v=i.create(l,{mouse:f,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,v),u.mouse=f,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.gravity.title="Reverse Gravity",r.gravity.for=">0.16.1",e.exports=r.gravity},"9K63":function(e,t){var n={};e.exports=n,n.create=function(e){return{vertex:e,normalImpulse:0,tangentImpulse:0}}},"9Nbg":function(e,t,n){var r={};e.exports=r;var o=n("wAS/"),i=n("ga9t"),a=n("571F"),s=n("IbIC"),c=n("oT59"),l=a.deprecated;r.stack=function(e,t,n,r,i,a,c){for(var l,d=o.create({label:"Stack"}),u=e,p=t,m=0,f=0;fv&&(v=x),s.translate(y,{x:.5*h,y:.5*x}),u=y.bounds.max.x+i,o.addBody(d,y),l=y,m+=1}else u+=i}p+=v+a,u=e}return d},r.chain=function(e,t,n,r,s,c){for(var l=e.bodies,d=1;d0)for(l=0;l0&&(p=m[l-1+(c-1)*t],o.addConstraint(e,i.create(a.extend({bodyA:p,bodyB:u},s)))),r&&lp||a<(l=p-l)||a>n-1-l))return 1===u&&s.translate(d,{x:(a+(n%2==1?1:-1))*m,y:0}),c(e+(d?a*m:0)+a*i,r,a,l,d,u)}))},r.newtonsCradle=function(e,t,n,r,a){for(var s=o.create({label:"Newtons Cradle"}),l=0;l=0.14.2",e.exports=r.avalanche},"C6Q+":function(e,t,n){var r=r||{};r.events=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Events,i=Matter.Composite,a=Matter.Composites,s=Matter.Common,c=Matter.MouseConstraint,l=Matter.Mouse,d=Matter.Bodies,u=e.create(),p=u.world,m=t.create({element:document.body,engine:u,options:{width:800,height:600,wireframes:!1}});t.run(m);var f=n.create();n.run(f,u),o.on(p,"afterAdd",(function(e){})),o.on(u,"beforeUpdate",(function(e){var t=e.source;e.timestamp%5e3<50&&y(t)})),o.on(u,"collisionStart",(function(e){for(var t=e.pairs,n=0;n=500){var a=.02*o.mass;r.applyForce(o,o.position,{x:(a+s.random()*a)*s.choose([1,-1]),y:-a+s.random()*-a})}}},x=l.create(m.canvas),h=c.create(u,{mouse:x,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(p,h),m.mouse=x,o.on(h,"mousedown",(function(e){var t=e.mouse.position;console.log("mousedown at "+t.x+" "+t.y),y(u)})),o.on(h,"mouseup",(function(e){var t=e.mouse.position;console.log("mouseup at "+t.x+" "+t.y)})),o.on(h,"startdrag",(function(e){console.log("startdrag",e)})),o.on(h,"enddrag",(function(e){console.log("enddrag",e)})),t.lookAt(m,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:u,runner:f,render:m,canvas:m.canvas,stop:function(){Matter.Render.stop(m),Matter.Runner.stop(f)}}},r.events.title="Events",r.events.for=">=0.14.2",e.exports=r.events},Czbd:function(e,t,n){var r=r||{};r.softBody=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,o=(Matter.Composites,Matter.MouseConstraint),i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showAngleIndicator:!1}});t.run(d);var u=n.create();n.run(u,c);var p={friction:.05,frictionStatic:.1,render:{visible:!0}};a.add(l,[r.softBody.softBody(250,100,5,5,0,0,!0,18,p),r.softBody.softBody(400,300,8,3,0,0,!0,15,p),r.softBody.softBody(250,400,4,4,0,0,!0,15,p),s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(400,600,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0})]);var m=i.create(d.canvas),f=o.create(c,{mouse:m,constraint:{stiffness:.9,render:{visible:!1}}});return a.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.softBody.title="Soft Body",r.softBody.for=">=0.14.2",r.softBody.softBody=function(e,t,n,r,o,i,a,s,c,l){var d=Matter.Common,u=Matter.Composites,p=Matter.Bodies;c=d.extend({inertia:1/0},c),l=d.extend({stiffness:.2,render:{type:"line",anchors:!1}},l);var m=u.stack(e,t,n,r,o,i,(function(e,t){return p.circle(e,t,s,c)}));return u.mesh(m,n,r,a,l),m.label="Soft Body",m},e.exports=r.softBody},DeYi:function(e,t,n){var r=r||{};r.friction=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.MouseConstraint,o=Matter.Mouse,i=Matter.Composite,a=Matter.Bodies,s=e.create(),c=s.world,l=t.create({element:document.body,engine:s,options:{width:800,height:600,showVelocity:!0}});t.run(l);var d=n.create();n.run(d,s),i.add(c,[a.rectangle(400,0,800,50,{isStatic:!0}),a.rectangle(400,600,800,50,{isStatic:!0}),a.rectangle(800,300,50,600,{isStatic:!0}),a.rectangle(0,300,50,600,{isStatic:!0})]),i.add(c,[a.rectangle(300,180,700,20,{isStatic:!0,angle:.06*Math.PI,render:{fillStyle:"#060a19"}}),a.rectangle(300,70,40,40,{friction:.001})]),i.add(c,[a.rectangle(300,350,700,20,{isStatic:!0,angle:.06*Math.PI,render:{fillStyle:"#060a19"}}),a.rectangle(300,250,40,40,{friction:5e-4})]),i.add(c,[a.rectangle(300,520,700,20,{isStatic:!0,angle:.06*Math.PI,render:{fillStyle:"#060a19"}}),a.rectangle(300,430,40,40,{friction:0})]);var u=o.create(l.canvas),p=r.create(s,{mouse:u,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(c,p),l.mouse=u,t.lookAt(l,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:s,runner:d,render:l,canvas:l.canvas,stop:function(){Matter.Render.stop(l),Matter.Runner.stop(d)}}},r.friction.title="Friction",r.friction.for=">=0.14.2",e.exports=r.friction},DqtB:function(e,t,n){var r={};e.exports=r;var o=n("Z/YF"),i=n("571F").deprecated;r.collides=function(e,t){return o.collides(e,t)},i(r,"collides","SAT.collides ➤ replaced by Collision.collides")},EUdY:function(e,t,n){var r=r||{};r.stress=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.MouseConstraint,i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showStats:!0,showPerformance:!0}});t.run(d);var u=n.create();n.run(u,c);var p=r.stack(90,50,18,15,0,0,(function(e,t){return s.rectangle(e,t,35,35)}));a.add(l,[p,s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(400,600,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0})]);var m=i.create(d.canvas),f=o.create(c,{mouse:m,constraint:{stiffness:.2,render:{visible:!1}}});return a.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.stress.title="Stress",r.stress.for=">=0.14.2",e.exports=r.stress},H5X8:function(e,t,n){var r=r||{};r.stress3=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Common,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=e.create({positionIterations:10,velocityIterations:10}),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showStats:!0,showPerformance:!0}});t.run(u);var p=n.create({isFixed:!0});n.run(p,l);var m=.3,f=r.stack(40,40,38,18,0,0,(function(e,t){var n=Math.round(o.random(1,8));switch(Math.round(o.random(0,1))){case 0:return o.random()<.8?c.rectangle(e,t,o.random(25,50)*m,o.random(25,50)*m):c.rectangle(e,t,o.random(80,120)*m,o.random(25,30)*m);case 1:return c.polygon(e,t,n,o.random(25,50)*m)}}));s.add(d,f),s.add(d,[c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var v=a.create(u.canvas),g=i.create(l,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,g),u.mouse=v,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.stress3.title="Stress 3",r.stress3.for=">=0.14.2",e.exports=r.stress3},I5nt:function(e,t,n){var r={};e.exports=r;var o=n("571F");r.create=function(e){var t={};return e||o.log("Mouse.create: element was undefined, defaulting to document.body","warn"),t.element=e||document.body,t.absolute={x:0,y:0},t.position={x:0,y:0},t.mousedownPosition={x:0,y:0},t.mouseupPosition={x:0,y:0},t.offset={x:0,y:0},t.scale={x:1,y:1},t.wheelDelta=0,t.button=-1,t.pixelRatio=parseInt(t.element.getAttribute("data-pixel-ratio"),10)||1,t.sourceEvents={mousemove:null,mousedown:null,mouseup:null,mousewheel:null},t.mousemove=function(e){var n=r._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches&&(t.button=0,e.preventDefault()),t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.sourceEvents.mousemove=e},t.mousedown=function(e){var n=r._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches?(t.button=0,e.preventDefault()):t.button=e.button,t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.mousedownPosition.x=t.position.x,t.mousedownPosition.y=t.position.y,t.sourceEvents.mousedown=e},t.mouseup=function(e){var n=r._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches&&e.preventDefault(),t.button=-1,t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.mouseupPosition.x=t.position.x,t.mouseupPosition.y=t.position.y,t.sourceEvents.mouseup=e},t.mousewheel=function(e){t.wheelDelta=Math.max(-1,Math.min(1,e.wheelDelta||-e.detail)),e.preventDefault()},r.setElement(t,t.element),t},r.setElement=function(e,t){e.element=t,t.addEventListener("mousemove",e.mousemove),t.addEventListener("mousedown",e.mousedown),t.addEventListener("mouseup",e.mouseup),t.addEventListener("mousewheel",e.mousewheel),t.addEventListener("DOMMouseScroll",e.mousewheel),t.addEventListener("touchmove",e.mousemove),t.addEventListener("touchstart",e.mousedown),t.addEventListener("touchend",e.mouseup)},r.clearSourceEvents=function(e){e.sourceEvents.mousemove=null,e.sourceEvents.mousedown=null,e.sourceEvents.mouseup=null,e.sourceEvents.mousewheel=null,e.wheelDelta=0},r.setOffset=function(e,t){e.offset.x=t.x,e.offset.y=t.y,e.position.x=e.absolute.x*e.scale.x+e.offset.x,e.position.y=e.absolute.y*e.scale.y+e.offset.y},r.setScale=function(e,t){e.scale.x=t.x,e.scale.y=t.y,e.position.x=e.absolute.x*e.scale.x+e.offset.x,e.position.y=e.absolute.y*e.scale.y+e.offset.y},r._getRelativeMousePosition=function(e,t,n){var r,o,i=t.getBoundingClientRect(),a=document.documentElement||document.body.parentNode||document.body,s=void 0!==window.pageXOffset?window.pageXOffset:a.scrollLeft,c=void 0!==window.pageYOffset?window.pageYOffset:a.scrollTop,l=e.changedTouches;return l?(r=l[0].pageX-i.left-s,o=l[0].pageY-i.top-c):(r=e.pageX-i.left-s,o=e.pageY-i.top-c),{x:r/(t.clientWidth/(t.width||t.clientWidth)*n),y:o/(t.clientHeight/(t.height||t.clientHeight)*n)}}},IOLg:function(e,t,n){var r=r||{};r.concave=function(){var e=Matter.Engine,t=Matter.Render,r=Matter.Runner,o=Matter.Composites,i=Matter.Common,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Composite,l=Matter.Vertices,d=Matter.Bodies;i.setDecomp(n("Dded"));var u=e.create(),p=u.world,m=t.create({element:document.body,engine:u,options:{width:800,height:600}});t.run(m);var f=r.create();r.run(f,u),c.add(p,[d.rectangle(400,0,800,50,{isStatic:!0}),d.rectangle(400,600,800,50,{isStatic:!0}),d.rectangle(800,300,50,600,{isStatic:!0}),d.rectangle(0,300,50,600,{isStatic:!0})]);var v=l.fromPath("40 0 40 20 100 20 100 80 40 80 40 100 0 50"),g=l.fromPath("100 0 75 50 100 100 25 100 0 50 25 0"),y=l.fromPath("50 0 63 38 100 38 69 59 82 100 50 75 18 100 31 59 0 38 37 38"),x=l.fromPath("35 7 19 17 14 38 14 58 25 79 45 85 65 84 65 66 46 67 34 59 30 44 33 29 45 23 66 23 66 7 53 7"),h=o.stack(50,50,6,4,10,10,(function(e,t){var n=i.choose(["#f19648","#f5d259","#f55a3c","#063e7b","#ececd1"]);return d.fromVertices(e,t,i.choose([v,g,y,x]),{render:{fillStyle:n,strokeStyle:n,lineWidth:1}},!0)}));c.add(p,h);var M=s.create(m.canvas),b=a.create(u,{mouse:M,constraint:{stiffness:.2,render:{visible:!1}}});return c.add(p,b),m.mouse=M,t.lookAt(m,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:u,runner:f,render:m,canvas:m.canvas,stop:function(){Matter.Render.stop(m),Matter.Runner.stop(f)}}},r.concave.title="Concave",r.concave.for=">0.16.1",e.exports=r.concave},IbIC:function(e,t,n){var r={};e.exports=r;var o=n("0kzT"),i=n("m6Dm"),a=n("yw0d"),s=(n("lWug"),n("571F")),c=n("Tgw/"),l=n("JKEF");!function(){r._inertiaScale=4,r._nextCollidingGroupId=1,r._nextNonCollidingGroupId=-1,r._nextCategory=1,r.create=function(t){var n={id:s.nextId(),type:"body",label:"Body",parts:[],plugin:{},angle:0,vertices:o.fromPath("L 0 0 L 40 0 L 40 40 L 0 40"),position:{x:0,y:0},force:{x:0,y:0},torque:0,positionImpulse:{x:0,y:0},constraintImpulse:{x:0,y:0,angle:0},totalContacts:0,speed:0,angularSpeed:0,velocity:{x:0,y:0},angularVelocity:0,isSensor:!1,isStatic:!1,isSleeping:!1,motion:0,sleepThreshold:60,density:.001,restitution:0,friction:.1,frictionStatic:.5,frictionAir:.01,collisionFilter:{category:1,mask:4294967295,group:0},slop:.05,timeScale:1,render:{visible:!0,opacity:1,strokeStyle:null,fillStyle:null,lineWidth:null,sprite:{xScale:1,yScale:1,xOffset:0,yOffset:0}},events:null,bounds:null,chamfer:null,circleRadius:0,positionPrev:null,anglePrev:0,parent:null,axes:null,area:0,mass:0,inertia:0,_original:null},r=s.extend(n,t);return e(r,t),r},r.nextGroup=function(e){return e?r._nextNonCollidingGroupId--:r._nextCollidingGroupId++},r.nextCategory=function(){return r._nextCategory=r._nextCategory<<1,r._nextCategory};var e=function(e,t){t=t||{},r.set(e,{bounds:e.bounds||c.create(e.vertices),positionPrev:e.positionPrev||i.clone(e.position),anglePrev:e.anglePrev||e.angle,vertices:e.vertices,parts:e.parts||[e],isStatic:e.isStatic,isSleeping:e.isSleeping,parent:e.parent||e}),o.rotate(e.vertices,e.angle,e.position),l.rotate(e.axes,e.angle),c.update(e.bounds,e.vertices,e.velocity),r.set(e,{axes:t.axes||e.axes,area:t.area||e.area,mass:t.mass||e.mass,inertia:t.inertia||e.inertia});var n=e.isStatic?"#14151f":s.choose(["#f19648","#f5d259","#f55a3c","#063e7b","#ececd1"]),a=e.isStatic?"#555":"#ccc",d=e.isStatic&&null===e.render.fillStyle?1:0;e.render.fillStyle=e.render.fillStyle||n,e.render.strokeStyle=e.render.strokeStyle||a,e.render.lineWidth=e.render.lineWidth||d,e.render.sprite.xOffset+=-(e.bounds.min.x-e.position.x)/(e.bounds.max.x-e.bounds.min.x),e.render.sprite.yOffset+=-(e.bounds.min.y-e.position.y)/(e.bounds.max.y-e.bounds.min.y)};r.set=function(e,t,n){var o;for(o in"string"==typeof t&&(o=t,(t={})[o]=n),t)if(Object.prototype.hasOwnProperty.call(t,o))switch(n=t[o],o){case"isStatic":r.setStatic(e,n);break;case"isSleeping":a.set(e,n);break;case"mass":r.setMass(e,n);break;case"density":r.setDensity(e,n);break;case"inertia":r.setInertia(e,n);break;case"vertices":r.setVertices(e,n);break;case"position":r.setPosition(e,n);break;case"angle":r.setAngle(e,n);break;case"velocity":r.setVelocity(e,n);break;case"angularVelocity":r.setAngularVelocity(e,n);break;case"parts":r.setParts(e,n);break;case"centre":r.setCentre(e,n);break;default:e[o]=n}},r.setStatic=function(e,t){for(var n=0;n0&&i.rotateAbout(a.position,n,e.position,a.position)}},r.setVelocity=function(e,t){e.positionPrev.x=e.position.x-t.x,e.positionPrev.y=e.position.y-t.y,e.velocity.x=t.x,e.velocity.y=t.y,e.speed=i.magnitude(e.velocity)},r.setAngularVelocity=function(e,t){e.anglePrev=e.angle-t,e.angularVelocity=t,e.angularSpeed=Math.abs(e.angularVelocity)},r.translate=function(e,t){r.setPosition(e,i.add(e.position,t))},r.rotate=function(e,t,n){if(n){var o=Math.cos(t),i=Math.sin(t),a=e.position.x-n.x,s=e.position.y-n.y;r.setPosition(e,{x:n.x+(a*o-s*i),y:n.y+(a*i+s*o)}),r.setAngle(e,e.angle+t)}else r.setAngle(e,e.angle+t)},r.scale=function(e,t,n,i){var a=0,s=0;i=i||e.position;for(var d=0;d0&&(a+=u.area,s+=u.inertia),u.position.x=i.x+(u.position.x-i.x)*t,u.position.y=i.y+(u.position.y-i.y)*n,c.update(u.bounds,u.vertices,e.velocity)}e.parts.length>1&&(e.area=a,e.isStatic||(r.setMass(e,e.density*a),r.setInertia(e,s))),e.circleRadius&&(t===n?e.circleRadius*=t:e.circleRadius=null)},r.update=function(e,t,n,r){var a=Math.pow(t*n*e.timeScale,2),s=1-e.frictionAir*n*e.timeScale,d=e.position.x-e.positionPrev.x,u=e.position.y-e.positionPrev.y;e.velocity.x=d*s*r+e.force.x/e.mass*a,e.velocity.y=u*s*r+e.force.y/e.mass*a,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.position.x+=e.velocity.x,e.position.y+=e.velocity.y,e.angularVelocity=(e.angle-e.anglePrev)*s*r+e.torque/e.inertia*a,e.anglePrev=e.angle,e.angle+=e.angularVelocity,e.speed=i.magnitude(e.velocity),e.angularSpeed=Math.abs(e.angularVelocity);for(var p=0;p0&&(m.position.x+=e.velocity.x,m.position.y+=e.velocity.y),0!==e.angularVelocity&&(o.rotate(m.vertices,e.angularVelocity,e.position),l.rotate(m.axes,e.angularVelocity),p>0&&i.rotateAbout(m.position,e.angularVelocity,e.position,m.position)),c.update(m.bounds,m.vertices,e.velocity)}},r.applyForce=function(e,t,n){e.force.x+=n.x,e.force.y+=n.y;var r=t.x-e.position.x,o=t.y-e.position.y;e.torque+=r*n.y-o*n.x},r._totalProperties=function(e){for(var t={mass:0,area:0,inertia:0,centre:{x:0,y:0}},n=1===e.parts.length?0:1;n=0.14.2",e.exports=r.sleeping},LS1c:function(e,t,n){var r=r||{};r.wreckingBall=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.MouseConstraint,i=Matter.Mouse,a=Matter.Composite,s=Matter.Constraint,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAngleIndicator:!0}});t.run(u);var p=n.create();n.run(p,l);var m=r.stack(400,175,5,10,0,0,(function(e,t){return c.rectangle(e,t,40,40)}));a.add(d,[m,c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var f=c.circle(100,400,50,{density:.04,frictionAir:.005});a.add(d,f),a.add(d,s.create({pointA:{x:300,y:100},bodyB:f}));var v=i.create(u.canvas),g=o.create(l,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});return a.add(d,g),u.mouse=v,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.wreckingBall.title="Wrecking Ball",r.wreckingBall.for=">=0.14.2",e.exports=r.wreckingBall},Me0I:function(e,t,n){var r=r||{};r.car=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,o=(Matter.Composites,Matter.MouseConstraint),i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showAngleIndicator:!0,showCollisions:!0}});t.run(d);var u=n.create();n.run(u,c),a.add(l,[s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(400,600,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0})]);var p=.9;a.add(l,r.car.car(150,100,150*p,30*p,30*p)),p=.8,a.add(l,r.car.car(350,300,150*p,30*p,30*p)),a.add(l,[s.rectangle(200,150,400,20,{isStatic:!0,angle:.06*Math.PI,render:{fillStyle:"#060a19"}}),s.rectangle(500,350,650,20,{isStatic:!0,angle:.06*-Math.PI,render:{fillStyle:"#060a19"}}),s.rectangle(300,560,600,20,{isStatic:!0,angle:.04*Math.PI,render:{fillStyle:"#060a19"}})]);var m=i.create(d.canvas),f=o.create(c,{mouse:m,constraint:{stiffness:.2,render:{visible:!1}}});return a.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.car.title="Car",r.car.for=">=0.14.2",r.car.car=function(e,t,n,r,o){var i=Matter.Body,a=Matter.Bodies,s=Matter.Composite,c=Matter.Constraint,l=i.nextGroup(!0),d=.5*-n+20,u=.5*n-20,p=s.create({label:"Car"}),m=a.rectangle(e,t,n,r,{collisionFilter:{group:l},chamfer:{radius:.5*r},density:2e-4}),f=a.circle(e+d,t+0,o,{collisionFilter:{group:l},friction:.8}),v=a.circle(e+u,t+0,o,{collisionFilter:{group:l},friction:.8}),g=c.create({bodyB:m,pointB:{x:d,y:0},bodyA:f,stiffness:1,length:0}),y=c.create({bodyB:m,pointB:{x:u,y:0},bodyA:v,stiffness:1,length:0});return s.addBody(p,m),s.addBody(p,f),s.addBody(p,v),s.addConstraint(p,g),s.addConstraint(p,y),p},e.exports=r.car},MhOg:function(e,t,n){var r=r||{};r.views=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Events,o=Matter.Composites,i=Matter.Common,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Composite,l=Matter.Vector,d=Matter.Bounds,u=Matter.Bodies,p=e.create(),m=p.world,f=t.create({element:document.body,engine:p,options:{width:800,height:600,hasBounds:!0,showAngleIndicator:!0}});t.run(f);var v=n.create();n.run(v,p);var g=s.create(f.canvas),y=a.create(p,{mouse:g,constraint:{stiffness:.2,render:{visible:!1}}});c.add(m,y),f.mouse=g;var x=o.stack(20,20,10,4,0,0,(function(e,t){switch(Math.round(i.random(0,1))){case 0:return i.random()<.8?u.rectangle(e,t,i.random(20,50),i.random(20,50)):u.rectangle(e,t,i.random(80,120),i.random(20,30));case 1:var n=Math.round(i.random(1,8));return n=3===n?4:n,u.polygon(e,t,n,i.random(20,50))}}));c.add(m,[x,u.rectangle(400,0,800,50,{isStatic:!0}),u.rectangle(400,600,800,50,{isStatic:!0}),u.rectangle(800,300,50,600,{isStatic:!0}),u.rectangle(0,300,50,600,{isStatic:!0})]);var h={x:.5*f.options.width,y:.5*f.options.height},M={x:-300,y:-300},b={x:1100,y:900},S=1,w={x:1,y:1};return r.on(f,"beforeRender",(function(){p.world;var e,t=y.mouse,n=-.1*t.wheelDelta;0!==n&&(n<0&&w.x>=.6||n>0&&w.x<=1.4)&&(S+=n),Math.abs(w.x-S)>.01&&(n=.2*(S-w.x),w.x+=n,w.y+=n,f.bounds.max.x=f.bounds.min.x+f.options.width*w.x,f.bounds.max.y=f.bounds.min.y+f.options.height*w.y,e={x:f.options.width*n*-.5,y:f.options.height*n*-.5},d.translate(f.bounds,e),s.setScale(t,w),s.setOffset(t,f.bounds.min));var r=l.sub(t.absolute,h),o=l.magnitude(r);if(o>50){var i=l.normalise(r),a=Math.min(10,2e-4*Math.pow(o-50,2));e=l.mult(i,a),f.bounds.min.x+e.xb.x&&(e.x=b.x-f.bounds.max.x),f.bounds.min.y+e.yb.y&&(e.y=b.y-f.bounds.max.y),d.translate(f.bounds,e),s.setOffset(t,f.bounds.min)}})),{engine:p,runner:v,render:f,canvas:f.canvas,stop:function(){Matter.Render.stop(f),Matter.Runner.stop(v)}}},r.views.title="Views",r.views.for=">=0.14.2",e.exports=r.views},NDQ1:function(e,t,n){var r={};e.exports=r;var o=n("m6Dm"),i=n("Z/YF"),a=n("Tgw/"),s=n("oT59"),c=n("0kzT");r.collides=function(e,t){for(var n=[],r=t.length,o=e.bounds,s=i.collides,c=a.overlaps,l=0;ld.bounds.max.x||m.bounds.max.yd.bounds.max.y))){var f=r._getRegion(e,m);if(!m.region||f.id!==m.region.id||o){m.region&&!o||(m.region=f);var v=r._regionUnion(f,m.region);for(a=v.startCol;a<=v.endCol;a++)for(s=v.startRow;s<=v.endRow;s++){c=u[l=r._getBucketId(a,s)];var g=a>=f.startCol&&a<=f.endCol&&s>=f.startRow&&s<=f.endRow,y=a>=m.region.startCol&&a<=m.region.endCol&&s>=m.region.startRow&&s<=m.region.endRow;!g&&y&&y&&c&&r._bucketRemoveBody(e,c,m),(m.region===f||g&&!y||o)&&(c||(c=r._createBucket(u,l)),r._bucketAddBody(e,c,m))}m.region=f,p=!0}}}p&&(e.pairsList=r._createActivePairsList(e))},a(r,"update","Grid.update ➤ replaced by Matter.Detector"),r.clear=function(e){e.buckets={},e.pairs={},e.pairsList=[]},a(r,"clear","Grid.clear ➤ replaced by Matter.Detector"),r._regionUnion=function(e,t){var n=Math.min(e.startCol,t.startCol),o=Math.max(e.endCol,t.endCol),i=Math.min(e.startRow,t.startRow),a=Math.max(e.endRow,t.endRow);return r._createRegion(n,o,i,a)},r._getRegion=function(e,t){var n=t.bounds,o=Math.floor(n.min.x/e.bucketWidth),i=Math.floor(n.max.x/e.bucketWidth),a=Math.floor(n.min.y/e.bucketHeight),s=Math.floor(n.max.y/e.bucketHeight);return r._createRegion(o,i,a,s)},r._createRegion=function(e,t,n,r){return{id:e+","+t+","+n+","+r,startCol:e,endCol:t,startRow:n,endRow:r}},r._getBucketId=function(e,t){return"C"+e+"R"+t},r._createBucket=function(e,t){return e[t]=[]},r._bucketAddBody=function(e,t,n){var r,i=e.pairs,a=o.id,s=t.length;for(r=0;r0?s.push(t):delete r[o[n]];return s}},"Tgw/":function(e,t){var n={};e.exports=n,n.create=function(e){var t={min:{x:0,y:0},max:{x:0,y:0}};return e&&n.update(t,e),t},n.update=function(e,t,n){e.min.x=1/0,e.max.x=-1/0,e.min.y=1/0,e.max.y=-1/0;for(var r=0;re.max.x&&(e.max.x=o.x),o.xe.max.y&&(e.max.y=o.y),o.y0?e.max.x+=n.x:e.min.x+=n.x,n.y>0?e.max.y+=n.y:e.min.y+=n.y)},n.contains=function(e,t){return t.x>=e.min.x&&t.x<=e.max.x&&t.y>=e.min.y&&t.y<=e.max.y},n.overlaps=function(e,t){return e.min.x<=t.max.x&&e.max.x>=t.min.x&&e.max.y>=t.min.y&&e.min.y<=t.max.y},n.translate=function(e,t){e.min.x+=t.x,e.max.x+=t.x,e.min.y+=t.y,e.max.y+=t.y},n.shift=function(e,t){var n=e.max.x-e.min.x,r=e.max.y-e.min.y;e.min.x=t.x,e.max.x=t.x+n,e.min.y=t.y,e.max.y=t.y+r}},VZUp:function(e,t,n){var r=r||{};r.manipulation=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Events,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAxes:!0,showCollisions:!0,showConvexHulls:!0}});t.run(u);var p=n.create();n.run(p,l);var m=c.rectangle(100,200,50,50,{isStatic:!0,render:{fillStyle:"#060a19"}}),f=c.rectangle(200,200,50,50),v=c.rectangle(300,200,50,50),g=c.rectangle(400,200,50,50),y=c.rectangle(550,200,50,50),x=c.rectangle(700,200,50,50),h=c.circle(400,100,25,{render:{fillStyle:"#060a19"}}),M=c.rectangle(600,200,120,50,{render:{fillStyle:"#060a19"}}),b=c.rectangle(660,200,50,190,{render:{fillStyle:"#060a19"}}),S=r.create({parts:[M,b],isStatic:!0});s.add(d,[m,f,v,g,y,x,h,S]),s.add(d,[c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var w=0,C=1.01;o.on(p,"afterTick",(function(e){40===(w+=1)&&r.setStatic(h,!0),C>1&&(r.scale(x,C,C),r.scale(S,.995,.995),y.vertices[0].x-=.2,y.vertices[0].y-=.2,y.vertices[1].x+=.2,y.vertices[1].y-=.2,r.setVertices(y,y.vertices));var t=300+100*Math.sin(.002*l.timing.timestamp);r.setVelocity(m,{x:0,y:t-m.position.y}),r.setPosition(m,{x:100,y:t}),r.setVelocity(S,{x:0,y:t-S.position.y}),r.setAngularVelocity(S,.02),r.setPosition(S,{x:600,y:t}),r.rotate(S,.02),w>=90&&(r.setVelocity(f,{x:0,y:-10}),r.setAngle(v,.26*-Math.PI),r.setAngularVelocity(g,.2),w=0,C=1)}));var A=a.create(u.canvas),B=i.create(l,{mouse:A,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,B),u.mouse=A,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.manipulation.title="Manipulation",r.manipulation.for=">=0.14.2",e.exports=r.manipulation},Vyp4:function(e,t,n){var r=r||{};r.remove=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Common,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=Matter.Events,d=e.create(),u=d.world,p=t.create({element:document.body,engine:d,options:{width:800,height:600,showAngleIndicator:!0}});t.run(p);var m=n.create();n.run(m,d);var f=null,v=0,g=function(){return r.stack(20,20,10,5,0,0,(function(e,t){var n=Math.round(o.random(1,8)),r=null;switch(n>2&&o.random()>.7&&(r={radius:10}),Math.round(o.random(0,1))){case 0:return o.random()<.8?c.rectangle(e,t,o.random(25,50),o.random(25,50),{chamfer:r}):c.rectangle(e,t,o.random(80,120),o.random(25,30),{chamfer:r});case 1:return c.polygon(e,t,n,o.random(25,50),{chamfer:r})}}))};l.on(d,"afterUpdate",(function(){f&&v<=50?v+=1:(v=0,f&&s.remove(u,f),f=g(),s.add(u,f))})),s.add(u,g()),s.add(u,[c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var y=a.create(p.canvas),x=i.create(d,{mouse:y,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(u,x),p.mouse=y,t.lookAt(p,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:d,runner:m,render:p,canvas:p.canvas,stop:function(){Matter.Render.stop(p),Matter.Runner.stop(m)}}},r.remove.title="Composite Remove",r.remove.for=">=0.14.2",e.exports=r.remove},"Z/YF":function(e,t,n){var r={};e.exports=r;var o,i,a,s=n("0kzT"),c=n("t8gT");o=[],i={overlap:0,axis:null},a={overlap:0,axis:null},r.create=function(e,t){return{pair:null,collided:!1,bodyA:e,bodyB:t,parentA:e.parent,parentB:t.parent,depth:0,normal:{x:0,y:0},tangent:{x:0,y:0},penetration:{x:0,y:0},supports:[]}},r.collides=function(e,t,n){if(r._overlapAxes(i,e.vertices,t.vertices,e.axes),i.overlap<=0)return null;if(r._overlapAxes(a,t.vertices,e.vertices,t.axes),a.overlap<=0)return null;var o,l,d=n&&n.table[c.id(e,t)];d?o=d.collision:((o=r.create(e,t)).collided=!0,o.bodyA=e.idC?C=s:sA?A=s:so?o=a:a=0.14.2",e.exports=r.restitution},ZUN1:function(e,t,n){var r=r||{};r.chains=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Composite,i=Matter.Composites,a=Matter.Constraint,s=Matter.MouseConstraint,c=Matter.Mouse,l=Matter.Bodies,d=e.create(),u=d.world,p=t.create({element:document.body,engine:d,options:{width:800,height:600,showAngleIndicator:!0,showCollisions:!0,showVelocity:!0}});t.run(p);var m=n.create();n.run(m,d);var f=r.nextGroup(!0),v=i.stack(100,50,8,1,10,10,(function(e,t){return l.rectangle(e,t,50,20,{collisionFilter:{group:f}})}));i.chain(v,.5,0,-.5,0,{stiffness:.8,length:2,render:{type:"line"}}),o.add(v,a.create({bodyB:v.bodies[0],pointB:{x:-25,y:0},pointA:{x:v.bodies[0].position.x,y:v.bodies[0].position.y},stiffness:.5})),f=r.nextGroup(!0);var g=i.stack(350,50,10,1,10,10,(function(e,t){return l.circle(e,t,20,{collisionFilter:{group:f}})}));i.chain(g,.5,0,-.5,0,{stiffness:.8,length:2,render:{type:"line"}}),o.add(g,a.create({bodyB:g.bodies[0],pointB:{x:-20,y:0},pointA:{x:g.bodies[0].position.x,y:g.bodies[0].position.y},stiffness:.5})),f=r.nextGroup(!0);var y=i.stack(600,50,13,1,10,10,(function(e,t){return l.rectangle(e-20,t,50,20,{collisionFilter:{group:f},chamfer:5})}));i.chain(y,.3,0,-.3,0,{stiffness:1,length:0}),o.add(y,a.create({bodyB:y.bodies[0],pointB:{x:-20,y:0},pointA:{x:y.bodies[0].position.x,y:y.bodies[0].position.y},stiffness:.5})),o.add(u,[v,g,y,l.rectangle(400,600,1200,50.5,{isStatic:!0})]);var x=c.create(p.canvas),h=s.create(d,{mouse:x,constraint:{stiffness:.2,render:{visible:!1}}});return o.add(u,h),p.mouse=x,t.lookAt(p,{min:{x:0,y:0},max:{x:700,y:600}}),{engine:d,runner:m,render:p,canvas:p.canvas,stop:function(){Matter.Render.stop(p),Matter.Runner.stop(m)}}},r.chains.title="Chains",r.chains.for=">=0.14.2",e.exports=r.chains},Zo9v:function(e,t,n){var r=r||{};r.collisionFiltering=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composite,o=Matter.Composites,i=(Matter.Common,Matter.MouseConstraint),a=Matter.Mouse,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,wireframes:!1}});t.run(d);var u=n.create();n.run(u,c);var p="#f55a3c",m="#063e7b",f="#f5d259";r.add(l,s.rectangle(400,600,900,50,{isStatic:!0,render:{fillStyle:"transparent",lineWidth:1}})),r.add(l,o.stack(275,100,5,9,10,10,(function(e,t,n,r){var o=2,i=p;return r>5?(o=8,i=m):r>2&&(o=4,i=f),s.circle(e,t,20,{collisionFilter:{category:o},render:{strokeStyle:i,fillStyle:"transparent",lineWidth:1}})}))),r.add(l,s.circle(310,40,30,{collisionFilter:{mask:5},render:{fillStyle:f}})),r.add(l,s.circle(400,40,30,{collisionFilter:{mask:3},render:{fillStyle:p}})),r.add(l,s.circle(480,40,30,{collisionFilter:{mask:9},render:{fillStyle:m}}));var v=a.create(d.canvas),g=i.create(c,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});return r.add(l,g),d.mouse=v,g.collisionFilter.mask=13,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.collisionFiltering.title="Collision Filtering",r.collisionFiltering.for=">=0.14.2",e.exports=r.collisionFiltering},ZpbE:function(e,t,n){var r=r||{};r.stack=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.MouseConstraint,i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showAngleIndicator:!0}});t.run(d);var u=n.create();n.run(u,c);var p=r.stack(200,380.75,10,5,0,0,(function(e,t){return s.rectangle(e,t,40,40)}));a.add(l,[p,s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0}),s.rectangle(400,606,800,50.5,{isStatic:!0})]);var m=i.create(d.canvas),f=o.create(c,{mouse:m,constraint:{stiffness:.2,render:{visible:!1}}});return a.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.stack.title="Stack",r.stack.for=">=0.14.2",e.exports=r.stack},"a+ms":function(e,t,n){var r=n("akk5"),o=n("lniP"),i=n("lniP");e.exports={compare:function(e,t){var n=r.Demo.create({toolbar:{title:"matter-js ・ "+(t?"dev":"")+" ・ comparing to "+i.version,url:"https://github.com/liabru/matter-js",reset:!0,source:!0,inspector:!1,tools:!1,fullscreen:!0,exampleSelect:!0},tools:{inspector:!1,gui:!1},inline:!1,preventZoom:!0,resetOnOrientation:!0,routing:!0,startExample:!1,examples:e}),a=r.Demo.create({toolbar:{title:"matter-js-compare-build",reset:!1,source:!1,inspector:!1,tools:!1,fullscreen:!1,exampleSelect:!1},tools:{inspector:!1,gui:!1},inline:!1,preventZoom:!0,resetOnOrientation:!0,routing:!1,startExample:!1,examples:e.map((function(e){return Matter.Common.extend({},e)}))});i.Runner.run=function(){},i.Render.run=function(){},o.Runner._tick=o.Runner.tick,o.Render._world=o.Render.world,i.Mouse._setElement=i.Mouse.setElement,r.Demo._setExample=r.Demo.setExample,r.Demo.setExample=function(e,t){i.Common._nextId=i.Common._seed=0,o.Common._nextId=o.Common._seed=0,i.Plugin._registry=o.Plugin._registry,i.use.apply(null,o.used),window.Matter=o,r.Demo._setExample(n,n.examples.find((function(e){return e.name===t.name})));var s=parseFloat(window.location.search.split("=")[1]),c=0;o.Runner.tick=function(e,t,n){if(-1!==c){if(c>=s)return console.info("Demo.Compare: ran "+c+" ticks, timestamp is now "+t.timing.timestamp.toFixed(2)),void(c=-1);c+=1;var r=a.example.instance;return e.isFixed=r.runner.isFixed=!0,e.delta=r.runner.delta=1e3/60,window.Matter=i,i.Runner.tick(r.runner,r.engine,n),window.Matter=o,o.Runner._tick(e,t,n)}},o.Render.world=function(e){return window.Matter=i,i.Render.world(a.example.instance.render),window.Matter=o,o.Render._world(e)},i.Mouse.setElement=function(e){return i.Mouse._setElement(e,n.example.instance.render.canvas)},window.Matter=i,r.Demo._setExample(a,a.examples.find((function(e){return e.name===t.name}))),window.Matter=o},r.Demo._reset=r.Demo.reset,r.Demo.reset=function(e){i.Common._nextId=i.Common._seed=0,o.Common._nextId=o.Common._seed=0,window.Matter=i,r.Demo._reset(a),window.Matter=o,r.Demo._reset(n)},document.body.appendChild(n.dom.root),document.body.appendChild(a.dom.root),r.Demo.start(n),document.title="Matter.js Compare"+(t?" ・ Dev":""),console.info("Demo.Compare: matter-js@"+o.version+" with matter-js@"+i.version)}}},a3OZ:function(e,t,n){var r=r||{};r.compositeManipulation=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Events,o=Matter.Composite,i=Matter.Composites,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAngleIndicator:!0}});t.run(u);var p=n.create();n.run(p,l),o.add(d,[c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var m=i.stack(200,200,4,4,0,0,(function(e,t){return c.rectangle(e,t,40,40)}));o.add(d,m),l.gravity.y=0,r.on(l,"afterUpdate",(function(e){var t=l.timing.timestamp;o.translate(m,{x:2*Math.sin(.001*t),y:0}),o.rotate(m,.01*Math.sin(.001*t),{x:300,y:300});var n=1+.01*Math.sin(.001*t);o.scale(m,n,n,{x:300,y:300})}));var f=s.create(u.canvas),v=a.create(l,{mouse:f,constraint:{stiffness:.2,render:{visible:!1}}});return o.add(d,v),u.mouse=f,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.compositeManipulation.title="Composite Manipulation",r.compositeManipulation.for=">0.16.1",e.exports=r.compositeManipulation},c7us:function(e,t,n){var r=r||{};r.ragdoll=function(){var e=Matter.Engine,t=Matter.Events,n=Matter.Render,o=Matter.Runner,i=Matter.Body,a=Matter.Common,s=Matter.Composite,c=Matter.Composites,l=(Matter.Constraint,Matter.MouseConstraint),d=Matter.Mouse,u=Matter.Bodies,p=(Matter.Vector,e.create()),m=p.world,f=n.create({element:document.body,engine:p,options:{width:800,height:600,showAngleIndicator:!0}});n.run(f);var v=o.create();o.run(v,p);for(var g=(f.bounds.max.y-f.bounds.min.y)/50,y=c.stack(0,0,g+2,1,0,0,(function(e,t,n){return u.rectangle(e-50,t+50*n,100,1e3,{isStatic:!0,render:{fillStyle:"#060a19",strokeStyle:"#ffffff",lineWidth:1}})})),x=c.stack(300,0,15,3,10,10,(function(e,t,n){var r=Math.round(a.random(1,8)),o={render:{fillStyle:a.choose(["#f19648","#f5d259","#f55a3c","#063e7b","#ececd1"])}};switch(Math.round(a.random(0,1))){case 0:return a.random()<.8?u.rectangle(e,t,a.random(25,50),a.random(25,50),o):u.rectangle(e,t,a.random(80,120),a.random(25,30),o);case 1:return u.polygon(e,t,r,a.random(25,50),o)}})),h=s.create(),M=0;M<1;M+=1){var b=r.ragdoll.ragdoll(200,-1e3*M,1.3);s.add(h,b)}s.add(m,[y,x,h]);var S=1,w=0;t.on(p,"afterUpdate",(function(e){-1===C.button?p.timing.timeScale+=.05*(S-p.timing.timeScale):p.timing.timeScale=1,(w+=1)>=90&&(S=S<1?1:.05,w=0);for(var t=0;tf.bounds.max.y+100&&s.translate(r,{x:.9*-o.min.x,y:-f.bounds.max.y-400})}for(t=0;tf.bounds.max.y+100&&i.translate(n,{x:-o.min.x,y:-f.bounds.max.y-300})}}));var C=d.create(f.canvas),A=l.create(p,{mouse:C,constraint:{stiffness:.6,length:0,angularStiffness:0,render:{visible:!1}}});return s.add(m,A),f.mouse=C,n.lookAt(f,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:p,runner:v,render:f,canvas:f.canvas,stop:function(){Matter.Render.stop(f),Matter.Runner.stop(v)}}},r.ragdoll.ragdoll=function(e,t,n,r){n=void 0===n?1:n;var o=Matter.Body,i=Matter.Bodies,a=Matter.Constraint,s=Matter.Composite,c=Matter.Common,l=c.extend({label:"head",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:[15*n,15*n,15*n,15*n]},render:{fillStyle:"#FFBC42"}},r),d=c.extend({label:"chest",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:[20*n,20*n,26*n,26*n]},render:{fillStyle:"#E0A423"}},r),u=c.extend({label:"left-arm",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:10*n},render:{fillStyle:"#FFBC42"}},r),p=c.extend({},u,{render:{fillStyle:"#E59B12"}}),m=c.extend({label:"right-arm",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:10*n},render:{fillStyle:"#FFBC42"}},r),f=c.extend({},m,{render:{fillStyle:"#E59B12"}}),v=c.extend({label:"left-leg",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:10*n},render:{fillStyle:"#FFBC42"}},r),g=c.extend({},v,{render:{fillStyle:"#E59B12"}}),y=c.extend({label:"right-leg",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:10*n},render:{fillStyle:"#FFBC42"}},r),x=c.extend({},y,{render:{fillStyle:"#E59B12"}}),h=i.rectangle(e,t-60*n,34*n,40*n,l),M=i.rectangle(e,t,55*n,80*n,d),b=i.rectangle(e+39*n,t-15*n,20*n,40*n,m),S=i.rectangle(e+39*n,t+25*n,20*n,60*n,f),w=i.rectangle(e-39*n,t-15*n,20*n,40*n,u),C=i.rectangle(e-39*n,t+25*n,20*n,60*n,p),A=i.rectangle(e-20*n,t+57*n,20*n,40*n,v),B=i.rectangle(e-20*n,t+97*n,20*n,60*n,g),k=i.rectangle(e+20*n,t+57*n,20*n,40*n,y),R=i.rectangle(e+20*n,t+97*n,20*n,60*n,x),P=a.create({bodyA:M,pointA:{x:24*n,y:-23*n},pointB:{x:0,y:-8*n},bodyB:b,stiffness:.6,render:{visible:!1}}),I=a.create({bodyA:M,pointA:{x:-24*n,y:-23*n},pointB:{x:0,y:-8*n},bodyB:w,stiffness:.6,render:{visible:!1}}),E=a.create({bodyA:M,pointA:{x:-10*n,y:30*n},pointB:{x:0,y:-10*n},bodyB:A,stiffness:.6,render:{visible:!1}}),_=a.create({bodyA:M,pointA:{x:10*n,y:30*n},pointB:{x:0,y:-10*n},bodyB:k,stiffness:.6,render:{visible:!1}}),T=a.create({bodyA:b,bodyB:S,pointA:{x:0,y:15*n},pointB:{x:0,y:-25*n},stiffness:.6,render:{visible:!1}}),F=a.create({bodyA:w,bodyB:C,pointA:{x:0,y:15*n},pointB:{x:0,y:-25*n},stiffness:.6,render:{visible:!1}}),V=a.create({bodyA:A,bodyB:B,pointA:{x:0,y:20*n},pointB:{x:0,y:-20*n},stiffness:.6,render:{visible:!1}}),D=a.create({bodyA:k,bodyB:R,pointA:{x:0,y:20*n},pointB:{x:0,y:-20*n},stiffness:.6,render:{visible:!1}}),L=a.create({bodyA:h,pointA:{x:0,y:25*n},pointB:{x:0,y:-35*n},bodyB:M,stiffness:.6,render:{visible:!1}}),O=a.create({bodyA:B,bodyB:R,stiffness:.01,render:{visible:!1}});return s.create({bodies:[M,h,C,w,S,b,B,R,A,k],constraints:[F,T,I,P,L,V,D,E,_,O]})},r.ragdoll.title="Ragdoll",r.ragdoll.for=">=0.14.2",e.exports=r.ragdoll},djnZ:function(e,t,n){var r=r||{};r.compound=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Constraint,i=Matter.Composite,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAxes:!0,showConvexHulls:!0}});t.run(u);var p=n.create();n.run(p,l);var m=200,f=200,v=200,g=c.rectangle(f,v,m,m/5),y=c.rectangle(f,v,m/5,m,{render:g.render}),x=r.create({parts:[g,y]});m=150,f=400,v=300;var h=c.circle(f,v,30),M=c.circle(f+m,v,30),b=c.circle(f+m,v+m,30),S=c.circle(f,v+m,30),w=r.create({parts:[h,M,b,S]}),C=o.create({pointA:{x:400,y:100},bodyB:w,pointB:{x:0,y:0}});i.add(d,[x,w,C,c.rectangle(400,600,800,50.5,{isStatic:!0})]);var A=s.create(u.canvas),B=a.create(l,{mouse:A,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(d,B),u.mouse=A,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.compound.title="Compound Bodies",r.compound.for=">=0.14.2",e.exports=r.compound},elWf:function(e,t,n){var r=r||{};r.rounded=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.MouseConstraint,o=Matter.Mouse,i=Matter.Composite,a=Matter.Bodies,s=e.create(),c=s.world,l=t.create({element:document.body,engine:s,options:{width:800,height:600,showAxes:!0}});t.run(l);var d=n.create();n.run(d,s),i.add(c,[a.rectangle(400,0,800,50,{isStatic:!0}),a.rectangle(400,600,800,50,{isStatic:!0}),a.rectangle(800,300,50,600,{isStatic:!0}),a.rectangle(0,300,50,600,{isStatic:!0})]),i.add(c,[a.rectangle(200,200,100,100,{chamfer:{radius:20}}),a.rectangle(300,200,100,100,{chamfer:{radius:[90,0,0,0]}}),a.rectangle(400,200,200,200,{chamfer:{radius:[150,20,40,20]}}),a.rectangle(200,200,200,200,{chamfer:{radius:[150,20,150,20]}}),a.rectangle(300,200,200,50,{chamfer:{radius:[25,25,0,0]}}),a.polygon(200,100,8,80,{chamfer:{radius:30}}),a.polygon(300,100,5,80,{chamfer:{radius:[10,40,20,40,10]}}),a.polygon(400,200,3,50,{chamfer:{radius:[20,0,20]}})]);var u=o.create(l.canvas),p=r.create(s,{mouse:u,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(c,p),l.mouse=u,t.lookAt(l,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:s,runner:d,render:l,canvas:l.canvas,stop:function(){Matter.Render.stop(l),Matter.Runner.stop(d)}}},r.rounded.title="Rounded Corners (Chamfering)",r.rounded.for=">=0.14.2",e.exports=r.rounded},ga9t:function(e,t,n){var r={};e.exports=r;var o=n("0kzT"),i=n("m6Dm"),a=n("yw0d"),s=n("Tgw/"),c=n("JKEF"),l=n("571F");r._warming=.4,r._torqueDampen=1,r._minLength=1e-6,r.create=function(e){var t=e;t.bodyA&&!t.pointA&&(t.pointA={x:0,y:0}),t.bodyB&&!t.pointB&&(t.pointB={x:0,y:0});var n=t.bodyA?i.add(t.bodyA.position,t.pointA):t.pointA,r=t.bodyB?i.add(t.bodyB.position,t.pointB):t.pointB,o=i.magnitude(i.sub(n,r));t.length=void 0!==t.length?t.length:o,t.id=t.id||l.nextId(),t.label=t.label||"Constraint",t.type="constraint",t.stiffness=t.stiffness||(t.length>0?1:.7),t.damping=t.damping||0,t.angularStiffness=t.angularStiffness||0,t.angleA=t.bodyA?t.bodyA.angle:t.angleA,t.angleB=t.bodyB?t.bodyB.angle:t.angleB,t.plugin={};var a={visible:!0,lineWidth:2,strokeStyle:"#ffffff",type:"line",anchors:!0};return 0===t.length&&t.stiffness>.1?(a.type="pin",a.anchors=!1):t.stiffness<.9&&(a.type="spring"),t.render=l.extend(a,t.render),t},r.preSolveAll=function(e){for(var t=0;t0&&(u.position.x+=l.x,u.position.y+=l.y),0!==l.angle&&(o.rotate(u.vertices,l.angle,n.position),c.rotate(u.axes,l.angle),d>0&&i.rotateAbout(u.position,l.angle,n.position,u.position)),s.update(u.bounds,u.vertices,n.velocity)}l.angle*=r._warming,l.x*=r._warming,l.y*=r._warming}}},r.pointAWorld=function(e){return{x:(e.bodyA?e.bodyA.position.x:0)+e.pointA.x,y:(e.bodyA?e.bodyA.position.y:0)+e.pointA.y}},r.pointBWorld=function(e){return{x:(e.bodyB?e.bodyB.position.x:0)+e.pointB.x,y:(e.bodyB?e.bodyB.position.y:0)+e.pointB.y}}},ibhy:function(e,t,n){var r=r||{};r.mixed=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Common,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAngleIndicator:!0}});t.run(u);var p=n.create();n.run(p,l);var m=r.stack(20,20,10,5,0,0,(function(e,t){var n=Math.round(o.random(1,8)),r=null;switch(n>2&&o.random()>.7&&(r={radius:10}),Math.round(o.random(0,1))){case 0:return o.random()<.8?c.rectangle(e,t,o.random(25,50),o.random(25,50),{chamfer:r}):c.rectangle(e,t,o.random(80,120),o.random(25,30),{chamfer:r});case 1:return c.polygon(e,t,n,o.random(25,50),{chamfer:r})}}));s.add(d,m),s.add(d,[c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var f=a.create(u.canvas),v=i.create(l,{mouse:f,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,v),u.mouse=f,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.mixed.title="Mixed Shapes",r.mixed.for=">=0.14.2",e.exports=r.mixed},"ix+/":function(e,t,n){var r={};e.exports=r;var o=n("571F"),i=n("Z/YF");r.create=function(e){return o.extend({bodies:[],pairs:null},e)},r.setBodies=function(e,t){e.bodies=t.slice(0)},r.clear=function(e){e.bodies=[]},r.collisions=function(e){var t,n,o=[],a=e.pairs,s=e.bodies,c=s.length,l=r.canCollide,d=i.collides;for(s.sort(r._compareBoundsX),t=0;tm)break;if(!(fk.max.y)&&(!g||!h.isStatic&&!h.isSleeping)&&l(u.collisionFilter,h.collisionFilter)){var M=h.parts.length;if(x&&1===M)(A=d(u,h,a))&&o.push(A);else for(var b=M>1?1:0,S=y>1?1:0;Sk.max.x||p.max.xk.max.y||(A=d(w,B,a))&&o.push(A)}}}}return o},r.canCollide=function(e,t){return e.group===t.group&&0!==e.group?e.group>0:0!=(e.mask&t.category)&&0!=(t.mask&e.category)},r._compareBoundsX=function(e,t){return e.bounds.min.x-t.bounds.min.x}},k7Ch:function(e,t,n){var r=r||{};r.gyro=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Common,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAngleIndicator:!0}});t.run(u);var p=n.create();n.run(p,l);var m=r.stack(20,20,10,5,0,0,(function(e,t){var n=Math.round(o.random(1,8)),r=null;switch(n>2&&o.random()>.7&&(r={radius:10}),Math.round(o.random(0,1))){case 0:return o.random()<.8?c.rectangle(e,t,o.random(25,50),o.random(25,50),{chamfer:r}):c.rectangle(e,t,o.random(80,120),o.random(25,30),{chamfer:r});case 1:return c.polygon(e,t,n,o.random(25,50),{chamfer:r})}}));if(s.add(d,[m,c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]),"undefined"!=typeof window){var f=function(e){var t=void 0!==window.orientation?window.orientation:0,n=l.gravity;0===t?(n.x=o.clamp(e.gamma,-90,90)/90,n.y=o.clamp(e.beta,-90,90)/90):180===t?(n.x=o.clamp(e.gamma,-90,90)/90,n.y=o.clamp(-e.beta,-90,90)/90):90===t?(n.x=o.clamp(e.beta,-90,90)/90,n.y=o.clamp(-e.gamma,-90,90)/90):-90===t&&(n.x=o.clamp(-e.beta,-90,90)/90,n.y=o.clamp(e.gamma,-90,90)/90)};window.addEventListener("deviceorientation",f)}var v=a.create(u.canvas),g=i.create(l,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,g),u.mouse=v,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p),"undefined"!=typeof window&&window.removeEventListener("deviceorientation",f)}}},r.gyro.title="Gyroscope",r.gyro.for=">=0.14.2",e.exports=r.gyro},lWug:function(e,t,n){var r={};e.exports=r;var o=n("571F"),i=n("wAS/"),a=n("Tgw/"),s=n("yTB+"),c=n("m6Dm"),l=n("I5nt");!function(){var e,t;"undefined"!=typeof window&&(e=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout((function(){e(o.now())}),1e3/60)},t=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame),r._goodFps=30,r._goodDelta=1e3/60,r.create=function(e){var t={controller:r,engine:null,element:null,canvas:null,mouse:null,frameRequestId:null,timing:{historySize:60,delta:0,deltaHistory:[],lastTime:0,lastTimestamp:0,lastElapsed:0,timestampElapsed:0,timestampElapsedHistory:[],engineDeltaHistory:[],engineElapsedHistory:[],elapsedHistory:[]},options:{width:800,height:600,pixelRatio:1,background:"#14151f",wireframeBackground:"#14151f",hasBounds:!!e.bounds,enabled:!0,wireframes:!0,showSleeping:!0,showDebug:!1,showStats:!1,showPerformance:!1,showBounds:!1,showVelocity:!1,showCollisions:!1,showSeparations:!1,showAxes:!1,showPositions:!1,showAngleIndicator:!1,showIds:!1,showVertexNumbers:!1,showConvexHulls:!1,showInternalEdges:!1,showMousePosition:!1}},n=o.extend(t,e);return n.canvas&&(n.canvas.width=n.options.width||n.canvas.width,n.canvas.height=n.options.height||n.canvas.height),n.mouse=e.mouse,n.engine=e.engine,n.canvas=n.canvas||u(n.options.width,n.options.height),n.context=n.canvas.getContext("2d"),n.textures={},n.bounds=n.bounds||{min:{x:0,y:0},max:{x:n.canvas.width,y:n.canvas.height}},n.options.showBroadphase=!1,1!==n.options.pixelRatio&&r.setPixelRatio(n,n.options.pixelRatio),o.isElement(n.element)?n.element.appendChild(n.canvas):n.canvas.parentNode||o.log("Render.create: options.element was undefined, render.canvas was created but not appended","warn"),n},r.run=function(t){!function o(i){t.frameRequestId=e(o),n(t,i),r.world(t,i),(t.options.showStats||t.options.showDebug)&&r.stats(t,t.context,i),(t.options.showPerformance||t.options.showDebug)&&r.performance(t,t.context,i)}()},r.stop=function(e){t(e.frameRequestId)},r.setPixelRatio=function(e,t){var n=e.options,r=e.canvas;"auto"===t&&(t=p(r)),n.pixelRatio=t,r.setAttribute("data-pixel-ratio",t),r.width=n.width*t,r.height=n.height*t,r.style.width=n.width+"px",r.style.height=n.height+"px"},r.lookAt=function(e,t,n,r){r=void 0===r||r,t=o.isArray(t)?t:[t],n=n||{x:0,y:0};for(var i={min:{x:1/0,y:1/0},max:{x:-1/0,y:-1/0}},a=0;ai.max.x&&(i.max.x=d.x),c.yi.max.y&&(i.max.y=d.y))}var u=i.max.x-i.min.x+2*n.x,p=i.max.y-i.min.y+2*n.y,m=e.canvas.height,f=e.canvas.width/m,v=u/p,g=1,y=1;v>f?y=v/f:g=f/v,e.options.hasBounds=!0,e.bounds.min.x=i.min.x,e.bounds.max.x=i.min.x+u*g,e.bounds.min.y=i.min.y,e.bounds.max.y=i.min.y+p*y,r&&(e.bounds.min.x+=.5*u-u*g*.5,e.bounds.max.x+=.5*u-u*g*.5,e.bounds.min.y+=.5*p-p*y*.5,e.bounds.max.y+=.5*p-p*y*.5),e.bounds.min.x-=n.x,e.bounds.max.x-=n.x,e.bounds.min.y-=n.y,e.bounds.max.y-=n.y,e.mouse&&(l.setScale(e.mouse,{x:(e.bounds.max.x-e.bounds.min.x)/e.canvas.width,y:(e.bounds.max.y-e.bounds.min.y)/e.canvas.height}),l.setOffset(e.mouse,e.bounds.min))},r.startViewTransform=function(e){var t=e.bounds.max.x-e.bounds.min.x,n=e.bounds.max.y-e.bounds.min.y,r=t/e.options.width,o=n/e.options.height;e.context.setTransform(e.options.pixelRatio/r,0,0,e.options.pixelRatio/o,0,0),e.context.translate(-e.bounds.min.x,-e.bounds.min.y)},r.endViewTransform=function(e){e.context.setTransform(e.options.pixelRatio,0,0,e.options.pixelRatio,0,0)},r.world=function(e,t){var n,d=o.now(),u=e.engine,p=u.world,m=e.canvas,v=e.context,g=e.options,y=e.timing,x=i.allBodies(p),h=i.allConstraints(p),M=g.wireframes?g.wireframeBackground:g.background,b=[],S=[],w={timestamp:u.timing.timestamp};if(s.trigger(e,"beforeRender",w),e.currentBackground!==M&&f(e,M),v.globalCompositeOperation="source-in",v.fillStyle="transparent",v.fillRect(0,0,m.width,m.height),v.globalCompositeOperation="source-over",g.hasBounds){for(n=0;n1?1:0;a1?1:0;s1?1:0;i1?1:0;s1?1:0;i1?1:0;i1?1:0;o0)){var d=r.activeContacts[0].vertex.x,u=r.activeContacts[0].vertex.y;2===r.activeContacts.length&&(d=(r.activeContacts[0].vertex.x+r.activeContacts[1].vertex.x)/2,u=(r.activeContacts[0].vertex.y+r.activeContacts[1].vertex.y)/2),o.bodyB===o.supports[0].body||!0===o.bodyA.isStatic?s.moveTo(d-8*o.normal.x,u-8*o.normal.y):s.moveTo(d+8*o.normal.x,u+8*o.normal.y),s.lineTo(d,u)}c.wireframes?s.strokeStyle="rgba(255,165,0,0.7)":s.strokeStyle="orange",s.lineWidth=1,s.stroke()},r.separations=function(e,t,n){var r,o,i,a,s,c=n,l=e.options;for(c.beginPath(),s=0;s0&&c.trigger(e,"collisionStart",{pairs:g.collisionStart}),i.preSolvePosition(g.list),m=0;m0&&c.trigger(e,"collisionActive",{pairs:g.collisionActive}),g.collisionEnd.length>0&&c.trigger(e,"collisionEnd",{pairs:g.collisionEnd}),r._bodiesClearForces(M),c.trigger(e,"afterUpdate",h),e.timing.lastElapsed=u.now()-p,e},r.merge=function(e,t){if(u.extend(e,t),t.world){e.world=t.world,r.clear(e);for(var n=l.allBodies(e.world),i=0;i0&&o.area(B)1?(f=a.create(i.extend({parts:v.slice(0)},r)),a.setPosition(f,{x:e,y:t}),f):v[0]}},"pm/U":function(e,t,n){var r={};e.exports=r;var o=n("2Og8"),i=n("571F");r.name="matter-js",r.version="*",r.uses=[],r.used=[],r.use=function(){o.use(r,Array.prototype.slice.call(arguments))},r.before=function(e,t){return e=e.replace(/^Matter./,""),i.chainPathBefore(r,e,t)},r.after=function(e,t){return e=e.replace(/^Matter./,""),i.chainPathAfter(r,e,t)}},q4y7:function(e,t,n){var r={};e.exports=r;var o=n("0kzT"),i=n("yw0d"),a=n("I5nt"),s=n("yTB+"),c=n("ix+/"),l=n("ga9t"),d=n("wAS/"),u=n("571F"),p=n("Tgw/");r.create=function(e,t){var n=(e?e.mouse:null)||(t?t.mouse:null);n||(e&&e.render&&e.render.canvas?n=a.create(e.render.canvas):t&&t.element?n=a.create(t.element):(n=a.create(),u.warn("MouseConstraint.create: options.mouse was undefined, options.element was undefined, may not function as expected")));var o={type:"mouseConstraint",mouse:n,element:null,body:null,constraint:l.create({label:"Mouse Constraint",pointA:n.position,pointB:{x:0,y:0},length:.01,stiffness:.1,angularStiffness:1,render:{strokeStyle:"#90EE90",lineWidth:3}}),collisionFilter:{category:1,mask:4294967295,group:0}},i=u.extend(o,t);return s.on(e,"beforeUpdate",(function(){var t=d.allBodies(e.world);r.update(i,t),r._triggerEvents(i)})),i},r.update=function(e,t){var n=e.mouse,r=e.constraint,a=e.body;if(0===n.button){if(r.bodyB)i.set(r.bodyB,!1),r.pointA=n.position;else for(var l=0;l1?1:0;d=0.14.2",r.cloth.cloth=function(e,t,n,r,o,i,a,s,c,l){var d=Matter.Body,u=Matter.Bodies,p=Matter.Common,m=Matter.Composites,f=d.nextGroup(!0);c=p.extend({inertia:1/0,friction:1e-5,collisionFilter:{group:f},render:{visible:!1}},c),l=p.extend({stiffness:.06,render:{type:"line",anchors:!1}},l);var v=m.stack(e,t,n,r,o,i,(function(e,t){return u.circle(e,t,s,c)}));return m.mesh(v,n,r,a,l),v.label="Cloth Body",v},e.exports=r.cloth},t8gT:function(e,t,n){var r={};e.exports=r;var o=n("9K63");r.create=function(e,t){var n=e.bodyA,o=e.bodyB,i={id:r.id(n,o),bodyA:n,bodyB:o,collision:e,contacts:[],activeContacts:[],separation:0,isActive:!0,confirmedActive:!0,isSensor:n.isSensor||o.isSensor,timeCreated:t,timeUpdated:t,inverseMass:0,friction:0,frictionStatic:0,restitution:0,slop:0};return r.update(i,e,t),i},r.update=function(e,t,n){var r=e.contacts,i=t.supports,a=e.activeContacts,s=t.parentA,c=t.parentB,l=s.vertices.length;e.isActive=!0,e.timeUpdated=n,e.collision=t,e.separation=t.depth,e.inverseMass=s.inverseMass+c.inverseMass,e.friction=s.frictionc.frictionStatic?s.frictionStatic:c.frictionStatic,e.restitution=s.restitution>c.restitution?s.restitution:c.restitution,e.slop=s.slop>c.slop?s.slop:c.slop,t.pair=e,a.length=0;for(var d=0;d.35?c.rectangle(e,t,64,64,{render:{strokeStyle:"#ffffff",sprite:{texture:"./img/box.png"}}}):c.circle(e,t,46,{density:5e-4,frictionAir:.06,restitution:.3,friction:.01,render:{sprite:{texture:"./img/ball.png"}}})}));s.add(d,f);var v=a.create(u.canvas),g=i.create(l,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,g),u.mouse=v,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.sprites.title="Sprites",r.sprites.for=">=0.14.2",e.exports=r.sprites},ttsO:function(e,t,n){var r={};e.exports=r;var o=n("t8gT"),i=n("571F");r.create=function(e){return i.extend({table:{},list:[],collisionStart:[],collisionActive:[],collisionEnd:[]},e)},r.update=function(e,t,n){var r,i,a,s,c=e.list,l=c.length,d=e.table,u=t.length,p=e.collisionStart,m=e.collisionEnd,f=e.collisionActive;for(p.length=0,m.length=0,f.length=0,s=0;s=0?i(s,false):a(s,false)},vSND:function(e,t,n){var r=r||{};r.stats=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Common,o=Matter.Composites,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.World,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showStats:!0,showPerformance:!0}});t.run(u);var p=n.create();n.run(p,l);var m=o.stack(70,30,13,9,5,5,(function(e,t){return c.circle(e,t,10+20*r.random())}));s.add(d,[m,c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var f=a.create(u.canvas),v=i.create(l,{mouse:f,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,v),u.mouse=f,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.stats.title="Stats & Performance",r.stats.for=">=0.16.1",e.exports=r.stats},"wAS/":function(e,t,n){var r={};e.exports=r;var o=n("yTB+"),i=n("571F"),a=n("Tgw/"),s=n("IbIC");r.create=function(e){return i.extend({id:i.nextId(),type:"composite",parent:null,isModified:!1,bodies:[],constraints:[],composites:[],label:"Composite",plugin:{},cache:{allBodies:null,allConstraints:null,allComposites:null}},e)},r.setModified=function(e,t,n,o){if(e.isModified=t,t&&e.cache&&(e.cache.allBodies=null,e.cache.allConstraints=null,e.cache.allComposites=null),n&&e.parent&&r.setModified(e.parent,t,n,o),o)for(var i=0;i=0.14.2",e.exports=r.bridge},xoNv:function(e,t,n){var r=r||{};r.newtonsCradle=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,o=Matter.Body,i=(Matter.Composites,Matter.MouseConstraint),a=Matter.Mouse,s=Matter.Composite,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showVelocity:!0}});t.run(d);var u=n.create();n.run(u,c);var p=r.newtonsCradle.newtonsCradle(280,100,5,30,200);s.add(l,p),o.translate(p.bodies[0],{x:-180,y:-100}),p=r.newtonsCradle.newtonsCradle(280,380,7,20,140),s.add(l,p),o.translate(p.bodies[0],{x:-140,y:-100});var m=a.create(d.canvas),f=i.create(c,{mouse:m,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:50},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.newtonsCradle.title="Newton's Cradle",r.newtonsCradle.for=">=0.14.2",r.newtonsCradle.newtonsCradle=function(e,t,n,r,o){for(var i=Matter.Composite,a=Matter.Constraint,s=Matter.Bodies,c=i.create({label:"Newtons Cradle"}),l=0;l0){n||(n={}),r=t.split(" ");for(var l=0;l=0.14.2",e.exports=r.sensors},yw0d:function(e,t,n){var r={};e.exports=r;var o=n("yTB+");r._motionWakeThreshold=.18,r._motionSleepThreshold=.08,r._minBias=.9,r.update=function(e,t){for(var n=t*t*t,o=0;o0&&i.motion=i.sleepThreshold&&r.set(i,!0)):i.sleepCounter>0&&(i.sleepCounter-=1)}else r.set(i,!1)}},r.afterCollisions=function(e,t){for(var n=t*t*t,o=0;or._motionWakeThreshold*n&&r.set(l,!1)}}}},r.set=function(e,t){var n=e.isSleeping;t?(e.isSleeping=!0,e.sleepCounter=e.sleepThreshold,e.positionImpulse.x=0,e.positionImpulse.y=0,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.anglePrev=e.angle,e.speed=0,e.angularSpeed=0,e.motion=0,n||o.trigger(e,"sleepStart")):(e.isSleeping=!1,e.sleepCounter=0,n&&o.trigger(e,"sleepEnd"))}},zc7U:function(e,t,n){var r=r||{};r.circleStack=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.MouseConstraint,i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showAngleIndicator:!0}});t.run(d);var u=n.create();n.run(u,c);var p=r.stack(100,179,10,10,20,0,(function(e,t){return s.circle(e,t,20)}));a.add(l,[s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(400,600,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0}),p]);var m=i.create(d.canvas),f=o.create(c,{mouse:m,constraint:{stiffness:.2,render:{visible:!1}}});return a.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.circleStack.title="Circle Stack",r.circleStack.for=">=0.14.2",e.exports=r.circleStack}},[["uZME",1,2,3,4,5]]])})); \ No newline at end of file diff --git a/demo/js/matter-demo.e7da73.min.js b/demo/js/matter-demo.e7da73.min.js deleted file mode 100644 index fd24318..0000000 --- a/demo/js/matter-demo.e7da73.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * matter-demo bundle 0.17.1 by @liabru - * http://brm.io/matter-js/ - * License MIT - */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("MatterDemo",[],t):"object"==typeof exports?exports.MatterDemo=t():e.MatterDemo=t()}(this,(function(){return(this.webpackJsonpMatterDemo=this.webpackJsonpMatterDemo||[]).push([[0],{"+QOk":function(e,t,n){var r=r||{};r.timescale=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Events,i=Matter.Composite,a=Matter.Composites,s=Matter.Common,c=Matter.MouseConstraint,l=Matter.Mouse,d=Matter.Bodies,u=e.create(),p=u.world,m=t.create({element:document.body,engine:u,options:{width:800,height:600,showAngleIndicator:!0}});t.run(m);var f=n.create();n.run(f,u),i.add(p,[d.rectangle(400,0,800,50,{isStatic:!0}),d.rectangle(400,600,800,50,{isStatic:!0}),d.rectangle(800,300,50,600,{isStatic:!0}),d.rectangle(0,300,50,600,{isStatic:!0})]);var v=1,g=0;o.on(u,"afterUpdate",(function(e){u.timing.timeScale+=.05*(v-u.timing.timeScale),(g+=1)>=90&&(v=v<1?1:.05,function(e){for(var t=i.allBodies(e.world),n=0;n=500){var a=.05*o.mass;r.applyForce(o,o.position,{x:(a+s.random()*a)*s.choose([1,-1]),y:-a+s.random()*-a})}}}(u),g=0)}));var y={frictionAir:0,friction:1e-4,restitution:.8};i.add(p,a.stack(20,100,15,3,20,40,(function(e,t){return d.circle(e,t,s.random(10,20),y)}))),i.add(p,a.stack(50,50,8,3,0,0,(function(e,t){switch(Math.round(s.random(0,1))){case 0:return s.random()<.8?d.rectangle(e,t,s.random(20,50),s.random(20,50),y):d.rectangle(e,t,s.random(80,120),s.random(20,30),y);case 1:return d.polygon(e,t,Math.round(s.random(4,8)),s.random(20,50),y)}})));var x=l.create(m.canvas),h=c.create(u,{mouse:x,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(p,h),m.mouse=x,t.lookAt(m,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:u,runner:f,render:m,canvas:m.canvas,stop:function(){Matter.Render.stop(m),Matter.Runner.stop(f)}}},r.timescale.title="Time Scaling",r.timescale.for=">=0.14.2",e.exports=r.timescale},"+jwT":function(e,t,n){var r=r||{};r.constraints=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=(Matter.Composites,Matter.Common,Matter.Constraint),o=Matter.MouseConstraint,i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showAngleIndicator:!0}});t.run(d);var u=n.create();n.run(u,c);var p=s.polygon(150,200,5,30),m=r.create({pointA:{x:150,y:100},bodyB:p,pointB:{x:-10,y:-10}});a.add(l,[p,m]);p=s.polygon(280,100,3,30),m=r.create({pointA:{x:280,y:120},bodyB:p,pointB:{x:-10,y:-7},stiffness:.001});a.add(l,[p,m]);p=s.polygon(400,100,4,30),m=r.create({pointA:{x:400,y:120},bodyB:p,pointB:{x:-10,y:-10},stiffness:.001,damping:.05});a.add(l,[p,m]);p=s.rectangle(600,200,200,20);var f=s.circle(550,150,20);m=r.create({pointA:{x:600,y:200},bodyB:p,length:0});a.add(l,[p,f,m]);p=s.rectangle(500,400,100,20,{collisionFilter:{group:-1}}),f=s.circle(600,400,20,{collisionFilter:{group:-1}}),m=r.create({bodyA:p,bodyB:f});a.add(l,[p,f,m]);var v=s.polygon(100,400,6,20),g=s.polygon(200,400,1,50);m=r.create({bodyA:v,pointA:{x:-10,y:-10},bodyB:g,pointB:{x:-10,y:-10}});a.add(l,[v,g,m]);v=s.polygon(300,400,4,20),g=s.polygon(400,400,3,30),m=r.create({bodyA:v,pointA:{x:-10,y:-10},bodyB:g,pointB:{x:-10,y:-7},stiffness:.001});a.add(l,[v,g,m]);v=s.polygon(500,400,6,30),g=s.polygon(600,400,7,60),m=r.create({bodyA:v,pointA:{x:-10,y:-10},bodyB:g,pointB:{x:-10,y:-10},stiffness:.001,damping:.1});a.add(l,[v,g,m]),a.add(l,[s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(400,600,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0})]);var y=i.create(d.canvas),x=o.create(c,{mouse:y,constraint:{angularStiffness:0,render:{visible:!1}}});return a.add(l,x),d.mouse=y,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.constraints.title="Constraints",r.constraints.for=">=0.14.2",e.exports=r.constraints},"/iAh":function(e,t,n){var r=r||{};r.svg=function(){var e=Matter.Engine,t=Matter.Render,r=Matter.Runner,o=Matter.Common,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Vertices,l=Matter.Svg,d=Matter.Bodies;o.setDecomp(n("Dded"));var u=e.create(),p=u.world,m=t.create({element:document.body,engine:u,options:{width:800,height:600}});t.run(m);var f=r.create();if(r.run(f,u),"undefined"!=typeof fetch){var v=function(e,t){return Array.prototype.slice.call(e.querySelectorAll(t))},g=function(e){return fetch(e).then((function(e){return e.text()})).then((function(e){return(new window.DOMParser).parseFromString(e,"image/svg+xml")}))};["./svg/iconmonstr-check-mark-8-icon.svg","./svg/iconmonstr-paperclip-2-icon.svg","./svg/iconmonstr-puzzle-icon.svg","./svg/iconmonstr-user-icon.svg"].forEach((function(e,t){g(e).then((function(e){var n=o.choose(["#f19648","#f5d259","#f55a3c","#063e7b","#ececd1"]),r=v(e,"path").map((function(e){return c.scale(l.pathToVertices(e,30),.4,.4)}));s.add(p,d.fromVertices(100+150*t,200+50*t,r,{render:{fillStyle:n,strokeStyle:n,lineWidth:1}},!0))}))})),g("./svg/svg.svg").then((function(e){var t=o.choose(["#f19648","#f5d259","#f55a3c","#063e7b","#ececd1"]),n=v(e,"path").map((function(e){return l.pathToVertices(e,30)}));s.add(p,d.fromVertices(400,80,n,{render:{fillStyle:t,strokeStyle:t,lineWidth:1}},!0))}))}else o.warn("Fetch is not available. Could not load SVG.");s.add(p,[d.rectangle(400,0,800,50,{isStatic:!0}),d.rectangle(400,600,800,50,{isStatic:!0}),d.rectangle(800,300,50,600,{isStatic:!0}),d.rectangle(0,300,50,600,{isStatic:!0})]);var y=a.create(m.canvas),x=i.create(u,{mouse:y,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(p,x),m.mouse=y,t.lookAt(m,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:u,runner:f,render:m,canvas:m.canvas,stop:function(){Matter.Render.stop(m),Matter.Runner.stop(f)}}},r.svg.title="Concave SVG Paths",r.svg.for=">0.16.1",e.exports=r.svg},"02te":function(e,t,n){var r=r||{};r.ballPool=function(){try{"undefined"!=typeof MatterWrap?Matter.use("matter-wrap"):Matter.use(n("OPlj"))}catch(e){}var e=Matter.Engine,t=Matter.Render,r=Matter.Runner,o=Matter.Composite,i=Matter.Composites,a=Matter.Common,s=Matter.MouseConstraint,c=Matter.Mouse,l=Matter.Bodies,d=e.create(),u=d.world,p=t.create({element:document.body,engine:d,options:{width:800,height:600,showAngleIndicator:!0}});t.run(p);var m=r.create();r.run(m,d),o.add(u,[l.rectangle(400,600,1200,50.5,{isStatic:!0,render:{fillStyle:"#060a19"}})]);var f=i.stack(100,0,10,8,10,10,(function(e,t){return l.circle(e,t,a.random(15,30),{restitution:.6,friction:.1})}));o.add(u,[f,l.polygon(200,460,3,60),l.polygon(400,460,5,60),l.rectangle(600,460,80,80)]);var v=c.create(p.canvas),g=s.create(d,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});o.add(u,g),p.mouse=v,t.lookAt(p,{min:{x:0,y:0},max:{x:800,y:600}});for(var y=o.allBodies(u),x=0;x1;if(!p||e!=p.x||t!=p.y){p&&r?(m=p.x,f=p.y):(m=0,f=0);var o={x:m+e,y:f+t};!r&&p||(p=o),v.push(o),y=m+e,x=f+t}},b=function(e){var t=e.pathSegTypeAsLetter.toUpperCase();if("Z"!==t){switch(t){case"M":case"L":case"T":case"C":case"S":case"Q":y=e.x,x=e.y;break;case"H":y=e.x;break;case"V":x=e.y}h(y,x,e.pathSegType)}};for(r._svgPathToAbsolute(e),a=e.getTotalLength(),l=[],n=0;n0)return!1}return!0},r.scale=function(e,t,n,i){if(1===t&&1===n)return e;var a,s;i=i||r.centre(e);for(var c=0;c=0?c-1:e.length-1],d=e[c],u=e[(c+1)%e.length],p=t[c0&&(i|=2),3===i)return!1;return 0!==i||null},r.hull=function(e){var t,n,r=[],i=[];for((e=e.slice(0)).sort((function(e,t){var n=e.x-t.x;return 0!==n?n:e.y-t.y})),n=0;n=2&&o.cross3(i[i.length-2],i[i.length-1],t)<=0;)i.pop();i.push(t)}for(n=e.length-1;n>=0;n-=1){for(t=e[n];r.length>=2&&o.cross3(r[r.length-2],r[r.length-1],t)<=0;)r.pop();r.push(t)}return r.pop(),i.pop(),r.concat(i)}},"0mtl":function(e,t,n){var r=r||{};r.catapult=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Constraint,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=Matter.Body,d=Matter.Vector,u=e.create(),p=u.world,m=t.create({element:document.body,engine:u,options:{width:800,height:600,showAngleIndicator:!0,showCollisions:!0,showVelocity:!0}});t.run(m);var f=n.create();n.run(f,u);var v=l.nextGroup(!0),g=r.stack(250,255,1,6,0,0,(function(e,t){return c.rectangle(e,t,30,30)})),y=c.rectangle(400,520,320,20,{collisionFilter:{group:v}});s.add(p,[g,y,c.rectangle(400,600,800,50.5,{isStatic:!0,render:{fillStyle:"#060a19"}}),c.rectangle(250,555,20,50,{isStatic:!0,render:{fillStyle:"#060a19"}}),c.rectangle(400,535,20,80,{isStatic:!0,collisionFilter:{group:v},render:{fillStyle:"#060a19"}}),c.circle(560,100,50,{density:.005}),o.create({bodyA:y,pointB:d.clone(y.position),stiffness:1,length:0})]);var x=a.create(m.canvas),h=i.create(u,{mouse:x,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(p,h),m.mouse=x,t.lookAt(m,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:u,runner:f,render:m,canvas:m.canvas,stop:function(){Matter.Render.stop(m),Matter.Runner.stop(f)}}},r.catapult.title="Catapult",r.catapult.for=">=0.14.2",e.exports=r.catapult},"136C":function(e,t,n){var r=r||{};r.slingshot=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Events,i=Matter.Constraint,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Composite,l=Matter.Bodies,d=e.create(),u=d.world,p=t.create({element:document.body,engine:d,options:{width:800,height:600,showAngleIndicator:!0}});t.run(p);var m=n.create();n.run(m,d);var f=l.rectangle(395,600,815,50,{isStatic:!0,render:{fillStyle:"#060a19"}}),v={density:.004},g=l.polygon(170,450,8,20,v),y=i.create({pointA:{x:170,y:450},bodyB:g,stiffness:.05}),x=r.pyramid(500,300,9,10,0,0,(function(e,t){return l.rectangle(e,t,25,40)})),h=l.rectangle(610,250,200,20,{isStatic:!0,render:{fillStyle:"#060a19"}}),b=r.pyramid(550,0,5,10,0,0,(function(e,t){return l.rectangle(e,t,25,40)}));c.add(d.world,[f,x,h,b,g,y]),o.on(d,"afterUpdate",(function(){-1===S.mouse.button&&(g.position.x>190||g.position.y<430)&&(g=l.polygon(170,450,7,20,v),c.add(d.world,g),y.bodyB=g)}));var M=s.create(p.canvas),S=a.create(d,{mouse:M,constraint:{stiffness:.2,render:{visible:!1}}});return c.add(u,S),p.mouse=M,t.lookAt(p,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:d,runner:m,render:p,canvas:p.canvas,stop:function(){Matter.Render.stop(p),Matter.Runner.stop(m)}}},r.slingshot.title="Slingshot",r.slingshot.for=">=0.14.2",e.exports=r.slingshot},"2Og8":function(e,t,n){var r={};e.exports=r;var o=n("571F");r._registry={},r.register=function(e){if(r.isPlugin(e)||o.warn("Plugin.register:",r.toString(e),"does not implement all required fields."),e.name in r._registry){var t=r._registry[e.name],n=r.versionParse(e.version).number,i=r.versionParse(t.version).number;n>i?(o.warn("Plugin.register:",r.toString(t),"was upgraded to",r.toString(e)),r._registry[e.name]=e):n-1},r.isFor=function(e,t){var n=e.for&&r.dependencyParse(e.for);return!e.for||t.name===n.name&&r.versionSatisfies(t.version,n.range)},r.use=function(e,t){if(e.uses=(e.uses||[]).concat(t||[]),0!==e.uses.length){for(var n=r.dependencies(e),i=o.topologicalSort(n),a=[],s=0;s0&&o.info(a.join(" "))}else o.warn("Plugin.use:",r.toString(e),"does not specify any dependencies to install.")},r.dependencies=function(e,t){var n=r.dependencyParse(e),i=n.name;if(!(i in(t=t||{}))){e=r.resolve(e)||e,t[i]=o.map(e.uses||[],(function(t){r.isPlugin(t)&&r.register(t);var i=r.dependencyParse(t),a=r.resolve(t);return a&&!r.versionSatisfies(a.version,i.range)?(o.warn("Plugin.dependencies:",r.toString(a),"does not satisfy",r.toString(i),"used by",r.toString(n)+"."),a._warned=!0,e._warned=!0):a||(o.warn("Plugin.dependencies:",r.toString(t),"used by",r.toString(n),"could not be resolved."),e._warned=!0),i.name}));for(var a=0;a=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-]+)?$/;t.test(e)||o.warn("Plugin.versionParse:",e,"is not a valid version or range.");var n=t.exec(e),r=Number(n[4]),i=Number(n[5]),a=Number(n[6]);return{isRange:Boolean(n[1]||n[2]),version:n[3],range:e,operator:n[1]||n[2]||"",major:r,minor:i,patch:a,parts:[r,i,a],prerelease:n[7],number:1e8*r+1e4*i+a}},r.versionSatisfies=function(e,t){t=t||"*";var n=r.versionParse(t),o=r.versionParse(e);if(n.isRange){if("*"===n.operator||"*"===e)return!0;if(">"===n.operator)return o.number>n.number;if(">="===n.operator)return o.number>=n.number;if("~"===n.operator)return o.major===n.major&&o.minor===n.minor&&o.patch>=n.patch;if("^"===n.operator)return n.major>0?o.major===n.major&&o.number>=n.number:n.minor>0?o.minor===n.minor&&o.patch>=n.patch:o.patch===n.patch}return e===t||"*"===e}},"2oV2":function(e,t,n){var r=r||{};r.raycasting=function(){var e=Matter.Engine,t=Matter.Render,r=Matter.Runner,o=Matter.Composite,i=Matter.Composites,a=Matter.Common,s=Matter.Query,c=Matter.MouseConstraint,l=Matter.Mouse,d=Matter.Events,u=Matter.Vertices,p=Matter.Bodies,m=e.create(),f=m.world,v=t.create({element:document.body,engine:m,options:{width:800,height:600,showAngleIndicator:!0}});t.run(v);var g=r.create();r.run(g,m);var y=i.stack(20,20,12,4,0,0,(function(e,t){switch(Math.round(a.random(0,1))){case 0:return a.random()<.8?p.rectangle(e,t,a.random(20,50),a.random(20,50)):p.rectangle(e,t,a.random(80,120),a.random(20,30));case 1:var n=Math.round(a.random(1,8));return n=3===n?4:n,p.polygon(e,t,n,a.random(20,50))}}));a.setDecomp(n("Dded"));var x=u.fromPath("50 0 63 38 100 38 69 59 82 100 50 75 18 100 31 59 0 38 37 38"),h=p.fromVertices(200,200,x);o.add(f,[y,h,p.rectangle(400,0,800,50,{isStatic:!0}),p.rectangle(400,600,800,50,{isStatic:!0}),p.rectangle(800,300,50,600,{isStatic:!0}),p.rectangle(0,300,50,600,{isStatic:!0})]),d.on(v,"afterRender",(function(){var e=M.mouse,n=v.context,r=o.allBodies(m.world),i={x:400,y:100},a=e.position,c=s.ray(r,i,a);t.startViewTransform(v),n.beginPath(),n.moveTo(i.x,i.y),n.lineTo(a.x,a.y),c.length>0?n.strokeStyle="#fff":n.strokeStyle="#555",n.lineWidth=.5,n.stroke();for(var l=0;l0.16.1",e.exports=r.raycasting},"3Slt":function(e,t,n){var r={};e.exports=r;var o=n("0kzT"),i=n("m6Dm"),a=n("571F"),s=n("Tgw/");r._restingThresh=4,r._restingThreshTangent=6,r._positionDampen=.9,r._positionWarming=.8,r._frictionNormalMultiplier=5,r.preSolvePosition=function(e){var t,n,r;for(t=0;tm.friction*m.frictionStatic*F*n&&(V=_,D=a.clamp(m.friction*E*n,-V,V));var O=i.cross(C,y),L=i.cross(A,y),W=b/(v.inverseMass+g.inverseMass+v.inverseInertia*O*O+g.inverseInertia*L*L);if(T*=W,D*=W,P<0&&P*P>r._restingThresh*n)S.normalImpulse=0;else{var j=S.normalImpulse;S.normalImpulse=Math.min(S.normalImpulse+T,0),T=S.normalImpulse-j}if(I*I>r._restingThreshTangent*n)S.tangentImpulse=0;else{var q=S.tangentImpulse;S.tangentImpulse=a.clamp(S.tangentImpulse+D,-V,V),D=S.tangentImpulse-q}o.x=y.x*T+x.x*D,o.y=y.y*T+x.y*D,v.isStatic||v.isSleeping||(v.positionPrev.x+=o.x*v.inverseMass,v.positionPrev.y+=o.y*v.inverseMass,v.anglePrev+=i.cross(C,o)*v.inverseInertia),g.isStatic||g.isSleeping||(g.positionPrev.x-=o.x*g.inverseMass,g.positionPrev.y-=o.y*g.inverseMass,g.anglePrev-=i.cross(A,o)*g.inverseInertia)}}}}},"4XQC":function(e,t,n){var r=r||{};r.staticFriction=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Composites,i=Matter.Events,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Composite,l=Matter.Bodies,d=e.create(),u=d.world,p=t.create({element:document.body,engine:d,options:{width:800,height:600,showVelocity:!0}});t.run(p);var m=n.create();n.run(m,d);var f=l.rectangle(400,500,200,60,{isStatic:!0,chamfer:10,render:{fillStyle:"#060a19"}}),v=-1,g=o.stack(350,170,1,6,0,0,(function(e,t){return l.rectangle(e,t,100,50,{slop:.5,friction:1,frictionStatic:1/0})}));c.add(u,[f,g,l.rectangle(400,0,800,50,{isStatic:!0}),l.rectangle(400,600,800,50,{isStatic:!0}),l.rectangle(800,300,50,600,{isStatic:!0}),l.rectangle(0,300,50,600,{isStatic:!0})]),i.on(d,"beforeUpdate",(function(e){if(!((v+=.014)<0)){var t=400+100*Math.sin(v);r.setVelocity(f,{x:t-f.position.x,y:0}),r.setPosition(f,{x:t,y:f.position.y})}}));var y=s.create(p.canvas),x=a.create(d,{mouse:y,constraint:{stiffness:.2,render:{visible:!1}}});return c.add(u,x),p.mouse=y,t.lookAt(p,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:d,runner:m,render:p,canvas:p.canvas,stop:function(){Matter.Render.stop(p),Matter.Runner.stop(m)}}},r.staticFriction.title="Static Friction",r.staticFriction.for=">=0.14.2",e.exports=r.staticFriction},"4d8i":function(e,t,n){var r={};e.exports=r;var o=n("yTB+"),i=n("nIFq"),a=n("571F");!function(){var e,t,n;("undefined"!=typeof window&&(e=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame,t=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame),e)||(e=function(e){n=setTimeout((function(){e(a.now())}),1e3/60)},t=function(){clearTimeout(n)});r.create=function(e){var t=a.extend({fps:60,correction:1,deltaSampleSize:60,counterTimestamp:0,frameCounter:0,deltaHistory:[],timePrev:null,timeScalePrev:1,frameRequestId:null,isFixed:!1,enabled:!0},e);return t.delta=t.delta||1e3/t.fps,t.deltaMin=t.deltaMin||1e3/t.fps,t.deltaMax=t.deltaMax||1e3/(.5*t.fps),t.fps=1e3/t.delta,t},r.run=function(t,n){return void 0!==t.positionIterations&&(n=t,t=r.create()),function o(i){t.frameRequestId=e(o),i&&t.enabled&&r.tick(t,n,i)}(),t},r.tick=function(e,t,n){var r,a=t.timing,s=1,c={timestamp:a.timestamp};o.trigger(e,"beforeTick",c),e.isFixed?r=e.delta:(r=n-e.timePrev||e.delta,e.timePrev=n,e.deltaHistory.push(r),e.deltaHistory=e.deltaHistory.slice(-e.deltaSampleSize),s=(r=(r=(r=Math.min.apply(null,e.deltaHistory))e.deltaMax?e.deltaMax:r)/e.delta,e.delta=r),0!==e.timeScalePrev&&(s*=a.timeScale/e.timeScalePrev),0===a.timeScale&&(s=0),e.timeScalePrev=a.timeScale,e.correction=s,e.frameCounter+=1,n-e.counterTimestamp>=1e3&&(e.fps=e.frameCounter*((n-e.counterTimestamp)/1e3),e.counterTimestamp=n,e.frameCounter=0),o.trigger(e,"tick",c),o.trigger(e,"beforeUpdate",c),i.update(t,r,s),o.trigger(e,"afterUpdate",c),o.trigger(e,"afterTick",c)},r.stop=function(e){t(e.frameRequestId)},r.start=function(e,t){r.run(e,t)}}()},"52dP":function(e,t,n){var r=r||{};r.terrain=function(){var e=Matter.Engine,t=Matter.Render,r=Matter.Runner,o=Matter.Composites,i=Matter.Common,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Composite,l=Matter.Query,d=Matter.Svg,u=Matter.Bodies;i.setDecomp(n("Dded"));var p=e.create(),m=p.world,f=t.create({element:document.body,engine:p,options:{width:800,height:600}});t.run(f);var v,g=r.create();if(r.run(g,p),"undefined"!=typeof fetch){(v="./svg/terrain.svg",fetch(v).then((function(e){return e.text()})).then((function(e){return(new window.DOMParser).parseFromString(e,"image/svg+xml")}))).then((function(e){var t=function(e,t){return Array.prototype.slice.call(e.querySelectorAll(t))}(e,"path").map((function(e){return d.pathToVertices(e,30)})),n=u.fromVertices(400,350,t,{isStatic:!0,render:{fillStyle:"#060a19",strokeStyle:"#060a19",lineWidth:1}},!0);c.add(m,n);var r={frictionAir:0,friction:1e-4,restitution:.6};c.add(m,o.stack(80,100,20,20,10,10,(function(e,t){if(0===l.point([n],{x:e,y:t}).length)return u.polygon(e,t,5,12,r)})))}))}else i.warn("Fetch is not available. Could not load SVG.");var y=s.create(f.canvas),x=a.create(p,{mouse:y,constraint:{stiffness:.2,render:{visible:!1}}});return c.add(m,x),f.mouse=y,t.lookAt(f,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:p,runner:g,render:f,canvas:f.canvas,stop:function(){Matter.Render.stop(f),Matter.Runner.stop(g)}}},r.terrain.title="Terrain",r.terrain.for=">0.16.1",e.exports=r.terrain},"571F":function(e,t){var n={};e.exports=n,function(){n._nextId=0,n._seed=0,n._nowStartTime=+new Date,n._warnedOnce={},n._decomp=null,n.extend=function(e,t){var r,o;"boolean"==typeof t?(r=2,o=t):(r=1,o=!0);for(var i=r;i0;t--){var r=Math.floor(n.random()*(t+1)),o=e[t];e[t]=e[r],e[r]=o}return e},n.choose=function(e){return e[Math.floor(n.random()*e.length)]},n.isElement=function(e){return"undefined"!=typeof HTMLElement?e instanceof HTMLElement:!!(e&&e.nodeType&&e.nodeName)},n.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)},n.isFunction=function(e){return"function"==typeof e},n.isPlainObject=function(e){return"object"==typeof e&&e.constructor===Object},n.isString=function(e){return"[object String]"===toString.call(e)},n.clamp=function(e,t,n){return en?n:e},n.sign=function(e){return e<0?-1:1},n.now=function(){if("undefined"!=typeof window&&window.performance){if(window.performance.now)return window.performance.now();if(window.performance.webkitNow)return window.performance.webkitNow()}return Date.now?Date.now():new Date-n._nowStartTime},n.random=function(t,n){return n=void 0!==n?n:1,(t=void 0!==t?t:0)+e()*(n-t)};var e=function(){return n._seed=(9301*n._seed+49297)%233280,n._seed/233280};n.colorToNumber=function(e){return 3==(e=e.replace("#","")).length&&(e=e.charAt(0)+e.charAt(0)+e.charAt(1)+e.charAt(1)+e.charAt(2)+e.charAt(2)),parseInt(e,16)},n.logLevel=1,n.log=function(){console&&n.logLevel>0&&n.logLevel<=3&&console.log.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.info=function(){console&&n.logLevel>0&&n.logLevel<=2&&console.info.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.warn=function(){console&&n.logLevel>0&&n.logLevel<=3&&console.warn.apply(console,["matter-js:"].concat(Array.prototype.slice.call(arguments)))},n.warnOnce=function(){var e=Array.prototype.slice.call(arguments).join(" ");n._warnedOnce[e]||(n.warn(e),n._warnedOnce[e]=!0)},n.deprecated=function(e,t,r){e[t]=n.chain((function(){n.warnOnce("🔅 deprecated 🔅",r)}),e[t])},n.nextId=function(){return n._nextId++},n.indexOf=function(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0;n=0.14.2",e.exports=r.pyramid},"6IAi":function(e,t,n){var r=r||{};r.airFriction=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.MouseConstraint,o=Matter.Mouse,i=Matter.Composite,a=Matter.Bodies,s=e.create(),c=s.world,l=t.create({element:document.body,engine:s,options:{width:800,height:600,showVelocity:!0}});t.run(l);var d=n.create();n.run(d,s),i.add(c,[a.rectangle(200,100,60,60,{frictionAir:.001}),a.rectangle(400,100,60,60,{frictionAir:.05}),a.rectangle(600,100,60,60,{frictionAir:.1}),a.rectangle(400,0,800,50,{isStatic:!0}),a.rectangle(400,600,800,50,{isStatic:!0}),a.rectangle(800,300,50,600,{isStatic:!0}),a.rectangle(0,300,50,600,{isStatic:!0})]);var u=o.create(l.canvas),p=r.create(s,{mouse:u,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(c,p),l.mouse=u,t.lookAt(l,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:s,runner:d,render:l,canvas:l.canvas,stop:function(){Matter.Render.stop(l),Matter.Runner.stop(d)}}},r.airFriction.title="Air Friction",r.airFriction.for=">=0.14.2",e.exports=r.airFriction},"71Xj":function(e,t,n){var r=n("akk5");e.exports={demo:function(e,t){var n=r.Demo.create({toolbar:{title:"matter-js"+(t?" ・ dev":""),url:"https://github.com/liabru/matter-js",reset:!0,source:!0,inspector:!0,tools:!0,fullscreen:!0,exampleSelect:!0},tools:{inspector:!0,gui:!0},inline:!1,preventZoom:!0,resetOnOrientation:!0,routing:!0,startExample:"mixed",examples:e});if(window.MatterDemoInstance=n,document.body.appendChild(n.dom.root),document.title="Matter.js Demo"+(t?" ・ Dev":""),r.Demo.start(n),t){var o=n.dom.buttonSource,i=o.cloneNode(!0);i.textContent="⎄",i.title="Compare",i.href="?compare",i.target="",i.className="matter-btn matter-btn-compare",i.addEventListener("click",(function(e){window.location="?compare#"+n.example.id,e.preventDefault()})),o.parentNode.insertBefore(i,o.nextSibling)}}}},"74MQ":function(e,t,n){var r=r||{};r.doublePendulum=function(){var e=Matter.Engine,t=Matter.Events,n=Matter.Render,r=Matter.Runner,o=Matter.Body,i=Matter.Composite,a=Matter.Composites,s=Matter.Constraint,c=Matter.MouseConstraint,l=Matter.Mouse,d=Matter.Bodies,u=Matter.Vector,p=e.create(),m=p.world,f=n.create({element:document.body,engine:p,options:{width:800,height:600,wireframes:!1}});n.run(f);var v=r.create();r.run(v,p);var g=o.nextGroup(!0),y=a.stack(350,160,2,1,-20,0,(function(e,t){return d.rectangle(e,t,200,25,{collisionFilter:{group:g},frictionAir:0,chamfer:5,render:{fillStyle:"transparent",lineWidth:1}})}));p.gravity.scale=.002,a.chain(y,.45,0,-.45,0,{stiffness:.9,length:0,angularStiffness:.7,render:{strokeStyle:"#4a485b"}}),i.add(y,s.create({bodyB:y.bodies[0],pointB:{x:-84,y:0},pointA:{x:y.bodies[0].position.x-84,y:y.bodies[0].position.y},stiffness:.9,length:0,render:{strokeStyle:"#4a485b"}}));var x=y.bodies[1];o.rotate(x,.3*-Math.PI,{x:x.position.x-100,y:x.position.y}),i.add(m,y);var h=[];t.on(f,"afterRender",(function(){h.unshift({position:u.clone(x.position),speed:x.speed}),n.startViewTransform(f),f.context.globalAlpha=.7;for(var e=0;e2e3&&h.pop()}));var b=l.create(f.canvas),M=c.create(p,{mouse:b,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(m,M),f.mouse=b,n.lookAt(f,{min:{x:0,y:0},max:{x:700,y:600}}),{engine:p,runner:v,render:f,canvas:f.canvas,stop:function(){Matter.Render.stop(f),Matter.Runner.stop(v)}}},r.doublePendulum.title="Double Pendulum",r.doublePendulum.for=">0.16.1",e.exports=r.doublePendulum},"7MJU":function(e,t,n){var r=r||{};r.gravity=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Common,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showVelocity:!0,showAngleIndicator:!0}});t.run(u);var p=n.create();n.run(p,l),s.add(d,[c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50.5,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]),l.gravity.y=-1;var m=r.stack(50,120,11,5,0,0,(function(e,t){switch(Math.round(o.random(0,1))){case 0:return o.random()<.8?c.rectangle(e,t,o.random(20,50),o.random(20,50)):c.rectangle(e,t,o.random(80,120),o.random(20,30));case 1:return c.polygon(e,t,Math.round(o.random(1,8)),o.random(20,50))}}));s.add(d,m);var f=a.create(u.canvas),v=i.create(l,{mouse:f,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,v),u.mouse=f,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.gravity.title="Reverse Gravity",r.gravity.for=">0.16.1",e.exports=r.gravity},"9K63":function(e,t){var n={};e.exports=n,n.create=function(e){return{id:n.id(e),vertex:e,normalImpulse:0,tangentImpulse:0}},n.id=function(e){return e.body.id+"_"+e.index}},"9Nbg":function(e,t,n){var r={};e.exports=r;var o=n("wAS/"),i=n("ga9t"),a=n("571F"),s=n("IbIC"),c=n("oT59"),l=a.deprecated;r.stack=function(e,t,n,r,i,a,c){for(var l,d=o.create({label:"Stack"}),u=e,p=t,m=0,f=0;fv&&(v=x),s.translate(y,{x:.5*h,y:.5*x}),u=y.bounds.max.x+i,o.addBody(d,y),l=y,m+=1}else u+=i}p+=v+a,u=e}return d},r.chain=function(e,t,n,r,s,c){for(var l=e.bodies,d=1;d0)for(l=0;l0&&(p=m[l-1+(c-1)*t],o.addConstraint(e,i.create(a.extend({bodyA:p,bodyB:u},s)))),r&&lp||a<(l=p-l)||a>n-1-l))return 1===u&&s.translate(d,{x:(a+(n%2==1?1:-1))*m,y:0}),c(e+(d?a*m:0)+a*i,r,a,l,d,u)}))},r.newtonsCradle=function(e,t,n,r,a){for(var s=o.create({label:"Newtons Cradle"}),l=0;l=0.14.2",e.exports=r.avalanche},"C6Q+":function(e,t,n){var r=r||{};r.events=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Events,i=Matter.Composite,a=Matter.Composites,s=Matter.Common,c=Matter.MouseConstraint,l=Matter.Mouse,d=Matter.Bodies,u=e.create(),p=u.world,m=t.create({element:document.body,engine:u,options:{width:800,height:600,wireframes:!1}});t.run(m);var f=n.create();n.run(f,u),o.on(p,"afterAdd",(function(e){})),o.on(u,"beforeUpdate",(function(e){var t=e.source;e.timestamp%5e3<50&&y(t)})),o.on(u,"collisionStart",(function(e){for(var t=e.pairs,n=0;n=500){var a=.02*o.mass;r.applyForce(o,o.position,{x:(a+s.random()*a)*s.choose([1,-1]),y:-a+s.random()*-a})}}},x=l.create(m.canvas),h=c.create(u,{mouse:x,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(p,h),m.mouse=x,o.on(h,"mousedown",(function(e){var t=e.mouse.position;console.log("mousedown at "+t.x+" "+t.y),y(u)})),o.on(h,"mouseup",(function(e){var t=e.mouse.position;console.log("mouseup at "+t.x+" "+t.y)})),o.on(h,"startdrag",(function(e){console.log("startdrag",e)})),o.on(h,"enddrag",(function(e){console.log("enddrag",e)})),t.lookAt(m,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:u,runner:f,render:m,canvas:m.canvas,stop:function(){Matter.Render.stop(m),Matter.Runner.stop(f)}}},r.events.title="Events",r.events.for=">=0.14.2",e.exports=r.events},Czbd:function(e,t,n){var r=r||{};r.softBody=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,o=(Matter.Composites,Matter.MouseConstraint),i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showAngleIndicator:!1}});t.run(d);var u=n.create();n.run(u,c);var p={friction:.05,frictionStatic:.1,render:{visible:!0}};a.add(l,[r.softBody.softBody(250,100,5,5,0,0,!0,18,p),r.softBody.softBody(400,300,8,3,0,0,!0,15,p),r.softBody.softBody(250,400,4,4,0,0,!0,15,p),s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(400,600,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0})]);var m=i.create(d.canvas),f=o.create(c,{mouse:m,constraint:{stiffness:.9,render:{visible:!1}}});return a.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.softBody.title="Soft Body",r.softBody.for=">=0.14.2",r.softBody.softBody=function(e,t,n,r,o,i,a,s,c,l){var d=Matter.Common,u=Matter.Composites,p=Matter.Bodies;c=d.extend({inertia:1/0},c),l=d.extend({stiffness:.2,render:{type:"line",anchors:!1}},l);var m=u.stack(e,t,n,r,o,i,(function(e,t){return p.circle(e,t,s,c)}));return u.mesh(m,n,r,a,l),m.label="Soft Body",m},e.exports=r.softBody},DeYi:function(e,t,n){var r=r||{};r.friction=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.MouseConstraint,o=Matter.Mouse,i=Matter.Composite,a=Matter.Bodies,s=e.create(),c=s.world,l=t.create({element:document.body,engine:s,options:{width:800,height:600,showVelocity:!0}});t.run(l);var d=n.create();n.run(d,s),i.add(c,[a.rectangle(400,0,800,50,{isStatic:!0}),a.rectangle(400,600,800,50,{isStatic:!0}),a.rectangle(800,300,50,600,{isStatic:!0}),a.rectangle(0,300,50,600,{isStatic:!0})]),i.add(c,[a.rectangle(300,180,700,20,{isStatic:!0,angle:.06*Math.PI,render:{fillStyle:"#060a19"}}),a.rectangle(300,70,40,40,{friction:.001})]),i.add(c,[a.rectangle(300,350,700,20,{isStatic:!0,angle:.06*Math.PI,render:{fillStyle:"#060a19"}}),a.rectangle(300,250,40,40,{friction:5e-4})]),i.add(c,[a.rectangle(300,520,700,20,{isStatic:!0,angle:.06*Math.PI,render:{fillStyle:"#060a19"}}),a.rectangle(300,430,40,40,{friction:0})]);var u=o.create(l.canvas),p=r.create(s,{mouse:u,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(c,p),l.mouse=u,t.lookAt(l,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:s,runner:d,render:l,canvas:l.canvas,stop:function(){Matter.Render.stop(l),Matter.Runner.stop(d)}}},r.friction.title="Friction",r.friction.for=">=0.14.2",e.exports=r.friction},DqtB:function(e,t,n){var r={};e.exports=r;var o=n("0kzT"),i=n("m6Dm");r.collides=function(e,t,n){var a,s,c,l,d=!1;if(n){var u=e.parent,p=t.parent,m=u.speed*u.speed+u.angularSpeed*u.angularSpeed+p.speed*p.speed+p.angularSpeed*p.angularSpeed;d=n&&n.collided&&m<.2,l=n}else l={collided:!1,bodyA:e,bodyB:t};if(n&&d){var f=l.axisBody,v=f===e?t:e,g=[f.axes[n.axisNumber]];if(c=r._overlapAxes(f.vertices,v.vertices,g),l.reused=!0,c.overlap<=0)return l.collided=!1,l}else{if((a=r._overlapAxes(e.vertices,t.vertices,e.axes)).overlap<=0)return l.collided=!1,l;if((s=r._overlapAxes(t.vertices,e.vertices,t.axes)).overlap<=0)return l.collided=!1,l;a.overlapo?o=s:s=0?a.index-1:d.length-1],l.x=o.x-u.x,l.y=o.y-u.y,c=-i.dot(n,l),s=o,o=d[(a.index+1)%d.length],l.x=o.x-u.x,l.y=o.y-u.y,(r=-i.dot(n,l))=0.14.2",e.exports=r.stress},I5nt:function(e,t,n){var r={};e.exports=r;var o=n("571F");r.create=function(e){var t={};return e||o.log("Mouse.create: element was undefined, defaulting to document.body","warn"),t.element=e||document.body,t.absolute={x:0,y:0},t.position={x:0,y:0},t.mousedownPosition={x:0,y:0},t.mouseupPosition={x:0,y:0},t.offset={x:0,y:0},t.scale={x:1,y:1},t.wheelDelta=0,t.button=-1,t.pixelRatio=parseInt(t.element.getAttribute("data-pixel-ratio"),10)||1,t.sourceEvents={mousemove:null,mousedown:null,mouseup:null,mousewheel:null},t.mousemove=function(e){var n=r._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches&&(t.button=0,e.preventDefault()),t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.sourceEvents.mousemove=e},t.mousedown=function(e){var n=r._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches?(t.button=0,e.preventDefault()):t.button=e.button,t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.mousedownPosition.x=t.position.x,t.mousedownPosition.y=t.position.y,t.sourceEvents.mousedown=e},t.mouseup=function(e){var n=r._getRelativeMousePosition(e,t.element,t.pixelRatio);e.changedTouches&&e.preventDefault(),t.button=-1,t.absolute.x=n.x,t.absolute.y=n.y,t.position.x=t.absolute.x*t.scale.x+t.offset.x,t.position.y=t.absolute.y*t.scale.y+t.offset.y,t.mouseupPosition.x=t.position.x,t.mouseupPosition.y=t.position.y,t.sourceEvents.mouseup=e},t.mousewheel=function(e){t.wheelDelta=Math.max(-1,Math.min(1,e.wheelDelta||-e.detail)),e.preventDefault()},r.setElement(t,t.element),t},r.setElement=function(e,t){e.element=t,t.addEventListener("mousemove",e.mousemove),t.addEventListener("mousedown",e.mousedown),t.addEventListener("mouseup",e.mouseup),t.addEventListener("mousewheel",e.mousewheel),t.addEventListener("DOMMouseScroll",e.mousewheel),t.addEventListener("touchmove",e.mousemove),t.addEventListener("touchstart",e.mousedown),t.addEventListener("touchend",e.mouseup)},r.clearSourceEvents=function(e){e.sourceEvents.mousemove=null,e.sourceEvents.mousedown=null,e.sourceEvents.mouseup=null,e.sourceEvents.mousewheel=null,e.wheelDelta=0},r.setOffset=function(e,t){e.offset.x=t.x,e.offset.y=t.y,e.position.x=e.absolute.x*e.scale.x+e.offset.x,e.position.y=e.absolute.y*e.scale.y+e.offset.y},r.setScale=function(e,t){e.scale.x=t.x,e.scale.y=t.y,e.position.x=e.absolute.x*e.scale.x+e.offset.x,e.position.y=e.absolute.y*e.scale.y+e.offset.y},r._getRelativeMousePosition=function(e,t,n){var r,o,i=t.getBoundingClientRect(),a=document.documentElement||document.body.parentNode||document.body,s=void 0!==window.pageXOffset?window.pageXOffset:a.scrollLeft,c=void 0!==window.pageYOffset?window.pageYOffset:a.scrollTop,l=e.changedTouches;return l?(r=l[0].pageX-i.left-s,o=l[0].pageY-i.top-c):(r=e.pageX-i.left-s,o=e.pageY-i.top-c),{x:r/(t.clientWidth/(t.width||t.clientWidth)*n),y:o/(t.clientHeight/(t.height||t.clientHeight)*n)}}},IOLg:function(e,t,n){var r=r||{};r.concave=function(){var e=Matter.Engine,t=Matter.Render,r=Matter.Runner,o=Matter.Composites,i=Matter.Common,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Composite,l=Matter.Vertices,d=Matter.Bodies;i.setDecomp(n("Dded"));var u=e.create(),p=u.world,m=t.create({element:document.body,engine:u,options:{width:800,height:600}});t.run(m);var f=r.create();r.run(f,u),c.add(p,[d.rectangle(400,0,800,50,{isStatic:!0}),d.rectangle(400,600,800,50,{isStatic:!0}),d.rectangle(800,300,50,600,{isStatic:!0}),d.rectangle(0,300,50,600,{isStatic:!0})]);var v=l.fromPath("40 0 40 20 100 20 100 80 40 80 40 100 0 50"),g=l.fromPath("100 0 75 50 100 100 25 100 0 50 25 0"),y=l.fromPath("50 0 63 38 100 38 69 59 82 100 50 75 18 100 31 59 0 38 37 38"),x=l.fromPath("35 7 19 17 14 38 14 58 25 79 45 85 65 84 65 66 46 67 34 59 30 44 33 29 45 23 66 23 66 7 53 7"),h=o.stack(50,50,6,4,10,10,(function(e,t){var n=i.choose(["#f19648","#f5d259","#f55a3c","#063e7b","#ececd1"]);return d.fromVertices(e,t,i.choose([v,g,y,x]),{render:{fillStyle:n,strokeStyle:n,lineWidth:1}},!0)}));c.add(p,h);var b=s.create(m.canvas),M=a.create(u,{mouse:b,constraint:{stiffness:.2,render:{visible:!1}}});return c.add(p,M),m.mouse=b,t.lookAt(m,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:u,runner:f,render:m,canvas:m.canvas,stop:function(){Matter.Render.stop(m),Matter.Runner.stop(f)}}},r.concave.title="Concave",r.concave.for=">0.16.1",e.exports=r.concave},IbIC:function(e,t,n){var r={};e.exports=r;var o=n("0kzT"),i=n("m6Dm"),a=n("yw0d"),s=(n("lWug"),n("571F")),c=n("Tgw/"),l=n("JKEF");!function(){r._inertiaScale=4,r._nextCollidingGroupId=1,r._nextNonCollidingGroupId=-1,r._nextCategory=1,r.create=function(t){var n={id:s.nextId(),type:"body",label:"Body",parts:[],plugin:{},angle:0,vertices:o.fromPath("L 0 0 L 40 0 L 40 40 L 0 40"),position:{x:0,y:0},force:{x:0,y:0},torque:0,positionImpulse:{x:0,y:0},constraintImpulse:{x:0,y:0,angle:0},totalContacts:0,speed:0,angularSpeed:0,velocity:{x:0,y:0},angularVelocity:0,isSensor:!1,isStatic:!1,isSleeping:!1,motion:0,sleepThreshold:60,density:.001,restitution:0,friction:.1,frictionStatic:.5,frictionAir:.01,collisionFilter:{category:1,mask:4294967295,group:0},slop:.05,timeScale:1,render:{visible:!0,opacity:1,strokeStyle:null,fillStyle:null,lineWidth:null,sprite:{xScale:1,yScale:1,xOffset:0,yOffset:0}},events:null,bounds:null,chamfer:null,circleRadius:0,positionPrev:null,anglePrev:0,parent:null,axes:null,area:0,mass:0,inertia:0,_original:null},r=s.extend(n,t);return e(r,t),r},r.nextGroup=function(e){return e?r._nextNonCollidingGroupId--:r._nextCollidingGroupId++},r.nextCategory=function(){return r._nextCategory=r._nextCategory<<1,r._nextCategory};var e=function(e,t){t=t||{},r.set(e,{bounds:e.bounds||c.create(e.vertices),positionPrev:e.positionPrev||i.clone(e.position),anglePrev:e.anglePrev||e.angle,vertices:e.vertices,parts:e.parts||[e],isStatic:e.isStatic,isSleeping:e.isSleeping,parent:e.parent||e}),o.rotate(e.vertices,e.angle,e.position),l.rotate(e.axes,e.angle),c.update(e.bounds,e.vertices,e.velocity),r.set(e,{axes:t.axes||e.axes,area:t.area||e.area,mass:t.mass||e.mass,inertia:t.inertia||e.inertia});var n=e.isStatic?"#14151f":s.choose(["#f19648","#f5d259","#f55a3c","#063e7b","#ececd1"]),a=e.isStatic?"#555":"#ccc",d=e.isStatic&&null===e.render.fillStyle?1:0;e.render.fillStyle=e.render.fillStyle||n,e.render.strokeStyle=e.render.strokeStyle||a,e.render.lineWidth=e.render.lineWidth||d,e.render.sprite.xOffset+=-(e.bounds.min.x-e.position.x)/(e.bounds.max.x-e.bounds.min.x),e.render.sprite.yOffset+=-(e.bounds.min.y-e.position.y)/(e.bounds.max.y-e.bounds.min.y)};r.set=function(e,t,n){var o;for(o in"string"==typeof t&&(o=t,(t={})[o]=n),t)if(Object.prototype.hasOwnProperty.call(t,o))switch(n=t[o],o){case"isStatic":r.setStatic(e,n);break;case"isSleeping":a.set(e,n);break;case"mass":r.setMass(e,n);break;case"density":r.setDensity(e,n);break;case"inertia":r.setInertia(e,n);break;case"vertices":r.setVertices(e,n);break;case"position":r.setPosition(e,n);break;case"angle":r.setAngle(e,n);break;case"velocity":r.setVelocity(e,n);break;case"angularVelocity":r.setAngularVelocity(e,n);break;case"parts":r.setParts(e,n);break;case"centre":r.setCentre(e,n);break;default:e[o]=n}},r.setStatic=function(e,t){for(var n=0;n0&&i.rotateAbout(a.position,n,e.position,a.position)}},r.setVelocity=function(e,t){e.positionPrev.x=e.position.x-t.x,e.positionPrev.y=e.position.y-t.y,e.velocity.x=t.x,e.velocity.y=t.y,e.speed=i.magnitude(e.velocity)},r.setAngularVelocity=function(e,t){e.anglePrev=e.angle-t,e.angularVelocity=t,e.angularSpeed=Math.abs(e.angularVelocity)},r.translate=function(e,t){r.setPosition(e,i.add(e.position,t))},r.rotate=function(e,t,n){if(n){var o=Math.cos(t),i=Math.sin(t),a=e.position.x-n.x,s=e.position.y-n.y;r.setPosition(e,{x:n.x+(a*o-s*i),y:n.y+(a*i+s*o)}),r.setAngle(e,e.angle+t)}else r.setAngle(e,e.angle+t)},r.scale=function(e,t,n,i){var a=0,s=0;i=i||e.position;for(var d=0;d0&&(a+=u.area,s+=u.inertia),u.position.x=i.x+(u.position.x-i.x)*t,u.position.y=i.y+(u.position.y-i.y)*n,c.update(u.bounds,u.vertices,e.velocity)}e.parts.length>1&&(e.area=a,e.isStatic||(r.setMass(e,e.density*a),r.setInertia(e,s))),e.circleRadius&&(t===n?e.circleRadius*=t:e.circleRadius=null)},r.update=function(e,t,n,r){var a=Math.pow(t*n*e.timeScale,2),s=1-e.frictionAir*n*e.timeScale,d=e.position.x-e.positionPrev.x,u=e.position.y-e.positionPrev.y;e.velocity.x=d*s*r+e.force.x/e.mass*a,e.velocity.y=u*s*r+e.force.y/e.mass*a,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.position.x+=e.velocity.x,e.position.y+=e.velocity.y,e.angularVelocity=(e.angle-e.anglePrev)*s*r+e.torque/e.inertia*a,e.anglePrev=e.angle,e.angle+=e.angularVelocity,e.speed=i.magnitude(e.velocity),e.angularSpeed=Math.abs(e.angularVelocity);for(var p=0;p0&&(m.position.x+=e.velocity.x,m.position.y+=e.velocity.y),0!==e.angularVelocity&&(o.rotate(m.vertices,e.angularVelocity,e.position),l.rotate(m.axes,e.angularVelocity),p>0&&i.rotateAbout(m.position,e.angularVelocity,e.position,m.position)),c.update(m.bounds,m.vertices,e.velocity)}},r.applyForce=function(e,t,n){e.force.x+=n.x,e.force.y+=n.y;var r=t.x-e.position.x,o=t.y-e.position.y;e.torque+=r*n.y-o*n.x},r._totalProperties=function(e){for(var t={mass:0,area:0,inertia:0,centre:{x:0,y:0}},n=1===e.parts.length?0:1;n=0.14.2",e.exports=r.sleeping},LS1c:function(e,t,n){var r=r||{};r.wreckingBall=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.MouseConstraint,i=Matter.Mouse,a=Matter.Composite,s=Matter.Constraint,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAngleIndicator:!0}});t.run(u);var p=n.create();n.run(p,l);var m=r.stack(400,175,5,10,0,0,(function(e,t){return c.rectangle(e,t,40,40)}));a.add(d,[m,c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var f=c.circle(100,400,50,{density:.04,frictionAir:.005});a.add(d,f),a.add(d,s.create({pointA:{x:300,y:100},bodyB:f}));var v=i.create(u.canvas),g=o.create(l,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});return a.add(d,g),u.mouse=v,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.wreckingBall.title="Wrecking Ball",r.wreckingBall.for=">=0.14.2",e.exports=r.wreckingBall},Me0I:function(e,t,n){var r=r||{};r.car=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,o=(Matter.Composites,Matter.MouseConstraint),i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showAngleIndicator:!0,showCollisions:!0}});t.run(d);var u=n.create();n.run(u,c),a.add(l,[s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(400,600,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0})]);var p=.9;a.add(l,r.car.car(150,100,150*p,30*p,30*p)),p=.8,a.add(l,r.car.car(350,300,150*p,30*p,30*p)),a.add(l,[s.rectangle(200,150,400,20,{isStatic:!0,angle:.06*Math.PI,render:{fillStyle:"#060a19"}}),s.rectangle(500,350,650,20,{isStatic:!0,angle:.06*-Math.PI,render:{fillStyle:"#060a19"}}),s.rectangle(300,560,600,20,{isStatic:!0,angle:.04*Math.PI,render:{fillStyle:"#060a19"}})]);var m=i.create(d.canvas),f=o.create(c,{mouse:m,constraint:{stiffness:.2,render:{visible:!1}}});return a.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.car.title="Car",r.car.for=">=0.14.2",r.car.car=function(e,t,n,r,o){var i=Matter.Body,a=Matter.Bodies,s=Matter.Composite,c=Matter.Constraint,l=i.nextGroup(!0),d=.5*-n+20,u=.5*n-20,p=s.create({label:"Car"}),m=a.rectangle(e,t,n,r,{collisionFilter:{group:l},chamfer:{radius:.5*r},density:2e-4}),f=a.circle(e+d,t+0,o,{collisionFilter:{group:l},friction:.8}),v=a.circle(e+u,t+0,o,{collisionFilter:{group:l},friction:.8}),g=c.create({bodyB:m,pointB:{x:d,y:0},bodyA:f,stiffness:1,length:0}),y=c.create({bodyB:m,pointB:{x:u,y:0},bodyA:v,stiffness:1,length:0});return s.addBody(p,m),s.addBody(p,f),s.addBody(p,v),s.addConstraint(p,g),s.addConstraint(p,y),p},e.exports=r.car},MhOg:function(e,t,n){var r=r||{};r.views=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Events,o=Matter.Composites,i=Matter.Common,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Composite,l=Matter.Vector,d=Matter.Bounds,u=Matter.Bodies,p=e.create(),m=p.world,f=t.create({element:document.body,engine:p,options:{width:800,height:600,hasBounds:!0,showAngleIndicator:!0}});t.run(f);var v=n.create();n.run(v,p);var g=s.create(f.canvas),y=a.create(p,{mouse:g,constraint:{stiffness:.2,render:{visible:!1}}});c.add(m,y),f.mouse=g;var x=o.stack(20,20,10,4,0,0,(function(e,t){switch(Math.round(i.random(0,1))){case 0:return i.random()<.8?u.rectangle(e,t,i.random(20,50),i.random(20,50)):u.rectangle(e,t,i.random(80,120),i.random(20,30));case 1:var n=Math.round(i.random(1,8));return n=3===n?4:n,u.polygon(e,t,n,i.random(20,50))}}));c.add(m,[x,u.rectangle(400,0,800,50,{isStatic:!0}),u.rectangle(400,600,800,50,{isStatic:!0}),u.rectangle(800,300,50,600,{isStatic:!0}),u.rectangle(0,300,50,600,{isStatic:!0})]);var h={x:.5*f.options.width,y:.5*f.options.height},b={x:-300,y:-300},M={x:1100,y:900},S=1,w={x:1,y:1};return r.on(f,"beforeRender",(function(){p.world;var e,t=y.mouse,n=-.1*t.wheelDelta;0!==n&&(n<0&&w.x>=.6||n>0&&w.x<=1.4)&&(S+=n),Math.abs(w.x-S)>.01&&(n=.2*(S-w.x),w.x+=n,w.y+=n,f.bounds.max.x=f.bounds.min.x+f.options.width*w.x,f.bounds.max.y=f.bounds.min.y+f.options.height*w.y,e={x:f.options.width*n*-.5,y:f.options.height*n*-.5},d.translate(f.bounds,e),s.setScale(t,w),s.setOffset(t,f.bounds.min));var r=l.sub(t.absolute,h),o=l.magnitude(r);if(o>50){var i=l.normalise(r),a=Math.min(10,2e-4*Math.pow(o-50,2));e=l.mult(i,a),f.bounds.min.x+e.xM.x&&(e.x=M.x-f.bounds.max.x),f.bounds.min.y+e.yM.y&&(e.y=M.y-f.bounds.max.y),d.translate(f.bounds,e),s.setOffset(t,f.bounds.min)}})),{engine:p,runner:v,render:f,canvas:f.canvas,stop:function(){Matter.Render.stop(f),Matter.Runner.stop(v)}}},r.views.title="Views",r.views.for=">=0.14.2",e.exports=r.views},NDQ1:function(e,t,n){var r={};e.exports=r;var o=n("m6Dm"),i=n("DqtB"),a=n("Tgw/"),s=n("oT59"),c=n("0kzT");r.collides=function(e,t){for(var n=[],r=0;r=0.14.2",e.exports=r.broadphase},Qnbh:function(e,t,n){var r=r||{};r.stress2=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.MouseConstraint,i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showStats:!0,showPerformance:!0}});t.run(d);var u=n.create();n.run(u,c);var p=r.stack(100,125,25,18,0,0,(function(e,t){return s.rectangle(e,t,25,25)}));a.add(l,[p,s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(400,600,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0})]);var m=i.create(d.canvas),f=o.create(c,{mouse:m,constraint:{stiffness:.2,render:{visible:!1}}});return a.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.stress2.title="Stress 2",r.stress2.for=">=0.14.2",e.exports=r.stress2},Sq1W:function(e,t,n){var r={};e.exports=r;var o=n("t8gT"),i=n("571F");r.create=function(e){return i.extend({buckets:{},pairs:{},pairsList:[],bucketWidth:48,bucketHeight:48},e)},r.update=function(e,t,n,o){var i,a,s,c,l,d=n.world,u=e.buckets,p=!1;for(i=0;id.bounds.max.x||m.bounds.max.yd.bounds.max.y))){var f=r._getRegion(e,m);if(!m.region||f.id!==m.region.id||o){m.region&&!o||(m.region=f);var v=r._regionUnion(f,m.region);for(a=v.startCol;a<=v.endCol;a++)for(s=v.startRow;s<=v.endRow;s++){c=u[l=r._getBucketId(a,s)];var g=a>=f.startCol&&a<=f.endCol&&s>=f.startRow&&s<=f.endRow,y=a>=m.region.startCol&&a<=m.region.endCol&&s>=m.region.startRow&&s<=m.region.endRow;!g&&y&&y&&c&&r._bucketRemoveBody(e,c,m),(m.region===f||g&&!y||o)&&(c||(c=r._createBucket(u,l)),r._bucketAddBody(e,c,m))}m.region=f,p=!0}}}p&&(e.pairsList=r._createActivePairsList(e))},r.clear=function(e){e.buckets={},e.pairs={},e.pairsList=[]},r._regionUnion=function(e,t){var n=Math.min(e.startCol,t.startCol),o=Math.max(e.endCol,t.endCol),i=Math.min(e.startRow,t.startRow),a=Math.max(e.endRow,t.endRow);return r._createRegion(n,o,i,a)},r._getRegion=function(e,t){var n=t.bounds,o=Math.floor(n.min.x/e.bucketWidth),i=Math.floor(n.max.x/e.bucketWidth),a=Math.floor(n.min.y/e.bucketHeight),s=Math.floor(n.max.y/e.bucketHeight);return r._createRegion(o,i,a,s)},r._createRegion=function(e,t,n,r){return{id:e+","+t+","+n+","+r,startCol:e,endCol:t,startRow:n,endRow:r}},r._getBucketId=function(e,t){return"C"+e+"R"+t},r._createBucket=function(e,t){return e[t]=[]},r._bucketAddBody=function(e,t,n){for(var r=0;r0?r.push(n):delete e.pairs[t[o]];return r}},"Tgw/":function(e,t){var n={};e.exports=n,n.create=function(e){var t={min:{x:0,y:0},max:{x:0,y:0}};return e&&n.update(t,e),t},n.update=function(e,t,n){e.min.x=1/0,e.max.x=-1/0,e.min.y=1/0,e.max.y=-1/0;for(var r=0;re.max.x&&(e.max.x=o.x),o.xe.max.y&&(e.max.y=o.y),o.y0?e.max.x+=n.x:e.min.x+=n.x,n.y>0?e.max.y+=n.y:e.min.y+=n.y)},n.contains=function(e,t){return t.x>=e.min.x&&t.x<=e.max.x&&t.y>=e.min.y&&t.y<=e.max.y},n.overlaps=function(e,t){return e.min.x<=t.max.x&&e.max.x>=t.min.x&&e.max.y>=t.min.y&&e.min.y<=t.max.y},n.translate=function(e,t){e.min.x+=t.x,e.max.x+=t.x,e.min.y+=t.y,e.max.y+=t.y},n.shift=function(e,t){var n=e.max.x-e.min.x,r=e.max.y-e.min.y;e.min.x=t.x,e.max.x=t.x+n,e.min.y=t.y,e.max.y=t.y+r}},VZUp:function(e,t,n){var r=r||{};r.manipulation=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Events,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAxes:!0,showCollisions:!0,showConvexHulls:!0}});t.run(u);var p=n.create();n.run(p,l);var m=c.rectangle(100,200,50,50,{isStatic:!0,render:{fillStyle:"#060a19"}}),f=c.rectangle(200,200,50,50),v=c.rectangle(300,200,50,50),g=c.rectangle(400,200,50,50),y=c.rectangle(550,200,50,50),x=c.rectangle(700,200,50,50),h=c.circle(400,100,25,{render:{fillStyle:"#060a19"}}),b=c.rectangle(600,200,120,50,{render:{fillStyle:"#060a19"}}),M=c.rectangle(660,200,50,190,{render:{fillStyle:"#060a19"}}),S=r.create({parts:[b,M],isStatic:!0});s.add(d,[m,f,v,g,y,x,h,S]),s.add(d,[c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var w=0,C=1.01;o.on(l,"beforeUpdate",(function(e){40===(w+=1)&&r.setStatic(h,!0),C>1&&(r.scale(x,C,C),r.scale(S,.995,.995),y.vertices[0].x-=.2,y.vertices[0].y-=.2,y.vertices[1].x+=.2,y.vertices[1].y-=.2,r.setVertices(y,y.vertices));var t=300+100*Math.sin(.002*l.timing.timestamp);r.setVelocity(m,{x:0,y:t-m.position.y}),r.setPosition(m,{x:100,y:t}),r.setVelocity(S,{x:0,y:t-S.position.y}),r.setAngularVelocity(S,.02),r.setPosition(S,{x:600,y:t}),r.rotate(S,.02),w>=90&&(r.setVelocity(f,{x:0,y:-10}),r.setAngle(v,.26*-Math.PI),r.setAngularVelocity(g,.2),w=0,C=1)}));var A=a.create(u.canvas),B=i.create(l,{mouse:A,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,B),u.mouse=A,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.manipulation.title="Manipulation",r.manipulation.for=">=0.14.2",e.exports=r.manipulation},ZRMD:function(e,t,n){var r=r||{};r.restitution=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.MouseConstraint,o=Matter.Mouse,i=Matter.Composite,a=Matter.Bodies,s=e.create(),c=s.world,l=t.create({element:document.body,engine:s,options:{width:800,height:600,showAngleIndicator:!0,showCollisions:!0,showVelocity:!0}});t.run(l);var d=n.create();n.run(d,s);var u=.9;i.add(c,[a.rectangle(100,150,50,50,{restitution:u}),a.rectangle(220,150,50,50,{restitution:u,angle:.15*-Math.PI}),a.rectangle(340,150,50,50,{restitution:u,angle:.25*-Math.PI}),a.circle(460,150,25,{restitution:u}),a.rectangle(700,150,180,20,{restitution:u,angle:.5*-Math.PI}),a.rectangle(400,0,800,50,{isStatic:!0}),a.rectangle(400,600,800,50,{isStatic:!0}),a.rectangle(800,300,50,600,{isStatic:!0}),a.rectangle(0,300,50,600,{isStatic:!0})]);var p=o.create(l.canvas),m=r.create(s,{mouse:p,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(c,m),l.mouse=p,t.lookAt(l,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:s,runner:d,render:l,canvas:l.canvas,stop:function(){Matter.Render.stop(l),Matter.Runner.stop(d)}}},r.restitution.title="Restitution",r.restitution.for=">=0.14.2",e.exports=r.restitution},ZUN1:function(e,t,n){var r=r||{};r.chains=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Composite,i=Matter.Composites,a=Matter.Constraint,s=Matter.MouseConstraint,c=Matter.Mouse,l=Matter.Bodies,d=e.create(),u=d.world,p=t.create({element:document.body,engine:d,options:{width:800,height:600,showAngleIndicator:!0,showCollisions:!0,showVelocity:!0}});t.run(p);var m=n.create();n.run(m,d);var f=r.nextGroup(!0),v=i.stack(100,50,8,1,10,10,(function(e,t){return l.rectangle(e,t,50,20,{collisionFilter:{group:f}})}));i.chain(v,.5,0,-.5,0,{stiffness:.8,length:2,render:{type:"line"}}),o.add(v,a.create({bodyB:v.bodies[0],pointB:{x:-25,y:0},pointA:{x:v.bodies[0].position.x,y:v.bodies[0].position.y},stiffness:.5})),f=r.nextGroup(!0);var g=i.stack(350,50,10,1,10,10,(function(e,t){return l.circle(e,t,20,{collisionFilter:{group:f}})}));i.chain(g,.5,0,-.5,0,{stiffness:.8,length:2,render:{type:"line"}}),o.add(g,a.create({bodyB:g.bodies[0],pointB:{x:-20,y:0},pointA:{x:g.bodies[0].position.x,y:g.bodies[0].position.y},stiffness:.5})),f=r.nextGroup(!0);var y=i.stack(600,50,13,1,10,10,(function(e,t){return l.rectangle(e-20,t,50,20,{collisionFilter:{group:f},chamfer:5})}));i.chain(y,.3,0,-.3,0,{stiffness:1,length:0}),o.add(y,a.create({bodyB:y.bodies[0],pointB:{x:-20,y:0},pointA:{x:y.bodies[0].position.x,y:y.bodies[0].position.y},stiffness:.5})),o.add(u,[v,g,y,l.rectangle(400,600,1200,50.5,{isStatic:!0})]);var x=c.create(p.canvas),h=s.create(d,{mouse:x,constraint:{stiffness:.2,render:{visible:!1}}});return o.add(u,h),p.mouse=x,t.lookAt(p,{min:{x:0,y:0},max:{x:700,y:600}}),{engine:d,runner:m,render:p,canvas:p.canvas,stop:function(){Matter.Render.stop(p),Matter.Runner.stop(m)}}},r.chains.title="Chains",r.chains.for=">=0.14.2",e.exports=r.chains},Zo9v:function(e,t,n){var r=r||{};r.collisionFiltering=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composite,o=Matter.Composites,i=(Matter.Common,Matter.MouseConstraint),a=Matter.Mouse,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,wireframes:!1}});t.run(d);var u=n.create();n.run(u,c);var p="#f55a3c",m="#063e7b",f="#f5d259";r.add(l,s.rectangle(400,600,900,50,{isStatic:!0,render:{fillStyle:"transparent",lineWidth:1}})),r.add(l,o.stack(275,100,5,9,10,10,(function(e,t,n,r){var o=2,i=p;return r>5?(o=8,i=m):r>2&&(o=4,i=f),s.circle(e,t,20,{collisionFilter:{category:o},render:{strokeStyle:i,fillStyle:"transparent",lineWidth:1}})}))),r.add(l,s.circle(310,40,30,{collisionFilter:{mask:5},render:{fillStyle:f}})),r.add(l,s.circle(400,40,30,{collisionFilter:{mask:3},render:{fillStyle:p}})),r.add(l,s.circle(480,40,30,{collisionFilter:{mask:9},render:{fillStyle:m}}));var v=a.create(d.canvas),g=i.create(c,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});return r.add(l,g),d.mouse=v,g.collisionFilter.mask=13,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.collisionFiltering.title="Collision Filtering",r.collisionFiltering.for=">=0.14.2",e.exports=r.collisionFiltering},ZpbE:function(e,t,n){var r=r||{};r.stack=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.MouseConstraint,i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showAngleIndicator:!0}});t.run(d);var u=n.create();n.run(u,c);var p=r.stack(200,380.75,10,5,0,0,(function(e,t){return s.rectangle(e,t,40,40)}));a.add(l,[p,s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0}),s.rectangle(400,606,800,50.5,{isStatic:!0})]);var m=i.create(d.canvas),f=o.create(c,{mouse:m,constraint:{stiffness:.2,render:{visible:!1}}});return a.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.stack.title="Stack",r.stack.for=">=0.14.2",e.exports=r.stack},"a+ms":function(e,t,n){var r=n("akk5"),o=n("lniP"),i=n("lniP");e.exports={compare:function(e,t){var n=r.Demo.create({toolbar:{title:"matter-js ・ "+(t?"dev":"")+" ・ comparing to "+i.version,url:"https://github.com/liabru/matter-js",reset:!0,source:!0,inspector:!0,tools:!0,fullscreen:!0,exampleSelect:!0},tools:{inspector:!0,gui:!0},inline:!1,preventZoom:!0,resetOnOrientation:!0,routing:!0,startExample:"mixed",examples:e}),a=r.Demo.create({toolbar:{title:"matter-js-compare-build",reset:!1,source:!1,inspector:!1,tools:!1,fullscreen:!1,exampleSelect:!1},tools:{inspector:!1,gui:!1},inline:!1,preventZoom:!0,resetOnOrientation:!0,routing:!1,startExample:"mixed",examples:e.map((function(e){return Matter.Common.extend({},e)}))});i.Runner.run=function(){},i.Render.run=function(){},o.Runner._tick=o.Runner.tick,o.Render._world=o.Render.world,i.Mouse._setElement=i.Mouse.setElement,r.Demo._setExample=r.Demo.setExample,r.Demo.setExample=function(e,t){i.Common._nextId=i.Common._seed=0,o.Common._nextId=o.Common._seed=0,i.Plugin._registry=o.Plugin._registry,i.use.apply(null,o.used),window.Matter=o,r.Demo._setExample(n,n.examples.find((function(e){return e.name===t.name})));var s=parseFloat(window.location.search.split("=")[1]),c=0;o.Runner.tick=function(e,t,n){if(-1!==c){if(c>=s)return console.info("Demo.Compare: ran "+c+" ticks, timestamp is now "+t.timing.timestamp.toFixed(2)),void(c=-1);c+=1;var r=a.example.instance;return e.isFixed=r.runner.isFixed=!0,e.delta=r.runner.delta=1e3/60,window.Matter=i,i.Runner.tick(r.runner,r.engine,n),window.Matter=o,o.Runner._tick(e,t,n)}},o.Render.world=function(e){return window.Matter=i,i.Render.world(a.example.instance.render),window.Matter=o,o.Render._world(e)},i.Mouse.setElement=function(e){return i.Mouse._setElement(e,n.example.instance.render.canvas)},window.Matter=i,r.Demo._setExample(a,a.examples.find((function(e){return e.name===t.name}))),window.Matter=o},r.Demo._reset=r.Demo.reset,r.Demo.reset=function(e){i.Common._nextId=i.Common._seed=0,o.Common._nextId=o.Common._seed=0,window.Matter=i,r.Demo._reset(a),window.Matter=o,r.Demo._reset(n)},document.body.appendChild(n.dom.root),document.body.appendChild(a.dom.root),r.Demo.start(n),document.title="Matter.js Compare"+(t?" ・ Dev":""),console.info("Demo.Compare: matter-js@"+o.version+" with matter-js@"+i.version)}}},a3OZ:function(e,t,n){var r=r||{};r.compositeManipulation=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Events,o=Matter.Composite,i=Matter.Composites,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAngleIndicator:!0}});t.run(u);var p=n.create();n.run(p,l),o.add(d,[c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var m=i.stack(200,200,4,4,0,0,(function(e,t){return c.rectangle(e,t,40,40)}));o.add(d,m),l.gravity.y=0,r.on(l,"afterUpdate",(function(e){var t=l.timing.timestamp;o.translate(m,{x:2*Math.sin(.001*t),y:0}),o.rotate(m,.01*Math.sin(.001*t),{x:300,y:300});var n=1+.01*Math.sin(.001*t);o.scale(m,n,n,{x:300,y:300})}));var f=s.create(u.canvas),v=a.create(l,{mouse:f,constraint:{stiffness:.2,render:{visible:!1}}});return o.add(d,v),u.mouse=f,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.compositeManipulation.title="Composite Manipulation",r.compositeManipulation.for=">0.16.1",e.exports=r.compositeManipulation},c7us:function(e,t,n){var r=r||{};r.ragdoll=function(){var e=Matter.Engine,t=Matter.Events,n=Matter.Render,o=Matter.Runner,i=Matter.Body,a=Matter.Common,s=Matter.Composite,c=Matter.Composites,l=(Matter.Constraint,Matter.MouseConstraint),d=Matter.Mouse,u=Matter.Bodies,p=(Matter.Vector,e.create()),m=p.world,f=n.create({element:document.body,engine:p,options:{width:800,height:600,showAngleIndicator:!0}});n.run(f);var v=o.create();o.run(v,p);for(var g=(f.bounds.max.y-f.bounds.min.y)/50,y=c.stack(0,0,g+2,1,0,0,(function(e,t,n){return u.rectangle(e-50,t+50*n,100,1e3,{isStatic:!0,render:{fillStyle:"#060a19",strokeStyle:"#ffffff",lineWidth:1}})})),x=c.stack(300,0,15,3,10,10,(function(e,t,n){var r=Math.round(a.random(1,8)),o={render:{fillStyle:a.choose(["#f19648","#f5d259","#f55a3c","#063e7b","#ececd1"])}};switch(Math.round(a.random(0,1))){case 0:return a.random()<.8?u.rectangle(e,t,a.random(25,50),a.random(25,50),o):u.rectangle(e,t,a.random(80,120),a.random(25,30),o);case 1:return u.polygon(e,t,r,a.random(25,50),o)}})),h=s.create(),b=0;b<1;b+=1){var M=r.ragdoll.ragdoll(200,-1e3*b,1.3);s.add(h,M)}s.add(m,[y,x,h]);var S=1,w=0;t.on(p,"afterUpdate",(function(e){-1===C.button?p.timing.timeScale+=.05*(S-p.timing.timeScale):p.timing.timeScale=1,(w+=1)>=90&&(S=S<1?1:.05,w=0);for(var t=0;tf.bounds.max.y+100&&s.translate(r,{x:.9*-o.min.x,y:-f.bounds.max.y-400})}for(t=0;tf.bounds.max.y+100&&i.translate(n,{x:-o.min.x,y:-f.bounds.max.y-300})}}));var C=d.create(f.canvas),A=l.create(p,{mouse:C,constraint:{stiffness:.6,length:0,angularStiffness:0,render:{visible:!1}}});return s.add(m,A),f.mouse=C,n.lookAt(f,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:p,runner:v,render:f,canvas:f.canvas,stop:function(){Matter.Render.stop(f),Matter.Runner.stop(v)}}},r.ragdoll.ragdoll=function(e,t,n,r){n=void 0===n?1:n;var o=Matter.Body,i=Matter.Bodies,a=Matter.Constraint,s=Matter.Composite,c=Matter.Common,l=c.extend({label:"head",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:[15*n,15*n,15*n,15*n]},render:{fillStyle:"#FFBC42"}},r),d=c.extend({label:"chest",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:[20*n,20*n,26*n,26*n]},render:{fillStyle:"#E0A423"}},r),u=c.extend({label:"left-arm",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:10*n},render:{fillStyle:"#FFBC42"}},r),p=c.extend({},u,{render:{fillStyle:"#E59B12"}}),m=c.extend({label:"right-arm",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:10*n},render:{fillStyle:"#FFBC42"}},r),f=c.extend({},m,{render:{fillStyle:"#E59B12"}}),v=c.extend({label:"left-leg",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:10*n},render:{fillStyle:"#FFBC42"}},r),g=c.extend({},v,{render:{fillStyle:"#E59B12"}}),y=c.extend({label:"right-leg",collisionFilter:{group:o.nextGroup(!0)},chamfer:{radius:10*n},render:{fillStyle:"#FFBC42"}},r),x=c.extend({},y,{render:{fillStyle:"#E59B12"}}),h=i.rectangle(e,t-60*n,34*n,40*n,l),b=i.rectangle(e,t,55*n,80*n,d),M=i.rectangle(e+39*n,t-15*n,20*n,40*n,m),S=i.rectangle(e+39*n,t+25*n,20*n,60*n,f),w=i.rectangle(e-39*n,t-15*n,20*n,40*n,u),C=i.rectangle(e-39*n,t+25*n,20*n,60*n,p),A=i.rectangle(e-20*n,t+57*n,20*n,40*n,v),B=i.rectangle(e-20*n,t+97*n,20*n,60*n,g),k=i.rectangle(e+20*n,t+57*n,20*n,40*n,y),R=i.rectangle(e+20*n,t+97*n,20*n,60*n,x),P=a.create({bodyA:b,pointA:{x:24*n,y:-23*n},pointB:{x:0,y:-8*n},bodyB:M,stiffness:.6,render:{visible:!1}}),I=a.create({bodyA:b,pointA:{x:-24*n,y:-23*n},pointB:{x:0,y:-8*n},bodyB:w,stiffness:.6,render:{visible:!1}}),_=a.create({bodyA:b,pointA:{x:-10*n,y:30*n},pointB:{x:0,y:-10*n},bodyB:A,stiffness:.6,render:{visible:!1}}),E=a.create({bodyA:b,pointA:{x:10*n,y:30*n},pointB:{x:0,y:-10*n},bodyB:k,stiffness:.6,render:{visible:!1}}),T=a.create({bodyA:M,bodyB:S,pointA:{x:0,y:15*n},pointB:{x:0,y:-25*n},stiffness:.6,render:{visible:!1}}),F=a.create({bodyA:w,bodyB:C,pointA:{x:0,y:15*n},pointB:{x:0,y:-25*n},stiffness:.6,render:{visible:!1}}),D=a.create({bodyA:A,bodyB:B,pointA:{x:0,y:20*n},pointB:{x:0,y:-20*n},stiffness:.6,render:{visible:!1}}),V=a.create({bodyA:k,bodyB:R,pointA:{x:0,y:20*n},pointB:{x:0,y:-20*n},stiffness:.6,render:{visible:!1}}),O=a.create({bodyA:h,pointA:{x:0,y:25*n},pointB:{x:0,y:-35*n},bodyB:b,stiffness:.6,render:{visible:!1}}),L=a.create({bodyA:B,bodyB:R,stiffness:.01,render:{visible:!1}});return s.create({bodies:[b,h,C,w,S,M,B,R,A,k],constraints:[F,T,I,P,O,D,V,_,E,L]})},r.ragdoll.title="Ragdoll",r.ragdoll.for=">=0.14.2",e.exports=r.ragdoll},djnZ:function(e,t,n){var r=r||{};r.compound=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Body,o=Matter.Constraint,i=Matter.Composite,a=Matter.MouseConstraint,s=Matter.Mouse,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAxes:!0,showConvexHulls:!0}});t.run(u);var p=n.create();n.run(p,l);var m=200,f=200,v=200,g=c.rectangle(f,v,m,m/5),y=c.rectangle(f,v,m/5,m,{render:g.render}),x=r.create({parts:[g,y]});m=150,f=400,v=300;var h=c.circle(f,v,30),b=c.circle(f+m,v,30),M=c.circle(f+m,v+m,30),S=c.circle(f,v+m,30),w=r.create({parts:[h,b,M,S]}),C=o.create({pointA:{x:400,y:100},bodyB:w,pointB:{x:0,y:0}});i.add(d,[x,w,C,c.rectangle(400,600,800,50.5,{isStatic:!0})]);var A=s.create(u.canvas),B=a.create(l,{mouse:A,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(d,B),u.mouse=A,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.compound.title="Compound Bodies",r.compound.for=">=0.14.2",e.exports=r.compound},elWf:function(e,t,n){var r=r||{};r.rounded=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.MouseConstraint,o=Matter.Mouse,i=Matter.Composite,a=Matter.Bodies,s=e.create(),c=s.world,l=t.create({element:document.body,engine:s,options:{width:800,height:600,showAxes:!0}});t.run(l);var d=n.create();n.run(d,s),i.add(c,[a.rectangle(400,0,800,50,{isStatic:!0}),a.rectangle(400,600,800,50,{isStatic:!0}),a.rectangle(800,300,50,600,{isStatic:!0}),a.rectangle(0,300,50,600,{isStatic:!0})]),i.add(c,[a.rectangle(200,200,100,100,{chamfer:{radius:20}}),a.rectangle(300,200,100,100,{chamfer:{radius:[90,0,0,0]}}),a.rectangle(400,200,200,200,{chamfer:{radius:[150,20,40,20]}}),a.rectangle(200,200,200,200,{chamfer:{radius:[150,20,150,20]}}),a.rectangle(300,200,200,50,{chamfer:{radius:[25,25,0,0]}}),a.polygon(200,100,8,80,{chamfer:{radius:30}}),a.polygon(300,100,5,80,{chamfer:{radius:[10,40,20,40,10]}}),a.polygon(400,200,3,50,{chamfer:{radius:[20,0,20]}})]);var u=o.create(l.canvas),p=r.create(s,{mouse:u,constraint:{stiffness:.2,render:{visible:!1}}});return i.add(c,p),l.mouse=u,t.lookAt(l,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:s,runner:d,render:l,canvas:l.canvas,stop:function(){Matter.Render.stop(l),Matter.Runner.stop(d)}}},r.rounded.title="Rounded Corners (Chamfering)",r.rounded.for=">=0.14.2",e.exports=r.rounded},ga9t:function(e,t,n){var r={};e.exports=r;var o=n("0kzT"),i=n("m6Dm"),a=n("yw0d"),s=n("Tgw/"),c=n("JKEF"),l=n("571F");r._warming=.4,r._torqueDampen=1,r._minLength=1e-6,r.create=function(e){var t=e;t.bodyA&&!t.pointA&&(t.pointA={x:0,y:0}),t.bodyB&&!t.pointB&&(t.pointB={x:0,y:0});var n=t.bodyA?i.add(t.bodyA.position,t.pointA):t.pointA,r=t.bodyB?i.add(t.bodyB.position,t.pointB):t.pointB,o=i.magnitude(i.sub(n,r));t.length=void 0!==t.length?t.length:o,t.id=t.id||l.nextId(),t.label=t.label||"Constraint",t.type="constraint",t.stiffness=t.stiffness||(t.length>0?1:.7),t.damping=t.damping||0,t.angularStiffness=t.angularStiffness||0,t.angleA=t.bodyA?t.bodyA.angle:t.angleA,t.angleB=t.bodyB?t.bodyB.angle:t.angleB,t.plugin={};var a={visible:!0,lineWidth:2,strokeStyle:"#ffffff",type:"line",anchors:!0};return 0===t.length&&t.stiffness>.1?(a.type="pin",a.anchors=!1):t.stiffness<.9&&(a.type="spring"),t.render=l.extend(a,t.render),t},r.preSolveAll=function(e){for(var t=0;t0&&(u.position.x+=l.x,u.position.y+=l.y),0!==l.angle&&(o.rotate(u.vertices,l.angle,n.position),c.rotate(u.axes,l.angle),d>0&&i.rotateAbout(u.position,l.angle,n.position,u.position)),s.update(u.bounds,u.vertices,n.velocity)}l.angle*=r._warming,l.x*=r._warming,l.y*=r._warming}}},r.pointAWorld=function(e){return{x:(e.bodyA?e.bodyA.position.x:0)+e.pointA.x,y:(e.bodyA?e.bodyA.position.y:0)+e.pointA.y}},r.pointBWorld=function(e){return{x:(e.bodyB?e.bodyB.position.x:0)+e.pointB.x,y:(e.bodyB?e.bodyB.position.y:0)+e.pointB.y}}},ibhy:function(e,t,n){var r=r||{};r.mixed=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Common,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAngleIndicator:!0}});t.run(u);var p=n.create();n.run(p,l);var m=r.stack(20,20,10,5,0,0,(function(e,t){var n=Math.round(o.random(1,8)),r=null;switch((n=3===n?4:n)>2&&o.random()>.7&&(r={radius:10}),Math.round(o.random(0,1))){case 0:return o.random()<.8?c.rectangle(e,t,o.random(25,50),o.random(25,50),{chamfer:r}):c.rectangle(e,t,o.random(80,120),o.random(25,30),{chamfer:r});case 1:return c.polygon(e,t,n,o.random(25,50),{chamfer:r})}}));s.add(d,m),s.add(d,[c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var f=a.create(u.canvas),v=i.create(l,{mouse:f,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,v),u.mouse=f,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.mixed.title="Mixed Shapes",r.mixed.for=">=0.14.2",e.exports=r.mixed},"ix+/":function(e,t,n){var r={};e.exports=r;var o=n("DqtB"),i=n("t8gT"),a=n("Tgw/");r.collisions=function(e,t){for(var n=[],s=t.pairs.table,c=0;c1?1:0;u1?1:0;m0:0!=(e.mask&t.category)&&0!=(t.mask&e.category)}},k7Ch:function(e,t,n){var r=r||{};r.gyro=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.Common,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.Composite,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showAngleIndicator:!0}});t.run(u);var p=n.create();n.run(p,l);var m=r.stack(20,20,10,5,0,0,(function(e,t){var n=Math.round(o.random(1,8)),r=null;switch((n=3===n?4:n)>2&&o.random()>.7&&(r={radius:10}),Math.round(o.random(0,1))){case 0:return o.random()<.8?c.rectangle(e,t,o.random(25,50),o.random(25,50),{chamfer:r}):c.rectangle(e,t,o.random(80,120),o.random(25,30),{chamfer:r});case 1:return c.polygon(e,t,n,o.random(25,50),{chamfer:r})}}));if(s.add(d,[m,c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]),"undefined"!=typeof window){var f=function(e){var t=void 0!==window.orientation?window.orientation:0,n=l.gravity;0===t?(n.x=o.clamp(e.gamma,-90,90)/90,n.y=o.clamp(e.beta,-90,90)/90):180===t?(n.x=o.clamp(e.gamma,-90,90)/90,n.y=o.clamp(-e.beta,-90,90)/90):90===t?(n.x=o.clamp(e.beta,-90,90)/90,n.y=o.clamp(-e.gamma,-90,90)/90):-90===t&&(n.x=o.clamp(-e.beta,-90,90)/90,n.y=o.clamp(e.gamma,-90,90)/90)};window.addEventListener("deviceorientation",f)}var v=a.create(u.canvas),g=i.create(l,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,g),u.mouse=v,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p),"undefined"!=typeof window&&window.removeEventListener("deviceorientation",f)}}},r.gyro.title="Gyroscope",r.gyro.for=">=0.14.2",e.exports=r.gyro},lWug:function(e,t,n){var r={};e.exports=r;var o=n("571F"),i=n("wAS/"),a=n("Tgw/"),s=n("yTB+"),c=n("m6Dm"),l=n("I5nt");!function(){var e,t;"undefined"!=typeof window&&(e=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout((function(){e(o.now())}),1e3/60)},t=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame),r._goodFps=30,r._goodDelta=1e3/60,r.create=function(e){var t={controller:r,engine:null,element:null,canvas:null,mouse:null,frameRequestId:null,timing:{historySize:60,delta:0,deltaHistory:[],lastTime:0,lastTimestamp:0,lastElapsed:0,timestampElapsed:0,timestampElapsedHistory:[],engineDeltaHistory:[],engineElapsedHistory:[],elapsedHistory:[]},options:{width:800,height:600,pixelRatio:1,background:"#14151f",wireframeBackground:"#14151f",hasBounds:!!e.bounds,enabled:!0,wireframes:!0,showSleeping:!0,showDebug:!1,showStats:!1,showPerformance:!1,showBroadphase:!1,showBounds:!1,showVelocity:!1,showCollisions:!1,showSeparations:!1,showAxes:!1,showPositions:!1,showAngleIndicator:!1,showIds:!1,showVertexNumbers:!1,showConvexHulls:!1,showInternalEdges:!1,showMousePosition:!1}},n=o.extend(t,e);return n.canvas&&(n.canvas.width=n.options.width||n.canvas.width,n.canvas.height=n.options.height||n.canvas.height),n.mouse=e.mouse,n.engine=e.engine,n.canvas=n.canvas||u(n.options.width,n.options.height),n.context=n.canvas.getContext("2d"),n.textures={},n.bounds=n.bounds||{min:{x:0,y:0},max:{x:n.canvas.width,y:n.canvas.height}},1!==n.options.pixelRatio&&r.setPixelRatio(n,n.options.pixelRatio),o.isElement(n.element)?n.element.appendChild(n.canvas):n.canvas.parentNode||o.log("Render.create: options.element was undefined, render.canvas was created but not appended","warn"),n},r.run=function(t){!function o(i){t.frameRequestId=e(o),n(t,i),r.world(t,i),(t.options.showStats||t.options.showDebug)&&r.stats(t,t.context,i),(t.options.showPerformance||t.options.showDebug)&&r.performance(t,t.context,i)}()},r.stop=function(e){t(e.frameRequestId)},r.setPixelRatio=function(e,t){var n=e.options,r=e.canvas;"auto"===t&&(t=p(r)),n.pixelRatio=t,r.setAttribute("data-pixel-ratio",t),r.width=n.width*t,r.height=n.height*t,r.style.width=n.width+"px",r.style.height=n.height+"px"},r.lookAt=function(e,t,n,r){r=void 0===r||r,t=o.isArray(t)?t:[t],n=n||{x:0,y:0};for(var i={min:{x:1/0,y:1/0},max:{x:-1/0,y:-1/0}},a=0;ai.max.x&&(i.max.x=d.x),c.yi.max.y&&(i.max.y=d.y))}var u=i.max.x-i.min.x+2*n.x,p=i.max.y-i.min.y+2*n.y,m=e.canvas.height,f=e.canvas.width/m,v=u/p,g=1,y=1;v>f?y=v/f:g=f/v,e.options.hasBounds=!0,e.bounds.min.x=i.min.x,e.bounds.max.x=i.min.x+u*g,e.bounds.min.y=i.min.y,e.bounds.max.y=i.min.y+p*y,r&&(e.bounds.min.x+=.5*u-u*g*.5,e.bounds.max.x+=.5*u-u*g*.5,e.bounds.min.y+=.5*p-p*y*.5,e.bounds.max.y+=.5*p-p*y*.5),e.bounds.min.x-=n.x,e.bounds.max.x-=n.x,e.bounds.min.y-=n.y,e.bounds.max.y-=n.y,e.mouse&&(l.setScale(e.mouse,{x:(e.bounds.max.x-e.bounds.min.x)/e.canvas.width,y:(e.bounds.max.y-e.bounds.min.y)/e.canvas.height}),l.setOffset(e.mouse,e.bounds.min))},r.startViewTransform=function(e){var t=e.bounds.max.x-e.bounds.min.x,n=e.bounds.max.y-e.bounds.min.y,r=t/e.options.width,o=n/e.options.height;e.context.setTransform(e.options.pixelRatio/r,0,0,e.options.pixelRatio/o,0,0),e.context.translate(-e.bounds.min.x,-e.bounds.min.y)},r.endViewTransform=function(e){e.context.setTransform(e.options.pixelRatio,0,0,e.options.pixelRatio,0,0)},r.world=function(e,t){var n,d=o.now(),u=e.engine,p=u.world,m=e.canvas,v=e.context,g=e.options,y=e.timing,x=i.allBodies(p),h=i.allConstraints(p),b=g.wireframes?g.wireframeBackground:g.background,M=[],S=[],w={timestamp:u.timing.timestamp};if(s.trigger(e,"beforeRender",w),e.currentBackground!==b&&f(e,b),v.globalCompositeOperation="source-in",v.fillStyle="transparent",v.fillRect(0,0,m.width,m.height),v.globalCompositeOperation="source-over",g.hasBounds){for(n=0;n1?1:0;a1?1:0;s1?1:0;i1?1:0;s1?1:0;i1?1:0;i1?1:0;o0)){var d=r.activeContacts[0].vertex.x,u=r.activeContacts[0].vertex.y;2===r.activeContacts.length&&(d=(r.activeContacts[0].vertex.x+r.activeContacts[1].vertex.x)/2,u=(r.activeContacts[0].vertex.y+r.activeContacts[1].vertex.y)/2),o.bodyB===o.supports[0].body||!0===o.bodyA.isStatic?s.moveTo(d-8*o.normal.x,u-8*o.normal.y):s.moveTo(d+8*o.normal.x,u+8*o.normal.y),s.lineTo(d,u)}c.wireframes?s.strokeStyle="rgba(255,165,0,0.7)":s.strokeStyle="orange",s.lineWidth=1,s.stroke()},r.separations=function(e,t,n){var r,o,i,a,s,c=n,l=e.options;for(c.beginPath(),s=0;s0&&l.trigger(e,"collisionStart",{pairs:w.collisionStart}),i.preSolvePosition(w.list),v=0;v0&&l.trigger(e,"collisionActive",{pairs:w.collisionActive}),w.collisionEnd.length>0&&l.trigger(e,"collisionEnd",{pairs:w.collisionEnd}),r._bodiesClearForces(b),l.trigger(e,"afterUpdate",h),e.timing.lastElapsed=p.now()-m,e},r.merge=function(e,t){if(p.extend(e,t),t.world){e.world=t.world,r.clear(e);for(var n=d.allBodies(e.world),i=0;i0&&o.area(B)1?(f=a.create(i.extend({parts:v.slice(0)},r)),a.setPosition(f,{x:e,y:t}),f):v[0]}},"pm/U":function(e,t,n){var r={};e.exports=r;var o=n("2Og8"),i=n("571F");r.name="matter-js",r.version="*",r.uses=[],r.used=[],r.use=function(){o.use(r,Array.prototype.slice.call(arguments))},r.before=function(e,t){return e=e.replace(/^Matter./,""),i.chainPathBefore(r,e,t)},r.after=function(e,t){return e=e.replace(/^Matter./,""),i.chainPathAfter(r,e,t)}},q4y7:function(e,t,n){var r={};e.exports=r;var o=n("0kzT"),i=n("yw0d"),a=n("I5nt"),s=n("yTB+"),c=n("ix+/"),l=n("ga9t"),d=n("wAS/"),u=n("571F"),p=n("Tgw/");r.create=function(e,t){var n=(e?e.mouse:null)||(t?t.mouse:null);n||(e&&e.render&&e.render.canvas?n=a.create(e.render.canvas):t&&t.element?n=a.create(t.element):(n=a.create(),u.warn("MouseConstraint.create: options.mouse was undefined, options.element was undefined, may not function as expected")));var o={type:"mouseConstraint",mouse:n,element:null,body:null,constraint:l.create({label:"Mouse Constraint",pointA:n.position,pointB:{x:0,y:0},length:.01,stiffness:.1,angularStiffness:1,render:{strokeStyle:"#90EE90",lineWidth:3}}),collisionFilter:{category:1,mask:4294967295,group:0}},i=u.extend(o,t);return s.on(e,"beforeUpdate",(function(){var t=d.allBodies(e.world);r.update(i,t),r._triggerEvents(i)})),i},r.update=function(e,t){var n=e.mouse,r=e.constraint,a=e.body;if(0===n.button){if(r.bodyB)i.set(r.bodyB,!1),r.pointA=n.position;else for(var l=0;l1?1:0;d=0.14.2",r.cloth.cloth=function(e,t,n,r,o,i,a,s,c,l){var d=Matter.Body,u=Matter.Bodies,p=Matter.Common,m=Matter.Composites,f=d.nextGroup(!0);c=p.extend({inertia:1/0,friction:1e-5,collisionFilter:{group:f},render:{visible:!1}},c),l=p.extend({stiffness:.06,render:{type:"line",anchors:!1}},l);var v=m.stack(e,t,n,r,o,i,(function(e,t){return u.circle(e,t,s,c)}));return m.mesh(v,n,r,a,l),v.label="Cloth Body",v},e.exports=r.cloth},t8gT:function(e,t,n){var r={};e.exports=r;var o=n("9K63");r.create=function(e,t){var n=e.bodyA,o=e.bodyB,i=e.parentA,a=e.parentB,s={id:r.id(n,o),bodyA:n,bodyB:o,contacts:{},activeContacts:[],separation:0,isActive:!0,confirmedActive:!0,isSensor:n.isSensor||o.isSensor,timeCreated:t,timeUpdated:t,inverseMass:i.inverseMass+a.inverseMass,friction:Math.min(i.friction,a.friction),frictionStatic:Math.max(i.frictionStatic,a.frictionStatic),restitution:Math.max(i.restitution,a.restitution),slop:Math.max(i.slop,a.slop)};return r.update(s,e,t),s},r.update=function(e,t,n){var i=e.contacts,a=t.supports,s=e.activeContacts,c=t.parentA,l=t.parentB;if(e.collision=t,e.inverseMass=c.inverseMass+l.inverseMass,e.friction=Math.min(c.friction,l.friction),e.frictionStatic=Math.max(c.frictionStatic,l.frictionStatic),e.restitution=Math.max(c.restitution,l.restitution),e.slop=Math.max(c.slop,l.slop),s.length=0,t.collided){for(var d=0;d.35?c.rectangle(e,t,64,64,{render:{strokeStyle:"#ffffff",sprite:{texture:"./img/box.png"}}}):c.circle(e,t,46,{density:5e-4,frictionAir:.06,restitution:.3,friction:.01,render:{sprite:{texture:"./img/ball.png"}}})}));s.add(d,f);var v=a.create(u.canvas),g=i.create(l,{mouse:v,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,g),u.mouse=v,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.sprites.title="Sprites",r.sprites.for=">=0.14.2",e.exports=r.sprites},ttsO:function(e,t,n){var r={};e.exports=r;var o=n("t8gT"),i=n("571F");r._pairMaxIdleLife=1e3,r.create=function(e){return i.extend({table:{},list:[],collisionStart:[],collisionActive:[],collisionEnd:[]},e)},r.update=function(e,t,n){var r,i,a,s,c=e.list,l=e.table,d=e.collisionStart,u=e.collisionEnd,p=e.collisionActive;for(d.length=0,u.length=0,p.length=0,s=0;sr._pairMaxIdleLife&&l.push(a);for(a=0;a=0?i(s,false):a(s,false)},vSND:function(e,t,n){var r=r||{};r.stats=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Common,o=Matter.Composites,i=Matter.MouseConstraint,a=Matter.Mouse,s=Matter.World,c=Matter.Bodies,l=e.create(),d=l.world,u=t.create({element:document.body,engine:l,options:{width:800,height:600,showStats:!0,showPerformance:!0}});t.run(u);var p=n.create();n.run(p,l);var m=o.stack(70,30,13,9,5,5,(function(e,t){return c.circle(e,t,10+20*r.random())}));s.add(d,[m,c.rectangle(400,0,800,50,{isStatic:!0}),c.rectangle(400,600,800,50,{isStatic:!0}),c.rectangle(800,300,50,600,{isStatic:!0}),c.rectangle(0,300,50,600,{isStatic:!0})]);var f=a.create(u.canvas),v=i.create(l,{mouse:f,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(d,v),u.mouse=f,t.lookAt(u,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:l,runner:p,render:u,canvas:u.canvas,stop:function(){Matter.Render.stop(u),Matter.Runner.stop(p)}}},r.stats.title="Stats & Performance",r.stats.for=">=0.16.1",e.exports=r.stats},"wAS/":function(e,t,n){var r={};e.exports=r;var o=n("yTB+"),i=n("571F"),a=n("Tgw/"),s=n("IbIC");r.create=function(e){return i.extend({id:i.nextId(),type:"composite",parent:null,isModified:!1,bodies:[],constraints:[],composites:[],label:"Composite",plugin:{}},e)},r.setModified=function(e,t,n,o){if(e.isModified=t,n&&e.parent&&r.setModified(e.parent,t,n,o),o)for(var i=0;i=0.14.2",e.exports=r.bridge},xoNv:function(e,t,n){var r=r||{};r.newtonsCradle=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,o=Matter.Body,i=(Matter.Composites,Matter.MouseConstraint),a=Matter.Mouse,s=Matter.Composite,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showVelocity:!0}});t.run(d);var u=n.create();n.run(u,c);var p=r.newtonsCradle.newtonsCradle(280,100,5,30,200);s.add(l,p),o.translate(p.bodies[0],{x:-180,y:-100}),p=r.newtonsCradle.newtonsCradle(280,380,7,20,140),s.add(l,p),o.translate(p.bodies[0],{x:-140,y:-100});var m=a.create(d.canvas),f=i.create(c,{mouse:m,constraint:{stiffness:.2,render:{visible:!1}}});return s.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:50},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.newtonsCradle.title="Newton's Cradle",r.newtonsCradle.for=">=0.14.2",r.newtonsCradle.newtonsCradle=function(e,t,n,r,o){for(var i=Matter.Composite,a=Matter.Constraint,s=Matter.Bodies,c=i.create({label:"Newtons Cradle"}),l=0;l0){n||(n={}),r=t.split(" ");for(var l=0;l=0.14.2",e.exports=r.sensors},yw0d:function(e,t,n){var r={};e.exports=r;var o=n("yTB+");r._motionWakeThreshold=.18,r._motionSleepThreshold=.08,r._minBias=.9,r.update=function(e,t){for(var n=t*t*t,o=0;o0&&i.motion=i.sleepThreshold&&r.set(i,!0)):i.sleepCounter>0&&(i.sleepCounter-=1)}else r.set(i,!1)}},r.afterCollisions=function(e,t){for(var n=t*t*t,o=0;or._motionWakeThreshold*n&&r.set(l,!1)}}}},r.set=function(e,t){var n=e.isSleeping;t?(e.isSleeping=!0,e.sleepCounter=e.sleepThreshold,e.positionImpulse.x=0,e.positionImpulse.y=0,e.positionPrev.x=e.position.x,e.positionPrev.y=e.position.y,e.anglePrev=e.angle,e.speed=0,e.angularSpeed=0,e.motion=0,n||o.trigger(e,"sleepStart")):(e.isSleeping=!1,e.sleepCounter=0,n&&o.trigger(e,"sleepEnd"))}},zc7U:function(e,t,n){var r=r||{};r.circleStack=function(){var e=Matter.Engine,t=Matter.Render,n=Matter.Runner,r=Matter.Composites,o=Matter.MouseConstraint,i=Matter.Mouse,a=Matter.Composite,s=Matter.Bodies,c=e.create(),l=c.world,d=t.create({element:document.body,engine:c,options:{width:800,height:600,showAngleIndicator:!0}});t.run(d);var u=n.create();n.run(u,c);var p=r.stack(100,179,10,10,20,0,(function(e,t){return s.circle(e,t,20)}));a.add(l,[s.rectangle(400,0,800,50,{isStatic:!0}),s.rectangle(400,600,800,50,{isStatic:!0}),s.rectangle(800,300,50,600,{isStatic:!0}),s.rectangle(0,300,50,600,{isStatic:!0}),p]);var m=i.create(d.canvas),f=o.create(c,{mouse:m,constraint:{stiffness:.2,render:{visible:!1}}});return a.add(l,f),d.mouse=m,t.lookAt(d,{min:{x:0,y:0},max:{x:800,y:600}}),{engine:c,runner:u,render:d,canvas:d.canvas,stop:function(){Matter.Render.stop(d),Matter.Runner.stop(u)}}},r.circleStack.title="Circle Stack",r.circleStack.for=">=0.14.2",e.exports=r.circleStack}},[["uZME",1,2,3,4,5]]])})); \ No newline at end of file diff --git a/demo/js/matter-demo.main.5a504f.min.js b/demo/js/matter-demo.main.5754e1.min.js similarity index 97% rename from demo/js/matter-demo.main.5a504f.min.js rename to demo/js/matter-demo.main.5754e1.min.js index 6c59ec5..1455919 100644 --- a/demo/js/matter-demo.main.5a504f.min.js +++ b/demo/js/matter-demo.main.5754e1.min.js @@ -1,5 +1,5 @@ /*! - * matter-demo bundle 0.17.1 by @liabru + * matter-demo bundle 0.18.0 by @liabru * http://brm.io/matter-js/ * License MIT */!function(e){function t(t){for(var n,l,a=t[0],f=t[1],i=t[2],c=0,s=[];c (http://brm.io/)", From 9393ecb5b2c22b91fc4fdc94dbffed9b7946d15c Mon Sep 17 00:00:00 2001 From: liabru Date: Mon, 20 Dec 2021 21:17:02 +0000 Subject: [PATCH 55/62] improved error messages on tests --- test/ExampleWorker.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 7b8b43d..0187069 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -23,10 +23,14 @@ const runExample = options => { let totalDuration = 0; let overlapTotal = 0; let overlapCount = 0; + let i; - global.gc(); + if (global.gc) { + global.gc(); + } - for (let i = 0; i < options.updates; i += 1) { + try { + for (i = 0; i < options.updates; i += 1) { const startTime = process.hrtime(); totalMemory += process.memoryUsage().heapUsed; @@ -48,6 +52,10 @@ const runExample = options => { overlapCount += 1; } } + } + } catch (err) { + err.message = `On example '${options.name}' update ${i}:\n\n ${err.message}`; + throw err; } resetEnvironment(); From e41446493cfd4aa411b772e8bcf3348a4fed3594 Mon Sep 17 00:00:00 2001 From: liabru Date: Tue, 21 Dec 2021 20:09:01 +0000 Subject: [PATCH 56/62] handle null constraint points in Constraint.pointAWorld and Constraint.pointBWorld --- src/constraint/Constraint.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/constraint/Constraint.js b/src/constraint/Constraint.js index bb40fa5..cebdbc5 100644 --- a/src/constraint/Constraint.js +++ b/src/constraint/Constraint.js @@ -308,8 +308,10 @@ var Common = require('../core/Common'); */ Constraint.pointAWorld = function(constraint) { return { - x: (constraint.bodyA ? constraint.bodyA.position.x : 0) + constraint.pointA.x, - y: (constraint.bodyA ? constraint.bodyA.position.y : 0) + constraint.pointA.y + x: (constraint.bodyA ? constraint.bodyA.position.x : 0) + + (constraint.pointA ? constraint.pointA.x : 0), + y: (constraint.bodyA ? constraint.bodyA.position.y : 0) + + (constraint.pointA ? constraint.pointA.y : 0) }; }; @@ -321,8 +323,10 @@ var Common = require('../core/Common'); */ Constraint.pointBWorld = function(constraint) { return { - x: (constraint.bodyB ? constraint.bodyB.position.x : 0) + constraint.pointB.x, - y: (constraint.bodyB ? constraint.bodyB.position.y : 0) + constraint.pointB.y + x: (constraint.bodyB ? constraint.bodyB.position.x : 0) + + (constraint.pointB ? constraint.pointB.x : 0), + y: (constraint.bodyB ? constraint.bodyB.position.y : 0) + + (constraint.pointB ? constraint.pointB.y : 0) }; }; From 04d229ebeca53333b1629a3891ea7bb6da8e68d9 Mon Sep 17 00:00:00 2001 From: liabru Date: Tue, 21 Dec 2021 20:10:48 +0000 Subject: [PATCH 57/62] deprecated render.controller property --- src/render/Render.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/render/Render.js b/src/render/Render.js index 7888226..9532cd4 100644 --- a/src/render/Render.js +++ b/src/render/Render.js @@ -44,7 +44,6 @@ var Mouse = require('../core/Mouse'); */ Render.create = function(options) { var defaults = { - controller: Render, engine: null, element: null, canvas: null, @@ -116,6 +115,7 @@ var Mouse = require('../core/Mouse'); }; // for temporary back compatibility only + render.controller = Render; render.options.showBroadphase = false; if (render.options.pixelRatio !== 1) { @@ -1525,6 +1525,7 @@ var Mouse = require('../core/Mouse'); /** * A back-reference to the `Matter.Render` module. * + * @deprecated * @property controller * @type render */ From 459425b2a323e7c2e795f14620bbd94dd8206aef Mon Sep 17 00:00:00 2001 From: liabru Date: Tue, 21 Dec 2021 20:25:26 +0000 Subject: [PATCH 58/62] remove render element warning --- src/render/Render.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/render/Render.js b/src/render/Render.js index 9532cd4..ae52040 100644 --- a/src/render/Render.js +++ b/src/render/Render.js @@ -124,8 +124,6 @@ var Mouse = require('../core/Mouse'); if (Common.isElement(render.element)) { render.element.appendChild(render.canvas); - } else if (!render.canvas.parentNode) { - Common.log('Render.create: options.element was undefined, render.canvas was created but not appended', 'warn'); } return render; From 7d7bad04562489df4662f0b49d96f63cf658efb0 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 22 Dec 2021 23:33:57 +0000 Subject: [PATCH 59/62] added support for Matter.Runner and Matter.Render in tests --- test/ExampleWorker.js | 204 ++++++++++++++++++++++++++---------------- test/TestTools.js | 50 ++++++++++- 2 files changed, 174 insertions(+), 80 deletions(-) diff --git a/test/ExampleWorker.js b/test/ExampleWorker.js index 0187069..5fe23bb 100644 --- a/test/ExampleWorker.js +++ b/test/ExampleWorker.js @@ -3,21 +3,22 @@ "use strict"; const mock = require('mock-require'); -const { requireUncached } = require('./TestTools'); +const { requireUncached, serialize } = require('./TestTools'); const consoleOriginal = global.console; const runExample = options => { - const Matter = prepareMatter(options); - const logs = prepareEnvironment(Matter); + const { + Matter, + logs, + frameCallbacks + } = prepareEnvironment(options); const Examples = requireUncached('../examples/index'); const example = Examples[options.name](); const engine = example.engine; const runner = example.runner; - - runner.delta = 1000 / 60; - runner.isFixed = true; + const render = example.render; let totalMemory = 0; let totalDuration = 0; @@ -31,14 +32,20 @@ const runExample = options => { try { for (i = 0; i < options.updates; i += 1) { - const startTime = process.hrtime(); - totalMemory += process.memoryUsage().heapUsed; + const time = i * runner.delta; + const callbackCount = frameCallbacks.length; - Matter.Runner.tick(runner, engine, i * runner.delta); + for (let p = 0; p < callbackCount; p += 1) { + totalMemory += process.memoryUsage().heapUsed; + const callback = frameCallbacks.shift(); + const startTime = process.hrtime(); - const duration = process.hrtime(startTime); - totalDuration += duration[0] * 1e9 + duration[1]; - totalMemory += process.memoryUsage().heapUsed; + callback(time); + + const duration = process.hrtime(startTime); + totalMemory += process.memoryUsage().heapUsed; + totalDuration += duration[0] * 1e9 + duration[1]; + } const pairsList = engine.pairs.list; const pairsListLength = engine.pairs.list.length; @@ -53,22 +60,24 @@ const runExample = options => { } } } + + resetEnvironment(); + + return { + name: options.name, + duration: totalDuration, + overlap: overlapTotal / (overlapCount || 1), + memory: totalMemory, + logs: logs, + extrinsic: captureExtrinsics(engine, Matter), + intrinsic: captureIntrinsics(engine, Matter), + state: captureState(engine, runner, render) + }; + } catch (err) { err.message = `On example '${options.name}' update ${i}:\n\n ${err.message}`; throw err; } - - resetEnvironment(); - - return { - name: options.name, - duration: totalDuration, - overlap: overlapTotal / (overlapCount || 1), - memory: totalMemory, - logs: logs, - extrinsic: captureExtrinsics(engine, Matter), - intrinsic: captureIntrinsics(engine, Matter), - }; }; const prepareMatter = (options) => { @@ -78,12 +87,6 @@ const prepareMatter = (options) => { throw 'Matter instance has already been used.'; } - const noop = () => ({ collisionFilter: {}, mouse: {} }); - - Matter.Render.create = () => ({ options: {}, bounds: { min: { x: 0, y: 0 }, max: { x: 800, y: 600 }}}); - Matter.Render.run = Matter.Render.lookAt = noop; - Matter.Runner.create = Matter.Runner.run = noop; - Matter.MouseConstraint.create = Matter.Mouse.create = noop; Matter.Common.info = Matter.Common.warn = Matter.Common.log; if (options.stableSort) { @@ -129,19 +132,50 @@ const prepareMatter = (options) => { return Matter; }; -const prepareEnvironment = Matter => { - mock('matter-js', Matter); - global.Matter = Matter; - +const prepareEnvironment = options => { const logs = []; - global.document = global.window = { addEventListener: () => {} }; + const frameCallbacks = []; + + global.document = global.window = { + addEventListener: () => {}, + requestAnimationFrame: callback => { + frameCallbacks.push(callback); + return frameCallbacks.length; + }, + createElement: () => ({ + parentNode: {}, + width: 800, + height: 600, + style: {}, + addEventListener: () => {}, + getAttribute: name => ({ + 'data-pixel-ratio': '1' + }[name]), + getContext: () => new Proxy({}, { + get() { return () => {}; } + }) + }) + }; + + global.document.body = global.document.createElement(); + + global.Image = function Image() { }; + global.console = { log: (...args) => { logs.push(args.join(' ')); } }; - return logs; + const Matter = prepareMatter(options); + mock('matter-js', Matter); + global.Matter = Matter; + + return { + Matter, + logs, + frameCallbacks + }; }; const resetEnvironment = () => { @@ -167,8 +201,20 @@ const captureExtrinsics = ({ world }, Matter) => ({ return bodies; }, {}), constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => { - const positionA = Matter.Constraint.pointAWorld(constraint); - const positionB = Matter.Constraint.pointBWorld(constraint); + let positionA; + let positionB; + + try { + positionA = Matter.Constraint.pointAWorld(constraint); + } catch (err) { + positionA = { x: 0, y: 0 }; + } + + try { + positionB = Matter.Constraint.pointBWorld(constraint); + } catch (err) { + positionB = { x: 0, y: 0 }; + } constraints[constraint.id] = [ positionA.x, @@ -181,7 +227,7 @@ const captureExtrinsics = ({ world }, Matter) => ({ }, {}) }); -const captureIntrinsics = ({ world }, Matter) => formatIntrinsics({ +const captureIntrinsics = ({ world }, Matter) => serialize({ bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => { bodies[body.id] = body; return bodies; @@ -198,39 +244,16 @@ const captureIntrinsics = ({ world }, Matter) => formatIntrinsics({ }; return composites; }, {}) -}); +}, (key) => !Number.isInteger(parseInt(key)) && !intrinsicProperties.includes(key)); -const formatIntrinsics = (obj, depth=0) => { - if (obj === Infinity) { - return 'Infinity'; - } else if (typeof obj === 'number') { - return limitPrecision(obj); - } else if (Array.isArray(obj)) { - return obj.map(item => formatIntrinsics(item, depth + 1)); - } else if (typeof obj !== 'object') { - return obj; - } - - const result = Object.entries(obj) - .filter(([key]) => depth <= 1 || intrinsicProperties.includes(key)) - .reduce((cleaned, [key, val]) => { - if (val && val.id && String(val.id) !== key) { - val = val.id; - } - - if (Array.isArray(val) && !['composites', 'constraints', 'bodies'].includes(key)) { - val = `[${val.length}]`; - } - - cleaned[key] = formatIntrinsics(val, depth + 1); - return cleaned; - }, {}); - - return Object.keys(result).sort() - .reduce((sorted, key) => (sorted[key] = result[key], sorted), {}); -}; +const captureState = (engine, runner, render, excludeKeys=excludeStateProperties) => ( + serialize({ engine, runner, render }, (key) => excludeKeys.includes(key)) +); const intrinsicProperties = [ + // Composite + 'bodies', 'constraints', 'composites', + // Common 'id', 'label', @@ -238,15 +261,46 @@ const intrinsicProperties = [ 'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness', // Body - 'area', 'axes', 'collisionFilter', 'category', 'mask', - 'group', 'density', 'friction', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', 'isSensor', - 'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution', 'sleepThreshold', 'slop', - 'timeScale', 'vertices', + 'area', 'collisionFilter', 'category', 'mask', 'group', 'density', 'friction', + 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', + 'isSensor', 'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution', + 'sleepThreshold', 'slop', 'timeScale', // Composite 'bodies', 'constraints', 'composites' ]; +const extrinsicProperties = [ + 'axes', + 'vertices', + 'bounds', + 'angle', + 'anglePrev', + 'angularVelocity', + 'angularSpeed', + 'speed', + 'velocity', + 'position', + 'positionPrev', +]; + +const excludeStateProperties = [ + 'cache', + 'grid', + 'context', + 'broadphase', + 'metrics', + 'controller', + 'detector', + 'pairs', + 'lastElapsed', + 'deltaHistory', + 'elapsedHistory', + 'engineDeltaHistory', + 'engineElapsedHistory', + 'timestampElapsedHistory', +].concat(extrinsicProperties); + const collisionId = (collision) => Math.min(collision.bodyA.id, collision.bodyB.id) + Math.max(collision.bodyA.id, collision.bodyB.id) * 10000; @@ -254,6 +308,4 @@ const collisionCompareId = (collisionA, collisionB) => collisionId(collisionA) - const sortById = (objs) => objs.sort((objA, objB) => objA.id - objB.id); -const limitPrecision = (val, precision=3) => parseFloat(val.toPrecision(precision)); - -module.exports = { runExample }; \ No newline at end of file +module.exports = { runExample }; diff --git a/test/TestTools.js b/test/TestTools.js index f0da95c..86c4485 100644 --- a/test/TestTools.js +++ b/test/TestTools.js @@ -35,11 +35,12 @@ const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildV const captureSummary = Object.entries(capturesDev) .map(([name]) => { const changedIntrinsics = !equals(capturesDev[name].intrinsic, capturesBuild[name].intrinsic); + if (changedIntrinsics) { capturesDev[name].changedIntrinsics = true; - if (intrinsicChangeCount < 2) { - devIntrinsicsChanged[name] = capturesDev[name].intrinsic; - buildIntrinsicsChanged[name] = capturesBuild[name].intrinsic; + if (intrinsicChangeCount < 1) { + devIntrinsicsChanged[name] = capturesDev[name].state; + buildIntrinsicsChanged[name] = capturesBuild[name].state; intrinsicChangeCount += 1; } } @@ -172,6 +173,47 @@ const extrinsicSimilarityAverage = (similaritys) => { return average /= entries.length; }; +const serialize = (obj, exclude=()=>false, precision=4, path='$', visited=[], paths=[]) => { + if (typeof obj === 'number') { + return parseFloat(obj.toPrecision(precision)); + } else if (typeof obj === 'string' || typeof obj === 'boolean') { + return obj; + } else if (obj === null) { + return 'null'; + } else if (typeof obj === 'undefined') { + return 'undefined'; + } else if (obj === Infinity) { + return 'Infinity'; + } else if (obj === -Infinity) { + return '-Infinity'; + } else if (typeof obj === 'function') { + return 'function'; + } else if (Array.isArray(obj)) { + return obj.map( + (item, index) => serialize(item, exclude, precision, path + '.' + index, visited, paths) + ); + } + + const visitedIndex = visited.indexOf(obj); + + if (visitedIndex !== -1) { + return paths[visitedIndex]; + } + + visited.push(obj); + paths.push(path); + + const result = {}; + + for (const key of Object.keys(obj).sort()) { + if (!exclude(key, obj[key], path + '.' + key)) { + result[key] = serialize(obj[key], exclude, precision, path + '.' + key, visited, paths); + } + } + + return result; +}; + const writeResult = (name, obj) => { try { fs.mkdirSync(comparePath, { recursive: true }); @@ -245,5 +287,5 @@ const toMatchIntrinsics = { module.exports = { requireUncached, comparisonReport, logReport, - toMatchExtrinsics, toMatchIntrinsics + serialize, toMatchExtrinsics, toMatchIntrinsics }; \ No newline at end of file From 2d483c1a970cd353a275035032cc9211a9571810 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 26 Dec 2021 21:41:33 +0000 Subject: [PATCH 60/62] added ci workflow action --- .github/workflows/ci.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ab96992 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [12.x, 14.x, 16.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run lint + - run: npm run benchmark + - run: npm run test + - run: npm run build + - run: npm run build-demo From e553779dbce3d6577ed05bb87447c062f50d71cf Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 26 Dec 2021 21:57:37 +0000 Subject: [PATCH 61/62] remove travis config --- .travis.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fdb6065..0000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: node_js -sudo: false -node_js: - - "node" -install: - - npm ci -script: - - npm run lint - - npm run benchmark - - npm run test - - npm run build - - npm run build-demo \ No newline at end of file From 014ef77d1e30b44cb65098c0efc9c9d623a40496 Mon Sep 17 00:00:00 2001 From: liabru Date: Sun, 26 Dec 2021 21:57:51 +0000 Subject: [PATCH 62/62] increase test timeout --- test/Examples.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Examples.spec.js b/test/Examples.spec.js index fd30ca9..b1fb870 100644 --- a/test/Examples.spec.js +++ b/test/Examples.spec.js @@ -1,7 +1,7 @@ /* eslint-env es6 */ "use strict"; -jest.setTimeout(30 * 1000); +jest.setTimeout(2 * 60 * 1000); const fs = require('fs');