Add bezier demo
This commit is contained in:
parent
f5a53bdcc1
commit
bab3208e61
197
cg/bezier/bezier.js
Normal file
197
cg/bezier/bezier.js
Normal 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
44
cg/bezier/index.html
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user