diff --git a/build/matter-0.8.0.js b/build/matter-0.8.0.js deleted file mode 100644 index 3184f5cf..00000000 --- a/build/matter-0.8.0.js +++ /dev/null @@ -1,6360 +0,0 @@ -/** -* matter-0.8.0.js 0.8.0-alpha 2014-05-04 -* http://brm.io/matter-js/ -* License: MIT -*/ - -/** - * The MIT License (MIT) - * - * Copyright (c) 2014 Liam Brummitt - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -(function() { - -var Matter = {}; - -// Begin Matter namespace closure - -// All Matter modules are included below during build -// Outro.js then closes at the end of the file - - -// Begin src/body/Body.js - -/** -* 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 Body -*/ - -var Body = {}; - -(function() { - - var _nextGroupId = 1; - - /** - * Description to be written. - * @method create - * @param {} options - * @return {body} body - */ - Body.create = function(options) { - var defaults = { - id: Common.nextId(), - type: 'body', - label: 'Body', - angle: 0, - vertices: Vertices.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: false, - isSleeping: false, - motion: 0, - sleepThreshold: 60, - density: 0.001, - restitution: 0, - friction: 0.1, - frictionAir: 0.01, - groupId: 0, - slop: 0.05, - timeScale: 1, - render: { - visible: true, - sprite: { - xScale: 1, - yScale: 1 - }, - lineWidth: 1.5 - } - }; - - var body = Common.extend(defaults, options); - - _initProperties(body); - - return body; - }; - - /** - * Description - * @method nextGroupId - * @return {Number} Unique groupID - */ - Body.nextGroupId = function() { - return _nextGroupId++; - }; - - /** - * Initialises body properties - * @method _initProperties - * @private - * @param {body} body - */ - var _initProperties = function(body) { - // calculated properties - body.axes = body.axes || Axes.fromVertices(body.vertices); - body.area = Vertices.area(body.vertices); - body.bounds = Bounds.create(body.vertices); - body.mass = body.mass || (body.density * body.area); - body.inverseMass = 1 / body.mass; - body.inertia = body.inertia || Vertices.inertia(body.vertices, body.mass); - body.inverseInertia = 1 / body.inertia; - body.positionPrev = body.positionPrev || { x: body.position.x, y: body.position.y }; - body.anglePrev = body.anglePrev || body.angle; - body.render.fillStyle = body.render.fillStyle || (body.isStatic ? '#eeeeee' : Common.choose(['#556270', '#4ECDC4', '#C7F464', '#FF6B6B', '#C44D58'])); - body.render.strokeStyle = body.render.strokeStyle || Common.shadeColor(body.render.fillStyle, -20); - - // update geometry - Vertices.create(body.vertices, body); - var centre = Vertices.centre(body.vertices); - Vertices.translate(body.vertices, body.position); - Vertices.translate(body.vertices, centre, -1); - Vertices.rotate(body.vertices, body.angle, body.position); - Axes.rotate(body.axes, body.angle); - Bounds.update(body.bounds, body.vertices, body.velocity); - - Body.setStatic(body, body.isStatic); - Sleeping.set(body, body.isSleeping); - }; - - /** - * Sets the body as static, including isStatic flag and setting mass and inertia to Infinity - * @method setStatic - * @param {bool} isStatic - */ - Body.setStatic = function(body, isStatic) { - body.isStatic = isStatic; - - if (isStatic) { - body.restitution = 0; - body.friction = 1; - body.mass = body.inertia = body.density = Infinity; - body.inverseMass = body.inverseInertia = 0; - body.render.lineWidth = 1; - - 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; - } - }; - - /** - * Description - * @method resetForcesAll - * @param {body[]} bodies - */ - Body.resetForcesAll = function(bodies) { - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - // reset force buffers - body.force.x = 0; - body.force.y = 0; - body.torque = 0; - } - }; - - /** - * Description - * @method applyGravityAll - * @param {body[]} bodies - * @param {vector} gravity - */ - Body.applyGravityAll = function(bodies, gravity) { - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - if (body.isStatic || body.isSleeping) - continue; - - // apply gravity - body.force.y += body.mass * gravity.y * 0.001; - body.force.x += body.mass * gravity.x * 0.001; - } - }; - - /** - * Description - * @method updateAll - * @param {body[]} bodies - * @param {number} deltaTime - * @param {number} timeScale - * @param {number} correction - * @param {bounds} worldBounds - */ - Body.updateAll = function(bodies, deltaTime, timeScale, correction, worldBounds) { - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - if (body.isStatic || body.isSleeping) - continue; - - // don't update out of world bodies - // TODO: viewports - if (body.bounds.max.x < worldBounds.min.x || body.bounds.min.x > worldBounds.max.x - || body.bounds.max.y < worldBounds.min.y || body.bounds.min.y > worldBounds.max.y) - continue; - - Body.update(body, deltaTime, timeScale, correction); - } - }; - - /** - * Description - * @method update - * @param {body} body - * @param {number} deltaTime - * @param {number} timeScale - * @param {number} correction - */ - Body.update = function(body, deltaTime, timeScale, correction) { - var deltaTimeSquared = Math.pow(deltaTime * timeScale * body.timeScale, 2); - - // from the previous step - var frictionAir = 1 - body.frictionAir * timeScale * body.timeScale, - velocityPrevX = body.position.x - body.positionPrev.x, - velocityPrevY = body.position.y - body.positionPrev.y; - - // update velocity with verlet integration - body.velocity.x = (velocityPrevX * frictionAir * correction) + (body.force.x / body.mass) * deltaTimeSquared; - body.velocity.y = (velocityPrevY * frictionAir * correction) + (body.force.y / body.mass) * deltaTimeSquared; - - body.positionPrev.x = body.position.x; - body.positionPrev.y = body.position.y; - body.position.x += body.velocity.x; - body.position.y += body.velocity.y; - - // update angular velocity with verlet integration - body.angularVelocity = ((body.angle - body.anglePrev) * frictionAir * correction) + (body.torque / body.inertia) * deltaTimeSquared; - body.anglePrev = body.angle; - body.angle += body.angularVelocity; - - // track speed and acceleration - body.speed = Vector.magnitude(body.velocity); - 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); - } - Bounds.update(body.bounds, body.vertices, body.velocity); - }; - - /** - * Description - * @method applyForce - * @param {body} body - * @param {vector} position - * @param {vector} force - */ - Body.applyForce = function(body, position, force) { - body.force.x += force.x; - body.force.y += force.y; - var offset = { x: position.x - body.position.x, y: position.y - body.position.y }; - body.torque += (offset.x * force.y - offset.y * force.x) * body.inverseInertia; - }; - - /** - * Description - * @method translate - * @param {body} body - * @param {vector} translation - */ - Body.translate = function(body, translation) { - body.positionPrev.x += translation.x; - body.positionPrev.y += translation.y; - body.position.x += translation.x; - body.position.y += translation.y; - Vertices.translate(body.vertices, translation); - Bounds.update(body.bounds, body.vertices, body.velocity); - }; - - /** - * Description - * @method rotate - * @param {body} body - * @param {number} angle - */ - Body.rotate = function(body, angle) { - body.anglePrev += angle; - body.angle += angle; - Vertices.rotate(body.vertices, angle, body.position); - Axes.rotate(body.axes, angle); - Bounds.update(body.bounds, body.vertices, body.velocity); - }; - - /** - * Scales the body, including updating physical properties (mass, area, axes, inertia), from a point (default is centre) - * @method translate - * @param {body} body - * @param {number} scaleX - * @param {number} scaleY - * @param {vector} point - */ - Body.scale = function(body, scaleX, scaleY, point) { - // scale vertices - Vertices.scale(body.vertices, scaleX, scaleY, point); - - // update properties - body.axes = Axes.fromVertices(body.vertices); - body.area = Vertices.area(body.vertices); - body.mass = body.density * body.area; - body.inverseMass = 1 / body.mass; - - // update inertia (requires vertices to be at origin) - Vertices.translate(body.vertices, { x: -body.position.x, y: -body.position.y }); - body.inertia = Vertices.inertia(body.vertices, body.mass); - body.inverseInertia = 1 / body.inertia; - Vertices.translate(body.vertices, { x: body.position.x, y: body.position.y }); - - // update bounds - Bounds.update(body.bounds, body.vertices, body.velocity); - }; - -})(); - -; // End src/body/Body.js - - -// Begin src/body/Composite.js - -/** -* 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 Composite -*/ - -// TODO: composite translate, rotate - -var Composite = {}; - -(function() { - - /** - * Description - * @method create - * @param {} options - * @return {composite} A new composite - */ - Composite.create = function(options) { - return Common.extend({ - id: Common.nextId(), - type: 'composite', - parent: null, - isModified: false, - bodies: [], - constraints: [], - composites: [], - label: 'Composite' - }, options); - }; - - /** - * Sets the composite's `isModified` flag. - * If `updateParents` is true, all parents will be set (default: false). - * If `updateChildren` is true, all children will be set (default: false). - * @method setModified - * @param {composite} composite - * @param {boolean} isModified - * @param {boolean} updateParents - * @param {boolean} updateChildren - */ - Composite.setModified = function(composite, isModified, updateParents, updateChildren) { - composite.isModified = isModified; - - if (updateParents && composite.parent) { - Composite.setModified(composite.parent, isModified, updateParents, updateChildren); - } - - if (updateChildren) { - for(var i = 0; i < composite.composites.length; i++) { - var childComposite = composite.composites[i]; - Composite.setModified(childComposite, isModified, updateParents, updateChildren); - } - } - }; - - /** - * Generic add function. Adds one or many body(s), constraint(s) or a composite(s) to the given composite. - * @method add - * @param {composite} composite - * @param {} object - * @return {composite} The original composite with the objects added - */ - Composite.add = function(composite, object) { - var objects = [].concat(object); - - for (var i = 0; i < objects.length; i++) { - var obj = objects[i]; - - switch (obj.type) { - - case 'body': - Composite.addBody(composite, obj); - break; - case 'constraint': - Composite.addConstraint(composite, obj); - break; - case 'composite': - Composite.addComposite(composite, obj); - break; - case 'mouseConstraint': - Composite.addConstraint(composite, obj.constraint); - break; - - } - } - - return composite; - }; - - /** - * Generic remove function. Removes one or many body(s), constraint(s) or a composite(s) to the given composite. - * Optionally searching its children recursively. - * @method remove - * @param {composite} composite - * @param {} object - * @param {boolean} deep - * @return {composite} The original composite with the objects removed - */ - Composite.remove = function(composite, object, deep) { - var objects = [].concat(object); - - for (var i = 0; i < objects.length; i++) { - var obj = objects[i]; - - switch (obj.type) { - - case 'body': - Composite.removeBody(composite, obj, deep); - break; - case 'constraint': - Composite.removeConstraint(composite, obj, deep); - break; - case 'composite': - Composite.removeComposite(composite, obj, deep); - break; - case 'mouseConstraint': - Composite.removeConstraint(composite, obj.constraint); - break; - - } - } - - return composite; - }; - - /** - * Description - * @method addComposite - * @param {composite} compositeA - * @param {composite} compositeB - * @return {composite} The original compositeA with the objects from compositeB added - */ - Composite.addComposite = function(compositeA, compositeB) { - compositeA.composites.push(compositeB); - compositeB.parent = compositeA; - Composite.setModified(compositeA, true, true, false); - return compositeA; - }; - - /** - * Removes a composite from the given composite, and optionally searching its children recursively - * @method removeComposite - * @param {composite} compositeA - * @param {composite} compositeB - * @param {boolean} deep - * @return {composite} The original compositeA with the composite removed - */ - Composite.removeComposite = function(compositeA, compositeB, deep) { - var position = compositeA.composites.indexOf(compositeB); - if (position !== -1) { - Composite.removeCompositeAt(compositeA, position); - Composite.setModified(compositeA, true, true, false); - } - - if (deep) { - for (var i = 0; i < compositeA.composites.length; i++){ - Composite.removeComposite(compositeA.composites[i], compositeB, true); - } - } - - return compositeA; - }; - - /** - * Removes a composite from the given composite - * @method removeCompositeAt - * @param {composite} composite - * @param {number} position - * @return {composite} The original composite with the composite removed - */ - Composite.removeCompositeAt = function(composite, position) { - composite.composites.splice(position, 1); - Composite.setModified(composite, true, true, false); - return composite; - }; - - /** - * Description - * @method addBody - * @param {composite} composite - * @param {body} body - * @return {composite} The original composite with the body added - */ - Composite.addBody = function(composite, body) { - composite.bodies.push(body); - Composite.setModified(composite, true, true, false); - return composite; - }; - - /** - * Removes a body from the given composite, and optionally searching its children recursively - * @method removeBody - * @param {composite} composite - * @param {body} body - * @param {boolean} deep - * @return {composite} The original composite with the body removed - */ - Composite.removeBody = function(composite, body, deep) { - var position = composite.bodies.indexOf(body); - if (position !== -1) { - Composite.removeBodyAt(composite, position); - Composite.setModified(composite, true, true, false); - } - - if (deep) { - for (var i = 0; i < composite.composites.length; i++){ - Composite.removeBody(composite.composites[i], body, true); - } - } - - return composite; - }; - - /** - * Removes a body from the given composite - * @method removeBodyAt - * @param {composite} composite - * @param {number} position - * @return {composite} The original composite with the body removed - */ - Composite.removeBodyAt = function(composite, position) { - composite.bodies.splice(position, 1); - Composite.setModified(composite, true, true, false); - return composite; - }; - - /** - * Description - * @method addConstraint - * @param {composite} composite - * @param {constraint} constraint - * @return {composite} The original composite with the constraint added - */ - Composite.addConstraint = function(composite, constraint) { - composite.constraints.push(constraint); - Composite.setModified(composite, true, true, false); - return composite; - }; - - /** - * Removes a constraint from the given composite, and optionally searching its children recursively - * @method removeConstraint - * @param {composite} composite - * @param {constraint} constraint - * @param {boolean} deep - * @return {composite} The original composite with the constraint removed - */ - Composite.removeConstraint = function(composite, constraint, deep) { - var position = composite.constraints.indexOf(constraint); - if (position !== -1) { - Composite.removeConstraintAt(composite, position); - } - - if (deep) { - for (var i = 0; i < composite.composites.length; i++){ - Composite.removeConstraint(composite.composites[i], constraint, true); - } - } - - return composite; - }; - - /** - * Removes a body from the given composite - * @method removeConstraintAt - * @param {composite} composite - * @param {number} position - * @return {composite} The original composite with the constraint removed - */ - Composite.removeConstraintAt = function(composite, position) { - composite.constraints.splice(position, 1); - Composite.setModified(composite, true, true, false); - return composite; - }; - - /** - * Removes all bodies, constraints and composites from the given composite - * Optionally clearing its children recursively - * @method clear - * @param {world} world - * @param {boolean} keepStatic - * @param {boolean} deep - */ - Composite.clear = function(composite, keepStatic, deep) { - if (deep) { - for (var i = 0; i < composite.composites.length; i++){ - Composite.clear(composite.composites[i], keepStatic, true); - } - } - - if (keepStatic) { - composite.bodies = composite.bodies.filter(function(body) { return body.isStatic; }); - } else { - composite.bodies.length = 0; - } - - composite.constraints.length = 0; - composite.composites.length = 0; - Composite.setModified(composite, true, true, false); - - return composite; - }; - - /** - * Returns all bodies in the given composite, including all bodies in its children, recursively - * @method allBodies - * @param {composite} composite - * @return {body[]} All the bodies - */ - Composite.allBodies = function(composite) { - var bodies = [].concat(composite.bodies); - - for (var i = 0; i < composite.composites.length; i++) - bodies = bodies.concat(Composite.allBodies(composite.composites[i])); - - return bodies; - }; - - /** - * Returns all constraints in the given composite, including all constraints in its children, recursively - * @method allConstraints - * @param {composite} composite - * @return {constraint[]} All the constraints - */ - Composite.allConstraints = function(composite) { - var constraints = [].concat(composite.constraints); - - for (var i = 0; i < composite.composites.length; i++) - constraints = constraints.concat(Composite.allConstraints(composite.composites[i])); - - return constraints; - }; - - /** - * Returns all composites in the given composite, including all composites in its children, recursively - * @method allComposites - * @param {composite} composite - * @return {composite[]} All the composites - */ - Composite.allComposites = function(composite) { - var composites = [].concat(composite.composites); - - for (var i = 0; i < composite.composites.length; i++) - composites = composites.concat(Composite.allComposites(composite.composites[i])); - - return composites; - }; - - /** - * Searches the composite recursively for an object matching the type and id supplied, null if not found - * @method get - * @param {composite} composite - * @param {number} id - * @param {string} type - * @return {object} The requested object, if found - */ - Composite.get = function(composite, id, type) { - var objects, - object; - - switch (type) { - case 'body': - objects = Composite.allBodies(composite); - break; - case 'constraint': - objects = Composite.allConstraints(composite); - break; - case 'composite': - objects = Composite.allComposites(composite).concat(composite); - break; - } - - if (!objects) - return null; - - object = objects.filter(function(object) { - return object.id.toString() === id.toString(); - }); - - return object.length === 0 ? null : object[0]; - }; - - /** - * Moves the given object(s) from compositeA to compositeB (equal to a remove followed by an add) - * @method move - * @param {compositeA} compositeA - * @param {object[]} objects - * @param {compositeB} compositeB - * @return {composite} Returns compositeA - */ - Composite.move = function(compositeA, objects, compositeB) { - Composite.remove(compositeA, objects); - Composite.add(compositeB, objects); - return compositeA; - }; - - /** - * Assigns new ids for all objects in the composite, recursively - * @method rebase - * @param {composite} composite - * @return {composite} Returns composite - */ - Composite.rebase = function(composite) { - var objects = Composite.allBodies(composite) - .concat(Composite.allConstraints(composite)) - .concat(Composite.allComposites(composite)); - - for (var i = 0; i < objects.length; i++) { - objects[i].id = Common.nextId(); - } - - Composite.setModified(composite, true, true, false); - - return composite; - }; - -})(); - -; // End src/body/Composite.js - - -// Begin src/body/World.js - -/** -* 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 World -*/ - -var World = {}; - -(function() { - - /** - * Description - * @method create - * @constructor - * @param {} options - * @return {world} A new world - */ - World.create = function(options) { - var composite = Composite.create(); - - var defaults = { - label: 'World', - gravity: { x: 0, y: 1 }, - bounds: { - min: { x: 0, y: 0 }, - max: { x: 800, y: 600 } - } - }; - - return Common.extend(composite, defaults, options); - }; - - // World is a Composite body - // see src/module/Outro.js for these aliases: - - /** - * An alias for Composite.clear since World is also a Composite (see Outro.js) - * @method clear - * @param {world} world - * @param {boolean} keepStatic - */ - - /** - * An alias for Composite.add since World is also a Composite (see Outro.js) - * @method addComposite - * @param {world} world - * @param {composite} composite - * @return {world} The original world with the objects from composite added - */ - - /** - * An alias for Composite.addBody since World is also a Composite (see Outro.js) - * @method addBody - * @param {world} world - * @param {body} body - * @return {world} The original world with the body added - */ - - /** - * An alias for Composite.addConstraint since World is also a Composite (see Outro.js) - * @method addConstraint - * @param {world} world - * @param {constraint} constraint - * @return {world} The original world with the constraint added - */ - -})(); - -; // End src/body/World.js - - -// Begin src/collision/Contact.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Contact -*/ - -var Contact = {}; - -(function() { - - /** - * Description - * @method create - * @param {vertex} vertex - * @return {contact} A new contact - */ - Contact.create = function(vertex) { - return { - id: Contact.id(vertex), - vertex: vertex, - normalImpulse: 0, - tangentImpulse: 0 - }; - }; - - /** - * Description - * @method id - * @param {vertex} vertex - * @return {Number} Unique contactID - */ - Contact.id = function(vertex) { - return vertex.body.id + '_' + vertex.index; - }; - -})(); - -; // End src/collision/Contact.js - - -// Begin src/collision/Detector.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Detector -*/ - -// TODO: speculative contacts - -var Detector = {}; - -(function() { - - /** - * Description - * @method collisions - * @param {pair[]} broadphasePairs - * @param {engine} engine - * @return {array} collisions - */ - 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]; - - // NOTE: could share a function for the below, but may drop performance? - - if (bodyA.groupId && bodyB.groupId && bodyA.groupId === bodyB.groupId) - continue; - - if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping)) - continue; - - metrics.midphaseTests += 1; - - // mid phase - if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) { - - // find a previous collision we could reuse - var pairId = Pair.id(bodyA, bodyB), - pair = pairsTable[pairId], - previousCollision; - - if (pair && pair.isActive) { - previousCollision = pair.collision; - } else { - previousCollision = null; - } - - // 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; - } - } - } - - return collisions; - }; - - /** - * Description - * @method bruteForce - * @param {body[]} bodies - * @param {engine} engine - * @return {array} collisions - */ - Detector.bruteForce = function(bodies, engine) { - var collisions = [], - metrics = engine.metrics, - pairsTable = engine.pairs.table; - - for (var i = 0; i < bodies.length; i++) { - for (var j = i + 1; j < bodies.length; j++) { - var bodyA = bodies[i], - bodyB = bodies[j]; - - // NOTE: could share a function for the below, but may drop performance? - - if (bodyA.groupId && bodyB.groupId && bodyA.groupId === bodyB.groupId) - continue; - - if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping)) - continue; - - metrics.midphaseTests += 1; - - // mid phase - if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) { - - // find a previous collision we could reuse - var pairId = Pair.id(bodyA, bodyB), - pair = pairsTable[pairId], - previousCollision; - - if (pair && pair.isActive) { - previousCollision = pair.collision; - } else { - previousCollision = null; - } - - // 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; - } - } - } - } - - return collisions; - }; - -})(); - -; // End src/collision/Detector.js - - -// Begin src/collision/Grid.js - -/** -* 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 Grid -*/ - -var Grid = {}; - -(function() { - - /** - * Description - * @method create - * @param {number} bucketWidth - * @param {number} bucketHeight - * @return {grid} A new grid - */ - Grid.create = function(bucketWidth, bucketHeight) { - return { - buckets: {}, - pairs: {}, - pairsList: [], - bucketWidth: bucketWidth || 48, - bucketHeight: bucketHeight || 48 - }; - }; - - /** - * Description - * @method update - * @param {grid} grid - * @param {body[]} bodies - * @param {engine} engine - * @param {boolean} forceUpdate - */ - Grid.update = function(grid, bodies, engine, forceUpdate) { - var i, col, row, - world = engine.world, - buckets = grid.buckets, - bucket, - bucketId, - metrics = engine.metrics, - gridChanged = false; - - metrics.broadphaseTests = 0; - - for (i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - if (body.isSleeping && !forceUpdate) - continue; - - // don't update out of world bodies - if (body.bounds.max.x < 0 || body.bounds.min.x > world.bounds.width - || body.bounds.max.y < 0 || body.bounds.min.y > world.bounds.height) - continue; - - var newRegion = _getRegion(grid, body); - - // 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; - - var union = _regionUnion(newRegion, body.region); - - // update grid buckets affected by region change - // iterate over the union of both regions - for (col = union.startCol; col <= union.endCol; col++) { - for (row = union.startRow; row <= union.endRow; row++) { - bucketId = _getBucketId(col, row); - bucket = buckets[bucketId]; - - var isInsideNewRegion = (col >= newRegion.startCol && col <= newRegion.endCol - && row >= newRegion.startRow && row <= newRegion.endRow); - - var isInsideOldRegion = (col >= body.region.startCol && col <= body.region.endCol - && row >= body.region.startRow && row <= body.region.endRow); - - // remove from old region buckets - if (!isInsideNewRegion && isInsideOldRegion) { - if (isInsideOldRegion) { - if (bucket) - _bucketRemoveBody(grid, bucket, body); - } - } - - // add to new region buckets - if (body.region === newRegion || (isInsideNewRegion && !isInsideOldRegion) || forceUpdate) { - if (!bucket) - bucket = _createBucket(buckets, bucketId); - _bucketAddBody(grid, bucket, body); - } - } - } - - // set the new region - body.region = newRegion; - - // flag changes so we can update pairs - gridChanged = true; - } - } - - // update pairs list only if pairs changed (i.e. a body changed region) - if (gridChanged) - grid.pairsList = _createActivePairsList(grid); - }; - - /** - * Description - * @method clear - * @param {grid} grid - */ - Grid.clear = function(grid) { - grid.buckets = {}; - grid.pairs = {}; - grid.pairsList = []; - }; - - /** - * Description - * @method _regionUnion - * @private - * @param {} regionA - * @param {} regionB - * @return CallExpression - */ - var _regionUnion = function(regionA, regionB) { - var startCol = Math.min(regionA.startCol, regionB.startCol), - endCol = Math.max(regionA.endCol, regionB.endCol), - startRow = Math.min(regionA.startRow, regionB.startRow), - endRow = Math.max(regionA.endRow, regionB.endRow); - - return _createRegion(startCol, endCol, startRow, endRow); - }; - - /** - * Description - * @method _getRegion - * @private - * @param {} grid - * @param {} body - * @return CallExpression - */ - var _getRegion = function(grid, body) { - var bounds = body.bounds, - startCol = Math.floor(bounds.min.x / grid.bucketWidth), - endCol = Math.floor(bounds.max.x / grid.bucketWidth), - startRow = Math.floor(bounds.min.y / grid.bucketHeight), - endRow = Math.floor(bounds.max.y / grid.bucketHeight); - - return _createRegion(startCol, endCol, startRow, endRow); - }; - - /** - * Description - * @method _createRegion - * @private - * @param {} startCol - * @param {} endCol - * @param {} startRow - * @param {} endRow - * @return ObjectExpression - */ - var _createRegion = function(startCol, endCol, startRow, endRow) { - return { - id: startCol + ',' + endCol + ',' + startRow + ',' + endRow, - startCol: startCol, - endCol: endCol, - startRow: startRow, - endRow: endRow - }; - }; - - /** - * Description - * @method _getBucketId - * @private - * @param {} column - * @param {} row - * @return BinaryExpression - */ - var _getBucketId = function(column, row) { - return column + ',' + row; - }; - - /** - * Description - * @method _createBucket - * @private - * @param {} buckets - * @param {} bucketId - * @return bucket - */ - var _createBucket = function(buckets, bucketId) { - var bucket = buckets[bucketId] = []; - return bucket; - }; - - /** - * Description - * @method _bucketAddBody - * @private - * @param {} grid - * @param {} bucket - * @param {} body - */ - var _bucketAddBody = function(grid, bucket, body) { - // add new pairs - for (var i = 0; i < bucket.length; i++) { - var bodyB = bucket[i]; - - if (body.id === bodyB.id || (body.isStatic && bodyB.isStatic)) - continue; - - // keep track of the number of buckets the pair exists in - // important for Grid.update to work - var pairId = Pair.id(body, bodyB), - pair = grid.pairs[pairId]; - - if (pair) { - pair[2] += 1; - } else { - grid.pairs[pairId] = [body, bodyB, 1]; - } - } - - // add to bodies (after pairs, otherwise pairs with self) - bucket.push(body); - }; - - /** - * Description - * @method _bucketRemoveBody - * @private - * @param {} grid - * @param {} bucket - * @param {} body - */ - var _bucketRemoveBody = function(grid, bucket, body) { - // remove from bucket - bucket.splice(bucket.indexOf(body), 1); - - // update pair counts - for (var i = 0; i < bucket.length; i++) { - // keep track of the number of buckets the pair exists in - // important for _createActivePairsList to work - var bodyB = bucket[i], - pairId = Pair.id(body, bodyB), - pair = grid.pairs[pairId]; - - if (pair) - pair[2] -= 1; - } - }; - - /** - * Description - * @method _createActivePairsList - * @private - * @param {} grid - * @return pairs - */ - var _createActivePairsList = function(grid) { - var pairKeys, - pair, - pairs = []; - - // grid.pairs is used as a hashmap - pairKeys = Common.keys(grid.pairs); - - // iterate over grid.pairs - for (var k = 0; k < pairKeys.length; k++) { - pair = grid.pairs[pairKeys[k]]; - - // if pair exists in at least one bucket - // it is a pair that needs further collision testing so push it - if (pair[2] > 0) { - pairs.push(pair); - } else { - delete grid.pairs[pairKeys[k]]; - } - } - - return pairs; - }; - -})(); - -; // End src/collision/Grid.js - - -// Begin src/collision/Pair.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Pair -*/ - -var Pair = {}; - -(function() { - - /** - * Description - * @method create - * @param {collision} collision - * @return {pair} A new pair - */ - Pair.create = function(collision, timestamp) { - var bodyA = collision.bodyA, - bodyB = collision.bodyB; - - var pair = { - id: Pair.id(bodyA, bodyB), - bodyA: bodyA, - bodyB: bodyB, - contacts: {}, - activeContacts: [], - separation: 0, - 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) - }; - - Pair.update(pair, collision, timestamp); - - return pair; - }; - - /** - * Description - * @method update - * @param {pair} pair - * @param {collision} collision - */ - Pair.update = function(pair, collision, timestamp) { - var contacts = pair.contacts, - supports = collision.supports, - activeContacts = pair.activeContacts; - - pair.collision = collision; - activeContacts.length = 0; - - if (collision.collided) { - for (var i = 0; i < supports.length; i++) { - var support = supports[i], - contactId = Contact.id(support), - contact = contacts[contactId]; - - if (contact) { - activeContacts.push(contact); - } else { - activeContacts.push(contacts[contactId] = Contact.create(support)); - } - } - - pair.separation = collision.depth; - Pair.setActive(pair, true, timestamp); - } else { - if (pair.isActive === true) - Pair.setActive(pair, false, timestamp); - } - }; - - /** - * Description - * @method setActive - * @param {pair} pair - * @param {bool} isActive - */ - Pair.setActive = function(pair, isActive, timestamp) { - if (isActive) { - pair.isActive = true; - pair.timeUpdated = timestamp; - } else { - pair.isActive = false; - pair.activeContacts.length = 0; - } - }; - - /** - * Description - * @method id - * @param {body} bodyA - * @param {body} bodyB - * @return {number} Unique pairId - */ - Pair.id = function(bodyA, bodyB) { - if (bodyA.id < bodyB.id) { - return bodyA.id + '_' + bodyB.id; - } else { - return bodyB.id + '_' + bodyA.id; - } - }; - -})(); - -; // End src/collision/Pair.js - - -// Begin src/collision/Pairs.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Pairs -*/ - -var Pairs = {}; - -(function() { - - var _pairMaxIdleLife = 1000; - - /** - * Creates a new pairs structure - * @method create - * @param {object} options - * @return {pairs} A new pairs structure - */ - Pairs.create = function(options) { - return Common.extend({ - table: {}, - list: [], - collisionStart: [], - collisionActive: [], - collisionEnd: [] - }, options); - }; - - /** - * Description - * @method update - * @param {object} pairs - * @param {collision[]} collisions - */ - Pairs.update = function(pairs, collisions, timestamp) { - var pairsList = pairs.list, - pairsTable = pairs.table, - collisionStart = pairs.collisionStart, - collisionEnd = pairs.collisionEnd, - collisionActive = pairs.collisionActive, - activePairIds = [], - collision, - pairId, - pair, - i; - - // clear collision state arrays, but maintain old reference - collisionStart.length = 0; - collisionEnd.length = 0; - collisionActive.length = 0; - - for (i = 0; i < collisions.length; i++) { - collision = collisions[i]; - - if (collision.collided) { - pairId = Pair.id(collision.bodyA, collision.bodyB); - activePairIds.push(pairId); - - pair = pairsTable[pairId]; - - if (pair) { - // pair already exists (but may or may not be active) - if (pair.isActive) { - // pair exists and is active - collisionActive.push(pair); - } else { - // pair exists but was inactive, so a collision has just started again - collisionStart.push(pair); - } - - // update the pair - Pair.update(pair, collision, timestamp); - } else { - // pair did not exist, create a new pair - pair = Pair.create(collision, timestamp); - pairsTable[pairId] = pair; - - // push the new pair - collisionStart.push(pair); - pairsList.push(pair); - } - } - } - - // deactivate previously active pairs that are now inactive - for (i = 0; i < pairsList.length; i++) { - pair = pairsList[i]; - if (pair.isActive && activePairIds.indexOf(pair.id) === -1) { - Pair.setActive(pair, false, timestamp); - collisionEnd.push(pair); - } - } - }; - - /** - * Description - * @method removeOld - * @param {object} pairs - */ - Pairs.removeOld = function(pairs, timestamp) { - var pairsList = pairs.list, - pairsTable = pairs.table, - indexesToRemove = [], - pair, - collision, - pairIndex, - i; - - for (i = 0; i < pairsList.length; i++) { - pair = pairsList[i]; - collision = pair.collision; - - // never remove sleeping pairs - if (collision.bodyA.isSleeping || collision.bodyB.isSleeping) { - pair.timeUpdated = timestamp; - continue; - } - - // if pair is inactive for too long, mark it to be removed - if (timestamp - pair.timeUpdated > _pairMaxIdleLife) { - indexesToRemove.push(i); - } - } - - // remove marked pairs - for (i = 0; i < indexesToRemove.length; i++) { - pairIndex = indexesToRemove[i] - i; - pair = pairsList[pairIndex]; - delete pairsTable[pair.id]; - pairsList.splice(pairIndex, 1); - } - }; - - /** - * Clears the given pairs structure - * @method create - * @param {object} options - * @param {pairs} pairs - */ - Pairs.clear = function(pairs) { - pairs.table = {}; - pairs.list.length = 0; - pairs.collisionStart.length = 0; - pairs.collisionActive.length = 0; - pairs.collisionEnd.length = 0; - return pairs; - }; - -})(); - -; // End src/collision/Pairs.js - - -// Begin src/collision/Query.js - -/** -* Functions for performing collision queries -* -* @class Query -*/ - -var Query = {}; - -(function() { - - /** - * Casts a ray segment against a set of bodies and returns all collisions, ray width is optional. Intersection points are not provided. - * @method ray - * @param {body[]} bodies - * @param {vector} startPoint - * @param {vector} endPoint - * @return {object[]} Collisions - */ - Query.ray = function(bodies, startPoint, endPoint, rayWidth) { - rayWidth = rayWidth || Number.MIN_VALUE; - - var rayAngle = Vector.angle(startPoint, endPoint), - rayLength = Vector.magnitude(Vector.sub(startPoint, endPoint)), - rayX = (endPoint.x + startPoint.x) * 0.5, - rayY = (endPoint.y + startPoint.y) * 0.5, - ray = Bodies.rectangle(rayX, rayY, rayLength, rayWidth, { angle: rayAngle }), - collisions = []; - - 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); - } - } - } - - return collisions; - }; - - /** - * Returns all bodies whose bounds are inside (or outside if set) the given set of bounds, from the given set of bodies - * @method region - * @param {body[]} bodies - * @param {bounds} bounds - * @param {bool} outside - * @return {body[]} The bodies matching the query - */ - Query.region = function(bodies, bounds, outside) { - var result = []; - - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i], - overlaps = Bounds.overlaps(body.bounds, bounds); - if ((overlaps && !outside) || (!overlaps && outside)) - result.push(body); - } - - return result; - }; - -})(); - -; // End src/collision/Query.js - - -// Begin src/collision/Resolver.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Resolver -*/ - -var Resolver = {}; - -(function() { - - var _restingThresh = 4, - _positionDampen = 0.2, - _positionWarming = 0.6; - - /** - * Description - * @method solvePosition - * @param {pair[]} pairs - * @param {number} timeScale - */ - Resolver.solvePosition = function(pairs, timeScale) { - var i, - pair, - collision, - bodyA, - bodyB, - vertex, - vertexCorrected, - normal, - bodyBtoA; - - // find impulses required to resolve penetration - for (i = 0; i < pairs.length; i++) { - pair = pairs[i]; - - if (!pair.isActive) - continue; - - collision = pair.collision; - bodyA = collision.bodyA; - bodyB = collision.bodyB; - vertex = collision.supports[0]; - vertexCorrected = collision.supportCorrected; - 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)); - - pair.separation = Vector.dot(normal, bodyBtoA); - } - - for (i = 0; i < pairs.length; i++) { - pair = pairs[i]; - - if (!pair.isActive) - continue; - - collision = pair.collision; - bodyA = collision.bodyA; - bodyB = collision.bodyB; - normal = collision.normal; - positionImpulse = ((pair.separation * _positionDampen) - 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; - } - - if (!(bodyB.isStatic || bodyB.isSleeping)) { - bodyB.positionImpulse.x -= normal.x * positionImpulse; - bodyB.positionImpulse.y -= normal.y * positionImpulse; - } - } - }; - - /** - * Description - * @method postSolvePosition - * @param {body[]} bodies - */ - Resolver.postSolvePosition = function(bodies) { - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - if (body.positionImpulse.x !== 0 || body.positionImpulse.y !== 0) { - // 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; - } - } - }; - - /** - * Description - * @method preSolveVelocity - * @param {pair[]} pairs - */ - Resolver.preSolveVelocity = function(pairs) { - var impulse = {}, - i, - j, - pair, - contacts, - collision, - bodyA, - bodyB, - normal, - tangent, - contact, - contactVertex, - normalImpulse, - tangentImpulse, - offset; - - for (i = 0; i < pairs.length; i++) { - pair = pairs[i]; - - if (!pair.isActive) - continue; - - contacts = pair.activeContacts; - collision = pair.collision; - bodyA = collision.bodyA; - bodyB = collision.bodyB; - normal = collision.normal; - tangent = collision.tangent; - - // resolve each contact - for (j = 0; j < contacts.length; j++) { - contact = contacts[j]; - contactVertex = contact.vertex; - normalImpulse = contact.normalImpulse; - tangentImpulse = contact.tangentImpulse; - - // 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; - } - } - } - }; - - /** - * Description - * @method solveVelocity - * @param {pair[]} pairs - */ - Resolver.solveVelocity = function(pairs, timeScale) { - var impulse = {}, - timeScaleSquared = timeScale * timeScale; - - for (var i = 0; i < pairs.length; i++) { - var pair = pairs[i]; - - if (!pair.isActive) - continue; - - var collision = pair.collision, - bodyA = collision.bodyA, - bodyB = collision.bodyB, - normal = collision.normal, - tangent = collision.tangent, - contacts = pair.activeContacts, - contactShare = 1 / contacts.length; - - // update body velocities - bodyA.velocity.x = bodyA.position.x - bodyA.positionPrev.x; - bodyA.velocity.y = bodyA.position.y - bodyA.positionPrev.y; - bodyB.velocity.x = bodyB.position.x - bodyB.positionPrev.x; - bodyB.velocity.y = bodyB.position.y - bodyB.positionPrev.y; - bodyA.angularVelocity = bodyA.angle - bodyA.anglePrev; - bodyB.angularVelocity = bodyB.angle - bodyB.anglePrev; - - // resolve each contact - 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), - normalVelocity = Vector.dot(normal, relativeVelocity); - - var tangentVelocity = Vector.dot(tangent, relativeVelocity), - tangentSpeed = Math.abs(tangentVelocity), - tangentVelocityDirection = Common.sign(tangentVelocity); - - // raw impulses - var normalImpulse = (1 + pair.restitution) * normalVelocity, - normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1); - - // coulomb friction - var tangentImpulse = tangentVelocity; - if (tangentSpeed > normalForce * pair.friction * timeScaleSquared) - tangentImpulse = normalForce * pair.friction * timeScaleSquared * tangentVelocityDirection; - - // 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; - - // handle high velocity and resting collisions separately - if (normalVelocity < 0 && normalVelocity * normalVelocity > _restingThresh * timeScaleSquared) { - // high velocity so clear cached contact impulse - contact.normalImpulse = 0; - contact.tangentImpulse = 0; - } else { - // solve resting collision constraints using Erin Catto's method (GDC08) - - // impulse constraint, tends to 0 - var contactNormalImpulse = contact.normalImpulse; - contact.normalImpulse = Math.min(contact.normalImpulse + normalImpulse, 0); - normalImpulse = contact.normalImpulse - contactNormalImpulse; - - // tangent impulse, tends to -maxFriction or maxFriction - var contactTangentImpulse = contact.tangentImpulse; - contact.tangentImpulse = Common.clamp(contact.tangentImpulse + tangentImpulse, -tangentSpeed, tangentSpeed); - tangentImpulse = contact.tangentImpulse - contactTangentImpulse; - } - - // 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)) { - bodyA.positionPrev.x += impulse.x * bodyA.inverseMass; - bodyA.positionPrev.y += impulse.y * bodyA.inverseMass; - bodyA.anglePrev += Vector.cross(offsetA, impulse) * bodyA.inverseInertia; - } - - if (!(bodyB.isStatic || bodyB.isSleeping)) { - bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass; - bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass; - bodyB.anglePrev -= Vector.cross(offsetB, impulse) * bodyB.inverseInertia; - } - } - } - }; - -})(); - -; // End src/collision/Resolver.js - - -// Begin src/collision/SAT.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class SAT -*/ - -// TODO: true circles and curves - -var SAT = {}; - -(function() { - - /** - * Description - * @method collides - * @param {body} bodyA - * @param {body} bodyB - * @param {collision} previousCollision - * @return {collision} collision - */ - SAT.collides = function(bodyA, bodyB, previousCollision) { - var overlapAB, - overlapBA, - minOverlap, - collision, - prevCol = previousCollision, - canReusePrevCol = false; - - if (prevCol) { - // estimate total motion - var motion = bodyA.speed * bodyA.speed + bodyA.angularSpeed * bodyA.angularSpeed - + bodyB.speed * bodyB.speed + bodyB.angularSpeed * bodyB.angularSpeed; - - // we may be able to (partially) reuse collision result - // but only safe if collision was resting - canReusePrevCol = prevCol && prevCol.collided && motion < 0.2; - - // reuse collision object - collision = prevCol; - } else { - collision = { collided: false, bodyA: bodyA, bodyB: bodyB }; - } - - 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]]; - - minOverlap = _overlapAxes(prevCol.bodyA.vertices, prevCol.bodyB.vertices, axes); - collision.reused = true; - - if (minOverlap.overlap <= 0) { - collision.collided = false; - return collision; - } - } else { - // if we can't reuse a result, perform a full SAT test - - overlapAB = _overlapAxes(bodyA.vertices, bodyB.vertices, bodyA.axes); - - if (overlapAB.overlap <= 0) { - collision.collided = false; - return collision; - } - - overlapBA = _overlapAxes(bodyB.vertices, bodyA.vertices, bodyB.axes); - - if (overlapBA.overlap <= 0) { - collision.collided = false; - return collision; - } - - if (overlapAB.overlap < overlapBA.overlap) { - minOverlap = overlapAB; - collision.bodyA = bodyA; - collision.bodyB = bodyB; - } else { - minOverlap = overlapBA; - collision.bodyA = bodyB; - collision.bodyB = bodyA; - } - - // important for reuse later - collision.axisNumber = minOverlap.axisNumber; - } - - collision.collided = true; - collision.normal = minOverlap.axis; - collision.depth = minOverlap.overlap; - - bodyA = collision.bodyA; - bodyB = collision.bodyB; - - // ensure normal is facing away from bodyA - if (Vector.dot(collision.normal, Vector.sub(bodyB.position, bodyA.position)) > 0) - collision.normal = Vector.neg(collision.normal); - - collision.tangent = Vector.perp(collision.normal); - - collision.penetration = { - x: collision.normal.x * collision.depth, - y: collision.normal.y * collision.depth - }; - - // find support points, there is always either exactly one or two - var verticesB = _findSupports(bodyA, bodyB, collision.normal), - supports = [verticesB[0]]; - - if (Vertices.contains(bodyA.vertices, verticesB[1])) { - supports.push(verticesB[1]); - } else { - var verticesA = _findSupports(bodyB, bodyA, Vector.neg(collision.normal)); - - if (Vertices.contains(bodyB.vertices, verticesA[0])) { - supports.push(verticesA[0]); - } - - if (supports.length < 2 && Vertices.contains(bodyB.vertices, verticesA[1])) { - supports.push(verticesA[1]); - } - } - - collision.supports = supports; - collision.supportCorrected = Vector.sub(verticesB[0], collision.penetration); - - return collision; - }; - - /** - * Description - * @method _overlapAxes - * @private - * @param {} verticesA - * @param {} verticesB - * @param {} axes - * @return result - */ - var _overlapAxes = function(verticesA, verticesB, axes) { - var projectionA = {}, - projectionB = {}, - result = { overlap: Number.MAX_VALUE }, - overlap, - axis; - - for (var i = 0; i < axes.length; i++) { - axis = axes[i]; - - _projectToAxis(projectionA, verticesA, axis); - _projectToAxis(projectionB, verticesB, axis); - - overlap = projectionA.min < projectionB.min - ? projectionA.max - projectionB.min - : projectionB.max - projectionA.min; - - if (overlap <= 0) { - result.overlap = overlap; - return result; - } - - if (overlap < result.overlap) { - result.overlap = overlap; - result.axis = axis; - result.axisNumber = i; - } - } - - return result; - }; - - /** - * Description - * @method _projectToAxis - * @private - * @param {} projection - * @param {} vertices - * @param {} axis - */ - var _projectToAxis = function(projection, vertices, axis) { - var min = Vector.dot(vertices[0], axis), - max = min; - - for (var i = 1; i < vertices.length; i += 1) { - var dot = Vector.dot(vertices[i], axis); - - if (dot > max) { - max = dot; - } else if (dot < min) { - min = dot; - } - } - - projection.min = min; - projection.max = max; - }; - - /** - * Description - * @method _findSupports - * @private - * @param {} bodyA - * @param {} bodyB - * @param {} normal - * @return ArrayExpression - */ - var _findSupports = function(bodyA, bodyB, normal) { - var nearestDistance = Number.MAX_VALUE, - vertexToBody = { x: 0, y: 0 }, - vertices = bodyB.vertices, - bodyAPosition = bodyA.position, - distance, - vertex, - vertexA = vertices[0], - vertexB = vertices[1]; - - // find closest vertex on bodyB - for (var i = 0; i < vertices.length; i++) { - vertex = vertices[i]; - vertexToBody.x = vertex.x - bodyAPosition.x; - vertexToBody.y = vertex.y - bodyAPosition.y; - distance = -Vector.dot(normal, vertexToBody); - - if (distance < nearestDistance) { - nearestDistance = distance; - vertexA = vertex; - } - } - - // find next closest vertex using the two connected to it - var prevIndex = vertexA.index - 1 >= 0 ? vertexA.index - 1 : vertices.length - 1; - vertex = vertices[prevIndex]; - vertexToBody.x = vertex.x - bodyAPosition.x; - vertexToBody.y = vertex.y - bodyAPosition.y; - nearestDistance = -Vector.dot(normal, vertexToBody); - vertexB = vertex; - - var nextIndex = (vertexA.index + 1) % vertices.length; - vertex = vertices[nextIndex]; - vertexToBody.x = vertex.x - bodyAPosition.x; - vertexToBody.y = vertex.y - bodyAPosition.y; - distance = -Vector.dot(normal, vertexToBody); - if (distance < nearestDistance) { - nearestDistance = distance; - vertexB = vertex; - } - - return [vertexA, vertexB]; - }; - -})(); - -; // End src/collision/SAT.js - - -// Begin src/constraint/Constraint.js - -/** -* 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 Constraint -*/ - -// TODO: fix instabillity issues with torque -// TODO: linked constraints -// TODO: breakable constraints -// TODO: collidable constraints -// TODO: allow constrained bodies to sleep -// TODO: handle 0 length constraints properly -// TODO: impulse caching and warming - -var Constraint = {}; - -(function() { - - var _minLength = 0.000001, - _minDifference = 0.001; - - /** - * Description - * @method create - * @param {} options - * @return {constraint} constraint - */ - Constraint.create = function(options) { - var constraint = options; - - // if bodies defined but no points, use body centre - if (constraint.bodyA && !constraint.pointA) - constraint.pointA = { x: 0, y: 0 }; - if (constraint.bodyB && !constraint.pointB) - constraint.pointB = { x: 0, y: 0 }; - - // calculate static length using initial world space points - var initialPointA = constraint.bodyA ? Vector.add(constraint.bodyA.position, constraint.pointA) : constraint.pointA, - initialPointB = constraint.bodyB ? Vector.add(constraint.bodyB.position, constraint.pointB) : constraint.pointB, - length = Vector.magnitude(Vector.sub(initialPointA, initialPointB)); - - constraint.length = constraint.length || length || _minLength; - - // render - var render = { - visible: true, - lineWidth: 2, - strokeStyle: '#666' - }; - - constraint.render = Common.extend(render, constraint.render); - - // option defaults - constraint.id = constraint.id || Common.nextId(); - constraint.label = constraint.label || 'Constraint'; - constraint.type = 'constraint'; - constraint.stiffness = constraint.stiffness || 1; - constraint.angularStiffness = constraint.angularStiffness || 0; - constraint.angleA = constraint.bodyA ? constraint.bodyA.angle : constraint.angleA; - constraint.angleB = constraint.bodyB ? constraint.bodyB.angle : constraint.angleB; - - return constraint; - }; - - /** - * Description - * @method solveAll - * @param {constraint[]} constraints - * @param {number} timeScale - */ - Constraint.solveAll = function(constraints, timeScale) { - for (var i = 0; i < constraints.length; i++) { - Constraint.solve(constraints[i], timeScale); - } - }; - - /** - * Description - * @method solve - * @param {constraint} constraint - * @param {number} timeScale - */ - Constraint.solve = function(constraint, timeScale) { - var bodyA = constraint.bodyA, - bodyB = constraint.bodyB, - pointA = constraint.pointA, - pointB = constraint.pointB; - - // update reference angle - if (bodyA && !bodyA.isStatic) { - constraint.pointA = Vector.rotate(pointA, bodyA.angle - constraint.angleA); - constraint.angleA = bodyA.angle; - } - - // update reference angle - if (bodyB && !bodyB.isStatic) { - constraint.pointB = Vector.rotate(pointB, bodyB.angle - constraint.angleB); - constraint.angleB = bodyB.angle; - } - - var pointAWorld = pointA, - pointBWorld = pointB; - - if (bodyA) pointAWorld = Vector.add(bodyA.position, pointA); - if (bodyB) pointBWorld = Vector.add(bodyB.position, pointB); - - if (!pointAWorld || !pointBWorld) - return; - - var delta = Vector.sub(pointAWorld, pointBWorld), - currentLength = Vector.magnitude(delta); - - // prevent singularity - if (currentLength === 0) - currentLength = _minLength; - - // solve distance constraint with Gauss-Siedel method - var difference = (currentLength - constraint.length) / currentLength, - normal = Vector.div(delta, currentLength), - force = Vector.mult(delta, difference * 0.5 * constraint.stiffness * timeScale * timeScale); - - // if difference is very small, we can skip - if (Math.abs(1 - (currentLength / constraint.length)) < _minDifference * timeScale) - return; - - var velocityPointA, - velocityPointB, - offsetA, - offsetB, - oAn, - oBn, - bodyADenom, - bodyBDenom; - - if (bodyA && !bodyA.isStatic) { - // point body offset - offsetA = { - x: pointAWorld.x - bodyA.position.x + force.x, - y: pointAWorld.y - bodyA.position.y + force.y - }; - - // update velocity - bodyA.velocity.x = bodyA.position.x - bodyA.positionPrev.x; - bodyA.velocity.y = bodyA.position.y - bodyA.positionPrev.y; - bodyA.angularVelocity = bodyA.angle - bodyA.anglePrev; - - // find point velocity and body mass - velocityPointA = Vector.add(bodyA.velocity, Vector.mult(Vector.perp(offsetA), bodyA.angularVelocity)); - oAn = Vector.dot(offsetA, normal); - bodyADenom = bodyA.inverseMass + bodyA.inverseInertia * oAn * oAn; - } else { - velocityPointA = { x: 0, y: 0 }; - bodyADenom = bodyA ? bodyA.inverseMass : 0; - } - - if (bodyB && !bodyB.isStatic) { - // point body offset - offsetB = { - x: pointBWorld.x - bodyB.position.x - force.x, - y: pointBWorld.y - bodyB.position.y - force.y - }; - - // update velocity - bodyB.velocity.x = bodyB.position.x - bodyB.positionPrev.x; - bodyB.velocity.y = bodyB.position.y - bodyB.positionPrev.y; - bodyB.angularVelocity = bodyB.angle - bodyB.anglePrev; - - // find point velocity and body mass - velocityPointB = Vector.add(bodyB.velocity, Vector.mult(Vector.perp(offsetB), bodyB.angularVelocity)); - oBn = Vector.dot(offsetB, normal); - bodyBDenom = bodyB.inverseMass + bodyB.inverseInertia * oBn * oBn; - } else { - velocityPointB = { x: 0, y: 0 }; - bodyBDenom = bodyB ? bodyB.inverseMass : 0; - } - - var relativeVelocity = Vector.sub(velocityPointB, velocityPointA), - normalImpulse = Vector.dot(normal, relativeVelocity) / (bodyADenom + bodyBDenom); - - if (normalImpulse > 0) normalImpulse = 0; - - var normalVelocity = { - x: normal.x * normalImpulse, - y: normal.y * normalImpulse - }; - - var torque; - - if (bodyA && !bodyA.isStatic) { - torque = Vector.cross(offsetA, normalVelocity) * bodyA.inverseInertia * (1 - constraint.angularStiffness); - - Sleeping.set(bodyA, false); - - // clamp to prevent instabillity - // TODO: solve this properlly - torque = Common.clamp(torque, -0.01, 0.01); - - // keep track of applied impulses for post solving - bodyA.constraintImpulse.x -= force.x; - bodyA.constraintImpulse.y -= force.y; - bodyA.constraintImpulse.angle += torque; - - // apply forces - bodyA.position.x -= force.x; - bodyA.position.y -= force.y; - bodyA.angle += torque; - } - - if (bodyB && !bodyB.isStatic) { - torque = Vector.cross(offsetB, normalVelocity) * bodyB.inverseInertia * (1 - constraint.angularStiffness); - - Sleeping.set(bodyB, false); - - // clamp to prevent instabillity - // TODO: solve this properlly - torque = Common.clamp(torque, -0.01, 0.01); - - // keep track of applied impulses for post solving - bodyB.constraintImpulse.x += force.x; - bodyB.constraintImpulse.y += force.y; - bodyB.constraintImpulse.angle -= torque; - - // apply forces - bodyB.position.x += force.x; - bodyB.position.y += force.y; - bodyB.angle -= torque; - } - - }; - - /** - * Performs body updates required after solving constraints - * @method postSolveAll - * @param {body[]} bodies - */ - Constraint.postSolveAll = function(bodies) { - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i], - impulse = body.constraintImpulse; - - // update geometry and reset - Vertices.translate(body.vertices, impulse); - - if (impulse.angle !== 0) { - Vertices.rotate(body.vertices, impulse.angle, body.position); - Axes.rotate(body.axes, impulse.angle); - impulse.angle = 0; - } - - Bounds.update(body.bounds, body.vertices); - - impulse.x = 0; - impulse.y = 0; - } - }; - -})(); - -; // End src/constraint/Constraint.js - - -// Begin src/constraint/MouseConstraint.js - -/** -* 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 MouseConstraint -*/ - -var MouseConstraint = {}; - -(function() { - - /** - * Description - * @method create - * @param {engine} engine - * @param {} options - * @return {MouseConstraint} A new MouseConstraint - */ - MouseConstraint.create = function(engine, options) { - var mouse = engine.input.mouse; - - var constraint = Constraint.create({ - label: 'Mouse Constraint', - pointA: mouse.position, - pointB: { x: 0, y: 0 }, - length: 0.01, - stiffness: 0.1, - angularStiffness: 1, - render: { - strokeStyle: '#90EE90', - lineWidth: 3 - } - }); - - var defaults = { - type: 'mouseConstraint', - mouse: mouse, - dragBody: null, - dragPoint: null, - constraint: constraint - }; - - var mouseConstraint = Common.extend(defaults, options); - - Events.on(engine, 'tick', function(event) { - var allBodies = Composite.allBodies(engine.world); - MouseConstraint.update(mouseConstraint, allBodies); - }); - - return mouseConstraint; - }; - - /** - * Description - * @method update - * @param {MouseConstraint} mouseConstraint - * @param {body[]} bodies - */ - MouseConstraint.update = function(mouseConstraint, bodies) { - var mouse = mouseConstraint.mouse, - constraint = mouseConstraint.constraint; - - if (mouse.button === 0) { - if (!constraint.bodyB) { - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - if (Bounds.contains(body.bounds, mouse.position) - && Vertices.contains(body.vertices, mouse.position)) { - constraint.pointA = mouse.position; - constraint.bodyB = 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); - } - } - } - } else { - constraint.bodyB = null; - constraint.pointB = null; - } - - if (constraint.bodyB) { - Sleeping.set(constraint.bodyB, false); - constraint.pointA = mouse.position; - } - }; - -})(); - -; // End src/constraint/MouseConstraint.js - - -// Begin src/core/Common.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Common -*/ - -var Common = {}; - -(function() { - - Common._nextId = 0; - - /** - * Description - * @method extend - * @param {} obj - * @param {boolean} deep - * @return {} obj extended - */ - Common.extend = function(obj, deep) { - var argsStart, - args, - deepClone; - - if (typeof deep === 'boolean') { - argsStart = 2; - deepClone = deep; - } else { - argsStart = 1; - deepClone = true; - } - - args = Array.prototype.slice.call(arguments, argsStart); - - for (var i = 0; i < args.length; i++) { - var source = args[i]; - - if (source) { - for (var prop in source) { - if (deepClone && source[prop] && source[prop].constructor === Object) { - if (!obj[prop] || obj[prop].constructor === Object) { - obj[prop] = obj[prop] || {}; - Common.extend(obj[prop], deepClone, source[prop]); - } else { - obj[prop] = source[prop]; - } - } else { - obj[prop] = source[prop]; - } - } - } - } - - return obj; - }; - - /** - * Creates a new clone of the object, if deep is true references will also be cloned - * @method clone - * @param {} obj - * @param {bool} deep - * @return {} obj cloned - */ - Common.clone = function(obj, deep) { - return Common.extend({}, deep, obj); - }; - - /** - * Description - * @method keys - * @param {} obj - * @return {string[]} keys - */ - Common.keys = function(obj) { - if (Object.keys) - return Object.keys(obj); - - // avoid hasOwnProperty for performance - var keys = []; - for (var key in obj) - keys.push(key); - return keys; - }; - - /** - * Description - * @method values - * @param {} obj - * @return {array} Array of the objects property values - */ - Common.values = function(obj) { - var values = []; - - if (Object.keys) { - var keys = Object.keys(obj); - for (var i = 0; i < keys.length; i++) { - values.push(obj[keys[i]]); - } - return values; - } - - // avoid hasOwnProperty for performance - for (var key in obj) - values.push(obj[key]); - return values; - }; - - /** - * Description - * @method shadeColor - * @param {string} color - * @param {number} percent - * @return {string} A hex colour string made by lightening or darkening color by percent - */ - Common.shadeColor = function(color, percent) { - // http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color - var colorInteger = parseInt(color.slice(1),16), - amount = Math.round(2.55 * percent), - R = (colorInteger >> 16) + amount, - B = (colorInteger >> 8 & 0x00FF) + amount, - G = (colorInteger & 0x0000FF) + amount; - return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R :255) * 0x10000 - + (B < 255 ? B < 1 ? 0 : B : 255) * 0x100 - + (G < 255 ? G < 1 ? 0 : G : 255)).toString(16).slice(1); - }; - - /** - * Description - * @method shuffle - * @param {array} array - * @return {array} array shuffled randomly - */ - Common.shuffle = function(array) { - for (var i = array.length - 1; i > 0; i--) { - var j = Math.floor(Math.random() * (i + 1)); - var temp = array[i]; - array[i] = array[j]; - array[j] = temp; - } - return array; - }; - - /** - * Description - * @method choose - * @param {array} choices - * @return {object} A random choice object from the array - */ - Common.choose = function(choices) { - return choices[Math.floor(Math.random() * choices.length)]; - }; - - /** - * Description - * @method isElement - * @param {object} obj - * @return {boolean} True if the object is a HTMLElement, otherwise false - */ - Common.isElement = function(obj) { - // http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object - try { - return obj instanceof HTMLElement; - } - catch(e){ - return (typeof obj==="object") && - (obj.nodeType===1) && (typeof obj.style === "object") && - (typeof obj.ownerDocument ==="object"); - } - }; - - /** - * Description - * @method clamp - * @param {number} value - * @param {number} min - * @param {number} max - * @return {number} The value clamped between min and max inclusive - */ - Common.clamp = function(value, min, max) { - if (value < min) - return min; - if (value > max) - return max; - return value; - }; - - /** - * Description - * @method sign - * @param {number} value - * @return {number} -1 if negative, +1 if 0 or positive - */ - Common.sign = function(value) { - return value < 0 ? -1 : 1; - }; - - /** - * Description - * @method now - * @return {number} the current timestamp (high-res if avaliable) - */ - Common.now = function() { - // http://stackoverflow.com/questions/221294/how-do-you-get-a-timestamp-in-javascript - // https://gist.github.com/davidwaterston/2982531 - - var perf = window.performance; - - if (perf) { - perf.now = perf.now || perf.webkitNow || perf.msNow || perf.oNow || perf.mozNow; - return +(perf.now()); - } - - return +(new Date()); - }; - - - /** - * Description - * @method random - * @param {number} min - * @param {number} max - * @return {number} A random number between min and max inclusive - */ - Common.random = function(min, max) { - return min + Math.random() * (max - min); - }; - - /** - * Converts a CSS hex colour string into an integer - * @method colorToNumber - * @param {string} colorString - * @return {number} An integer representing the CSS hex string - */ - Common.colorToNumber = function(colorString) { - colorString = colorString.replace('#',''); - - if (colorString.length == 3) { - colorString = colorString.charAt(0) + colorString.charAt(0) - + colorString.charAt(1) + colorString.charAt(1) - + colorString.charAt(2) + colorString.charAt(2); - } - - return parseInt(colorString, 16); - }; - - /** - * A wrapper for console.log, for providing errors and warnings - * @method log - * @param {string} message - * @param {string} type - */ - Common.log = function(message, type) { - if (!console || !console.log) - return; - - var style; - - switch (type) { - - case 'warn': - style = 'color: coral'; - break; - case 'error': - style = 'color: red'; - break; - - } - - console.log('%c [Matter] ' + type + ': ' + message, style); - }; - - /** - * Returns the next unique sequential ID - * @method nextId - * @return {Number} Unique sequential ID - */ - Common.nextId = function() { - return Common._nextId++; - }; - -})(); - -; // End src/core/Common.js - - -// Begin src/core/Engine.js - -/** -* 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 Engine -*/ - -// TODO: viewports - -var Engine = {}; - -(function() { - - var _fps = 60, - _deltaSampleSize = _fps, - _delta = 1000 / _fps; - - var _requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame - || window.mozRequestAnimationFrame || window.msRequestAnimationFrame - || function(callback){ window.setTimeout(function() { callback(Common.now()); }, _delta); }; - - /** - * Description - * @method create - * @param {HTMLElement} element - * @param {object} options - * @return {engine} engine - */ - Engine.create = function(element, options) { - - // options may be passed as the first (and only) argument - options = Common.isElement(element) ? options : element; - element = Common.isElement(element) ? element : null; - - var defaults = { - enabled: true, - positionIterations: 6, - velocityIterations: 4, - constraintIterations: 2, - enableSleeping: false, - timeScale: 1, - input: {}, - events: [], - timing: { - fps: _fps, - timestamp: 0, - delta: _delta, - correction: 1, - deltaMin: 1000 / _fps, - deltaMax: 1000 / (_fps * 0.5), - timeScale: 1, - isFixed: false - }, - render: { - element: element, - controller: Render - } - }; - - var engine = Common.extend(defaults, options); - - 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.input.mouse = engine.input.mouse || Mouse.create(engine.render.canvas); - - engine.broadphase = engine.broadphase || { - current: 'grid', - grid: { - controller: Grid, - instance: Grid.create(), - detector: Detector.collisions - }, - bruteForce: { - detector: Detector.bruteForce - } - }; - - return engine; - }; - - /** - * An optional utility function that provides a game loop, that handles updating the engine for you. - * Calls `Engine.update` and `Engine.render` on the `requestAnimationFrame` event automatically. - * Handles time correction and non-fixed dynamic timing (if enabled). - * Triggers `beforeTick`, `tick` and `afterTick` events. - * @method run - * @param {engine} engine - */ - Engine.run = function(engine) { - var counterTimestamp = 0, - frameCounter = 0, - deltaHistory = [], - timePrev, - timeScalePrev = 1; - - (function render(time){ - _requestAnimationFrame(render); - - if (!engine.enabled) - return; - - var timing = engine.timing, - delta, - correction; - - // create an event object - var event = { - timestamp: time - }; - - Events.trigger(engine, 'beforeTick', event); - - if (timing.isFixed) { - // fixed timestep - delta = timing.delta; - } else { - // dynamic timestep based on wall clock between calls - delta = (time - timePrev) || timing.delta; - timePrev = time; - - // optimistically filter delta over a few frames, to improve stability - deltaHistory.push(delta); - deltaHistory = deltaHistory.slice(-_deltaSampleSize); - delta = Math.min.apply(null, deltaHistory); - - // limit delta - delta = delta < timing.deltaMin ? timing.deltaMin : delta; - delta = delta > timing.deltaMax ? timing.deltaMax : delta; - - // time correction for delta - correction = delta / timing.delta; - - // update engine timing object - timing.delta = delta; - } - - // time correction for time scaling - if (timeScalePrev !== 0) - correction *= timing.timeScale / timeScalePrev; - - if (timing.timeScale === 0) - correction = 0; - - timeScalePrev = timing.timeScale; - - // fps counter - frameCounter += 1; - if (time - counterTimestamp >= 1000) { - timing.fps = frameCounter * ((time - counterTimestamp) / 1000); - counterTimestamp = time; - frameCounter = 0; - } - - Events.trigger(engine, 'tick', event); - - // if world has been modified, clear the render scene graph - if (engine.world.isModified) - engine.render.controller.clear(engine.render); - - // update - Engine.update(engine, delta, correction); - - // trigger events that may have occured during the step - _triggerCollisionEvents(engine); - _triggerMouseEvents(engine); - - // render - Engine.render(engine); - - Events.trigger(engine, 'afterTick', event); - })(); - }; - - /** - * Moves the simulation forward in time by `delta` ms. Triggers `beforeUpdate` and `afterUpdate` events. - * @method update - * @param {engine} engine - * @param {number} delta - * @param {number} correction - * @return engine - */ - Engine.update = function(engine, delta, correction) { - correction = (typeof correction !== 'undefined') ? correction : 1; - - var world = engine.world, - timing = engine.timing, - broadphase = engine.broadphase[engine.broadphase.current], - broadphasePairs = [], - i; - - // increment timestamp - timing.timestamp += delta * timing.timeScale; - timing.correction = correction; - - // create an event object - var event = { - timestamp: engine.timing.timestamp - }; - - Events.trigger(engine, 'beforeUpdate', event); - - // get lists of all bodies and constraints, no matter what composites they are in - 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); - - // applies gravity to all bodies - Body.applyGravityAll(allBodies, world.gravity); - - // update all body position and rotation by integration - Body.updateAll(allBodies, delta, timing.timeScale, correction, world.bounds); - - // update all constraints - for (i = 0; i < engine.constraintIterations; i++) { - Constraint.solveAll(allConstraints, timing.timeScale); - } - Constraint.postSolveAll(allBodies); - - // broadphase pass: find potential collision pairs - if (broadphase.controller) { - - // if world is dirty, we must flush the whole grid - if (world.isModified) - broadphase.controller.clear(broadphase.instance); - - // update the grid buckets based on current bodies - broadphase.controller.update(broadphase.instance, allBodies, engine, world.isModified); - broadphasePairs = broadphase.instance.pairsList; - } else { - - // if no broadphase set, we just pass all bodies - broadphasePairs = allBodies; - } - - // narrowphase pass: find actual collisions, then create or update collision pairs - var collisions = broadphase.detector(broadphasePairs, engine); - - // update collision pairs - var pairs = engine.pairs, - timestamp = timing.timestamp; - Pairs.update(pairs, collisions, timestamp); - Pairs.removeOld(pairs, timestamp); - - // wake up bodies involved in collisions - if (engine.enableSleeping) - Sleeping.afterCollisions(pairs.list); - - // 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); - - // update metrics log - Metrics.update(engine.metrics, engine); - - // clear force buffers - Body.resetForcesAll(allBodies); - - // clear all composite modified flags - if (world.isModified) - Composite.setModified(world, false, false, true); - - Events.trigger(engine, 'afterUpdate', event); - - return engine; - }; - - /** - * Renders the world by calling its defined renderer `engine.render.controller`. Triggers `beforeRender` and `afterRender` events. - * @method render - * @param {engine} engineA - * @param {engine} engineB - */ - Engine.render = function(engine) { - // create an event object - var event = { - timestamp: engine.timing.timestamp - }; - - Events.trigger(engine, 'beforeRender', event); - engine.render.controller.world(engine); - Events.trigger(engine, 'afterRender', event); - }; - - /** - * Description - * @method merge - * @param {engine} engineA - * @param {engine} engineB - */ - Engine.merge = function(engineA, engineB) { - Common.extend(engineA, engineB); - - if (engineB.world) { - engineA.world = engineB.world; - - Engine.clear(engineA); - - var bodies = Composite.allBodies(engineA.world); - - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - Sleeping.set(body, false); - body.id = Common.nextId(); - } - } - }; - - /** - * Description - * @method clear - * @param {engine} engine - */ - Engine.clear = function(engine) { - var world = engine.world; - - Pairs.clear(engine.pairs); - - var broadphase = engine.broadphase[engine.broadphase.current]; - if (broadphase.controller) { - var bodies = Composite.allBodies(world); - broadphase.controller.clear(broadphase.instance); - broadphase.controller.update(broadphase.instance, bodies, engine, true); - } - }; - - /** - * Triggers mouse events - * @method _triggerMouseEvents - * @private - * @param {engine} engine - */ - var _triggerMouseEvents = function(engine) { - var mouse = engine.input.mouse, - mouseEvents = mouse.sourceEvents; - - if (mouseEvents.mousemove) { - Events.trigger(engine, 'mousemove', { - mouse: mouse - }); - } - - if (mouseEvents.mousedown) { - Events.trigger(engine, 'mousedown', { - mouse: mouse - }); - } - - if (mouseEvents.mouseup) { - Events.trigger(engine, 'mouseup', { - mouse: mouse - }); - } - - // reset the mouse state ready for the next step - Mouse.clearSourceEvents(mouse); - }; - - /** - * Triggers collision events - * @method _triggerMouseEvents - * @private - * @param {engine} engine - */ - var _triggerCollisionEvents = function(engine) { - var pairs = engine.pairs; - - if (pairs.collisionStart.length > 0) { - Events.trigger(engine, 'collisionStart', { - pairs: pairs.collisionStart - }); - } - - if (pairs.collisionActive.length > 0) { - Events.trigger(engine, 'collisionActive', { - pairs: pairs.collisionActive - }); - } - - if (pairs.collisionEnd.length > 0) { - Events.trigger(engine, 'collisionEnd', { - pairs: pairs.collisionEnd - }); - } - }; - - /* - * - * Events Documentation - * - */ - - /** - * Fired at the start of a tick, before any updates to the engine or timing - * - * @event beforeTick - * @param {} event An event object - * @param {DOMHighResTimeStamp} event.timestamp The timestamp of the current tick - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired after engine timing updated, but just before engine state updated - * - * @event tick - * @param {} event An event object - * @param {DOMHighResTimeStamp} event.timestamp The timestamp of the current tick - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired just before an update - * - * @event beforeUpdate - * @param {} event An event object - * @param {DOMHighResTimeStamp} event.timestamp The timestamp of the current tick - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired after engine update and all collision events - * - * @event afterUpdate - * @param {} event An event object - * @param {DOMHighResTimeStamp} event.timestamp The timestamp of the current tick - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired just before rendering - * - * @event beforeRender - * @param {} event An event object - * @param {DOMHighResTimeStamp} event.timestamp The timestamp of the current tick - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired after rendering - * - * @event afterRender - * @param {} event An event object - * @param {DOMHighResTimeStamp} event.timestamp The timestamp of the current tick - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired after engine update and after rendering - * - * @event afterTick - * @param {} event An event object - * @param {DOMHighResTimeStamp} event.timestamp The timestamp of the current tick - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired when the mouse has moved (or a touch moves) during the last step - * - * @event mousemove - * @param {} event An event object - * @param {mouse} event.mouse The engine's mouse instance - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired when the mouse is down (or a touch has started) during the last step - * - * @event mousedown - * @param {} event An event object - * @param {mouse} event.mouse The engine's mouse instance - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired when the mouse is up (or a touch has ended) during the last step - * - * @event mouseup - * @param {} event An event object - * @param {mouse} event.mouse The engine's mouse instance - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired after engine update, provides a list of all pairs that have started to collide in the current tick (if any) - * - * @event collisionStart - * @param {} event An event object - * @param {} event.pairs List of affected pairs - * @param {DOMHighResTimeStamp} event.timestamp The timestamp of the current tick - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired after engine update, provides a list of all pairs that are colliding in the current tick (if any) - * - * @event collisionActive - * @param {} event An event object - * @param {} event.pairs List of affected pairs - * @param {DOMHighResTimeStamp} event.timestamp The timestamp of the current tick - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - - /** - * Fired after engine update, provides a list of all pairs that have ended collision in the current tick (if any) - * - * @event collisionEnd - * @param {} event An event object - * @param {} event.pairs List of affected pairs - * @param {DOMHighResTimeStamp} event.timestamp The timestamp of the current tick - * @param {} event.source The source object of the event - * @param {} event.name The name of the event - */ - -})(); - -; // End src/core/Engine.js - - -// Begin src/core/Events.js - -/** -* 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 Events -*/ - -var Events = {}; - -(function() { - - /** - * Subscribes a callback function to the given object's eventName - * @method on - * @param {} object - * @param {string} eventNames - * @param {function} callback - */ - Events.on = function(object, eventNames, callback) { - var names = eventNames.split(' '), - name; - - for (var i = 0; i < names.length; i++) { - name = names[i]; - object.events = object.events || {}; - object.events[name] = object.events[name] || []; - object.events[name].push(callback); - } - - return callback; - }; - - /** - * Removes the given event callback. If no callback, clears all callbacks in eventNames. If no eventNames, clears all events. - * @method off - * @param {} object - * @param {string} eventNames - * @param {function} callback - */ - Events.off = function(object, eventNames, callback) { - if (!eventNames) { - object.events = {}; - return; - } - - // handle Events.off(object, callback) - if (typeof eventNames === 'function') { - callback = eventNames; - eventNames = Common.keys(object.events).join(' '); - } - - var names = eventNames.split(' '); - - for (var i = 0; i < names.length; i++) { - var callbacks = object.events[names[i]], - newCallbacks = []; - - if (callback) { - for (var j = 0; j < callbacks.length; j++) { - if (callbacks[j] !== callback) - newCallbacks.push(callbacks[j]); - } - } - - object.events[names[i]] = newCallbacks; - } - }; - - /** - * Fires all the callbacks subscribed to the given object's eventName, in the order they subscribed, if any - * @method trigger - * @param {} object - * @param {string} eventNames - * @param {} event - */ - Events.trigger = function(object, eventNames, event) { - var names, - name, - callbacks, - eventClone; - - if (object.events) { - if (!event) - event = {}; - - names = eventNames.split(' '); - - for (var i = 0; i < names.length; i++) { - name = names[i]; - callbacks = object.events[name]; - - if (callbacks) { - eventClone = Common.clone(event, false); - eventClone.name = name; - eventClone.source = object; - - for (var j = 0; j < callbacks.length; j++) { - callbacks[j].apply(object, [eventClone]); - } - } - } - } - }; - -})(); - -; // End src/core/Events.js - - -// 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, - broadphase = engine.broadphase[engine.broadphase.current], - 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); - //if (broadphase.instance) - // metrics.buckets = Common.keys(broadphase.instance.buckets).length; - } - }; - -})(); - -; // End src/core/Metrics.js - - -// Begin src/core/Mouse.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Mouse -*/ - -var Mouse; - -(function() { - - /** - * Description - * @param {HTMLElement} element - */ - Mouse = function(element) { - var mouse = this; - - this.element = element || document.body; - this.absolute = { x: 0, y: 0 }; - this.position = { x: 0, y: 0 }; - this.mousedownPosition = { x: 0, y: 0 }; - this.mouseupPosition = { x: 0, y: 0 }; - this.offset = { x: 0, y: 0 }; - this.scale = { x: 1, y: 1 }; - this.wheelDelta = 0; - this.button = -1; - - this.sourceEvents = { - mousemove: null, - mousedown: null, - mouseup: null, - mousewheel: null - }; - - this.mousemove = function(event) { - var position = _getRelativeMousePosition(event, mouse.element), - touches = event.changedTouches; - - if (touches) { - mouse.button = 0; - event.preventDefault(); - } - - mouse.absolute.x = position.x; - mouse.absolute.y = position.y; - mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x; - mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y; - mouse.sourceEvents.mousemove = event; - }; - - this.mousedown = function(event) { - var position = _getRelativeMousePosition(event, mouse.element), - touches = event.changedTouches; - - if (touches) { - mouse.button = 0; - event.preventDefault(); - } else { - mouse.button = event.button; - } - - mouse.absolute.x = position.x; - mouse.absolute.y = position.y; - mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x; - mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y; - mouse.mousedownPosition.x = mouse.position.x; - mouse.mousedownPosition.y = mouse.position.y; - mouse.sourceEvents.mousedown = event; - }; - - this.mouseup = function(event) { - var position = _getRelativeMousePosition(event, mouse.element), - touches = event.changedTouches; - - if (touches) { - event.preventDefault(); - } - - mouse.button = -1; - mouse.absolute.x = position.x; - mouse.absolute.y = position.y; - mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x; - mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y; - mouse.mouseupPosition.x = mouse.position.x; - mouse.mouseupPosition.y = mouse.position.y; - mouse.sourceEvents.mouseup = event; - }; - - this.mousewheel = function(event) { - mouse.wheelDelta = Math.max(-1, Math.min(1, event.wheelDelta || -event.detail)); - event.preventDefault(); - }; - - Mouse.setElement(mouse, mouse.element); - }; - - /** - * Description - * @method create - * @param {HTMLElement} element - * @return {mouse} A new mouse - */ - Mouse.create = function(element) { - return new Mouse(element); - }; - - /** - * Sets the element the mouse is bound to (and relative to) - * @method setElement - * @param {mouse} mouse - * @param {HTMLElement} element - */ - Mouse.setElement = function(mouse, element) { - mouse.element = element; - - element.addEventListener('mousemove', mouse.mousemove); - element.addEventListener('mousedown', mouse.mousedown); - element.addEventListener('mouseup', mouse.mouseup); - - element.addEventListener("mousewheel", mouse.mousewheel); - element.addEventListener("DOMMouseScroll", mouse.mousewheel); - - element.addEventListener('touchmove', mouse.mousemove); - element.addEventListener('touchstart', mouse.mousedown); - element.addEventListener('touchend', mouse.mouseup); - }; - - /** - * Clears all captured source events - * @method clearSourceEvents - * @param {mouse} mouse - */ - Mouse.clearSourceEvents = function(mouse) { - mouse.sourceEvents.mousemove = null; - mouse.sourceEvents.mousedown = null; - mouse.sourceEvents.mouseup = null; - mouse.sourceEvents.mousewheel = null; - mouse.wheelDelta = 0; - }; - - /** - * Sets the offset - * @method setOffset - * @param {mouse} mouse - */ - Mouse.setOffset = function(mouse, offset) { - mouse.offset.x = offset.x; - mouse.offset.y = offset.y; - mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x; - mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y; - }; - - /** - * Sets the scale - * @method setScale - * @param {mouse} mouse - */ - Mouse.setScale = function(mouse, scale) { - mouse.scale.x = scale.x; - mouse.scale.y = scale.y; - mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x; - mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y; - }; - - /** - * Description - * @method _getRelativeMousePosition - * @private - * @param {} event - * @param {} element - * @return ObjectExpression - */ - var _getRelativeMousePosition = function(event, element) { - var elementBounds = element.getBoundingClientRect(), - rootNode = (document.documentElement || document.body.parentNode || document.body), - scrollX = (window.pageXOffset !== undefined) ? window.pageXOffset : rootNode.scrollLeft, - scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset : rootNode.scrollTop, - touches = event.changedTouches, - x, y; - - if (touches) { - x = touches[0].pageX - elementBounds.left - scrollX; - y = touches[0].pageY - elementBounds.top - scrollY; - } else { - x = event.pageX - elementBounds.left - scrollX; - y = event.pageY - elementBounds.top - scrollY; - } - - return { - x: x / (element.clientWidth / element.width), - y: y / (element.clientHeight / element.height) - }; - }; - -})(); - -; // End src/core/Mouse.js - - -// Begin src/core/Sleeping.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Sleeping -*/ - -var Sleeping = {}; - -(function() { - - var _motionWakeThreshold = 0.18, - _motionSleepThreshold = 0.08, - _minBias = 0.9; - - /** - * Description - * @method update - * @param {body[]} bodies - */ - Sleeping.update = function(bodies) { - // update bodies sleeping status - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i], - motion = body.speed * body.speed + body.angularSpeed * body.angularSpeed; - - // wake up bodies if they have a force applied - if (body.force.x > 0 || body.force.y > 0) { - Sleeping.set(body, false); - continue; - } - - var minMotion = Math.min(body.motion, motion), - maxMotion = Math.max(body.motion, motion); - - // biased average motion estimation between frames - body.motion = _minBias * minMotion + (1 - _minBias) * maxMotion; - - if (body.sleepThreshold > 0 && body.motion < _motionSleepThreshold) { - body.sleepCounter += 1; - - if (body.sleepCounter >= body.sleepThreshold) - Sleeping.set(body, true); - } else if (body.sleepCounter > 0) { - body.sleepCounter -= 1; - } - } - }; - - /** - * Description - * @method afterCollisions - * @param {pair[]} pairs - */ - Sleeping.afterCollisions = function(pairs) { - // wake up bodies involved in collisions - for (var i = 0; i < pairs.length; i++) { - var pair = pairs[i]; - - // don't wake inactive pairs - if (!pair.isActive) - continue; - - var collision = pair.collision, - bodyA = collision.bodyA, - bodyB = collision.bodyB; - - // don't wake if at least one body is static - if ((bodyA.isSleeping && bodyB.isSleeping) || bodyA.isStatic || bodyB.isStatic) - continue; - - if (bodyA.isSleeping || bodyB.isSleeping) { - var sleepingBody = (bodyA.isSleeping && !bodyA.isStatic) ? bodyA : bodyB, - movingBody = sleepingBody === bodyA ? bodyB : bodyA; - - if (!sleepingBody.isStatic && movingBody.motion > _motionWakeThreshold) { - Sleeping.set(sleepingBody, false); - } - } - } - }; - - /** - * Description - * @method set - * @param {body} body - * @param {boolean} isSleeping - */ - Sleeping.set = function(body, isSleeping) { - if (isSleeping) { - body.isSleeping = true; - body.sleepCounter = body.sleepThreshold; - - body.positionImpulse.x = 0; - body.positionImpulse.y = 0; - - body.positionPrev.x = body.position.x; - body.positionPrev.y = body.position.y; - - body.anglePrev = body.angle; - body.speed = 0; - body.angularSpeed = 0; - body.motion = 0; - } else { - body.isSleeping = false; - body.sleepCounter = 0; - } - }; - -})(); - -; // End src/core/Sleeping.js - - -// Begin src/factory/Bodies.js - -/** -* 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 Bodies -*/ - -// TODO: true circle bodies - -var Bodies = {}; - -(function() { - - /** - * Description - * @method rectangle - * @param {number} x - * @param {number} y - * @param {number} width - * @param {number} height - * @param {object} options - * @return {body} A new rectangle body - */ - Bodies.rectangle = function(x, y, width, height, options) { - options = options || {}; - - var rectangle = { - label: 'Rectangle Body', - position: { x: x, y: y }, - vertices: Vertices.fromPath('L 0 0 L ' + width + ' 0 L ' + width + ' ' + height + ' L 0 ' + height) - }; - - if (options.chamfer) { - var chamfer = options.chamfer; - rectangle.vertices = Vertices.chamfer(rectangle.vertices, chamfer.radius, - chamfer.quality, chamfer.qualityMin, chamfer.qualityMax); - delete options.chamfer; - } - - return Body.create(Common.extend({}, rectangle, options)); - }; - - /** - * Description - * @method trapezoid - * @param {number} x - * @param {number} y - * @param {number} width - * @param {number} height - * @param {number} slope - * @param {object} options - * @return {body} A new trapezoid body - */ - Bodies.trapezoid = function(x, y, width, height, slope, options) { - options = options || {}; - - slope *= 0.5; - var roof = (1 - (slope * 2)) * width; - - var x1 = width * slope, - x2 = x1 + roof, - x3 = x2 + x1; - - var trapezoid = { - label: 'Trapezoid Body', - position: { x: x, y: y }, - vertices: Vertices.fromPath('L 0 0 L ' + x1 + ' ' + (-height) + ' L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0') - }; - - if (options.chamfer) { - var chamfer = options.chamfer; - trapezoid.vertices = Vertices.chamfer(trapezoid.vertices, chamfer.radius, - chamfer.quality, chamfer.qualityMin, chamfer.qualityMax); - delete options.chamfer; - } - - return Body.create(Common.extend({}, trapezoid, options)); - }; - - /** - * Description - * @method circle - * @param {number} x - * @param {number} y - * @param {number} radius - * @param {object} options - * @param {number} maxSides - * @return {body} A new circle body - */ - Bodies.circle = function(x, y, radius, options, maxSides) { - options = options || {}; - options.label = 'Circle Body'; - - // approximate circles with polygons until true circles implemented in SAT - - maxSides = maxSides || 25; - var sides = Math.ceil(Math.max(10, Math.min(maxSides, radius))); - - // optimisation: always use even number of sides (half the number of unique axes) - if (sides % 2 === 1) - sides += 1; - - // flag for better rendering - options.circleRadius = radius; - - return Bodies.polygon(x, y, sides, radius, options); - }; - - /** - * Description - * @method polygon - * @param {number} x - * @param {number} y - * @param {number} sides - * @param {number} radius - * @param {object} options - * @return {body} A new regular polygon body - */ - Bodies.polygon = function(x, y, sides, radius, options) { - options = options || {}; - - if (sides < 3) - return Bodies.circle(x, y, radius, options); - - var theta = 2 * Math.PI / sides, - path = '', - offset = theta * 0.5; - - for (var i = 0; i < sides; i += 1) { - var angle = offset + (i * theta), - xx = Math.cos(angle) * radius, - yy = Math.sin(angle) * radius; - - path += 'L ' + xx.toFixed(3) + ' ' + yy.toFixed(3) + ' '; - } - - var polygon = { - label: 'Polygon Body', - position: { x: x, y: y }, - vertices: Vertices.fromPath(path) - }; - - if (options.chamfer) { - var chamfer = options.chamfer; - polygon.vertices = Vertices.chamfer(polygon.vertices, chamfer.radius, - chamfer.quality, chamfer.qualityMin, chamfer.qualityMax); - delete options.chamfer; - } - - return Body.create(Common.extend({}, polygon, options)); - }; - -})(); - -; // End src/factory/Bodies.js - - -// Begin src/factory/Composites.js - -/** -* 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 Composites -*/ - -var Composites = {}; - -(function() { - - /** - * Description - * @method stack - * @param {number} xx - * @param {number} yy - * @param {number} columns - * @param {number} rows - * @param {number} columnGap - * @param {number} rowGap - * @param {function} callback - * @return {composite} A new composite containing objects created in the callback - */ - Composites.stack = function(xx, yy, columns, rows, columnGap, rowGap, callback) { - var stack = Composite.create({ label: 'Stack' }), - x = xx, - y = yy, - lastBody, - i = 0; - - for (var row = 0; row < rows; row++) { - var maxHeight = 0; - - for (var column = 0; column < columns; column++) { - var body = callback(x, y, column, row, lastBody, i); - - if (body) { - var bodyHeight = body.bounds.max.y - body.bounds.min.y, - bodyWidth = body.bounds.max.x - body.bounds.min.x; - - if (bodyHeight > maxHeight) - maxHeight = bodyHeight; - - Body.translate(body, { x: bodyWidth * 0.5, y: bodyHeight * 0.5 }); - - x = body.bounds.max.x + columnGap; - - Composite.addBody(stack, body); - - lastBody = body; - i += 1; - } - } - - y += maxHeight + rowGap; - x = xx; - } - - return stack; - }; - - /** - * Description - * @method chain - * @param {composite} composite - * @param {number} xOffsetA - * @param {number} yOffsetA - * @param {number} xOffsetB - * @param {number} yOffsetB - * @param {object} options - * @return {composite} A new composite containing objects chained together with constraints - */ - Composites.chain = function(composite, xOffsetA, yOffsetA, xOffsetB, yOffsetB, options) { - var bodies = composite.bodies; - - for (var i = 1; i < bodies.length; i++) { - var bodyA = bodies[i - 1], - bodyB = bodies[i], - bodyAHeight = bodyA.bounds.max.y - bodyA.bounds.min.y, - bodyAWidth = bodyA.bounds.max.x - bodyA.bounds.min.x, - bodyBHeight = bodyB.bounds.max.y - bodyB.bounds.min.y, - bodyBWidth = bodyB.bounds.max.x - bodyB.bounds.min.x; - - var defaults = { - bodyA: bodyA, - pointA: { x: bodyAWidth * xOffsetA, y: bodyAHeight * yOffsetA }, - bodyB: bodyB, - pointB: { x: bodyBWidth * xOffsetB, y: bodyBHeight * yOffsetB } - }; - - var constraint = Common.extend(defaults, options); - - Composite.addConstraint(composite, Constraint.create(constraint)); - } - - composite.label += ' Chain'; - - return composite; - }; - - /** - * Connects bodies in the composite with constraints in a grid pattern, with optional cross braces - * @method mesh - * @param {composite} composite - * @param {number} columns - * @param {number} rows - * @param {boolean} crossBrace - * @param {object} options - * @return {composite} The composite containing objects meshed together with constraints - */ - Composites.mesh = function(composite, columns, rows, crossBrace, options) { - var bodies = composite.bodies, - row, - col, - bodyA, - bodyB, - bodyC; - - for (row = 0; row < rows; row++) { - for (col = 0; col < columns; col++) { - if (col > 0) { - bodyA = bodies[(col - 1) + (row * columns)]; - bodyB = bodies[col + (row * columns)]; - Composite.addConstraint(composite, Constraint.create(Common.extend({ bodyA: bodyA, bodyB: bodyB }, options))); - } - } - - for (col = 0; col < columns; col++) { - if (row > 0) { - bodyA = bodies[col + ((row - 1) * columns)]; - bodyB = bodies[col + (row * columns)]; - Composite.addConstraint(composite, Constraint.create(Common.extend({ bodyA: bodyA, bodyB: bodyB }, options))); - - if (crossBrace && col > 0) { - bodyC = bodies[(col - 1) + ((row - 1) * columns)]; - Composite.addConstraint(composite, Constraint.create(Common.extend({ bodyA: bodyC, bodyB: bodyB }, options))); - } - - if (crossBrace && col < columns - 1) { - bodyC = bodies[(col + 1) + ((row - 1) * columns)]; - Composite.addConstraint(composite, Constraint.create(Common.extend({ bodyA: bodyC, bodyB: bodyB }, options))); - } - } - } - } - - composite.label += ' Mesh'; - - return composite; - }; - - /** - * Description - * @method pyramid - * @param {number} xx - * @param {number} yy - * @param {number} columns - * @param {number} rows - * @param {number} columnGap - * @param {number} rowGap - * @param {function} callback - * @return {composite} A new composite containing objects created in the callback - */ - Composites.pyramid = function(xx, yy, columns, rows, columnGap, rowGap, callback) { - return Composites.stack(xx, yy, columns, rows, columnGap, rowGap, function(x, y, column, row, lastBody, i) { - var actualRows = Math.min(rows, Math.ceil(columns / 2)), - lastBodyWidth = lastBody ? lastBody.bounds.max.x - lastBody.bounds.min.x : 0; - - if (row > actualRows) - return; - - // reverse row order - row = actualRows - row; - - var start = row, - end = columns - 1 - row; - - if (column < start || column > end) - return; - - // retroactively fix the first body's position, since width was unknown - if (i === 1) { - Body.translate(lastBody, { x: (column + (columns % 2 === 1 ? 1 : -1)) * lastBodyWidth, y: 0 }); - } - - var xOffset = lastBody ? column * lastBodyWidth : 0; - - return callback(xx + xOffset + column * columnGap, y, column, row, lastBody, i); - }); - }; - - /** - * Description - * @method newtonsCradle - * @param {number} xx - * @param {number} yy - * @param {number} number - * @param {number} size - * @param {number} length - * @return {composite} A new composite newtonsCradle body - */ - Composites.newtonsCradle = function(xx, yy, number, size, length) { - var newtonsCradle = Composite.create({ label: 'Newtons Cradle' }); - - for (var i = 0; i < number; i++) { - var separation = 1.9, - circle = Bodies.circle(xx + i * (size * separation), yy + length, size, - { inertia: 99999, restitution: 1, friction: 0, frictionAir: 0.0001, slop: 0.01 }), - constraint = Constraint.create({ pointA: { x: xx + i * (size * separation), y: yy }, bodyB: circle }); - - Composite.addBody(newtonsCradle, circle); - Composite.addConstraint(newtonsCradle, constraint); - } - - return newtonsCradle; - }; - - /** - * Description - * @method car - * @param {number} xx - * @param {number} yy - * @param {number} width - * @param {number} height - * @param {number} wheelSize - * @return {composite} A new composite car body - */ - Composites.car = function(xx, yy, width, height, wheelSize) { - var groupId = Body.nextGroupId(), - wheelBase = -20, - wheelAOffset = -width * 0.5 + wheelBase, - wheelBOffset = width * 0.5 - wheelBase, - wheelYOffset = 0; - - var car = Composite.create({ label: 'Car' }), - body = Bodies.trapezoid(xx, yy, width, height, 0.3, { - groupId: groupId, - friction: 0.01, - chamfer: { - radius: 10 - } - }); - - var wheelA = Bodies.circle(xx + wheelAOffset, yy + wheelYOffset, wheelSize, { - groupId: groupId, - restitution: 0.5, - friction: 0.9, - density: 0.01 - }); - - var wheelB = Bodies.circle(xx + wheelBOffset, yy + wheelYOffset, wheelSize, { - groupId: groupId, - restitution: 0.5, - friction: 0.9, - density: 0.01 - }); - - var axelA = Constraint.create({ - bodyA: body, - pointA: { x: wheelAOffset, y: wheelYOffset }, - bodyB: wheelA, - stiffness: 0.5 - }); - - var axelB = Constraint.create({ - bodyA: body, - pointA: { x: wheelBOffset, y: wheelYOffset }, - bodyB: wheelB, - stiffness: 0.5 - }); - - Composite.addBody(car, body); - Composite.addBody(car, wheelA); - Composite.addBody(car, wheelB); - Composite.addConstraint(car, axelA); - Composite.addConstraint(car, axelB); - - return car; - }; - - /** - * Creates a simple soft body like object - * @method softBody - * @param {number} xx - * @param {number} yy - * @param {number} columns - * @param {number} rows - * @param {number} columnGap - * @param {number} rowGap - * @param {boolean} crossBrace - * @param {number} particleRadius - * @param {} particleOptions - * @param {} constraintOptions - * @return {composite} A new composite softBody - */ - Composites.softBody = function(xx, yy, columns, rows, columnGap, rowGap, crossBrace, particleRadius, particleOptions, constraintOptions) { - particleOptions = Common.extend({ inertia: Infinity }, particleOptions); - constraintOptions = Common.extend({ stiffness: 0.4 }, constraintOptions); - - var softBody = Composites.stack(xx, yy, columns, rows, columnGap, rowGap, function(x, y, column, row) { - return Bodies.circle(x, y, particleRadius, particleOptions); - }); - - Composites.mesh(softBody, columns, rows, crossBrace, constraintOptions); - - softBody.label = 'Soft Body'; - - return softBody; - }; - -})(); - -; // End src/factory/Composites.js - - -// Begin src/geometry/Axes.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Axes -*/ - -var Axes = {}; - -(function() { - - /** - * Description - * @method fromVertices - * @param {vertices} vertices - * @return {axes} A new axes from the given vertices - */ - Axes.fromVertices = function(vertices) { - var axes = {}; - - // find the unique axes, using edge normal gradients - for (var i = 0; i < vertices.length; i++) { - var j = (i + 1) % vertices.length, - normal = Vector.normalise({ - x: vertices[j].y - vertices[i].y, - y: vertices[i].x - vertices[j].x - }), - gradient = (normal.y === 0) ? Infinity : (normal.x / normal.y); - - // limit precision - gradient = gradient.toFixed(3).toString(); - - axes[gradient] = normal; - } - - return Common.values(axes); - }; - - /** - * Description - * @method rotate - * @param {axes} axes - * @param {number} angle - */ - Axes.rotate = function(axes, angle) { - if (angle === 0) - return; - - var cos = Math.cos(angle), - sin = Math.sin(angle); - - for (var i = 0; i < axes.length; i++) { - var axis = axes[i], - xx; - xx = axis.x * cos - axis.y * sin; - axis.y = axis.x * sin + axis.y * cos; - axis.x = xx; - } - }; - -})(); - -; // End src/geometry/Axes.js - - -// Begin src/geometry/Bounds.js - -/** -* _Internal Class_, not generally used outside of the engine's internals. -* -* @class Bounds -*/ - -var Bounds = {}; - -(function() { - - /** - * Description - * @method create - * @param {vertices} vertices - * @return {bounds} A new bounds object - */ - Bounds.create = function(vertices) { - var bounds = { - min: { x: 0, y: 0 }, - max: { x: 0, y: 0 } - }; - - if (vertices) - Bounds.update(bounds, vertices); - - return bounds; - }; - - /** - * Description - * @method update - * @param {bounds} bounds - * @param {vertices} vertices - * @param {vector} velocity - */ - Bounds.update = function(bounds, vertices, velocity) { - bounds.min.x = Number.MAX_VALUE; - bounds.max.x = Number.MIN_VALUE; - bounds.min.y = Number.MAX_VALUE; - bounds.max.y = Number.MIN_VALUE; - - for (var i = 0; i < vertices.length; i++) { - var vertex = vertices[i]; - if (vertex.x > bounds.max.x) bounds.max.x = vertex.x; - if (vertex.x < bounds.min.x) bounds.min.x = vertex.x; - if (vertex.y > bounds.max.y) bounds.max.y = vertex.y; - if (vertex.y < bounds.min.y) bounds.min.y = vertex.y; - } - - if (velocity) { - if (velocity.x > 0) { - bounds.max.x += velocity.x; - } else { - bounds.min.x += velocity.x; - } - - if (velocity.y > 0) { - bounds.max.y += velocity.y; - } else { - bounds.min.y += velocity.y; - } - } - }; - - /** - * Description - * @method contains - * @param {bounds} bounds - * @param {vector} point - * @return {boolean} True if the bounds contain the point, otherwise false - */ - Bounds.contains = function(bounds, point) { - return point.x >= bounds.min.x && point.x <= bounds.max.x - && point.y >= bounds.min.y && point.y <= bounds.max.y; - }; - - /** - * Description - * @method overlaps - * @param {bounds} boundsA - * @param {bounds} boundsB - * @return {boolean} True if the bounds overlap, otherwise false - */ - Bounds.overlaps = function(boundsA, boundsB) { - return (boundsA.min.x <= boundsB.max.x && boundsA.max.x >= boundsB.min.x - && boundsA.max.y >= boundsB.min.y && boundsA.min.y <= boundsB.max.y); - }; - - /** - * Translates the bounds by the given vector - * @method translate - * @param {bounds} bounds - * @param {vector} vector - */ - Bounds.translate = function(bounds, vector) { - bounds.min.x += vector.x; - bounds.max.x += vector.x; - bounds.min.y += vector.y; - bounds.max.y += vector.y; - }; - - /** - * Shifts the bounds to the given position - * @method shift - * @param {bounds} bounds - * @param {vector} position - */ - Bounds.shift = function(bounds, position) { - var deltaX = bounds.max.x - bounds.min.x, - deltaY = bounds.max.y - bounds.min.y; - - bounds.min.x = position.x; - bounds.max.x = position.x + deltaX; - bounds.min.y = position.y; - bounds.max.y = position.y + deltaY; - }; - -})(); - -; // End src/geometry/Bounds.js - - -// Begin src/geometry/Vector.js - -/** -* 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 Vector -*/ - -// TODO: consider params for reusing vector objects - -var Vector = {}; - -(function() { - - /** - * Description - * @method magnitude - * @param {vector} vector - * @return {number} The magnitude of the vector - */ - Vector.magnitude = function(vector) { - return Math.sqrt((vector.x * vector.x) + (vector.y * vector.y)); - }; - - /** - * Description - * @method magnitudeSquared - * @param {vector} vector - * @return {number} The squared magnitude of the vector - */ - Vector.magnitudeSquared = function(vector) { - return (vector.x * vector.x) + (vector.y * vector.y); - }; - - /** - * Description - * @method rotate - * @param {vector} vector - * @param {number} angle - * @return {vector} A new vector rotated - */ - Vector.rotate = function(vector, angle) { - var cos = Math.cos(angle), sin = Math.sin(angle); - return { - x: vector.x * cos - vector.y * sin, - y: vector.x * sin + vector.y * cos - }; - }; - - /** - * Description - * @method rotateAbout - * @param {vector} vector - * @param {number} angle - * @param {vector} point - * @return {vector} A new vector rotated about the point - */ - Vector.rotateAbout = function(vector, angle, point) { - 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) - }; - }; - - /** - * Description - * @method normalise - * @param {vector} vector - * @return {vector} A new vector normalised - */ - Vector.normalise = function(vector) { - var magnitude = Vector.magnitude(vector); - if (magnitude === 0) - return { x: 0, y: 0 }; - return { x: vector.x / magnitude, y: vector.y / magnitude }; - }; - - /** - * Description - * @method dot - * @param {vector} vectorA - * @param {vector} vectorB - * @return {number} The dot product of the two vectors - */ - Vector.dot = function(vectorA, vectorB) { - return (vectorA.x * vectorB.x) + (vectorA.y * vectorB.y); - }; - - /** - * Description - * @method cross - * @param {vector} vectorA - * @param {vector} vectorB - * @return {number} The cross product of the two vectors - */ - Vector.cross = function(vectorA, vectorB) { - return (vectorA.x * vectorB.y) - (vectorA.y * vectorB.x); - }; - - /** - * Description - * @method add - * @param {vector} vectorA - * @param {vector} vectorB - * @return {vector} A new vector added - */ - Vector.add = function(vectorA, vectorB) { - return { x: vectorA.x + vectorB.x, y: vectorA.y + vectorB.y }; - }; - - /** - * Description - * @method sub - * @param {vector} vectorA - * @param {vector} vectorB - * @return {vector} A new vector subtracted - */ - Vector.sub = function(vectorA, vectorB) { - return { x: vectorA.x - vectorB.x, y: vectorA.y - vectorB.y }; - }; - - /** - * Description - * @method mult - * @param {vector} vector - * @param {number} scalar - * @return {vector} A new vector multiplied by scalar - */ - Vector.mult = function(vector, scalar) { - return { x: vector.x * scalar, y: vector.y * scalar }; - }; - - /** - * Description - * @method div - * @param {vector} vector - * @param {number} scalar - * @return {vector} A new vector divided by scalar - */ - Vector.div = function(vector, scalar) { - return { x: vector.x / scalar, y: vector.y / scalar }; - }; - - /** - * Description - * @method perp - * @param {vector} vector - * @param {bool} negate - * @return {vector} The perpendicular vector - */ - Vector.perp = function(vector, negate) { - negate = negate === true ? -1 : 1; - return { x: negate * -vector.y, y: negate * vector.x }; - }; - - /** - * Description - * @method neg - * @param {vector} vector - * @return {vector} The negated vector - */ - Vector.neg = function(vector) { - return { x: -vector.x, y: -vector.y }; - }; - - /** - * Returns the angle in radians between the two vectors relative to the x-axis - * @method angle - * @param {vector} vectorA - * @param {vector} vectorB - * @return {number} The angle in radians - */ - Vector.angle = function(vectorA, vectorB) { - return Math.atan2(vectorB.y - vectorA.y, vectorB.x - vectorA.x); - }; - -})(); - -; // End src/geometry/Vector.js - - -// Begin src/geometry/Vertices.js - -/** -* 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 Vertices -*/ - -// TODO: convex decomposition - http://mnbayazit.com/406/bayazit - -var Vertices = {}; - -(function() { - - /** - * Description - * @method create - * @param {vertices} vertices - * @param {body} body - */ - Vertices.create = function(vertices, body) { - for (var i = 0; i < vertices.length; i++) { - vertices[i].index = i; - vertices[i].body = body; - } - }; - - /** - * Description - * @method fromPath - * @param {string} path - * @return {vertices} vertices - */ - Vertices.fromPath = function(path) { - var pathPattern = /L\s*([\-\d\.]*)\s*([\-\d\.]*)/ig, - vertices = []; - - path.replace(pathPattern, function(match, x, y) { - vertices.push({ x: parseFloat(x, 10), y: parseFloat(y, 10) }); - }); - - return vertices; - }; - - /** - * Description - * @method centre - * @param {vertices} vertices - * @return {vector} The centre point - */ - Vertices.centre = function(vertices) { - var area = Vertices.area(vertices, true), - centre = { x: 0, y: 0 }, - cross, - temp, - j; - - for (var i = 0; i < vertices.length; i++) { - j = (i + 1) % vertices.length; - cross = Vector.cross(vertices[i], vertices[j]); - temp = Vector.mult(Vector.add(vertices[i], vertices[j]), cross); - centre = Vector.add(centre, temp); - } - - return Vector.div(centre, 6 * area); - }; - - /** - * Description - * @method area - * @param {vertices} vertices - * @param {bool} signed - * @return {number} The area - */ - Vertices.area = function(vertices, signed) { - var area = 0, - j = vertices.length - 1; - - for (var i = 0; i < vertices.length; i++) { - area += (vertices[j].x - vertices[i].x) * (vertices[j].y + vertices[i].y); - j = i; - } - - if (signed) - return area / 2; - - return Math.abs(area) / 2; - }; - - /** - * Description - * @method inertia - * @param {vertices} vertices - * @param {number} mass - * @return {number} The polygon's moment of inertia, using second moment of area - */ - Vertices.inertia = function(vertices, mass) { - var numerator = 0, - denominator = 0, - v = vertices, - cross, - j; - - // find the polygon's moment of inertia, using second moment of area - // http://www.physicsforums.com/showthread.php?t=25293 - for (var n = 0; n < v.length; n++) { - j = (n + 1) % v.length; - cross = Math.abs(Vector.cross(v[j], v[n])); - numerator += cross * (Vector.dot(v[j], v[j]) + Vector.dot(v[j], v[n]) + Vector.dot(v[n], v[n])); - denominator += cross; - } - - return (mass / 6) * (numerator / denominator); - }; - - /** - * Description - * @method translate - * @param {vertices} vertices - * @param {vector} vector - * @param {number} scalar - */ - Vertices.translate = function(vertices, vector, scalar) { - var i; - if (scalar) { - for (i = 0; i < vertices.length; i++) { - vertices[i].x += vector.x * scalar; - vertices[i].y += vector.y * scalar; - } - } else { - for (i = 0; i < vertices.length; i++) { - vertices[i].x += vector.x; - vertices[i].y += vector.y; - } - } - }; - - /** - * Description - * @method rotate - * @param {vertices} vertices - * @param {number} angle - * @param {vector} point - */ - Vertices.rotate = function(vertices, angle, point) { - if (angle === 0) - return; - - var cos = Math.cos(angle), - sin = Math.sin(angle); - - for (var i = 0; i < vertices.length; i++) { - var vertice = vertices[i], - dx = vertice.x - point.x, - dy = vertice.y - point.y; - - vertice.x = point.x + (dx * cos - dy * sin); - vertice.y = point.y + (dx * sin + dy * cos); - } - }; - - /** - * Description - * @method contains - * @param {vertices} vertices - * @param {vector} point - * @return {boolean} True if the vertices contains point, otherwise false - */ - Vertices.contains = function(vertices, point) { - for (var i = 0; i < vertices.length; i++) { - var vertice = vertices[i], - nextVertice = vertices[(i + 1) % vertices.length]; - if ((point.x - vertice.x) * (nextVertice.y - vertice.y) + (point.y - vertice.y) * (vertice.x - nextVertice.x) > 0) { - return false; - } - } - - return true; - }; - - /** - * Scales the vertices from a point (default is centre) - * @method scale - * @param {vertices} vertices - * @param {number} scaleX - * @param {number} scaleY - * @param {vector} point - */ - Vertices.scale = function(vertices, scaleX, scaleY, point) { - if (scaleX === 1 && scaleY === 1) - return vertices; - - point = point || Vertices.centre(vertices); - - var vertex, - delta; - - for (var i = 0; i < vertices.length; i++) { - vertex = vertices[i]; - delta = Vector.sub(vertex, point); - vertices[i].x = point.x + delta.x * scaleX; - vertices[i].y = point.y + delta.y * scaleY; - } - - return vertices; - }; - - /** - * Chamfers a set of vertices by giving them rounded corners, returns a new set of vertices. - * The radius parameter is a single number or an array to specify the radius for each vertex. - * @method chamfer - * @param {vertices} vertices - * @param {number[]} radius - * @param {number} quality - * @param {number} qualityMin - * @param {number} qualityMax - */ - Vertices.chamfer = function(vertices, radius, quality, qualityMin, qualityMax) { - radius = radius || [8]; - - if (!radius.length) - radius = [radius]; - - // quality defaults to -1, which is auto - quality = (typeof quality !== 'undefined') ? quality : -1; - qualityMin = qualityMin || 2; - qualityMax = qualityMax || 14; - - var centre = Vertices.centre(vertices), - newVertices = []; - - for (var i = 0; i < vertices.length; i++) { - var prevVertex = vertices[i - 1 >= 0 ? i - 1 : vertices.length - 1], - vertex = vertices[i], - nextVertex = vertices[(i + 1) % vertices.length], - currentRadius = radius[i < radius.length ? i : radius.length - 1]; - - if (currentRadius === 0) { - newVertices.push(vertex); - continue; - } - - var prevNormal = Vector.normalise({ - x: vertex.y - prevVertex.y, - y: prevVertex.x - vertex.x - }); - - var nextNormal = Vector.normalise({ - x: nextVertex.y - vertex.y, - y: vertex.x - nextVertex.x - }); - - var diagonalRadius = Math.sqrt(2 * Math.pow(currentRadius, 2)), - radiusVector = Vector.mult(Common.clone(prevNormal), currentRadius), - midNormal = Vector.normalise(Vector.mult(Vector.add(prevNormal, nextNormal), 0.5)), - scaledVertex = Vector.sub(vertex, Vector.mult(midNormal, diagonalRadius)); - - var precision = quality; - - if (quality === -1) { - // automatically decide precision - precision = Math.pow(currentRadius, 0.32) * 1.75; - } - - precision = Common.clamp(precision, qualityMin, qualityMax); - - // use an even value for precision, more likely to reduce axes by using symmetry - if (precision % 2 === 1) - precision += 1; - - var alpha = Math.acos(Vector.dot(prevNormal, nextNormal)), - theta = alpha / precision; - - for (var j = 0; j < precision; j++) { - newVertices.push(Vector.add(Vector.rotate(radiusVector, theta * j), scaledVertex)); - } - } - - return newVertices; - }; - -})(); - -; // End src/geometry/Vertices.js - - -// Begin src/render/Render.js - -/** -* 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 Render -*/ - -// TODO: viewports -// TODO: two.js, pixi.js - -var Render = {}; - -(function() { - - /** - * Description - * @method create - * @param {object} options - * @return {render} A new renderer - */ - Render.create = function(options) { - var defaults = { - controller: Render, - element: null, - canvas: null, - options: { - width: 800, - height: 600, - background: '#fafafa', - wireframeBackground: '#222', - hasBounds: false, - enabled: true, - wireframes: true, - showSleeping: true, - showDebug: false, - showBroadphase: false, - showBounds: false, - showVelocity: false, - showCollisions: false, - showAxes: false, - showPositions: false, - showAngleIndicator: false, - showIds: false, - showShadows: false - } - }; - - var render = Common.extend(defaults, options); - - render.canvas = render.canvas || _createCanvas(render.options.width, render.options.height); - render.context = render.canvas.getContext('2d'); - render.textures = {}; - - render.bounds = render.bounds || { - min: { - x: 0, - y: 0 - }, - max: { - x: render.options.width, - y: render.options.height - } - }; - - Render.setBackground(render, render.options.background); - - if (Common.isElement(render.element)) { - render.element.appendChild(render.canvas); - } else { - Common.log('No "render.element" passed, "render.canvas" was not inserted into document.', 'warn'); - } - - return render; - }; - - /** - * Clears the renderer. In this implementation, this is a noop. - * @method clear - * @param {RenderPixi} render - */ - Render.clear = function(render) { - // nothing required to clear this renderer implentation - // if a scene graph is required, clear it here (see RenderPixi.js) - }; - - /** - * Sets the background CSS property of the canvas - * @method setBackground - * @param {render} render - * @param {string} background - */ - Render.setBackground = function(render, background) { - if (render.currentBackground !== background) { - var cssBackground = background; - - if (/(jpg|gif|png)$/.test(background)) - cssBackground = 'url(' + background + ')'; - - render.canvas.style.background = cssBackground; - render.canvas.style.backgroundSize = "contain"; - render.currentBackground = background; - } - }; - - /** - * Description - * @method world - * @param {engine} engine - */ - Render.world = function(engine) { - var render = engine.render, - world = engine.world, - canvas = render.canvas, - context = render.context, - options = render.options, - allBodies = Composite.allBodies(world), - allConstraints = Composite.allConstraints(world), - bodies = [], - constraints = [], - i; - - if (options.wireframes) { - Render.setBackground(render, options.wireframeBackground); - } else { - Render.setBackground(render, options.background); - } - - // clear the canvas with a transparent fill, to allow the canvas background to show - context.globalCompositeOperation = 'source-in'; - context.fillStyle = "transparent"; - context.fillRect(0, 0, canvas.width, canvas.height); - context.globalCompositeOperation = 'source-over'; - - // handle bounds - var boundsWidth = render.bounds.max.x - render.bounds.min.x, - boundsHeight = render.bounds.max.y - render.bounds.min.y, - boundsScaleX = boundsWidth / render.options.width, - boundsScaleY = boundsHeight / render.options.height; - - if (options.hasBounds) { - // filter out bodies that are not in view - for (i = 0; i < allBodies.length; i++) { - var body = allBodies[i]; - if (Bounds.overlaps(body.bounds, render.bounds)) - bodies.push(body); - } - - // filter out constraints that are not in view - for (i = 0; i < allConstraints.length; i++) { - var constraint = allConstraints[i], - bodyA = constraint.bodyA, - bodyB = constraint.bodyB, - pointAWorld = constraint.pointA, - pointBWorld = constraint.pointB; - - if (bodyA) pointAWorld = Vector.add(bodyA.position, constraint.pointA); - if (bodyB) pointBWorld = Vector.add(bodyB.position, constraint.pointB); - - if (!pointAWorld || !pointBWorld) - continue; - - if (Bounds.contains(render.bounds, pointAWorld) || Bounds.contains(render.bounds, pointBWorld)) - constraints.push(constraint); - } - - // transform the view - context.scale(1 / boundsScaleX, 1 / boundsScaleY); - context.translate(-render.bounds.min.x, -render.bounds.min.y); - } else { - constraints = allConstraints; - bodies = allBodies; - } - - if (!options.wireframes || (engine.enableSleeping && options.showSleeping)) { - // fully featured rendering of bodies - Render.bodies(engine, bodies, context); - } else { - // optimised method for wireframes only - Render.bodyWireframes(engine, bodies, context); - } - - if (options.showBounds) - Render.bodyBounds(engine, bodies, context); - - if (options.showAxes || options.showAngleIndicator) - Render.bodyAxes(engine, bodies, context); - - if (options.showPositions) - Render.bodyPositions(engine, bodies, context); - - if (options.showVelocity) - Render.bodyVelocity(engine, bodies, context); - - if (options.showIds) - Render.bodyIds(engine, bodies, context); - - if (options.showCollisions) - Render.collisions(engine, engine.pairs.list, context); - - Render.constraints(constraints, context); - - if (options.showBroadphase && engine.broadphase.current === 'grid') - Render.grid(engine, engine.broadphase[engine.broadphase.current].instance, context); - - if (options.showDebug) - Render.debug(engine, context); - - if (options.hasBounds) { - // revert view transforms - context.setTransform(1, 0, 0, 1, 0, 0); - } - }; - - /** - * Description - * @method debug - * @param {engine} engine - * @param {RenderingContext} context - */ - Render.debug = function(engine, context) { - var c = context, - world = engine.world, - render = engine.render, - options = render.options, - bodies = Composite.allBodies(world), - space = " "; - - if (engine.timing.timestamp - (render.debugTimestamp || 0) >= 500) { - 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; - } - - if (render.debugString) { - c.font = "12px Arial"; - - if (options.wireframes) { - c.fillStyle = 'rgba(255,255,255,0.5)'; - } else { - c.fillStyle = 'rgba(0,0,0,0.5)'; - } - - var split = render.debugString.split('\n'); - - for (var i = 0; i < split.length; i++) { - c.fillText(split[i], 50, 50 + i * 18); - } - } - }; - - /** - * Description - * @method constraints - * @param {constraint[]} constraints - * @param {RenderingContext} context - */ - Render.constraints = function(constraints, context) { - var c = context; - - for (var i = 0; i < constraints.length; i++) { - var constraint = constraints[i]; - - if (!constraint.render.visible || !constraint.pointA || !constraint.pointB) - continue; - - var bodyA = constraint.bodyA, - bodyB = constraint.bodyB; - - if (bodyA) { - c.beginPath(); - c.moveTo(bodyA.position.x + constraint.pointA.x, bodyA.position.y + constraint.pointA.y); - } else { - c.beginPath(); - c.moveTo(constraint.pointA.x, constraint.pointA.y); - } - - if (bodyB) { - c.lineTo(bodyB.position.x + constraint.pointB.x, bodyB.position.y + constraint.pointB.y); - } else { - c.lineTo(constraint.pointB.x, constraint.pointB.y); - } - - c.lineWidth = constraint.render.lineWidth; - c.strokeStyle = constraint.render.strokeStyle; - c.stroke(); - } - }; - - /** - * Description - * @method bodyShadows - * @param {engine} engine - * @param {body[]} bodies - * @param {RenderingContext} context - */ - Render.bodyShadows = function(engine, bodies, context) { - var c = context, - render = engine.render, - options = render.options; - - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - if (!body.render.visible) - continue; - - if (body.circleRadius) { - c.beginPath(); - c.arc(body.position.x, body.position.y, body.circleRadius, 0, 2 * Math.PI); - c.closePath(); - } 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(); - } - - var distanceX = body.position.x - render.options.width * 0.5, - distanceY = body.position.y - render.options.height * 0.2, - distance = Math.abs(distanceX) + Math.abs(distanceY); - - c.shadowColor = 'rgba(0,0,0,0.15)'; - c.shadowOffsetX = 0.05 * distanceX; - c.shadowOffsetY = 0.05 * distanceY; - c.shadowBlur = 1 + 12 * Math.min(1, distance / 1000); - - c.fill(); - - c.shadowColor = null; - c.shadowOffsetX = null; - c.shadowOffsetY = null; - c.shadowBlur = null; - } - }; - - /** - * Description - * @method bodies - * @param {engine} engine - * @param {body[]} bodies - * @param {RenderingContext} context - */ - Render.bodies = function(engine, bodies, context) { - var c = context, - render = engine.render, - options = render.options, - i; - - for (i = 0; i < bodies.length; i++) { - var 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); - - if (options.showSleeping && body.isSleeping) - c.globalAlpha = 0.5; - - c.translate(body.position.x, body.position.y); - c.rotate(body.angle); - - c.drawImage(texture, texture.width * -0.5 * sprite.xScale, texture.height * -0.5 * sprite.yScale, - texture.width * sprite.xScale, texture.height * sprite.yScale); - - // revert translation, hopefully faster than save / restore - c.rotate(-body.angle); - c.translate(-body.position.x, -body.position.y); - - 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); - } 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); - } else { - c.fillStyle = body.render.fillStyle; - } - - 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(); - } - } - } - - }; - - /** - * Optimised method for drawing body wireframes in one pass - * @method bodyWireframes - * @param {engine} engine - * @param {body[]} bodies - * @param {RenderingContext} context - */ - Render.bodyWireframes = function(engine, bodies, context) { - var c = context, - i, - j; - - c.beginPath(); - - for (i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - if (!body.render.visible) - continue; - - c.moveTo(body.vertices[0].x, body.vertices[0].y); - - for (j = 1; j < body.vertices.length; j++) { - c.lineTo(body.vertices[j].x, body.vertices[j].y); - } - - c.lineTo(body.vertices[0].x, body.vertices[0].y); - } - - c.lineWidth = 1; - c.strokeStyle = '#bbb'; - c.stroke(); - }; - - /** - * Draws body bounds - * @method bodyBounds - * @param {engine} engine - * @param {body[]} bodies - * @param {RenderingContext} context - */ - Render.bodyBounds = function(engine, bodies, context) { - var c = context, - render = engine.render, - options = render.options; - - c.beginPath(); - - 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 (options.wireframes) { - c.strokeStyle = 'rgba(255,255,255,0.08)'; - } else { - c.strokeStyle = 'rgba(0,0,0,0.1)'; - } - - c.lineWidth = 1; - c.stroke(); - }; - - /** - * Draws body angle indicators and axes - * @method bodyAxes - * @param {engine} engine - * @param {body[]} bodies - * @param {RenderingContext} context - */ - Render.bodyAxes = function(engine, bodies, context) { - var c = context, - render = engine.render, - options = render.options, - i, - j; - - c.beginPath(); - - for (i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - 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); - } - } 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); - } - } - - if (options.wireframes) { - c.strokeStyle = 'indianred'; - } else { - c.strokeStyle = 'rgba(0,0,0,0.3)'; - } - - c.lineWidth = 1; - c.stroke(); - }; - - /** - * Draws body positions - * @method bodyPositions - * @param {engine} engine - * @param {body[]} bodies - * @param {RenderingContext} context - */ - Render.bodyPositions = function(engine, bodies, context) { - var c = context, - render = engine.render, - options = render.options, - body, - i; - - c.beginPath(); - - // 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); - c.closePath(); - } - } - - if (options.wireframes) { - c.fillStyle = 'indianred'; - } else { - c.fillStyle = 'rgba(0,0,0,0.5)'; - } - c.fill(); - - c.beginPath(); - - // render previous positions - for (i = 0; i < bodies.length; i++) { - body = bodies[i]; - if (body.render.visible) { - c.arc(body.positionPrev.x, body.positionPrev.y, 2, 0, 2 * Math.PI, false); - c.closePath(); - } - } - - c.fillStyle = 'rgba(255,165,0,0.8)'; - c.fill(); - }; - - /** - * Draws body velocity - * @method bodyVelocity - * @param {engine} engine - * @param {body[]} bodies - * @param {RenderingContext} context - */ - Render.bodyVelocity = function(engine, bodies, context) { - var c = context, - render = engine.render, - options = render.options; - - c.beginPath(); - - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - if (!body.render.visible) - continue; - - c.moveTo(body.position.x, body.position.y); - c.lineTo(body.position.x + (body.position.x - body.positionPrev.x) * 2, body.position.y + (body.position.y - body.positionPrev.y) * 2); - } - - c.lineWidth = 3; - c.strokeStyle = 'cornflowerblue'; - c.stroke(); - }; - - /** - * Draws body ids - * @method bodyIds - * @param {engine} engine - * @param {body[]} bodies - * @param {RenderingContext} context - */ - Render.bodyIds = function(engine, bodies, context) { - var c = context; - - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - - if (!body.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); - } - }; - - /** - * Description - * @method collisions - * @param {engine} engine - * @param {pair[]} pairs - * @param {RenderingContext} context - */ - Render.collisions = function(engine, pairs, context) { - var c = context, - options = engine.render.options, - pair, - collision, - i, - j; - - c.beginPath(); - - // render collision positions - for (i = 0; i < pairs.length; i++) { - pair = pairs[i]; - collision = pair.collision; - for (j = 0; j < pair.activeContacts.length; j++) { - var contact = pair.activeContacts[j], - vertex = contact.vertex; - c.rect(vertex.x - 1.5, vertex.y - 1.5, 3.5, 3.5); - } - } - - if (options.wireframes) { - c.fillStyle = 'rgba(255,255,255,0.7)'; - } else { - c.fillStyle = 'orange'; - } - c.fill(); - - c.beginPath(); - - // render collision normals - for (i = 0; i < pairs.length; i++) { - pair = pairs[i]; - collision = pair.collision; - - if (pair.activeContacts.length > 0) { - var normalPosX = pair.activeContacts[0].vertex.x, - normalPosY = pair.activeContacts[0].vertex.y; - - if (pair.activeContacts.length === 2) { - normalPosX = (pair.activeContacts[0].vertex.x + pair.activeContacts[1].vertex.x) / 2; - normalPosY = (pair.activeContacts[0].vertex.y + pair.activeContacts[1].vertex.y) / 2; - } - - c.moveTo(normalPosX - collision.normal.x * 8, normalPosY - collision.normal.y * 8); - c.lineTo(normalPosX, normalPosY); - } - } - - if (options.wireframes) { - c.strokeStyle = 'rgba(255,165,0,0.7)'; - } else { - c.strokeStyle = 'orange'; - } - - c.lineWidth = 1; - c.stroke(); - }; - - /** - * Description - * @method grid - * @param {engine} engine - * @param {grid} grid - * @param {RenderingContext} context - */ - Render.grid = function(engine, grid, context) { - var c = context, - options = engine.render.options; - - if (options.wireframes) { - c.strokeStyle = 'rgba(255,180,0,0.1)'; - } else { - c.strokeStyle = 'rgba(255,180,0,0.5)'; - } - - c.beginPath(); - - var bucketKeys = Common.keys(grid.buckets); - - for (var i = 0; i < bucketKeys.length; i++) { - var bucketId = bucketKeys[i]; - - if (grid.buckets[bucketId].length < 2) - continue; - - var region = bucketId.split(','); - c.rect(0.5 + parseInt(region[0], 10) * grid.bucketWidth, - 0.5 + parseInt(region[1], 10) * grid.bucketHeight, - grid.bucketWidth, - grid.bucketHeight); - } - - c.lineWidth = 1; - c.stroke(); - }; - - /** - * Description - * @method inspector - * @param {inspector} inspector - * @param {RenderingContext} context - */ - Render.inspector = function(inspector, context) { - var engine = inspector.engine, - mouse = engine.input.mouse, - selected = inspector.selected, - c = context, - render = engine.render, - options = render.options, - bounds; - - if (options.hasBounds) { - var boundsWidth = render.bounds.max.x - render.bounds.min.x, - boundsHeight = render.bounds.max.y - render.bounds.min.y, - boundsScaleX = boundsWidth / render.options.width, - boundsScaleY = boundsHeight / render.options.height; - - context.scale(1 / boundsScaleX, 1 / boundsScaleY); - context.translate(-render.bounds.min.x, -render.bounds.min.y); - } - - for (var i = 0; i < selected.length; i++) { - var item = selected[i].data; - - context.translate(0.5, 0.5); - context.lineWidth = 1; - context.strokeStyle = 'rgba(255,165,0,0.9)'; - context.setLineDash([1,2]); - - switch (item.type) { - - case 'body': - - // render body selections - bounds = item.bounds; - context.beginPath(); - context.rect(Math.floor(bounds.min.x - 3), Math.floor(bounds.min.y - 3), - Math.floor(bounds.max.x - bounds.min.x + 6), Math.floor(bounds.max.y - bounds.min.y + 6)); - context.closePath(); - context.stroke(); - - break; - - case 'constraint': - - // render constraint selections - var point = item.pointA; - if (item.bodyA) - point = item.pointB; - context.beginPath(); - context.arc(point.x, point.y, 10, 0, 2 * Math.PI); - context.closePath(); - context.stroke(); - - break; - - } - - context.setLineDash([0]); - context.translate(-0.5, -0.5); - } - - // render selection region - if (inspector.selectStart !== null) { - context.translate(0.5, 0.5); - context.lineWidth = 1; - context.strokeStyle = 'rgba(255,165,0,0.6)'; - context.fillStyle = 'rgba(255,165,0,0.1)'; - bounds = inspector.selectBounds; - context.beginPath(); - context.rect(Math.floor(bounds.min.x), Math.floor(bounds.min.y), - Math.floor(bounds.max.x - bounds.min.x), Math.floor(bounds.max.y - bounds.min.y)); - context.closePath(); - context.stroke(); - context.fill(); - context.translate(-0.5, -0.5); - } - - if (options.hasBounds) - context.setTransform(1, 0, 0, 1, 0, 0); - }; - - /** - * Description - * @method _createCanvas - * @private - * @param {} width - * @param {} height - * @return canvas - */ - var _createCanvas = function(width, height) { - var canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - canvas.oncontextmenu = function() { return false; }; - canvas.onselectstart = function() { return false; }; - return canvas; - }; - - /** - * Gets the requested texture (an Image) via its path - * @method _getTexture - * @private - * @param {render} render - * @param {string} imagePath - * @return {Image} texture - */ - var _getTexture = function(render, imagePath) { - var image = render.textures[imagePath]; - - if (image) - return image; - - image = render.textures[imagePath] = new Image(); - image.src = imagePath; - - return image; - }; - -})(); - -; // End src/render/Render.js - - -// Begin src/render/RenderPixi.js - -/** -* 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 RenderPixi -*/ - -var RenderPixi = {}; - -(function() { - - /** - * Creates a new Pixi.js WebGL renderer - * @method create - * @param {object} options - * @return {RenderPixi} A new renderer - */ - RenderPixi.create = function(options) { - var defaults = { - controller: RenderPixi, - element: null, - canvas: null, - options: { - width: 800, - height: 600, - background: '#fafafa', - wireframeBackground: '#222', - enabled: true, - wireframes: true, - showSleeping: true, - showDebug: false, - showBroadphase: false, - showBounds: false, - showVelocity: false, - showCollisions: false, - showAxes: false, - showPositions: false, - showAngleIndicator: false, - showIds: false, - showShadows: false - } - }; - - var render = Common.extend(defaults, options); - - // init pixi - render.context = new PIXI.WebGLRenderer(800, 600, render.canvas, false, true); - render.canvas = render.context.view; - render.stage = new PIXI.Stage(); - - // caches - render.textures = {}; - render.sprites = {}; - render.primitives = {}; - - // use a sprite batch for performance - render.spriteBatch = new PIXI.SpriteBatch(); - render.stage.addChild(render.spriteBatch); - - // insert canvas - if (Common.isElement(render.element)) { - render.element.appendChild(render.canvas); - } else { - Common.log('No "render.element" passed, "render.canvas" was not inserted into document.', 'warn'); - } - - // prevent menus on canvas - render.canvas.oncontextmenu = function() { return false; }; - render.canvas.onselectstart = function() { return false; }; - - return render; - }; - - /** - * Clears the scene graph - * @method clear - * @param {RenderPixi} render - */ - RenderPixi.clear = function(render) { - var stage = render.stage, - spriteBatch = render.spriteBatch; - - // clear stage - while (stage.children[0]) { - stage.removeChild(stage.children[0]); - } - - // clear sprite batch - while (spriteBatch.children[0]) { - spriteBatch.removeChild(spriteBatch.children[0]); - } - - var bgSprite = render.sprites['bg-0']; - - // clear caches - render.textures = {}; - render.sprites = {}; - render.primitives = {}; - - // set background sprite - render.sprites['bg-0'] = bgSprite; - if (bgSprite) - spriteBatch.addChildAt(bgSprite, 0); - - // add sprite batch back into stage - render.stage.addChild(render.spriteBatch); - - // reset background state - render.currentBackground = null; - }; - - /** - * Sets the background of the canvas - * @method setBackground - * @param {RenderPixi} render - * @param {string} background - */ - RenderPixi.setBackground = function(render, background) { - if (render.currentBackground !== background) { - var isColor = background.indexOf && background.indexOf('#') !== -1, - bgSprite = render.sprites['bg-0']; - - if (isColor) { - // if solid background color - var color = Common.colorToNumber(background); - render.stage.setBackgroundColor(color); - - // remove background sprite if existing - if (bgSprite) - render.spriteBatch.removeChild(bgSprite); - } else { - // initialise background sprite if needed - if (!bgSprite) { - var texture = _getTexture(render, background); - - bgSprite = render.sprites['bg-0'] = new PIXI.Sprite(texture); - bgSprite.position.x = 0; - bgSprite.position.y = 0; - render.spriteBatch.addChildAt(bgSprite, 0); - } - } - - render.currentBackground = background; - } - }; - - /** - * Description - * @method world - * @param {engine} engine - */ - RenderPixi.world = function(engine) { - var render = engine.render, - world = engine.world, - context = render.context, - stage = render.stage, - options = render.options, - bodies = Composite.allBodies(world), - constraints = Composite.allConstraints(world), - i; - - if (options.wireframes) { - RenderPixi.setBackground(render, options.wireframeBackground); - } else { - RenderPixi.setBackground(render, options.background); - } - - for (i = 0; i < bodies.length; i++) - RenderPixi.body(engine, bodies[i]); - - for (i = 0; i < constraints.length; i++) - RenderPixi.constraint(engine, constraints[i]); - - context.render(stage); - }; - - - /** - * Description - * @method constraint - * @param {engine} engine - * @param {constraint} constraint - */ - RenderPixi.constraint = function(engine, constraint) { - var render = engine.render, - bodyA = constraint.bodyA, - bodyB = constraint.bodyB, - pointA = constraint.pointA, - pointB = constraint.pointB, - stage = render.stage, - constraintRender = constraint.render, - primitiveId = 'c-' + constraint.id, - primitive = render.primitives[primitiveId]; - - // initialise constraint primitive if not existing - if (!primitive) - primitive = render.primitives[primitiveId] = new PIXI.Graphics(); - - // don't render if constraint does not have two end points - if (!constraintRender.visible || !constraint.pointA || !constraint.pointB) { - primitive.clear(); - return; - } - - // add to scene graph if not already there - if (stage.children.indexOf(primitive) === -1) - stage.addChild(primitive); - - // render the constraint on every update, since they can change dynamically - primitive.clear(); - primitive.beginFill(0, 0); - primitive.lineStyle(constraintRender.lineWidth, Common.colorToNumber(constraintRender.strokeStyle), 1); - - if (bodyA) { - primitive.moveTo(bodyA.position.x + pointA.x, bodyA.position.y + pointA.y); - } else { - primitive.moveTo(pointA.x, pointA.y); - } - - if (bodyB) { - primitive.lineTo(bodyB.position.x + pointB.x, bodyB.position.y + pointB.y); - } else { - primitive.lineTo(pointB.x, pointB.y); - } - - primitive.endFill(); - }; - - /** - * Description - * @method body - * @param {engine} engine - * @param {body} body - */ - RenderPixi.body = function(engine, body) { - var render = engine.render, - bodyRender = body.render; - - if (!bodyRender.visible) - return; - - if (bodyRender.sprite && bodyRender.sprite.texture) { - var spriteId = 'b-' + body.id, - sprite = render.sprites[spriteId], - spriteBatch = render.spriteBatch; - - // initialise body sprite if not existing - if (!sprite) - sprite = render.sprites[spriteId] = _createBodySprite(render, body); - - // add to scene graph if not already there - if (spriteBatch.children.indexOf(sprite) === -1) - spriteBatch.addChild(sprite); - - // update body sprite - sprite.position.x = body.position.x; - sprite.position.y = body.position.y; - sprite.rotation = body.angle; - } else { - var primitiveId = 'b-' + body.id, - primitive = render.primitives[primitiveId], - stage = render.stage; - - // initialise body primitive if not existing - if (!primitive) { - primitive = render.primitives[primitiveId] = _createBodyPrimitive(render, body); - primitive.initialAngle = body.angle; - } - - // add to scene graph if not already there - if (stage.children.indexOf(primitive) === -1) - stage.addChild(primitive); - - // update body primitive - primitive.position.x = body.position.x; - primitive.position.y = body.position.y; - primitive.rotation = body.angle - primitive.initialAngle; - } - }; - - /** - * Creates a body sprite - * @method _createBodySprite - * @private - * @param {RenderPixi} render - * @param {body} body - * @return {PIXI.Sprite} sprite - */ - var _createBodySprite = function(render, body) { - var bodyRender = body.render, - texturePath = bodyRender.sprite.texture, - texture = _getTexture(render, texturePath), - sprite = new PIXI.Sprite(texture); - - sprite.anchor.x = 0.5; - sprite.anchor.y = 0.5; - - return sprite; - }; - - /** - * Creates a body primitive - * @method _createBodyPrimitive - * @private - * @param {RenderPixi} render - * @param {body} body - * @return {PIXI.Graphics} graphics - */ - var _createBodyPrimitive = function(render, body) { - var bodyRender = body.render, - options = render.options, - primitive = new PIXI.Graphics(); - - 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); - } - - 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); - } else { - primitive.lineStyle(1, Common.colorToNumber(body.render.strokeStyle)); - } - - 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.endFill(); - } - - return primitive; - }; - - /** - * Gets the requested texture (a PIXI.Texture) via its path - * @method _getTexture - * @private - * @param {RenderPixi} render - * @param {string} imagePath - * @return {PIXI.Texture} texture - */ - var _getTexture = function(render, imagePath) { - var texture = render.textures[imagePath]; - - if (!texture) - texture = render.textures[imagePath] = PIXI.Texture.fromImage(imagePath); - - return texture; - }; - -})(); - -; // End src/render/RenderPixi.js - - -// aliases - -World.add = Composite.add; -World.remove = Composite.remove; -World.addComposite = Composite.addComposite; -World.addBody = Composite.addBody; -World.addConstraint = Composite.addConstraint; -World.clear = Composite.clear; - -// exports - -Matter.Body = Body; -Matter.Composite = Composite; -Matter.World = World; -Matter.Contact = Contact; -Matter.Detector = Detector; -Matter.Grid = Grid; -Matter.Pairs = Pairs; -Matter.Pair = Pair; -Matter.Resolver = Resolver; -Matter.SAT = SAT; -Matter.Constraint = Constraint; -Matter.MouseConstraint = MouseConstraint; -Matter.Common = Common; -Matter.Engine = Engine; -Matter.Metrics = Metrics; -Matter.Mouse = Mouse; -Matter.Sleeping = Sleeping; -Matter.Bodies = Bodies; -Matter.Composites = Composites; -Matter.Axes = Axes; -Matter.Bounds = Bounds; -Matter.Vector = Vector; -Matter.Vertices = Vertices; -Matter.Render = Render; -Matter.RenderPixi = RenderPixi; -Matter.Events = Events; -Matter.Query = Query; - -// CommonJS module -if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = Matter; - } - exports.Matter = Matter; -} - -// AMD module -if (typeof define === 'function' && define.amd) { - define('Matter', [], function () { - return Matter; - }); -} - -// browser -if (typeof window === 'object' && typeof window.document === 'object') { - window.Matter = Matter; -} - -// End Matter namespace closure - -})(); \ No newline at end of file diff --git a/build/matter-0.8.0.min.js b/build/matter-0.8.0.min.js deleted file mode 100644 index f4851903..00000000 --- a/build/matter-0.8.0.min.js +++ /dev/null @@ -1,8 +0,0 @@ -/** -* matter-0.8.0.min.js 0.8.0-alpha 2014-05-04 -* http://brm.io/matter-js/ -* License: MIT -*/ - -!function(){var a={},b={};!function(){var a=1;b.create=function(a){var b={id:o.nextId(),type:"body",label:"Body",angle:0,vertices:z.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,groupId:0,slop:.05,timeScale:1,render:{visible:!0,sprite:{xScale:1,yScale:1},lineWidth:1.5}},d=o.extend(b,a);return c(d),d},b.nextGroupId=function(){return a++};var c=function(a){a.axes=a.axes||w.fromVertices(a.vertices),a.area=z.area(a.vertices),a.bounds=x.create(a.vertices),a.mass=a.mass||a.density*a.area,a.inverseMass=1/a.mass,a.inertia=a.inertia||z.inertia(a.vertices,a.mass),a.inverseInertia=1/a.inertia,a.positionPrev=a.positionPrev||{x:a.position.x,y:a.position.y},a.anglePrev=a.anglePrev||a.angle,a.render.fillStyle=a.render.fillStyle||(a.isStatic?"#eeeeee":o.choose(["#556270","#4ECDC4","#C7F464","#FF6B6B","#C44D58"])),a.render.strokeStyle=a.render.strokeStyle||o.shadeColor(a.render.fillStyle,-20),z.create(a.vertices,a);var c=z.centre(a.vertices);z.translate(a.vertices,a.position),z.translate(a.vertices,c,-1),z.rotate(a.vertices,a.angle,a.position),w.rotate(a.axes,a.angle),x.update(a.bounds,a.vertices,a.velocity),b.setStatic(a,a.isStatic),t.set(a,a.isSleeping)};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.render.lineWidth=1,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.resetForcesAll=function(a){for(var b=0;bf.max.x||h.bounds.max.yf.max.y||b.update(h,c,d,e)}},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=y.magnitude(a.velocity),a.angularSpeed=Math.abs(a.angularVelocity),z.translate(a.vertices,a.velocity),0!==a.angularVelocity&&(z.rotate(a.vertices,a.angularVelocity,a.position),w.rotate(a.axes,a.angularVelocity)),x.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},b.translate=function(a,b){a.positionPrev.x+=b.x,a.positionPrev.y+=b.y,a.position.x+=b.x,a.position.y+=b.y,z.translate(a.vertices,b),x.update(a.bounds,a.vertices,a.velocity)},b.rotate=function(a,b){a.anglePrev+=b,a.angle+=b,z.rotate(a.vertices,b,a.position),w.rotate(a.axes,b),x.update(a.bounds,a.vertices,a.velocity)},b.scale=function(a,b,c,d){z.scale(a.vertices,b,c,d),a.axes=w.fromVertices(a.vertices),a.area=z.area(a.vertices),a.mass=a.density*a.area,a.inverseMass=1/a.mass,z.translate(a.vertices,{x:-a.position.x,y:-a.position.y}),a.inertia=z.inertia(a.vertices,a.mass),a.inverseInertia=1/a.inertia,z.translate(a.vertices,{x:a.position.x,y:a.position.y}),x.update(a.bounds,a.vertices,a.velocity)}}();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;fq.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||k){s.broadphaseTests+=1,(!u.region||k)&&(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&&i(c,o,u),(u.region===v||x&&!y||k)&&(o||(o=e(r,p)),f(c,o,u))}u.region=v,t=!0}}}t&&(c.pairsList=j(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},f=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,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=y.cross(s,k),G=y.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+=y.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-=y.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=y.neg(i.normal)),i.tangent=y.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=[n[0]];if(z.contains(b.vertices,n[1]))o.push(n[1]);else{var p=c(d,b,y.neg(i.normal));z.contains(d.vertices,p[0])&&o.push(p[0]),o.length<2&&z.contains(d.vertices,p[1])&&o.push(p[1])}return i.supports=o,i.supportCorrected=y.sub(n[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=-y.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=-y.dot(c,g),f>d&&(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?y.add(c.bodyA.position,c.pointA):c.pointA,e=c.bodyB?y.add(c.bodyB.position,c.pointB):c.pointB,f=y.magnitude(y.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=y.cross(s,D)*e.inverseInertia*(1-c.angularStiffness),t.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=y.cross(u,D)*f.inverseInertia*(1-c.angularStiffness),t.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(Math.random()*(b+1)),d=a[b];a[b]=a[c],a[c]=d}return a},o.choose=function(a){return a[Math.floor(Math.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(a,b){return a+Math.random()*(b-a)},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){var c;switch(b){case"warn":c="color: coral";break;case"error":c="color: red"}console.log("%c [Matter] "+b+": "+a,c)}},o.nextId=function(){return o._nextId++}}();var p={};!function(){var a=60,e=a,h=1e3/a,j=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(function(){a(o.now())},h)};p.create=function(b,c){c=o.isElement(b)?c:b,b=o.isElement(b)?b:null;var e={enabled:!0,positionIterations:6,velocityIterations:4,constraintIterations:2,enableSleeping:!1,timeScale:1,input:{},events:[],timing:{fps:a,timestamp:0,delta:h,correction:1,deltaMin:1e3/a,deltaMax:1e3/(.5*a),timeScale:1,isFixed:!1},render:{element:b,controller:A}},j=o.extend(e,c);return j.render=j.render.controller.create(j.render),j.world=d.create(j.world),j.pairs=i.create(),j.metrics=j.metrics||r.create(),j.input.mouse=j.input.mouse||s.create(j.render.canvas),j.broadphase=j.broadphase||{current:"grid",grid:{controller:g,instance:g.create(),detector:f.collisions},bruteForce:{detector:f.bruteForce}},j},p.run=function(a){var b,c=0,d=0,f=[],g=1;!function h(i){if(j(h),a.enabled){var k,m,o=a.timing,r={timestamp:i};q.trigger(a,"beforeTick",r),o.isFixed?k=o.delta:(k=i-b||o.delta,b=i,f.push(k),f=f.slice(-e),k=Math.min.apply(null,f),k=ko.deltaMax?o.deltaMax:k,m=k/o.delta,o.delta=k),0!==g&&(m*=o.timeScale/g),0===o.timeScale&&(m=0),g=o.timeScale,d+=1,i-c>=1e3&&(o.fps=d*((i-c)/1e3),c=i,d=0),q.trigger(a,"tick",r),a.world.isModified&&a.render.controller.clear(a.render),p.update(a,k,m),n(a),l(a),p.render(a),q.trigger(a,"afterTick",r)}}()},p.update=function(a,d,e){e="undefined"!=typeof e?e:1;var f,g=a.world,h=a.timing,j=a.broadphase[a.broadphase.current],l=[];h.timestamp+=d*h.timeScale,h.correction=e;var n={timestamp:a.timing.timestamp};q.trigger(a,"beforeUpdate",n);var o=c.allBodies(g),p=c.allConstraints(g);for(r.reset(a.metrics),a.enableSleeping&&t.update(o),b.applyGravityAll(o,g.gravity),b.updateAll(o,d,h.timeScale,e,g.bounds),f=0;f0&&q.trigger(a,"collisionStart",{pairs:b.collisionStart}),b.collisionActive.length>0&&q.trigger(a,"collisionActive",{pairs:b.collisionActive}),b.collisionEnd.length>0&&q.trigger(a,"collisionEnd",{pairs:b.collisionEnd})}}();var q={};!function(){q.on=function(a,b,c){for(var d,e=b.split(" "),f=0;f0||e.force.y>0)t.set(e,!1);else{var g=Math.min(e.motion,f),h=Math.max(e.motion,f);e.motion=c*g+(1-c)*h,e.sleepThreshold>0&&e.motion=e.sleepThreshold&&t.set(e,!0)):e.sleepCounter>0&&(e.sleepCounter-=1)}}},t.afterCollisions=function(b){for(var c=0;ca&&t.set(h,!1)}}}},t.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 u={};!function(){u.rectangle=function(a,c,d,e,f){f=f||{};var g={label:"Rectangle Body",position:{x:a,y:c},vertices:z.fromPath("L 0 0 L "+d+" 0 L "+d+" "+e+" L 0 "+e)};if(f.chamfer){var h=f.chamfer;g.vertices=z.chamfer(g.vertices,h.radius,h.quality,h.qualityMin,h.qualityMax),delete f.chamfer}return b.create(o.extend({},g,f))},u.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:z.fromPath("L 0 0 L "+i+" "+-e+" L "+j+" "+-e+" L "+k+" 0")}; -if(g.chamfer){var m=g.chamfer;l.vertices=z.chamfer(l.vertices,m.radius,m.quality,m.qualityMin,m.qualityMax),delete g.chamfer}return b.create(o.extend({},l,g))},u.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,u.polygon(a,b,f,c,d)},u.polygon=function(a,c,d,e,f){if(f=f||{},3>d)return u.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:z.fromPath(h)};if(f.chamfer){var p=f.chamfer;n.vertices=z.chamfer(n.vertices,p.radius,p.quality,p.qualityMin,p.qualityMax),delete f.chamfer}return b.create(o.extend({},n,f))}}();var v={};!function(){v.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},v.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},v.pyramid=function(a,c,d,e,f,g,h){return v.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)}}})},v.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=u.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},v.car=function(a,d,e,f,g){var h=b.nextGroupId(),i=-20,j=.5*-e+i,k=.5*e-i,l=0,n=c.create({label:"Car"}),o=u.trapezoid(a,d,e,f,.3,{groupId:h,friction:.01,chamfer:{radius:10}}),p=u.circle(a+j,d+l,g,{groupId:h,restitution:.5,friction:.9,density:.01}),q=u.circle(a+k,d+l,g,{groupId: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},v.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=v.stack(a,b,c,d,e,f,function(a,b){return u.circle(a,b,h,i)});return v.mesh(k,c,d,g,j),k.label="Soft Body",k}}();var w={};!function(){w.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)},x.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},x.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},x.translate=function(a,b){a.min.x+=b.x,a.max.x+=b.x,a.min.y+=b.y,a.max.y+=b.y},x.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 y={};!function(){y.magnitude=function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},y.magnitudeSquared=function(a){return a.x*a.x+a.y*a.y},y.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}},y.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)}},y.normalise=function(a){var b=y.magnitude(a);return 0===b?{x:0,y:0}:{x:a.x/b,y:a.y/b}},y.dot=function(a,b){return a.x*b.x+a.y*b.y},y.cross=function(a,b){return a.x*b.y-a.y*b.x},y.add=function(a,b){return{x:a.x+b.x,y:a.y+b.y}},y.sub=function(a,b){return{x:a.x-b.x,y:a.y-b.y}},y.mult=function(a,b){return{x:a.x*b,y:a.y*b}},y.div=function(a,b){return{x:a.x/b,y:a.y/b}},y.perp=function(a,b){return b=b===!0?-1:1,{x:b*-a.y,y:b*a.x}},y.neg=function(a){return{x:-a.x,y:-a.y}},y.angle=function(a,b){return Math.atan2(b.y-a.y,b.x-a.x)}}();var z={};!function(){z.create=function(a,b){for(var c=0;c0)return!1}return!0},z.scale=function(a,b,c,d){if(1===b&&1===c)return a;d=d||z.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(y.add(y.rotate(p,u*v),r))}else f.push(i)}return f}}();var A={};!function(){A.create=function(b){var c={controller:A,element:null,canvas:null,options:{width:800,height:600,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}},A.setBackground(d,d.options.background),o.isElement(d.element)?d.element.appendChild(d.canvas):o.log('No "render.element" passed, "render.canvas" was not inserted into document.',"warn"),d},A.clear=function(){},A.setBackground=function(a,b){if(a.currentBackground!==b){var c=b;/(jpg|gif|png)$/.test(b)&&(c="url("+b+")"),a.canvas.style.background=c,a.canvas.style.backgroundSize="contain",a.currentBackground=b}},A.world=function(a){var b,d=a.render,e=a.world,f=d.canvas,g=d.context,h=d.options,i=c.allBodies(e),j=c.allConstraints(e),k=[],l=[];h.wireframes?A.setBackground(d,h.wireframeBackground):A.setBackground(d,h.background),g.globalCompositeOperation="source-in",g.fillStyle="transparent",g.fillRect(0,0,f.width,f.height),g.globalCompositeOperation="source-over";var m=d.bounds.max.x-d.bounds.min.x,n=d.bounds.max.y-d.bounds.min.y,o=m/d.options.width,p=n/d.options.height;if(h.hasBounds){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()},A.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;g