xxxxxxxxxx
161
/*
After watching The Coding Train's video about Bezier curves,
and seeing their implementation with nested lerps, and
eventually the equation for a cubic bezier curve, I wanted to
extrapolate that to higher order curves (as best as I can).
This is my first time using Javascript, and is most likely
highly unoptimized, but it was a fun little experiment.
Hit the Play Button above to begin.
Controls:
- LMB: add a point to the curve
- Hold CMB (mouse wheel) over a point: move a point
- Enter: toggle lines
- ESC: clear all points
- Delete/Backspace: remove last point
*/
var points = [];
let deltaT = 0.01;
let showLines = false;
function setup() {
createCanvas(600, 600);
}
function draw() {
background(0);
stroke(255);
noFill();
colorMode(HSB);
textSize(15);
textAlign(CENTER, CENTER);
if (mouseIsPressed && mouseButton == CENTER)
points.forEach(point => point.mouseOver(mouseX, mouseY));
if (points.length > 1)
{
// draw all points to visualize control points
for (let i = 0; i < points.length; i++)
{
stroke(255);
strokeWeight(24);
point(points[i].x, points[i].y);
stroke(0);
strokeWeight(1);
text(i, points[i].x, points[i].y);
}
// render all points along the curve
beginShape();
for (let t = 0; t < 1.01; t += deltaT)
{
let n = points.length - 1;
stroke(t * 360, 255, 255);
strokeWeight(0.5);
// find bezier point
let p = findBezier(t);
if (showLines)
{
line(points[0].x, points[0].y, p.x, p.y);
line(p.x, p.y, points[n].x, points[n].y);
}
else
{
vertex(p.x, p.y);
}
}
endShape();
}
// single point
else if (points.length == 1)
{
point(points[0].x, points[0].y);
}
}
function keyPressed()
{
if (keyCode == ESCAPE)
{
while (points.length != 0)
{
points.pop();
}
print("Points cleared");
}
if (keyCode == DELETE || keyCode == BACKSPACE)
points.pop();
if (keyCode == ENTER)
showLines = !showLines;
}
function mouseClicked()
{
if (mouseButton == LEFT &&
(mouseX > 0 && mouseX < 600 && mouseY > 0 && mouseY < 600))
{
let p = new MovingPoint(mouseX, mouseY, 24);
points.push(p);
strokeWeight(24);
point(p.x, p.y);
}
}
function calculateFactorial(val)
{
if (val == 0)
return 1; // 0! = 1
let fac = val;
for (let i = fac - 1; i > 0; i--)
{
fac *= i;
}
return fac;
}
function calculateBinomialCoefficient(k, n)
{
let numer = calculateFactorial(n);
let denom = calculateFactorial(k) * calculateFactorial(n - k);
return numer / denom;
}
/* Bezier Curve equation (https://en.wikipedia.org/wiki/Bézier_curve#Explicit_definition):
Bt = (n:i)(1-t)^n-i * t^i * Pi
where n is the order of the curve, and i is the current point index,
and n:i is the Binomial coefficient (https://en.wikipedia.org/wiki/Binomial_coefficient)
returns a vector that represents the weight of the curve at that point
*/
function findBezier(t)
{
let output = createVector(0, 0);
let n = points.length - 1;
let oneMinT = pow((1-t), n); //(1-t)^n-i
let ti = 1; //t^i
// start with first anchor point
output.x = oneMinT * ti * points[0].x;
output.y = oneMinT * ti * points[0].y;
// loop through the rest of the points and run the calculation
for (let i = 1; i < points.length; i++)
{
let bin = calculateBinomialCoefficient(i, n); //(n:i)
oneMinT = pow((1-t), n - i);
ti = pow(t, i);
output.x += bin * ti * oneMinT * points[i].x;
output.y += bin * ti * oneMinT * points[i].y;
}
return output;
}