Naposledy aktivní 2 years ago

evert's Avatar evert revidoval tento gist 2 years ago. Přejít na revizi

1 file changed, 223 insertions

bezier.ts(vytvořil soubor)

@@ -0,0 +1,223 @@
1 + import { vec2 } from 'gl-matrix';
2 + import './index.scss';
3 +
4 + const canvas = document.createElement('canvas');
5 + const ctx = canvas.getContext('2d')!;
6 +
7 + interface Bezier {
8 + start: vec2;
9 + controlS: vec2;
10 + controlE: vec2;
11 + end: vec2;
12 + }
13 +
14 + interface DrawBezier extends Bezier {
15 + width: number;
16 + color: string;
17 + }
18 +
19 + const cursor: vec2 = [0, 0];
20 + let movingPoint: vec2 | undefined;
21 + let currentCurve: Bezier | undefined;
22 + let currentPointIndex: number | undefined;
23 + let handleOffset: vec2 | undefined;
24 + let pointOffsets: vec2[] = [];
25 + let shiftDown = false;
26 +
27 + const drawCirclei = (x: number, y: number, radius: number) => {
28 + ctx.beginPath();
29 + ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
30 + ctx.fill();
31 + ctx.stroke();
32 + };
33 + const drawCirclep = (point: vec2, radius: number) =>
34 + drawCirclei(point[0], point[1], radius);
35 +
36 + const drawBezier = (bezier: DrawBezier) => {
37 + ctx.strokeStyle = bezier.color;
38 + ctx.lineWidth = bezier.width;
39 + ctx.beginPath();
40 + ctx.moveTo(bezier.start[0], bezier.start[1]);
41 + ctx.bezierCurveTo(
42 + bezier.controlS[0],
43 + bezier.controlS[1],
44 + bezier.controlE[0],
45 + bezier.controlE[1],
46 + bezier.end[0],
47 + bezier.end[1]
48 + );
49 + ctx.stroke();
50 + };
51 +
52 + const inCircle = (point: vec2, circle: vec2, radius: number) =>
53 + vec2.dist(point, circle) < radius;
54 +
55 + const drawHandle = (point: vec2, radius: number) => {
56 + ctx.fillStyle = inCircle(cursor, point, radius + 1)
57 + ? 'rgba(0, 0, 0, 0.15)'
58 + : 'transparent';
59 + ctx.strokeStyle = '#000';
60 + ctx.lineWidth = 1;
61 + drawCirclep(point, radius);
62 + };
63 +
64 + const lines: DrawBezier[] = [
65 + {
66 + start: [100, 100],
67 + controlS: [250, 250],
68 + controlE: [500, 250],
69 + end: [650, 100],
70 + width: 15,
71 + color: '#ff11aa',
72 + },
73 + ];
74 +
75 + const getControlledBezier = () => {
76 + let bezier: Bezier | undefined;
77 + let point: vec2 | undefined;
78 + let pindex: number | undefined;
79 +
80 + for (const curve of lines) {
81 + if (inCircle(cursor, curve.start, curve.width + 5 + 1)) {
82 + bezier = curve;
83 + point = curve.start;
84 + pindex = 0;
85 + }
86 +
87 + if (inCircle(cursor, curve.end, curve.width + 5 + 1)) {
88 + bezier = curve;
89 + point = curve.end;
90 + pindex = 3;
91 + }
92 +
93 + if (inCircle(cursor, curve.controlS, 10 + 1)) {
94 + bezier = curve;
95 + point = curve.controlS;
96 + pindex = 1;
97 + }
98 +
99 + if (inCircle(cursor, curve.controlE, 10 + 1)) {
100 + bezier = curve;
101 + point = curve.controlE;
102 + pindex = 2;
103 + }
104 + }
105 +
106 + return { bezier, point, pindex };
107 + };
108 +
109 + const getHandleOffset = () => {
110 + if (!currentCurve || !movingPoint) {
111 + return;
112 + }
113 +
114 + if (currentPointIndex === 1) {
115 + handleOffset = vec2.sub(
116 + vec2.create(),
117 + currentCurve.start,
118 + currentCurve.controlS
119 + );
120 + } else if (currentPointIndex === 2) {
121 + handleOffset = vec2.sub(
122 + vec2.create(),
123 + currentCurve.end,
124 + currentCurve.controlE
125 + );
126 + } else {
127 + handleOffset = undefined;
128 +
129 + if (currentPointIndex === 0 || currentPointIndex === 3) {
130 + pointOffsets = [
131 + vec2.sub(vec2.create(), currentCurve.start, movingPoint),
132 + vec2.sub(vec2.create(), currentCurve.controlS, movingPoint),
133 + vec2.sub(vec2.create(), currentCurve.controlE, movingPoint),
134 + vec2.sub(vec2.create(), currentCurve.end, movingPoint),
135 + ];
136 + }
137 + }
138 + };
139 +
140 + const relativeMove = () => {
141 + if (!currentCurve || !movingPoint) {
142 + return;
143 + }
144 +
145 + if (currentPointIndex === 1) {
146 + vec2.add(currentCurve.start, movingPoint, handleOffset!);
147 + } else if (currentPointIndex === 2) {
148 + vec2.add(currentCurve.end, movingPoint, handleOffset!);
149 + } else {
150 + vec2.add(currentCurve.start, pointOffsets[0], movingPoint);
151 + vec2.add(currentCurve.controlS, pointOffsets[1], movingPoint);
152 + vec2.add(currentCurve.controlE, pointOffsets[2], movingPoint);
153 + vec2.add(currentCurve.end, pointOffsets[3], movingPoint);
154 + }
155 + };
156 +
157 + function resize() {
158 + canvas.width = window.innerWidth;
159 + canvas.height = window.innerHeight;
160 + }
161 +
162 + function draw() {
163 + ctx.clearRect(0, 0, canvas.width, canvas.height);
164 + ctx.lineCap = 'round';
165 + lines.forEach((item) => {
166 + drawBezier(item);
167 + drawHandle(item.start, item.width + 5);
168 + drawHandle(item.end, item.width + 5);
169 + drawHandle(item.controlS, 10);
170 + drawHandle(item.controlE, 10);
171 + });
172 + }
173 +
174 + function loop() {
175 + requestAnimationFrame(loop);
176 + draw();
177 + }
178 +
179 + canvas.addEventListener('mousemove', (ev) => {
180 + cursor[0] = ev.clientX;
181 + cursor[1] = ev.clientY;
182 +
183 + if (movingPoint) {
184 + if (shiftDown && currentCurve) {
185 + getHandleOffset();
186 + }
187 +
188 + vec2.copy(movingPoint, cursor);
189 +
190 + if (shiftDown && currentCurve) {
191 + relativeMove();
192 + }
193 + }
194 + });
195 +
196 + canvas.addEventListener('mousedown', (ev) => {
197 + const { point, bezier, pindex } = getControlledBezier();
198 + if (point && bezier) {
199 + movingPoint = point;
200 + currentCurve = bezier;
201 + currentPointIndex = pindex;
202 + }
203 + });
204 +
205 + canvas.addEventListener('mouseup', () => {
206 + movingPoint = undefined;
207 + });
208 +
209 + window.addEventListener('keydown', (ev) => {
210 + if (ev.key === 'Shift') {
211 + shiftDown = true;
212 + }
213 + });
214 +
215 + window.addEventListener('keyup', (ev) => {
216 + if (ev.key === 'Shift') {
217 + shiftDown = false;
218 + }
219 + });
220 + resize();
221 + window.addEventListener('resize', resize);
222 + document.body.appendChild(canvas);
223 + loop();
Novější Starší