HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

De Casteljau's Algorithm Tool for Khan Academy Contest

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
khanacademyalgorithmcontestforcasteljautool

Problem

So I wrote this code for a contest going on over at Khan Academy known as "Pixar in a Program". The goal of the contest is to create an entry that uses one of the skills shown in the new "Pixar in a Box" course made by them in their partnership with Disney Pixar. In my entry I used De Casteljau's algorithm to make a tool that allowed for easy editing to find the touching point of a parabola. My entry can be found here: https://www.khanacademy.org/computer-programming/de-casteljaus-algorithm-made-easy-wip/5879067530887168

It is written in their live editor over at Khan Academy using the Processing port to JavaScript known as Processing.JS. Here's the code:

```
var a = [-150, 50], b = [0, -50], c = [150, 50];
var ar = [], br = [], cr = []; // The "r" in these variable names means rounded, this is for grid snapping.
var ad = [], bd = [], cd = [], qd = [], rd = [], pd = []; // The "d" in these variable names means data, this is for data ouput to match the Cartesian coordinate plane snaps.
var t = 0.25;
var q = [(1 - t) a[0] + t b[0], (1 - t) a[1] + t b[1]];
var r = [(1 - t) b[0] + t c[0], (1 - t) b[1] + t c[1]];
var p = [(1 - t) q[0] + t r[0], (1 - t) q[1] + t r[1]];
var qMenu = false;
var qMenuHover = false;
var rMenu = false;
var rMenuHover = false;
var pMenu = false;
var pMenuHover = false;
var settingsMenu = false;
var settingsMenuHover = false;
var mfp = false; // Stands for "Make Full Parabola"
var totalPoints = 3;
var selected = false;

var x = function(i) {
if(i === 0) {
return ar[0];
} else if(i === 1) {
return br[0];
} else if(i === 2) {
return cr[0];
}
}; // Used to get the x value of a certain point
var y = function(i) {
if(i === 0) {
return ar[1];
} else if(i === 1) {
return br[1];
} else if(i === 2) {
return cr[1];
}
}; // Used to get the y value of a certain point
var setX = function(i, value) {
if(i === 0) {
a[0] = value;
} else if(i

Solution

This will be a slightly abstract review. I'm not a big fan of Processing.js - or rather, I don't know it well enough to know if I'm going against the grain. And Khan Academy's editor is annoying me (it reminds me too much of Microsoft's Clippy; always butting in).

So I'll be talking mostly about how you could do things in raw JavaScript. Some of it already exists in Processing.js, or is done differently in Processing.js.

Anyway, my first point would be that user interface and core logic are too intermingled here. But that's Processing for you - it gets messy.

My next point would be to attack this more high-level. The core data type you're dealing with is coordinates - x and y. Also known as a point, or a vector.

You're storing them as two numbers in an array, which is valid, but with a little more preparation, you can use objects instead, which in turn can make you code more expressive. Right now a lot of your code relies on hard-coded array indices, when what you really mean is x or y, or point a or b.

So let's make some points, using a Point constructor (Processing has a PVector constructor you can use instead):

function Point(x, y) {
  this.x = x;
  this.y = y;
}

var a = new Point(-150, 20);
var b = new Point(0, -50);
var c = new Point(150, 50);


That's our control points. Next are the interpolated points Q, R, and P.

Here's where object orientation comes in handy. The coordinates of the interpolated points are derived entirely from the coordinates of the control points. So we'll want our InterpolatedPoint constructor to take two Points as arguments:

function InterpolatedPoint(a, b) {
    this.a = a;
    this.b = b;
}


Now, instead of just assigning some numbers to the interpolated point's x and y, let's make getter methods that'll calculate coordinates on the fly:

InterpolatedPoint.prototype = {
    getX: function (t) {
        return lerp(this.a.x, this.b.x, t);
    },

    getY: function (t) {
        return lerp(this.a.y, this.b.y, t);
    }
};


I'm using lerp here, which is a basic linear interpolation function that's also in Processing.js. It's just (b - a) * t + a.

So putting that to use:

var q = new InterpolatedPoint(a, b);
var r = new InterpolatedPoint(b, c);
var p = new InterpolatedPoint(q, r);


So now, you can change the coordinates of points a, b, and c, yet as soon as you call p.getX(0.5) or q.getY(0.8) you'll get the right value back. The InterpolatedPoint object keep references to the Point objects that define them. So you're pretty close to a algebraic definition, and the logic has been encapsulated in objects.

$$
\vec{q} = (\vec{b}-\vec{a})t + \vec{a}
$$
$$
\vec{r} = (\vec{c}-\vec{b})t + \vec{b}
$$
$$
\vec{p} = (\vec{r}-\vec{q})t + \vec{q}
$$

From here it's a question of drawing the points and lines. I'd suggest moving some of this logic to methods on Point/InterpolatedPoint - i.e. make them draw themselves.

Because it was a fun little challenge, I've written an alternative implementation (which also differs from the above a little by defining "real" getters) in plain JS. Note: Since I'm using a built-in slider input, it won't work in IE9 and below.

Code Snippets

function Point(x, y) {
  this.x = x;
  this.y = y;
}

var a = new Point(-150, 20);
var b = new Point(0, -50);
var c = new Point(150, 50);
function InterpolatedPoint(a, b) {
    this.a = a;
    this.b = b;
}
InterpolatedPoint.prototype = {
    getX: function (t) {
        return lerp(this.a.x, this.b.x, t);
    },

    getY: function (t) {
        return lerp(this.a.y, this.b.y, t);
    }
};
var q = new InterpolatedPoint(a, b);
var r = new InterpolatedPoint(b, c);
var p = new InterpolatedPoint(q, r);

Context

StackExchange Code Review Q#108522, answer score: 3

Revisions (0)

No revisions yet.