Wednesday, March 17, 2010

Canvas Wrapper with getTransform()

The Canvas spec does not include a getTransform() method, so after performing rotation/scale/translation operations there is no way to work out where you really are.

So, I have created a wrapper class, which sits over the top of Canvas, and should behave exactly the same, except you now have a getTransform() method which returns a matrix such that the following doesn't do anything:
var m = ctx.getTransform();
ctx.setTransform( m[0][0], m[0][1], m[1][0], m[1][1], m[2][0], m[2][1] );
To use, add the following line:
<script type="text/javascript" src="canvas_wrapper.js"></script>
then wrap your canvas object in a CanvasWrapper, ie:
ctx = new CanvasWrapper(document.getElementById("canvas").getContext("2d"));
It works by duplicating Canvas' matrix operations, and mimicking the Canvas interface. An annoyance is that Canvas has public fields, instead of accessor methods, so I can't tell when it has been changed and have to update the canvas state before any drawing call.

Update 10/2/2013: Thanks to Heikki Pora for a patch adding some missing features.

Update 19/5/2015: The HTML Spec now includes context.currentTransform but it doesn't work for me in Firefox or Chromium, ctx.mozCurrentTransform does work in Firefox.

Tuesday, March 16, 2010

L-System v2

I have modified my previous L-System demo and added a few new features. Click on the image on the left to load the canvas demo.

The axiom, rules and angle are now in editable fields, so you can change them and click [update] to view. This makes it quite quick to play around and explore l-systems.

For the code internals, I separated out the l-system code into its own .js file, and also changed how the turtle works. Instead of a case statement, it now uses a map of functions, which looks like this:
return {
// Turn right
'+': function(args) { args.ctx.rotate(vary( args.angle, args.angle * args.angleVariance)); },
// Turn left
'-': function(args) { args.ctx.rotate(vary(-args.angle, args.angle * args.angleVariance)); },
// Push
'[': function(args) { args.ctx.save(); args.depth++; },
// Pop
']': function(args) { args.ctx.restore(); args.depth--; },
// Draw forward by length computed by recursion depth
'|': function(args) { args.distance /= Math.pow(2.0, args.depth); drawForward() },
'F': drawForward,
'A': drawForward,
'B': drawForward,
'G': goForward
};
This makes it extremely easy to add new turtle commands, for instance to draw a green dot every time you see a 'L' just add the following to the map:
turtleHandler['L'] = function(args) {
args.ctx.fillStyle = '#00ff00';
args.ctx.beginPath();
args.ctx.arc(0, 0, 2, 0, 2 * Math.PI, true);
args.ctx.fill();
}

This was used to draw the leaves in the image above. To get 'L's in the right place in the l-system, I used 'L->' as the first rule, which deletes all existing 'L's, so the only ones that remain at the end of the iterations are the most recently created ones, which are on the (appropriately named) leaf nodes.

TODO: Make extra functions an editable field, make better alignment/zooming.