-
Notifications
You must be signed in to change notification settings - Fork 407
Fundamentals
In a general sense, PhysicsJS isn't very complicated. This is by design. PhysicsJS is meant to be modular, and easy to use and hence, understand. There is complicated math going on at a deeper level, but on a surface level, you should be able to understand mostly what PhysicsJS is all about by reading this page.
The physics simulation is a glorified animation loop. We attach our simulation to something like requestAnimationFrame, and whenever we need to draw a new frame we call the step method of the simulation to calculate the new positions of our physical objects, and use these new positions to update the screen. In PhysicsJS this corresponds to calling the world.step(time)
method, and the world.render()
which will tell your renderer to update the screen.
Stepping is broken up into smaller iterations to keep the numbers more accurate. Within every world.step
there will usually be a few iterations of the physics simulation before rendering is done. This depends on the size of the timestep
which is the time between iterations. However, don't worry, world.step
can be called at arbitrary times so you don't need to worry about calling step at multiples of timestep
or anything like that.
Let's go a bit deeper.
The world
in PhysicsJS is created by calling Physics()
as a function, (as you have read in the Getting Started docs). The world
is what controls the flow of the simulation, and keeps track of all the parts of the simulation.
Physical objects are represented by PhysicsJS Bodies. (We call them Bodies to avoid confusion with javascript "objects"). The world will use an Integrator to update the positions and velocities of its bodies on every iteration. So far, this will result in the physics of non-interacting objects in empty space. Pretty boring. Things get more interesting because of Behaviors. Behaviors will subscribe to integration events and modify bodies' accelerations, and sometimes velocities, and positions according to rules that will result in interesting behavior like collisions, gravity, constraints, etc.
Let's recap. So far the flow of a PhysicsJS simulation goes something like this:
while( animating )
time = the current time
call world.step( time )
world.step( time ):
while( currentTime < time )
currentTime += timestep
Integrator.integrate( bodies in the world )
emitEvent( 'step' )
Integrator.integrate( bodies )
integrateVelocities( bodies )
// some behaviors listen for this event and update body properties accordingly
emitEvent( 'integrate:velocities' )
integratePositions( bodies )
// MOST behaviors listen for this event and update body properties accordingly
emitEvent( 'integrate:positions' )
// optionally (but usually) we can listen for the "step" event and call world.render()
on event 'step' do
world.render()
Positive direction of:
- x: right
- y: down
- angle: clockwise
Like most physics engines, PhysicsJS comes baked with its own vector class. Vectors can be created by simply calling Physics.vector( values );
Vectors can be created from number arguments, literal objects, or other vectors.
var v = Physics.vector( 2, 3 );
// same as...
var v = Physics.vector({ x: 2, y: 3 });
// same as...
var other = Physics.vector( 2, 3 );
var v = Physics.vector( other );
Values can be read and modified by using the .get()
and .set()
methods respectively.
v.set( 2, 0 );
var x = v.get(0); // x coord
x = v.x; // same thing but slightly slower
// x === 2
Please see the vector API documentation for a complete list of methods.
Many objects in PhysicsJS (such as bodies, behaviors, ...) follow the factory pattern in terms of creation. This involves calling an appropriate factory function with a configuration object that specifies settings for that object. The general pattern looks like this:
var instance = Physics.FACTORY('NAME', { SETTINGS });
For example, when creating a circle body, we can call the .body()
factory and specify the circle properties (x, y, velocity, radius, ...).
var ball = Physics.body('circle', {
x: 140,
y: 100,
radius: 20,
vx: 0.1
});
Factory functions can also be used to create new subtypes, and extend subtypes. To create a new subtype, a function is passed (in place of the configuration) which should return an object to mix into the base. The function will be called immediately, and passed the parent object (like "super").
To handle initialization, the mixins may include an .init()
method that will be called when the new subtype is created, and passed the configuration options for the new instance.
Physics.FACTORY('NEW_NAME', function( parent ){
// shared resources here...
return {
// optional initialization
init: function( options ){
// call parent init method
parent.init.call(this, options);
// ...
},
// mixin methods...
};
});
As an example, look at the circle body's definition.
Physics.body('circle', function( parent ){
var defaults = {
// default config options
};
return {
init: function( options ){
// call parent init method
parent.init.call(this, options);
options = Physics.util.extend({}, defaults, options);
this.geometry = Physics.geometry('circle', {
radius: options.radius
});
this.recalc();
},
recalc: function(){
parent.recalc.call(this);
// moment of inertia
this.moi = this.mass * this.geometry.radius * this.geometry.radius / 2;
}
};
});
Factory functions can be used to extend other subtypes also. This is done similarly to declaring new types, but the second argument specifies the name of the existing subtype:
Physics.FACTORY('NEW_NAME', 'PARENT_NAME', function( parent ){
// parent is of type 'PARENT_NAME'
return {
// optional initialization
init: function( options ){
// call parent init method
parent.init.call(this, options);
// ...
},
// mixin methods...
};
});
For example, suppose we want to extend the "circle" type into a "wheel" type that has a method called .spin()
that will give the object a quick spin in the clockwise direction. We could do that like this:
Physics.body('wheel', 'circle', function( parent ){
return {
// no need for an init
// spin the wheel at desired speed
spin: function( speed ){
// the wheels are spinning...
this.state.angular.vel = speed;
}
};
});
var myWheel = Physics.body('wheel', {
x: 40,
y: 340,
radius: 60
});
world.add( myWheel );
// for example, use jquery to listen for a button click, and spin the wheel on the next step
$('button').on('click', function(){
// wait for the next step before spinning the wheel
world.subscribe('step', function( data ){
myWheel.spin( 0.3 );
// only execute callback once
world.unsubscribe( 'step', data.handler );
});
});