Fundamentals

Jasper edited this page Apr 22, 2014 · 9 revisions

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()

Coordinates and Directionality

Positive direction of:

  • x: right
  • y: down
  • angle: clockwise

Vectors

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.

The Factory Pattern

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

Creating new subtypes

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

Extending subtypes

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

See this example on jsFiddle.