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

Merge branch 'timing-improve'

This commit is contained in:
liabru 2023-02-11 18:05:33 +00:00
commit b81d6e6d7f
18 changed files with 589 additions and 322 deletions

View file

@ -50,8 +50,8 @@ Example.bridge = function() {
});
Composites.chain(bridge, 0.3, 0, -0.3, 0, {
stiffness: 1,
length: 0,
stiffness: 0.99,
length: 0.0001,
render: {
visible: false
}

View file

@ -50,19 +50,20 @@ Example.compositeManipulation = function() {
engine.gravity.y = 0;
Events.on(engine, 'afterUpdate', function(event) {
var time = engine.timing.timestamp;
var time = engine.timing.timestamp,
timeScale = (event.delta || (1000 / 60)) / 1000;
Composite.translate(stack, {
x: Math.sin(time * 0.001) * 2,
x: Math.sin(time * 0.001) * 10 * timeScale,
y: 0
});
Composite.rotate(stack, Math.sin(time * 0.001) * 0.01, {
Composite.rotate(stack, Math.sin(time * 0.001) * 0.75 * timeScale, {
x: 300,
y: 300
});
var scale = 1 + (Math.sin(time * 0.001) * 0.01);
var scale = 1 + (Math.sin(time * 0.001) * 0.75 * timeScale);
Composite.scale(stack, scale, scale, {
x: 300,

View file

@ -39,13 +39,19 @@ Example.events = function() {
// do something with event.object
});
var lastTime = Common.now();
// an example of using beforeUpdate event on an engine
Events.on(engine, 'beforeUpdate', function(event) {
var engine = event.source;
// apply random forces every 5 secs
if (event.timestamp % 5000 < 50)
if (Common.now() - lastTime >= 5000) {
shakeScene(engine);
// update last time
lastTime = Common.now();
}
});
// an example of using collisionStart event on an engine
@ -102,14 +108,17 @@ Example.events = function() {
Composite.add(world, stack);
var shakeScene = function(engine) {
var timeScale = (1000 / 60) / engine.timing.lastDelta;
var bodies = Composite.allBodies(engine.world);
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (!body.isStatic && body.position.y >= 500) {
var forceMagnitude = 0.02 * body.mass;
// scale force for mass and time applied
var forceMagnitude = (0.03 * body.mass) * timeScale;
// apply the force over a single update
Body.applyForce(body, body.position, {
x: (forceMagnitude + Common.random() * forceMagnitude) * Common.choose([1, -1]),
y: -forceMagnitude + Common.random() * -forceMagnitude

View file

@ -35,20 +35,24 @@ Example.manipulation = function() {
Runner.run(runner, engine);
// add bodies
var bodyA = Bodies.rectangle(100, 200, 50, 50, { isStatic: true, render: { fillStyle: '#060a19' } }),
var bodyA = Bodies.rectangle(100, 300, 50, 50, { isStatic: true, render: { fillStyle: '#060a19' } }),
bodyB = Bodies.rectangle(200, 200, 50, 50),
bodyC = Bodies.rectangle(300, 200, 50, 50),
bodyD = Bodies.rectangle(400, 200, 50, 50),
bodyE = Bodies.rectangle(550, 200, 50, 50),
bodyF = Bodies.rectangle(700, 200, 50, 50),
bodyG = Bodies.circle(400, 100, 25, { render: { fillStyle: '#060a19' } }),
partA = Bodies.rectangle(600, 200, 120, 50, { render: { fillStyle: '#060a19' } }),
partB = Bodies.rectangle(660, 200, 50, 190, { render: { fillStyle: '#060a19' } }),
bodyG = Bodies.circle(400, 100, 25, { render: { fillStyle: '#060a19' } });
// add compound body
var partA = Bodies.rectangle(600, 200, 120 * 0.8, 50 * 0.8, { render: { fillStyle: '#060a19' } }),
partB = Bodies.rectangle(660, 200, 50 * 0.8, 190 * 0.8, { render: { fillStyle: '#060a19' } }),
compound = Body.create({
parts: [partA, partB],
isStatic: true
});
Body.setPosition(compound, { x: 600, y: 300 });
Composite.add(world, [bodyA, bodyB, bodyC, bodyD, bodyE, bodyF, bodyG, compound]);
Composite.add(world, [
@ -59,48 +63,57 @@ Example.manipulation = function() {
Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
]);
var counter = 0,
scaleFactor = 1.01;
var lastTime = 0,
scaleRate = 0.6;
Events.on(runner, 'afterTick', function(event) {
counter += 1;
Events.on(engine, 'beforeUpdate', function(event) {
var timeScale = (event.delta || (1000 / 60)) / 1000;
if (counter === 40)
Body.setStatic(bodyG, true);
if (scaleFactor > 1) {
Body.scale(bodyF, scaleFactor, scaleFactor);
Body.scale(compound, 0.995, 0.995);
if (scaleRate > 0) {
Body.scale(bodyF, 1 + (scaleRate * timeScale), 1 + (scaleRate * timeScale));
// modify bodyE vertices
bodyE.vertices[0].x -= 0.2;
bodyE.vertices[0].y -= 0.2;
bodyE.vertices[1].x += 0.2;
bodyE.vertices[1].y -= 0.2;
bodyE.vertices[0].x -= 0.2 * timeScale;
bodyE.vertices[0].y -= 0.2 * timeScale;
bodyE.vertices[1].x += 0.2 * timeScale;
bodyE.vertices[1].y -= 0.2 * timeScale;
Body.setVertices(bodyE, bodyE.vertices);
}
// make bodyA move up and down
// body is static so must manually update velocity for friction to work
var py = 300 + 100 * Math.sin(engine.timing.timestamp * 0.002);
Body.setVelocity(bodyA, { x: 0, y: py - bodyA.position.y });
Body.setPosition(bodyA, { x: 100, y: py });
// make compound body move up and down and rotate constantly
Body.setVelocity(compound, { x: 0, y: py - compound.position.y });
Body.setAngularVelocity(compound, 0.02);
Body.setPosition(compound, { x: 600, y: py });
Body.rotate(compound, 0.02);
// manual update velocity required for older releases
if (Matter.version === '0.18.0') {
Body.setVelocity(bodyA, { x: 0, y: py - bodyA.position.y });
Body.setVelocity(compound, { x: 0, y: py - compound.position.y });
Body.setAngularVelocity(compound, 1 * Math.PI * timeScale);
}
// every 1.5 sec
if (counter >= 60 * 1.5) {
// move body and update velocity
Body.setPosition(bodyA, { x: 100, y: py }, true);
// move compound body move up and down and update velocity
Body.setPosition(compound, { x: 600, y: py }, true);
// rotate compound body and update angular velocity
Body.rotate(compound, 1 * Math.PI * timeScale, null, true);
// after first 0.8 sec (simulation time)
if (engine.timing.timestamp >= 800)
Body.setStatic(bodyG, true);
// every 1.5 sec (simulation time)
if (engine.timing.timestamp - lastTime >= 1500) {
Body.setVelocity(bodyB, { x: 0, y: -10 });
Body.setAngle(bodyC, -Math.PI * 0.26);
Body.setAngularVelocity(bodyD, 0.2);
// reset counter
counter = 0;
scaleFactor = 1;
// stop scaling
scaleRate = 0;
// update last time
lastTime = engine.timing.timestamp;
}
});

View file

@ -99,7 +99,7 @@ Example.newtonsCradle.newtonsCradle = function(xx, yy, number, size, length) {
for (var i = 0; i < number; i++) {
var separation = 1.9,
circle = Bodies.circle(xx + i * (size * separation), yy + length, size,
{ inertia: Infinity, restitution: 1, friction: 0, frictionAir: 0.0001, slop: 1 }),
{ inertia: Infinity, restitution: 1, friction: 0, frictionAir: 0, slop: size * 0.02 }),
constraint = Constraint.create({ pointA: { x: xx + i * (size * separation), y: yy }, bodyB: circle });
Composite.addBody(newtonsCradle, circle);

View file

@ -9,11 +9,9 @@ Example.ragdoll = function() {
Common = Matter.Common,
Composite = Matter.Composite,
Composites = Matter.Composites,
Constraint = Matter.Constraint,
MouseConstraint = Matter.MouseConstraint,
Mouse = Matter.Mouse,
Bodies = Matter.Bodies,
Vector = Matter.Vector;
Bodies = Matter.Bodies;
// create engine
var engine = Engine.create(),
@ -82,21 +80,20 @@ Example.ragdoll = function() {
Composite.add(world, [stack, obstacles, ragdolls]);
var timeScaleTarget = 1,
counter = 0;
lastTime = Common.now();
Events.on(engine, 'afterUpdate', function(event) {
var timeScale = (event.delta || (1000 / 60)) / 1000;
// tween the timescale for slow-mo
if (mouse.button === -1) {
engine.timing.timeScale += (timeScaleTarget - engine.timing.timeScale) * 0.05;
engine.timing.timeScale += (timeScaleTarget - engine.timing.timeScale) * 3 * timeScale;
} else {
engine.timing.timeScale = 1;
}
counter += 1;
// every 1.5 sec
if (counter >= 60 * 1.5) {
// every 2 sec (real time)
if (Common.now() - lastTime >= 2000) {
// flip the timescale
if (timeScaleTarget < 1) {
timeScaleTarget = 1;
@ -104,8 +101,8 @@ Example.ragdoll = function() {
timeScaleTarget = 0.05;
}
// reset counter
counter = 0;
// update last time
lastTime = Common.now();
}
for (var i = 0; i < stack.bodies.length; i += 1) {
@ -113,8 +110,8 @@ Example.ragdoll = function() {
// animate stairs
Body.translate(body, {
x: -0.5 * engine.timing.timeScale,
y: -0.5 * engine.timing.timeScale
x: -30 * timeScale,
y: -30 * timeScale
});
// loop stairs when they go off screen

View file

@ -34,7 +34,7 @@ Example.remove = function() {
Runner.run(runner, engine);
var stack = null,
updateCount = 0;
lastTimestamp = 0;
var createStack = function() {
return Composites.stack(20, 20, 10, 5, 0, 0, function(x, y) {
@ -62,14 +62,13 @@ Example.remove = function() {
};
// add and remove stacks every few updates
Events.on(engine, 'afterUpdate', function() {
Events.on(engine, 'afterUpdate', function(event) {
// limit rate
if (stack && updateCount <= 50) {
updateCount += 1;
if (stack && event.timestamp - lastTimestamp < 800) {
return;
}
updateCount = 0;
lastTimestamp = event.timestamp;
// remove last stack
if (stack) {

View file

@ -9,6 +9,7 @@ Example.slingshot = function() {
Constraint = Matter.Constraint,
MouseConstraint = Matter.MouseConstraint,
Mouse = Matter.Mouse,
Body = Matter.Body,
Composite = Matter.Composite,
Bodies = Matter.Bodies;
@ -41,6 +42,8 @@ Example.slingshot = function() {
elastic = Constraint.create({
pointA: anchor,
bodyB: rock,
length: 0.01,
damping: 0.01,
stiffness: 0.05
});
@ -58,6 +61,12 @@ Example.slingshot = function() {
Events.on(engine, 'afterUpdate', function() {
if (mouseConstraint.mouse.button === -1 && (rock.position.x > 190 || rock.position.y < 430)) {
// Limit maximum speed of current rock.
if (Body.getSpeed(rock) > 45) {
Body.setSpeed(rock, 45);
}
// Release current rock and add a new one.
rock = Bodies.polygon(170, 450, 7, 20, rockOptions);
Composite.add(engine.world, rock);
elastic.bodyB = rock;

View file

@ -35,8 +35,7 @@ Example.staticFriction = function() {
// add bodies
var body = Bodies.rectangle(400, 500, 200, 60, { isStatic: true, chamfer: 10, render: { fillStyle: '#060a19' } }),
size = 50,
counter = -1;
size = 50;
var stack = Composites.stack(350, 470 - 6 * size, 1, 6, 0, 0, function(x, y) {
return Bodies.rectangle(x, y, size * 2, size, {
@ -56,18 +55,19 @@ Example.staticFriction = function() {
Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
]);
Events.on(engine, 'beforeUpdate', function(event) {
counter += 0.014;
if (counter < 0) {
Events.on(engine, 'beforeUpdate', function() {
if (engine.timing.timestamp < 1500) {
return;
}
var px = 400 + 100 * Math.sin(counter);
var px = 400 + 100 * Math.sin((engine.timing.timestamp - 1500) * 0.001);
// body is static so must manually update velocity for friction to work
Body.setVelocity(body, { x: px - body.position.x, y: 0 });
Body.setPosition(body, { x: px, y: body.position.y });
// manual update velocity required for older releases
if (Matter.version === '0.18.0') {
Body.setVelocity(body, { x: px - body.position.x, y: 0 });
}
Body.setPosition(body, { x: px, y: body.position.y }, true);
});
// add mouse control

View file

@ -42,15 +42,18 @@ Example.timescale = function() {
Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
]);
var explosion = function(engine) {
var explosion = function(engine, delta) {
var timeScale = (1000 / 60) / delta;
var bodies = Composite.allBodies(engine.world);
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (!body.isStatic && body.position.y >= 500) {
var forceMagnitude = 0.05 * body.mass;
// scale force for mass and time applied
var forceMagnitude = (0.05 * body.mass) * timeScale;
// apply the force over a single update
Body.applyForce(body, body.position, {
x: (forceMagnitude + Common.random() * forceMagnitude) * Common.choose([1, -1]),
y: -forceMagnitude + Common.random() * -forceMagnitude
@ -60,30 +63,29 @@ Example.timescale = function() {
};
var timeScaleTarget = 1,
counter = 0;
lastTime = Common.now();
Events.on(engine, 'afterUpdate', function(event) {
var timeScale = (event.delta || (1000 / 60)) / 1000;
// tween the timescale for bullet time slow-mo
engine.timing.timeScale += (timeScaleTarget - engine.timing.timeScale) * 0.05;
engine.timing.timeScale += (timeScaleTarget - engine.timing.timeScale) * 12 * timeScale;
counter += 1;
// every 1.5 sec
if (counter >= 60 * 1.5) {
// every 2 sec (real time)
if (Common.now() - lastTime >= 2000) {
// flip the timescale
if (timeScaleTarget < 1) {
timeScaleTarget = 1;
} else {
timeScaleTarget = 0.05;
timeScaleTarget = 0;
}
// create some random forces
explosion(engine);
explosion(engine, event.delta);
// reset counter
counter = 0;
// update last time
lastTime = Common.now();
}
});
@ -93,12 +95,10 @@ Example.timescale = function() {
restitution: 0.8
};
// add some small bouncy circles... remember Swordfish?
Composite.add(world, Composites.stack(20, 100, 15, 3, 20, 40, function(x, y) {
return Bodies.circle(x, y, Common.random(10, 20), bodyOptions);
}));
// add some larger random bouncy objects
Composite.add(world, Composites.stack(50, 50, 8, 3, 0, 0, function(x, y) {
switch (Math.round(Common.random(0, 1))) {
@ -155,4 +155,4 @@ Example.timescale.for = '>=0.14.2';
if (typeof module !== 'undefined') {
module.exports = Example.timescale;
}
}

View file

@ -1,7 +1,6 @@
/**
* The `Matter.Body` module contains methods for creating and manipulating body models.
* A `Matter.Body` is a rigid body that can be simulated by a `Matter.Engine`.
* Factories for commonly used body configurations (such as rectangles, circles and other polygons) can be found in the module `Matter.Bodies`.
* The `Matter.Body` module contains methods for creating and manipulating rigid bodies.
* For creating bodies with common configurations such as rectangles, circles and other polygons see the module `Matter.Bodies`.
*
* See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples).
@ -15,17 +14,18 @@ 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;
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.
@ -96,6 +96,7 @@ var Axes = require('../geometry/Axes');
area: 0,
mass: 0,
inertia: 0,
deltaTime: 1000 / 60,
_original: null
};
@ -231,6 +232,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;
@ -320,7 +327,7 @@ var Axes = require('../geometry/Axes');
};
/**
* Sets the moment of inertia (i.e. second moment of area) of the body.
* Sets the moment of inertia of the body. This is the second moment of area in two dimensions.
* Inverse inertia is automatically updated to reflect the change. Mass is not changed.
* @method setInertia
* @param {body} body
@ -337,8 +344,8 @@ var Axes = require('../geometry/Axes');
* They are then automatically translated to world space based on `body.position`.
*
* The `vertices` argument should be passed as an array of `Matter.Vector` points (or a `Matter.Vertices` array).
* Vertices must form a convex hull, concave hulls are not supported.
*
* Vertices must form a convex hull. Concave vertices must be decomposed into convex parts.
*
* @method setVertices
* @param {body} body
* @param {vector[]} vertices
@ -375,7 +382,7 @@ var Axes = require('../geometry/Axes');
* Note that this method will ensure that the first part in `body.parts` will always be the `body`.
* @method setParts
* @param {body} body
* @param [body] parts
* @param {body[]} parts
* @param {bool} [autoHull=true]
*/
Body.setParts = function(body, parts, autoHull) {
@ -457,15 +464,26 @@ var Axes = require('../geometry/Axes');
};
/**
* Sets the position of the body instantly. Velocity, angle, force etc. are unchanged.
* Sets the position of the body. By default velocity is unchanged.
* If `updateVelocity` is `true` then velocity is inferred from the change in position.
* @method setPosition
* @param {body} body
* @param {vector} position
* @param {boolean} [updateVelocity=false]
*/
Body.setPosition = function(body, position) {
Body.setPosition = function(body, position, updateVelocity) {
var delta = Vector.sub(position, body.position);
body.positionPrev.x += delta.x;
body.positionPrev.y += delta.y;
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];
@ -477,14 +495,23 @@ var Axes = require('../geometry/Axes');
};
/**
* Sets the angle of the body instantly. Angular velocity, position, force etc. are unchanged.
* Sets the angle of the body. By default angular velocity is unchanged.
* If `updateVelocity` is `true` then angular velocity is inferred from the change in angle.
* @method setAngle
* @param {body} body
* @param {number} angle
* @param {boolean} [updateVelocity=false]
*/
Body.setAngle = function(body, angle) {
Body.setAngle = function(body, angle, updateVelocity) {
var delta = angle - body.angle;
body.anglePrev += delta;
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];
@ -499,51 +526,128 @@ var Axes = require('../geometry/Axes');
};
/**
* Sets the linear velocity of the body instantly. Position, angle, force etc. are unchanged. See also `Body.applyForce`.
* Sets the current linear velocity of the body.
* Affects body speed.
* @method setVelocity
* @param {body} body
* @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);
};
/**
* Sets the angular velocity of the body instantly. Position, angle, force etc. are unchanged. See also `Body.applyForce`.
* 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 current rotational velocity of the body.
* Affects body angular speed.
* @method setAngularVelocity
* @param {body} body
* @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);
};
/**
* Moves a body by a given vector relative to its current position, without imparting any velocity.
* @method translate
* Gets the current rotational velocity of the body.
* @method getAngularVelocity
* @param {body} body
* @param {vector} translation
* @return {number} angular velocity
*/
Body.translate = function(body, translation) {
Body.setPosition(body, Vector.add(body.position, translation));
Body.getAngularVelocity = function(body) {
return (body.angle - body.anglePrev) * Body._baseDelta / body.deltaTime;
};
/**
* Rotates a body by a given angle relative to its current angle, without imparting any angular velocity.
* 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. By default velocity is unchanged.
* If `updateVelocity` is `true` then velocity is inferred from the change in position.
* @method translate
* @param {body} body
* @param {vector} translation
* @param {boolean} [updateVelocity=false]
*/
Body.translate = function(body, translation, updateVelocity) {
Body.setPosition(body, Vector.add(body.position, translation), updateVelocity);
};
/**
* Rotates a body by a given angle relative to its current angle. By default angular velocity is unchanged.
* If `updateVelocity` is `true` then angular velocity is inferred from the change in angle.
* @method rotate
* @param {body} body
* @param {number} rotation
* @param {vector} [point]
* @param {boolean} [updateVelocity=false]
*/
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),
@ -553,9 +657,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);
}
};
@ -624,39 +728,38 @@ var Axes = require('../geometry/Axes');
};
/**
* Performs a simulation step for the given `body`, including updating position and angle using Verlet integration.
* Performs an update by integrating the equations of motion on the `body`.
* This is applied every update by `Matter.Engine` automatically.
* @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 : (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;
body.anglePrev = body.angle;
body.angle += body.angularVelocity;
// track speed and acceleration
body.speed = Vector.magnitude(body.velocity);
body.angularSpeed = Math.abs(body.angularVelocity);
// transform the body geometry
for (var i = 0; i < body.parts.length; i++) {
var part = body.parts[i];
@ -681,16 +784,45 @@ var Axes = require('../geometry/Axes');
};
/**
* Applies a force to a body from a given world-space position, including resulting torque.
* 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 the `force` to the `body` from the force origin `position` in world-space, over a single timestep, including applying any resulting angular torque.
*
* Forces are useful for effects like gravity, wind or rocket thrust, but can be difficult in practice when precise control is needed. In these cases see `Body.setVelocity` and `Body.setPosition` as an alternative.
*
* The force from this function is only applied once for the duration of a single timestep, in other words the duration depends directly on the current engine update `delta` and the rate of calls to this function.
*
* Therefore to account for time, you should apply the force constantly over as many engine updates as equivalent to the intended duration.
*
* If all or part of the force duration is some fraction of a timestep, first multiply the force by `duration / timestep`.
*
* The force origin `position` in world-space must also be specified. Passing `body.position` will result in zero angular effect as the force origin would be at the centre of mass.
*
* The `body` will take time to accelerate under a force, the resulting effect depends on duration of the force, the body mass and other forces on the body including friction combined.
* @method applyForce
* @param {body} body
* @param {vector} position
* @param {vector} position The force origin in world-space. Pass `body.position` to avoid angular torque.
* @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;
};
@ -769,12 +901,14 @@ var Axes = require('../geometry/Axes');
*/
/**
* _Read only_. Set by `Body.create`.
*
* A `String` denoting the type of object.
*
* @readOnly
* @property type
* @type string
* @default "body"
* @readOnly
*/
/**
@ -786,6 +920,8 @@ var Axes = require('../geometry/Axes');
*/
/**
* _Read only_. Use `Body.setParts` to set.
*
* An array of bodies that make up this body.
* The first body in the array must always be a self reference to the current body instance.
* All bodies in the `parts` array together form a single rigid compound body.
@ -793,6 +929,7 @@ var Axes = require('../geometry/Axes');
* Parts themselves should never be added to a `World`, only the parent body should be.
* Use `Body.setParts` when setting parts to ensure correct updates of all properties.
*
* @readOnly
* @property parts
* @type body[]
*/
@ -805,10 +942,12 @@ var Axes = require('../geometry/Axes');
*/
/**
* A self reference if the body is _not_ a part of another body.
* Otherwise this is a reference to the body that this is a part of.
* See `body.parts`.
* _Read only_. Updated by `Body.setParts`.
*
* A reference to the body that this is a part of. See `body.parts`.
* This is a self reference if the body is not a part of another body.
*
* @readOnly
* @property parent
* @type body
*/
@ -822,48 +961,65 @@ var Axes = require('../geometry/Axes');
*/
/**
* _Read only_. Use `Body.setVertices` or `Body.setParts` to set. See also `Bodies.fromVertices`.
*
* An array of `Vector` objects that specify the convex hull of the rigid body.
* These should be provided about the origin `(0, 0)`. E.g.
*
* [{ x: 0, y: 0 }, { x: 25, y: 50 }, { x: 50, y: 0 }]
* `[{ x: 0, y: 0 }, { x: 25, y: 50 }, { x: 50, y: 0 }]`
*
* Vertices must always be convex, in clockwise order and must not contain any duplicate points.
*
* Concave vertices should be decomposed into convex `parts`, see `Bodies.fromVertices` and `Body.setParts`.
*
* When passed via `Body.create`, the vertices are translated relative to `body.position` (i.e. world-space, and constantly updated by `Body.update` during simulation).
* The `Vector` objects are also augmented with additional properties required for efficient collision detection.
*
* Other properties such as `inertia` and `bounds` are automatically calculated from the passed vertices (unless provided via `options`).
* Concave hulls are not currently supported. The module `Matter.Vertices` contains useful methods for working with vertices.
* When set the vertices are translated such that `body.position` is at the centre of mass.
* Many other body properties are automatically calculated from these vertices when set including `density`, `area` and `inertia`.
*
* The module `Matter.Vertices` contains useful methods for working with vertices.
*
* @readOnly
* @property vertices
* @type vector[]
*/
/**
* _Read only_. Use `Body.setPosition` to set.
*
* A `Vector` that specifies the current world-space position of the body.
*
*
* @readOnly
* @property position
* @type vector
* @default { x: 0, y: 0 }
*/
/**
* A `Vector` that specifies the force to apply in the current step. It is zeroed after every `Body.update`. See also `Body.applyForce`.
*
* A `Vector` that accumulates the total force applied to the body for a single update.
* Force is zeroed after every `Engine.update`, so constant forces should be applied for every update they are needed. See also `Body.applyForce`.
*
* @property force
* @type vector
* @default { x: 0, y: 0 }
*/
/**
* A `Number` that specifies the torque (turning force) to apply in the current step. It is zeroed after every `Body.update`.
* A `Number` that accumulates the total torque (turning force) applied to the body for a single update. See also `Body.applyForce`.
* Torque is zeroed after every `Engine.update`, so constant torques should be applied for every update they are needed.
*
* Torques result in angular acceleration on every update, which depends on body inertia and the engine update delta.
*
* @property torque
* @type number
* @default 0
*/
/**
* A `Number` that _measures_ the current speed of the body after the last `Body.update`. It is read-only and always positive (it's the magnitude of `body.velocity`).
*
* _Read only_. Use `Body.setSpeed` to set.
*
* See `Body.getSpeed` for details.
*
* Equivalent to the magnitude of `body.velocity` (always positive).
*
* @readOnly
* @property speed
* @type number
@ -871,18 +1027,12 @@ var Axes = require('../geometry/Axes');
*/
/**
* A `Number` that _measures_ the current angular speed of the body after the last `Body.update`. It is read-only and always positive (it's the magnitude of `body.angularVelocity`).
*
* @readOnly
* @property angularSpeed
* @type number
* @default 0
*/
/**
* 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).
*
* _Read only_. Use `Body.setVelocity` to set.
*
* See `Body.getVelocity` for details.
*
* Equivalent to the magnitude of `body.angularVelocity` (always positive).
*
* @readOnly
* @property velocity
* @type vector
@ -890,8 +1040,22 @@ var Axes = require('../geometry/Axes');
*/
/**
* A `Number` that _measures_ the current angular velocity of the body after the last `Body.update`. It is read-only.
* If you need to modify a body's angular velocity directly, you should apply a torque or simply change the body's `angle` (as the engine uses position-Verlet integration).
* _Read only_. Use `Body.setAngularSpeed` to set.
*
* See `Body.getAngularSpeed` for details.
*
*
* @readOnly
* @property angularSpeed
* @type number
* @default 0
*/
/**
* _Read only_. Use `Body.setAngularVelocity` to set.
*
* See `Body.getAngularVelocity` for details.
*
*
* @readOnly
* @property angularVelocity
@ -900,9 +1064,11 @@ var Axes = require('../geometry/Axes');
*/
/**
* _Read only_. Use `Body.setStatic` to set.
*
* A flag that indicates whether a body is considered static. A static body can never change position or angle and is completely fixed.
* If you need to set a body as static after its creation, you should use `Body.setStatic` as this requires more than just setting this flag.
*
* @readOnly
* @property isStatic
* @type boolean
* @default false
@ -917,18 +1083,23 @@ var Axes = require('../geometry/Axes');
*/
/**
* _Read only_. Use `Sleeping.set` to set.
*
* A flag that indicates whether the body is considered sleeping. A sleeping body acts similar to a static body, except it is only temporary and can be awoken.
* If you need to set a body as sleeping, you should use `Sleeping.set` as this requires more than just setting this flag.
*
* @readOnly
* @property isSleeping
* @type boolean
* @default false
*/
/**
* A `Number` that _measures_ the amount of movement a body currently has (a combination of `speed` and `angularSpeed`). It is read-only and always positive.
* It is used and updated by the `Matter.Sleeping` module during simulation to decide if a body has come to rest.
* _Read only_. Calculated during engine update only when sleeping is enabled.
*
* A `Number` that loosely measures the amount of movement a body currently has.
*
* Derived from `body.speed^2 + body.angularSpeed^2`. See `Sleeping.update`.
*
* @readOnly
* @property motion
* @type number
@ -936,52 +1107,66 @@ var Axes = require('../geometry/Axes');
*/
/**
* A `Number` that defines the number of updates in which this body must have near-zero velocity before it is set as sleeping by the `Matter.Sleeping` module (if sleeping is enabled by the engine).
*
* A `Number` that defines the length of time during which this body must have near-zero velocity before it is set as sleeping by the `Matter.Sleeping` module (if sleeping is enabled by the engine).
*
* @property sleepThreshold
* @type number
* @default 60
*/
/**
* A `Number` that defines the density of the body, that is its mass per unit area.
* If you pass the density via `Body.create` the `mass` property is automatically calculated for you based on the size (area) of the object.
* This is generally preferable to simply setting mass and allows for more intuitive definition of materials (e.g. rock has a higher density than wood).
* _Read only_. Use `Body.setDensity` to set.
*
* A `Number` that defines the density of the body (mass per unit area).
*
* Mass will also be updated when set.
*
* @readOnly
* @property density
* @type number
* @default 0.001
*/
/**
* A `Number` that defines the mass of the body, although it may be more appropriate to specify the `density` property instead.
* If you modify this value, you must also modify the `body.inverseMass` property (`1 / mass`).
*
* _Read only_. Use `Body.setMass` to set.
*
* A `Number` that defines the mass of the body.
*
* Density will also be updated when set.
*
* @readOnly
* @property mass
* @type number
*/
/**
* _Read only_. Use `Body.setMass` to set.
*
* A `Number` that defines the inverse mass of the body (`1 / mass`).
* If you modify this value, you must also modify the `body.mass` property.
*
* @readOnly
* @property inverseMass
* @type number
*/
/**
* A `Number` that defines the moment of inertia (i.e. second moment of area) of the body.
* It is automatically calculated from the given convex hull (`vertices` array) and density in `Body.create`.
* If you modify this value, you must also modify the `body.inverseInertia` property (`1 / inertia`).
*
* _Read only_. Automatically calculated when vertices, mass or density are set or set through `Body.setInertia`.
*
* A `Number` that defines the moment of inertia of the body. This is the second moment of area in two dimensions.
*
* Can be manually set to `Infinity` to prevent rotation of the body. See `Body.setInertia`.
*
* @readOnly
* @property inertia
* @type number
*/
/**
* _Read only_. Automatically calculated when vertices, mass or density are set or calculated by `Body.setInertia`.
*
* A `Number` that defines the inverse moment of inertia of the body (`1 / inertia`).
* If you modify this value, you must also modify the `body.inertia` property.
*
*
* @readOnly
* @property inverseInertia
* @type number
*/
@ -992,7 +1177,7 @@ var Axes = require('../geometry/Axes');
* A value of `0.8` means the body may bounce back with approximately 80% of its kinetic energy.
* Note that collision response is based on _pairs_ of bodies, and that `restitution` values are _combined_ with the following formula:
*
* Math.max(bodyA.restitution, bodyB.restitution)
* `Math.max(bodyA.restitution, bodyB.restitution)`
*
* @property restitution
* @type number
@ -1009,7 +1194,7 @@ var Axes = require('../geometry/Axes');
* The engine uses a Coulomb friction model including static and kinetic friction.
* Note that collision response is based on _pairs_ of bodies, and that `friction` values are _combined_ with the following formula:
*
* Math.min(bodyA.friction, bodyB.friction)
* `Math.min(bodyA.friction, bodyB.friction)`
*
* @property friction
* @type number
@ -1094,9 +1279,11 @@ var Axes = require('../geometry/Axes');
*/
/**
* A `Number` that specifies a tolerance on how far a body is allowed to 'sink' or rotate into other bodies.
* Avoid changing this value unless you understand the purpose of `slop` in physics engines.
* The default should generally suffice, although very large bodies may require larger values for stable stacking.
* A `Number` that specifies a thin boundary around the body where it is allowed to slightly sink into other bodies.
*
* This is required for proper collision response, including friction and restitution effects.
*
* The default should generally suffice in most cases. You may need to decrease this value for very small bodies that are nearing the default value in scale.
*
* @property slop
* @type number
@ -1104,13 +1291,25 @@ var Axes = require('../geometry/Axes');
*/
/**
* A `Number` that allows per-body time scaling, e.g. a force-field where bodies inside are in slow-motion, while others are at full speed.
* A `Number` that specifies per-body time scaling.
*
* @property timeScale
* @type number
* @default 1
*/
/**
* _Read only_. Updated during engine update.
*
* A `Number` that records the last delta time value used to update this body.
* Used to calculate speed and velocity.
*
* @readOnly
* @property deltaTime
* @type number
* @default 1000 / 60
*/
/**
* An `Object` that defines the rendering properties to be consumed by the module `Matter.Render`.
*
@ -1208,17 +1407,23 @@ var Axes = require('../geometry/Axes');
*/
/**
* _Read only_. Calculated automatically when vertices are set.
*
* An array of unique axis vectors (edge normals) used for collision detection.
* These are automatically calculated from the given convex hull (`vertices` array) in `Body.create`.
* These are automatically calculated when vertices are set.
* They are constantly updated by `Body.update` during the simulation.
*
* @readOnly
* @property axes
* @type vector[]
*/
/**
* A `Number` that _measures_ the area of the body's convex hull, calculated at creation by `Body.create`.
*
* _Read only_. Calculated automatically when vertices are set.
*
* A `Number` that measures the area of the body's convex hull.
*
* @readOnly
* @property area
* @type string
* @default
@ -1226,8 +1431,8 @@ var Axes = require('../geometry/Axes');
/**
* A `Bounds` object that defines the AABB region for the body.
* It is automatically calculated from the given convex hull (`vertices` array) in `Body.create` and constantly updated by `Body.update` during simulation.
*
* It is automatically calculated when vertices are set and constantly updated by `Body.update` during simulation.
*
* @property bounds
* @type bounds
*/

View file

@ -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.
@ -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,7 +61,8 @@ 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
@ -89,7 +93,7 @@ var Bounds = require('../geometry/Bounds');
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;
@ -219,14 +223,16 @@ 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,
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,
@ -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;
@ -287,12 +293,12 @@ 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;
@ -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 {

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._baseDelta, 0, 1);
// Solve fixed constraints first.
for (var i = 0; i < constraints.length; i += 1) {
var constraint = constraints[i],
@ -183,7 +185,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),
@ -193,8 +198,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 +223,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 +246,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._baseDelta = 1000 / 60;
Common._nextId = 0;
Common._seed = 0;
Common._nowStartTime = +(new Date());
@ -322,7 +323,8 @@ module.exports = Common;
* - 2 = Info
* - 3 = Warn
* - 4 = Error
* @property Common.logLevel
* @static
* @property logLevel
* @type {Number}
* @default 1
*/

View file

@ -71,26 +71,16 @@ 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.
*
* Moves the simulation forward in time by `delta` milliseconds.
* 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,31 @@ 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);
}
// 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,33 +144,37 @@ var Body = require('../body/Body');
// wake up bodies involved in collisions
if (engine.enableSleeping)
Sleeping.afterCollisions(pairs.list, timing.timeScale);
// trigger collision events
if (pairs.collisionStart.length > 0)
Events.trigger(engine, 'collisionStart', { pairs: pairs.collisionStart });
Sleeping.afterCollisions(pairs.list);
// 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);
}
// update body speed and velocity properties
Engine._bodiesUpdateVelocities(allBodies);
// trigger collision events
if (pairs.collisionStart.length > 0)
Events.trigger(engine, 'collisionStart', { pairs: pairs.collisionStart });
if (pairs.collisionActive.length > 0)
Events.trigger(engine, 'collisionActive', { pairs: pairs.collisionActive });
@ -234,7 +233,9 @@ 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
@ -245,51 +246,65 @@ var Body = require('../body/Body');
};
/**
* Applys a mass dependant force to all given bodies.
* Applies gravitational acceleration to all `bodies`.
* This models a [uniform gravitational field](https://en.wikipedia.org/wiki/Gravity_of_Earth), similar to near the surface of a planet.
*
* @method _bodiesApplyGravity
* @private
* @param {body[]} bodies
* @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++) {
for (var i = 0; i < bodiesLength; i++) {
var body = bodies[i];
if (body.isStatic || body.isSleeping)
continue;
// apply gravity
// add the resultant force of gravity
body.force.y += body.mass * gravity.y * gravityScale;
body.force.x += body.mass * gravity.x * gravityScale;
}
};
/**
* Applys `Body.update` to all given `bodies`.
* Applies `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
* @param {number} delta The amount of time elapsed between updates
*/
Engine._bodiesUpdate = function(bodies, deltaTime, timeScale, correction, worldBounds) {
for (var i = 0; i < bodies.length; i++) {
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, deltaTime, timeScale, correction);
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]);
}
};
@ -306,6 +321,7 @@ var Body = require('../body/Body');
* @event beforeUpdate
* @param {object} event An event object
* @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 {engine} event.source The source object of the event
* @param {string} event.name The name of the event
*/
@ -316,6 +332,7 @@ var Body = require('../body/Body');
* @event afterUpdate
* @param {object} event An event object
* @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 {engine} event.source The source object of the event
* @param {string} event.name The name of the event
*/
@ -327,6 +344,7 @@ var Body = require('../body/Body');
* @param {object} event An event object
* @param {pair[]} event.pairs List of affected pairs
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {number} event.delta The delta time in milliseconds value used in the update
* @param {engine} event.source The source object of the event
* @param {string} event.name The name of the event
*/
@ -338,6 +356,7 @@ var Body = require('../body/Body');
* @param {object} event An event object
* @param {pair[]} event.pairs List of affected pairs
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {number} event.delta The delta time in milliseconds value used in the update
* @param {engine} event.source The source object of the event
* @param {string} event.name The name of the event
*/
@ -349,6 +368,7 @@ var Body = require('../body/Body');
* @param {object} event An event object
* @param {pair[]} event.pairs List of affected pairs
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {number} event.delta The delta time in milliseconds value used in the update
* @param {engine} event.source The source object of the event
* @param {string} event.name The name of the event
*/
@ -417,7 +437,7 @@ 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.
*
*
* @property timing.timestamp
* @type number
* @default 0
@ -428,7 +448,7 @@ var Body = require('../body/Body');
* It is updated by timing from the start of the last `Engine.update` call until it ends.
*
* This value will also include the total execution time of all event handlers directly or indirectly triggered by the engine update.
*
*
* @property timing.lastElapsed
* @type number
* @default 0
@ -436,7 +456,7 @@ var Body = require('../body/Body');
/**
* A `Number` that represents the `delta` value used in the last engine update.
*
*
* @property timing.lastDelta
* @type number
* @default 0
@ -484,22 +504,29 @@ var Body = require('../body/Body');
*/
/**
* The gravity to apply on all bodies in `engine.world`.
* An optional gravitational acceleration applied to all bodies in `engine.world` on every update.
*
* This models a [uniform gravitational field](https://en.wikipedia.org/wiki/Gravity_of_Earth), similar to near the surface of a planet. For gravity in other contexts, disable this and apply forces as needed.
*
* To disable set the `scale` component to `0`.
*
* This is split into three components for ease of use:
* a normalised direction (`x` and `y`) and magnitude (`scale`).
*
* @property gravity
* @type object
*/
/**
* The gravity x component.
*
* The gravitational direction normal `x` component, to be multiplied by `gravity.scale`.
*
* @property gravity.x
* @type object
* @default 0
*/
/**
* The gravity y component.
* The gravitational direction normal `y` component, to be multiplied by `gravity.scale`.
*
* @property gravity.y
* @type object
@ -507,8 +534,8 @@ var Body = require('../body/Body');
*/
/**
* The gravity scale factor.
*
* The magnitude of the gravitational acceleration.
*
* @property gravity.scale
* @type object
* @default 0.001

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
@ -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,9 @@ 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);

View file

@ -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);
}
}

View file

@ -10,6 +10,7 @@ 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');
@ -1106,8 +1107,10 @@ var Mouse = require('../core/Mouse');
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;