views:

477

answers:

1

Hi,

I'm trying to define a path of points. Each point has an x, y, and time. I then want to query this path and get the current position at that point in time. Let me share some pseudo code.

point {x, y, time}


function initialisePath(point[] path) {
    ... // Create Bezier Path
}

function getXYAtTime(time) {
    return ... // Get interpolated point along the bezier path at the specified time
}

I'm trying to implement this in javascript using the canvas tag. However a sample in any language will do. Does anyone know any open source library (in any language) that creates this kind of queriable path??

Note: I've been trying to get my head around this sample and code from the DynApi project but moving from this sample to a time aware path is a stretch for my poor animational skills.

Thanks

Guido

A: 

A Bézier curve has not only a start and end point but also control points that guide the shape of the curve. In the DynApi demo you linked, the end points are marked in yellow, and the control points are marked in red.

Your path will be a sequence of Bézier curves, connected end-to-end.

So let's take your pseudocode, but we'll treat all points that do not have a .time property to be control points.

function Path(points) {
    this.points = points;

    // Sanity check.
    if (points[0].time == undefined || points[points.length - 1].time == undefined)
        throw new Error("all control points must be between two real points");
}

Path.prototype.getXYAtTime = function (t) {
    var points = this.points;

    // First, see if t is out of range.
    if (t < points[0].time)
        return points[0];
    if (t > points[points.length - 1].time)
        return points[points.length - 1];

    // OK, t is in range. Find out which Bezier curve we're in.
    //
    // Specifically we want 'start' and 'stop' to be the indexes of two points
    // that each have a .time property, bracketing the current time t; and
    // all the points in between 'start' and 'stop' should be control points.
    //
    var start = 0, stop = points.length - 1;
    for (var i = 1; i < points.length; i++) {
        var p = points[i];
        if (t < p.time) {
            stop = i;
            break;
        }
        if (p.time != undefined)
            start = i;
    }
    var n = stop - start;

    // Adjust t to be in the range [0, 1).
    var t0 = points[start].time, t1 = points[stop].time;
    t = (t - t0) / (t1 - t0);
    var tInv = 1 - t;

    // Now calculate the current position in the curve.
    // Wikipedia says this is:
    //   sum for i = 0 to n of (n C i * (1 - t) ^ (n - i) * t ^ i * P[i])
    // 
    var x = 0, y = 0;
    for (var i = 0; i <= n; i++) {
        var p = points[start + i];
        var c = nCr(n, i) * Math.pow(1 - t, n - i) * Math.pow(t, i);
        x += c * p.x;
        y += c * p.y;
    }
    return {x: x, y: y};
}

// The number of k-combinations of a set of size n.
function nCr(n, k) {
    var z = 1;
    for (var i = 1; i <= k; i++)
        z *= (n + 1 - i) / i;
    return z;
}

So that's the math part done. It's up to you to hook it up to canvas and make it go.

Here's how you call that method:

// Here's a Path consisting of a single Bezier curve.
var path = new Path([
    {x: 200, y: 150, time: 0},  // start point
    {x: 200, y: 500},           // 2 control points
    {x: 250, y: 100},
    {x: 500, y: 300, time: 50}  // end point
  ]);

var p = path.getXYAtTime(2.718);
alert(p.x + ", " + p.y);
Jason Orendorff
Wow, thanks for the very descriptive answer. I now feel foolish in adding that the path (collection of points) I have does not contain any control points. I should really have mentioned that I just want to smooth this path. So instead of a jagged join the dots animation I want to produce a smooth animation that passes through these points.I have been playing around with the concept of 'guessing' these control points given the previous point and the next point (of a 2 point path segment), however this is not getting me too far.Tnx again for a greatly described response.
gatapia
Oh! I feel foolish now for not understanding what you wanted. That sounds like a reasonable approach, but it might be hard to guess those control points. You need to figure out what the constraints are. For example, a given point and the control points just before and just after need to line up. Otherwise you'll get a sudden change in direction at the point.
Jason Orendorff
Something I did once in this situation is (a) pick the velocity I want at each point, based on the surrounding points; (b) given two points, and the desired velocity at each point, calculate the unique cubic function that fits the bill. In your case you have an extra constraint, though: a fixed amount of time.
Jason Orendorff
Hi Jason,I don't fully understand your velocity suggestion. With my current setup I can definately calculate the avg velocity between two points (as each point has x, y and time). So I can assume the velocity at each point is this avg velocity. However I dont understand how to from this velocity then calculate a quadratic function to meet these points.Hang on 1 sec, by velocity you mean the vector velocity (including direction)? This sounds interesting but again its beyond my abilities (I am however great at yoyo), do you know of any open source project doing this type of thing?
gatapia
Well -- yeah, I did mean vector velocity, but that's not a big problem. You don't have to actually do any vector math at all. You can calculate the x coordinate completely independently of the y coordinate.
Jason Orendorff