I thought i'd put up a quick sample of my latest play-thing - a 3D rendering of a rubik's cube, using javascript (GWT) to implement the z-buffer and the html5 canvas as the display. I originally built this in plain old javascript, but have now converted it to GWT - partly just for fun and partly to make the code a bit more manageable and refactorable (if that's even a word). Along the way I made a few optimisations like caching radian, sin and cosine values of angles.

The cube is made from 27 "blocks", each of which has a face in each of the rubik-cube face colours. When the 27 blocks are put together we get the completed cube. Admittedly the 27th block isn't really necessary (its right in the middle where it is never visible), but it made life just that little bit simpler to ignore that and go with it.

The cubes you can see below are rendered when the page loads. The scenes are pretty simple, so render very quickly. You may have seen the cube configuration instead of the cubes for a short while until the javascript loaded, at which point the configuration is replaced by the rendered cubes.

Configuring a cube to display is done progressive enhancement style - simply by adding a <div> element like this:

<div class="rubiks" style="width:150px; height:150px;">
  {"rubik-cube":{}, "camera":{}}}
</div>

This sets up the default position and orientation for both camera and cube, which looks like this:

{"rubik-cube":{}, "camera":{}}

That's a pretty boring view, so lets rotate the cube so that we can see more than just the red-face nearest us. To do that we supply some rotations around the x and y axis like this:

<div class="rubiks" style="width:150px; height:150px;">
  { 
    "rubik-cube":{ 
      "rotate-x":35, 
      "rotate-y":35, 
      "rotate-z":35 
    }, 
    "camera":{}
  }
</div>

This results in the following cube:

{ "rubik-cube": { "rotate-x":35, "rotate-y":35, "rotate-z":35 }, "camera":{} }

I'm quite pleased with the results so far, and the performance is not bad either it seems. The speed of actually drawing on the canvas seems to be plenty good enough to get away with a bit of animation. On my (Core i7) laptop I can spin the cube on all 3 axis at a couple-hundred frames per second (hopefully i'll put up an example in a later post).

Next steps for the rubik's application (yeah, like i'll get around to it) would be:

  • Supply "twist" information in the configuration so I can show cubes in various stages of completion.
  • Animate the twists, allowing a viewer to step through the twists, for example to see how to solve a particular part of a cube.
  • Interactive mode where the viewer can play with the cube.
  • In a separate direction, the z-buffer i've built up in order to render the cubes could be improved in lots of nice ways too.

Just before I finish off with a few more views of the cube, I wanted to mention that I had a few issues finding a way to host the scripts and use them in Blogger. Annoyingly you can't host javascript or html files directly from Blogger, so I had to find somewhere else to put them.

My usual approach for hosting source code is to use github "gists", which provides a neat way of showing prettified source, but also lets you view the "raw" source. That allowed me to host the js somewhere, but I had to work around a cross-site scripting issue loading the GWT sources from a different domain.

Fortunately GWT makes that easy with its "xs" linker, which produces a bunch of ".no-cache.js" files instead of "no-cache.html" files, and solves the XSS problem. To use the xs linker you just add a single line to your .gwt.xml file:

<add-linker name="xs" />

Sadly that prevents hosted mode from working (currently), but that's easy enough to work around :)

{ "rubik-cube": { "rotate-x":35, "rotate-y":35 }, "camera":{} }
{ "rubik-cube": { "rotate-x":45, "rotate-y":135 }, "camera":{} }
{ "rubik-cube": { "rotate-x":45, "rotate-z":135 }, "camera":{} }
{ "rubik-cube": { "rotate-x":45, "rotate-y":215 }, "camera":{} }
{ "rubik-cube": { "rotate-x":135, "rotate-y":35 }, "camera":{} }
{ "rubik-cube": { "rotate-x":95, "rotate-y":125 }, "camera":{} }
{ "rubik-cube": { "rotate-x":15, "rotate-y":15, "rotate-z":15 }, "camera":{} }
{ "rubik-cube": { "rotate-x":25, "rotate-y":-25 }, "camera":{} }


blog comments powered by Disqus