Add bezier demo

This commit is contained in:
Paul Brinkmeier 2024-08-07 11:48:07 +02:00
parent f5a53bdcc1
commit bab3208e61
2 changed files with 241 additions and 0 deletions

197
cg/bezier/bezier.js Normal file
View File

@ -0,0 +1,197 @@
(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();
});
})();

44
cg/bezier/index.html Normal file
View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>bezier</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Helvetica, Arial, sans-serif;
}
canvas {
display: block;
}
.canvas {
width: 100vw;
height: 100vh;
}
.legend {
position: absolute;
left: 2em;
top: 2em;
color: white;
border: 1px dashed;
padding: 1em;
white-space: pre-wrap;
}
.legend.--hidden {
display: none;
}
.smol {
font-size: .7em;
}
</style>
</head>
<body>
<div class="canvas"></div>
<div class="legend">j, k: Select vertex
h, l: Select recursion depth
<span class="smol">⬅⬆⬇➡</span>: Move selected vertex
?: hide/unhide this tooltip</div>
<script src="./bezier.js"></script>
</body>
</html>