0
0
Fork 0
mirror of https://github.com/liabru/matter-js.git synced 2024-11-27 09:50:52 -05:00

Added readonly body.deltaTime

Added delta property to engine update event
Added delta argument to various internal functions
Changed timeScale argument to use delta instead on various internal functions
Fixed issues when using an engine update delta of 0
Improved time independence for friction, air friction, restitution, sleeping, collisions, constraints
Removed optional correction argument from Engine.update
Removed correction and timeScale from Body.update and Matter.Runner
This commit is contained in:
liabru 2019-09-01 12:21:16 +01:00
parent 2ec247b7af
commit 0784a5b5df
9 changed files with 114 additions and 107 deletions

View file

@ -15,13 +15,13 @@ module.exports = Body;
var Vertices = require('../geometry/Vertices');
var Vector = require('../geometry/Vector');
var Sleeping = require('../core/Sleeping');
var Render = require('../render/Render');
var Common = require('../core/Common');
var Bounds = require('../geometry/Bounds');
var Axes = require('../geometry/Axes');
(function() {
Body._timeCorrection = true;
Body._inertiaScale = 4;
Body._nextCollidingGroupId = 1;
Body._nextNonCollidingGroupId = -1;
@ -95,6 +95,7 @@ var Axes = require('../geometry/Axes');
area: 0,
mass: 0,
inertia: 0,
deltaTime: null,
_original: null
};
@ -462,8 +463,8 @@ var Axes = require('../geometry/Axes');
*/
Body.setPosition = function(body, position) {
var delta = Vector.sub(position, body.position);
body.positionPrev.x += delta.x;
body.positionPrev.y += delta.y;
body.positionPrev.x += delta.x;
body.positionPrev.y += delta.y;
for (var i = 0; i < body.parts.length; i++) {
var part = body.parts[i];
@ -482,7 +483,7 @@ var Axes = require('../geometry/Axes');
*/
Body.setAngle = function(body, angle) {
var delta = angle - body.angle;
body.anglePrev += delta;
body.anglePrev += delta;
for (var i = 0; i < body.parts.length; i++) {
var part = body.parts[i];
@ -625,26 +626,28 @@ var Axes = require('../geometry/Axes');
* Performs a simulation step for the given `body`, including updating position and angle using Verlet integration.
* @method update
* @param {body} body
* @param {number} deltaTime
* @param {number} timeScale
* @param {number} correction
* @param {number} [deltaTime=16.666]
*/
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 : Common._timeUnit) * 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._timeUnit),
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;
@ -880,7 +883,7 @@ var Axes = require('../geometry/Axes');
/**
* A `Vector` that _measures_ the current velocity of the body after the last `Body.update`. It is read-only.
* If you need to modify a body's velocity directly, you should either apply a force or simply change the body's `position` (as the engine uses position-Verlet integration).
*
*
* @readOnly
* @property velocity
* @type vector
@ -1109,6 +1112,16 @@ var Axes = require('../geometry/Axes');
* @default 1
*/
/**
* A `Number` that records the last delta time value used to update this body.
* This is automatically updated by the engine inside of `Body.update`.
*
* @readOnly
* @property deltaTime
* @type number
* @default null
*/
/**
* An `Object` that defines the rendering properties to be consumed by the module `Matter.Render`.
*

View file

@ -21,9 +21,10 @@ var Bounds = require('../geometry/Bounds');
* @method collisions
* @param {pair[]} broadphasePairs
* @param {engine} engine
* @param {number} delta
* @return {array} collisions
*/
Detector.collisions = function(broadphasePairs, engine) {
Detector.collisions = function(broadphasePairs, engine, delta) {
var collisions = [],
pairsTable = engine.pairs.table;
@ -66,7 +67,7 @@ var Bounds = require('../geometry/Bounds');
}
// narrow phase
var collision = SAT.collides(partA, partB, previousCollision);
var collision = SAT.collides(partA, partB, previousCollision, delta);
// @if DEBUG
metrics.narrowphaseTests += 1;

View file

@ -49,9 +49,9 @@ var Bounds = require('../geometry/Bounds');
* @method solvePosition
* @param {pair[]} pairs
* @param {body[]} bodies
* @param {number} timeScale
* @param {number} delta
*/
Resolver.solvePosition = function(pairs, bodies, timeScale) {
Resolver.solvePosition = function(pairs, bodies, delta) {
var i,
normalX,
normalY,
@ -68,7 +68,8 @@ var Bounds = require('../geometry/Bounds');
bodyBtoAX,
bodyBtoAY,
positionImpulse,
impulseCoefficient = timeScale * Resolver._positionDampen;
timeScale = delta / Common._timeUnit,
impulseCoefficient = Resolver._positionDampen * timeScale;
for (i = 0; i < bodies.length; i++) {
var body = bodies[i];
@ -231,10 +232,12 @@ var Bounds = require('../geometry/Bounds');
* Find a solution for pair velocities.
* @method solveVelocity
* @param {pair[]} pairs
* @param {number} timeScale
* @param {number} delta
*/
Resolver.solveVelocity = function(pairs, timeScale) {
var timeScaleSquared = timeScale * timeScale,
Resolver.solveVelocity = function(pairs, delta) {
var timeScale = delta / Common._timeUnit,
timeScale2 = timeScale * timeScale,
timeScale3 = timeScale2 * timeScale,
impulse = Vector._temp[0],
tempA = Vector._temp[1],
tempB = Vector._temp[2],
@ -287,10 +290,10 @@ var Bounds = require('../geometry/Bounds');
var tangentImpulse = tangentVelocity,
maxFriction = Infinity;
if (tangentSpeed > pair.friction * pair.frictionStatic * normalForce * timeScaleSquared) {
maxFriction = tangentSpeed;
if (tangentSpeed > pair.friction * pair.frictionStatic * normalForce * timeScale3) {
maxFriction = tangentSpeed * timeScale;
tangentImpulse = Common.clamp(
pair.friction * tangentVelocityDirection * timeScaleSquared,
pair.friction * tangentVelocityDirection * timeScale3,
-maxFriction, maxFriction
);
}
@ -304,7 +307,7 @@ var Bounds = require('../geometry/Bounds');
tangentImpulse *= share;
// handle high velocity and resting collisions separately
if (normalVelocity < 0 && normalVelocity * normalVelocity > Resolver._restingThresh * timeScaleSquared) {
if (normalVelocity < 0 && normalVelocity * normalVelocity > Resolver._restingThresh * timeScale2) {
// high normal velocity so clear cached contact normal impulse
contact.normalImpulse = 0;
} else {
@ -316,7 +319,7 @@ var Bounds = require('../geometry/Bounds');
}
// handle high velocity and resting collisions separately
if (tangentVelocity * tangentVelocity > Resolver._restingThreshTangent * timeScaleSquared) {
if (tangentVelocity * tangentVelocity > Resolver._restingThreshTangent * timeScale2) {
// high tangent velocity so clear cached contact tangent impulse
contact.tangentImpulse = 0;
} else {

View file

@ -12,23 +12,30 @@ module.exports = SAT;
var Vertices = require('../geometry/Vertices');
var Vector = require('../geometry/Vector');
var Common = require('../core/Common');
(function() {
SAT._reuseMotionThresh = 0.2;
/**
* Detect collision between two bodies using the Separating Axis Theorem.
* @method collides
* @param {body} bodyA
* @param {body} bodyB
* @param {collision} previousCollision
* @param {number} [delta=0]
* @return {collision} collision
*/
SAT.collides = function(bodyA, bodyB, previousCollision) {
SAT.collides = function(bodyA, bodyB, previousCollision, delta) {
var overlapAB,
overlapBA,
minOverlap,
collision,
canReusePrevCol = false;
canReusePrevCol = false,
timeScale = delta / Common._timeUnit;
delta = typeof delta !== 'undefined' ? delta : 0;
if (previousCollision) {
// estimate total motion
@ -39,7 +46,7 @@ var Vector = require('../geometry/Vector');
// we may be able to (partially) reuse collision result
// but only safe if collision was resting
canReusePrevCol = previousCollision && previousCollision.collided && motion < 0.2;
canReusePrevCol = previousCollision && previousCollision.collided && motion < SAT._reuseMotionThresh * timeScale * timeScale;
// reuse collision object
collision = previousCollision;

View file

@ -110,9 +110,11 @@ 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._timeUnit, 0, 1);
// Solve fixed constraints first.
for (var i = 0; i < constraints.length; i += 1) {
var constraint = constraints[i],
@ -183,7 +185,9 @@ 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 : 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),
@ -193,8 +197,8 @@ var Common = require('../core/Common');
normal,
normalVelocity,
relativeVelocity;
if (constraint.damping) {
if (damping > 0) {
var zero = Vector.create();
normal = Vector.div(delta, currentLength);
@ -218,9 +222,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
@ -241,9 +245,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

View file

@ -10,6 +10,7 @@ module.exports = Common;
(function() {
Common._timeUnit = 1000 / 60;
Common._nextId = 0;
Common._seed = 0;
Common._nowStartTime = +(new Date());

View file

@ -97,35 +97,29 @@ var Body = require('../body/Body');
/**
* 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 <a href="http://lonesock.net/article/verlet.html">Time Corrected Verlet</a> 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) {
delta = delta || 1000 / 60;
correction = correction || 1;
Engine.update = function(engine, delta) {
var world = engine.world,
timing = engine.timing,
broadphase = engine.broadphase,
broadphasePairs = [],
broadphasePairs,
i;
delta = typeof delta !== 'undefined' ? delta : Common._timeUnit;
delta *= timing.timeScale;
// increment timestamp
timing.timestamp += delta * timing.timeScale;
timing.timestamp += delta;
// create an event object
var event = {
timestamp: timing.timestamp
timestamp: timing.timestamp,
delta: delta
};
Events.trigger(engine, 'beforeUpdate', event);
@ -141,18 +135,20 @@ var Body = require('../body/Body');
// if sleeping enabled, call the sleeping controller
if (engine.enableSleeping)
Sleeping.update(allBodies, timing.timeScale);
Sleeping.update(allBodies, delta);
// applies gravity to all bodies
Engine._bodiesApplyGravity(allBodies, world.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);
}
// 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);
@ -176,7 +172,7 @@ var Body = require('../body/Body');
}
// narrowphase pass: find actual collisions, then create or update collision pairs
var collisions = broadphase.detector(broadphasePairs, engine);
var collisions = broadphase.detector(broadphasePairs, engine, delta);
// update collision pairs
var pairs = engine.pairs,
@ -186,7 +182,7 @@ var Body = require('../body/Body');
// wake up bodies involved in collisions
if (engine.enableSleeping)
Sleeping.afterCollisions(pairs.list, timing.timeScale);
Sleeping.afterCollisions(pairs.list, delta);
// trigger collision events
if (pairs.collisionStart.length > 0)
@ -195,21 +191,21 @@ var Body = require('../body/Body');
// iteratively resolve position between collisions
Resolver.preSolvePosition(pairs.list);
for (i = 0; i < engine.positionIterations; i++) {
Resolver.solvePosition(pairs.list, allBodies, timing.timeScale);
Resolver.solvePosition(pairs.list, allBodies, delta);
}
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
@ -322,21 +318,16 @@ var Body = require('../body/Body');
* @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
* @param {number} delta The amount of time elapsed between updates
*/
Engine._bodiesUpdate = function(bodies, deltaTime, timeScale, correction, worldBounds) {
Engine._bodiesUpdate = function(bodies, delta) {
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (body.isStatic || body.isSleeping)
continue;
Body.update(body, deltaTime, timeScale, correction);
Body.update(body, delta);
}
};
@ -373,6 +364,7 @@ var Body = require('../body/Body');
* @param {} event An event object
* @param {} event.pairs List of affected pairs
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {number} event.delta The delta time in milliseconds value used in the update
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
@ -384,6 +376,7 @@ var Body = require('../body/Body');
* @param {} event An event object
* @param {} event.pairs List of affected pairs
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {number} event.delta The delta time in milliseconds value used in the update
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/

View file

@ -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
@ -100,7 +98,7 @@ var Common = require('./Common');
/**
* A game loop utility that updates the engine and renderer by one step (a 'tick').
* Features delta smoothing, time correction and fixed or dynamic timing.
* Features delta smoothing and fixed or dynamic timing.
* Triggers `beforeTick`, `tick` and `afterTick` events on the engine.
* Consider just `Engine.update(engine, delta)` if you're using your own loop.
* @method tick
@ -110,17 +108,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);
Events.trigger(engine, 'beforeTick', event); // @deprecated
if (runner.isFixed) {
// fixed timestep
delta = runner.delta;
@ -133,27 +122,22 @@ 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);
Events.trigger(engine, 'beforeTick', event); // @deprecated
// fps counter
runner.frameCounter += 1;

View file

@ -9,6 +9,7 @@ var Sleeping = {};
module.exports = Sleeping;
var Events = require('./Events');
var Common = require('./Common');
(function() {
@ -20,11 +21,11 @@ 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._timeUnit;
// update bodies sleeping status
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i],
@ -41,11 +42,11 @@ 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 < Sleeping._motionSleepThreshold * timeScale * timeScale) {
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 +58,10 @@ var Events = require('./Events');
* Given a set of colliding pairs, wakes the sleeping bodies involved.
* @method afterCollisions
* @param {pair[]} pairs
* @param {number} timeScale
* @param {number} delta
*/
Sleeping.afterCollisions = function(pairs, timeScale) {
var timeFactor = timeScale * timeScale * timeScale;
Sleeping.afterCollisions = function(pairs, delta) {
var timeScale = delta / Common._timeUnit;
// wake up bodies involved in collisions
for (var i = 0; i < pairs.length; i++) {
@ -82,7 +83,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 > Sleeping._motionWakeThreshold * timeScale * timeScale) {
Sleeping.set(sleepingBody, false);
}
}