Rubik's cube 5x5x5 edgeswap added.
[zzandy.git] / metaballs.html
blobcf7f3ca2ca1854c09a8c7d320864e05ae519b510
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml">
3 <head>
4 <title>Metaballs</title>
5 <style type="text/css">
6 html, body {
7 background-color: #232220;
8 color: #dad6d0;
10 </style>
11 </head>
12 <body>
13 <canvas id="can"></canvas>
15 <!-- random -->
16 <script type="text/javascript">
18 /** rnd() random value from 0 to 1
19 * rnd(n) random from 0 to n
20 * rnd(n, m) random between n and m
21 * rnd(array) random element
23 function rnd(a, b) {
24 switch (arguments.length) {
25 case 0:
26 return Math.random();
27 case 1:
28 if (a instanceof Array)
29 return a[Math.floor(Math.random() * a.length)];
30 else
31 return a * Math.random();
32 default: // case 2 actually
33 return a + (b - a) * Math.random();
37 function rndi() {
38 return Math.floor(rnd.apply(null, arguments));
41 </script>
43 <!-- Point -->
44 <script type="text/javascript">
46 function Point(x, y) {
47 this.x = x;
48 this.y = y;
51 Point.prototype.toString = function () { return this.x + ',' + this.y }
53 Point.prototype.equal = function (p) {
54 return this.x == p.x && this.y == p.y;
57 Point.prototype.times = function (n) {
58 return new Point(this.x * n, this.y * n);
61 Point.prototype.add = function (dx, dy) {
62 return new Point(this.x + dx, this.y + dy);
65 Point.prototype.plus = function (p) {
66 return new Point(this.x + p.x, this.y + p.y);
69 </script>
71 <!-- Hexstore -->
72 <script type="text/javascript">
74 function GridPoint(i, j) {
75 this.i = i;
76 this.j = j;
79 var sq32 = Math.sqrt(3) / 2;
81 function world(i, j) {
82 if (arguments.length == 1) {
83 j = i.j;
84 i = i.i;
87 return new Point(j * sq32, i - j / 2);
90 function grid(x, y) {
91 if (arguments.length == 1) {
92 y = x.y;
93 x = x.x;
96 var j = x / sq32;
98 return new GridPoint(y + j / 2, j);
101 function MakeData(extremes, initFn) {
103 var minmax = extremes.reduce(function(current, point) {
104 var i = point.y;
105 var j = point.x;
107 return current == null
108 ? [i, j, i, j]
110 Math.min(current[0], i),
111 Math.min(current[1], j),
112 Math.max(current[2], i),
113 Math.max(current[3], j)
115 }, null);
117 var mini = minmax[0];
118 var minj = minmax[1];
119 var maxi = minmax[2];
120 var maxj = minmax[3];
122 // x, y (getStore(origin) -> 0 0)
123 var origin = [-mini, -minj];
125 // max i, max j
126 var n = maxi - mini + 1;
127 var m = maxj - minj + 1;
129 var data = [];
131 var i = -1;
132 while (++i < n) {
133 var row = [];
134 var j = -1;
135 while (++j < m) {
136 row.push(initFn(new GridPoint(i - origin[1], j - origin[0])));
139 data.push(row);
143 return [origin, data];
146 function MakeHexStore(extremes, initFn, cloneFn) {
147 var originData = MakeData(extremes, initFn);
149 return new HexStore(originData[0], originData[1], initFn, cloneFn);
152 function HexStore(origin, data, initFn, cloneFn) {
153 this.origin = origin;
154 this.data = data;
155 this.initFn = initFn;
156 this.cloneFn = cloneFn;
159 HexStore.prototype.set = function (pos, value) {
160 var n = this.data.length;
161 var m = this.data[0].length;
163 var xo = this.origin[0];
164 var yo = this.origin[1];
166 var j = pos.j + xo;
167 var i = pos.i + yo;
169 if (i >= 0 && j >= 0 && i < n && j < m) this.data[i][j] = value;
172 HexStore.prototype.forEach = function (processFn) {
174 var n = this.data.length;
175 var m = this.data[0].length;
177 var xo = this.origin[0];
178 var yo = this.origin[1];
180 var i = -1;
181 while (++i < n) {
182 var j = -1;
183 while (++j < m)
184 if (this.data[i][j] != null)
185 processFn(this.data[i][j], new GridPoint(i - yo, j - xo));
189 var dirdr = 0;
190 var dirur = 1;
191 var dirup = 2;
192 var dirul = 3;
193 var dirdl = 4;
194 var durdn = 5;
196 // dx dy
197 var dirsdeltas = [
198 [/*down-right*/ 1, 0],
199 [/*up-right*/ 1, 1],
200 [/*up*/ 0, 1],
201 [/*up-left*/ -1, 0],
202 [/*down-left*/ -1, -1],
203 [/*down*/ 0, -1]
206 function Adjacent(dir, cell) {
207 this.dir = dir;
208 this.cell = cell;
211 HexStore.prototype.get = function (pos, dir) {
212 var i = pos.i + this.origin[1];
213 var j = pos.j + this.origin[0];
215 var data = this.data;
216 var n = this.data.length;
217 var m = this.data[0].length;
219 var id = i + dirsdeltas[dir][1];
220 var jd = j + dirsdeltas[dir][0];
222 return (id >= 0 && jd >= 0 && id < n && jd < m) ? this.data[id][jd] : null
225 HexStore.prototype.adjacent = function (pos) {
227 var i = pos.i + this.origin[1];
228 var j = pos.j + this.origin[0];
230 var data = this.data;
231 var n = this.data.length;
232 var m = this.data[0].length;
234 return dirsdeltas.map(function (d, dir) {
235 var dx = d[0];
236 var dy = d[1];
238 var id = i + dy;
239 var jd = j + dx;
241 return new Adjacent(dir, (id >= 0 && jd >= 0 && id < n && jd < m) ? data[id][jd] : null);
246 HexStore.prototype.copy = function () {
248 var n = this.data.length;
249 var m = this.data[0].length;
251 var copy = [];
253 for (var i = 0; i < n; ++i) {
254 var row = [];
255 for (var j = 0; j < m; ++j) {
256 var val = this.data[i][j];
257 row.push(val == null ? null : this.cloneFn(val));
259 copy.push(row);
262 return new HexStore(this.origin, copy, this.initFn, this.cloneFn);
265 HexStore.prototype.nextGen = function (cellCellStoreStoreFunc) {
267 var newStore = this.copy();
269 var n = this.data.length;
270 var m = this.data[0].length;
272 var xo = this.origin[0];
273 var yo = this.origin[1];
275 for (var i = 0; i < n; ++i) {
276 for (var j = 0; j < m; ++j) {
277 var pos = new GridPoint(i - yo, j - xo);
278 cellCellStoreStoreFunc(this.data[i][j], newStore.data[i][j], pos, this, newStore);
282 return newStore;
284 </script>
286 <!-- Fullscreen canvas and drawhex -->
287 <script type="text/javascript">
288 function fullscreenCanvas(id) {
289 var c = window.document.getElementById(id);
290 var ctx = c.getContext('2d');
292 ctx.canvas.width = window.innerWidth;
293 ctx.canvas.height = window.innerHeight;
294 ctx.canvas.style.position = 'absolute';
295 ctx.canvas.style.top = 0;
296 ctx.canvas.style.left = 0;
298 return ctx;
301 var ctx = fullscreenCanvas('can');
302 var w = ctx.canvas.width;
303 var h = ctx.canvas.height;
305 var q = 1 / Math.sqrt(3);
307 ctx.pathHex = function (h) {
308 var dx = q * h / 2;
309 var dy = h / 2;
311 this.beginPath();
312 this.moveTo(2 * dx, 0);
313 this.lineTo(dx, -dy);
314 this.lineTo(-dx, -dy);
315 this.lineTo(-2 * dx, 0);
316 this.lineTo(-dx, dy);
317 this.lineTo(dx, dy);
318 this.closePath();
321 ctx.fillHex = function (x, y, h) {
322 this.save();
323 this.translate(x, y);
324 this.pathHex(h);
325 this.fill();
326 this.restore();
329 ctx.strokeHex = function (x, y, h) {
330 this.save();
331 this.translate(x, y);
332 this.pathHex(h);
333 this.stroke();
334 this.restore();
337 ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
338 ctx.scale(1, -1);
339 </script>
341 <!-- Supercell -->
342 <script type="text/javascript">
343 /* Hexagonal area with same tiling properties as underlying cells.
345 function SuperCell(x, y, rank) {
346 this.x = x;
347 this.y = y;
348 this.rank = rank;
351 SuperCell.prototype.getCorners = function () {
352 var r = this.rank;
354 return [new Point(2 * r, r - 1),
355 new Point(2 * r, r),
356 new Point(r, 2 * r),
357 new Point(r - 1, 2 * r),
358 new Point(-r + 1, r + 1),
359 new Point(-r, r),
360 new Point(-2 * r, -r),
361 new Point(-2 * r, -r - 1),
362 new Point(-r - 1, -2 * r),
363 new Point(-r, -2 * r),
364 new Point(r, -r),
365 new Point(r + 1, -r + 1)
369 SuperCell.prototype.contains = function (point) {
370 var x = point.j - this.x;
371 var y = point.i - this.y;
373 var a = this.rank * 3 + 1;
374 var b = this.rank * 3;
376 var leftup = 2 * x + b;
377 var top = (x + a) / 2;
378 var rightup = b - x;
379 var rightdown = 2 * x - a;
380 var bottom = (x - b) / 2;
381 var leftdown = -x - a;
383 return leftup >= y
384 && top >= y
385 && rightup >= y
386 && rightdown <= y
387 && bottom <= y
388 && leftdown <= y;
391 </script>
393 <!-- Colors -->
394 <script type="text/javascript">
396 // hue Chroma luma
397 function hcy2rgb(h, c, y, a) {
398 // 601
399 var r = .3;
400 var g = .59;
401 var b = .11;
403 var h0 = h;
404 h /= 60;
406 var k = (1 - Math.abs((h % 2) - 1));
408 var K = h < 1 ? r + k * g
409 : h < 2 ? g + k * r
410 : h < 3 ? g + k * b
411 : h < 4 ? b + k * g
412 : h < 5 ? b + k * r
413 : r + k * b;
415 var cmax = 1;
417 if (y <= 0 || y >= 1) cmax = 0;
418 else cmax *= K < y ? (y - 1) / (K - 1) : K > y ? y / K : 1;
419 //c *= cmax;
420 c = Math.min(c, cmax);
422 var x = c * k;
423 var rgb = h < 1 ? [c, x, 0]
424 : h < 2 ? [x, c, 0]
425 : h < 3 ? [0, c, x]
426 : h < 4 ? [0, x, c]
427 : h < 5 ? [x, 0, c]
428 : [c, 0, x];
430 var m = y - (r * rgb[0] + g * rgb[1] + b * rgb[2]);
432 var rgbdata = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
433 return 'rgba(' + (rgbdata[0] * 255).toFixed(0) + ',' + (rgbdata[1] * 255).toFixed(0) + ',' + (rgbdata[2] * 255).toFixed(0) + ', ' + (a || 1) + ')';
437 </script>
439 <!-- Balls -->
440 <script type="text/javascript">
441 function Ball(pos, r, v) {
442 this.pos = pos;
443 this.r = r;
444 this.v = v;
446 </script>
448 <script type="text/javascript">
450 var rank = 25;
451 var supercell = new SuperCell(0, 0, rank);
452 var extremes = supercell.getCorners();
454 function dist(p, q) {
455 var dx = (p.x - q.x);
456 var dy = (p.y - q.y);
457 return Math.sqrt(dx * dx + dy * dy);
460 var zero = new Point(0, 0);
462 var zoom = Math.min(w, h) / supercell.getCorners().reduce(function (max, c) { return Math.max(max, dist(zero, c)) }, 0)*sq32;
464 function CellValue(v) {
465 this.v = v;
468 function CloneCellValue(arg) {
469 return new CellValue(arg.v);
472 var tau = 2 * Math.PI;
473 var cos = Math.cos;
474 var sin = Math.sin;
476 var maxd = supercell.getCorners().reduce(function (max, c) { return Math.max(max, dist(zero, c)) }, 0) / 2;
478 var mind = maxd/2;
479 var minballs = 10;
480 var maxballs = 15;
481 var minr = maxd/15;
482 var maxr = maxd/3;
483 var balls = [];
485 // generate metaballs
486 for (var i = 0; i < rnd(minballs, maxballs) ; ++i) {
487 var a = rnd(tau);
488 var d = rnd(mind, maxd);
489 var pos = new Point(d * cos(a), d * sin(a));
490 a = rnd(tau);
491 var v = new Point(mind * cos(a) / 100, mind * sin(a) / 100);
492 var r = rnd(minr, maxr);
494 balls.push(new Ball(pos, r, v));
497 function MakeCellValue(arg) {
498 if (supercell.contains(arg)) {
499 return new CellValue(0);
502 return null;
505 var store = new MakeHexStore(supercell.getCorners(), MakeCellValue, CloneCellValue);
507 var ps = [
508 new Point(0, 0),
509 new Point(0, 0),
510 new Point(0, 1),
511 new Point(-sq32, .5),
512 new Point(-sq32, -.5)
515 function getk(a, b) {
516 a = a < 1 ? 0 : a;
519 var enableLepr = true;
520 var oneOnly = false;
522 function draw(store, clear) {
524 function z(a, b, c) {
525 return (a >=1 && b < 1 && c < 1) || (a < 1 && b >= 1 && c >= 1);
528 function calcpoint(a, b, k) {
529 var a = ps[a];
530 var b = ps[b];
532 return a.plus(b.plus(a.times(-1)).times(k))
535 function line(dc, dk, a1, a2, b1, b2, ka, kb) {
536 if (!enableLepr) ka = kb = .5;
538 var a = calcpoint(dk + a1, dk + a2, ka);
539 var b = calcpoint(dk + b1, dk + b2, kb);
541 ctx.beginPath();
542 ctx.moveTo(a.x, a.y);
543 ctx.lineTo(b.x, b.y);
544 ctx.stroke();
547 function getk(v0, v1){
548 return (1 - v0) / (v1 - v0);
551 function triangle(a, b, c, t, x) {
552 var va = (a == null ? 0 : a.v) / t;
553 var vb = (b == null ? 0 : b.v) / t;
554 var vc = (c == null ? 0 : c.v) / t;
556 a = va >= 1 ? 1 : 0;
557 b = vb >= 1 ? 1 : 0;
558 c = vc >= 1 ? 1 : 0;
560 if ((a == 0 && b == 0 && c == 0) || (a != 0 && b != 0 && c != 0)) return;
562 ctx.strokeStyle = hcy2rgb(360*t, 1, .5, 1);
564 if (z(a, b, c)) line(0, x, 0, 2, 0, 3, getk(va, vb), getk(va, vc));
565 else if (z(b, c, a)) line(1, x, 0, 2, 2, 3, getk(va, vb), getk(vb, vc));
566 else if (z(c, a, b)) line(2, x, 2, 3, 0, 3, getk(vb, vc), getk(va, vc));
571 function drawCell(cell, pos) {
572 var screen = world(pos);
573 var x = screen.x;
574 var y = screen.y;
576 ctx.save();
577 ctx.translate(x, y);
579 var a = store.get(pos, dirup);
580 var b = store.get(pos, dirul);
581 var c = store.get(pos, dirdl);
583 if (oneOnly) {
584 triangle(cell, a, b, .5, 0);
585 triangle(cell, b, c, .5, 1);
587 else {
588 var n = 10;
589 for (var i = 0; i < n; ++i) {
590 triangle(cell, a, b, i / (n - 1), 0);
591 triangle(cell, b, c, i / (n - 1), 1);
595 ctx.restore();
598 if(clear)
599 ctx.clearRect(-ctx.canvas.width / 2, -ctx.canvas.height / 2, ctx.canvas.width, ctx.canvas.height);
601 ctx.save();
602 ctx.scale(zoom, zoom);
603 var lw = ctx.lineWidth = 1 / zoom;
605 ctx.strokeStyle = '#fff';
606 store.forEach(drawCell);
608 ctx.restore();
611 var x = Math.PI / 3;
612 var a = 4 * Math.PI / 3;
613 var da = [a, a + x, a + 2 * x, a + 3 * x, a + 4 * x, a + 5 * x];
614 var ddd = rnd(Math.PI*2);
616 var csr = new Point(0, 0);
617 var frameNum = 500;
618 var frameNo = -1;
619 var frames = new Array(frameNum);
621 function fl(n) { return Math.floor(n); }
623 function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
624 function tick() {
625 store = store.nextGen(function (oldCell, newCell, pos, oldStore, newStore) {
627 if (oldCell != null) {
628 var p = world(pos);
629 var sum = balls.reduce(function (s, b) {
630 var d = dist(p, b.pos) / b.r / 2;
632 if (d > 1) return s;
634 return s + fade(1 - d);
635 }, 0);
637 newCell.v = sum;
642 balls.forEach(function (b) {
644 var nextpos = b.pos.plus(b.v);
646 while(dist(nextpos, zero) > maxd) {
647 var a = rnd(tau);
648 b.v = new Point(mind * cos(a) / 50, mind * sin(a) / 50);
649 nextpos = b.pos.plus(b.v);
652 b.pos = nextpos;
656 draw(store, true);
658 ddd += .01;
659 window.requestAnimationFrame(tick);
662 tick();
664 window.addEventListener('click', function () {
666 if (oneOnly == enableLepr)
667 oneOnly = !oneOnly;
668 else enableLepr = !enableLepr;
669 }, false);
671 </script>
672 </body>
673 </html>