Files
havox/projects/cubic_bezier_curve/sketch.js
2026-03-16 19:04:56 +00:00

240 lines
5.8 KiB
JavaScript

let p = [], v = [];
let lines = [];
let theta = (3 / 2) * Math.PI, t = 0;
let paused = false;
let showLabels = false;
let showInterPoints = true;
let hoverIndex = -1;
let selectedIndex = -1;
let prevMouse = { x: 0, y: 0 };
let draggingAll = false;
const HIT_R = 10;
const MAX_TRAIL = 1000;
function setup() {
createCanvas(1000, 800);
initCubicCurve(true);
}
function draw() {
background(32);
stroke(120, 120, 120, 180);
strokeWeight(2);
noFill();
beginShape();
vertex(p[0].x, p[0].y);
vertex(p[1].x, p[1].y);
vertex(p[2].x, p[2].y);
vertex(p[3].x, p[3].y);
endShape();
if (!paused) {
if (theta > TWO_PI) theta = 0;
theta += 0.01;
t = (sin(theta) + 1) / 2; // 0..1
}
lerpCubicCurve();
for (let i = 0; i < lines.length; i++) {
lines[i].drawLine();
}
drawCurve();
drawPointsAndLabels();
drawHUD();
}
function drawCurve() {
if (v.length > MAX_TRAIL) v.splice(0, v.length - MAX_TRAIL);
stroke(0, 255, 0, 200);
strokeWeight(5);
noFill();
beginShape();
for (let i = 0; i < v.length; i++) {
vertex(v[i].x, v[i].y);
}
endShape();
}
function drawPointsAndLabels() {
push();
noStroke();
fill(255, 70, 70, 230);
circle(p[9].x, p[9].y, 14);
pop();
for (let i = 0; i < 4; i++) {
const hovered = (i === hoverIndex);
const selected = (i === selectedIndex);
p[i].drawPoint(hovered, selected);
if (showLabels) {
noStroke();
fill(255);
textSize(14);
textAlign(LEFT, BOTTOM);
text(`P${i}`, p[i].x + 10, p[i].y - 10);
}
}
if (showLabels) {
noStroke(); fill(220);
textSize(12); textAlign(LEFT, BOTTOM);
text(`P4`, p[4].x + 8, p[4].y - 8);
}
if (showInterPoints) {
const styleMid = {
stroke: [80, 180, 255, 100],
fill: [80, 180, 255, 50]
};
for (let i = 4; i <= 8; i++) {
p[i].drawPoint(false, false, styleMid);
if (showLabels) {
noStroke(); fill(200);
textSize(12); textAlign(LEFT, BOTTOM);
text(`P${i}`, p[i].x + 8, p[i].y - 8);
}
}
}
}
function drawHUD() {
noStroke();
fill(255);
textSize(14);
textAlign(LEFT, TOP);
const hud = [
`t = ${t.toFixed(3)}`,
`Space = ${paused ? 'Resume' : 'Pause'}`,
`H = Toggle points 5..8 | L = Toggle labels | R = Reset`,
`Drag a handle (P0..P3). Hold Shift to move the entire curve.`
];
for (let i = 0; i < hud.length; i++) {
text(hud[i], 12, 12 + i * 18);
}
}
function initCubicCurve(resetPositions = true) {
p.length = 0;
v.length = 0;
lines.length = 0;
theta = (3 / 2) * Math.PI;
t = 0;
if (resetPositions) {
p[0] = new cPoint(width * 0.15, height * 0.75, 'end');
p[1] = new cPoint(width * 0.35, height * 0.20, 'control');
p[2] = new cPoint(width * 0.65, height * 0.20, 'control');
p[3] = new cPoint(width * 0.85, height * 0.75, 'end');
} else {
for (let i = 0; i < 4; i++) {
if (!p[i]) p[i] = new cPoint(width * (0.15 + i * 0.2), height / 2, i === 0 || i === 3 ? 'end' : 'control');
}
}
// Derived points
p[4] = new cPoint(0, 0, 'derived');
p[5] = new cPoint(0, 0, 'derived');
p[6] = new cPoint(0, 0, 'derived');
p[7] = new cPoint(0, 0, 'derived');
p[8] = new cPoint(0, 0, 'derived');
p[9] = new cPoint(0, 0, 'derived');
lines[0] = new cLine(p[4], p[5], color(160, 160, 160, 200), 2);
lines[1] = new cLine(p[5], p[6], color(160, 160, 160, 200), 2);
lines[2] = new cLine(p[7], p[8], color(200, 200, 200, 220), 3);
}
function lerpCubicCurve() {
// Level 1
p[4].changeX = (1 - t) * p[0].x + t * p[1].x;
p[4].changeY = (1 - t) * p[0].y + t * p[1].y;
p[5].changeX = (1 - t) * p[1].x + t * p[2].x;
p[5].changeY = (1 - t) * p[1].y + t * p[2].y;
p[6].changeX = (1 - t) * p[2].x + t * p[3].x;
p[6].changeY = (1 - t) * p[2].y + t * p[3].y;
// Level 2
p[7].changeX = (1 - t) * p[4].x + t * p[5].x;
p[7].changeY = (1 - t) * p[4].y + t * p[5].y;
p[8].changeX = (1 - t) * p[5].x + t * p[6].x;
p[8].changeY = (1 - t) * p[5].y + t * p[6].y;
// Final on-curve
p[9].changeX = (1 - t) * p[7].x + t * p[8].x;
p[9].changeY = (1 - t) * p[7].y + t * p[8].y;
v.push(new cPoint(p[9].x, p[9].y, 'derived'));
}
function mouseMoved() {
hoverIndex = -1;
for (let i = 0; i < 4; i++) {
if (p[i].isHit(mouseX, mouseY)) {
hoverIndex = i;
break;
}
}
}
function mousePressed() {
prevMouse.x = mouseX;
prevMouse.y = mouseY;
selectedIndex = -1;
for (let i = 0; i < 4; i++) {
if (p[i].isHit(mouseX, mouseY)) {
selectedIndex = i;
break;
}
}
draggingAll = keyIsDown(SHIFT) && selectedIndex === -1;
}
function mouseDragged() {
const dx = mouseX - prevMouse.x;
const dy = mouseY - prevMouse.y;
if (selectedIndex >= 0) {
p[selectedIndex].changeX = mouseX;
p[selectedIndex].changeY = mouseY;
v.length = 0;
} else if (draggingAll) {
for (let i = 0; i < 4; i++) {
p[i].changeX = p[i].x + dx;
p[i].changeY = p[i].y + dy;
}
v.length = 0;
}
prevMouse.x = mouseX;
prevMouse.y = mouseY;
return false;
}
function mouseReleased() {
selectedIndex = -1;
draggingAll = false;
}
function keyPressed() {
if (key === ' ') {
paused = !paused;
} else if (key === 'R' || key === 'r') {
initCubicCurve(true);
} else if (key === 'H' || key === 'h') {
showInterPoints = !showInterPoints;
} else if (key === 'L' || key === 'l') {
showLabels = !showLabels;
}
}