Add 'bezier.ts'

This commit is contained in:
Evert Prants 2023-07-29 06:56:47 +00:00
parent 5b56273ffa
commit dc8cea6a05

223
bezier.ts Normal file
View File

@ -0,0 +1,223 @@
import { vec2 } from 'gl-matrix';
import './index.scss';
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
interface Bezier {
start: vec2;
controlS: vec2;
controlE: vec2;
end: vec2;
}
interface DrawBezier extends Bezier {
width: number;
color: string;
}
const cursor: vec2 = [0, 0];
let movingPoint: vec2 | undefined;
let currentCurve: Bezier | undefined;
let currentPointIndex: number | undefined;
let handleOffset: vec2 | undefined;
let pointOffsets: vec2[] = [];
let shiftDown = false;
const drawCirclei = (x: number, y: number, radius: number) => {
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.fill();
ctx.stroke();
};
const drawCirclep = (point: vec2, radius: number) =>
drawCirclei(point[0], point[1], radius);
const drawBezier = (bezier: DrawBezier) => {
ctx.strokeStyle = bezier.color;
ctx.lineWidth = bezier.width;
ctx.beginPath();
ctx.moveTo(bezier.start[0], bezier.start[1]);
ctx.bezierCurveTo(
bezier.controlS[0],
bezier.controlS[1],
bezier.controlE[0],
bezier.controlE[1],
bezier.end[0],
bezier.end[1]
);
ctx.stroke();
};
const inCircle = (point: vec2, circle: vec2, radius: number) =>
vec2.dist(point, circle) < radius;
const drawHandle = (point: vec2, radius: number) => {
ctx.fillStyle = inCircle(cursor, point, radius + 1)
? 'rgba(0, 0, 0, 0.15)'
: 'transparent';
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;
drawCirclep(point, radius);
};
const lines: DrawBezier[] = [
{
start: [100, 100],
controlS: [250, 250],
controlE: [500, 250],
end: [650, 100],
width: 15,
color: '#ff11aa',
},
];
const getControlledBezier = () => {
let bezier: Bezier | undefined;
let point: vec2 | undefined;
let pindex: number | undefined;
for (const curve of lines) {
if (inCircle(cursor, curve.start, curve.width + 5 + 1)) {
bezier = curve;
point = curve.start;
pindex = 0;
}
if (inCircle(cursor, curve.end, curve.width + 5 + 1)) {
bezier = curve;
point = curve.end;
pindex = 3;
}
if (inCircle(cursor, curve.controlS, 10 + 1)) {
bezier = curve;
point = curve.controlS;
pindex = 1;
}
if (inCircle(cursor, curve.controlE, 10 + 1)) {
bezier = curve;
point = curve.controlE;
pindex = 2;
}
}
return { bezier, point, pindex };
};
const getHandleOffset = () => {
if (!currentCurve || !movingPoint) {
return;
}
if (currentPointIndex === 1) {
handleOffset = vec2.sub(
vec2.create(),
currentCurve.start,
currentCurve.controlS
);
} else if (currentPointIndex === 2) {
handleOffset = vec2.sub(
vec2.create(),
currentCurve.end,
currentCurve.controlE
);
} else {
handleOffset = undefined;
if (currentPointIndex === 0 || currentPointIndex === 3) {
pointOffsets = [
vec2.sub(vec2.create(), currentCurve.start, movingPoint),
vec2.sub(vec2.create(), currentCurve.controlS, movingPoint),
vec2.sub(vec2.create(), currentCurve.controlE, movingPoint),
vec2.sub(vec2.create(), currentCurve.end, movingPoint),
];
}
}
};
const relativeMove = () => {
if (!currentCurve || !movingPoint) {
return;
}
if (currentPointIndex === 1) {
vec2.add(currentCurve.start, movingPoint, handleOffset!);
} else if (currentPointIndex === 2) {
vec2.add(currentCurve.end, movingPoint, handleOffset!);
} else {
vec2.add(currentCurve.start, pointOffsets[0], movingPoint);
vec2.add(currentCurve.controlS, pointOffsets[1], movingPoint);
vec2.add(currentCurve.controlE, pointOffsets[2], movingPoint);
vec2.add(currentCurve.end, pointOffsets[3], movingPoint);
}
};
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.lineCap = 'round';
lines.forEach((item) => {
drawBezier(item);
drawHandle(item.start, item.width + 5);
drawHandle(item.end, item.width + 5);
drawHandle(item.controlS, 10);
drawHandle(item.controlE, 10);
});
}
function loop() {
requestAnimationFrame(loop);
draw();
}
canvas.addEventListener('mousemove', (ev) => {
cursor[0] = ev.clientX;
cursor[1] = ev.clientY;
if (movingPoint) {
if (shiftDown && currentCurve) {
getHandleOffset();
}
vec2.copy(movingPoint, cursor);
if (shiftDown && currentCurve) {
relativeMove();
}
}
});
canvas.addEventListener('mousedown', (ev) => {
const { point, bezier, pindex } = getControlledBezier();
if (point && bezier) {
movingPoint = point;
currentCurve = bezier;
currentPointIndex = pindex;
}
});
canvas.addEventListener('mouseup', () => {
movingPoint = undefined;
});
window.addEventListener('keydown', (ev) => {
if (ev.key === 'Shift') {
shiftDown = true;
}
});
window.addEventListener('keyup', (ev) => {
if (ev.key === 'Shift') {
shiftDown = false;
}
});
resize();
window.addEventListener('resize', resize);
document.body.appendChild(canvas);
loop();