Javascript Beziers
The OpenLaszlo application below demonstrates animation along a line, a quadratic Bezier, and a cubic Bezier (the top three paths). It also demonstrates (the bottom path) animation along a path composed of multiple segments.
Drag the slider back and forth to display the point on each path at t=slider.value/100, or click the “Animate” button to animate t from 0 to 1.
I wrote this in order to animate the state markers along the edges of the graph in reAnimator. The GraphViz dot tool, which I’m using for graph layout, generates cubic beziers, so I had to write code to render and evaluate them.
If you’re writing for FireFox a Browser that supports the canvas element, you can use the same code with the new HTML canvas element. Click on “Start Animation” to animate the points on the canvas below. And click here to open the HTML applet in its own window.
Files:
- bezier.js — measurement, interpolation, sampling, and subdivision for arbitrary-order Beziers
- path.js — measurement, interpolation, and sampling for paths composed of multiple lines and Beziers
- bezier-demo.js — the code to draw the paths in the demo. This is platform-agnostic, and works in OpenLaszlo (bezier-demo.lzx) and HTML (bezier-demo.html).
- bezier-demo.lzx — the OpenLaszlo demo
- bezier-demo.html — the HTML demo
- drawview-patches.js — patches to the OpenLaszlo LzDrawView. This includes an implementation of LzDrawView.bezierCurveTo.
A couple of caveats:
- This animates along the Bezier parameterization, not the path length. This was good enough for my application, but you could get animation that speeds up and slows down, depending on how you choose your control points.
The demo code is written for brevity, not speed. In particular, it re-renders the background each time, which involves some expensive math to render the cubic. In reAnimator, I placed the animated elements on a separate view in front of the background. I didn’t do that here because I wanted to share code between the OpenLaszlo and HTML demos, and the techniques for doing overlays were too different.Using a separate overlay for the background didn’t actually speed this up. The hot spot is the parametric position calculation.
1 I think the reason the code doesn’t work in Safari is that Bezier.draw uses the Function.apply method to apply methods on the graphics context, such as quadraticCurveTo, to argument lists. It looks like Safari doesn’t implement apply when the function is a native method. I didn’t try to work around this because in cases where I’ve actually tried to use canvas (such as the “Parse” and “Graph” tab in reWork), there were other problems with Safari anyway.
Update: The inline examples weren’t showing up. Thanks to Bret Victor for both finding and diagnosing the problem. (I was linking to my laptop, osteele.dev. The post looked fine from my laptop!)
March 2nd, 2006 at 8:58 pm
[...] Update: Some of the support libraries are now available as open source. See JSON for OpenLaszlo, JavaScript Beziers, and Canvas with Text. [...]
March 10th, 2006 at 10:15 pm
Oliver, this is fabulous stuff! I’m integrating it into the platform now. I borrowed a testcase from http://developer.mozilla.org/samples/canvas-tutorial/2_6_canvas_beziercurveto.html
this.beginPath();
this.moveTo(75,40);
this.bezierCurveTo(75,37,70,25,50,25);
this.bezierCurveTo(20,25,20,62.5,20,62.5);
this.bezierCurveTo(20,80,40,102,75,120);
this.bezierCurveTo(110,102,130,80,130,62.5);
this.bezierCurveTo(130,62.5,130,25,100,25);
this.bezierCurveTo(85,25,75,37,75,40);
this.fill();
Two of the curves are drawn as lines because the intersection method thinks they’re collinear. Any idea why? Thanks!
Regards,
Max Carlson
OpenLaszlo.org
March 10th, 2006 at 10:18 pm
Drat - wordpress (quite reasonably) stripped the LZX out of my example - I’ll try again:
this.beginPath();
this.moveTo(75,40);
this.bezierCurveTo(75,37,70,25,50,25);
this.bezierCurveTo(20,25,20,62.5,20,62.5);
this.bezierCurveTo(20,80,40,102,75,120);
this.bezierCurveTo(110,102,130,80,130,62.5);
this.bezierCurveTo(130,62.5,130,25,100,25);
this.bezierCurveTo(85,25,75,37,75,40);
this.fill();
March 13th, 2006 at 1:56 pm
Wow, this is great. I did something similar with polygons (no curves). Performance, however, is an issue. I’m running a continuous animation with around 50 points.
Is this just going to be slow or is there something I can do to speed it up?
Thanks,
Jason
March 21st, 2006 at 8:44 pm
I just sped bezier.atT() up by 20%. It’s now around 35ms on my MacBook (in power saving mode), in Firefox 1.5 and Safari 2.0.1. It was 45ms in Safari and 55ms in Firefox.
(Yahoo has an implementation to0, but it’s around 150/300ms, or 110ms/460ms if you leave in the calls to parseInt.)
If you’re calling bezier.atT() directly, the next optimization would be to inline that so that you can evaluated multiple points at once. That will save one function call per point.
If you’re using the path interface, you could stub out the call to bezier.atT() to see if searching for the relevant segment is appreciable. That could use a binary search.
March 18th, 2008 at 9:07 am
I’m really impressed with that! Thanks for sharing.
June 15th, 2008 at 9:06 pm
This was quite refreshing after spending a dull sunday afternoon watching nonsense on youtube. I am trying to get my programming chops up to snuff, but i am terrible