Add 'bezier.ts'
This commit is contained in:
parent
5b56273ffa
commit
dc8cea6a05
223
bezier.ts
Normal file
223
bezier.ts
Normal 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();
|
Reference in New Issue
Block a user