3D CSS boxes and Physics with Cannon.js

4th Jun 2013

3D CSS boxes and Physics with Cannon.js

I've been doing a fair amount of creative and 3d programming recently. One major drawback of using something like WebGL at the moment is that it isn't natively supported on many mobile devices and falling back to something like the Three.js canvas 2d renderer is very slow.

So I've been playing around with using CSS 3D transforms to see what you can achieve. Here's an example of a simple scene showing boxes being dropped on to a plane (Click the window to drop them).

<iframe src="https://davetayls.me/teststation/css3/boxes/physics.html" width="100%" height="500"> <a href="https://davetayls.me/teststation/css3/boxes/physics.html">View it</a> </iframe>

I was able to use Cannon.js for the physics engine. Unfortunately the phone couldn't cope with the physics as well so here is a simple version which runs well on a phone

Building

David Desandro has done a really good intro to 3D transforms if you haven't taken a look yet.

The Box / Cube

I managed to simplify the markup down to a container and a couple of inner elements.

<div class="box"> <i class="front"></i> <i class="back"></i> </div>

The main box has a fixed height and starts in the middle of the page so that translate3d(0,0,0) is the center of the page.

.box { display: block; width: 14px; height: 14px; position: absolute; top: 50%; left: 50%; margin-left: -7px; margin-top: -7px; transform-style: preserve-3d; }

I created the extra sides of the cube from the :before and :after selectors instead of using more elements. You can do this as long as you don't need to apply CSS transitions to them which we don't in this case.

.box i:before, .box i:after, .box i { display: block; background: #999; width: 14px; height: 14px; position: absolute; top: 0; left: 0; transform-style: preserve-3d; } .box i:after, .box i:before { content: ""; }

It then rotates and translates the individual faces to form the sides of the cube.

.box .front { transform: rotateX(90deg) translateZ(7px); } .box .front:before { transform: translateZ(-14px); } .box .front:after { transform: rotateY(90deg) translateZ(7px) translateX(7px); } .box i.back { transform: rotateY(0deg) translateZ(7px); background: #aaa; } .box i.back:before { transform: translateZ(-14px); background: #ccc; } .box i.back:after { transform: rotateY(90deg) translateZ(-7px) translateX(7px); background: #bbb; }

We now have a reusable box to add to our scene.

Applying some physics

The next task is to apply the Cannon.js physics engine to our boxes. It has an API reminiscent of Three.js as it has been inspired by it. All the examples on the homepage are using Three.js but it can be used independently.

First we set up our world and specify how much gravity it has.

var world = new CANNON.World(); world.gravity.set(0,0,-90.82); world.broadphase = new CANNON.NaiveBroadphase();

We then can place a plane to act as the ground and cause the boxes to bounce and eventually come to rest on it.

var groundShape = new CANNON.Plane(); var groundMat = new CANNON.Material(); var groundBody = new CANNON.RigidBody(0,groundShape, groundMat); world.add(groundBody);

Then during the creation of the a box we define a new shape and describe how it's material will react when it comes in to contact with the ground material.

var shape = new CANNON.Box(new CANNON.Vec3(10,10,6)); var mat = new CANNON.Material(); this.body = new CANNON.RigidBody(0.1, shape, mat); this.body.linearDamping = 0.1; world.add(this.body);

Defining a CANNON.ContactMaterial sets up the relationship where we pass in the two materials, then how much friction the surface has and finally the restitution which affects how bouncy it is.

var mat_ground = new CANNON.ContactMaterial(groundMat, mat, 0.5, 0.2); world.addContactMaterial(mat_ground);

Frames

Finally in the animation loop we tell the world to calculate positions at it's next step.

world.step(1.0/60.0);

... we update the positions of all the boxes and update the relevant CSS properties on the box.

Box.prototype = { render: function(){ var pos = 'translate3d('+ [ this.body.position.x, this.body.position.z*-1, this.body.position.y ].join('px,') +'px)', rot = 'rotate3d('+ [ this.body.quaternion.x, this.body.quaternion.y, this.body.quaternion.z ].join(',') +', '+ this.body.quaternion.w +'rad)' ; this.el.style[Modernizr.prefixed('transform')] = pos +' '+ rot; } };

Notice that in the CSS coordinate system the Z axis goes from font to back whereas CANNON seems to expect the Z axis to go up to down. This means that I use the body's Z position (reversed) in the CSS Y axis.

That's about it

Take a look at the code if you want to see the specifics. This is the first time I've used Cannon.js so I welcome any corrections.