diff --git a/src/physics/matter-js/World.js b/src/physics/matter-js/World.js index a24e15c8f..7afd37c34 100644 --- a/src/physics/matter-js/World.js +++ b/src/physics/matter-js/World.js @@ -110,20 +110,6 @@ var World = new Class({ */ this.enabled = GetValue(config, 'enabled', true); - /** - * The correction argument is an optional Number that specifies the time correction factor to apply to the update. - * This can help improve the accuracy of the simulation in cases where delta is changing between updates. - * The value of correction is defined as delta / lastDelta, i.e. the percentage change of delta over the last step. - * Therefore the value is always 1 (no correction) when delta is constant (or when no correction is desired, which is the default). - * See the paper on Time Corrected Verlet for more information. - * - * @name Phaser.Physics.Matter.World#correction - * @type {number} - * @default 1 - * @since 3.4.0 - */ - this.correction = GetValue(config, 'correction', 1); - /** * This function is called every time the core game loop steps, which is bound to the * Request Animation Frame frequency unless otherwise modified. @@ -181,7 +167,6 @@ var World = new Class({ */ this.runner = { fps: fps, - correction: GetFastValue(runnerConfig, 'correction', 1), deltaSampleSize: GetFastValue(runnerConfig, 'deltaSampleSize', 60), counterTimestamp: 0, frameCounter: 0, @@ -1122,7 +1107,6 @@ var World = new Class({ var runner = this.runner; var timing = engine.timing; - var correction = this.correction; if (runner.isFixed) { @@ -1144,26 +1128,11 @@ var World = new Class({ delta = delta < runner.deltaMin ? runner.deltaMin : delta; delta = delta > runner.deltaMax ? runner.deltaMax : delta; - // correction for delta - correction = delta / runner.delta; - // update engine timing object runner.delta = delta; } - // time correction for time scaling - if (runner.timeScalePrev !== 0) - { - correction *= timing.timeScale / runner.timeScalePrev; - } - - if (timing.timeScale === 0) - { - correction = 0; - } - runner.timeScalePrev = timing.timeScale; - runner.correction = correction; // fps counter runner.frameCounter += 1; @@ -1175,7 +1144,7 @@ var World = new Class({ runner.frameCounter = 0; } - Engine.update(engine, delta, correction); + Engine.update(engine, delta); }, /** @@ -1203,11 +1172,10 @@ var World = new Class({ * @since 3.4.0 * * @param {number} [delta=16.666] - The delta value. - * @param {number} [correction=1] - Optional delta correction value. */ - step: function (delta, correction) + step: function (delta) { - Engine.update(this.engine, delta, correction); + Engine.update(this.engine, delta); }, /** diff --git a/src/physics/matter-js/lib/body/Body.js b/src/physics/matter-js/lib/body/Body.js index fd4084fbd..83202139b 100644 --- a/src/physics/matter-js/lib/body/Body.js +++ b/src/physics/matter-js/lib/body/Body.js @@ -20,10 +20,12 @@ var Axes = require('../geometry/Axes'); (function() { + Body._timeCorrection = true; Body._inertiaScale = 4; Body._nextCollidingGroupId = 1; Body._nextNonCollidingGroupId = -1; Body._nextCategory = 0x0001; + Body._baseDelta = 1000 / 60; /** * Creates a new rigid body model. The options parameter is an object that specifies any properties you wish to override the defaults. @@ -82,6 +84,7 @@ var Axes = require('../geometry/Axes'); mass: 0, inverseMass: 0, inertia: 0, + deltaTime: 1000 / 60, inverseInertia: 0, _original: null, render: { @@ -189,11 +192,9 @@ var Axes = require('../geometry/Axes'); parent: body.parent || body }); - var bounds = body.bounds; - Vertices.rotate(body.vertices, body.angle, body.position); Axes.rotate(body.axes, body.angle); - Bounds.update(bounds, body.vertices, body.velocity); + Bounds.update(body.bounds, body.vertices, body.velocity); // allow options to override the automatically calculated properties Body.set(body, { @@ -205,6 +206,8 @@ var Axes = require('../geometry/Axes'); if (body.parts.length === 1) { + var bounds = body.bounds; + var centerOfMass = body.centerOfMass; var centerOffset = body.centerOffset; @@ -217,6 +220,10 @@ var Axes = require('../geometry/Axes'); centerOffset.x = bodyWidth * centerOfMass.x; centerOffset.y = bodyHeight * centerOfMass.y; } + + // From Matter render code: + // body.render.sprite.xOffset += -(body.bounds.min.x - body.position.x) / (body.bounds.max.x - body.bounds.min.x); + // body.render.sprite.yOffset += -(body.bounds.min.y - body.position.y) / (body.bounds.max.y - body.bounds.min.y); }; /** @@ -273,6 +280,12 @@ var Axes = require('../geometry/Axes'); case 'angularVelocity': Body.setAngularVelocity(body, value); break; + case 'speed': + Body.setSpeed(body, value); + break; + case 'angularSpeed': + Body.setAngularSpeed(body, value); + break; case 'parts': Body.setParts(body, value); break; @@ -294,9 +307,9 @@ var Axes = require('../geometry/Axes'); Body.setStatic = function(body, isStatic) { for (var i = 0; i < body.parts.length; i++) { var part = body.parts[i]; - part.isStatic = isStatic; if (isStatic) { + if (!part.isStatic) { part._original = { restitution: part.restitution, friction: part.friction, @@ -306,6 +319,7 @@ var Axes = require('../geometry/Axes'); inverseMass: part.inverseMass, inverseInertia: part.inverseInertia }; + } part.restitution = 0; part.friction = 1; @@ -330,6 +344,7 @@ var Axes = require('../geometry/Axes'); part._original = null; } + part.isStatic = isStatic; } }; @@ -484,6 +499,12 @@ var Axes = require('../geometry/Axes'); body.positionPrev.x = cx; body.positionPrev.y = cy; + // Matter.js original + // body.position.x = total.centre.x; + // body.position.y = total.centre.y; + // body.positionPrev.x = total.centre.x; + // body.positionPrev.y = total.centre.y; + Body.setMass(body, total.mass); Body.setInertia(body, total.inertia); Body.setPosition(body, total.centre); @@ -520,10 +541,18 @@ var Axes = require('../geometry/Axes'); * @param {body} body * @param {vector} position */ - Body.setPosition = function(body, position) { + Body.setPosition = function(body, position, updateVelocity) { var delta = Vector.sub(position, body.position); + if (updateVelocity) { + body.positionPrev.x = body.position.x; + body.positionPrev.y = body.position.y; + body.velocity.x = delta.x; + body.velocity.y = delta.y; + body.speed = Vector.magnitude(delta); + } else { body.positionPrev.x += delta.x; body.positionPrev.y += delta.y; + } for (var i = 0; i < body.parts.length; i++) { var part = body.parts[i]; @@ -540,9 +569,15 @@ var Axes = require('../geometry/Axes'); * @param {body} body * @param {number} angle */ - Body.setAngle = function(body, angle) { + Body.setAngle = function(body, angle, updateVelocity) { var delta = angle - body.angle; + if (updateVelocity) { + body.anglePrev = body.angle; + body.angularVelocity = delta; + body.angularSpeed = Math.abs(delta); + } else { body.anglePrev += delta; + } for (var i = 0; i < body.parts.length; i++) { var part = body.parts[i]; @@ -563,13 +598,51 @@ var Axes = require('../geometry/Axes'); * @param {vector} velocity */ Body.setVelocity = function(body, velocity) { - body.positionPrev.x = body.position.x - velocity.x; - body.positionPrev.y = body.position.y - velocity.y; - body.velocity.x = velocity.x; - body.velocity.y = velocity.y; + var timeScale = body.deltaTime / Body._baseDelta; + body.positionPrev.x = body.position.x - velocity.x * timeScale; + body.positionPrev.y = body.position.y - velocity.y * timeScale; + body.velocity.x = (body.position.x - body.positionPrev.x) / timeScale; + body.velocity.y = (body.position.y - body.positionPrev.y) / timeScale; body.speed = Vector.magnitude(body.velocity); }; + /** + * Gets the current linear velocity of the body. + * @method getVelocity + * @param {body} body + * @return {vector} velocity + */ + Body.getVelocity = function(body) { + var timeScale = Body._baseDelta / body.deltaTime; + + return { + x: (body.position.x - body.positionPrev.x) * timeScale, + y: (body.position.y - body.positionPrev.y) * timeScale + }; + }; + + /** + * Gets the current linear speed of the body. + * Equivalent to the magnitude of its velocity. + * @method getSpeed + * @param {body} body + * @return {number} speed + */ + Body.getSpeed = function(body) { + return Vector.magnitude(Body.getVelocity(body)); + }; + + /** + * Sets the current linear speed of the body. + * Direction is maintained. Affects body velocity. + * @method setSpeed + * @param {body} body + * @param {number} speed + */ + Body.setSpeed = function(body, speed) { + Body.setVelocity(body, Vector.mult(Vector.normalise(Body.getVelocity(body)), speed)); + }; + /** * Sets the angular velocity of the body instantly. Position, angle, force etc. are unchanged. See also `Body.applyForce`. * @method setAngularVelocity @@ -577,19 +650,52 @@ var Axes = require('../geometry/Axes'); * @param {number} velocity */ Body.setAngularVelocity = function(body, velocity) { - body.anglePrev = body.angle - velocity; - body.angularVelocity = velocity; + var timeScale = body.deltaTime / Body._baseDelta; + body.anglePrev = body.angle - velocity * timeScale; + body.angularVelocity = (body.angle - body.anglePrev) / timeScale; body.angularSpeed = Math.abs(body.angularVelocity); }; + /** + * Gets the current rotational velocity of the body. + * @method getAngularVelocity + * @param {body} body + * @return {number} angular velocity + */ + Body.getAngularVelocity = function(body) { + return (body.angle - body.anglePrev) * Body._baseDelta / body.deltaTime; + }; + + /** + * Gets the current rotational speed of the body. + * Equivalent to the magnitude of its angular velocity. + * @method getAngularSpeed + * @param {body} body + * @return {number} angular speed + */ + Body.getAngularSpeed = function(body) { + return Math.abs(Body.getAngularVelocity(body)); + }; + + /** + * Sets the current rotational speed of the body. + * Direction is maintained. Affects body angular velocity. + * @method setAngularSpeed + * @param {body} body + * @param {number} speed + */ + Body.setAngularSpeed = function(body, speed) { + Body.setAngularVelocity(body, Common.sign(Body.getAngularVelocity(body)) * speed); + }; + /** * Moves a body by a given vector relative to its current position, without imparting any velocity. * @method translate * @param {body} body * @param {vector} translation */ - Body.translate = function(body, translation) { - Body.setPosition(body, Vector.add(body.position, translation)); + Body.translate = function(body, translation, updateVelocity) { + Body.setPosition(body, Vector.add(body.position, translation), updateVelocity); }; /** @@ -599,9 +705,9 @@ var Axes = require('../geometry/Axes'); * @param {number} rotation * @param {vector} [point] */ - Body.rotate = function(body, rotation, point) { + Body.rotate = function(body, rotation, point, updateVelocity) { if (!point) { - Body.setAngle(body, body.angle + rotation); + Body.setAngle(body, body.angle + rotation, updateVelocity); } else { var cos = Math.cos(rotation), sin = Math.sin(rotation), @@ -611,9 +717,9 @@ var Axes = require('../geometry/Axes'); Body.setPosition(body, { x: point.x + (dx * cos - dy * sin), y: point.y + (dx * sin + dy * cos) - }); + }, updateVelocity); - Body.setAngle(body, body.angle + rotation); + Body.setAngle(body, body.angle + rotation, updateVelocity); } }; @@ -692,22 +798,25 @@ var Axes = require('../geometry/Axes'); * @param {number} timeScale * @param {number} correction */ - Body.update = function(body, deltaTime, timeScale, correction) { - var deltaTimeSquared = Math.pow(deltaTime * timeScale * body.timeScale, 2); + Body.update = function(body, deltaTime) { + deltaTime = (typeof deltaTime !== 'undefined' ? deltaTime : (1000 / 60)) * body.timeScale; + var deltaTimeSquared = deltaTime * deltaTime, + correction = Body._timeCorrection ? deltaTime / (body.deltaTime || deltaTime) : 1; // 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; + var frictionAir = 1 - body.frictionAir * (deltaTime / Common._baseDelta), + velocityPrevX = (body.position.x - body.positionPrev.x) * correction, + velocityPrevY = (body.position.y - body.positionPrev.y) * correction; // 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.velocity.x = (velocityPrevX * frictionAir) + (body.force.x / body.mass) * deltaTimeSquared; + body.velocity.y = (velocityPrevY * frictionAir) + (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; + body.deltaTime = deltaTime; // update angular velocity with Verlet integration body.angularVelocity = ((body.angle - body.anglePrev) * frictionAir * correction) + (body.torque / body.inertia) * deltaTimeSquared; @@ -741,6 +850,23 @@ var Axes = require('../geometry/Axes'); } }; + /** + * Updates properties `body.velocity`, `body.speed`, `body.angularVelocity` and `body.angularSpeed` which are normalised in relation to `Body._baseDelta`. + * @method updateVelocities + * @param {body} body + */ + Body.updateVelocities = function(body) { + var timeScale = Body._baseDelta / body.deltaTime, + bodyVelocity = body.velocity; + + bodyVelocity.x = (body.position.x - body.positionPrev.x) * timeScale; + bodyVelocity.y = (body.position.y - body.positionPrev.y) * timeScale; + body.speed = Math.sqrt((bodyVelocity.x * bodyVelocity.x) + (bodyVelocity.y * bodyVelocity.y)); + + body.angularVelocity = (body.angle - body.anglePrev) * timeScale; + body.angularSpeed = Math.abs(body.angularVelocity); + }; + /** * Applies a force to a body from a given world-space position, including resulting torque. * @method applyForce @@ -749,9 +875,9 @@ var Axes = require('../geometry/Axes'); * @param {vector} force */ Body.applyForce = function(body, position, force) { + var offset = { x: position.x - body.position.x, y: position.y - body.position.y }; 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; }; diff --git a/src/physics/matter-js/lib/collision/Resolver.js b/src/physics/matter-js/lib/collision/Resolver.js index ba69d26d6..2f161db6f 100644 --- a/src/physics/matter-js/lib/collision/Resolver.js +++ b/src/physics/matter-js/lib/collision/Resolver.js @@ -9,15 +9,17 @@ var Resolver = {}; module.exports = Resolver; var Vertices = require('../geometry/Vertices'); +var Common = require('../core/Common'); var Bounds = require('../geometry/Bounds'); (function() { - Resolver._restingThresh = 4; - Resolver._restingThreshTangent = 6; + Resolver._restingThresh = 2; + Resolver._restingThreshTangent = Math.sqrt(6); Resolver._positionDampen = 0.9; Resolver._positionWarming = 0.8; Resolver._frictionNormalMultiplier = 5; + Resolver._frictionMaxStatic = Number.MAX_VALUE; /** * Prepare pairs for position solving. @@ -33,10 +35,10 @@ var Bounds = require('../geometry/Bounds'); // find total contacts on each body for (i = 0; i < pairsLength; i++) { pair = pairs[i]; - + if (!pair.isActive) continue; - + activeCount = pair.activeContacts.length; pair.collision.parentA.totalContacts += activeCount; pair.collision.parentB.totalContacts += activeCount; @@ -47,9 +49,10 @@ var Bounds = require('../geometry/Bounds'); * Find a solution for pair positions. * @method solvePosition * @param {pair[]} pairs - * @param {number} timeScale + * @param {number} delta + * @param {number} [damping=1] */ - Resolver.solvePosition = function(pairs, timeScale) { + Resolver.solvePosition = function(pairs, delta, damping) { var i, pair, collision, @@ -58,13 +61,14 @@ var Bounds = require('../geometry/Bounds'); normal, contactShare, positionImpulse, - positionDampen = Resolver._positionDampen, + positionDampen = Resolver._positionDampen * (damping || 1), + slopDampen = Common.clamp(delta / Common._baseDelta, 0, 1), pairsLength = pairs.length; // find impulses required to resolve penetration for (i = 0; i < pairsLength; i++) { pair = pairs[i]; - + if (!pair.isActive || pair.isSensor) continue; @@ -74,26 +78,26 @@ var Bounds = require('../geometry/Bounds'); normal = collision.normal; // get current separation between body edges involved in collision - pair.separation = + pair.separation = normal.x * (bodyB.positionImpulse.x + collision.penetration.x - bodyA.positionImpulse.x) + normal.y * (bodyB.positionImpulse.y + collision.penetration.y - bodyA.positionImpulse.y); } - + for (i = 0; i < pairsLength; i++) { pair = pairs[i]; if (!pair.isActive || pair.isSensor) continue; - + collision = pair.collision; bodyA = collision.parentA; bodyB = collision.parentB; normal = collision.normal; - positionImpulse = (pair.separation - pair.slop) * timeScale; + positionImpulse = pair.separation - pair.slop * slopDampen; if (bodyA.isStatic || bodyB.isStatic) positionImpulse *= 2; - + if (!(bodyA.isStatic || bodyA.isSleeping)) { contactShare = positionDampen / bodyA.totalContacts; bodyA.positionImpulse.x += normal.x * positionImpulse * contactShare; @@ -165,13 +169,13 @@ var Bounds = require('../geometry/Bounds'); var pairsLength = pairs.length, i, j; - + for (i = 0; i < pairsLength; i++) { var pair = pairs[i]; - + if (!pair.isActive || pair.isSensor) continue; - + var contacts = pair.activeContacts, contactsLength = contacts.length, collision = pair.collision, @@ -179,19 +183,19 @@ var Bounds = require('../geometry/Bounds'); bodyB = collision.parentB, normal = collision.normal, tangent = collision.tangent; - + // resolve each contact for (j = 0; j < contactsLength; j++) { var contact = contacts[j], contactVertex = contact.vertex, normalImpulse = contact.normalImpulse, tangentImpulse = contact.tangentImpulse; - + if (normalImpulse !== 0 || tangentImpulse !== 0) { // total impulse from contact var impulseX = normal.x * normalImpulse + tangent.x * tangentImpulse, impulseY = normal.y * normalImpulse + tangent.y * tangentImpulse; - + // apply impulse from contact if (!(bodyA.isStatic || bodyA.isSleeping)) { bodyA.positionPrev.x += impulseX * bodyA.inverseMass; @@ -201,12 +205,12 @@ var Bounds = require('../geometry/Bounds'); - (contactVertex.y - bodyA.position.y) * impulseX ); } - + if (!(bodyB.isStatic || bodyB.isSleeping)) { bodyB.positionPrev.x -= impulseX * bodyB.inverseMass; bodyB.positionPrev.y -= impulseY * bodyB.inverseMass; bodyB.anglePrev -= bodyB.inverseInertia * ( - (contactVertex.x - bodyB.position.x) * impulseY + (contactVertex.x - bodyB.position.x) * impulseY - (contactVertex.y - bodyB.position.y) * impulseX ); } @@ -221,12 +225,14 @@ var Bounds = require('../geometry/Bounds'); * @param {pair[]} pairs * @param {number} timeScale */ - Resolver.solveVelocity = function(pairs, timeScale) { - var timeScaleSquared = timeScale * timeScale, - restingThresh = Resolver._restingThresh * timeScaleSquared, - frictionNormalMultiplier = Resolver._frictionNormalMultiplier, - restingThreshTangent = Resolver._restingThreshTangent * timeScaleSquared, - NumberMaxValue = Number.MAX_VALUE, + Resolver.solveVelocity = function(pairs, delta) { + var timeScale = delta / Common._baseDelta, + timeScaleSquared = timeScale * timeScale, + timeScaleCubed = timeScaleSquared * timeScale, + restingThresh = -Resolver._restingThresh * timeScale, + restingThreshTangent = Resolver._restingThreshTangent, + frictionNormalMultiplier = Resolver._frictionNormalMultiplier * timeScale, + frictionMaxStatic = Resolver._frictionMaxStatic, pairsLength = pairs.length, tangentImpulse, maxFriction, @@ -235,10 +241,10 @@ var Bounds = require('../geometry/Bounds'); for (i = 0; i < pairsLength; i++) { var pair = pairs[i]; - + if (!pair.isActive || pair.isSensor) continue; - + var collision = pair.collision, bodyA = collision.parentA, bodyB = collision.parentB, @@ -252,7 +258,7 @@ var Bounds = require('../geometry/Bounds'); contactsLength = contacts.length, contactShare = 1 / contactsLength, inverseMassTotal = bodyA.inverseMass + bodyB.inverseMass, - friction = pair.friction * pair.frictionStatic * frictionNormalMultiplier * timeScaleSquared; + friction = pair.friction * pair.frictionStatic * frictionNormalMultiplier; // update body velocities bodyAVelocity.x = bodyA.position.x - bodyA.positionPrev.x; @@ -271,7 +277,7 @@ var Bounds = require('../geometry/Bounds'); offsetAY = contactVertex.y - bodyA.position.y, offsetBX = contactVertex.x - bodyB.position.x, offsetBY = contactVertex.y - bodyB.position.y; - + var velocityPointAX = bodyAVelocity.x - offsetAY * bodyA.angularVelocity, velocityPointAY = bodyAVelocity.y + offsetAX * bodyA.angularVelocity, velocityPointBX = bodyBVelocity.x - offsetBY * bodyB.angularVelocity, @@ -287,13 +293,13 @@ var Bounds = require('../geometry/Bounds'); var normalOverlap = pair.separation + normalVelocity; var normalForce = Math.min(normalOverlap, 1); normalForce = normalOverlap < 0 ? 0 : normalForce; - + var frictionLimit = normalForce * friction; - if (tangentVelocity > frictionLimit || -tangentVelocity > frictionLimit) { - maxFriction = tangentVelocity > 0 ? tangentVelocity : -tangentVelocity; - tangentImpulse = pair.friction * (tangentVelocity > 0 ? 1 : -1) * timeScaleSquared; - + if (tangentVelocity < -frictionLimit || tangentVelocity > frictionLimit) { + maxFriction = (tangentVelocity > 0 ? tangentVelocity : -tangentVelocity); + tangentImpulse = pair.friction * (tangentVelocity > 0 ? 1 : -1) * timeScaleCubed; + if (tangentImpulse < -maxFriction) { tangentImpulse = -maxFriction; } else if (tangentImpulse > maxFriction) { @@ -301,7 +307,7 @@ var Bounds = require('../geometry/Bounds'); } } else { tangentImpulse = tangentVelocity; - maxFriction = NumberMaxValue; + maxFriction = frictionMaxStatic; } // account for mass, inertia and contact offset @@ -314,7 +320,7 @@ var Bounds = require('../geometry/Bounds'); tangentImpulse *= share; // handle high velocity and resting collisions separately - if (normalVelocity * normalVelocity > restingThresh && normalVelocity < 0) { + if (normalVelocity < restingThresh) { // high normal velocity so clear cached contact normal impulse contact.normalImpulse = 0; } else { @@ -322,12 +328,12 @@ var Bounds = require('../geometry/Bounds'); // impulse constraint tends to 0 var contactNormalImpulse = contact.normalImpulse; contact.normalImpulse += normalImpulse; - contact.normalImpulse = Math.min(contact.normalImpulse, 0); + if (contact.normalImpulse > 0) contact.normalImpulse = 0; normalImpulse = contact.normalImpulse - contactNormalImpulse; } // handle high velocity and resting collisions separately - if (tangentVelocity * tangentVelocity > restingThreshTangent) { + if (tangentVelocity < -restingThreshTangent || tangentVelocity > restingThreshTangent) { // high tangent velocity so clear cached contact tangent impulse contact.tangentImpulse = 0; } else { @@ -343,7 +349,7 @@ var Bounds = require('../geometry/Bounds'); // total impulse from contact var impulseX = normalX * normalImpulse + tangentX * tangentImpulse, impulseY = normalY * normalImpulse + tangentY * tangentImpulse; - + // apply impulse from contact if (!(bodyA.isStatic || bodyA.isSleeping)) { bodyA.positionPrev.x += impulseX * bodyA.inverseMass; diff --git a/src/physics/matter-js/lib/constraint/Constraint.js b/src/physics/matter-js/lib/constraint/Constraint.js index b28f30c62..f224e2973 100644 --- a/src/physics/matter-js/lib/constraint/Constraint.js +++ b/src/physics/matter-js/lib/constraint/Constraint.js @@ -114,9 +114,10 @@ var Common = require('../core/Common'); * @private * @method solveAll * @param {constraint[]} constraints - * @param {number} timeScale + * @param {number} delta */ - Constraint.solveAll = function(constraints, timeScale) { + Constraint.solveAll = function(constraints, delta) { + var timeScale = Common.clamp(delta / Common._baseDelta, 0, 1); // Solve fixed constraints first. for (var i = 0; i < constraints.length; i += 1) { var constraint = constraints[i], @@ -187,7 +188,10 @@ var Common = require('../core/Common'); // solve distance constraint with Gauss-Siedel method var difference = (currentLength - constraint.length) / currentLength, - stiffness = constraint.stiffness < 1 ? constraint.stiffness * timeScale : constraint.stiffness, + isRigid = constraint.stiffness >= 1 || constraint.length === 0, + stiffness = isRigid ? constraint.stiffness * timeScale + : constraint.stiffness * timeScale * timeScale, + damping = constraint.damping * timeScale, force = Vector.mult(delta, difference * stiffness), massTotal = (bodyA ? bodyA.inverseMass : 0) + (bodyB ? bodyB.inverseMass : 0), inertiaTotal = (bodyA ? bodyA.inverseInertia : 0) + (bodyB ? bodyB.inverseInertia : 0), @@ -198,7 +202,7 @@ var Common = require('../core/Common'); normalVelocity, relativeVelocity; - if (constraint.damping) { + if (damping > 0) { var zero = Vector.create(); normal = Vector.div(delta, currentLength); @@ -222,9 +226,9 @@ var Common = require('../core/Common'); bodyA.position.y -= force.y * share; // apply damping - if (constraint.damping) { - bodyA.positionPrev.x -= constraint.damping * normal.x * normalVelocity * share; - bodyA.positionPrev.y -= constraint.damping * normal.y * normalVelocity * share; + if (damping > 0) { + bodyA.positionPrev.x -= damping * normal.x * normalVelocity * share; + bodyA.positionPrev.y -= damping * normal.y * normalVelocity * share; } // apply torque @@ -245,9 +249,9 @@ var Common = require('../core/Common'); bodyB.position.y += force.y * share; // apply damping - if (constraint.damping) { - bodyB.positionPrev.x += constraint.damping * normal.x * normalVelocity * share; - bodyB.positionPrev.y += constraint.damping * normal.y * normalVelocity * share; + if (damping > 0) { + bodyB.positionPrev.x += damping * normal.x * normalVelocity * share; + bodyB.positionPrev.y += damping * normal.y * normalVelocity * share; } // apply torque @@ -334,6 +338,32 @@ var Common = require('../core/Common'); }; }; + /** + * Returns the current length of the constraint. + * This is the distance between both of the constraint's end points. + * See `constraint.length` for the target rest length. + * @method currentLength + * @param {constraint} constraint + * @returns {number} the current length + */ + Constraint.currentLength = function(constraint) { + var pointAX = (constraint.bodyA ? constraint.bodyA.position.x : 0) + + (constraint.pointA ? constraint.pointA.x : 0); + + var pointAY = (constraint.bodyA ? constraint.bodyA.position.y : 0) + + (constraint.pointA ? constraint.pointA.y : 0); + + var pointBX = (constraint.bodyB ? constraint.bodyB.position.x : 0) + + (constraint.pointB ? constraint.pointB.x : 0); + + var pointBY = (constraint.bodyB ? constraint.bodyB.position.y : 0) + + (constraint.pointB ? constraint.pointB.y : 0); + + var deltaX = pointAX - pointBX; + var deltaY = pointAY - pointBY; + + return Math.sqrt(deltaX * deltaX + deltaY * deltaY); + }; /* * * Properties Documentation diff --git a/src/physics/matter-js/lib/core/Common.js b/src/physics/matter-js/lib/core/Common.js index 895fa390e..aebfbd861 100644 --- a/src/physics/matter-js/lib/core/Common.js +++ b/src/physics/matter-js/lib/core/Common.js @@ -10,6 +10,7 @@ module.exports = Common; (function() { + Common._baseDelta = 1000 / 60; Common._nextId = 0; Common._seed = 0; Common._nowStartTime = +(new Date()); diff --git a/src/physics/matter-js/lib/core/Engine.js b/src/physics/matter-js/lib/core/Engine.js index 2bf1b2872..e5abb46ca 100644 --- a/src/physics/matter-js/lib/core/Engine.js +++ b/src/physics/matter-js/lib/core/Engine.js @@ -66,31 +66,21 @@ var Body = require('../body/Body'); engine.world.gravity = engine.gravity; engine.broadphase = engine.grid; engine.metrics = {}; - + return engine; }; /** * Moves the simulation forward in time by `delta` ms. - * The `correction` argument is an optional `Number` that specifies the time correction factor to apply to the update. - * This can help improve the accuracy of the simulation in cases where `delta` is changing between updates. - * The value of `correction` is defined as `delta / lastDelta`, i.e. the percentage change of `delta` over the last step. - * Therefore the value is always `1` (no correction) when `delta` constant (or when no correction is desired, which is the default). - * See the paper on Time Corrected Verlet for more information. - * * Triggers `beforeUpdate` and `afterUpdate` events. * Triggers `collisionStart`, `collisionActive` and `collisionEnd` events. * @method update * @param {engine} engine * @param {number} [delta=16.666] - * @param {number} [correction=1] */ - Engine.update = function(engine, delta, correction) { + Engine.update = function(engine, delta) { var startTime = Common.now(); - delta = delta || 1000 / 60; - correction = correction || 1; - var world = engine.world, detector = engine.detector, pairs = engine.pairs, @@ -98,13 +88,17 @@ var Body = require('../body/Body'); timestamp = timing.timestamp, i; + delta = typeof delta !== 'undefined' ? delta : Common._baseDelta; + delta *= timing.timeScale; + // increment timestamp - timing.timestamp += delta * timing.timeScale; - timing.lastDelta = delta * timing.timeScale; + timing.timestamp += delta; + timing.lastDelta = delta; // create an event object var event = { - timestamp: timing.timestamp + timestamp: timing.timestamp, + delta: delta }; Events.trigger(engine, 'beforeUpdate', event); @@ -113,30 +107,33 @@ var Body = require('../body/Body'); var allBodies = Composite.allBodies(world), allConstraints = Composite.allConstraints(world); - // update the detector bodies if they have changed + // if the world has changed if (world.isModified) { + // update the detector bodies Detector.setBodies(detector, allBodies); - } - // reset all composite modified flags - if (world.isModified) { + // reset all composite modified flags Composite.setModified(world, false, false, true); } // update sleeping if enabled if (engine.enableSleeping) - Sleeping.update(allBodies, timing.timeScale); + Sleeping.update(allBodies, delta); // apply gravity to all bodies Engine._bodiesApplyGravity(allBodies, engine.gravity); // update all body position and rotation by integration - Engine._bodiesUpdate(allBodies, delta, timing.timeScale, correction, world.bounds); + if (delta > 0) { + Engine._bodiesUpdate(allBodies, delta); + } + + Events.trigger(engine, 'beforeSolve', event); // update all constraints (first pass) Constraint.preSolveAll(allBodies); for (i = 0; i < engine.constraintIterations; i++) { - Constraint.solveAll(allConstraints, timing.timeScale); + Constraint.solveAll(allConstraints, delta); } Constraint.postSolveAll(allBodies); @@ -149,38 +146,58 @@ var Body = require('../body/Body'); // wake up bodies involved in collisions if (engine.enableSleeping) - Sleeping.afterCollisions(pairs.list, timing.timeScale); + Sleeping.afterCollisions(pairs.list); // trigger collision events - if (pairs.collisionStart.length > 0) - Events.trigger(engine, 'collisionStart', { pairs: pairs.collisionStart }); + if (pairs.collisionStart.length > 0) { + Events.trigger(engine, 'collisionStart', { + pairs: pairs.collisionStart, + timestamp: timing.timestamp, + delta: delta + }); + } // iteratively resolve position between collisions + var positionDamping = Common.clamp(20 / engine.positionIterations, 0, 1); + Resolver.preSolvePosition(pairs.list); for (i = 0; i < engine.positionIterations; i++) { - Resolver.solvePosition(pairs.list, timing.timeScale); + Resolver.solvePosition(pairs.list, delta, positionDamping); } Resolver.postSolvePosition(allBodies); // update all constraints (second pass) Constraint.preSolveAll(allBodies); for (i = 0; i < engine.constraintIterations; i++) { - Constraint.solveAll(allConstraints, timing.timeScale); + Constraint.solveAll(allConstraints, delta); } Constraint.postSolveAll(allBodies); // iteratively resolve velocity between collisions Resolver.preSolveVelocity(pairs.list); for (i = 0; i < engine.velocityIterations; i++) { - Resolver.solveVelocity(pairs.list, timing.timeScale); + Resolver.solveVelocity(pairs.list, delta); } - // trigger collision events - if (pairs.collisionActive.length > 0) - Events.trigger(engine, 'collisionActive', { pairs: pairs.collisionActive }); + // update body speed and velocity properties + Engine._bodiesUpdateVelocities(allBodies); - if (pairs.collisionEnd.length > 0) - Events.trigger(engine, 'collisionEnd', { pairs: pairs.collisionEnd }); + // trigger collision events + if (pairs.collisionActive.length > 0) { + Events.trigger(engine, 'collisionActive', { + pairs: pairs.collisionActive, + timestamp: timing.timestamp, + delta: delta + }); + } + + if (pairs.collisionEnd.length > 0) { + Events.trigger(engine, 'collisionEnd', { + pairs: pairs.collisionEnd, + timestamp: timing.timestamp, + delta: delta + }); + } // clear force buffers Engine._bodiesClearForces(allBodies); @@ -192,7 +209,7 @@ var Body = require('../body/Body'); return engine; }; - + /** * Merges two engines by keeping the configuration of `engineA` but replacing the world with the one from `engineB`. * @method merge @@ -201,7 +218,7 @@ var Body = require('../body/Body'); */ Engine.merge = function(engineA, engineB) { Common.extend(engineA, engineB); - + if (engineB.world) { engineA.world = engineB.world; @@ -234,7 +251,8 @@ var Body = require('../body/Body'); * @param {body[]} bodies */ Engine._bodiesClearForces = function(bodies) { - for (var i = 0; i < bodies.length; i++) { + var bodiesLength = bodies.length; + for (var i = 0; i < bodiesLength; i++) { var body = bodies[i]; // reset force buffers @@ -252,44 +270,56 @@ var Body = require('../body/Body'); * @param {vector} gravity */ Engine._bodiesApplyGravity = function(bodies, gravity) { - var gravityScale = typeof gravity.scale !== 'undefined' ? gravity.scale : 0.001; + var gravityScale = typeof gravity.scale !== 'undefined' ? gravity.scale : 0.001, + bodiesLength = bodies.length; if ((gravity.x === 0 && gravity.y === 0) || gravityScale === 0) { return; } - - for (var i = 0; i < bodies.length; i++) { - var body = bodies[i]; - if (body.ignoreGravity || body.isStatic || body.isSleeping) - continue; - - // apply gravity - body.force.x += (body.mass * gravity.x * gravityScale) * body.gravityScale.x; - body.force.y += (body.mass * gravity.y * gravityScale) * body.gravityScale.y; - } - }; - - /** - * Applys `Body.update` to all given `bodies`. - * @method _bodiesUpdate - * @private - * @param {body[]} bodies - * @param {number} deltaTime - * The amount of time elapsed between updates - * @param {number} timeScale - * @param {number} correction - * The Verlet correction factor (deltaTime / lastDeltaTime) - * @param {bounds} worldBounds - */ - Engine._bodiesUpdate = function(bodies, deltaTime, timeScale, correction, worldBounds) { - for (var i = 0; i < bodies.length; i++) { + for (var i = 0; i < bodiesLength; i++) { var body = bodies[i]; if (body.isStatic || body.isSleeping) continue; - Body.update(body, deltaTime, timeScale, correction); + // add the resultant force of gravity + body.force.y += body.mass * gravity.y * gravityScale; + body.force.x += body.mass * gravity.x * gravityScale; + } + }; + + /** + * Applies `Body.update` to all given `bodies`. + * @method _bodiesUpdate + * @private + * @param {body[]} bodies + * @param {number} delta The amount of time elapsed between updates + */ + Engine._bodiesUpdate = function(bodies, delta) { + var bodiesLength = bodies.length; + + for (var i = 0; i < bodiesLength; i++) { + var body = bodies[i]; + + if (body.isStatic || body.isSleeping) + continue; + + Body.update(body, delta); + } + }; + + /** + * Applies `Body.updateVelocities` to all given `bodies`. + * @method _bodiesUpdateVelocities + * @private + * @param {body[]} bodies + */ + Engine._bodiesUpdateVelocities = function(bodies) { + var bodiesLength = bodies.length; + + for (var i = 0; i < bodiesLength; i++) { + Body.updateVelocities(bodies[i]); } }; @@ -397,7 +427,7 @@ var Body = require('../body/Body'); */ /** - * An `Object` containing properties regarding the timing systems of the engine. + * An `Object` containing properties regarding the timing systems of the engine. * * @property timing * @type object @@ -415,8 +445,8 @@ var Body = require('../body/Body'); */ /** - * A `Number` that specifies the current simulation-time in milliseconds starting from `0`. - * It is incremented on every `Engine.update` by the given `delta` argument. + * A `Number` that specifies the current simulation-time in milliseconds starting from `0`. + * It is incremented on every `Engine.update` by the given `delta` argument. * * @property timing.timestamp * @type number diff --git a/src/physics/matter-js/lib/core/Matter.js b/src/physics/matter-js/lib/core/Matter.js index 384818552..fac48f49b 100644 --- a/src/physics/matter-js/lib/core/Matter.js +++ b/src/physics/matter-js/lib/core/Matter.js @@ -27,7 +27,7 @@ var Common = require('./Common'); * @readOnly * @type {String} */ - Matter.version = '0.18.0'; + Matter.version = '0.19.0'; /** * A list of plugin dependencies to be installed. These are normally set and installed through `Matter.use`. diff --git a/src/physics/matter-js/lib/core/Runner.js b/src/physics/matter-js/lib/core/Runner.js index 05d0293dc..7dbc05aa4 100644 --- a/src/physics/matter-js/lib/core/Runner.js +++ b/src/physics/matter-js/lib/core/Runner.js @@ -53,13 +53,11 @@ var Common = require('./Common'); Runner.create = function(options) { var defaults = { fps: 60, - correction: 1, deltaSampleSize: 60, counterTimestamp: 0, frameCounter: 0, deltaHistory: [], timePrev: null, - timeScalePrev: 1, frameRequestId: null, isFixed: false, enabled: true @@ -87,8 +85,8 @@ var Common = require('./Common'); runner = Runner.create(); } - (function render(time){ - runner.frameRequestId = _requestAnimationFrame(render); + (function run(time){ + runner.frameRequestId = _requestAnimationFrame(run); if (time && runner.enabled) { Runner.tick(runner, engine, time); @@ -109,16 +107,8 @@ var Common = require('./Common'); */ Runner.tick = function(runner, engine, time) { var timing = engine.timing, - correction = 1, delta; - // create an event object - var event = { - timestamp: timing.timestamp - }; - - Events.trigger(runner, 'beforeTick', event); - if (runner.isFixed) { // fixed timestep delta = runner.delta; @@ -131,27 +121,21 @@ var Common = require('./Common'); runner.deltaHistory.push(delta); runner.deltaHistory = runner.deltaHistory.slice(-runner.deltaSampleSize); delta = Math.min.apply(null, runner.deltaHistory); - + // limit delta delta = delta < runner.deltaMin ? runner.deltaMin : delta; delta = delta > runner.deltaMax ? runner.deltaMax : delta; - // correction for delta - correction = delta / runner.delta; - // update engine timing object runner.delta = delta; } - // time correction for time scaling - if (runner.timeScalePrev !== 0) - correction *= timing.timeScale / runner.timeScalePrev; + // create an event object + var event = { + timestamp: timing.timestamp + }; - if (timing.timeScale === 0) - correction = 0; - - runner.timeScalePrev = timing.timeScale; - runner.correction = correction; + Events.trigger(runner, 'beforeTick', event); // fps counter runner.frameCounter += 1; @@ -165,7 +149,8 @@ var Common = require('./Common'); // update Events.trigger(runner, 'beforeUpdate', event); - Engine.update(engine, delta, correction); + + Engine.update(engine, delta); Events.trigger(runner, 'afterUpdate', event); Events.trigger(runner, 'afterTick', event); diff --git a/src/physics/matter-js/lib/core/Sleeping.js b/src/physics/matter-js/lib/core/Sleeping.js index b284ea933..fc7fe162e 100644 --- a/src/physics/matter-js/lib/core/Sleeping.js +++ b/src/physics/matter-js/lib/core/Sleeping.js @@ -8,7 +8,9 @@ var Sleeping = {}; module.exports = Sleeping; +var Body = require('../body/Body'); var Events = require('./Events'); +var Common = require('./Common'); (function() { @@ -20,15 +22,18 @@ var Events = require('./Events'); * Puts bodies to sleep or wakes them up depending on their motion. * @method update * @param {body[]} bodies - * @param {number} timeScale + * @param {number} delta */ - Sleeping.update = function(bodies, timeScale) { - var timeFactor = timeScale * timeScale * timeScale; - + Sleeping.update = function(bodies, delta) { + var timeScale = delta / Common._baseDelta, + motionSleepThreshold = Sleeping._motionSleepThreshold; + // 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; + speed = Body.getSpeed(body), + angularSpeed = Body.getAngularSpeed(body), + motion = speed * speed + angularSpeed * angularSpeed; // wake up bodies if they have a force applied if (body.force.x !== 0 || body.force.y !== 0) { @@ -41,12 +46,13 @@ var Events = require('./Events'); // biased average motion estimation between frames body.motion = Sleeping._minBias * minMotion + (1 - Sleeping._minBias) * maxMotion; - - if (body.sleepThreshold > 0 && body.motion < Sleeping._motionSleepThreshold * timeFactor) { + + if (body.sleepThreshold > 0 && body.motion < motionSleepThreshold) { body.sleepCounter += 1; - if (body.sleepCounter >= body.sleepThreshold) + if (body.sleepCounter >= body.sleepThreshold / timeScale) { Sleeping.set(body, true); + } } else if (body.sleepCounter > 0) { body.sleepCounter -= 1; } @@ -57,10 +63,9 @@ var Events = require('./Events'); * Given a set of colliding pairs, wakes the sleeping bodies involved. * @method afterCollisions * @param {pair[]} pairs - * @param {number} timeScale */ - Sleeping.afterCollisions = function(pairs, timeScale) { - var timeFactor = timeScale * timeScale * timeScale; + Sleeping.afterCollisions = function(pairs) { + var motionSleepThreshold = Sleeping._motionSleepThreshold; // wake up bodies involved in collisions for (var i = 0; i < pairs.length; i++) { @@ -82,7 +87,7 @@ var Events = require('./Events'); var sleepingBody = (bodyA.isSleeping && !bodyA.isStatic) ? bodyA : bodyB, movingBody = sleepingBody === bodyA ? bodyB : bodyA; - if (!sleepingBody.isStatic && movingBody.motion > Sleeping._motionWakeThreshold * timeFactor) { + if (!sleepingBody.isStatic && movingBody.motion > motionSleepThreshold) { Sleeping.set(sleepingBody, false); } } diff --git a/src/physics/matter-js/lib/factory/Bodies.js b/src/physics/matter-js/lib/factory/Bodies.js index 34cbc8eff..e69a75d92 100644 --- a/src/physics/matter-js/lib/factory/Bodies.js +++ b/src/physics/matter-js/lib/factory/Bodies.js @@ -54,6 +54,7 @@ var Vector = require('../geometry/Vector'); /** * Creates a new rigid body model with a trapezoid hull. + * The `slope` is parameterised as a fraction of `width` and must be < 1 to form a valid trapezoid. * The options parameter is an object that specifies any properties you wish to override the defaults. * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object. * @method trapezoid @@ -61,7 +62,7 @@ var Vector = require('../geometry/Vector'); * @param {number} y * @param {number} width * @param {number} height - * @param {number} slope + * @param {number} slope Must be a number < 1. * @param {object} [options] * @return {body} A new trapezoid body */ @@ -309,9 +310,37 @@ var Vector = require('../geometry/Vector'); } // flag internal edges (coincident part edges) - if (flagInternal) - { - Bodies.flagCoincidentParts(parts, 5); + if (flagInternal) { + var coincident_max_dist = 5; + + for (i = 0; i < parts.length; i++) { + var partA = parts[i]; + + for (j = i + 1; j < parts.length; j++) { + var partB = parts[j]; + + if (Bounds.overlaps(partA.bounds, partB.bounds)) { + var pav = partA.vertices, + pbv = partB.vertices; + + // iterate vertices of both parts + for (k = 0; k < partA.vertices.length; k++) { + for (z = 0; z < partB.vertices.length; z++) { + // find distances between the vertices + var da = Vector.magnitudeSquared(Vector.sub(pav[(k + 1) % pav.length], pbv[z])), + db = Vector.magnitudeSquared(Vector.sub(pav[k], pbv[(z + 1) % pbv.length])); + + // if both vertices are very close, consider the edge concident (internal) + if (da < coincident_max_dist && db < coincident_max_dist) { + pav[k].isInternal = true; + pbv[z].isInternal = true; + } + } + } + + } + } + } } if (parts.length > 1) { diff --git a/src/physics/matter-js/lib/factory/Composites.js b/src/physics/matter-js/lib/factory/Composites.js index 0545614f0..e52fe0ad2 100644 --- a/src/physics/matter-js/lib/factory/Composites.js +++ b/src/physics/matter-js/lib/factory/Composites.js @@ -16,7 +16,6 @@ var Constraint = require('../constraint/Constraint'); var Common = require('../core/Common'); var Body = require('../body/Body'); var Bodies = require('./Bodies'); -var deprecated = Common.deprecated; (function() { @@ -24,8 +23,8 @@ var deprecated = Common.deprecated; * Create a new composite containing bodies created in the callback in a grid arrangement. * This function uses the body's bounds to prevent overlaps. * @method stack - * @param {number} xx - * @param {number} yy + * @param {number} x Starting position in X. + * @param {number} y Starting position in Y. * @param {number} columns * @param {number} rows * @param {number} columnGap @@ -33,46 +32,46 @@ var deprecated = Common.deprecated; * @param {function} callback * @return {composite} A new composite containing objects created in the callback */ - Composites.stack = function(xx, yy, columns, rows, columnGap, rowGap, callback) { + Composites.stack = function(x, y, columns, rows, columnGap, rowGap, callback) { var stack = Composite.create({ label: 'Stack' }), - x = xx, - y = yy, + currentX = x, + currentY = y, 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); - + var body = callback(currentX, currentY, 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; + 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; + currentX = body.bounds.max.x + columnGap; Composite.addBody(stack, body); - + lastBody = body; i += 1; } else { - x += columnGap; + currentX += columnGap; } } - - y += maxHeight + rowGap; - x = xx; + + currentY += maxHeight + rowGap; + currentX = x; } return stack; }; - + /** * Chains all bodies in the given composite together using constraints. * @method chain @@ -86,29 +85,29 @@ var deprecated = Common.deprecated; */ 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, + 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; }; @@ -129,7 +128,7 @@ var deprecated = Common.deprecated; bodyA, bodyB, bodyC; - + for (row = 0; row < rows; row++) { for (col = 1; col < columns; col++) { bodyA = bodies[(col - 1) + (row * columns)]; @@ -157,16 +156,16 @@ var deprecated = Common.deprecated; } composite.label += ' Mesh'; - + return composite; }; - + /** * Create a new composite containing bodies created in the callback in a pyramid arrangement. * This function uses the body's bounds to prevent overlaps. * @method pyramid - * @param {number} xx - * @param {number} yy + * @param {number} x Starting position in X. + * @param {number} y Starting position in Y. * @param {number} columns * @param {number} rows * @param {number} columnGap @@ -174,31 +173,31 @@ var deprecated = Common.deprecated; * @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) { + Composites.pyramid = function(x, y, columns, rows, columnGap, rowGap, callback) { + return Composites.stack(x, y, columns, rows, columnGap, rowGap, function(stackX, stackY, 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); + + return callback(x + xOffset + column * columnGap, stackY, column, row, lastBody, i); }); }; @@ -206,21 +205,21 @@ var deprecated = Common.deprecated; * This has now moved to the [newtonsCradle example](https://github.com/liabru/matter-js/blob/master/examples/newtonsCradle.js), follow that instead as this function is deprecated here. * @deprecated moved to newtonsCradle example * @method newtonsCradle - * @param {number} xx - * @param {number} yy + * @param {number} x Starting position in X. + * @param {number} y Starting position in Y. * @param {number} number * @param {number} size * @param {number} length * @return {composite} A new composite newtonsCradle body */ - Composites.newtonsCradle = function(xx, yy, number, size, length) { + Composites.newtonsCradle = function(x, y, 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, + circle = Bodies.circle(x + i * (size * separation), y + length, size, { inertia: Infinity, restitution: 1, friction: 0, frictionAir: 0.0001, slop: 1 }), - constraint = Constraint.create({ pointA: { x: xx + i * (size * separation), y: yy }, bodyB: circle }); + constraint = Constraint.create({ pointA: { x: x + i * (size * separation), y: y }, bodyB: circle }); Composite.addBody(newtonsCradle, circle); Composite.addConstraint(newtonsCradle, constraint); @@ -229,28 +228,26 @@ var deprecated = Common.deprecated; return newtonsCradle; }; - deprecated(Composites, 'newtonsCradle', 'Composites.newtonsCradle ➤ moved to newtonsCradle example'); - /** * This has now moved to the [car example](https://github.com/liabru/matter-js/blob/master/examples/car.js), follow that instead as this function is deprecated here. * @deprecated moved to car example * @method car - * @param {number} xx - * @param {number} yy + * @param {number} x Starting position in X. + * @param {number} y Starting position in Y. * @param {number} width * @param {number} height * @param {number} wheelSize * @return {composite} A new composite car body */ - Composites.car = function(xx, yy, width, height, wheelSize) { + Composites.car = function(x, y, width, height, wheelSize) { var group = Body.nextGroup(true), wheelBase = 20, wheelAOffset = -width * 0.5 + wheelBase, wheelBOffset = width * 0.5 - wheelBase, wheelYOffset = 0; - + var car = Composite.create({ label: 'Car' }), - body = Bodies.rectangle(xx, yy, width, height, { + body = Bodies.rectangle(x, y, width, height, { collisionFilter: { group: group }, @@ -259,21 +256,21 @@ var deprecated = Common.deprecated; }, density: 0.0002 }); - - var wheelA = Bodies.circle(xx + wheelAOffset, yy + wheelYOffset, wheelSize, { + + var wheelA = Bodies.circle(x + wheelAOffset, y + wheelYOffset, wheelSize, { collisionFilter: { group: group }, friction: 0.8 }); - - var wheelB = Bodies.circle(xx + wheelBOffset, yy + wheelYOffset, wheelSize, { + + var wheelB = Bodies.circle(x + wheelBOffset, y + wheelYOffset, wheelSize, { collisionFilter: { group: group }, friction: 0.8 }); - + var axelA = Constraint.create({ bodyB: body, pointB: { x: wheelAOffset, y: wheelYOffset }, @@ -281,7 +278,7 @@ var deprecated = Common.deprecated; stiffness: 1, length: 0 }); - + var axelB = Constraint.create({ bodyB: body, pointB: { x: wheelBOffset, y: wheelYOffset }, @@ -289,7 +286,7 @@ var deprecated = Common.deprecated; stiffness: 1, length: 0 }); - + Composite.addBody(car, body); Composite.addBody(car, wheelA); Composite.addBody(car, wheelB); @@ -299,15 +296,13 @@ var deprecated = Common.deprecated; return car; }; - deprecated(Composites, 'car', 'Composites.car ➤ moved to car example'); - /** * This has now moved to the [softBody example](https://github.com/liabru/matter-js/blob/master/examples/softBody.js) * and the [cloth example](https://github.com/liabru/matter-js/blob/master/examples/cloth.js), follow those instead as this function is deprecated here. * @deprecated moved to softBody and cloth examples * @method softBody - * @param {number} xx - * @param {number} yy + * @param {number} x Starting position in X. + * @param {number} y Starting position in Y. * @param {number} columns * @param {number} rows * @param {number} columnGap @@ -318,12 +313,12 @@ var deprecated = Common.deprecated; * @param {} constraintOptions * @return {composite} A new composite softBody */ - Composites.softBody = function(xx, yy, columns, rows, columnGap, rowGap, crossBrace, particleRadius, particleOptions, constraintOptions) { + Composites.softBody = function(x, y, columns, rows, columnGap, rowGap, crossBrace, particleRadius, particleOptions, constraintOptions) { particleOptions = Common.extend({ inertia: Infinity }, particleOptions); constraintOptions = Common.extend({ stiffness: 0.2, render: { type: 'line', anchors: false } }, constraintOptions); - var softBody = Composites.stack(xx, yy, columns, rows, columnGap, rowGap, function(x, y) { - return Bodies.circle(x, y, particleRadius, particleOptions); + var softBody = Composites.stack(x, y, columns, rows, columnGap, rowGap, function(stackX, stackY) { + return Bodies.circle(stackX, stackY, particleRadius, particleOptions); }); Composites.mesh(softBody, columns, rows, crossBrace, constraintOptions); @@ -333,5 +328,4 @@ var deprecated = Common.deprecated; return softBody; }; - deprecated(Composites, 'softBody', 'Composites.softBody ➤ moved to softBody and cloth examples'); })(); diff --git a/src/physics/matter-js/lib/plugins/MatterAttractors.js b/src/physics/matter-js/lib/plugins/MatterAttractors.js index 5c555ed6f..1b07ba826 100644 --- a/src/physics/matter-js/lib/plugins/MatterAttractors.js +++ b/src/physics/matter-js/lib/plugins/MatterAttractors.js @@ -9,7 +9,7 @@ var MatterAttractors = { name: 'matter-attractors', version: '0.1.7', - for: 'matter-js@^0.18.0', + for: 'matter-js@^0.19.0', silent: true, // installs the plugin where `base` is `Matter` diff --git a/src/physics/matter-js/lib/plugins/MatterCollisionEvents.js b/src/physics/matter-js/lib/plugins/MatterCollisionEvents.js index f8ecc9187..b21cb8efe 100644 --- a/src/physics/matter-js/lib/plugins/MatterCollisionEvents.js +++ b/src/physics/matter-js/lib/plugins/MatterCollisionEvents.js @@ -8,7 +8,7 @@ var MatterCollisionEvents = { name: 'matter-collision-events', version: '0.1.6', - for: 'matter-js@^0.18.0', + for: 'matter-js@^0.19.0', silent: true, install: function (matter) diff --git a/src/physics/matter-js/lib/plugins/MatterWrap.js b/src/physics/matter-js/lib/plugins/MatterWrap.js index 06906b98b..790e85f30 100644 --- a/src/physics/matter-js/lib/plugins/MatterWrap.js +++ b/src/physics/matter-js/lib/plugins/MatterWrap.js @@ -9,7 +9,7 @@ var MatterWrap = { // plugin meta name: 'matter-wrap', // PLUGIN_NAME version: '0.1.4', // PLUGIN_VERSION - for: 'matter-js@^0.18.0', + for: 'matter-js@^0.19.0', silent: true, // no console log please // installs the plugin where `base` is `Matter` diff --git a/src/physics/matter-js/lib/render/Render.js b/src/physics/matter-js/lib/render/Render.js index aa36dbe8a..b9728870d 100644 --- a/src/physics/matter-js/lib/render/Render.js +++ b/src/physics/matter-js/lib/render/Render.js @@ -10,11 +10,11 @@ var Render = {}; module.exports = Render; +var Body = require('../body/Body'); var Common = require('../core/Common'); var Composite = require('../body/Composite'); var Bounds = require('../geometry/Bounds'); var Events = require('../core/Events'); -var Grid = require('../collision/Grid'); var Vector = require('../geometry/Vector'); // var Mouse = require('../core/Mouse'); @@ -32,6 +32,9 @@ var Vector = require('../geometry/Vector'); || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame; } + Render._goodFps = 30; + Render._goodDelta = 1000 / 60; + /** * Creates a new renderer. The options parameter is an object that specifies any properties you wish to override the defaults. * All properties have default values, and many are pre-calculated automatically based on other properties. @@ -42,24 +45,38 @@ var Vector = require('../geometry/Vector'); */ Render.create = function(options) { var defaults = { - controller: Render, engine: null, element: null, canvas: null, mouse: null, frameRequestId: null, + timing: { + historySize: 60, + delta: 0, + deltaHistory: [], + lastTime: 0, + lastTimestamp: 0, + lastElapsed: 0, + timestampElapsed: 0, + timestampElapsedHistory: [], + engineDeltaHistory: [], + engineElapsedHistory: [], + elapsedHistory: [] + }, options: { width: 800, height: 600, pixelRatio: 1, - background: '#18181d', - wireframeBackground: '#0f0f13', + background: '#14151f', + wireframeBackground: '#14151f', + wireframeStrokeStyle: '#bbb', hasBounds: !!options.bounds, enabled: true, wireframes: true, showSleeping: true, showDebug: false, - showBroadphase: false, + showStats: false, + showPerformance: false, showBounds: false, showVelocity: false, showCollisions: false, @@ -68,7 +85,6 @@ var Vector = require('../geometry/Vector'); showPositions: false, showAngleIndicator: false, showIds: false, - showShadows: false, showVertexNumbers: false, showConvexHulls: false, showInternalEdges: false, @@ -86,7 +102,7 @@ var Vector = require('../geometry/Vector'); render.mouse = options.mouse; render.engine = options.engine; render.canvas = render.canvas || _createCanvas(render.options.width, render.options.height); - render.context = render.canvas.getContext('2d', { willReadFrequently: true }); + render.context = render.canvas.getContext('2d'); render.textures = {}; render.bounds = render.bounds || { @@ -100,14 +116,16 @@ var Vector = require('../geometry/Vector'); } }; + // for temporary back compatibility only + render.controller = Render; + render.options.showBroadphase = false; + if (render.options.pixelRatio !== 1) { Render.setPixelRatio(render, render.options.pixelRatio); } if (Common.isElement(render.element)) { render.element.appendChild(render.canvas); - } else { - Common.log('Render.create: options.element was undefined, render.canvas was created but not appended', 'warn'); } return render; @@ -121,7 +139,18 @@ var Vector = require('../geometry/Vector'); Render.run = function(render) { (function loop(time){ render.frameRequestId = _requestAnimationFrame(loop); - Render.world(render); + + _updateTiming(render, time); + + Render.world(render, time); + + if (render.options.showStats || render.options.showDebug) { + Render.stats(render, render.context, time); + } + + if (render.options.showPerformance || render.options.showDebug) { + Render.performance(render, render.context, time); + } })(); }; @@ -155,7 +184,36 @@ var Vector = require('../geometry/Vector'); canvas.height = options.height * pixelRatio; canvas.style.width = options.width + 'px'; canvas.style.height = options.height + 'px'; - render.context.scale(pixelRatio, pixelRatio); + }; + + /** + * Sets the render `width` and `height`. + * + * Updates the canvas accounting for `render.options.pixelRatio`. + * + * Updates the bottom right render bound `render.bounds.max` relative to the provided `width` and `height`. + * The top left render bound `render.bounds.min` isn't changed. + * + * Follow this call with `Render.lookAt` if you need to change the render bounds. + * + * See also `Render.setPixelRatio`. + * @method setSize + * @param {render} render + * @param {number} width The width (in CSS pixels) + * @param {number} height The height (in CSS pixels) + */ + Render.setSize = function(render, width, height) { + render.options.width = width; + render.options.height = height; + render.bounds.max.x = render.bounds.min.x + width; + render.bounds.max.y = render.bounds.min.y + height; + + if (render.options.pixelRatio !== 1) { + Render.setPixelRatio(render, render.options.pixelRatio); + } else { + render.canvas.width = width; + render.canvas.height = height; + } }; /** @@ -267,7 +325,11 @@ var Vector = require('../geometry/Vector'); boundsScaleX = boundsWidth / render.options.width, boundsScaleY = boundsHeight / render.options.height; - render.context.scale(1 / boundsScaleX, 1 / boundsScaleY); + render.context.setTransform( + render.options.pixelRatio / boundsScaleX, 0, 0, + render.options.pixelRatio / boundsScaleY, 0, 0 + ); + render.context.translate(-render.bounds.min.x, -render.bounds.min.y); }; @@ -286,13 +348,16 @@ var Vector = require('../geometry/Vector'); * @method world * @param {render} render */ - Render.world = function(render) { - var engine = render.engine, + Render.world = function(render, time) { + var startTime = Common.now(), + engine = render.engine, world = engine.world, canvas = render.canvas, context = render.context, options = render.options, - allBodies = Composite.allBodies(world), + timing = render.timing; + + var allBodies = Composite.allBodies(world), allConstraints = Composite.allConstraints(world), background = options.wireframes ? options.wireframeBackground : options.background, bodies = [], @@ -347,16 +412,20 @@ var Vector = require('../geometry/Vector'); // update mouse if (render.mouse) { - // Mouse.setScale(render.mouse, { - // x: (render.bounds.max.x - render.bounds.min.x) / render.canvas.width, - // y: (render.bounds.max.y - render.bounds.min.y) / render.canvas.height - // }); + Mouse.setScale(render.mouse, { + x: (render.bounds.max.x - render.bounds.min.x) / render.options.width, + y: (render.bounds.max.y - render.bounds.min.y) / render.options.height + }); - // Mouse.setOffset(render.mouse, render.bounds.min); + Mouse.setOffset(render.mouse, render.bounds.min); } } else { constraints = allConstraints; bodies = allBodies; + + if (render.options.pixelRatio !== 1) { + render.context.setTransform(render.options.pixelRatio, 0, 0, render.options.pixelRatio, 0, 0); + } } if (!options.wireframes || (engine.enableSleeping && options.showSleeping)) { @@ -394,89 +463,193 @@ var Vector = require('../geometry/Vector'); if (options.showVertexNumbers) Render.vertexNumbers(render, bodies, context); - // if (options.showMousePosition) - // Render.mousePosition(render, render.mouse, context); + if (options.showMousePosition) + Render.mousePosition(render, render.mouse, context); Render.constraints(constraints, context); - if (options.showBroadphase && engine.broadphase.controller === Grid) - Render.grid(render, engine.broadphase, context); - - if (options.showDebug) - Render.debug(render, context); - if (options.hasBounds) { // revert view transforms Render.endViewTransform(render); } Events.trigger(render, 'afterRender', event); + + // log the time elapsed computing this update + timing.lastElapsed = Common.now() - startTime; }; /** - * Description + * Renders statistics about the engine and world useful for debugging. * @private - * @method debug + * @method stats + * @param {render} render + * @param {RenderingContext} context + * @param {Number} time + */ + Render.stats = function(render, context, time) { + var engine = render.engine, + world = engine.world, + bodies = Composite.allBodies(world), + parts = 0, + width = 55, + height = 44, + x = 0, + y = 0; + + // count parts + for (var i = 0; i < bodies.length; i += 1) { + parts += bodies[i].parts.length; + } + + // sections + var sections = { + 'Part': parts, + 'Body': bodies.length, + 'Cons': Composite.allConstraints(world).length, + 'Comp': Composite.allComposites(world).length, + 'Pair': engine.pairs.list.length + }; + + // background + context.fillStyle = '#0e0f19'; + context.fillRect(x, y, width * 5.5, height); + + context.font = '12px Arial'; + context.textBaseline = 'top'; + context.textAlign = 'right'; + + // sections + for (var key in sections) { + var section = sections[key]; + // label + context.fillStyle = '#aaa'; + context.fillText(key, x + width, y + 8); + + // value + context.fillStyle = '#eee'; + context.fillText(section, x + width, y + 26); + + x += width; + } + }; + + /** + * Renders engine and render performance information. + * @private + * @method performance * @param {render} render * @param {RenderingContext} context */ - Render.debug = function(render, context) { - var c = context, - engine = render.engine, - world = engine.world, - metrics = engine.metrics, - options = render.options, - bodies = Composite.allBodies(world), - space = " "; + Render.performance = function(render, context) { + var engine = render.engine, + timing = render.timing, + deltaHistory = timing.deltaHistory, + elapsedHistory = timing.elapsedHistory, + timestampElapsedHistory = timing.timestampElapsedHistory, + engineDeltaHistory = timing.engineDeltaHistory, + engineElapsedHistory = timing.engineElapsedHistory, + lastEngineDelta = engine.timing.lastDelta; + + var deltaMean = _mean(deltaHistory), + elapsedMean = _mean(elapsedHistory), + engineDeltaMean = _mean(engineDeltaHistory), + engineElapsedMean = _mean(engineElapsedHistory), + timestampElapsedMean = _mean(timestampElapsedHistory), + rateMean = (timestampElapsedMean / deltaMean) || 0, + fps = (1000 / deltaMean) || 0; - if (engine.timing.timestamp - (render.debugTimestamp || 0) >= 500) { - var text = ""; + var graphHeight = 4, + gap = 12, + width = 60, + height = 34, + x = 10, + y = 69; - if (metrics.timing) { - text += "fps: " + Math.round(metrics.timing.fps) + space; - } + // background + context.fillStyle = '#0e0f19'; + context.fillRect(0, 50, gap * 4 + width * 5 + 22, height); - // @if DEBUG - if (metrics.extended) { - if (metrics.timing) { - text += "delta: " + metrics.timing.delta.toFixed(3) + space; - text += "correction: " + metrics.timing.correction.toFixed(3) + space; - } + // show FPS + Render.status( + context, x, y, width, graphHeight, deltaHistory.length, + Math.round(fps) + ' fps', + fps / Render._goodFps, + function(i) { return (deltaHistory[i] / deltaMean) - 1; } + ); - text += "bodies: " + bodies.length + space; + // show engine delta + Render.status( + context, x + gap + width, y, width, graphHeight, engineDeltaHistory.length, + lastEngineDelta.toFixed(2) + ' dt', + Render._goodDelta / lastEngineDelta, + function(i) { return (engineDeltaHistory[i] / engineDeltaMean) - 1; } + ); - if (engine.broadphase.controller === Grid) - text += "buckets: " + metrics.buckets + space; + // show engine update time + Render.status( + context, x + (gap + width) * 2, y, width, graphHeight, engineElapsedHistory.length, + engineElapsedMean.toFixed(2) + ' ut', + 1 - (engineElapsedMean / Render._goodFps), + function(i) { return (engineElapsedHistory[i] / engineElapsedMean) - 1; } + ); - text += "\n"; + // show render time + Render.status( + context, x + (gap + width) * 3, y, width, graphHeight, elapsedHistory.length, + elapsedMean.toFixed(2) + ' rt', + 1 - (elapsedMean / Render._goodFps), + function(i) { return (elapsedHistory[i] / elapsedMean) - 1; } + ); - text += "collisions: " + metrics.collisions + space; - text += "pairs: " + engine.pairs.list.length + space; - text += "broad: " + metrics.broadEff + space; - text += "mid: " + metrics.midEff + space; - text += "narrow: " + metrics.narrowEff + space; - } - // @endif + // show effective speed + Render.status( + context, x + (gap + width) * 4, y, width, graphHeight, timestampElapsedHistory.length, + rateMean.toFixed(2) + ' x', + rateMean * rateMean * rateMean, + function(i) { return (((timestampElapsedHistory[i] / deltaHistory[i]) / rateMean) || 0) - 1; } + ); + }; - render.debugString = text; - render.debugTimestamp = engine.timing.timestamp; + /** + * Renders a label, indicator and a chart. + * @private + * @method status + * @param {RenderingContext} context + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {number} count + * @param {string} label + * @param {string} indicator + * @param {function} plotY + */ + Render.status = function(context, x, y, width, height, count, label, indicator, plotY) { + // background + context.strokeStyle = '#888'; + context.fillStyle = '#444'; + context.lineWidth = 1; + context.fillRect(x, y + 7, width, 1); + + // chart + context.beginPath(); + context.moveTo(x, y + 7 - height * Common.clamp(0.4 * plotY(0), -2, 2)); + for (var i = 0; i < width; i += 1) { + context.lineTo(x + i, y + 7 - (i < count ? height * Common.clamp(0.4 * plotY(i), -2, 2) : 0)); } + context.stroke(); - if (render.debugString) { - c.font = "12px Arial"; + // indicator + context.fillStyle = 'hsl(' + Common.clamp(25 + 95 * indicator, 0, 120) + ',100%,60%)'; + context.fillRect(x, y - 7, 4, 4); - 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); - } - } + // label + context.font = '12px Arial'; + context.textBaseline = 'middle'; + context.textAlign = 'right'; + context.fillStyle = '#eee'; + context.fillText(label, x + width, y - 5); }; /** @@ -556,55 +729,6 @@ var Vector = require('../geometry/Vector'); } }; - /** - * Description - * @private - * @method bodyShadows - * @param {render} render - * @param {body[]} bodies - * @param {RenderingContext} context - */ - Render.bodyShadows = function(render, bodies, context) { - var c = context, - engine = render.engine; - - 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 * @private @@ -698,7 +822,7 @@ var Vector = require('../geometry/Vector'); c.fill(); } else { c.lineWidth = 1; - c.strokeStyle = '#bbb'; + c.strokeStyle = render.options.wireframeStrokeStyle; c.stroke(); } } @@ -757,7 +881,7 @@ var Vector = require('../geometry/Vector'); } c.lineWidth = 1; - c.strokeStyle = '#bbb'; + c.strokeStyle = render.options.wireframeStrokeStyle; c.stroke(); }; @@ -920,7 +1044,7 @@ var Vector = require('../geometry/Vector'); // render a single axis indicator c.moveTo(part.position.x, part.position.y); c.lineTo((part.vertices[0].x + part.vertices[part.vertices.length-1].x) / 2, - (part.vertices[0].y + part.vertices[part.vertices.length-1].y) / 2); + (part.vertices[0].y + part.vertices[part.vertices.length-1].y) / 2); } } } @@ -1014,8 +1138,10 @@ var Vector = require('../geometry/Vector'); if (!body.render.visible) continue; + var velocity = Body.getVelocity(body); + 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.lineTo(body.position.x + velocity.x, body.position.y + velocity.y); } c.lineWidth = 3; @@ -1190,45 +1316,6 @@ var Vector = require('../geometry/Vector'); c.stroke(); }; - /** - * Description - * @private - * @method grid - * @param {render} render - * @param {grid} grid - * @param {RenderingContext} context - */ - Render.grid = function(render, grid, context) { - var c = context, - options = render.options; - - if (options.wireframes) { - c.strokeStyle = 'rgba(255,180,0,0.1)'; - } else { - c.strokeStyle = 'rgba(255,180,0,0.5)'; - } - - c.beginPath(); - - var bucketKeys = Common.keys(grid.buckets); - - for (var i = 0; i < bucketKeys.length; i++) { - var bucketId = bucketKeys[i]; - - if (grid.buckets[bucketId].length < 2) - continue; - - var region = bucketId.split(/C|R/); - c.rect(0.5 + parseInt(region[1], 10) * grid.bucketWidth, - 0.5 + parseInt(region[2], 10) * grid.bucketHeight, - grid.bucketWidth, - grid.bucketHeight); - } - - c.lineWidth = 1; - c.stroke(); - }; - /** * Description * @private @@ -1269,7 +1356,7 @@ var Vector = require('../geometry/Vector'); 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)); + Math.floor(bounds.max.x - bounds.min.x + 6), Math.floor(bounds.max.y - bounds.min.y + 6)); context.closePath(); context.stroke(); @@ -1303,7 +1390,7 @@ var Vector = require('../geometry/Vector'); 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)); + Math.floor(bounds.max.x - bounds.min.x), Math.floor(bounds.max.y - bounds.min.y)); context.closePath(); context.stroke(); context.fill(); @@ -1315,7 +1402,56 @@ var Vector = require('../geometry/Vector'); }; /** - * Description + * Updates render timing. + * @method _updateTiming + * @private + * @param {render} render + * @param {number} time + */ + var _updateTiming = function(render, time) { + var engine = render.engine, + timing = render.timing, + historySize = timing.historySize, + timestamp = engine.timing.timestamp; + + timing.delta = time - timing.lastTime || Render._goodDelta; + timing.lastTime = time; + + timing.timestampElapsed = timestamp - timing.lastTimestamp || 0; + timing.lastTimestamp = timestamp; + + timing.deltaHistory.unshift(timing.delta); + timing.deltaHistory.length = Math.min(timing.deltaHistory.length, historySize); + + timing.engineDeltaHistory.unshift(engine.timing.lastDelta); + timing.engineDeltaHistory.length = Math.min(timing.engineDeltaHistory.length, historySize); + + timing.timestampElapsedHistory.unshift(timing.timestampElapsed); + timing.timestampElapsedHistory.length = Math.min(timing.timestampElapsedHistory.length, historySize); + + timing.engineElapsedHistory.unshift(engine.timing.lastElapsed); + timing.engineElapsedHistory.length = Math.min(timing.engineElapsedHistory.length, historySize); + + timing.elapsedHistory.unshift(timing.lastElapsed); + timing.elapsedHistory.length = Math.min(timing.elapsedHistory.length, historySize); + }; + + /** + * Returns the mean value of the given numbers. + * @method _mean + * @private + * @param {Number[]} values + * @return {Number} the mean of given values + */ + var _mean = function(values) { + var result = 0; + for (var i = 0; i < values.length; i += 1) { + result += values[i]; + } + return (result / values.length) || 0; + }; + + /** * @method _createCanvas * @private * @param {} width @@ -1339,7 +1475,7 @@ var Vector = require('../geometry/Vector'); * @return {Number} pixel ratio */ var _getPixelRatio = function(canvas) { - var context = canvas.getContext('2d', { willReadFrequently: true }), + var context = canvas.getContext('2d'), devicePixelRatio = window.devicePixelRatio || 1, backingStorePixelRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio @@ -1421,6 +1557,7 @@ var Vector = require('../geometry/Vector'); /** * A back-reference to the `Matter.Render` module. * + * @deprecated * @property controller * @type render */ @@ -1448,37 +1585,6 @@ var Vector = require('../geometry/Vector'); * @default null */ - /** - * The configuration options of the renderer. - * - * @property options - * @type {} - */ - - /** - * The target width in pixels of the `render.canvas` to be created. - * - * @property options.width - * @type number - * @default 800 - */ - - /** - * The target height in pixels of the `render.canvas` to be created. - * - * @property options.height - * @type number - * @default 600 - */ - - /** - * A flag that specifies if `render.bounds` should be used when rendering. - * - * @property options.hasBounds - * @type boolean - * @default false - */ - /** * A `Bounds` object that specifies the drawing view region. * Rendering will be automatically transformed and scaled to fit within the canvas size (`render.options.width` and `render.options.height`). @@ -1503,4 +1609,264 @@ var Vector = require('../geometry/Vector'); * @type {} */ + /** + * The mouse to render if `render.options.showMousePosition` is enabled. + * + * @property mouse + * @type mouse + * @default null + */ + + /** + * The configuration options of the renderer. + * + * @property options + * @type {} + */ + + /** + * The target width in pixels of the `render.canvas` to be created. + * See also the `options.pixelRatio` property to change render quality. + * + * @property options.width + * @type number + * @default 800 + */ + + /** + * The target height in pixels of the `render.canvas` to be created. + * See also the `options.pixelRatio` property to change render quality. + * + * @property options.height + * @type number + * @default 600 + */ + + /** + * The [pixel ratio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) to use when rendering. + * + * @property options.pixelRatio + * @type number + * @default 1 + */ + + /** + * A CSS background color string to use when `render.options.wireframes` is disabled. + * This may be also set to `'transparent'` or equivalent. + * + * @property options.background + * @type string + * @default '#14151f' + */ + + /** + * A CSS color string to use for background when `render.options.wireframes` is enabled. + * This may be also set to `'transparent'` or equivalent. + * + * @property options.wireframeBackground + * @type string + * @default '#14151f' + */ + + /** + * A CSS color string to use for stroke when `render.options.wireframes` is enabled. + * This may be also set to `'transparent'` or equivalent. + * + * @property options.wireframeStrokeStyle + * @type string + * @default '#bbb' + */ + + /** + * A flag that specifies if `render.bounds` should be used when rendering. + * + * @property options.hasBounds + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable all debug information overlays together. + * This includes and has priority over the values of: + * + * - `render.options.showStats` + * - `render.options.showPerformance` + * + * @property options.showDebug + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the engine stats info overlay. + * From left to right, the values shown are: + * + * - body parts total + * - body total + * - constraints total + * - composites total + * - collision pairs total + * + * @property options.showStats + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable performance charts. + * From left to right, the values shown are: + * + * - average render frequency (e.g. 60 fps) + * - exact engine delta time used for last update (e.g. 16.66ms) + * - average engine execution duration (e.g. 5.00ms) + * - average render execution duration (e.g. 0.40ms) + * - average effective play speed (e.g. '1.00x' is 'real-time') + * + * Each value is recorded over a fixed sample of past frames (60 frames). + * + * A chart shown below each value indicates the variance from the average over the sample. + * The more stable or fixed the value is the flatter the chart will appear. + * + * @property options.showPerformance + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable rendering entirely. + * + * @property options.enabled + * @type boolean + * @default false + */ + + /** + * A flag to toggle wireframe rendering otherwise solid fill rendering is used. + * + * @property options.wireframes + * @type boolean + * @default true + */ + + /** + * A flag to enable or disable sleeping bodies indicators. + * + * @property options.showSleeping + * @type boolean + * @default true + */ + + /** + * A flag to enable or disable the debug information overlay. + * + * @property options.showDebug + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the collision broadphase debug overlay. + * + * @deprecated no longer implemented + * @property options.showBroadphase + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the body bounds debug overlay. + * + * @property options.showBounds + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the body velocity debug overlay. + * + * @property options.showVelocity + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the body collisions debug overlay. + * + * @property options.showCollisions + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the collision resolver separations debug overlay. + * + * @property options.showSeparations + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the body axes debug overlay. + * + * @property options.showAxes + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the body positions debug overlay. + * + * @property options.showPositions + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the body angle debug overlay. + * + * @property options.showAngleIndicator + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the body and part ids debug overlay. + * + * @property options.showIds + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the body vertex numbers debug overlay. + * + * @property options.showVertexNumbers + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the body convex hulls debug overlay. + * + * @property options.showConvexHulls + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the body internal edges debug overlay. + * + * @property options.showInternalEdges + * @type boolean + * @default false + */ + + /** + * A flag to enable or disable the mouse position debug overlay. + * + * @property options.showMousePosition + * @type boolean + * @default false + */ + })();