Control points
Change the control points and update the curve.
let curves, curve;
const points = [[90,310],[650,360],[650,40],[90,40],[90,360],[650,310]];
function setup() {
curves = initBezier(createCanvas(720, 400));
curve = curves.new(points, 'OPEN', 3);
noLoop();
}
function draw() {
background('#ffffff');
noFill();
stroke('#171717');
strokeWeight(3);
curve.draw();
stroke('#d4d4d4');
strokeWeight(1);
for (let i = 1; i < points.length; i++) line(...points[i - 1], ...points[i]);
fill('#ffffff');
stroke('#171717');
for (const [x, y] of points) circle(x, y, 10);
}
// Drag a control point. Rebuild closed curves to regenerate closure points.
let selected = -1;
function mousePressed() {
selected = points.findIndex(([x, y]) => dist(x, y, mouseX, mouseY) < 20);
}
function mouseDragged() {
if (selected < 0) return;
points[selected] = [constrain(mouseX, 0, width), constrain(mouseY, 0, height)];
curve.update(points.map(point => [...point]));
redraw();
}
function mouseReleased() { selected = -1; }
Freehand
Use a stroke as control points. Each stroke replaces the
previous curve.
let curves;
let points = [[90,310],[650,360],[650,40],[90,40],[90,360],[650,310]];
function setup() {
curves = initBezier(createCanvas(720, 400));
noLoop();
}
function draw() {
background('#ffffff');
noFill();
stroke('#171717');
strokeWeight(3);
if (points.length > 1) curves.draw(points, 'OPEN', 3);
}
function mousePressed() {
if (mouseX < 0 || mouseX > width || mouseY < 0 || mouseY > height) return;
points = [[mouseX, mouseY]];
redraw();
}
function mouseDragged() {
const next = [constrain(mouseX, 0, width), constrain(mouseY, 0, height)];
if (points.length === 0) return;
const last = points[points.length - 1];
if (dist(...last, ...next) < 8) return;
if (points.length >= 80) points = points.filter((_, i) => i % 2 === 0);
points.push(next);
redraw();
}
Dashed curves
Draw a cached curve with a dash and gap pattern.
let curves, curve;
const points = [[90,310],[650,360],[650,40],[90,40],[90,360],[650,310]];
function setup() {
curves = initBezier(createCanvas(720, 400));
curve = curves.new(points, 'OPEN', 3);
noLoop();
}
function draw() {
background('#ffffff');
noFill();
stroke('#171717');
strokeWeight(3);
curve.draw([16, 8]);
stroke('#d4d4d4');
strokeWeight(1);
for (let i = 1; i < points.length; i++) line(...points[i - 1], ...points[i]);
fill('#ffffff');
stroke('#171717');
for (const [x, y] of points) circle(x, y, 10);
}
// Drag a control point. Rebuild closed curves to regenerate closure points.
let selected = -1;
function mousePressed() {
selected = points.findIndex(([x, y]) => dist(x, y, mouseX, mouseY) < 20);
}
function mouseDragged() {
if (selected < 0) return;
points[selected] = [constrain(mouseX, 0, width), constrain(mouseY, 0, height)];
curve.update(points.map(point => [...point]));
redraw();
}
function mouseReleased() { selected = -1; }
Closest point
Find the nearest sampled vertex to the pointer.
let curves, curve;
const points = [[90,310],[650,360],[650,40],[90,40],[90,360],[650,310]];
let probe = [440,130];
function setup() {
curves = initBezier(createCanvas(720, 400));
curve = curves.new(points, 'OPEN', 3);
noLoop();
}
function draw() {
background('#ffffff');
noFill();
stroke('#171717');
strokeWeight(3);
curve.draw();
const nearest = curve.shortest(probe[0], probe[1]);
stroke('#a3a3a3');
strokeWeight(1);
line(probe[0], probe[1], nearest[0], nearest[1]);
fill('#171717');
noStroke();
circle(nearest[0], nearest[1], 9);
noFill();
stroke('#171717');
circle(probe[0], probe[1], 12);
}
function mouseMoved() {
probe = [constrain(mouseX, 0, width), constrain(mouseY, 0, height)];
redraw();
}
Smoothness
Compare five sampling levels. The selected level is drawn in
black.
let curves;
const points = [[90,310],[650,360],[650,40],[90,40],[90,360],[650,310]];
function setup() {
curves = initBezier(createCanvas(720, 400));
noLoop();
}
function draw() {
background('#ffffff');
noFill();
stroke('#171717');
strokeWeight(3);
for (let level = 1; level <= 5; level++) {
stroke(level === 3 ? '#171717' : '#a3a3a3');
strokeWeight(level === 3 ? 3 : 1);
const shifted = points.map(([x, y]) => [x, y * 0.6 + level * 25]);
curves.draw(shifted, 'OPEN', level);
}
}
Translation
Translate cached vertices to draw twelve copies of a curve.
let curves, curve;
const points = [[90,310],[650,360],[650,40],[90,40],[90,360],[650,310]];
function setup() {
curves = initBezier(createCanvas(720, 400));
curve = curves.new(points, 'OPEN', 3);
noLoop();
}
function draw() {
background('#ffffff');
noFill();
stroke('#171717');
strokeWeight(3);
for (let i = 11; i >= 0; i--) {
stroke(i === 0 ? '#171717' : '#a3a3a3');
strokeWeight(i === 0 ? 3 : 1);
curve.move(i * -6, i * 6);
}
stroke('#d4d4d4');
strokeWeight(1);
for (let i = 1; i < points.length; i++) line(...points[i - 1], ...points[i]);
fill('#ffffff');
stroke('#171717');
for (const [x, y] of points) circle(x, y, 10);
}
// Drag a control point. Rebuild closed curves to regenerate closure points.
let selected = -1;
function mousePressed() {
selected = points.findIndex(([x, y]) => dist(x, y, mouseX, mouseY) < 20);
}
function mouseDragged() {
if (selected < 0) return;
points[selected] = [constrain(mouseX, 0, width), constrain(mouseY, 0, height)];
curve.update(points.map(point => [...point]));
redraw();
}
function mouseReleased() { selected = -1; }
WebGL
Draw 3D curves with a p5.js WebGL graphics buffer.
let buffer, curve;
const points = [[90,310],[650,360],[650,40],[90,40],[90,360],[650,310]].map(([x, y], i) => [x - 360, y - 200, Math.sin(i * 1.4) * 140]);
function setup() {
createCanvas(720, 400);
buffer = createGraphics(720, 400, WEBGL);
const curves = initBezier(buffer);
curve = curves.new(points, 'OPEN', 3);
noLoop();
}
function draw() {
buffer.background('#0a0a0a');
buffer.push();
buffer.scale(0.48);
buffer.rotateY(0.45);
buffer.rotateX(-0.25);
buffer.noFill();
buffer.strokeWeight(2);
for (let i = 0; i < 14; i++) {
buffer.stroke(i % 3 === 0 ? '#ffffff' : '#a3a3a3');
buffer.push();
buffer.translate(0, (i - 7) * 9, (i - 7) * 8);
curve.draw();
buffer.pop();
}
buffer.pop();
image(buffer, 0, 0);
}