const canvas = document.querySelector('canvas'); const context = canvas.getContext('2d'); /** @type {number[][]} */ const points = []; /** * @param {number} x * @param {number} y */ function addPoint(x, y) { if (points.length === 0) { points.push([x, y]); return; } if (points.length === 1) { const h1x = lerp(0.25, points[0][0], x); const h1y = lerp(0.25, points[0][1], y); const h2x = lerp(0.75, points[0][0], x); const h2y = lerp(0.75, points[0][1], y); points.push([h1x, h1y], [h2x, h2y], [x, y]); return; } } /** * @param {number} t * @param {number} a * @param {number} b * @returns {number} */ function lerp(t, a, b) { return (1 - t) * a + t * b; } /** * interpolates the points * @param {number} t * @param {number[]} p array of points */ function curve(t, p) { if (p.length === 1) return p[0]; return lerp(t, curve(t, p.slice(0, -1)), curve(t, p.slice(1))); } function drawCircles() { for (const [x, y] of points) { context.beginPath(); context.ellipse(x, y, 10, 10, 0, 0, Math.PI * 2); context.stroke(); } } function drawLines() { for (let i = 0; i < points.length - 3; i += 3) { const p1 = points[i]; const p2 = points[i + 1]; const p3 = points[i + 2]; const p4 = points[i + 3]; context.beginPath(); context.moveTo(p1[0], p1[1]); context.lineTo(p2[0], p2[1]); context.moveTo(p3[0], p3[1]); context.lineTo(p4[0], p4[1]); context.moveTo(p1[0], p1[1]); for (let t = 0; t <= 1; t += 0.01) { const x = curve(t, [p1[0], p2[0], p3[0], p4[0]]); const y = curve(t, [p1[1], p2[1], p3[1], p4[1]]); context.lineTo(x, y); } context.stroke(); } } function draw() { context.clearRect(0, 0, canvas.width, canvas.height); if (points.length <= 0) return; drawCircles(); drawLines(); } function resizeCanvas() { canvas.height = window.innerHeight; canvas.width = window.innerWidth; draw(); } window.addEventListener('mousedown', (e) => { addPoint(e.pageX, e.pageY); draw(); }); window.addEventListener('resize', resizeCanvas); resizeCanvas();