Saturday, May 28, 2011

Calculating Cubic Bezier Function

If you ever played with the CSS3 native css-transitions, you know that they use cubic-bezier notation to define the transition formula. Cubic Bezier is a parametric function that allows you to define all sorts of smooth curvatures that looks kinda like that



The reason why it's used instead of sinuses and exponents, is that it allows you to define all sorts of shapes with just a few more or less intuitively understandable parameters. Plus, unlike other mathematical functions it can be computed very fast, because it's a simple parametric function, no sequences no nothing.

In RightJS we support native css3 transitions, but it also has a pure JavaScript fx-engine to handle old browsers. And the thing is that native CSS3 transitions use the cubic-bezier functions, while the pure JavaScript engine uses a bit of mumbo-jumbo with Math.sin() and Math.log(), and what I wanted is to allow the developers to use cubic-bezier notation with the old engine.

The problem is that it's not that simple. The cubic-bezier function itself is pretty much straightforward and you can calculate a set of X and Y coordinates in no time, but it won't make you any good because both x-s and y-s will be related to the third t parameter and therefore unevenly distributed agains the t parameter. What you really need is a straight Y = B(x) function.

And here it gets tricky. That much tricky. But, luckily for us, there is always a better way to do it. All you need is to recall a bit of kindergarden math.

Originally, the cubic bezier function is used in a parametric form, but it also can be converted into a polynomial form, in which case it will look like that (you can google it and find say this document)

a = 3 P0
b = 3 P1
c = 3 P2
c0 = P0
c1 = b - a
c2 = a - 2b + c
c3 = P3 - P0 + b - c

B(t) = c0 + t(c1 + t(c2 + tc3))

In case of CSS3 cubic-bezier definitions, P0 is always 0 and P3 is always 1, in which case the formula gets even simpler

c3 = 3 * P1
c2 = 3 * (P2 - P1) - c3
c1 = 1 - c3 - c2

B(t) = t * (c1 + t * (c2 + t * c3))

Which, in case of of CSS3 x1,y1,x2,y2 parameters, can be written in javascript with two functions, for example this way

var Cx = 3 * x1;
var Bx = 3 * (x2 - x1) - Cx;
var Ax = 1 - Cx - Bx;

var Cy = 3 * y1;
var By = 3 * (y2 - y1) - Cy;
var Ay = 1 - Cy - By;

function bezier_x(t) {
return t * (Cx + t * (Bx + t * Ax));
}

function bezier_y(t) {
return t * (Cy + t * (By + t * Ay));
}

There are two reasons why we use the polynomial form of the equation. Firstly, it has less operations and therefore will work faster. Secondly, we are going to use Newton's method to approximate the values and will need a first derivative of one of the functions, which in case of polynomial functions is pretty simple.

Now, to the fun part. We converted the function from one form to another, but the question remains the same, how are we going to get the y = B(x) from x = B(t) and y = B(t)? Quite simple my friends, we will use the kindergarden math, or more exactly the Newton's method to approximate a parametric x value for every t value so that we could get an evenly distributed set of x values and then get a set of y for them using the same bezier_y(t) function.

Newton's method might look a bit scary for those who are not familiar with it, but it is actually very simple. Check this video the guy explains it nicely.

All you need to make it work is to have a derivative to your function, which in our case will look like that

function bezier_x_der(x) {
return Cx + t * (2*Bx + t * 3*Ax);
}

And after that, we just define a little function that will make several iterations trying to find an x value which came from the current t value. We don't have to be very precise in our case, we just make 5 iterations tops and limit the precision by the 3 digits after the pointer.

function find_x_for(t) {
var x = t, i = 0, z;

while (i < 5) {
z = bezier_x(x) - t;
if (Math.abs(z) < 1e-3) break;

x = x - z / bezier_x_der(x);
i++;
}

return x;
}

Now you can create a loop and calculate a set of evenly distributed values, just like that:

var result = [];
for (var i=0; i < 1.001; i+=0.1) {
result.push(bezier_y(find_x_for(i)));
}
console.log(result);



That's basically all of it. You can find the full version of this script in this gist.

Enjoy!

Sunday, May 8, 2011

How To Read User Input With NodeJS

I think it's time for me to join the NodeJS hysteria. So, I'd like to share a bit of things I was tackling today because there is not really much of docs on that matter right now.

Short things short. If you're writing a little console script in NodeJS that supposed to ask the user this and that, here's how you can get it done.

Node's global object process has two properties called .stdin and .stdout, which are essentially streams. You can write things into the stdout and listen to the 'data' event in the stdin stream. A simple example from the api-docs looks like that

process.stdin.resume();
process.stdin.setEncoding('utf8');

process.stdin.on('data', function (chunk) {
process.stdout.write('data: ' + chunk);
});

You need to call that .resume() method first, it initializes the STDIN reading process.

It is pretty straightforward, but it's almost useless when you need to ask the user several questions, more of that validate the input data and re-ask questions when something was entered wrong.

The basic trouble here is that .on('data' handler that will fire every time the user hits the Enter, so you'll have to figure out which question is currently asked an where to put the data. Given the fact that everything is happening asynchronously, it pretty quickly gets really messy.

Fortunately for us, there is another events handler in NodeJS called .once(.. it's basically same as .on( but it detaches the listener after the first event received. Knowing that, we can write a little helper function like that

function ask(question, format, callback) {
var stdin = process.stdin, stdout = process.stdout;

stdin.resume();
stdout.write(question + ": ");

stdin.once('data', function(data) {
data = data.toString().trim();

if (format.test(data)) {
callback(data);
} else {
stdout.write("It should match: "+ format +"\n");
ask(question, format, callback);
}
});
}

There are two important moments. First of all, don't use console.log to ask the user the question, otherwise it will print a new line at the end, which is kinda sucks. Secondly note that you should call .toString() and .trim() on your data, the first one to convert the data from a stream into an actual string, the second one is needed because the string will have a trailing new line symbol at the end after the user hits the Enter key.

After that, you can ask the user all sorts of questions. For example like that

ask("Name", /.+/, function(name) {
ask("Email", /^.+@.+$/, function(email) {
console.log("Your name is: ", name);
console.log("Your email is:", email);

process.exit();
});
});

And don't forget to call the process.exit() when you done, otherwise the script will just hung in there.

That's basically all of it. Enjoy!