Rubik's cube 5x5x5 edgeswap added.
[zzandy.git] / forcefield.html
blob6d189cc58e851f8d74807847293b2b35f1f8f1e6
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>forcefield</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 // dx dy
190 var dirsdeltas = [
191 [/*down-right*/ 1, 0],
192 [/*up-right*/ 1, 1],
193 [/*up*/ 0, 1],
194 [/*up-left*/ -1, 0],
195 [/*down-left*/ -1, -1],
196 [/*down*/ 0, -1]
199 function Adjacent(dir, cell) {
200 this.dir = dir;
201 this.cell = cell;
204 HexStore.prototype.adjacent = function (pos) {
206 var i = pos.i + this.origin[1];
207 var j = pos.j + this.origin[0];
209 var data = this.data;
210 var n = this.data.length;
211 var m = this.data[0].length;
213 return dirsdeltas.map(function (d, dir) {
214 var dx = d[0];
215 var dy = d[1];
217 var id = i + dy;
218 var jd = j + dx;
220 return new Adjacent(dir, (id >= 0 && jd >= 0 && id < n && jd < m) ? data[id][jd] : null);
225 HexStore.prototype.copy = function () {
227 var n = this.data.length;
228 var m = this.data[0].length;
230 var copy = [];
232 for (var i = 0; i < n; ++i) {
233 var row = [];
234 for (var j = 0; j < m; ++j) {
235 var val = this.data[i][j];
236 row.push(val == null ? null : this.cloneFn(val));
238 copy.push(row);
241 return new HexStore(this.origin, copy, this.initFn, this.cloneFn);
244 HexStore.prototype.nextGen = function (cellCellStoreStoreFunc) {
246 var newStore = this.copy();
248 var n = this.data.length;
249 var m = this.data[0].length;
251 var xo = this.origin[0];
252 var yo = this.origin[1];
254 for (var i = 0; i < n; ++i) {
255 for (var j = 0; j < m; ++j) {
256 var pos = new GridPoint(i - yo, j - xo);
257 cellCellStoreStoreFunc(this.data[i][j], newStore.data[i][j], pos, this, newStore);
261 return newStore;
263 </script>
265 <!-- Fullscreen canvas and drawhex -->
266 <script type="text/javascript">
267 function fullscreenCanvas(id) {
268 var c = window.document.getElementById(id);
269 var ctx = c.getContext('2d');
271 ctx.canvas.width = window.innerWidth;
272 ctx.canvas.height = window.innerHeight;
273 ctx.canvas.style.position = 'absolute';
274 ctx.canvas.style.top = 0;
275 ctx.canvas.style.left = 0;
277 return ctx;
280 var ctx = fullscreenCanvas('can');
281 var w = ctx.canvas.width;
282 var h = ctx.canvas.height;
284 var q = 1 / Math.sqrt(3);
286 ctx.pathHex = function (h) {
287 var dx = q * h / 2;
288 var dy = h / 2;
290 this.beginPath();
291 this.moveTo(2 * dx, 0);
292 this.lineTo(dx, -dy);
293 this.lineTo(-dx, -dy);
294 this.lineTo(-2 * dx, 0);
295 this.lineTo(-dx, dy);
296 this.lineTo(dx, dy);
297 this.closePath();
300 ctx.fillHex = function (x, y, h) {
301 this.save();
302 this.translate(x, y);
303 this.pathHex(h);
304 this.fill();
305 this.restore();
308 ctx.strokeHex = function (x, y, h) {
309 this.save();
310 this.translate(x, y);
311 this.pathHex(h);
312 this.stroke();
313 this.restore();
316 ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
317 ctx.scale(1, -1);
318 </script>
320 <!-- Supercell -->
321 <script type="text/javascript">
322 /* Hexagonal area with same tiling properties as underlying cells.
324 function SuperCell(x, y, rank) {
325 this.x = x;
326 this.y = y;
327 this.rank = rank;
330 SuperCell.prototype.getCorners = function () {
331 var r = this.rank;
333 return [new Point(2 * r, r - 1),
334 new Point(2 * r, r),
335 new Point(r, 2 * r),
336 new Point(r - 1, 2 * r),
337 new Point(-r + 1, r + 1),
338 new Point(-r, r),
339 new Point(-2 * r, -r),
340 new Point(-2 * r, -r - 1),
341 new Point(-r - 1, -2 * r),
342 new Point(-r, -2 * r),
343 new Point(r, -r),
344 new Point(r + 1, -r + 1)
348 SuperCell.prototype.contains = function (point) {
349 var x = point.j - this.x;
350 var y = point.i - this.y;
352 var a = this.rank * 3 + 1;
353 var b = this.rank * 3;
355 var leftup = 2 * x + b;
356 var top = (x + a) / 2;
357 var rightup = b - x;
358 var rightdown = 2 * x - a;
359 var bottom = (x - b) / 2;
360 var leftdown = -x - a;
362 return leftup >= y
363 && top >= y
364 && rightup >= y
365 && rightdown <= y
366 && bottom <= y
367 && leftdown <= y;
370 </script>
372 <script type="text/javascript">
374 // hue Chroma luma
375 function hcy2rgb(h, c, y, a) {
376 // 601
377 var r = .3;
378 var g = .59;
379 var b = .11;
381 var h0 = h;
382 h /= 60;
384 var k = (1 - Math.abs((h % 2) - 1));
386 var K = h < 1 ? r + k * g
387 : h < 2 ? g + k * r
388 : h < 3 ? g + k * b
389 : h < 4 ? b + k * g
390 : h < 5 ? b + k * r
391 : r + k * b;
393 var cmax = 1;
395 if (y <= 0 || y >= 1) cmax = 0;
396 else cmax *= K < y ? (y - 1) / (K - 1) : K > y ? y / K : 1;
397 //c *= cmax;
398 c = Math.min(c, cmax);
400 var x = c * k;
401 var rgb = h < 1 ? [c, x, 0]
402 : h < 2 ? [x, c, 0]
403 : h < 3 ? [0, c, x]
404 : h < 4 ? [0, x, c]
405 : h < 5 ? [x, 0, c]
406 : [c, 0, x];
408 var m = y - (r * rgb[0] + g * rgb[1] + b * rgb[2]);
410 var rgbdata = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
411 return 'rgba(' + (rgbdata[0] * 255).toFixed(0) + ',' + (rgbdata[1] * 255).toFixed(0) + ',' + (rgbdata[2] * 255).toFixed(0) + ', ' + (a || 1) + ')';
415 </script>
417 <script type="text/javascript">
419 var rank = 15;
420 var supercell = new SuperCell(0, 0, rank);
421 var extremes = supercell.getCorners();
423 function CellValue(value) {
424 this.value = value;
427 function CloneCellValue(arg) {
428 return new CellValue(arg.value);
431 var cos = Math.cos;
432 var sin = Math.sin;
434 function MakeCellValue(arg) {
435 if (supercell.contains(arg)) {
436 var d = rnd(.1, .5);
437 var a = rnd(2 * Math.PI);
438 return new CellValue(new Point(d * cos(a), d * sin(a)));
441 return null;
444 var store = new MakeHexStore(supercell.getCorners(), MakeCellValue, CloneCellValue);
446 function draw(store) {
447 var amin = .3;
448 var amax = .9;
450 function cellColor(val, pos) {
451 if (val != null && val instanceof CellValue) {
452 var a = ((720 + Math.atan2(val.value.x, val.value.y) * 180 / Math.PI) % 360).toFixed();
453 var alpha = (amin + (amax - amin) * (val.value.x * val.value.x + val.value.y * val.value.y));
455 return hcy2rgb(a, 1, .5, alpha);
458 return 'transparent';
461 function drawCell(cell, pos) {
462 var screen = world(pos);
463 var x = screen.x;
464 var y = screen.y;
466 ctx.fillStyle = ctx.strokeStyle = cellColor(cell, pos);
468 ctx.save();
469 ctx.translate(x, y);
471 ctx.fillHex(0, 0, 1);
473 ctx.beginPath();
474 ctx.moveTo(0, 0);
476 var a = Math.atan2(cell.value.y, cell.value.x);
477 var dx = Math.cos(a);
478 var dy = Math.sin(a);
480 ctx.lineTo(dx / 2, dy / 2);
481 ctx.stroke();
482 ctx.restore();
485 ctx.clearRect(-ctx.canvas.width / 2, -ctx.canvas.height / 2, ctx.canvas.width, ctx.canvas.height);
487 ctx.save();
488 ctx.scale(1.3*rank,1.3* rank);
489 var lw = ctx.lineWidth = 1.5 / rank;
490 store.forEach(drawCell);
493 var first = true;
494 for (var i = 0; i < frameNum; ++i) {
495 var n = (frameNum + frameNo - i) % frameNum;
496 var frame = frames[n];
497 if (frame) {
499 if (first) {
500 first = false;
501 } else {
502 var q = 1 - i / frameNum;
503 ctx.strokeStyle = 'rgba(255,255,255,' + q + ')';
504 ctx.lineWidth= lw*q;
505 ctx.lineTo(frame.x, frame.y);
506 ctx.stroke();
509 ctx.beginPath();
510 ctx.moveTo(frame.x, frame.y);
514 ctx.restore();
517 var x = Math.PI / 3;
518 var a = 4 * Math.PI / 3;
519 var da = [a, a + x, a + 2 * x, a + 3 * x, a + 4 * x, a + 5 * x];
520 var ddd = rnd(Math.PI*2);
522 var csr = new Point(0, 0);
523 var frameNum = 500;
524 var frameNo = -1;
525 var frames = new Array(frameNum);
527 function fl(n) { return Math.floor(n); }
530 function dist(p, q) {
531 var dx = (p.x - q.x) ;
532 var dy = (p.y - q.y) ;
533 return Math.sqrt(dx * dx + dy * dy);
536 function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
537 function tick() {
538 var total = 0;
539 var force = new Point(0, 0);
541 var p = grid(csr);
542 var ix = fl(p.j);
543 var iy = fl(p.i);
544 var x1, x2, x3, y1, y2, y3;
546 if (p.j - ix <= p.i - iy) {
547 x1 = ix;
548 y1 = iy;
549 x2 = ix;
550 y2 = iy + 1;
551 x3 = ix + 1;
552 y3 = iy + 1;
554 else {
555 x1 = ix;
556 y1 = iy;
557 x2 = ix + 1;
558 y2 = iy;
559 x3 = ix + 1;
560 y3 = iy + 1;
563 store = store.nextGen(function (oldCell, newCell, pos, oldStore, newStore) {
565 if (oldCell != null) {
566 var zz = oldStore.adjacent(pos);
567 var adj = zz.filter(function (a) { return a.cell != null });
568 var extravalue = null;
570 if (adj.length == 6) {
571 if (pos.j == 0 && pos.i == 10) {
572 extravalue = 0;//ddd;
573 } else if (pos.j == 10 && pos.i == 10) {
574 extravalue = ddd - Math.PI / 3;
575 } else if (pos.j == 10 && pos.i == 0) {
576 extravalue = ddd - 2 * Math.PI / 3;
577 } else if (pos.j == 0 && pos.i == -10) {
578 extravalue = ddd - 3 * Math.PI / 3;
579 } else if (pos.j == -10 && pos.i == -10) {
580 extravalue = ddd - 4 * Math.PI / 3;
581 } else if (pos.j == -10 && pos.i == 0) {
582 extravalue = ddd - 5 * Math.PI / 3;
583 } else {
584 newCell.value = adj
585 .reduce(function (sum, el) { return sum == null ? el.cell.value : sum.plus(el.cell.value) }, null)
586 .times(1 / adj.length);
589 // wind
590 var windMaxAmount = .02*sin(3*ddd)*sin(5*ddd)*sin(7*ddd);
591 var windAngle = ddd * 2;
592 var wind = new Point(windMaxAmount * cos(windAngle), windMaxAmount * sin(windAngle));
594 newCell.value = newCell.value.plus(wind);
596 if (extravalue != null) {
597 var p = new Point(cos(extravalue), sin(extravalue));
598 var q = sin(ddd / 5) * sin(ddd / 5);
600 newCell.value = newCell.value.times(q).plus(p.times(1 - q));
602 } else {
603 var p = world(pos);
604 var a = Math.atan2(-p.y, -p.x);
605 var d = .8;
607 newCell.value = new Point(d * cos(a), d * sin(a));
610 total += newCell.value.x * newCell.value.x + newCell.value.y * newCell.value.y;
613 if ((pos.j == x1 && pos.i == y1) || (pos.j == x2 && pos.i == y2) || (pos.j == x3 && pos.i == y3)) {
614 var d = dist(world(pos), csr);
615 var k = fade(d);
616 force = force.plus(newCell.value.times(k));
621 csr = csr.plus(force);
622 frames[(++frameNo) % frameNum] = csr;
624 draw(store);
626 ddd += .01;
627 window.requestAnimationFrame(tick);
630 tick();
632 </script>
634 <!-- user input -->
635 <script type="text/javascript">
637 function InputManager(mapping) {
638 var $ = this;
639 this.action = null;
640 this.handler = function(event) { $.action = mapping[event.which] };
642 window.addEventListener('keydown', this.handler, false);
645 </script>
647 </body>
648 </html>