Declarative Physics with PhysicsJS and AngularJS

Wednesday, September 17, 2014

PhysicsJS + AngularJS One of the reasons for Angular’s success is the flexibility in the building blocks the framework provides. There is a delicate balance between productivity and flexibility. A heavily opinionated framework can be make a team highly productive, but the opinions can also make it harder to mix in additional features that conflict with the opinions, or sit outside the vision for the framework. Angular strikes a nice balance.

When I first thought about mixing PhysicsJS into an AngularJS application, I couldn’t see how a library based on timer ticks, frame rates, and a canvas could work with a framework like Angular, with its digest cycle and directives.

But, after 30 minutes I discovered that not only could these two work together, but Angular was flexible enough to provide some nice enhancements to the PhysicsJS experience.

Go to PhysicsJS + AngularJS to see the following code in action. The code is based on the tutorial “Creating a scene of colliding polygons”. 

Services

The first order of business is wrapping PhysicsJS components with services. Physics is the primary export of PhysicsJS, and this object provides factory methods to create shapes and behaviors. We can inject Physics into other components of the application that need to create shapes, but we’ll also create some additional services that use Physics to setup the “game loop” ticker and the world that contains all the shapes and behaviors for a scene.

var app = angular.module("app", []);

app.value("Physics", Physics);

app.factory("world", function (Physics) {
    var world = Physics();
    world.on("step", function () {
        world.render();
    });
    return world;
});

app.factory("ticker", function (Physics, world) {
    var start = function () {
        Physics.util.ticker.on(function (time) {
            world.step(time);
        });
        Physics.util.ticker.start()
    };

    return {
        start: start
    };
});

Directives

Directives can transform a PhysicsJS scene from imperative code and into a declarative language.  Here are a few simple directive definitions.

app.directive("physicsBehavior", function (Physics, world) {
    return {
        restrict: "E",
        scope: {
            name: "@"
        },
        link: function (scope) {
            world.add(Physics.behavior(scope.name));
        }
    };
});

app.directive("physicsBody", function (Physics, world) {
    return {
        restrict: "E",
        scope: {
            options: "=",
            body: "=",
            type: "@"
        },
        link: function (scope) {
            scope.body = Physics.body(scope.type, scope.options);
            world.add(scope.body);
        }
    };
});

These types of directives will let us write markup like the following.

<physics-canvas width="500" height="500">

    <physics-body type="rectangle" body="main.box1"
                  options="{x:450, y:10, vy:-0.3, width:5, height:5}">
    </physics-body>

    <physics-body type="rectangle" body="main.box2"
                  options="{x:250, y:210, vy:-0.3, width:5, height:5}">
    </physics-body>

    <physics-edge-detection min-x="0" min-y="0" max-x="500" 
                            max-y="500" restitution="0.3">
    </physics-edge-detection>

    <physics-behavior name="constant-acceleration"></physics-behavior>
    <physics-behavior name="body-impulse-response"></physics-behavior>
    <physics-behavior name="body-collision-detection"></physics-behavior>
    <physics-behavior name="sweep-prune"></physics-behavior>

</physics-canvas>

Controllers

Controllers can build models that interact with objects in the PhysicsJS world. The API allows us to apply forces and change the geometry of objects. One difficulty I did have is seeing the updated appearance of a shape after changing attributes. The code needs to set the view property of a shape to null for changes to attributes like width and height to appear. PhysicsJS will recreate the view on the next tick. 

model.kick = function () {
    model.box1.applyForce({x: 0.1, y: -0.2});
    model.box2.applyForce({x: -0.1, y: -0.2});
};

model.grow = function () {
    model.box1.geometry.width *= 1.5;
    model.box1.geometry.height *= 1.5;
    model.box1.mass *= 1.5;
    model.box1.view = null;
    model.box1.recalc();
};

But What About The Digest?

Anyone who has worked with jQuery widgets or native events knows that Angular needs to know when model data changes in order to update the DOM with new model values. Generally we accomplish this goal by wrapping code inside a scope’s $apply method.

The interesting thing about working with PhysicsJS is that the physics engine is constantly updating a canvas based on a timer, so it is not necessary for Angular to know about changes in shape properties, like position and acceleration. It just works.

Summary

Although there would be a lot more work needed to make a complete and general purpose wrapper for PhysicsJS, the integration with an Angular app works in a straightforward manner. 


Comments
gravatar Thomas Burleson Tuesday, September 23, 2014
I recommend that you delete your read-once physics model after you initialize your Physic world. This ensures your HTML is clean an uncluttered. @see http://plnkr.co/edit/zxnywA2ly6Dj79MYgnbJ?p=preview ```js var model = angular.element(element[0].querySelector('#physicsModel')); model.empty(); ```
gravatar Jose Manuel Jimenez Friday, September 26, 2014
Thank you for sharing this code. I have enjoyed a lot studying it. The only thing I miss is that you can't have more than one canvas in the same page. As far as I can see, it's because all object are using the same world from the factory. As I'm currently training a lot with AngularJS, I hope you don't mind I have forked your job at plunker to modify it a little (you can see it at http://plnkr.co/edit/7PrxVp?p=info). I don't know PhysicsJS at all, but I can guess you have one global object Physics and you can have several independent world instances. So, I have modified your physics-canvas directive to create its own private world and ticker. And I have modified the rest of the directives to use their parent's world instead of using the world from the factory (as a matter of fact, I have removed the world and the ticker factories). I hope you like it, any feedback would be appreciated. Thanks again
gravatar Scott Friday, September 26, 2014
@Jose: This looks great! Excellent insight and implementation.
Comments are closed.

My Pluralsight Courses

K.Scott Allen OdeToCode by K. Scott Allen
What JavaScript Developers Should Know About ECMAScript 2015
The Podcast!