251 lines
5.4 KiB
JavaScript
251 lines
5.4 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.2, 'control');
|
|
p[2] = new cPoint(width * 0.65, height * 0.2, '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;
|
|
}
|
|
}
|