(function () { function fillCanvas (element, draw) { var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); var windowToCanvas; function resize () { var vw = element.clientWidth; var vh = element.clientHeight; canvas.width = vw; canvas.height = vh; var vpMin = Math.min(vw, vh); // cartesion coordinates [0, 100] on both x and y axes ctx.setTransform( vpMin / 100, 0, 0, -vpMin / 100, (vw - vpMin) / 2, (vh - vpMin) / 2 + vh ); windowToCanvas = ctx.getTransform().inverse(); } new ResizeObserver(resize).observe(element); resize(); function render () { draw(ctx, canvas.width, canvas.height, windowToCanvas); requestAnimationFrame(render); } render(); element.appendChild(canvas); } const Vec2 = { add: (v1, v2) => [v1[0] + v2[0], v1[1] + v2[1]], sub: (v1, v2) => [v1[0] - v2[0], v1[1] - v2[1]], scale: (s, v) => [s * v[0], s * v[1]], magnitude: (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1]), distance: (v1, v2) => Vec2.magnitude(Vec2.sub(v1, v2)), normalize: (v) => Vec2.scale(1 / Vec2.magnitude(v), v), transform: (m, v) => { return [ m.a * v[0] + m.c * v[1] + m.e, m.b * v[1] + m.d * v[1] + m.f ]; }, dot: (v1, v2) => [v1[0] * v2[0] + v1[1] * v2[1]], triangleArea: (p1, p2, p3) => { // Cramers rule for signed area (necessary to get negative lambdas) var [x1, y1] = p1; var [x2, y2] = p2; var [x3, y3] = p3; return (x1 * y2 - x1 * y3 - x2 * y1 + x3 * y1 + x2 * y3 - x3 * y2) / 2; } }; var ptr = null; window.addEventListener("mousemove", function (e) { ptr = [e.clientX, e.clientY]; }); function drawPath (ctx, path) { for (var i = 1; i < path.length; i++) { ctx.beginPath(); ctx.moveTo(path[i - 1][0], path[i - 1][1]); ctx.lineTo(path[i][0], path[i][1]); ctx.stroke(); } for (var i = 0; i < path.length; i++) { ctx.beginPath(); ctx.arc(path[i][0], path[i][1], 1, 0, Math.PI * 2); ctx.fill(); } } function drawCurve (ctx, curve, u, depth) { var pointsOnCurve = drawPolygons(curve, u, depth); pointsOnCurve.splice(0, 0, curve[0]); pointsOnCurve.push(curve[curve.length - 1]); ctx.fillStyle = "blue"; ctx.strokeStyle = "blue"; ctx.lineWidth = 0.5; drawPath(ctx, pointsOnCurve, true); function drawPolygons (curve, u, depth) { if (depth <= 0) { return []; } ctx.fillStyle = "#555"; ctx.strokeStyle = "#555"; ctx.lineWidth = 0.25; drawPath(ctx, curve, true); var b = [curve]; for (var i = 1; i < curve.length; i++) { var midpoints = []; for (var k = 0; k < curve.length - i; k++) { midpoints.push(Vec2.add( Vec2.scale(u, b[i - 1][k]), Vec2.scale(1 - u, b[i - 1][k + 1]) )); } drawPath(ctx, midpoints); b.push(midpoints); } var pointsOnCurve = []; var c0 = b.map(function (bi) { return bi[0]; }); pointsOnCurve = pointsOnCurve.concat(drawPolygons(c0, u, depth - 1)); pointsOnCurve.push(b[b.length - 1][0]); var c1 = b.map(function (bi) { return bi[bi.length - 1]; }); pointsOnCurve = pointsOnCurve.concat(drawPolygons(c1, 1 - u, depth - 1).toReversed()); return pointsOnCurve; } } var selected = 0; var curve = [ [10, 10], [10, 90], [90, 10], [90, 90] ]; var depth = 5; window.addEventListener("keydown", function (e) { switch (e.key) { case "j": selected = (selected + 1) % curve.length; break; case "k": selected = (curve.length + selected - 1) % curve.length; break; case "ArrowLeft": curve[selected][0]--; break; case "ArrowRight": curve[selected][0]++; break; case "ArrowUp": curve[selected][1]++; break; case "ArrowDown": curve[selected][1]--; break; case "h": depth = Math.max(0, depth - 1); break; case "l": depth++; break; case "?": document.querySelector(".legend").classList.toggle("--hidden"); break; } }); fillCanvas(document.querySelector(".canvas"), function (ctx, vw, vh, w2c) { var origin = Vec2.transform(w2c, [0, 0]); var omega = Vec2.transform(w2c, [vw, vh]); ctx.fillStyle = "black"; ctx.fillRect(origin[0], origin[1], omega[0] - origin[0], omega[1] - origin[1]) drawCurve(ctx, curve, 0.5 + 0.45 * Math.cos(Math.PI * 2 * performance.now() / 5000), depth); /* ctx.beginPath(); ctx.moveTo(curve[0][0], curve[0][1]); ctx.bezierCurveTo( curve[1][0], curve[1][1], curve[2][0], curve[2][1], curve[3][0], curve[3][1] ); ctx.strokeStyle = "red"; ctx.lineWidth = 0.25; ctx.stroke(); */ ctx.beginPath(); ctx.arc(curve[selected][0], curve[selected][1], 2, 0, Math.PI * 2); ctx.strokeStyle = "green"; ctx.lineWidth = 0.5; ctx.stroke(); }); })();