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