From fbb7c1ad94bd530279e37e2a4d400aa5612a7914 Mon Sep 17 00:00:00 2001 From: liabru Date: Wed, 20 May 2015 20:38:41 +0100 Subject: [PATCH] updated edge build --- build/matter.js | 1822 ++++++++++++++++++++++++++++++++----------- build/matter.min.js | 7 +- 2 files changed, 1388 insertions(+), 441 deletions(-) diff --git a/build/matter.js b/build/matter.js index 0bac6f2..0d8ae0f 100644 --- a/build/matter.js +++ b/build/matter.js @@ -1,5 +1,5 @@ /** -* matter.js 0.8.0-edge 2015-01-21 +* matter.js edge-master 2015-05-20 * http://brm.io/matter-js/ * License: MIT */ @@ -74,6 +74,7 @@ var Body = {}; id: Common.nextId(), type: 'body', label: 'Body', + parts: [], angle: 0, vertices: Vertices.fromPath('L 0 0 L 40 0 L 40 40 L 0 40'), position: { x: 0, y: 0 }, @@ -81,6 +82,7 @@ var Body = {}; 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 }, @@ -92,6 +94,7 @@ var Body = {}; density: 0.001, restitution: 0, friction: 0.1, + frictionStatic: 0.5, frictionAir: 0.01, collisionFilter: { category: 0x0001, @@ -151,14 +154,16 @@ var Body = {}; * @param {} options */ var _initProperties = function(body, options) { - // init required properties + // init required properties (order is important) Body.set(body, { bounds: body.bounds || Bounds.create(body.vertices), positionPrev: body.positionPrev || Vector.clone(body.position), anglePrev: body.anglePrev || body.angle, vertices: body.vertices, + parts: body.parts || [body], isStatic: body.isStatic, - isSleeping: body.isSleeping + isSleeping: body.isSleeping, + parent: body.parent || body }); Vertices.rotate(body.vertices, body.angle, body.position); @@ -235,6 +240,9 @@ var Body = {}; case 'angularVelocity': Body.setAngularVelocity(body, value); break; + case 'parts': + Body.setParts(body, value); + break; default: body[property] = value; @@ -249,21 +257,24 @@ var Body = {}; * @param {bool} isStatic */ Body.setStatic = function(body, isStatic) { - body.isStatic = isStatic; + for (var i = 0; i < body.parts.length; i++) { + var part = body.parts[i]; + part.isStatic = isStatic; - if (isStatic) { - body.restitution = 0; - body.friction = 1; - body.mass = body.inertia = body.density = Infinity; - body.inverseMass = body.inverseInertia = 0; + if (isStatic) { + part.restitution = 0; + part.friction = 1; + part.mass = part.inertia = part.density = Infinity; + part.inverseMass = part.inverseInertia = 0; - body.positionPrev.x = body.position.x; - body.positionPrev.y = body.position.y; - body.anglePrev = body.angle; - body.angularVelocity = 0; - body.speed = 0; - body.angularSpeed = 0; - body.motion = 0; + part.positionPrev.x = part.position.x; + part.positionPrev.y = part.position.y; + part.anglePrev = part.angle; + part.angularVelocity = 0; + part.speed = 0; + part.angularSpeed = 0; + part.motion = 0; + } } }; @@ -339,6 +350,69 @@ var Body = {}; Bounds.update(body.bounds, body.vertices, body.velocity); }; + /** + * Sets the parts of the `body` and updates mass, inertia and centroid. + * Each part will have its parent set to `body`. + * By default the convex hull will be automatically computed and set on `body`, unless `autoHull` is set to `false.` + * Note that this method will ensure that the first part in `body.parts` will always be the `body`. + * @method setParts + * @param {body} body + * @param [body] parts + * @param {bool} [autoHull=true] + */ + Body.setParts = function(body, parts, autoHull) { + var i; + + // add all the parts, ensuring that the first part is always the parent body + parts = parts.slice(0); + body.parts.length = 0; + body.parts.push(body); + body.parent = body; + + for (i = 0; i < parts.length; i++) { + var part = parts[i]; + if (part !== body) { + part.parent = body; + body.parts.push(part); + } + } + + if (body.parts.length === 1) + return; + + autoHull = typeof autoHull !== 'undefined' ? autoHull : true; + + // find the convex hull of all parts to set on the parent body + if (autoHull) { + var vertices = []; + for (i = 0; i < parts.length; i++) { + vertices = vertices.concat(parts[i].vertices); + } + + Vertices.clockwiseSort(vertices); + + var hull = Vertices.hull(vertices), + hullCentre = Vertices.centre(hull); + + Body.setVertices(body, hull); + Vertices.translate(body.vertices, hullCentre); + } + + // sum the properties of all compound parts of the parent body + var total = _totalProperties(body); + + body.area = total.area; + body.parent = body; + body.position.x = total.centre.x; + body.position.y = total.centre.y; + body.positionPrev.x = total.centre.x; + body.positionPrev.y = total.centre.y; + + Body.setMass(body, total.mass); + Body.setInertia(body, total.inertia); + Body.setPosition(body, total.centre); + }; + /** * Sets the position of the body instantly. Velocity, angle, force etc. are unchanged. * @method setPosition @@ -347,14 +421,16 @@ var Body = {}; */ Body.setPosition = function(body, position) { var delta = Vector.sub(position, body.position); - - body.position.x = position.x; - body.position.y = position.y; body.positionPrev.x += delta.x; body.positionPrev.y += delta.y; - Vertices.translate(body.vertices, delta); - Bounds.update(body.bounds, body.vertices, body.velocity); + for (var i = 0; i < body.parts.length; i++) { + var part = body.parts[i]; + part.position.x += delta.x; + part.position.y += delta.y; + Vertices.translate(part.vertices, delta); + Bounds.update(part.bounds, part.vertices, body.velocity); + } }; /** @@ -365,13 +441,18 @@ var Body = {}; */ Body.setAngle = function(body, angle) { var delta = angle - body.angle; - - body.angle = angle; body.anglePrev += delta; - Vertices.rotate(body.vertices, delta, body.position); - Axes.rotate(body.axes, delta); - Bounds.update(body.bounds, body.vertices, body.velocity); + for (var i = 0; i < body.parts.length; i++) { + var part = body.parts[i]; + part.angle += delta; + Vertices.rotate(part.vertices, delta, body.position); + Axes.rotate(part.axes, delta); + Bounds.update(part.bounds, part.vertices, body.velocity); + if (i > 0) { + Vector.rotateAbout(part.position, delta, body.position, part.position); + } + } }; /** @@ -429,21 +510,35 @@ var Body = {}; * @param {vector} [point] */ Body.scale = function(body, scaleX, scaleY, point) { - // scale vertices - Vertices.scale(body.vertices, scaleX, scaleY, point); + for (var i = 0; i < body.parts.length; i++) { + var part = body.parts[i]; - // update properties - body.axes = Axes.fromVertices(body.vertices); - body.area = Vertices.area(body.vertices); - Body.setMass(body, body.density * body.area); + // scale vertices + Vertices.scale(part.vertices, scaleX, scaleY, body.position); - // update inertia (requires vertices to be at origin) - Vertices.translate(body.vertices, { x: -body.position.x, y: -body.position.y }); - Body.setInertia(body, Vertices.inertia(body.vertices, body.mass)); - Vertices.translate(body.vertices, { x: body.position.x, y: body.position.y }); + // update properties + part.axes = Axes.fromVertices(part.vertices); - // update bounds - Bounds.update(body.bounds, body.vertices, body.velocity); + if (!body.isStatic) { + part.area = Vertices.area(part.vertices); + Body.setMass(part, body.density * part.area); + + // update inertia (requires vertices to be at origin) + Vertices.translate(part.vertices, { x: -part.position.x, y: -part.position.y }); + Body.setInertia(part, Vertices.inertia(part.vertices, part.mass)); + Vertices.translate(part.vertices, { x: part.position.x, y: part.position.y }); + } + + // update bounds + Bounds.update(part.bounds, part.vertices, body.velocity); + } + + if (!body.isStatic) { + var total = _totalProperties(body); + body.area = total.area; + Body.setMass(body, total.mass); + Body.setInertia(body, total.inertia); + } }; /** @@ -481,12 +576,26 @@ var Body = {}; body.angularSpeed = Math.abs(body.angularVelocity); // transform the body geometry - Vertices.translate(body.vertices, body.velocity); - if (body.angularVelocity !== 0) { - Vertices.rotate(body.vertices, body.angularVelocity, body.position); - Axes.rotate(body.axes, body.angularVelocity); + for (var i = 0; i < body.parts.length; i++) { + var part = body.parts[i]; + + Vertices.translate(part.vertices, body.velocity); + + if (i > 0) { + part.position.x += body.velocity.x; + part.position.y += body.velocity.y; + } + + if (body.angularVelocity !== 0) { + Vertices.rotate(part.vertices, body.angularVelocity, body.position); + Axes.rotate(part.axes, body.angularVelocity); + if (i > 0) { + Vector.rotateAbout(part.position, body.angularVelocity, body.position, part.position); + } + } + + Bounds.update(part.bounds, part.vertices, body.velocity); } - Bounds.update(body.bounds, body.vertices, body.velocity); }; /** @@ -503,6 +612,40 @@ var Body = {}; body.torque += (offset.x * force.y - offset.y * force.x) * body.inverseInertia; }; + /** + * Returns the sums of the properties of all compound parts of the parent body. + * @method _totalProperties + * @private + * @param {body} body + * @return {} + */ + var _totalProperties = function(body) { + // https://ecourses.ou.edu/cgi-bin/ebook.cgi?doc=&topic=st&chap_sec=07.2&page=theory + // http://output.to/sideway/default.asp?qno=121100087 + + var properties = { + mass: 0, + area: 0, + inertia: 0, + centre: { x: 0, y: 0 } + }; + + // sum the properties of all compound parts of the parent body + for (var i = body.parts.length === 1 ? 0 : 1; i < body.parts.length; i++) { + var part = body.parts[i]; + properties.mass += part.mass; + properties.area += part.area; + properties.inertia += part.inertia; + properties.centre = Vector.add(properties.centre, + Vector.mult(part.position, part.mass !== Infinity ? part.mass : 1)); + } + + properties.centre = Vector.div(properties.centre, + properties.mass !== Infinity ? properties.mass : body.parts.length); + + return properties; + }; + /* * * Properties Documentation @@ -532,6 +675,27 @@ var Body = {}; * @default "Body" */ + /** + * An array of bodies that make up this body. + * The first body in the array must always be a self reference to the current body instance. + * All bodies in the `parts` array together form a single rigid compound body. + * Parts are allowed to overlap, have gaps or holes or even form concave bodies. + * Parts themselves should never be added to a `World`, only the parent body should be. + * Use `Body.setParts` when setting parts to ensure correct updates of all properties. + * + * @property parts + * @type body[] + */ + + /** + * A self reference if the body is _not_ a part of another body. + * Otherwise this is a reference to the body that this is a part of. + * See `body.parts`. + * + * @property parent + * @type body + */ + /** * A `Number` specifying the angle of the body, in radians. * @@ -727,6 +891,17 @@ var Body = {}; * @default 0.1 */ + /** + * A `Number` that defines the static friction of the body (in the Coulomb friction model). + * A value of `0` means the body will never 'stick' when it is nearly stationary and only dynamic `friction` is used. + * The higher the value (e.g. `10`), the more force it will take to initially get the body moving when nearly stationary. + * This value is multiplied with the `friction` property to make it easier to change `friction` and maintain an appropriate amount of static friction. + * + * @property frictionStatic + * @type number + * @default 0.5 + */ + /** * A `Number` that defines the air friction of the body (air resistance). * A value of `0` means the body will never slow as it moves through space. @@ -996,6 +1171,12 @@ var Composite = {}; switch (obj.type) { case 'body': + // skip adding compound parts + if (obj.parent !== obj) { + Common.log('Composite.add: skipped adding a compound body part (you must add its parent instead)', 'warn'); + break; + } + Composite.addBody(composite, obj); break; case 'constraint': @@ -1579,6 +1760,7 @@ var Composite = {}; * and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples. * * @class World +* @extends Composite */ var World = {}; @@ -1713,9 +1895,9 @@ var Detector = {}; */ Detector.collisions = function(broadphasePairs, engine) { var collisions = [], - metrics = engine.metrics, pairsTable = engine.pairs.table; + for (var i = 0; i < broadphasePairs.length; i++) { var bodyA = broadphasePairs[i][0], bodyB = broadphasePairs[i][1]; @@ -1726,33 +1908,34 @@ var Detector = {}; if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) continue; - metrics.midphaseTests += 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]; - // find a previous collision we could reuse - var pairId = Pair.id(bodyA, bodyB), - pair = pairsTable[pairId], - previousCollision; + for (var k = bodyB.parts.length > 1 ? 1 : 0; k < bodyB.parts.length; k++) { + var partB = bodyB.parts[k]; - if (pair && pair.isActive) { - previousCollision = pair.collision; - } else { - previousCollision = null; - } + 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; - // narrow phase - var collision = SAT.collides(bodyA, bodyB, previousCollision); + if (pair && pair.isActive) { + previousCollision = pair.collision; + } else { + previousCollision = null; + } - metrics.narrowphaseTests += 1; + // narrow phase + var collision = SAT.collides(partA, partB, previousCollision); - if (collision.reused) - metrics.narrowReuseCount += 1; - - if (collision.collided) { - collisions.push(collision); - metrics.narrowDetections += 1; + if (collision.collided) { + collisions.push(collision); + } + } + } } } } @@ -1769,7 +1952,6 @@ var Detector = {}; */ Detector.bruteForce = function(bodies, engine) { var collisions = [], - metrics = engine.metrics, pairsTable = engine.pairs.table; for (var i = 0; i < bodies.length; i++) { @@ -1785,8 +1967,6 @@ var Detector = {}; if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) continue; - metrics.midphaseTests += 1; - // mid phase if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) { @@ -1804,14 +1984,8 @@ var Detector = {}; // narrow phase var collision = SAT.collides(bodyA, bodyB, previousCollision); - metrics.narrowphaseTests += 1; - - if (collision.reused) - metrics.narrowReuseCount += 1; - if (collision.collided) { collisions.push(collision); - metrics.narrowDetections += 1; } } } @@ -1888,11 +2062,8 @@ var Grid = {}; buckets = grid.buckets, bucket, bucketId, - metrics = engine.metrics, gridChanged = false; - metrics.broadphaseTests = 0; - for (i = 0; i < bodies.length; i++) { var body = bodies[i]; @@ -1909,8 +2080,6 @@ var Grid = {}; // if the body has changed grid region if (!body.region || newRegion.id !== body.region.id || forceUpdate) { - metrics.broadphaseTests += 1; - if (!body.region || forceUpdate) body.region = newRegion; @@ -2163,7 +2332,9 @@ var Pair = {}; */ Pair.create = function(collision, timestamp) { var bodyA = collision.bodyA, - bodyB = collision.bodyB; + bodyB = collision.bodyB, + parentA = collision.parentA, + parentB = collision.parentB; var pair = { id: Pair.id(bodyA, bodyB), @@ -2175,10 +2346,11 @@ var Pair = {}; isActive: true, timeCreated: timestamp, timeUpdated: timestamp, - inverseMass: bodyA.inverseMass + bodyB.inverseMass, - friction: Math.min(bodyA.friction, bodyB.friction), - restitution: Math.max(bodyA.restitution, bodyB.restitution), - slop: Math.max(bodyA.slop, bodyB.slop) + 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); @@ -2195,13 +2367,16 @@ var Pair = {}; Pair.update = function(pair, collision, timestamp) { var contacts = pair.contacts, supports = collision.supports, - activeContacts = pair.activeContacts; + activeContacts = pair.activeContacts, + parentA = collision.parentA, + parentB = collision.parentB; pair.collision = collision; - pair.inverseMass = collision.bodyA.inverseMass + collision.bodyB.inverseMass; - pair.friction = Math.min(collision.bodyA.friction, collision.bodyB.friction); - pair.restitution = Math.max(collision.bodyA.restitution, collision.bodyB.restitution); - pair.slop = Math.max(collision.bodyA.slop, collision.bodyB.slop); + 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) { @@ -2439,7 +2614,7 @@ var Query = {}; * @return {object[]} Collisions */ Query.ray = function(bodies, startPoint, endPoint, rayWidth) { - rayWidth = rayWidth || Number.MIN_VALUE; + rayWidth = rayWidth || 1e-100; var rayAngle = Vector.angle(startPoint, endPoint), rayLength = Vector.magnitude(Vector.sub(startPoint, endPoint)), @@ -2450,12 +2625,19 @@ var Query = {}; for (var i = 0; i < bodies.length; i++) { var bodyA = bodies[i]; - + if (Bounds.overlaps(bodyA.bounds, ray.bounds)) { - var collision = SAT.collides(bodyA, ray); - if (collision.collided) { - collision.body = collision.bodyA = collision.bodyB = bodyA; - collisions.push(collision); + for (var j = bodyA.parts.length === 1 ? 0 : 1; j < bodyA.parts.length; j++) { + var part = bodyA.parts[j]; + + if (Bounds.overlaps(part.bounds, ray.bounds)) { + var collision = SAT.collides(part, ray); + if (collision.collided) { + collision.body = collision.bodyA = collision.bodyB = bodyA; + collisions.push(collision); + break; + } + } } } } @@ -2484,6 +2666,35 @@ var Query = {}; return result; }; + /** + * Returns all bodies whose vertices contain the given point, from the given set of bodies. + * @method point + * @param {body[]} bodies + * @param {vector} point + * @return {body[]} The bodies matching the query + */ + Query.point = function(bodies, point) { + var result = []; + + for (var i = 0; i < bodies.length; i++) { + var body = bodies[i]; + + if (Bounds.contains(body.bounds, point)) { + for (var j = body.parts.length === 1 ? 0 : 1; j < body.parts.length; j++) { + var part = body.parts[j]; + + if (Bounds.contains(part.bounds, point) + && Vertices.contains(part.vertices, point)) { + result.push(body); + break; + } + } + } + } + + return result; + }; + })(); ; // End src/collision/Query.js @@ -2501,9 +2712,33 @@ var Resolver = {}; (function() { - var _restingThresh = 4, - _positionDampen = 0.2, - _positionWarming = 0.6; + Resolver._restingThresh = 4; + Resolver._positionDampen = 0.9; + Resolver._positionWarming = 0.8; + Resolver._frictionNormalMultiplier = 5; + + /** + * Description + * @method preSolvePosition + * @param {pair[]} pairs + */ + Resolver.preSolvePosition = function(pairs) { + var i, + pair, + activeCount; + + // find total contacts on each body + for (i = 0; i < pairs.length; i++) { + pair = pairs[i]; + + if (!pair.isActive) + continue; + + activeCount = pair.activeContacts.length; + pair.collision.parentA.totalContacts += activeCount; + pair.collision.parentB.totalContacts += activeCount; + } + }; /** * Description @@ -2517,10 +2752,14 @@ var Resolver = {}; collision, bodyA, bodyB, - vertex, - vertexCorrected, normal, - bodyBtoA; + bodyBtoA, + contactShare, + contactCount = {}, + tempA = Vector._temp[0], + tempB = Vector._temp[1], + tempC = Vector._temp[2], + tempD = Vector._temp[3]; // find impulses required to resolve penetration for (i = 0; i < pairs.length; i++) { @@ -2530,42 +2769,43 @@ var Resolver = {}; continue; collision = pair.collision; - bodyA = collision.bodyA; - bodyB = collision.bodyB; - vertex = collision.supports[0]; - vertexCorrected = collision.supportCorrected; + bodyA = collision.parentA; + bodyB = collision.parentB; normal = collision.normal; // get current separation between body edges involved in collision - bodyBtoA = Vector.sub(Vector.add(bodyB.positionImpulse, vertex), - Vector.add(bodyA.positionImpulse, vertexCorrected)); + 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); } for (i = 0; i < pairs.length; i++) { pair = pairs[i]; - - if (!pair.isActive) + + if (!pair.isActive || pair.separation < 0) continue; collision = pair.collision; - bodyA = collision.bodyA; - bodyB = collision.bodyB; + bodyA = collision.parentA; + bodyB = collision.parentB; normal = collision.normal; - positionImpulse = ((pair.separation * _positionDampen) - pair.slop) * timeScale; + positionImpulse = (pair.separation - pair.slop) * timeScale; if (bodyA.isStatic || bodyB.isStatic) positionImpulse *= 2; if (!(bodyA.isStatic || bodyA.isSleeping)) { - bodyA.positionImpulse.x += normal.x * positionImpulse; - bodyA.positionImpulse.y += normal.y * positionImpulse; + contactShare = Resolver._positionDampen / bodyA.totalContacts; + bodyA.positionImpulse.x += normal.x * positionImpulse * contactShare; + bodyA.positionImpulse.y += normal.y * positionImpulse * contactShare; } if (!(bodyB.isStatic || bodyB.isSleeping)) { - bodyB.positionImpulse.x -= normal.x * positionImpulse; - bodyB.positionImpulse.y -= normal.y * positionImpulse; + contactShare = Resolver._positionDampen / bodyB.totalContacts; + bodyB.positionImpulse.x -= normal.x * positionImpulse * contactShare; + bodyB.positionImpulse.y -= normal.y * positionImpulse * contactShare; } } }; @@ -2579,20 +2819,32 @@ var Resolver = {}; for (var i = 0; i < bodies.length; i++) { var body = bodies[i]; + // reset contact count + body.totalContacts = 0; + if (body.positionImpulse.x !== 0 || body.positionImpulse.y !== 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; + } + // move the body without changing velocity - body.position.x += body.positionImpulse.x; - body.position.y += body.positionImpulse.y; body.positionPrev.x += body.positionImpulse.x; body.positionPrev.y += body.positionImpulse.y; - // update body geometry - Vertices.translate(body.vertices, body.positionImpulse); - Bounds.update(body.bounds, body.vertices, body.velocity); - - // dampen accumulator to warm the next step - body.positionImpulse.x *= _positionWarming; - body.positionImpulse.y *= _positionWarming; + if (Vector.dot(body.positionImpulse, body.velocity) < 0) { + // reset cached impulse if the body has velocity along it + body.positionImpulse.x = 0; + body.positionImpulse.y = 0; + } else { + // warm the next iteration + body.positionImpulse.x *= Resolver._positionWarming; + body.positionImpulse.y *= Resolver._positionWarming; + } } } }; @@ -2603,8 +2855,7 @@ var Resolver = {}; * @param {pair[]} pairs */ Resolver.preSolveVelocity = function(pairs) { - var impulse = {}, - i, + var i, j, pair, contacts, @@ -2617,7 +2868,9 @@ var Resolver = {}; contactVertex, normalImpulse, tangentImpulse, - offset; + offset, + impulse = Vector._temp[0], + tempA = Vector._temp[1]; for (i = 0; i < pairs.length; i++) { pair = pairs[i]; @@ -2627,8 +2880,8 @@ var Resolver = {}; contacts = pair.activeContacts; collision = pair.collision; - bodyA = collision.bodyA; - bodyB = collision.bodyB; + bodyA = collision.parentA; + bodyB = collision.parentB; normal = collision.normal; tangent = collision.tangent; @@ -2638,24 +2891,26 @@ var Resolver = {}; contactVertex = contact.vertex; normalImpulse = contact.normalImpulse; tangentImpulse = contact.tangentImpulse; - - // total impulse from contact - impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse); - impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse); - - // apply impulse from contact - if (!(bodyA.isStatic || bodyA.isSleeping)) { - offset = Vector.sub(contactVertex, bodyA.position); - bodyA.positionPrev.x += impulse.x * bodyA.inverseMass; - bodyA.positionPrev.y += impulse.y * bodyA.inverseMass; - bodyA.anglePrev += Vector.cross(offset, impulse) * bodyA.inverseInertia; - } - if (!(bodyB.isStatic || bodyB.isSleeping)) { - offset = Vector.sub(contactVertex, bodyB.position); - bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass; - bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass; - bodyB.anglePrev -= Vector.cross(offset, impulse) * bodyB.inverseInertia; + 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); + + // 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; + } + + 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; + } } } } @@ -2667,8 +2922,13 @@ var Resolver = {}; * @param {pair[]} pairs */ Resolver.solveVelocity = function(pairs, timeScale) { - var impulse = {}, - timeScaleSquared = timeScale * 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++) { var pair = pairs[i]; @@ -2677,8 +2937,8 @@ var Resolver = {}; continue; var collision = pair.collision, - bodyA = collision.bodyA, - bodyB = collision.bodyB, + bodyA = collision.parentA, + bodyB = collision.parentB, normal = collision.normal, tangent = collision.tangent, contacts = pair.activeContacts, @@ -2696,11 +2956,11 @@ var Resolver = {}; for (var j = 0; j < contacts.length; j++) { var contact = contacts[j], contactVertex = contact.vertex, - offsetA = Vector.sub(contactVertex, bodyA.position), - offsetB = Vector.sub(contactVertex, bodyB.position), - velocityPointA = Vector.add(bodyA.velocity, Vector.mult(Vector.perp(offsetA), bodyA.angularVelocity)), - velocityPointB = Vector.add(bodyB.velocity, Vector.mult(Vector.perp(offsetB), bodyB.angularVelocity)), - relativeVelocity = Vector.sub(velocityPointA, velocityPointB), + 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); var tangentVelocity = Vector.dot(tangent, relativeVelocity), @@ -2709,22 +2969,27 @@ var Resolver = {}; // raw impulses var normalImpulse = (1 + pair.restitution) * normalVelocity, - normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1); + normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1) * Resolver._frictionNormalMultiplier; // coulomb friction - var tangentImpulse = tangentVelocity; - if (tangentSpeed > normalForce * pair.friction * timeScaleSquared) - tangentImpulse = normalForce * pair.friction * timeScaleSquared * tangentVelocityDirection; + var tangentImpulse = tangentVelocity, + maxFriction = Infinity; + + if (tangentSpeed > pair.friction * pair.frictionStatic * normalForce * timeScaleSquared) { + tangentImpulse = pair.friction * tangentVelocityDirection * timeScaleSquared; + maxFriction = tangentSpeed; + } // modify impulses accounting for mass, inertia and offset var oAcN = Vector.cross(offsetA, normal), oBcN = Vector.cross(offsetB, normal), - share = contactShare / (pair.inverseMass + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN); - normalImpulse *= share; - tangentImpulse *= share; - + denom = bodyA.inverseMass + bodyB.inverseMass + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN; + + normalImpulse *= contactShare / denom; + tangentImpulse *= contactShare / (1 + denom); + // handle high velocity and resting collisions separately - if (normalVelocity < 0 && normalVelocity * normalVelocity > _restingThresh * timeScaleSquared) { + if (normalVelocity < 0 && normalVelocity * normalVelocity > Resolver._restingThresh * timeScaleSquared) { // high velocity so clear cached contact impulse contact.normalImpulse = 0; contact.tangentImpulse = 0; @@ -2738,7 +3003,7 @@ var Resolver = {}; // tangent impulse, tends to -maxFriction or maxFriction var contactTangentImpulse = contact.tangentImpulse; - contact.tangentImpulse = Common.clamp(contact.tangentImpulse + tangentImpulse, -tangentSpeed, tangentSpeed); + contact.tangentImpulse = Common.clamp(contact.tangentImpulse + tangentImpulse, -maxFriction, maxFriction); tangentImpulse = contact.tangentImpulse - contactTangentImpulse; } @@ -2799,8 +3064,10 @@ var SAT = {}; if (prevCol) { // estimate total motion - var motion = bodyA.speed * bodyA.speed + bodyA.angularSpeed * bodyA.angularSpeed - + bodyB.speed * bodyB.speed + bodyB.angularSpeed * bodyB.angularSpeed; + 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 @@ -2815,9 +3082,11 @@ var SAT = {}; if (prevCol && canReusePrevCol) { // if we can reuse the collision result // we only need to test the previously found axis - var axes = [prevCol.bodyA.axes[prevCol.axisNumber]]; + var axisBodyA = collision.axisBody, + axisBodyB = axisBodyA === bodyA ? bodyB : bodyA, + axes = [axisBodyA.axes[prevCol.axisNumber]]; - minOverlap = _overlapAxes(prevCol.bodyA.vertices, prevCol.bodyB.vertices, axes); + minOverlap = _overlapAxes(axisBodyA.vertices, axisBodyB.vertices, axes); collision.reused = true; if (minOverlap.overlap <= 0) { @@ -2843,21 +3112,23 @@ var SAT = {}; if (overlapAB.overlap < overlapBA.overlap) { minOverlap = overlapAB; - collision.bodyA = bodyA; - collision.bodyB = bodyB; + collision.axisBody = bodyA; } else { minOverlap = overlapBA; - collision.bodyA = bodyB; - collision.bodyB = bodyA; + 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.normal = minOverlap.axis; collision.depth = minOverlap.overlap; + collision.parentA = collision.bodyA.parent; + collision.parentB = collision.bodyB.parent; bodyA = collision.bodyA; bodyB = collision.bodyB; @@ -2897,11 +3168,10 @@ var SAT = {}; } // account for the edge case of overlapping but no vertex containment - if (supports.length < 2) + if (supports.length < 1) supports = [verticesB[0]]; collision.supports = supports; - collision.supportCorrected = Vector.sub(supports[0], collision.penetration); return collision; }; @@ -2916,8 +3186,8 @@ var SAT = {}; * @return result */ var _overlapAxes = function(verticesA, verticesB, axes) { - var projectionA = {}, - projectionB = {}, + var projectionA = Vector._temp[0], + projectionB = Vector._temp[1], result = { overlap: Number.MAX_VALUE }, overlap, axis; @@ -2928,9 +3198,7 @@ var SAT = {}; _projectToAxis(projectionA, verticesA, axis); _projectToAxis(projectionB, verticesB, axis); - overlap = projectionA.min < projectionB.min - ? projectionA.max - projectionB.min - : projectionB.max - projectionA.min; + overlap = Math.min(projectionA.max - projectionB.min, projectionB.max - projectionA.min); if (overlap <= 0) { result.overlap = overlap; @@ -2984,13 +3252,13 @@ var SAT = {}; */ var _findSupports = function(bodyA, bodyB, normal) { var nearestDistance = Number.MAX_VALUE, - vertexToBody = { x: 0, y: 0 }, + vertexToBody = Vector._temp[0], vertices = bodyB.vertices, bodyAPosition = bodyA.position, distance, vertex, - vertexA = vertices[0], - vertexB = vertices[1]; + vertexA, + vertexB; // find closest vertex on bodyB for (var i = 0; i < vertices.length; i++) { @@ -3284,16 +3552,28 @@ var Constraint = {}; impulse = body.constraintImpulse; // update geometry and reset - Vertices.translate(body.vertices, impulse); + for (var j = 0; j < body.parts.length; j++) { + var part = body.parts[j]; + + Vertices.translate(part.vertices, impulse); - if (impulse.angle !== 0) { - Vertices.rotate(body.vertices, impulse.angle, body.position); - Axes.rotate(body.axes, impulse.angle); - impulse.angle = 0; + if (j > 0) { + part.position.x += impulse.x; + part.position.y += impulse.y; + } + + if (impulse.angle !== 0) { + Vertices.rotate(part.vertices, impulse.angle, body.position); + Axes.rotate(part.axes, impulse.angle); + if (j > 0) { + Vector.rotateAbout(part.position, impulse.angle, body.position, part.position); + } + } + + Bounds.update(part.bounds, part.vertices); } - Bounds.update(body.bounds, body.vertices); - + impulse.angle = 0; impulse.x = 0; impulse.y = 0; } @@ -3504,16 +3784,21 @@ var MouseConstraint = {}; for (var i = 0; i < bodies.length; i++) { body = bodies[i]; if (Bounds.contains(body.bounds, mouse.position) - && Vertices.contains(body.vertices, mouse.position) && Detector.canCollide(body.collisionFilter, mouseConstraint.collisionFilter)) { - - constraint.pointA = mouse.position; - constraint.bodyB = mouseConstraint.body = body; - constraint.pointB = { x: mouse.position.x - body.position.x, y: mouse.position.y - body.position.y }; - constraint.angleB = body.angle; + for (var j = body.parts.length > 1 ? 1 : 0; j < body.parts.length; j++) { + var part = body.parts[j]; + if (Vertices.contains(part.vertices, mouse.position)) { + constraint.pointA = mouse.position; + constraint.bodyB = mouseConstraint.body = body; + constraint.pointB = { x: mouse.position.x - body.position.x, y: mouse.position.y - body.position.y }; + constraint.angleB = body.angle; - Sleeping.set(body, false); - Events.trigger(mouseConstraint, 'startdrag', { mouse: mouse, body: body }); + Sleeping.set(body, false); + Events.trigger(mouseConstraint, 'startdrag', { mouse: mouse, body: body }); + + break; + } + } } } } else { @@ -3834,6 +4119,16 @@ var Common = {}; (typeof obj.ownerDocument ==="object"); } }; + + /** + * Description + * @method isArray + * @param {object} obj + * @return {boolean} True if the object is an array, otherwise false + */ + Common.isArray = function(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; /** * Description @@ -3922,8 +4217,6 @@ var Common = {}; if (!console || !console.log || !console.warn) return; - var style; - switch (type) { case 'warn': @@ -4041,8 +4334,8 @@ var Engine = {}; engine.render = engine.render.controller.create(engine.render); engine.world = World.create(engine.world); engine.pairs = Pairs.create(); - engine.metrics = engine.metrics || Metrics.create(); engine.broadphase = engine.broadphase.controller.create(engine.broadphase); + engine.metrics = engine.metrics || { extended: false }; return engine; }; @@ -4080,9 +4373,6 @@ var Engine = {}; var allBodies = Composite.allBodies(world), allConstraints = Composite.allConstraints(world); - // reset metrics logging - Metrics.reset(engine.metrics); - // if sleeping enabled, call the sleeping controller if (engine.enableSleeping) Sleeping.update(allBodies, timing.timeScale); @@ -4132,17 +4422,18 @@ var Engine = {}; if (pairs.collisionStart.length > 0) Events.trigger(engine, 'collisionStart', { pairs: pairs.collisionStart }); + // iteratively resolve position between collisions + Resolver.preSolvePosition(pairs.list); + for (i = 0; i < engine.positionIterations; i++) { + Resolver.solvePosition(pairs.list, timing.timeScale); + } + Resolver.postSolvePosition(allBodies); + // iteratively resolve velocity between collisions Resolver.preSolveVelocity(pairs.list); for (i = 0; i < engine.velocityIterations; i++) { Resolver.solveVelocity(pairs.list, timing.timeScale); } - - // iteratively resolve position between collisions - for (i = 0; i < engine.positionIterations; i++) { - Resolver.solvePosition(pairs.list, timing.timeScale); - } - Resolver.postSolvePosition(allBodies); // trigger collision events if (pairs.collisionActive.length > 0) @@ -4151,9 +4442,6 @@ var Engine = {}; if (pairs.collisionEnd.length > 0) Events.trigger(engine, 'collisionEnd', { pairs: pairs.collisionEnd }); - // update metrics log - Metrics.update(engine.metrics, engine); - // clear force buffers _bodiesClearForces(allBodies); @@ -4662,90 +4950,6 @@ var Events = {}; // Begin src/core/Metrics.js -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Metrics -*/ - -var Metrics = {}; - -(function() { - - /** - * Description - * @method create - * @return {metrics} A new metrics - */ - Metrics.create = function() { - return { - extended: false, - narrowDetections: 0, - narrowphaseTests: 0, - narrowReuse: 0, - narrowReuseCount: 0, - midphaseTests: 0, - broadphaseTests: 0, - narrowEff: 0.0001, - midEff: 0.0001, - broadEff: 0.0001, - collisions: 0, - buckets: 0, - bodies: 0, - pairs: 0 - }; - }; - - /** - * Description - * @method reset - * @param {metrics} metrics - */ - Metrics.reset = function(metrics) { - if (metrics.extended) { - metrics.narrowDetections = 0; - metrics.narrowphaseTests = 0; - metrics.narrowReuse = 0; - metrics.narrowReuseCount = 0; - metrics.midphaseTests = 0; - metrics.broadphaseTests = 0; - metrics.narrowEff = 0; - metrics.midEff = 0; - metrics.broadEff = 0; - metrics.collisions = 0; - metrics.buckets = 0; - metrics.pairs = 0; - metrics.bodies = 0; - } - }; - - /** - * Description - * @method update - * @param {metrics} metrics - * @param {engine} engine - */ - Metrics.update = function(metrics, engine) { - if (metrics.extended) { - var world = engine.world, - bodies = Composite.allBodies(world); - - metrics.collisions = metrics.narrowDetections; - metrics.pairs = engine.pairs.list.length; - metrics.bodies = bodies.length; - metrics.midEff = (metrics.narrowDetections / (metrics.midphaseTests || 1)).toFixed(2); - metrics.narrowEff = (metrics.narrowDetections / (metrics.narrowphaseTests || 1)).toFixed(2); - metrics.broadEff = (1 - (metrics.broadphaseTests / (bodies.length || 1))).toFixed(2); - metrics.narrowReuse = (metrics.narrowReuseCount / (metrics.narrowphaseTests || 1)).toFixed(2); - //var broadphase = engine.broadphase[engine.broadphase.current]; - //if (broadphase.instance) - // metrics.buckets = Common.keys(broadphase.instance.buckets).length; - } - }; - -})(); - - ; // End src/core/Metrics.js @@ -5155,8 +5359,8 @@ var Sleeping = {}; continue; var collision = pair.collision, - bodyA = collision.bodyA, - bodyB = collision.bodyB; + bodyA = collision.bodyA.parent, + bodyB = collision.bodyB.parent; // don't wake if at least one body is static if ((bodyA.isSleeping && bodyB.isSleeping) || bodyA.isStatic || bodyB.isStatic) @@ -5302,7 +5506,7 @@ var Bodies = {}; * @param {number} y * @param {number} radius * @param {object} [options] - * @param {number} maxSides + * @param {number} [maxSides] * @return {body} A new circle body */ Bodies.circle = function(x, y, radius, options, maxSides) { @@ -5370,8 +5574,158 @@ var Bodies = {}; return Body.create(Common.extend({}, polygon, options)); }; -})(); + /** + * Creates a body using the supplied vertices (or an array containing multiple sets of vertices). + * If the vertices are convex, they will pass through as supplied. + * Otherwise if the vertices are concave, they will be decomposed if [poly-decomp.js](https://github.com/schteppe/poly-decomp.js) is available. + * Note that this process is not guaranteed to support complex sets of vertices (e.g. those with holes may fail). + * By default the decomposition will discard collinear edges (to improve performance). + * It can also optionally discard any parts that have an area less than `minimumArea`. + * If the vertices can not be decomposed, the result will fall back to using the convex hull. + * The options parameter is an object that specifies any `Matter.Body` properties you wish to override the defaults. + * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object. + * @method fromVertices + * @param {number} x + * @param {number} y + * @param [[vector]] vertexSets + * @param {object} [options] + * @param {bool} [flagInternal=false] + * @param {number} [removeCollinear=0.01] + * @param {number} [minimumArea=10] + * @return {body} + */ + Bodies.fromVertices = function(x, y, vertexSets, options, flagInternal, removeCollinear, minimumArea) { + var body, + parts, + isConvex, + vertices, + i, + j, + k, + v, + z; + options = options || {}; + parts = []; + + flagInternal = typeof flagInternal !== 'undefined' ? flagInternal : false; + removeCollinear = typeof removeCollinear !== 'undefined' ? removeCollinear : 0.01; + minimumArea = typeof minimumArea !== 'undefined' ? minimumArea : 10; + + if (!window.decomp) { + Common.log('Bodies.fromVertices: poly-decomp.js required. Could not decompose vertices. Fallback to convex hull.', 'warn'); + } + + // ensure vertexSets is an array of arrays + if (!Common.isArray(vertexSets[0])) { + vertexSets = [vertexSets]; + } + + for (v = 0; v < vertexSets.length; v += 1) { + vertices = vertexSets[v]; + isConvex = Vertices.isConvex(vertices); + + if (isConvex || !window.decomp) { + if (isConvex) { + vertices = Vertices.clockwiseSort(vertices); + } else { + // fallback to convex hull when decomposition is not possible + vertices = Vertices.hull(vertices); + } + + parts.push({ + position: { x: x, y: y }, + vertices: vertices + }); + } else { + // initialise a decomposition + var concave = new decomp.Polygon(); + for (i = 0; i < vertices.length; i++) { + concave.vertices.push([vertices[i].x, vertices[i].y]); + } + + // vertices are concave and simple, we can decompose into parts + concave.makeCCW(); + if (removeCollinear !== false) + concave.removeCollinearPoints(removeCollinear); + + // use the quick decomposition algorithm (Bayazit) + var decomposed = concave.quickDecomp(); + + // for each decomposed chunk + for (i = 0; i < decomposed.length; i++) { + var chunk = decomposed[i], + chunkVertices = []; + + // convert vertices into the correct structure + for (j = 0; j < chunk.vertices.length; j++) { + chunkVertices.push({ x: chunk.vertices[j][0], y: chunk.vertices[j][1] }); + } + + // skip small chunks + if (minimumArea > 0 && Vertices.area(chunkVertices) < minimumArea) + continue; + + // create a compound part + parts.push({ + position: Vertices.centre(chunkVertices), + vertices: chunkVertices + }); + } + } + } + + // create body parts + for (i = 0; i < parts.length; i++) { + parts[i] = Body.create(Common.extend(parts[i], options)); + } + + // flag internal edges (coincident part edges) + if (flagInternal) { + var coincident_max_dist = 5; + + for (i = 0; i < parts.length; i++) { + var partA = parts[i]; + + for (j = i + 1; j < parts.length; j++) { + var partB = parts[j]; + + if (Bounds.overlaps(partA.bounds, partB.bounds)) { + var pav = partA.vertices, + pbv = partB.vertices; + + // iterate vertices of both parts + for (k = 0; k < partA.vertices.length; k++) { + for (z = 0; z < partB.vertices.length; z++) { + // find distances between the vertices + var da = Vector.magnitudeSquared(Vector.sub(pav[(k + 1) % pav.length], pbv[z])), + db = Vector.magnitudeSquared(Vector.sub(pav[k], pbv[(z + 1) % pbv.length])); + + // if both vertices are very close, consider the edge concident (internal) + if (da < coincident_max_dist && db < coincident_max_dist) { + pav[k].isInternal = true; + pbv[z].isInternal = true; + } + } + } + + } + } + } + } + + if (parts.length > 1) { + // create the parent body to be returned, that contains generated compound parts + body = Body.create(Common.extend({ parts: parts.slice(0) }, options)); + Body.setPosition(body, { x: x, y: y }); + + return body; + } else { + return parts[0]; + } + }; + +})(); ; // End src/factory/Bodies.js @@ -5429,6 +5783,8 @@ var Composites = {}; lastBody = body; i += 1; + } else { + x += columnGap; } } @@ -5629,6 +5985,8 @@ var Composites = {}; }, restitution: 0.5, friction: 0.9, + frictionStatic: 10, + slop: 0.5, density: 0.01 }); @@ -5638,6 +5996,8 @@ var Composites = {}; }, restitution: 0.5, friction: 0.9, + frictionStatic: 10, + slop: 0.5, density: 0.01 }); @@ -5732,7 +6092,6 @@ var Axes = {}; // limit precision gradient = gradient.toFixed(3).toString(); - axes[gradient] = normal; } @@ -5890,6 +6249,224 @@ var Bounds = {}; ; // End src/geometry/Bounds.js +// Begin src/geometry/Svg.js + +/** +* The `Matter.Svg` module contains methods for converting SVG images into an array of vector points. +* +* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js) +* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples. +* +* @class Svg +*/ + +var Svg = {}; + +(function() { + + /** + * Converts an SVG path into an array of vector points. + * If the input path forms a concave shape, you must decompose the result into convex parts before use. + * See `Bodies.fromVertices` which provides support for this. + * Note that this function is not guaranteed to support complex paths (such as those with holes). + * @method pathToVertices + * @param {SVGPathElement} path + * @param {Number} [sampleLength=15] + * @return {Vector[]} points + */ + Svg.pathToVertices = function(path, sampleLength) { + // https://github.com/wout/svg.topoly.js/blob/master/svg.topoly.js + var i, il, total, point, segment, segments, + segmentsQueue, lastSegment, + lastPoint, segmentIndex, points = [], + length = 0, x = 0, y = 0; + + sampleLength = sampleLength || 15; + + var addPoint = function(px, py, pathSegType) { + // all odd-numbered path types are relative except PATHSEG_CLOSEPATH (1) + var isRelative = pathSegType % 2 === 1 && pathSegType > 1; + + // when the last point doesn't equal the current point add the current point + if (!lastPoint || px != lastPoint.x || py != lastPoint.y) { + if (lastPoint && isRelative) { + lx = lastPoint.x; + ly = lastPoint.y; + } else { + lx = 0; + ly = 0; + } + + var point = { + x: lx + px, + y: ly + py + }; + + // set last point + if (isRelative || !lastPoint) { + lastPoint = point; + } + + points.push(point); + + x = lx + px; + y = ly + py; + } + }; + + var addSegmentPoint = function(segment) { + var segType = segment.pathSegTypeAsLetter.toUpperCase(); + + // skip path ends + if (segType === 'Z') + return; + + // map segment to x and y + switch (segType) { + + case 'M': + case 'L': + case 'T': + case 'C': + case 'S': + case 'Q': + x = segment.x; + y = segment.y; + break; + case 'H': + x = segment.x; + break; + case 'V': + y = segment.y; + break; + } + + addPoint(x, y, segment.pathSegType); + }; + + // ensure path is absolute + _svgPathToAbsolute(path); + + // get total length + total = path.getTotalLength(); + + // queue segments + segments = []; + for (i = 0; i < path.pathSegList.numberOfItems; i += 1) + segments.push(path.pathSegList.getItem(i)); + + segmentsQueue = segments.concat(); + + // sample through path + while (length < total) { + // get segment at position + segmentIndex = path.getPathSegAtLength(length); + segment = segments[segmentIndex]; + + // new segment + if (segment != lastSegment) { + while (segmentsQueue.length && segmentsQueue[0] != segment) + addSegmentPoint(segmentsQueue.shift()); + + lastSegment = segment; + } + + // add points in between when curving + // TODO: adaptive sampling + switch (segment.pathSegTypeAsLetter.toUpperCase()) { + + case 'C': + case 'T': + case 'S': + case 'Q': + case 'A': + point = path.getPointAtLength(length); + addPoint(point.x, point.y, 0); + break; + + } + + // increment by sample value + length += sampleLength; + } + + // add remaining segments not passed by sampling + for (i = 0, il = segmentsQueue.length; i < il; ++i) + addSegmentPoint(segmentsQueue[i]); + + return points; + }; + + var _svgPathToAbsolute = function(path) { + // http://phrogz.net/convert-svg-path-to-all-absolute-commands + var x0, y0, x1, y1, x2, y2, segs = path.pathSegList, + x = 0, y = 0, len = segs.numberOfItems; + + for (var i = 0; i < len; ++i) { + var seg = segs.getItem(i), + segType = seg.pathSegTypeAsLetter; + + if (/[MLHVCSQTA]/.test(segType)) { + if ('x' in seg) x = seg.x; + if ('y' in seg) y = seg.y; + } else { + if ('x1' in seg) x1 = x + seg.x1; + if ('x2' in seg) x2 = x + seg.x2; + if ('y1' in seg) y1 = y + seg.y1; + if ('y2' in seg) y2 = y + seg.y2; + if ('x' in seg) x += seg.x; + if ('y' in seg) y += seg.y; + + switch (segType) { + + case 'm': + segs.replaceItem(path.createSVGPathSegMovetoAbs(x, y), i); + break; + case 'l': + segs.replaceItem(path.createSVGPathSegLinetoAbs(x, y), i); + break; + case 'h': + segs.replaceItem(path.createSVGPathSegLinetoHorizontalAbs(x), i); + break; + case 'v': + segs.replaceItem(path.createSVGPathSegLinetoVerticalAbs(y), i); + break; + case 'c': + segs.replaceItem(path.createSVGPathSegCurvetoCubicAbs(x, y, x1, y1, x2, y2), i); + break; + case 's': + segs.replaceItem(path.createSVGPathSegCurvetoCubicSmoothAbs(x, y, x2, y2), i); + break; + case 'q': + segs.replaceItem(path.createSVGPathSegCurvetoQuadraticAbs(x, y, x1, y1), i); + break; + case 't': + segs.replaceItem(path.createSVGPathSegCurvetoQuadraticSmoothAbs(x, y), i); + break; + case 'a': + segs.replaceItem(path.createSVGPathSegArcAbs(x, y, seg.r1, seg.r2, seg.angle, seg.largeArcFlag, seg.sweepFlag), i); + break; + case 'z': + case 'Z': + x = x0; + y = y0; + break; + + } + } + + if (segType == 'M' || segType == 'm') { + x0 = x; + y0 = y; + } + } + }; + +})(); + +; // End src/geometry/Svg.js + + // Begin src/geometry/Vector.js /** @@ -5909,6 +6486,17 @@ var Vector = {}; (function() { + /** + * Creates a new vector. + * @method create + * @param {number} x + * @param {number} y + * @return {vector} A new vector + */ + Vector.create = function(x, y) { + return { x: x || 0, y: y || 0 }; + }; + /** * Returns a new vector with `x` and `y` copied from the given `vector`. * @method clone @@ -5960,14 +6548,16 @@ var Vector = {}; * @param {vector} vector * @param {number} angle * @param {vector} point + * @param {vector} [output] * @return {vector} A new vector rotated about the point */ - Vector.rotateAbout = function(vector, angle, point) { + Vector.rotateAbout = function(vector, angle, point, output) { var cos = Math.cos(angle), sin = Math.sin(angle); - return { - x: point.x + ((vector.x - point.x) * cos - (vector.y - point.y) * sin), - y: point.y + ((vector.x - point.x) * sin + (vector.y - point.y) * cos) - }; + if (!output) output = {}; + var x = point.x + ((vector.x - point.x) * cos - (vector.y - point.y) * sin); + output.y = point.y + ((vector.x - point.x) * sin + (vector.y - point.y) * cos); + output.x = x; + return output; }; /** @@ -6005,15 +6595,31 @@ var Vector = {}; return (vectorA.x * vectorB.y) - (vectorA.y * vectorB.x); }; + /** + * Returns the cross-product of three vectors. + * @method cross3 + * @param {vector} vectorA + * @param {vector} vectorB + * @param {vector} vectorC + * @return {number} The cross product of the three vectors + */ + Vector.cross3 = function(vectorA, vectorB, vectorC) { + return (vectorB.x - vectorA.x) * (vectorC.y - vectorA.y) - (vectorB.y - vectorA.y) * (vectorC.x - vectorA.x); + }; + /** * Adds the two vectors. * @method add * @param {vector} vectorA * @param {vector} vectorB + * @param {vector} [output] * @return {vector} A new vector of vectorA and vectorB added */ - Vector.add = function(vectorA, vectorB) { - return { x: vectorA.x + vectorB.x, y: vectorA.y + vectorB.y }; + Vector.add = function(vectorA, vectorB, output) { + if (!output) output = {}; + output.x = vectorA.x + vectorB.x; + output.y = vectorA.y + vectorB.y; + return output; }; /** @@ -6021,10 +6627,14 @@ var Vector = {}; * @method sub * @param {vector} vectorA * @param {vector} vectorB + * @param {vector} [output] * @return {vector} A new vector of vectorA and vectorB subtracted */ - Vector.sub = function(vectorA, vectorB) { - return { x: vectorA.x - vectorB.x, y: vectorA.y - vectorB.y }; + Vector.sub = function(vectorA, vectorB, output) { + if (!output) output = {}; + output.x = vectorA.x - vectorB.x; + output.y = vectorA.y - vectorB.y; + return output; }; /** @@ -6082,6 +6692,16 @@ var Vector = {}; return Math.atan2(vectorB.y - vectorA.y, vectorB.x - vectorA.x); }; + /** + * Temporary vector pool (not thread-safe). + * @property _temp + * @type {vector[]} + * @private + */ + Vector._temp = [Vector.create(), Vector.create(), + Vector.create(), Vector.create(), + Vector.create(), Vector.create()]; + })(); ; // End src/geometry/Vector.js @@ -6100,8 +6720,6 @@ var Vector = {}; * @class Vertices */ -// TODO: convex decomposition - http://mnbayazit.com/406/bayazit - var Vertices = {}; (function() { @@ -6126,12 +6744,13 @@ var Vertices = {}; for (var i = 0; i < points.length; i++) { var point = points[i], - vertex = {}; - - vertex.x = point.x; - vertex.y = point.y; - vertex.index = i; - vertex.body = body; + vertex = { + x: point.x, + y: point.y, + index: i, + body: body, + isInternal: false + }; vertices.push(vertex); } @@ -6140,14 +6759,16 @@ var Vertices = {}; }; /** - * Parses a _simple_ SVG-style path into a `Matter.Vertices` object for the given `Matter.Body`. + * Parses a string containing ordered x y pairs separated by spaces (and optionally commas), + * into a `Matter.Vertices` object for the given `Matter.Body`. + * For parsing SVG paths, see `Svg.pathToVertices`. * @method fromPath * @param {string} path * @param {body} body * @return {vertices} vertices */ Vertices.fromPath = function(path, body) { - var pathPattern = /L\s*([\-\d\.]*)\s*([\-\d\.]*)/ig, + var pathPattern = /L?\s*([\-\d\.e]+)[\s,]*([\-\d\.e]+)*/ig, points = []; path.replace(pathPattern, function(match, x, y) { @@ -6180,6 +6801,23 @@ var Vertices = {}; return Vector.div(centre, 6 * area); }; + /** + * Returns the average (mean) of the set of vertices. + * @method mean + * @param {vertices} vertices + * @return {vector} The average point + */ + Vertices.mean = function(vertices) { + var average = { x: 0, y: 0 }; + + for (var i = 0; i < vertices.length; i++) { + average.x += vertices[i].x; + average.y += vertices[i].y; + } + + return Vector.div(average, vertices.length); + }; + /** * Returns the area of the set of vertices. * @method area @@ -6397,6 +7035,118 @@ var Vertices = {}; return newVertices; }; + /** + * Sorts the input vertices into clockwise order in place. + * @method clockwiseSort + * @param {vertices} vertices + * @return {vertices} vertices + */ + Vertices.clockwiseSort = function(vertices) { + var centre = Vertices.mean(vertices); + + vertices.sort(function(vertexA, vertexB) { + return Vector.angle(centre, vertexA) - Vector.angle(centre, vertexB); + }); + + return vertices; + }; + + /** + * Returns true if the vertices form a convex shape (vertices must be in clockwise order). + * @method isConvex + * @param {vertices} vertices + * @return {bool} `true` if the `vertices` are convex, `false` if not (or `null` if not computable). + */ + Vertices.isConvex = function(vertices) { + // http://paulbourke.net/geometry/polygonmesh/ + + var flag = 0, + n = vertices.length, + i, + j, + k, + z; + + if (n < 3) + return null; + + for (i = 0; i < n; i++) { + j = (i + 1) % n; + k = (i + 2) % n; + z = (vertices[j].x - vertices[i].x) * (vertices[k].y - vertices[j].y); + z -= (vertices[j].y - vertices[i].y) * (vertices[k].x - vertices[j].x); + + if (z < 0) { + flag |= 1; + } else if (z > 0) { + flag |= 2; + } + + if (flag === 3) { + return false; + } + } + + if (flag !== 0){ + return true; + } else { + return null; + } + }; + + /** + * Returns the convex hull of the input vertices as a new array of points. + * @method hull + * @param {vertices} vertices + * @return [vertex] vertices + */ + Vertices.hull = function(vertices) { + // http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain + + var upper = [], + lower = [], + vertex, + i; + + // sort vertices on x-axis (y-axis for ties) + vertices = vertices.slice(0); + vertices.sort(function(vertexA, vertexB) { + var dx = vertexA.x - vertexB.x; + return dx !== 0 ? dx : vertexA.y - vertexB.y; + }); + + // build lower hull + for (i = 0; i < vertices.length; i++) { + vertex = vertices[i]; + + while (lower.length >= 2 + && Vector.cross3(lower[lower.length - 2], lower[lower.length - 1], vertex) <= 0) { + lower.pop(); + } + + lower.push(vertex); + } + + // build upper hull + for (i = vertices.length - 1; i >= 0; i--) { + vertex = vertices[i]; + + while (upper.length >= 2 + && Vector.cross3(upper[upper.length - 2], upper[upper.length - 1], vertex) <= 0) { + upper.pop(); + } + + upper.push(vertex); + } + + // concatenation of the lower and upper hulls gives the convex hull + // omit last points because they are repeated at the beginning of the other list + upper.pop(); + lower.pop(); + + return upper.concat(lower); + }; + })(); @@ -6449,11 +7199,15 @@ var Render = {}; showBounds: false, showVelocity: false, showCollisions: false, + showSeparations: false, showAxes: false, showPositions: false, showAngleIndicator: false, showIds: false, - showShadows: false + showShadows: false, + showVertexNumbers: false, + showConvexHulls: false, + showInternalEdges: false } }; @@ -6584,6 +7338,9 @@ var Render = {}; // fully featured rendering of bodies Render.bodies(engine, bodies, context); } else { + if (options.showConvexHulls) + Render.bodyConvexHulls(engine, bodies, context); + // optimised method for wireframes only Render.bodyWireframes(engine, bodies, context); } @@ -6603,9 +7360,15 @@ var Render = {}; if (options.showIds) Render.bodyIds(engine, bodies, context); + if (options.showSeparations) + Render.separations(engine, engine.pairs.list, context); + if (options.showCollisions) Render.collisions(engine, engine.pairs.list, context); + if (options.showVertexNumbers) + Render.vertexNumbers(engine, bodies, context); + Render.constraints(constraints, context); if (options.showBroadphase && engine.broadphase.controller === Grid) @@ -6639,23 +7402,6 @@ var Render = {}; var text = ""; text += "fps: " + Math.round(engine.timing.fps) + space; - if (engine.metrics.extended) { - text += "delta: " + engine.timing.delta.toFixed(3) + space; - text += "correction: " + engine.timing.correction.toFixed(3) + space; - text += "bodies: " + bodies.length + space; - - if (engine.broadphase.controller === Grid) - text += "buckets: " + engine.metrics.buckets + space; - - text += "\n"; - - text += "collisions: " + engine.metrics.collisions + space; - text += "pairs: " + engine.pairs.list.length + space; - text += "broad: " + engine.metrics.broadEff + space; - text += "mid: " + engine.metrics.midEff + space; - text += "narrow: " + engine.metrics.narrowEff + space; - } - render.debugString = text; render.debugTimestamp = engine.timing.timestamp; } @@ -6777,69 +7523,75 @@ var Render = {}; var c = context, render = engine.render, options = render.options, + body, + part, i; for (i = 0; i < bodies.length; i++) { - var body = bodies[i]; + body = bodies[i]; if (!body.render.visible) continue; - if (body.render.sprite && body.render.sprite.texture && !options.wireframes) { - // body sprite - var sprite = body.render.sprite, - texture = _getTexture(render, sprite.texture); + // handle compound parts + for (k = body.parts.length > 1 ? 1 : 0; k < body.parts.length; k++) { + part = body.parts[k]; - if (options.showSleeping && body.isSleeping) - c.globalAlpha = 0.5; + if (part.render.sprite && part.render.sprite.texture && !options.wireframes) { + // part sprite + var sprite = part.render.sprite, + texture = _getTexture(render, sprite.texture); - c.translate(body.position.x, body.position.y); - c.rotate(body.angle); + if (options.showSleeping && body.isSleeping) + c.globalAlpha = 0.5; - c.drawImage(texture, texture.width * -0.5 * sprite.xScale, texture.height * -0.5 * sprite.yScale, - texture.width * sprite.xScale, texture.height * sprite.yScale); + c.translate(part.position.x, part.position.y); + c.rotate(part.angle); - // revert translation, hopefully faster than save / restore - c.rotate(-body.angle); - c.translate(-body.position.x, -body.position.y); + c.drawImage(texture, texture.width * -0.5 * sprite.xScale, texture.height * -0.5 * sprite.yScale, + texture.width * sprite.xScale, texture.height * sprite.yScale); - if (options.showSleeping && body.isSleeping) - c.globalAlpha = 1; - } else { - // body polygon - if (body.circleRadius) { - c.beginPath(); - c.arc(body.position.x, body.position.y, body.circleRadius, 0, 2 * Math.PI); + // revert translation, hopefully faster than save / restore + c.rotate(-part.angle); + c.translate(-part.position.x, -part.position.y); + + if (options.showSleeping && body.isSleeping) + c.globalAlpha = 1; } else { - c.beginPath(); - c.moveTo(body.vertices[0].x, body.vertices[0].y); - for (var j = 1; j < body.vertices.length; j++) { - c.lineTo(body.vertices[j].x, body.vertices[j].y); - } - c.closePath(); - } - - if (!options.wireframes) { - if (options.showSleeping && body.isSleeping) { - c.fillStyle = Common.shadeColor(body.render.fillStyle, 50); + // part polygon + if (part.circleRadius) { + c.beginPath(); + c.arc(part.position.x, part.position.y, part.circleRadius, 0, 2 * Math.PI); } else { - c.fillStyle = body.render.fillStyle; + c.beginPath(); + c.moveTo(part.vertices[0].x, part.vertices[0].y); + for (var j = 1; j < part.vertices.length; j++) { + c.lineTo(part.vertices[j].x, part.vertices[j].y); + } + c.closePath(); } - c.lineWidth = body.render.lineWidth; - c.strokeStyle = body.render.strokeStyle; - c.fill(); - c.stroke(); - } else { - c.lineWidth = 1; - c.strokeStyle = '#bbb'; - if (options.showSleeping && body.isSleeping) - c.strokeStyle = 'rgba(255,255,255,0.2)'; - c.stroke(); + if (!options.wireframes) { + if (options.showSleeping && body.isSleeping) { + c.fillStyle = Common.shadeColor(part.render.fillStyle, 50); + } else { + c.fillStyle = part.render.fillStyle; + } + + c.lineWidth = part.render.lineWidth; + c.strokeStyle = part.render.strokeStyle; + c.fill(); + c.stroke(); + } else { + c.lineWidth = 1; + c.strokeStyle = '#bbb'; + if (options.showSleeping && body.isSleeping) + c.strokeStyle = 'rgba(255,255,255,0.2)'; + c.stroke(); + } } } } - }; /** @@ -6852,17 +7604,74 @@ var Render = {}; */ Render.bodyWireframes = function(engine, bodies, context) { var c = context, + showInternalEdges = engine.render.options.showInternalEdges, + body, + part, i, - j; + j, + k; c.beginPath(); + // render all bodies for (i = 0; i < bodies.length; i++) { - var body = bodies[i]; + body = bodies[i]; if (!body.render.visible) continue; + // handle compound parts + for (k = body.parts.length > 1 ? 1 : 0; k < body.parts.length; k++) { + part = body.parts[k]; + + c.moveTo(part.vertices[0].x, part.vertices[0].y); + + for (j = 1; j < part.vertices.length; j++) { + if (!part.vertices[j - 1].isInternal || showInternalEdges) { + c.lineTo(part.vertices[j].x, part.vertices[j].y); + } else { + c.moveTo(part.vertices[j].x, part.vertices[j].y); + } + + if (part.vertices[j].isInternal && !showInternalEdges) { + c.moveTo(part.vertices[(j + 1) % part.vertices.length].x, part.vertices[(j + 1) % part.vertices.length].y); + } + } + + c.lineTo(part.vertices[0].x, part.vertices[0].y); + } + } + + c.lineWidth = 1; + c.strokeStyle = '#bbb'; + c.stroke(); + }; + + /** + * Optimised method for drawing body convex hull wireframes in one pass + * @private + * @method bodyConvexHulls + * @param {engine} engine + * @param {body[]} bodies + * @param {RenderingContext} context + */ + Render.bodyConvexHulls = function(engine, bodies, context) { + var c = context, + body, + part, + i, + j, + k; + + c.beginPath(); + + // render convex hulls + for (i = 0; i < bodies.length; i++) { + body = bodies[i]; + + if (!body.render.visible || body.parts.length === 1) + continue; + c.moveTo(body.vertices[0].x, body.vertices[0].y); for (j = 1; j < body.vertices.length; j++) { @@ -6873,10 +7682,36 @@ var Render = {}; } c.lineWidth = 1; - c.strokeStyle = '#bbb'; + c.strokeStyle = 'rgba(255,255,255,0.2)'; c.stroke(); }; + /** + * Renders body vertex numbers. + * @private + * @method vertexNumbers + * @param {engine} engine + * @param {body[]} bodies + * @param {RenderingContext} context + */ + Render.vertexNumbers = function(engine, bodies, context) { + var c = context, + i, + j, + k; + + for (i = 0; i < bodies.length; i++) { + var parts = bodies[i].parts; + for (k = parts.length > 1 ? 1 : 0; k < parts.length; k++) { + var part = parts[k]; + for (j = 0; j < part.vertices.length; j++) { + c.fillStyle = 'rgba(255,255,255,0.2)'; + c.fillText(i + '_' + j, part.position.x + (part.vertices[j].x - part.position.x) * 0.8, part.position.y + (part.vertices[j].y - part.position.y) * 0.8); + } + } + } + }; + /** * Draws body bounds * @private @@ -6895,8 +7730,13 @@ var Render = {}; for (var i = 0; i < bodies.length; i++) { var body = bodies[i]; - if (body.render.visible) - c.rect(body.bounds.min.x, body.bounds.min.y, body.bounds.max.x - body.bounds.min.x, body.bounds.max.y - body.bounds.min.y); + if (body.render.visible) { + var parts = bodies[i].parts; + for (var j = parts.length > 1 ? 1 : 0; j < parts.length; j++) { + var part = parts[j]; + c.rect(part.bounds.min.x, part.bounds.min.y, part.bounds.max.x - part.bounds.min.x, part.bounds.max.y - part.bounds.min.y); + } + } } if (options.wireframes) { @@ -6921,29 +7761,40 @@ var Render = {}; var c = context, render = engine.render, options = render.options, + part, i, - j; + j, + k; c.beginPath(); for (i = 0; i < bodies.length; i++) { - var body = bodies[i]; + var body = bodies[i], + parts = body.parts; if (!body.render.visible) continue; if (options.showAxes) { // render all axes - for (j = 0; j < body.axes.length; j++) { - var axis = body.axes[j]; - c.moveTo(body.position.x, body.position.y); - c.lineTo(body.position.x + axis.x * 20, body.position.y + axis.y * 20); + for (j = parts.length > 1 ? 1 : 0; j < parts.length; j++) { + part = parts[j]; + for (k = 0; k < part.axes.length; k++) { + var axis = part.axes[k]; + c.moveTo(part.position.x, part.position.y); + c.lineTo(part.position.x + axis.x * 20, part.position.y + axis.y * 20); + } } } else { - // render a single axis indicator - c.moveTo(body.position.x, body.position.y); - c.lineTo((body.vertices[0].x + body.vertices[body.vertices.length-1].x) / 2, - (body.vertices[0].y + body.vertices[body.vertices.length-1].y) / 2); + for (j = parts.length > 1 ? 1 : 0; j < parts.length; j++) { + part = parts[j]; + for (k = 0; k < part.axes.length; k++) { + // render a single axis indicator + c.moveTo(part.position.x, part.position.y); + c.lineTo((part.vertices[0].x + part.vertices[part.vertices.length-1].x) / 2, + (part.vertices[0].y + part.vertices[part.vertices.length-1].y) / 2); + } + } } } @@ -6970,6 +7821,7 @@ var Render = {}; render = engine.render, options = render.options, body, + part, i; c.beginPath(); @@ -6977,8 +7829,14 @@ var Render = {}; // render current positions for (i = 0; i < bodies.length; i++) { body = bodies[i]; - if (body.render.visible) { - c.arc(body.position.x, body.position.y, 3, 0, 2 * Math.PI, false); + + if (!body.render.visible) + continue; + + // handle compound parts + for (k = 0; k < body.parts.length; k++) { + part = body.parts[k]; + c.arc(part.position.x, part.position.y, 3, 0, 2 * Math.PI, false); c.closePath(); } } @@ -7042,17 +7900,21 @@ var Render = {}; * @param {RenderingContext} context */ Render.bodyIds = function(engine, bodies, context) { - var c = context; + var c = context, + i, + j; - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - if (!body.render.visible) + for (i = 0; i < bodies.length; i++) { + if (!bodies[i].render.visible) continue; - c.font = "12px Arial"; - c.fillStyle = 'rgba(255,255,255,0.5)'; - c.fillText(body.id, body.position.x + 10, body.position.y - 10); + var parts = bodies[i].parts; + for (j = parts.length > 1 ? 1 : 0; j < parts.length; j++) { + var part = parts[j]; + c.font = "12px Arial"; + c.fillStyle = 'rgba(255,255,255,0.5)'; + c.fillText(part.id, part.position.x + 10, part.position.y - 10); + } } }; @@ -7069,6 +7931,9 @@ var Render = {}; options = engine.render.options, pair, collision, + corrected, + bodyA, + bodyB, i, j; @@ -7077,6 +7942,10 @@ var Render = {}; // render collision positions for (i = 0; i < pairs.length; i++) { pair = pairs[i]; + + if (!pair.isActive) + continue; + collision = pair.collision; for (j = 0; j < pair.activeContacts.length; j++) { var contact = pair.activeContacts[j], @@ -7097,6 +7966,10 @@ var Render = {}; // render collision normals for (i = 0; i < pairs.length; i++) { pair = pairs[i]; + + if (!pair.isActive) + continue; + collision = pair.collision; if (pair.activeContacts.length > 0) { @@ -7108,7 +7981,12 @@ var Render = {}; normalPosY = (pair.activeContacts[0].vertex.y + pair.activeContacts[1].vertex.y) / 2; } - c.moveTo(normalPosX - collision.normal.x * 8, normalPosY - collision.normal.y * 8); + if (collision.bodyB === collision.supports[0].body || collision.bodyA.isStatic === true) { + c.moveTo(normalPosX - collision.normal.x * 8, normalPosY - collision.normal.y * 8); + } else { + c.moveTo(normalPosX + collision.normal.x * 8, normalPosY + collision.normal.y * 8); + } + c.lineTo(normalPosX, normalPosY); } } @@ -7123,6 +8001,63 @@ var Render = {}; c.stroke(); }; + /** + * Description + * @private + * @method separations + * @param {engine} engine + * @param {pair[]} pairs + * @param {RenderingContext} context + */ + Render.separations = function(engine, pairs, context) { + var c = context, + options = engine.render.options, + pair, + collision, + corrected, + bodyA, + bodyB, + i, + j; + + c.beginPath(); + + // render separations + for (i = 0; i < pairs.length; i++) { + pair = pairs[i]; + + if (!pair.isActive) + continue; + + collision = pair.collision; + bodyA = collision.bodyA; + bodyB = collision.bodyB; + + var k = 1; + + if (!bodyB.isStatic && !bodyA.isStatic) k = 0.5; + if (bodyB.isStatic) k = 0; + + c.moveTo(bodyB.position.x, bodyB.position.y); + c.lineTo(bodyB.position.x - collision.penetration.x * k, bodyB.position.y - collision.penetration.y * k); + + k = 1; + + if (!bodyB.isStatic && !bodyA.isStatic) k = 0.5; + if (bodyA.isStatic) k = 0; + + c.moveTo(bodyA.position.x, bodyA.position.y); + c.lineTo(bodyA.position.x + collision.penetration.x * k, bodyA.position.y + collision.penetration.y * k); + } + + if (options.wireframes) { + c.strokeStyle = 'rgba(255,165,0,0.5)'; + } else { + c.strokeStyle = 'orange'; + } + c.stroke(); + }; + /** * Description * @private @@ -7782,43 +8717,54 @@ var RenderPixi = {}; var _createBodyPrimitive = function(render, body) { var bodyRender = body.render, options = render.options, - primitive = new PIXI.Graphics(); + primitive = new PIXI.Graphics(), + fillStyle = Common.colorToNumber(bodyRender.fillStyle), + strokeStyle = Common.colorToNumber(bodyRender.strokeStyle), + strokeStyleIndicator = Common.colorToNumber(bodyRender.strokeStyle), + strokeStyleWireframe = Common.colorToNumber('#bbb'), + strokeStyleWireframeIndicator = Common.colorToNumber('#CD5C5C'), + part; primitive.clear(); - if (!options.wireframes) { - primitive.beginFill(Common.colorToNumber(bodyRender.fillStyle), 1); - primitive.lineStyle(body.render.lineWidth, Common.colorToNumber(bodyRender.strokeStyle), 1); - } else { - primitive.beginFill(0, 0); - primitive.lineStyle(1, Common.colorToNumber('#bbb'), 1); - } + // handle compound parts + for (var k = body.parts.length > 1 ? 1 : 0; k < body.parts.length; k++) { + part = body.parts[k]; - primitive.moveTo(body.vertices[0].x - body.position.x, body.vertices[0].y - body.position.y); - - for (var j = 1; j < body.vertices.length; j++) { - primitive.lineTo(body.vertices[j].x - body.position.x, body.vertices[j].y - body.position.y); - } - - primitive.lineTo(body.vertices[0].x - body.position.x, body.vertices[0].y - body.position.y); - - primitive.endFill(); - - // angle indicator - if (options.showAngleIndicator || options.showAxes) { - primitive.beginFill(0, 0); - - if (options.wireframes) { - primitive.lineStyle(1, Common.colorToNumber('#CD5C5C'), 1); + if (!options.wireframes) { + primitive.beginFill(fillStyle, 1); + primitive.lineStyle(bodyRender.lineWidth, strokeStyle, 1); } else { - primitive.lineStyle(1, Common.colorToNumber(body.render.strokeStyle)); + primitive.beginFill(0, 0); + primitive.lineStyle(1, strokeStyleWireframe, 1); } - primitive.moveTo(0, 0); - primitive.lineTo(((body.vertices[0].x + body.vertices[body.vertices.length-1].x) / 2) - body.position.x, - ((body.vertices[0].y + body.vertices[body.vertices.length-1].y) / 2) - body.position.y); + primitive.moveTo(part.vertices[0].x - body.position.x, part.vertices[0].y - body.position.y); + + for (var j = 1; j < part.vertices.length; j++) { + primitive.lineTo(part.vertices[j].x - body.position.x, part.vertices[j].y - body.position.y); + } + + primitive.lineTo(part.vertices[0].x - body.position.x, part.vertices[0].y - body.position.y); primitive.endFill(); + + // angle indicator + if (options.showAngleIndicator || options.showAxes) { + primitive.beginFill(0, 0); + + if (options.wireframes) { + primitive.lineStyle(1, strokeStyleWireframeIndicator, 1); + } else { + primitive.lineStyle(1, strokeStyleIndicator); + } + + primitive.moveTo(part.position.x - body.position.x, part.position.y - body.position.y); + primitive.lineTo(((part.vertices[0].x + part.vertices[part.vertices.length-1].x) / 2 - body.position.x), + ((part.vertices[0].y + part.vertices[part.vertices.length-1].y) / 2 - body.position.y)); + + primitive.endFill(); + } } return primitive; @@ -7874,7 +8820,6 @@ Matter.Constraint = Constraint; Matter.MouseConstraint = MouseConstraint; Matter.Common = Common; Matter.Engine = Engine; -Matter.Metrics = Metrics; Matter.Mouse = Mouse; Matter.Sleeping = Sleeping; Matter.Bodies = Bodies; @@ -7888,6 +8833,7 @@ Matter.RenderPixi = RenderPixi; Matter.Events = Events; Matter.Query = Query; Matter.Runner = Runner; +Matter.Svg = Svg; // CommonJS module if (typeof exports !== 'undefined') { diff --git a/build/matter.min.js b/build/matter.min.js index d123df7..f2273de 100644 --- a/build/matter.min.js +++ b/build/matter.min.js @@ -1,8 +1,9 @@ /** -* matter.min.js 0.8.0-edge 2015-01-21 +* matter.min.js edge-master 2015-05-20 * http://brm.io/matter-js/ * License: MIT */ -!function(){var a={},b={};!function(){b._inertiaScale=4;var a=1,c=-1,d=1;b.create=function(a){var b={id:o.nextId(),type:"body",label:"Body",angle:0,vertices:A.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},speed:0,angularSpeed:0,velocity:{x:0,y:0},angularVelocity:0,isStatic:!1,isSleeping:!1,motion:0,sleepThreshold:60,density:.001,restitution:0,friction:.1,frictionAir:.01,collisionFilter:{category:1,mask:4294967295,group:0},slop:.05,timeScale:1,render:{visible:!0,sprite:{xScale:1,yScale:1},lineWidth:1.5}},c=o.extend(b,a);return e(c,a),c},b.nextGroup=function(b){return b?c--:a++},b.nextCategory=function(){return d<<=1};var e=function(a,c){b.set(a,{bounds:a.bounds||y.create(a.vertices),positionPrev:a.positionPrev||z.clone(a.position),anglePrev:a.anglePrev||a.angle,vertices:a.vertices,isStatic:a.isStatic,isSleeping:a.isSleeping}),A.rotate(a.vertices,a.angle,a.position),x.rotate(a.axes,a.angle),y.update(a.bounds,a.vertices,a.velocity),b.set(a,{axes:c.axes||a.axes,area:c.area||a.area,mass:c.mass||a.mass,inertia:c.inertia||a.inertia});var d=a.isStatic?"#eeeeee":o.choose(["#556270","#4ECDC4","#C7F464","#FF6B6B","#C44D58"]),e=o.shadeColor(d,-20);a.render.fillStyle=a.render.fillStyle||d,a.render.strokeStyle=a.render.strokeStyle||e};b.set=function(a,c,d){var e;"string"==typeof c&&(e=c,c={},c[e]=d);for(e in c)if(d=c[e],c.hasOwnProperty(e))switch(e){case"isStatic":b.setStatic(a,d);break;case"isSleeping":u.set(a,d);break;case"mass":b.setMass(a,d);break;case"density":b.setDensity(a,d);break;case"inertia":b.setInertia(a,d);break;case"vertices":b.setVertices(a,d);break;case"position":b.setPosition(a,d);break;case"angle":b.setAngle(a,d);break;case"velocity":b.setVelocity(a,d);break;case"angularVelocity":b.setAngularVelocity(a,d);break;default:a[e]=d}},b.setStatic=function(a,b){a.isStatic=b,b&&(a.restitution=0,a.friction=1,a.mass=a.inertia=a.density=1/0,a.inverseMass=a.inverseInertia=0,a.positionPrev.x=a.position.x,a.positionPrev.y=a.position.y,a.anglePrev=a.angle,a.angularVelocity=0,a.speed=0,a.angularSpeed=0,a.motion=0)},b.setMass=function(a,b){a.mass=b,a.inverseMass=1/a.mass,a.density=a.mass/a.area},b.setDensity=function(a,c){b.setMass(a,c*a.area),a.density=c},b.setInertia=function(a,b){a.inertia=b,a.inverseInertia=1/a.inertia},b.setVertices=function(a,c){a.vertices=c[0].body===a?c:A.create(c,a),a.axes=x.fromVertices(a.vertices),a.area=A.area(a.vertices),b.setMass(a,a.density*a.area);var d=A.centre(a.vertices);A.translate(a.vertices,d,-1),b.setInertia(a,b._inertiaScale*A.inertia(a.vertices,a.mass)),A.translate(a.vertices,a.position),y.update(a.bounds,a.vertices,a.velocity)},b.setPosition=function(a,b){var c=z.sub(b,a.position);a.position.x=b.x,a.position.y=b.y,a.positionPrev.x+=c.x,a.positionPrev.y+=c.y,A.translate(a.vertices,c),y.update(a.bounds,a.vertices,a.velocity)},b.setAngle=function(a,b){var c=b-a.angle;a.angle=b,a.anglePrev+=c,A.rotate(a.vertices,c,a.position),x.rotate(a.axes,c),y.update(a.bounds,a.vertices,a.velocity)},b.setVelocity=function(a,b){a.positionPrev.x=a.position.x-b.x,a.positionPrev.y=a.position.y-b.y,a.velocity.x=b.x,a.velocity.y=b.y,a.speed=z.magnitude(a.velocity)},b.setAngularVelocity=function(a,b){a.anglePrev=a.angle-b,a.angularVelocity=b,a.angularSpeed=Math.abs(a.angularVelocity)},b.translate=function(a,c){b.setPosition(a,z.add(a.position,c))},b.rotate=function(a,c){b.setAngle(a,a.angle+c)},b.scale=function(a,c,d,e){A.scale(a.vertices,c,d,e),a.axes=x.fromVertices(a.vertices),a.area=A.area(a.vertices),b.setMass(a,a.density*a.area),A.translate(a.vertices,{x:-a.position.x,y:-a.position.y}),b.setInertia(a,A.inertia(a.vertices,a.mass)),A.translate(a.vertices,{x:a.position.x,y:a.position.y}),y.update(a.bounds,a.vertices,a.velocity)},b.update=function(a,b,c,d){var e=Math.pow(b*c*a.timeScale,2),f=1-a.frictionAir*c*a.timeScale,g=a.position.x-a.positionPrev.x,h=a.position.y-a.positionPrev.y;a.velocity.x=g*f*d+a.force.x/a.mass*e,a.velocity.y=h*f*d+a.force.y/a.mass*e,a.positionPrev.x=a.position.x,a.positionPrev.y=a.position.y,a.position.x+=a.velocity.x,a.position.y+=a.velocity.y,a.angularVelocity=(a.angle-a.anglePrev)*f*d+a.torque/a.inertia*e,a.anglePrev=a.angle,a.angle+=a.angularVelocity,a.speed=z.magnitude(a.velocity),a.angularSpeed=Math.abs(a.angularVelocity),A.translate(a.vertices,a.velocity),0!==a.angularVelocity&&(A.rotate(a.vertices,a.angularVelocity,a.position),x.rotate(a.axes,a.angularVelocity)),y.update(a.bounds,a.vertices,a.velocity)},b.applyForce=function(a,b,c){a.force.x+=c.x,a.force.y+=c.y;var d={x:b.x-a.position.x,y:b.y-a.position.y};a.torque+=(d.x*c.y-d.y*c.x)*a.inverseInertia}}();var c={};!function(){c.create=function(a){return o.extend({id:o.nextId(),type:"composite",parent:null,isModified:!1,bodies:[],constraints:[],composites:[],label:"Composite"},a)},c.setModified=function(a,b,d,e){if(a.isModified=b,d&&a.parent&&c.setModified(a.parent,b,d,e),e)for(var f=0;f0:0!==(a.mask&b.category)&&0!==(b.mask&a.category)}}();var g={};!function(){g.create=function(a){var b={controller:g,detector:f.collisions,buckets:{},pairs:{},pairsList:[],bucketWidth:48,bucketHeight:48};return o.extend(b,a)},g.update=function(c,f,g,h){var l,m,n,o,p,q=g.world,r=c.buckets,s=g.metrics,t=!1;for(s.broadphaseTests=0,l=0;lq.bounds.width||u.bounds.max.y<0||u.bounds.min.y>q.bounds.height)){var v=b(c,u);if(!u.region||v.id!==u.region.id||h){s.broadphaseTests+=1,(!u.region||h)&&(u.region=v);var w=a(v,u.region);for(m=w.startCol;m<=w.endCol;m++)for(n=w.startRow;n<=w.endRow;n++){p=d(m,n),o=r[p];var x=m>=v.startCol&&m<=v.endCol&&n>=v.startRow&&n<=v.endRow,y=m>=u.region.startCol&&m<=u.region.endCol&&n>=u.region.startRow&&n<=u.region.endRow;!x&&y&&y&&o&&j(c,o,u),(u.region===v||x&&!y||h)&&(o||(o=e(r,p)),i(c,o,u))}u.region=v,t=!0}}}t&&(c.pairsList=k(c))},g.clear=function(a){a.buckets={},a.pairs={},a.pairsList=[]};var a=function(a,b){var d=Math.min(a.startCol,b.startCol),e=Math.max(a.endCol,b.endCol),f=Math.min(a.startRow,b.startRow),g=Math.max(a.endRow,b.endRow);return c(d,e,f,g)},b=function(a,b){var d=b.bounds,e=Math.floor(d.min.x/a.bucketWidth),f=Math.floor(d.max.x/a.bucketWidth),g=Math.floor(d.min.y/a.bucketHeight),h=Math.floor(d.max.y/a.bucketHeight);return c(e,f,g,h)},c=function(a,b,c,d){return{id:a+","+b+","+c+","+d,startCol:a,endCol:b,startRow:c,endRow:d}},d=function(a,b){return a+","+b},e=function(a,b){var c=a[b]=[];return c},i=function(a,b,c){for(var d=0;d0?d.push(c):delete a.pairs[b[e]];return d}}();var h={};!function(){h.create=function(a,b){var c=a.bodyA,d=a.bodyB,e={id:h.id(c,d),bodyA:c,bodyB:d,contacts:{},activeContacts:[],separation:0,isActive:!0,timeCreated:b,timeUpdated:b,inverseMass:c.inverseMass+d.inverseMass,friction:Math.min(c.friction,d.friction),restitution:Math.max(c.restitution,d.restitution),slop:Math.max(c.slop,d.slop)};return h.update(e,a,b),e},h.update=function(a,b,c){var d=a.contacts,f=b.supports,g=a.activeContacts;if(a.collision=b,a.inverseMass=b.bodyA.inverseMass+b.bodyB.inverseMass,a.friction=Math.min(b.bodyA.friction,b.bodyB.friction),a.restitution=Math.max(b.bodyA.restitution,b.bodyB.restitution),a.slop=Math.max(b.bodyA.slop,b.bodyB.slop),g.length=0,b.collided){for(var i=0;ia&&j.push(g);for(g=0;gD*g.friction*e&&(E=D*g.friction*e*B);var F=z.cross(s,k),G=z.cross(t,k),H=n/(g.inverseMass+i.inverseInertia*F*F+j.inverseInertia*G*G);if(C*=H,E*=H,0>x&&x*x>a*e)q.normalImpulse=0,q.tangentImpulse=0;else{var I=q.normalImpulse;q.normalImpulse=Math.min(q.normalImpulse+C,0),C=q.normalImpulse-I;var J=q.tangentImpulse;q.tangentImpulse=o.clamp(q.tangentImpulse+E,-A,A),E=q.tangentImpulse-J}d.x=k.x*C+l.x*E,d.y=k.y*C+l.y*E,i.isStatic||i.isSleeping||(i.positionPrev.x+=d.x*i.inverseMass,i.positionPrev.y+=d.y*i.inverseMass,i.anglePrev+=z.cross(s,d)*i.inverseInertia),j.isStatic||j.isSleeping||(j.positionPrev.x-=d.x*j.inverseMass,j.positionPrev.y-=d.y*j.inverseMass,j.anglePrev-=z.cross(t,d)*j.inverseInertia)}}}}}();var l={};!function(){l.collides=function(b,d,e){var f,g,h,i,j=e,k=!1;if(j){var l=b.speed*b.speed+b.angularSpeed*b.angularSpeed+d.speed*d.speed+d.angularSpeed*d.angularSpeed;k=j&&j.collided&&.2>l,i=j}else i={collided:!1,bodyA:b,bodyB:d};if(j&&k){var m=[j.bodyA.axes[j.axisNumber]];if(h=a(j.bodyA.vertices,j.bodyB.vertices,m),i.reused=!0,h.overlap<=0)return i.collided=!1,i}else{if(f=a(b.vertices,d.vertices,b.axes),f.overlap<=0)return i.collided=!1,i;if(g=a(d.vertices,b.vertices,d.axes),g.overlap<=0)return i.collided=!1,i;f.overlap0&&(i.normal=z.neg(i.normal)),i.tangent=z.perp(i.normal),i.penetration={x:i.normal.x*i.depth,y:i.normal.y*i.depth};var n=c(b,d,i.normal),o=i.supports||[];if(o.length=0,A.contains(b.vertices,n[0])&&o.push(n[0]),A.contains(b.vertices,n[1])&&o.push(n[1]),o.length<2){var p=c(d,b,z.neg(i.normal));A.contains(d.vertices,p[0])&&o.push(p[0]),o.length<2&&A.contains(d.vertices,p[1])&&o.push(p[1])}return o.length<2&&(o=[n[0]]),i.supports=o,i.supportCorrected=z.sub(o[0],i.penetration),i};var a=function(a,c,d){for(var e,f,g={},h={},i={overlap:Number.MAX_VALUE},j=0;j=e)return i.overlap=e,i;ee?e=g:d>g&&(d=g)}a.min=d,a.max=e},c=function(a,b,c){for(var d,e,f=Number.MAX_VALUE,g={x:0,y:0},h=b.vertices,i=a.position,j=h[0],k=h[1],l=0;ld&&(f=d,j=e);var m=j.index-1>=0?j.index-1:h.length-1;e=h[m],g.x=e.x-i.x,g.y=e.y-i.y,f=-z.dot(c,g),k=e;var n=(j.index+1)%h.length;return e=h[n],g.x=e.x-i.x,g.y=e.y-i.y,d=-z.dot(c,g),f>d&&(k=e),[j,k]}}();var m={};!function(){var a=1e-6,b=.001;m.create=function(b){var c=b;c.bodyA&&!c.pointA&&(c.pointA={x:0,y:0}),c.bodyB&&!c.pointB&&(c.pointB={x:0,y:0});var d=c.bodyA?z.add(c.bodyA.position,c.pointA):c.pointA,e=c.bodyB?z.add(c.bodyB.position,c.pointB):c.pointB,f=z.magnitude(z.sub(d,e));c.length=c.length||f||a;var g={visible:!0,lineWidth:2,strokeStyle:"#666"};return c.render=o.extend(g,c.render),c.id=c.id||o.nextId(),c.label=c.label||"Constraint",c.type="constraint",c.stiffness=c.stiffness||1,c.angularStiffness=c.angularStiffness||0,c.angleA=c.bodyA?c.bodyA.angle:c.angleA,c.angleB=c.bodyB?c.bodyB.angle:c.angleB,c},m.solveAll=function(a,b){for(var c=0;c0&&(B=0);var C,D={x:n.x*B,y:n.y*B};e&&!e.isStatic&&(C=z.cross(s,D)*e.inverseInertia*(1-c.angularStiffness),u.set(e,!1),C=o.clamp(C,-.01,.01),e.constraintImpulse.x-=p.x,e.constraintImpulse.y-=p.y,e.constraintImpulse.angle+=C,e.position.x-=p.x,e.position.y-=p.y,e.angle+=C),f&&!f.isStatic&&(C=z.cross(t,D)*f.inverseInertia*(1-c.angularStiffness),u.set(f,!1),C=o.clamp(C,-.01,.01),f.constraintImpulse.x+=p.x,f.constraintImpulse.y+=p.y,f.constraintImpulse.angle-=C,f.position.x+=p.x,f.position.y+=p.y,f.angle-=C)}}},m.postSolveAll=function(a){for(var b=0;b>16)+d,f=(c>>8&255)+d,g=(255&c)+d;return"#"+(16777216+65536*(255>e?1>e?0:e:255)+256*(255>f?1>f?0:f:255)+(255>g?1>g?0:g:255)).toString(16).slice(1)},o.shuffle=function(a){for(var b=a.length-1;b>0;b--){var c=Math.floor(o.random()*(b+1)),d=a[b];a[b]=a[c],a[c]=d}return a},o.choose=function(a){return a[Math.floor(o.random()*a.length)]},o.isElement=function(a){try{return a instanceof HTMLElement}catch(b){return"object"==typeof a&&1===a.nodeType&&"object"==typeof a.style&&"object"==typeof a.ownerDocument}},o.clamp=function(a,b,c){return b>a?b:a>c?c:a},o.sign=function(a){return 0>a?-1:1},o.now=function(){var a=window.performance;return a?(a.now=a.now||a.webkitNow||a.msNow||a.oNow||a.mozNow,+a.now()):+new Date},o.random=function(b,c){return b="undefined"!=typeof b?b:0,c="undefined"!=typeof c?c:1,b+a()*(c-b)},o.colorToNumber=function(a){return a=a.replace("#",""),3==a.length&&(a=a.charAt(0)+a.charAt(0)+a.charAt(1)+a.charAt(1)+a.charAt(2)+a.charAt(2)),parseInt(a,16)},o.log=function(a,b){if(console&&console.log&&console.warn){switch(b){case"warn":console.warn("Matter.js:",a);break;case"error":console.log("Matter.js:",a)}}},o.nextId=function(){return o._nextId++},o.indexOf=function(a,b){if(a.indexOf)return a.indexOf(b);for(var c=0;c0&&q.trigger(a,"collisionStart",{pairs:w.collisionStart}),k.preSolveVelocity(w.list),e=0;e0&&q.trigger(a,"collisionActive",{pairs:w.collisionActive}),w.collisionEnd.length>0&&q.trigger(a,"collisionEnd",{pairs:w.collisionEnd}),r.update(a.metrics,a),f(s),g.isModified&&c.setModified(g,!1,!1,!0),q.trigger(a,"afterUpdate",p),a},p.render=function(a){var b={timestamp:a.timing.timestamp};q.trigger(a,"beforeRender",b),a.render.controller.world(a),q.trigger(a,"afterRender",b)},p.merge=function(a,b){if(o.extend(a,b),b.world){a.world=b.world,p.clear(a);for(var d=c.allBodies(a.world),e=0;ef.max.x||h.bounds.max.yf.max.y||b.update(h,c,d,e)}}}();var q={};!function(){q.on=function(a,b,c){for(var d,e=b.split(" "),f=0;fl.deltaMax?l.deltaMax:k,m=k/l.delta,l.delta=k),0!==h&&(m*=l.timeScale/h),0===l.timeScale&&(m=0),h=l.timeScale,f+=1,j-e>=1e3&&(l.fps=f*((j-e)/1e3),e=j,f=0),q.trigger(a,"tick",n),a.world.isModified&&a.render.controller.clear&&a.render.controller.clear(a.render),p.update(a,k,m),p.render(a),q.trigger(a,"afterTick",n)}}()},t.stop=function(a){e(a.timing.frameRequestId)}}();var u={};!function(){u._motionWakeThreshold=.18,u._motionSleepThreshold=.08,u._minBias=.9,u.update=function(a,b){for(var c=b*b*b,d=0;d0||e.force.y>0)u.set(e,!1);else{var g=Math.min(e.motion,f),h=Math.max(e.motion,f);e.motion=u._minBias*g+(1-u._minBias)*h,e.sleepThreshold>0&&e.motion=e.sleepThreshold&&u.set(e,!0)):e.sleepCounter>0&&(e.sleepCounter-=1)}}},u.afterCollisions=function(a,b){for(var c=b*b*b,d=0;du._motionWakeThreshold*c&&u.set(i,!1)}}}},u.set=function(a,b){b?(a.isSleeping=!0,a.sleepCounter=a.sleepThreshold,a.positionImpulse.x=0,a.positionImpulse.y=0,a.positionPrev.x=a.position.x,a.positionPrev.y=a.position.y,a.anglePrev=a.angle,a.speed=0,a.angularSpeed=0,a.motion=0):(a.isSleeping=!1,a.sleepCounter=0)}}();var v={};!function(){v.rectangle=function(a,c,d,e,f){f=f||{};var g={label:"Rectangle Body",position:{x:a,y:c},vertices:A.fromPath("L 0 0 L "+d+" 0 L "+d+" "+e+" L 0 "+e)};if(f.chamfer){var h=f.chamfer;g.vertices=A.chamfer(g.vertices,h.radius,h.quality,h.qualityMin,h.qualityMax),delete f.chamfer}return b.create(o.extend({},g,f))},v.trapezoid=function(a,c,d,e,f,g){g=g||{},f*=.5;var h=(1-2*f)*d,i=d*f,j=i+h,k=j+i,l={label:"Trapezoid Body",position:{x:a,y:c},vertices:A.fromPath("L 0 0 L "+i+" "+-e+" L "+j+" "+-e+" L "+k+" 0")};if(g.chamfer){var m=g.chamfer;l.vertices=A.chamfer(l.vertices,m.radius,m.quality,m.qualityMin,m.qualityMax),delete g.chamfer}return b.create(o.extend({},l,g))},v.circle=function(a,b,c,d,e){d=d||{},d.label="Circle Body",e=e||25;var f=Math.ceil(Math.max(10,Math.min(e,c)));return f%2===1&&(f+=1),d.circleRadius=c,v.polygon(a,b,f,c,d)},v.polygon=function(a,c,d,e,f){if(f=f||{},3>d)return v.circle(a,c,e,f);for(var g=2*Math.PI/d,h="",i=.5*g,j=0;d>j;j+=1){var k=i+j*g,l=Math.cos(k)*e,m=Math.sin(k)*e;h+="L "+l.toFixed(3)+" "+m.toFixed(3)+" "}var n={label:"Polygon Body",position:{x:a,y:c},vertices:A.fromPath(h)};if(f.chamfer){var p=f.chamfer;n.vertices=A.chamfer(n.vertices,p.radius,p.quality,p.qualityMin,p.qualityMax),delete f.chamfer}return b.create(o.extend({},n,f))}}();var w={};!function(){w.stack=function(a,d,e,f,g,h,i){for(var j,k=c.create({label:"Stack"}),l=a,m=d,n=0,o=0;f>o;o++){for(var p=0,q=0;e>q;q++){var r=i(l,m,q,o,j,n);if(r){var s=r.bounds.max.y-r.bounds.min.y,t=r.bounds.max.x-r.bounds.min.x;s>p&&(p=s),b.translate(r,{x:.5*t,y:.5*s}),l=r.bounds.max.x+g,c.addBody(k,r),j=r,n+=1}}m+=p+h,l=a}return k},w.chain=function(a,b,d,e,f,g){for(var h=a.bodies,i=1;ig;g++){for(h=0;b>h;h++)h>0&&(i=l[h-1+g*b],j=l[h+g*b],c.addConstraint(a,m.create(o.extend({bodyA:i,bodyB:j},f))));for(h=0;b>h;h++)g>0&&(i=l[h+(g-1)*b],j=l[h+g*b],c.addConstraint(a,m.create(o.extend({bodyA:i,bodyB:j},f))),e&&h>0&&(k=l[h-1+(g-1)*b],c.addConstraint(a,m.create(o.extend({bodyA:k,bodyB:j},f)))),e&&b-1>h&&(k=l[h+1+(g-1)*b],c.addConstraint(a,m.create(o.extend({bodyA:k,bodyB:j},f)))))}return a.label+=" Mesh",a},w.pyramid=function(a,c,d,e,f,g,h){return w.stack(a,c,d,e,f,g,function(c,g,i,j,k,l){var m=Math.min(e,Math.ceil(d/2)),n=k?k.bounds.max.x-k.bounds.min.x:0;if(!(j>m)){j=m-j;var o=j,p=d-1-j;if(!(o>i||i>p)){1===l&&b.translate(k,{x:(i+(d%2===1?1:-1))*n,y:0});var q=k?i*n:0;return h(a+q+i*f,g,i,j,k,l)}}})},w.newtonsCradle=function(a,b,d,e,f){for(var g=c.create({label:"Newtons Cradle"}),h=0;d>h;h++){var i=1.9,j=v.circle(a+h*e*i,b+f,e,{inertia:99999,restitution:1,friction:0,frictionAir:1e-4,slop:.01}),k=m.create({pointA:{x:a+h*e*i,y:b},bodyB:j});c.addBody(g,j),c.addConstraint(g,k)}return g},w.car=function(a,d,e,f,g){var h=b.nextGroup(!0),i=-20,j=.5*-e+i,k=.5*e-i,l=0,n=c.create({label:"Car"}),o=v.trapezoid(a,d,e,f,.3,{collisionFilter:{group:h},friction:.01,chamfer:{radius:10}}),p=v.circle(a+j,d+l,g,{collisionFilter:{group:h},restitution:.5,friction:.9,density:.01}),q=v.circle(a+k,d+l,g,{collisionFilter:{group:h},restitution:.5,friction:.9,density:.01}),r=m.create({bodyA:o,pointA:{x:j,y:l},bodyB:p,stiffness:.5}),s=m.create({bodyA:o,pointA:{x:k,y:l},bodyB:q,stiffness:.5});return c.addBody(n,o),c.addBody(n,p),c.addBody(n,q),c.addConstraint(n,r),c.addConstraint(n,s),n},w.softBody=function(a,b,c,d,e,f,g,h,i,j){i=o.extend({inertia:1/0},i),j=o.extend({stiffness:.4},j);var k=w.stack(a,b,c,d,e,f,function(a,b){return v.circle(a,b,h,i)});return w.mesh(k,c,d,g,j),k.label="Soft Body",k}}();var x={};!function(){x.fromVertices=function(a){for(var b={},c=0;ca.max.x&&(a.max.x=e.x),e.xa.max.y&&(a.max.y=e.y),e.y0?a.max.x+=c.x:a.min.x+=c.x,c.y>0?a.max.y+=c.y:a.min.y+=c.y)},y.contains=function(a,b){return b.x>=a.min.x&&b.x<=a.max.x&&b.y>=a.min.y&&b.y<=a.max.y},y.overlaps=function(a,b){return a.min.x<=b.max.x&&a.max.x>=b.min.x&&a.max.y>=b.min.y&&a.min.y<=b.max.y},y.translate=function(a,b){a.min.x+=b.x,a.max.x+=b.x,a.min.y+=b.y,a.max.y+=b.y},y.shift=function(a,b){var c=a.max.x-a.min.x,d=a.max.y-a.min.y;a.min.x=b.x,a.max.x=b.x+c,a.min.y=b.y,a.max.y=b.y+d}}();var z={};!function(){z.clone=function(a){return{x:a.x,y:a.y}},z.magnitude=function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},z.magnitudeSquared=function(a){return a.x*a.x+a.y*a.y},z.rotate=function(a,b){var c=Math.cos(b),d=Math.sin(b);return{x:a.x*c-a.y*d,y:a.x*d+a.y*c}},z.rotateAbout=function(a,b,c){var d=Math.cos(b),e=Math.sin(b);return{x:c.x+((a.x-c.x)*d-(a.y-c.y)*e),y:c.y+((a.x-c.x)*e+(a.y-c.y)*d)}},z.normalise=function(a){var b=z.magnitude(a);return 0===b?{x:0,y:0}:{x:a.x/b,y:a.y/b}},z.dot=function(a,b){return a.x*b.x+a.y*b.y},z.cross=function(a,b){return a.x*b.y-a.y*b.x},z.add=function(a,b){return{x:a.x+b.x,y:a.y+b.y}},z.sub=function(a,b){return{x:a.x-b.x,y:a.y-b.y}},z.mult=function(a,b){return{x:a.x*b,y:a.y*b}},z.div=function(a,b){return{x:a.x/b,y:a.y/b}},z.perp=function(a,b){return b=b===!0?-1:1,{x:b*-a.y,y:b*a.x}},z.neg=function(a){return{x:-a.x,y:-a.y}},z.angle=function(a,b){return Math.atan2(b.y-a.y,b.x-a.x)}}();var A={};!function(){A.create=function(a,b){for(var c=[],d=0;d0)return!1}return!0},A.scale=function(a,b,c,d){if(1===b&&1===c)return a;d=d||A.centre(a);for(var e,f,g=0;g=0?g-1:a.length-1],i=a[g],j=a[(g+1)%a.length],k=b[gv;v++)f.push(z.add(z.rotate(p,u*v),r))}else f.push(i)}return f}}();var B={};!function(){B.create=function(b){var c={controller:B,element:null,canvas:null,options:{width:800,height:600,pixelRatio:1,background:"#fafafa",wireframeBackground:"#222",hasBounds:!1,enabled:!0,wireframes:!0,showSleeping:!0,showDebug:!1,showBroadphase:!1,showBounds:!1,showVelocity:!1,showCollisions:!1,showAxes:!1,showPositions:!1,showAngleIndicator:!1,showIds:!1,showShadows:!1}},d=o.extend(c,b);return d.canvas=d.canvas||a(d.options.width,d.options.height),d.context=d.canvas.getContext("2d"),d.textures={},d.bounds=d.bounds||{min:{x:0,y:0},max:{x:d.options.width,y:d.options.height}},1!==d.options.pixelRatio&&B.setPixelRatio(d,d.options.pixelRatio),o.isElement(d.element)?d.element.appendChild(d.canvas):o.log("Render.create: options.element was undefined, render.canvas was created but not appended","warn"),d},B.setPixelRatio=function(a,c){var d=a.options,e=a.canvas;"auto"===c&&(c=b(e)),d.pixelRatio=c,e.setAttribute("data-pixel-ratio",c),e.width=d.width*c,e.height=d.height*c,e.style.width=d.width+"px",e.style.height=d.height+"px",a.context.scale(c,c)},B.world=function(a){var b,d=a.render,f=a.world,h=d.canvas,i=d.context,j=d.options,k=c.allBodies(f),l=c.allConstraints(f),m=j.wireframes?j.wireframeBackground:j.background,n=[],o=[];if(d.currentBackground!==m&&e(d,m),i.globalCompositeOperation="source-in",i.fillStyle="transparent",i.fillRect(0,0,h.width,h.height),i.globalCompositeOperation="source-over",j.hasBounds){var p=d.bounds.max.x-d.bounds.min.x,q=d.bounds.max.y-d.bounds.min.y,r=p/j.width,s=q/j.height;for(b=0;b=500){var k="";k+="fps: "+Math.round(a.timing.fps)+j,a.metrics.extended&&(k+="delta: "+a.timing.delta.toFixed(3)+j,k+="correction: "+a.timing.correction.toFixed(3)+j,k+="bodies: "+i.length+j,a.broadphase.controller===g&&(k+="buckets: "+a.metrics.buckets+j),k+="\n",k+="collisions: "+a.metrics.collisions+j,k+="pairs: "+a.pairs.list.length+j,k+="broad: "+a.metrics.broadEff+j,k+="mid: "+a.metrics.midEff+j,k+="narrow: "+a.metrics.narrowEff+j),f.debugString=k,f.debugTimestamp=a.timing.timestamp}if(f.debugString){d.font="12px Arial",d.fillStyle=h.wireframes?"rgba(255,255,255,0.5)":"rgba(0,0,0,0.5)";for(var l=f.debugString.split("\n"),m=0;m0){var l=d.activeContacts[0].vertex.x,m=d.activeContacts[0].vertex.y;2===d.activeContacts.length&&(l=(d.activeContacts[0].vertex.x+d.activeContacts[1].vertex.x)/2,m=(d.activeContacts[0].vertex.y+d.activeContacts[1].vertex.y)/2),h.moveTo(l-8*e.normal.x,m-8*e.normal.y),h.lineTo(l,m)}h.strokeStyle=i.wireframes?"rgba(255,165,0,0.7)":"orange",h.lineWidth=1,h.stroke()},B.grid=function(a,b,c){var d=c,e=a.render.options;d.strokeStyle=e.wireframes?"rgba(255,180,0,0.1)":"rgba(255,180,0,0.5)",d.beginPath();for(var f=o.keys(b.buckets),g=0;g0&&A.rotateAbout(e.position,c,a.position,e.position)}},b.setVelocity=function(a,b){a.positionPrev.x=a.position.x-b.x,a.positionPrev.y=a.position.y-b.y,a.velocity.x=b.x,a.velocity.y=b.y,a.speed=A.magnitude(a.velocity)},b.setAngularVelocity=function(a,b){a.anglePrev=a.angle-b,a.angularVelocity=b,a.angularSpeed=Math.abs(a.angularVelocity)},b.translate=function(a,c){b.setPosition(a,A.add(a.position,c))},b.rotate=function(a,c){b.setAngle(a,a.angle+c)},b.scale=function(a,c,d){for(var e=0;e0&&(j.position.x+=a.velocity.x,j.position.y+=a.velocity.y),0!==a.angularVelocity&&(B.rotate(j.vertices,a.angularVelocity,a.position),x.rotate(j.axes,a.angularVelocity),i>0&&A.rotateAbout(j.position,a.angularVelocity,a.position,j.position)),y.update(j.bounds,j.vertices,a.velocity)}},b.applyForce=function(a,b,c){a.force.x+=c.x,a.force.y+=c.y;var d={x:b.x-a.position.x,y:b.y-a.position.y};a.torque+=(d.x*c.y-d.y*c.x)*a.inverseInertia};var f=function(a){for(var b={mass:0,area:0,inertia:0,centre:{x:0,y:0}},c=1===a.parts.length?0:1;c1?1:0;j1?1:0;l0:0!==(a.mask&b.category)&&0!==(b.mask&a.category)}}();var g={};!function(){g.create=function(a){var b={controller:g,detector:f.collisions,buckets:{},pairs:{},pairsList:[],bucketWidth:48,bucketHeight:48};return p.extend(b,a)},g.update=function(c,f,g,h){var l,m,n,o,p,q=g.world,r=c.buckets,s=!1;for(l=0;lq.bounds.width||t.bounds.max.y<0||t.bounds.min.y>q.bounds.height)){var u=b(c,t);if(!t.region||u.id!==t.region.id||h){(!t.region||h)&&(t.region=u);var v=a(u,t.region);for(m=v.startCol;m<=v.endCol;m++)for(n=v.startRow;n<=v.endRow;n++){p=d(m,n),o=r[p];var w=m>=u.startCol&&m<=u.endCol&&n>=u.startRow&&n<=u.endRow,x=m>=t.region.startCol&&m<=t.region.endCol&&n>=t.region.startRow&&n<=t.region.endRow;!w&&x&&x&&o&&j(c,o,t),(t.region===u||w&&!x||h)&&(o||(o=e(r,p)),i(c,o,t))}t.region=u,s=!0}}}s&&(c.pairsList=k(c))},g.clear=function(a){a.buckets={},a.pairs={},a.pairsList=[]};var a=function(a,b){var d=Math.min(a.startCol,b.startCol),e=Math.max(a.endCol,b.endCol),f=Math.min(a.startRow,b.startRow),g=Math.max(a.endRow,b.endRow);return c(d,e,f,g)},b=function(a,b){var d=b.bounds,e=Math.floor(d.min.x/a.bucketWidth),f=Math.floor(d.max.x/a.bucketWidth),g=Math.floor(d.min.y/a.bucketHeight),h=Math.floor(d.max.y/a.bucketHeight);return c(e,f,g,h)},c=function(a,b,c,d){return{id:a+","+b+","+c+","+d,startCol:a,endCol:b,startRow:c,endRow:d}},d=function(a,b){return a+","+b},e=function(a,b){var c=a[b]=[];return c},i=function(a,b,c){for(var d=0;d0?d.push(c):delete a.pairs[b[e]];return d}}();var h={};!function(){h.create=function(a,b){var c=a.bodyA,d=a.bodyB,e=a.parentA,f=a.parentB,g={id:h.id(c,d),bodyA:c,bodyB:d,contacts:{},activeContacts:[],separation:0,isActive:!0,timeCreated:b,timeUpdated:b,inverseMass:e.inverseMass+f.inverseMass,friction:Math.min(e.friction,f.friction),frictionStatic:Math.max(e.frictionStatic,f.frictionStatic),restitution:Math.max(e.restitution,f.restitution),slop:Math.max(e.slop,f.slop)};return h.update(g,a,b),g},h.update=function(a,b,c){var d=a.contacts,f=b.supports,g=a.activeContacts,i=b.parentA,j=b.parentB;if(a.collision=b,a.inverseMass=i.inverseMass+j.inverseMass,a.friction=Math.min(i.friction,j.friction),a.frictionStatic=Math.max(i.frictionStatic,j.frictionStatic),a.restitution=Math.max(i.restitution,j.restitution),a.slop=Math.max(i.slop,j.slop),g.length=0,b.collided){for(var k=0;ka&&j.push(g);for(g=0;gk.friction*k.frictionStatic*I*c&&(J=k.friction*G*c,K=F);var L=A.cross(x,q),M=A.cross(y,q),N=n.inverseMass+o.inverseMass+n.inverseInertia*L*L+o.inverseInertia*M*M;if(H*=t/N,J*=t/(1+N),0>D&&D*D>l._restingThresh*c)v.normalImpulse=0,v.tangentImpulse=0;else{var O=v.normalImpulse;v.normalImpulse=Math.min(v.normalImpulse+H,0),H=v.normalImpulse-O;var P=v.tangentImpulse;v.tangentImpulse=p.clamp(v.tangentImpulse+J,-K,K),J=v.tangentImpulse-P}d.x=q.x*H+r.x*J,d.y=q.y*H+r.y*J,n.isStatic||n.isSleeping||(n.positionPrev.x+=d.x*n.inverseMass,n.positionPrev.y+=d.y*n.inverseMass,n.anglePrev+=A.cross(x,d)*n.inverseInertia),o.isStatic||o.isSleeping||(o.positionPrev.x-=d.x*o.inverseMass,o.positionPrev.y-=d.y*o.inverseMass,o.anglePrev-=A.cross(y,d)*o.inverseInertia)}}}}}();var m={};!function(){m.collides=function(b,d,e){var f,g,h,i,j=e,k=!1;if(j){var l=b.parent,m=d.parent,n=l.speed*l.speed+l.angularSpeed*l.angularSpeed+m.speed*m.speed+m.angularSpeed*m.angularSpeed;k=j&&j.collided&&.2>n,i=j}else i={collided:!1,bodyA:b,bodyB:d};if(j&&k){var o=i.axisBody,p=o===b?d:b,q=[o.axes[j.axisNumber]];if(h=a(o.vertices,p.vertices,q),i.reused=!0,h.overlap<=0)return i.collided=!1,i}else{if(f=a(b.vertices,d.vertices,b.axes),f.overlap<=0)return i.collided=!1,i;if(g=a(d.vertices,b.vertices,d.axes),g.overlap<=0)return i.collided=!1,i;f.overlap0&&(i.normal=A.neg(i.normal)),i.tangent=A.perp(i.normal),i.penetration={x:i.normal.x*i.depth,y:i.normal.y*i.depth};var r=c(b,d,i.normal),s=i.supports||[];if(s.length=0,B.contains(b.vertices,r[0])&&s.push(r[0]),B.contains(b.vertices,r[1])&&s.push(r[1]),s.length<2){var t=c(d,b,A.neg(i.normal));B.contains(d.vertices,t[0])&&s.push(t[0]),s.length<2&&B.contains(d.vertices,t[1])&&s.push(t[1])}return s.length<1&&(s=[r[0]]),i.supports=s,i};var a=function(a,c,d){for(var e,f,g=A._temp[0],h=A._temp[1],i={overlap:Number.MAX_VALUE},j=0;j=e)return i.overlap=e,i;ee?e=g:d>g&&(d=g)}a.min=d,a.max=e},c=function(a,b,c){for(var d,e,f,g,h=Number.MAX_VALUE,i=A._temp[0],j=b.vertices,k=a.position,l=0;ld&&(h=d,f=e);var m=f.index-1>=0?f.index-1:j.length-1;e=j[m],i.x=e.x-k.x,i.y=e.y-k.y,h=-A.dot(c,i),g=e;var n=(f.index+1)%j.length;return e=j[n],i.x=e.x-k.x,i.y=e.y-k.y,d=-A.dot(c,i),h>d&&(g=e),[f,g]}}();var n={};!function(){var a=1e-6,b=.001;n.create=function(b){var c=b;c.bodyA&&!c.pointA&&(c.pointA={x:0,y:0}),c.bodyB&&!c.pointB&&(c.pointB={x:0,y:0});var d=c.bodyA?A.add(c.bodyA.position,c.pointA):c.pointA,e=c.bodyB?A.add(c.bodyB.position,c.pointB):c.pointB,f=A.magnitude(A.sub(d,e));c.length=c.length||f||a;var g={visible:!0,lineWidth:2,strokeStyle:"#666"};return c.render=p.extend(g,c.render),c.id=c.id||p.nextId(),c.label=c.label||"Constraint",c.type="constraint",c.stiffness=c.stiffness||1,c.angularStiffness=c.angularStiffness||0,c.angleA=c.bodyA?c.bodyA.angle:c.angleA,c.angleB=c.bodyB?c.bodyB.angle:c.angleB,c},n.solveAll=function(a,b){for(var c=0;c0&&(B=0);var C,D={x:n.x*B,y:n.y*B};e&&!e.isStatic&&(C=A.cross(s,D)*e.inverseInertia*(1-c.angularStiffness),u.set(e,!1),C=p.clamp(C,-.01,.01),e.constraintImpulse.x-=o.x,e.constraintImpulse.y-=o.y,e.constraintImpulse.angle+=C,e.position.x-=o.x,e.position.y-=o.y,e.angle+=C),f&&!f.isStatic&&(C=A.cross(t,D)*f.inverseInertia*(1-c.angularStiffness),u.set(f,!1),C=p.clamp(C,-.01,.01),f.constraintImpulse.x+=o.x,f.constraintImpulse.y+=o.y,f.constraintImpulse.angle-=C,f.position.x+=o.x,f.position.y+=o.y,f.angle-=C)}}},n.postSolveAll=function(a){for(var b=0;b0&&(f.position.x+=d.x,f.position.y+=d.y),0!==d.angle&&(B.rotate(f.vertices,d.angle,c.position),x.rotate(f.axes,d.angle),e>0&&A.rotateAbout(f.position,d.angle,c.position,f.position)),y.update(f.bounds,f.vertices)}d.angle=0,d.x=0,d.y=0}}}();var o={};!function(){o.create=function(b,d){var e=(b?b.mouse:null)||(d?d.mouse:null);!e&&b&&b.render&&b.render.canvas?e=s.create(b.render.canvas):(e=s.create(),p.log("MouseConstraint.create: options.mouse was undefined, engine.render.canvas was undefined, may not function as expected","warn"));var f=n.create({label:"Mouse Constraint",pointA:e.position,pointB:{x:0,y:0},length:.01,stiffness:.1,angularStiffness:1,render:{strokeStyle:"#90EE90",lineWidth:3}}),g={type:"mouseConstraint",mouse:e,body:null,constraint:f,collisionFilter:{category:1,mask:4294967295,group:0}},h=p.extend(g,d);return r.on(b,"tick",function(){var d=c.allBodies(b.world);o.update(h,d),a(h)}),h},o.update=function(a,b){var c=a.mouse,d=a.constraint,e=a.body;if(0===c.button){if(d.bodyB)u.set(d.bodyB,!1),d.pointA=c.position;else for(var g=0;g1?1:0;h>16)+d,f=(c>>8&255)+d,g=(255&c)+d;return"#"+(16777216+65536*(255>e?1>e?0:e:255)+256*(255>f?1>f?0:f:255)+(255>g?1>g?0:g:255)).toString(16).slice(1)},p.shuffle=function(a){for(var b=a.length-1;b>0;b--){var c=Math.floor(p.random()*(b+1)),d=a[b];a[b]=a[c],a[c]=d}return a},p.choose=function(a){return a[Math.floor(p.random()*a.length)]},p.isElement=function(a){try{return a instanceof HTMLElement}catch(b){return"object"==typeof a&&1===a.nodeType&&"object"==typeof a.style&&"object"==typeof a.ownerDocument}},p.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)},p.clamp=function(a,b,c){return b>a?b:a>c?c:a},p.sign=function(a){return 0>a?-1:1},p.now=function(){var a=window.performance;return a?(a.now=a.now||a.webkitNow||a.msNow||a.oNow||a.mozNow,+a.now()):+new Date},p.random=function(b,c){return b="undefined"!=typeof b?b:0,c="undefined"!=typeof c?c:1,b+a()*(c-b)},p.colorToNumber=function(a){return a=a.replace("#",""),3==a.length&&(a=a.charAt(0)+a.charAt(0)+a.charAt(1)+a.charAt(1)+a.charAt(2)+a.charAt(2)),parseInt(a,16)},p.log=function(a,b){if(console&&console.log&&console.warn)switch(b){case"warn":console.warn("Matter.js:",a);break;case"error":console.log("Matter.js:",a)}},p.nextId=function(){return p._nextId++},p.indexOf=function(a,b){if(a.indexOf)return a.indexOf(b);for(var c=0;c0&&r.trigger(a,"collisionStart",{pairs:v.collisionStart}),l.preSolvePosition(v.list),e=0;e0&&r.trigger(a,"collisionActive",{pairs:v.collisionActive}),v.collisionEnd.length>0&&r.trigger(a,"collisionEnd",{pairs:v.collisionEnd}),f(q),g.isModified&&c.setModified(g,!1,!1,!0),r.trigger(a,"afterUpdate",p),a},q.render=function(a){var b={timestamp:a.timing.timestamp};r.trigger(a,"beforeRender",b),a.render.controller.world(a),r.trigger(a,"afterRender",b)},q.merge=function(a,b){if(p.extend(a,b),b.world){a.world=b.world,q.clear(a);for(var d=c.allBodies(a.world),e=0;ef.max.x||h.bounds.max.yf.max.y||b.update(h,c,d,e)}}}();var r={};!function(){r.on=function(a,b,c){for(var d,e=b.split(" "),f=0;fl.deltaMax?l.deltaMax:k,m=k/l.delta,l.delta=k),0!==h&&(m*=l.timeScale/h),0===l.timeScale&&(m=0),h=l.timeScale,f+=1,j-e>=1e3&&(l.fps=f*((j-e)/1e3),e=j,f=0),r.trigger(a,"tick",n),a.world.isModified&&a.render.controller.clear&&a.render.controller.clear(a.render),q.update(a,k,m),q.render(a),r.trigger(a,"afterTick",n)}}()},t.stop=function(a){e(a.timing.frameRequestId)}}();var u={};!function(){u._motionWakeThreshold=.18,u._motionSleepThreshold=.08,u._minBias=.9,u.update=function(a,b){for(var c=b*b*b,d=0;d0||e.force.y>0)u.set(e,!1);else{var g=Math.min(e.motion,f),h=Math.max(e.motion,f);e.motion=u._minBias*g+(1-u._minBias)*h,e.sleepThreshold>0&&e.motion=e.sleepThreshold&&u.set(e,!0)):e.sleepCounter>0&&(e.sleepCounter-=1)}}},u.afterCollisions=function(a,b){for(var c=b*b*b,d=0;du._motionWakeThreshold*c&&u.set(i,!1)}}}},u.set=function(a,b){b?(a.isSleeping=!0,a.sleepCounter=a.sleepThreshold,a.positionImpulse.x=0,a.positionImpulse.y=0,a.positionPrev.x=a.position.x,a.positionPrev.y=a.position.y,a.anglePrev=a.angle,a.speed=0,a.angularSpeed=0,a.motion=0):(a.isSleeping=!1,a.sleepCounter=0)}}();var v={};!function(){v.rectangle=function(a,c,d,e,f){f=f||{};var g={label:"Rectangle Body",position:{x:a,y:c},vertices:B.fromPath("L 0 0 L "+d+" 0 L "+d+" "+e+" L 0 "+e)};if(f.chamfer){var h=f.chamfer;g.vertices=B.chamfer(g.vertices,h.radius,h.quality,h.qualityMin,h.qualityMax),delete f.chamfer}return b.create(p.extend({},g,f))},v.trapezoid=function(a,c,d,e,f,g){g=g||{},f*=.5;var h=(1-2*f)*d,i=d*f,j=i+h,k=j+i,l={label:"Trapezoid Body",position:{x:a,y:c},vertices:B.fromPath("L 0 0 L "+i+" "+-e+" L "+j+" "+-e+" L "+k+" 0")};if(g.chamfer){var m=g.chamfer;l.vertices=B.chamfer(l.vertices,m.radius,m.quality,m.qualityMin,m.qualityMax),delete g.chamfer}return b.create(p.extend({},l,g))},v.circle=function(a,b,c,d,e){d=d||{},d.label="Circle Body",e=e||25;var f=Math.ceil(Math.max(10,Math.min(e,c)));return f%2===1&&(f+=1),d.circleRadius=c,v.polygon(a,b,f,c,d)},v.polygon=function(a,c,d,e,f){if(f=f||{},3>d)return v.circle(a,c,e,f);for(var g=2*Math.PI/d,h="",i=.5*g,j=0;d>j;j+=1){var k=i+j*g,l=Math.cos(k)*e,m=Math.sin(k)*e;h+="L "+l.toFixed(3)+" "+m.toFixed(3)+" "}var n={label:"Polygon Body",position:{x:a,y:c},vertices:B.fromPath(h)};if(f.chamfer){var o=f.chamfer;n.vertices=B.chamfer(n.vertices,o.radius,o.quality,o.qualityMin,o.qualityMax),delete f.chamfer}return b.create(p.extend({},n,f))},v.fromVertices=function(a,c,d,e,f,g,h){var i,j,k,l,m,n,o,q,r;for(e=e||{},j=[],f="undefined"!=typeof f?f:!1,g="undefined"!=typeof g?g:.01,h="undefined"!=typeof h?h:10,window.decomp||p.log("Bodies.fromVertices: poly-decomp.js required. Could not decompose vertices. Fallback to convex hull.","warn"),p.isArray(d[0])||(d=[d]),q=0;q0&&B.area(v)E&&w>F&&(C[o].isInternal=!0,D[r].isInternal=!0)}}}}}return j.length>1?(i=b.create(p.extend({parts:j.slice(0)},e)),b.setPosition(i,{x:a,y:c}),i):j[0]}}();var w={};!function(){w.stack=function(a,d,e,f,g,h,i){for(var j,k=c.create({label:"Stack"}),l=a,m=d,n=0,o=0;f>o;o++){for(var p=0,q=0;e>q;q++){var r=i(l,m,q,o,j,n);if(r){var s=r.bounds.max.y-r.bounds.min.y,t=r.bounds.max.x-r.bounds.min.x;s>p&&(p=s),b.translate(r,{x:.5*t,y:.5*s}),l=r.bounds.max.x+g,c.addBody(k,r),j=r,n+=1}else l+=g}m+=p+h,l=a}return k},w.chain=function(a,b,d,e,f,g){for(var h=a.bodies,i=1;ig;g++){for(h=0;b>h;h++)h>0&&(i=l[h-1+g*b],j=l[h+g*b],c.addConstraint(a,n.create(p.extend({bodyA:i,bodyB:j},f))));for(h=0;b>h;h++)g>0&&(i=l[h+(g-1)*b],j=l[h+g*b],c.addConstraint(a,n.create(p.extend({bodyA:i,bodyB:j},f))),e&&h>0&&(k=l[h-1+(g-1)*b],c.addConstraint(a,n.create(p.extend({bodyA:k,bodyB:j},f)))),e&&b-1>h&&(k=l[h+1+(g-1)*b],c.addConstraint(a,n.create(p.extend({bodyA:k,bodyB:j},f)))))}return a.label+=" Mesh",a},w.pyramid=function(a,c,d,e,f,g,h){return w.stack(a,c,d,e,f,g,function(c,g,i,j,k,l){var m=Math.min(e,Math.ceil(d/2)),n=k?k.bounds.max.x-k.bounds.min.x:0;if(!(j>m)){j=m-j;var o=j,p=d-1-j;if(!(o>i||i>p)){1===l&&b.translate(k,{x:(i+(d%2===1?1:-1))*n,y:0});var q=k?i*n:0;return h(a+q+i*f,g,i,j,k,l)}}})},w.newtonsCradle=function(a,b,d,e,f){for(var g=c.create({label:"Newtons Cradle"}),h=0;d>h;h++){var i=1.9,j=v.circle(a+h*e*i,b+f,e,{inertia:99999,restitution:1,friction:0,frictionAir:1e-4,slop:.01}),k=n.create({pointA:{x:a+h*e*i,y:b},bodyB:j});c.addBody(g,j),c.addConstraint(g,k)}return g},w.car=function(a,d,e,f,g){var h=b.nextGroup(!0),i=-20,j=.5*-e+i,k=.5*e-i,l=0,m=c.create({label:"Car"}),o=v.trapezoid(a,d,e,f,.3,{collisionFilter:{group:h},friction:.01,chamfer:{radius:10}}),p=v.circle(a+j,d+l,g,{collisionFilter:{group:h},restitution:.5,friction:.9,frictionStatic:10,slop:.5,density:.01}),q=v.circle(a+k,d+l,g,{collisionFilter:{group:h},restitution:.5,friction:.9,frictionStatic:10,slop:.5,density:.01}),r=n.create({bodyA:o,pointA:{x:j,y:l},bodyB:p,stiffness:.5}),s=n.create({bodyA:o,pointA:{x:k,y:l},bodyB:q,stiffness:.5});return c.addBody(m,o),c.addBody(m,p),c.addBody(m,q),c.addConstraint(m,r),c.addConstraint(m,s),m},w.softBody=function(a,b,c,d,e,f,g,h,i,j){i=p.extend({inertia:1/0},i),j=p.extend({stiffness:.4},j);var k=w.stack(a,b,c,d,e,f,function(a,b){return v.circle(a,b,h,i)});return w.mesh(k,c,d,g,j),k.label="Soft Body",k}}();var x={};!function(){x.fromVertices=function(a){for(var b={},c=0;ca.max.x&&(a.max.x=e.x),e.xa.max.y&&(a.max.y=e.y),e.y0?a.max.x+=c.x:a.min.x+=c.x,c.y>0?a.max.y+=c.y:a.min.y+=c.y)},y.contains=function(a,b){return b.x>=a.min.x&&b.x<=a.max.x&&b.y>=a.min.y&&b.y<=a.max.y},y.overlaps=function(a,b){return a.min.x<=b.max.x&&a.max.x>=b.min.x&&a.max.y>=b.min.y&&a.min.y<=b.max.y},y.translate=function(a,b){a.min.x+=b.x,a.max.x+=b.x,a.min.y+=b.y,a.max.y+=b.y},y.shift=function(a,b){var c=a.max.x-a.min.x,d=a.max.y-a.min.y;a.min.x=b.x,a.max.x=b.x+c,a.min.y=b.y,a.max.y=b.y+d}}();var z={};!function(){z.pathToVertices=function(b,c){var d,e,f,g,h,i,j,k,l,m,n=[],o=0,p=0,q=0;c=c||15;var r=function(a,b,c){var d=c%2===1&&c>1;if(!l||a!=l.x||b!=l.y){l&&d?(lx=l.x,ly=l.y):(lx=0,ly=0);var e={x:lx+a,y:ly+b};(d||!l)&&(l=e),n.push(e),p=lx+a,q=ly+b}},s=function(a){var b=a.pathSegTypeAsLetter.toUpperCase();if("Z"!==b){switch(b){case"M":case"L":case"T":case"C":case"S":case"Q":p=a.x,q=a.y;break;case"H":p=a.x;break;case"V":q=a.y}r(p,q,a.pathSegType)}};for(a(b),f=b.getTotalLength(),i=[],d=0;do;){if(m=b.getPathSegAtLength(o),h=i[m],h!=k){for(;j.length&&j[0]!=h;)s(j.shift());k=h}switch(h.pathSegTypeAsLetter.toUpperCase()){case"C":case"T":case"S":case"Q":case"A":g=b.getPointAtLength(o),r(g.x,g.y,0)}o+=c}for(d=0,e=j.length;e>d;++d)s(j[d]);return n};var a=function(a){for(var b,c,d,e,f,g,h=a.pathSegList,i=0,j=0,k=h.numberOfItems,l=0;k>l;++l){var m=h.getItem(l),n=m.pathSegTypeAsLetter;if(/[MLHVCSQTA]/.test(n))"x"in m&&(i=m.x),"y"in m&&(j=m.y);else switch("x1"in m&&(d=i+m.x1),"x2"in m&&(f=i+m.x2),"y1"in m&&(e=j+m.y1),"y2"in m&&(g=j+m.y2),"x"in m&&(i+=m.x),"y"in m&&(j+=m.y),n){case"m":h.replaceItem(a.createSVGPathSegMovetoAbs(i,j),l);break;case"l":h.replaceItem(a.createSVGPathSegLinetoAbs(i,j),l);break;case"h":h.replaceItem(a.createSVGPathSegLinetoHorizontalAbs(i),l);break;case"v":h.replaceItem(a.createSVGPathSegLinetoVerticalAbs(j),l);break;case"c":h.replaceItem(a.createSVGPathSegCurvetoCubicAbs(i,j,d,e,f,g),l);break;case"s":h.replaceItem(a.createSVGPathSegCurvetoCubicSmoothAbs(i,j,f,g),l);break;case"q":h.replaceItem(a.createSVGPathSegCurvetoQuadraticAbs(i,j,d,e),l);break;case"t":h.replaceItem(a.createSVGPathSegCurvetoQuadraticSmoothAbs(i,j),l);break;case"a":h.replaceItem(a.createSVGPathSegArcAbs(i,j,m.r1,m.r2,m.angle,m.largeArcFlag,m.sweepFlag),l);break;case"z":case"Z":i=b,j=c}("M"==n||"m"==n)&&(b=i,c=j)}}}();var A={};!function(){A.create=function(a,b){return{x:a||0,y:b||0}},A.clone=function(a){return{x:a.x,y:a.y}},A.magnitude=function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},A.magnitudeSquared=function(a){return a.x*a.x+a.y*a.y},A.rotate=function(a,b){var c=Math.cos(b),d=Math.sin(b);return{x:a.x*c-a.y*d,y:a.x*d+a.y*c}},A.rotateAbout=function(a,b,c,d){var e=Math.cos(b),f=Math.sin(b);d||(d={});var g=c.x+((a.x-c.x)*e-(a.y-c.y)*f);return d.y=c.y+((a.x-c.x)*f+(a.y-c.y)*e),d.x=g,d},A.normalise=function(a){var b=A.magnitude(a);return 0===b?{x:0,y:0}:{x:a.x/b,y:a.y/b}},A.dot=function(a,b){return a.x*b.x+a.y*b.y},A.cross=function(a,b){return a.x*b.y-a.y*b.x},A.cross3=function(a,b,c){return(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x)},A.add=function(a,b,c){return c||(c={}),c.x=a.x+b.x,c.y=a.y+b.y,c},A.sub=function(a,b,c){return c||(c={}),c.x=a.x-b.x,c.y=a.y-b.y,c},A.mult=function(a,b){return{x:a.x*b,y:a.y*b}},A.div=function(a,b){return{x:a.x/b,y:a.y/b}},A.perp=function(a,b){return b=b===!0?-1:1,{x:b*-a.y,y:b*a.x}},A.neg=function(a){return{x:-a.x,y:-a.y}},A.angle=function(a,b){return Math.atan2(b.y-a.y,b.x-a.x)},A._temp=[A.create(),A.create(),A.create(),A.create(),A.create(),A.create()]}();var B={};!function(){B.create=function(a,b){for(var c=[],d=0;d0)return!1}return!0},B.scale=function(a,b,c,d){if(1===b&&1===c)return a;d=d||B.centre(a);for(var e,f,g=0;g=0?g-1:a.length-1],i=a[g],j=a[(g+1)%a.length],k=b[gv;v++)f.push(A.add(A.rotate(o,u*v),r))}else f.push(i)}return f},B.clockwiseSort=function(a){var b=B.mean(a);return a.sort(function(a,c){return A.angle(b,a)-A.angle(b,c)}),a},B.isConvex=function(a){var b,c,d,e,f=0,g=a.length;if(3>g)return null;for(b=0;g>b;b++)if(c=(b+1)%g,d=(b+2)%g,e=(a[c].x-a[b].x)*(a[d].y-a[c].y),e-=(a[c].y-a[b].y)*(a[d].x-a[c].x),0>e?f|=1:e>0&&(f|=2),3===f)return!1;return 0!==f?!0:null},B.hull=function(a){var b,c,d=[],e=[];for(a=a.slice(0),a.sort(function(a,b){var c=a.x-b.x;return 0!==c?c:a.y-b.y}),c=0;c=2&&A.cross3(e[e.length-2],e[e.length-1],b)<=0;)e.pop();e.push(b)}for(c=a.length-1;c>=0;c--){for(b=a[c];d.length>=2&&A.cross3(d[d.length-2],d[d.length-1],b)<=0;)d.pop();d.push(b)}return d.pop(),e.pop(),d.concat(e)}}();var C={};!function(){C.create=function(b){var c={controller:C,element:null,canvas:null,options:{width:800,height:600,pixelRatio:1,background:"#fafafa",wireframeBackground:"#222",hasBounds:!1,enabled:!0,wireframes:!0,showSleeping:!0,showDebug:!1,showBroadphase:!1,showBounds:!1,showVelocity:!1,showCollisions:!1,showSeparations:!1,showAxes:!1,showPositions:!1,showAngleIndicator:!1,showIds:!1,showShadows:!1,showVertexNumbers:!1,showConvexHulls:!1,showInternalEdges:!1}},d=p.extend(c,b);return d.canvas=d.canvas||a(d.options.width,d.options.height),d.context=d.canvas.getContext("2d"),d.textures={},d.bounds=d.bounds||{min:{x:0,y:0},max:{x:d.options.width,y:d.options.height}},1!==d.options.pixelRatio&&C.setPixelRatio(d,d.options.pixelRatio),p.isElement(d.element)?d.element.appendChild(d.canvas):p.log("Render.create: options.element was undefined, render.canvas was created but not appended","warn"),d},C.setPixelRatio=function(a,c){var d=a.options,e=a.canvas;"auto"===c&&(c=b(e)),d.pixelRatio=c,e.setAttribute("data-pixel-ratio",c),e.width=d.width*c,e.height=d.height*c,e.style.width=d.width+"px",e.style.height=d.height+"px",a.context.scale(c,c)},C.world=function(a){var b,d=a.render,f=a.world,h=d.canvas,i=d.context,j=d.options,k=c.allBodies(f),l=c.allConstraints(f),m=j.wireframes?j.wireframeBackground:j.background,n=[],o=[];if(d.currentBackground!==m&&e(d,m),i.globalCompositeOperation="source-in",i.fillStyle="transparent",i.fillRect(0,0,h.width,h.height),i.globalCompositeOperation="source-over",j.hasBounds){var p=d.bounds.max.x-d.bounds.min.x,q=d.bounds.max.y-d.bounds.min.y,r=p/j.width,s=q/j.height;for(b=0;b=500){var i="";i+="fps: "+Math.round(a.timing.fps)+h,f.debugString=i,f.debugTimestamp=a.timing.timestamp}if(f.debugString){d.font="12px Arial",d.fillStyle=g.wireframes?"rgba(255,255,255,0.5)":"rgba(0,0,0,0.5)";for(var j=f.debugString.split("\n"),k=0;k1?1:0;k1?1:0;h1?1:0;f1?1:0;j1?1:0;f1?1:0;f1?1:0;e0)){var l=d.activeContacts[0].vertex.x,m=d.activeContacts[0].vertex.y;2===d.activeContacts.length&&(l=(d.activeContacts[0].vertex.x+d.activeContacts[1].vertex.x)/2,m=(d.activeContacts[0].vertex.y+d.activeContacts[1].vertex.y)/2),e.bodyB===e.supports[0].body||e.bodyA.isStatic===!0?h.moveTo(l-8*e.normal.x,m-8*e.normal.y):h.moveTo(l+8*e.normal.x,m+8*e.normal.y),h.lineTo(l,m)}h.strokeStyle=i.wireframes?"rgba(255,165,0,0.7)":"orange",h.lineWidth=1,h.stroke()},C.separations=function(a,b,c){var d,e,f,g,h,i=c,j=a.render.options;for(i.beginPath(),h=0;h1?1:0;l