File size: 5,239 Bytes
90a1397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>81 and a nought</title>
<style>
  html,body{margin:0;padding:0;background:#101010;color:#e8e6e3;overflow:hidden;
    font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
  canvas{display:block;width:100vw;height:100vh}
  a{position:fixed;left:50%;bottom:1.1rem;transform:translateX(-50%);z-index:2;
    color:#e8e6e3;text-decoration:none;border:1px solid #8467A9;border-radius:.4rem;
    padding:.7rem 1.9rem;letter-spacing:.22em;font-size:.78rem;background:#10101088;
    transition:background .25s,box-shadow .25s}
  a:hover{background:#8467A9;color:#fff;box-shadow:0 0 2.5rem -.5rem #8467A9}
</style>
<canvas id="c"></canvas>
<a href="https://jessebrown1980.github.io/asolaria-multiverse/">MULTIVERSE</a>
<script>
// 81 and a nought. Integer arithmetic only: every position, velocity and force below is a
// fixed-point integer, S = 1024. No float touches a body. Float appears once, at the
// boundary, to hand pixels to the canvas.
var S = 1024, N = 81, HALF = 40;
var c = document.getElementById('c'), g = c.getContext('2d'), W = 0, H = 0, DPR = 1;
function fit(){ DPR = Math.min(2, window.devicePixelRatio||1);
  W = c.width = (c.clientWidth*DPR)|0; H = c.height = (c.clientHeight*DPR)|0; }
addEventListener('resize', fit); fit();

// 27 red, 27 green, 27 blue -- 3 colour charges x 27 = 81 = 3^4 / 3.
var CH = [[224,64,56],[72,208,104],[80,128,232]];
var bx=[],by=[],bz=[],vx=[],vy=[],ch=[],tr=[];
var seed = 0x8467a937;
function rnd(){ seed ^= seed<<13; seed|=0; seed ^= seed>>>17; seed ^= seed<<5; seed|=0;
  return (seed>>>0); }
for (var i=0;i<N;i++){
  var k = i % 3, ring = 3 + ((i/3)|0) % 27;
  var ang = (rnd() % 6283) - 3141;                 // milliradians, integer
  var rad = (60 + ring*11) * S;
  bx[i] = ((rad * Math.cos(ang/1000))|0);          // laid out once, then integer forever
  by[i] = ((rad * Math.sin(ang/1000))|0);
  bz[i] = (rnd() % 27) - 13;                       // z is the squared wave: 27 bands
  var sp = 900 + (rnd()%600);
  vx[i] = ((-by[i]/ (rad/S||1) * sp / 900)|0)>>4;
  vy[i] = (( bx[i]/ (rad/S||1) * sp / 900)|0)>>4;
  ch[i] = k; tr[i] = [];
}
var G = 46, CORE = 1900, SOFT = 9*S, ZEB = 0;

function step(){
  for (var i=0;i<N;i++){
    var fx=0, fy=0;
    // the nought at the centre: the shared centre, one body the other 81 all feel
    var dx = -bx[i], dy = -by[i];
    var d2 = ((dx>>5)*(dx>>5) + (dy>>5)*(dy>>5)) + 64;
    var inv = (CORE*S)/d2|0;
    fx += (dx>>5)*inv>>7; fy += (dy>>5)*inv>>7;
    for (var j=0;j<N;j++){
      if (j===i) continue;
      var ex = bx[j]-bx[i], ey = by[j]-by[i];
      var e2 = ((ex>>6)*(ex>>6) + (ey>>6)*(ey>>6)) + 36;
      var f = (G*S)/e2|0;
      // colour charge: like repels, unlike attracts -- the triplet stays mixed
      if (ch[i]===ch[j]) f = -f;
      fx += (ex>>6)*f>>9; fy += (ey>>6)*f>>9;
    }
    vx[i] += fx; vy[i] += fy;
    if (vx[i]>9000) vx[i]=9000; if (vx[i]<-9000) vx[i]=-9000;
    if (vy[i]>9000) vy[i]=9000; if (vy[i]<-9000) vy[i]=-9000;
  }
  for (var i=0;i<N;i++){
    bx[i] += vx[i]>>4; by[i] += vy[i]>>4;
    bz[i] = ((bz[i]+1+13)%27)-13;                  // the squared wave marches
    var t = tr[i]; t.push(bx[i], by[i]);
    if (t.length > 44) t.splice(0,2);
  }
  ZEB = (ZEB+1)%54;
}

function draw(){
  var cx = W>>1, cy = H>>1, sc = Math.min(W,H)/ (HALF*S*2/1000) / 1000;
  g.fillStyle = '#101010'; g.fillRect(0,0,W,H);
  // the zebra: z is the squared wave, so depth is 27 hard bands, never a gradient
  for (var b=0;b<27;b++){
    if (((b + ((ZEB/2)|0)) % 2) === 0) continue;
    var yy = (b*H/27)|0;
    g.fillStyle = 'rgba(255,255,255,0.014)';
    g.fillRect(0, yy, W, (H/27)|0);
  }
  for (var i=0;i<N;i++){
    var col = CH[ch[i]], t = tr[i];
    var band = (bz[i]+13);                          // 0..26, the squared wave
    var lit = 0.30 + 0.70*((band%2)===0 ? 1 : 0.34);
    g.strokeStyle = 'rgba('+col[0]+','+col[1]+','+col[2]+','+(0.16*lit).toFixed(3)+')';
    g.lineWidth = DPR;
    g.beginPath();
    for (var k=0;k<t.length;k+=2){
      var X = cx + t[k]*sc, Y = cy + t[k+1]*sc;
      if (k===0) g.moveTo(X,Y); else g.lineTo(X,Y);
    }
    g.stroke();
    var X = cx + bx[i]*sc, Y = cy + by[i]*sc;
    var r = (1.6 + band*0.10) * DPR * lit;
    g.fillStyle = 'rgba('+col[0]+','+col[1]+','+col[2]+','+lit.toFixed(3)+')';
    g.beginPath(); g.arc(X,Y,r,0,6.2832); g.fill();
  }
  // the omega sphere, and the 1
  var R = Math.min(W,H)*0.052;
  var gr = g.createRadialGradient(cx-R*0.3, cy-R*0.35, R*0.1, cx, cy, R);
  gr.addColorStop(0,'#b9a3d8'); gr.addColorStop(0.55,'#8467A9'); gr.addColorStop(1,'#2b2140');
  g.fillStyle = gr; g.beginPath(); g.arc(cx,cy,R,0,6.2832); g.fill();
  g.strokeStyle = 'rgba(240,240,16,0.55)'; g.lineWidth = DPR;
  g.beginPath(); g.arc(cx,cy,R*1.34,0,6.2832); g.stroke();
  g.fillStyle = '#f4f1ea'; g.textAlign='center'; g.textBaseline='middle';
  g.font = (R*1.02|0)+'px ui-monospace,Menlo,Consolas,monospace';
  g.fillText('Ω', cx, cy - R*0.06);
  g.font = (R*0.52|0)+'px ui-monospace,Menlo,Consolas,monospace';
  g.fillStyle = '#101010';
  g.fillText('1', cx, cy + R*0.62);
}
(function loop(){ step(); draw(); requestAnimationFrame(loop); })();
</script>