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!)

7 Responses to “Javascript Beziers”

  1. Languages of the real and artificial ยท Visualizing Regular E Says:

    [...] Update: Some of the support libraries are now available as open source. See JSON for OpenLaszlo, JavaScript Beziers, and Canvas with Text. [...]

  2. Max Carlson Says:

    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

  3. Max Carlson Says:

    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();

  4. Jason Jobe Says:

    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

  5. Oliver Says:

    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.

  6. jbj Says:

    I’m really impressed with that! Thanks for sharing.

  7. learn to draw animation, without all that moving stuff Says:

    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