<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="icon" src="/images/favicon.gif" />
<title>metaball thing</title>
<style type="text/css">
* { padding: 0; margin: 0; border: 0; font-size: 20pt; font-weight: normal; }
html, body {
height: 100%;
width: 100%;
background: #000;
color: #fff;
font-family: sans-serif;
cursor: none;
}
html { display: table; }
body { display: table-cell; vertical-align: middle; text-align: center; }
a { color: #88f; }
</style>
<script type="text/javascript">
/* figure out how we're running relative to expected 60fps */
function speedcalc (samples) {
var lastframe, spd = [];
return new Promise(resolve => {
requestAnimationFrame(function frametimer (frametime) {
if (lastframe && lastframe != frametime)
spd.push(frametime - lastframe);
lastframe = frametime;
if (spd.length < samples) requestAnimationFrame(frametimer);
else return resolve(spd.reduce((s, c) => s += c) / spd.length);
});
});
}
// normalize a vector of N dimensions stored in a given array
function normalize (v) {
return v.map((h => c => c / h)(v.reduce((s, c) => s += c * c, 0) ** 0.5));
}
addEventListener("load", async function load (e) {
removeEventListener("load", load, false);
var ele = document.getElementsByTagName("canvas")[0];
var scr = ele.getContext("2d");
/* turn off smooth resizing of huge pixels. we want all the pixelly goodness. */
/* having to do this is silly. browser vendors, stop this shit. */
[ "i", "oI", "msI", "mozI", "webkitI" ].every(p => {
if (scr[p + "mageSmoothingEnabled"])
return scr[p + "mageSmoothingEnabled"] = false;
});
var rsz = document.createElement("canvas");
rsz.height = (""+document.location.href).match(/[?&](lines|height)=([^&]*)/) ? RegExp.lastParen : 240;
rsz.width = (""+document.location.href).match(/[?&]columns=([^&]*)/) ? RegExp.lastParen : 4 * rsz.height / 3;
var speed = (""+document.location.href).match(/[?&]speed=([^&]*)/) ? RegExp.lastParen : 1;
var ctx = rsz.getContext("2d");
var img = ctx.createImageData(rsz.width, rsz.height);
img.putpx = function putpx (y, x, r, g, b, a) {
if (y < 0 || y > img.height || x < 0 || x > img.width) return;
var p = 4 * (Math.floor(y) * img.width + Math.floor(x));
img.data[p + 0] = r; img.data[p + 1] = g;
img.data[p + 2] = b; img.data[p + 3] = a;
}
var scale = rsz.width / 320;
var radius = 35 * scale; // approximation, unfortunately.
var balls = [...new Array(
(""+document.location.href).match(/[?&]balls=([^&]*)/) ?
RegExp.lastParen - 0 : 14
)];
await speedcalc(5);
speed *= 50 / 3 / await speedcalc(15);
console.log("running at", Math.round(speed * 10000) / 100 + "% speed");
speed *= scale;
for (var i in balls) {
// generate random normalized direction vector without trig functions
// just because we don't have to think about quadrants to bounce them
// around the screen this way
var [ dy, dx ] = normalize([ 0, 0 ].map(_ => 2 * Math.random() - 1)).map(n => n * speed);
balls[i] = {
y: Math.floor(radius + (img.height - 2 * radius) * Math.random()),
x: Math.floor(radius + (img.width - 2 * radius) * Math.random()),
dy: dy, dx: dx, str: i < balls.length / 2 ? 127 : -128
};
}
/* render loop! go! */
requestAnimationFrame(function draw (frametime) {
/* draw balls */
for (var y = 0; y < img.height; y++) {
for (var x = 0; x < img.width; x++) {
var s = 0;
for (var ball of balls) {
var dy = ball.y - y;
var dx = ball.x - x;
s += ball.str / (dx * dx + dy * dy);
}
var p = 4 * (img.width * y + x);
if (.26 / scale / scale < s) {
if (.34 / scale / scale > s) {
img.data[p + 0] = 255 * 9 / 9;
img.data[p + 1] = 255 * 8 / 9;
img.data[p + 2] = 255 * 7 / 9;
} else {
img.data[p + 0] = 255 * 2 / 9;
img.data[p + 1] = 255 * 1 / 9;
img.data[p + 2] = 255 * 0 / 9;
}
} else if (-.26 / scale / scale > s) {
if (-.34 / scale / scale < s) {
img.data[p + 0] = 255 * 7 / 9;
img.data[p + 1] = 255 * 8 / 9;
img.data[p + 2] = 255 * 9 / 9;
} else {
img.data[p + 0] = 255 * 0 / 9;
img.data[p + 1] = 255 * 1 / 9;
img.data[p + 2] = 255 * 2 / 9;
}
} else img.data[p + 0] = img.data[p + 1] = img.data[p + 2] = 0;
img.data[p + 3] = 255;
}
}
/* move balls */
for (var i in balls) {
var ball = balls[i];
ball.y += ball.dy;
ball.x += ball.dx;
if ( // y out of bounds
ball.y < radius || ball.y > img.height - radius
) { // undo move
ball.y -= ball.dy;
// generate new direction
[ ball.dy, ball.dx ] = normalize([
-Math.sign(ball.dy) * Math.random(),
Math.sign(ball.dx) * Math.random()
]).map(n => n * speed);
// move again
ball.y += ball.dy;
}
if ( // x out of bounds
ball.x < radius || ball.x > img.width - radius
) { // undo move
ball.x -= ball.dx;
// generate new direction
[ ball.dy, ball.dx ] = normalize([
Math.sign(ball.dy) * Math.random(),
-Math.sign(ball.dx) * Math.random()
]).map(n => n * speed);
// move again
ball.x += ball.dx;
}
}
/* draw to buffer */
ctx.putImageData(img, 0, 0);
/* blit to screen */
scr.drawImage(rsz, 0, 0, ele.width, ele.height);
/* queue next frame */
requestAnimationFrame(draw);
});
});
</script>
</head>
<body>
<canvas height="600px" width="800px">Y U no canvas?</canvas>
</body>
</html>