-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjs1k-2013-loom.html
293 lines (274 loc) · 20.8 KB
/
js1k-2013-loom.html
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
<!doctype html>
<html>
<head>
<title>JS1k, 1k demo submission [ID]</title>
<meta charset="utf-8" />
</head>
<body>
<canvas id="c"></canvas>
<script>
var b = document.body;
var c = document.getElementsByTagName('canvas')[0];
var a = c.getContext('2d');
document.body.clientWidth; // fix bug in webkit: http://qfox.nl/weblog/218
</script>
<script>
/*
// "BEAUTIFIED" SOURCE, made from the much dirtier original one (10kB limit here, so to take a look at the implementation process, or at the tricks used, check my website)
// Maps:
var mapWithWater=[], map=[], mapFaces=[],
dim = 2, // Size of our square matrix (dim = rows = cols)
SIZE = 20, // Width of our whole square landscape
MID = SIZE/2, // Mid-width, to place the origin
WATER_LVL = 6, // Sea level
BEACH_LVL = 6.04, // Beach level
SNOW_LVL_INC = 7, // Snow level, subtracting the incline (Yeah, sounds strange. See the code lines)
sgmtLength = SIZE, // Length of the sub-squares sides
h=8, // Alterations amplitude
wHeight, wWidth, // Window dim.
angleYaw=angleHead = .6,// Camera orientation
cameraDistance = 20, // Camera distance to the origin
wantMove = 0, // To know which mode is used (3=moving, 0=displaying)
refreshPaint; // Generated by the setTimeOut to refresh the canvas.
map[0]=[2,4,9,8]; // Initial description of our height-map...
// Generating the landscape with an increasing level of details :
for(var l=1;l<9;l++){ // Our most-detailed map (map[8]) will thus be a 257x257 matrix, which means 66049 elements / 131072 faces for our mesh. map[9] is kindda computable, but displaying it almost kills my browser though...
// First we generate the new details by adding points using stochastic interpolation.
var newDim=2*dim-1; // We want to add 1 new element between each couple of them, so the size will increase of dim-1.
mapWithWater[l]=[];
map[l]=[];
h /= 2; // Every iteration, we reduce the "error" amplitude, to generate more subtle alterations.
for (var i=newDim; i--;) for (var j=newDim; j--;) {
// We thus populate the new map from the bottom right element to the top-left. It affects the way we evaluate some elements value if we want to do it in only one step: we can use all the values of the old matrix, but only the values of the new elements which has a bigger index than the current one.
var iMap = i*newDim+j,
iSMap= dim*(i>>1)+j/2|0; // Index of the top-left corner of the square in the smaller matrix.
// JS trick: Math.floor(X) = 0|X if X positive, and X>>Y = 0|(X/(2*Y))
map[l][iMap]=mapWithWater[l][iMap]=
(i%2?
j%2?// Element on an odd row and odd col: it corresponds to a square center, so we populate it as shown in the following ascii schema:
// 2 0 0 with X representing the current element, and the numbers those used to eval. it.
// 0 X 1 It's kind of a mix between the midpoint displacement and the diamond-square algo: we try to reduce the artifacts
// 0 1 0 the 1st algo may generate, without the 2nd step required by the other.
(mapWithWater[l][iMap+newDim] + mapWithWater[l][iMap+1] + 2*map[l-1][iSMap]) / 4
: // Element on an odd row and even col: we give it the avg of the elements on the prev. and next cols:
// 1 0 0 Example
// X 0 0
// 1 0 0
(map[l-1][iSMap] + map[l-1][iSMap+dim]) / 2
:
j%2?// Element on an even row and odd col: we give it the avg of the elements on the prev. and next rows:
// 1 X 1 Example
// 0 0 0
// 0 0 0
(map[l-1][iSMap] + map[l-1][iSMap+1]) / 2
: // Element on an even row and even col: it's one of the square corners, so we just give it the orig. val.:
map[l-1][iSMap]
) + h * (.6 - Math.random()); // And for each element, we add a small error to generate our random landscape. Our constant h controls the ampli. of this error. As h is divided by 2 every iteration, so is this coefficient. The first iterations thus generates the "rough geography" of our landscape while the last ones, with a smaller amplitude, add the details.
mapWithWater[l][iMap] = (mapWithWater[l][iMap] < WATER_LVL)? WATER_LVL:mapWithWater[l][iMap];// To get our flat water surface
}
// Then we make a mesh out of our height map: we take each square defining our map and cut them into 2 triangles, which will become 2 faces of our mesh. Each element, at the position (i,j) of our matrix and with the value z, will generate a 3D-vertex (X=i*r-A, Y=j*r-B, Z=z-C), with (A,B,C) the chosen origin (we take A=B=mapSize/2 and C=0, so the center of the map corresponds to the origin).
// We also use this step to evaluate the color of the faces as we generate them, using their height and a rough evaluation of their inclination as parameters.
dim=newDim--; // We do "newDim--" right now not to repeat "newDim-1" later.
sgmtLength/=2; // We want to increase the level of details of our map, not its size. So with each it., we divide by 2 the distance separating the vertices.
mapFaces[l]=[];
var trId = 0;
// As previously, we iterate desc. The idea is that for each element we iterate on, we evaluate the faces of the square which as for top-left corner this element. So we don't want to iterate on the last column and last row. We thus start at the prev. ones (reason why we decreased newDim by 1):
for (var i=newDim; i--;) {
// X --- o Schema representing the faces extracted from a square. X is the current element.
// | \ |
// o --- o
for (j = newDim; j--;) for (k=2;k--;) { // We iterate 2 times for each square in order to generate the 2 corresponding triangle-faces.
var elId = dim*i+j;
mapFaces[l][trId] = [
// 1st vertex
m=i*sgmtLength-MID,
q=j*sgmtLength-MID,
mapWithWater[l][elId],
// 2nd vertex - the one varying (either corresponding to the element on the next row, or the one on the next col). We use a modulo 2 on the face's id to select the good one.
(i + k)*sgmtLength-MID, // (trId+1)%2 = k. And we don't mind which triangle we generate first, so k is cheaper.
(j + 1-k)*sgmtLength-MID,
mapWithWater[l][elId + 1 + k*newDim], // elId + ((trId%2)? 1 : dim) -> elId + 1 + k*newDim
// 3rd vertex
m+sgmtLength, // m+sgmtLength = (i+1)*sgmtLength-MID
q+sgmtLength, // q+sgmtLength = (j+1)*sgmtLength-MID
mapWithWater[l][elId+dim+1]];
// The following code is a personal mishmash to evaluate the color, found after much tweaking.
// The basic idea was at least to represent:
// - the sea (blue), the vegetation (green), the snow (white), and some intermediate hues (alpine, meadow, beach,...)
// - the luminosity depending on the surface normal and sun position. Since computing the cross product costs too much bytes, only the face incline is used to evaluate it, relative to a fixed implicit sun position.
// For that, the HSL color system is perfect, separating hue and lightness. And for free, by playing with the saturation, we can get some nice vegetation effects! Moreover, when setting "hsl(H,S%,L%)", the values H, S and L don't need to be rounded (would have cost us some bytes - Though I have to check if it influences the performances...).
// So we use the face height (approximated using only 1 vertex), the slope (approximated as shown above), some tweaking, and voila!
var z2 = mapFaces[l][trId][5],
z3 = mapFaces[l][trId][8],
incli = (z2-z3)/sgmtLength; // Really cheap way to get a value proportional to the inclination, since we know the X and Y repartition is regular. At leat, by using the height of the 2nd vertex, we can get a different value for each face.
incCoef = 2+incli, // The tweaking...
zCol = z3 - incCoef; // Useful value to make the boundary snow/vegetation less obvious/more random, and more importantly to somehow reduce the melting-point for poorly-lighted faces. Or how to cheaply simulate the illumination effect on snow.
mapFaces[l][trId].push('hsl('+[ // Using coercion for the ","
99*( // Hue
(WATER_LVL<z2)? 6/z2 // Greenish
: 2 // Blue
),
32*( // Saturation
(WATER_LVL<z2)? incCoef*2/zCol // Ground saturation: medium, depending on the incline/height, giving us different kinds of veget.
: zCol // Water saturation: high
)+'%',
30*( // Lightness
(SNOW_LVL_INC<zCol)? incCoef/.6 // Bright snow
: (BEACH_LVL<z2)? incCoef*incCoef/7 // Normal vegetation
: (WATER_LVL<z2)? incCoef/.6 // Bright sand
: 2-Math.random()/3 // Water with random waves
)
]+'%)');
trId++;
}
}
}
function Paint(){ // See my other demo for details
a.fillRect(0, 0, wWidth=c.width=innerWidth-21,wHeight=c.height=innerHeight-21);
wWidth/=2; // We don't divide the height, to emphasize it.
var sCoord = [],
m=-Math.cos(angleYaw),w=-Math.sin(angleYaw),o=Math.cos(angleHead),x=Math.sin(angleHead),
wantMap = mapFaces[8-wantMove];
for (l in wantMap) {
sCoord[l] = []
for (j = 9;j;) {
var v=wantMap[l][--j]-cameraDistance*o, u=wantMap[l][--j]-cameraDistance*m, t=wantMap[l][--j]-cameraDistance*w;
sCoord[l].push(z = o*v+ (k=m*u+w*t)*x,(v*x-o*k)/z*wHeight + wHeight,(w*u-m*t)/z*wWidth + wWidth);
}
sCoord[l][9] = mapFaces[8-wantMove][l][9]
}
sCoord.sort(function(H,S,L){return H[3]-S[3]}); // Sorting
for (l in sCoord) // I had to choose between only stroking the triangles, only filling them, or sacrificing another feature. I found the holes left by only stroking interesting, like a reminder of those which are part of the universe fabric. ;-)
//a.fillStyle =
a.beginPath(a.strokeStyle=sCoord[l][9]),
a.moveTo(sCoord[l][8], sCoord[l][7]),
a.lineTo(sCoord[l][5], sCoord[l][4]),
a.lineTo(sCoord[l][2], sCoord[l][1]),
a.closePath(),
//a.fill(),
a.stroke()
//refreshPaint=wantMove? setTimeout(Paint,16) : 0;
};
// Drag to move around:
b.onmousedown=function(){
wantMove=3;
clearInterval(refreshPaint); refreshPaint=setInterval(Paint,16);
} // We have to clear the Interval in the case the user release the click out of the body.
b.onmouseup=function(){
wantMove=0;
clearInterval(refreshPaint); Paint();
}
b.onmousemove = function(H) { // We tie the mouse position with the camera orientation.
angleYaw = 2*H.clientX/wWidth;
angleHead = 2*H.clientY/wHeight
}
b.onmousewheel=b.onwheel = function(H) { // Zooming with the wheel, cross-browser solution
cameraDistance += H.deltaY | -H.wheelDeltaY/40;
}
Paint();
*/
/*
How to minify the demo? Tricks I learned ...
- Closure Compiler and JsCrush are your friends
- Check github.com/jed/140bytes/wiki/Byte-saving-techniques to learn more dirty tricks.
- Omit "var", noone will mind
- Inline when possible (don't use variables for 1-char-long const values, limit the number of functions, ...)
- Limit the size of your const (use string instead of array, 9 instead of 10, ...)
- Save delimiters by doing stuff within unused function or loop arguments
- If/else can be replaced by ternary assignations or bitewise operations
- Use the fact that the operator "=" assigns a value but also returns it
- Learn operators precedence to get rid of parentheses.
- To make the best out of JsCrush:
- Create patterns in your code:
- Limit the number of variables, and reuse them smartly (ex: if you keep using the variable A for arrays and i for the indices, every expression "A[i]" will be crushed, saving you 3n-7 bytes, with n the number of times this expression appears)
- Rearrange your expressions to create new pattern, by ordering the symbols appearances (ex: rearrange "m=i*r-9;q=r*j-9;" into "m=i*r-9;q=j*r-9;" to make the pattern "*r-9;" appears)
- Use exactly same function signatures (take the longest one and assign it to every function)
- Check the output of Closure Compile beforehand. It may have altered your signatures or symbols arrangements. It also adds a "useless" semicolon at the end of your script.
- Save 2 bytes by changing the jsCrush bootstrap: replace "for(Y=0;$='CHAR'[Y++];)with(_.split($))_=join(pop());" by "for(Y in $='CHAR')with(_.split($[Y]))_=join(pop());"
- Adapt your equations to the context to avoid useless operations or steps / Look at the whole picture
*/
/*
// Real code as implemented:
d=[],g=[], D=[], r=R=20,Y=.6
// Generating the landscape with various levels of details :
g[T=l=0]=[o=2,4,Z=9,h=8]//.sort(function(){return .5-Math.random()}) // Initial description of our map : a hole, a bump, and some stuff between ...
for(;l<8;){
i=p = 2*o-1,d[++l] = [], g[l]=[], h /= 2
for (; i--;)
for (j=p; j--;
v=g[l-1][u=o*(i/2|0)+j/2|0], w=i*p+j, g[l][w]=d[l][w] = (i%2?
j%2? (d[l][w+p]+d[l][w+1]+2*v)/4:(v+g[l-1][u+o])/2:
j%2? (v+g[l-1][u+1])/2:v)+h*(Y-Math.random()),
d[l][w]=d[l][w] < 6?6:d[l][w]);
//o=p,i=--p,
o=i=p--,i--, // Smaller once jsCrushed
r/=2,D[l] = []
for (; i--;)
for (j = p; j--;) {
for (k=2;k--;
D[l][Z++] = [
m=i*r-9,
q=j*r-9,
d[l][u=o*i+j],
(i+k)*r-9, // Z%2 = k
(j+1-k)*r-9,
z=d[l][u+1+k*p], // u+1+k*p = u+(Z%2)?1:o
m+r,
q+r,
y=d[l][u+o+1],
x=2+(z-y)/r,
'hsl('+[99*(7<(y-=x)?1:6<z?7/z:2),32*(6<z?x*2/y:y)+'%',44*(7<y?x:6.05<z?x/4:6<z?x:1-Math.random()/5)]+'%)']);
}
}
e=function(H,S,L){
a.fillRect(0, 0, h=c.width=innerWidth-17,r=c.height=innerHeight-21),
m=-Math.cos(Z),w=-Math.sin(Z),o=Math.cos(Y),x=Math.sin(Y),d = [], h/=2
for (l in D[8-T]) {
d[l] = []
for (j = 9;j;
v=D[8-T][l][--j]-R*o, u=D[8-T][l][--j]-R*m, t=D[8-T][l][--j]-R*w,
d[l].push(z = o*v+ (k=m*u+w*t)*x,(v*x-o*k)/z*r + r,(w*u-m*t)/z*h + h));
d[l][9] = D[8-T][l][10]
}
d.sort(function(H,S,L){return H[3]-S[3]})
for (l in d)
//a.fillStyle =
a.beginPath(a.strokeStyle=d[l][9]),
a.moveTo(d[l][8], d[l][7]),
a.lineTo(d[l][5], d[l][4]),
a.lineTo(d[l][2], d[l][1]),
a.lineTo(d[l][8], d[l][7]), // Smaller than closePath once jsCrushed
//a.fill(),
a.stroke()
//o=T?setTimeout(e,16):0
};
//b.onclick=function(H,S,L){clearTimeout(o),e(T^=3)}
b.onmousedown=function(H,S,L){T=3,o=setInterval(e,16)}
b.onmouseup=function(H,S,L){T=0,clearTimeout(o),e()}
b.onmousemove=function(H,S,L){Z=2*H.clientX/h,Y=2*H.clientY/r}
//b.onmousewheel=function(H,S,L){
// R+=H.wheelDelta/99} // Not supported by FF, alas...
//b.onkeydown=function(H,S,L){
// R+=(H.which==40)-(H.which==38)} // ... so we use the arrows instead. Less elegant, and 6B heavier.
b.onmousewheel=b.onwheel=function(H,S,L){
R+=H.deltaY|-H.wheelDeltaY/40} // Cross-browser solution, but to heavy ... --Update: Now we can afford it! \o/
e()
*/
/* Minimized + Spherical Movement + Rand for hue + No more "b." + better pattern for click handlers + d[l]->d + Z+3 + Trigo infine
d=[];g=[];R=r=22;Y=.6;g[l=0]=[o=2,5,Z=9,T=h=8];for(D=[];8>l;){i=p=2*o-1;d=[];h/=2;for(g[++l]=[];i--;)for(j=p;j--;v=g[l-1][u=o*(i/2|0)+j/2|0],w=i*p+j,g[l][w]=d[w]=(i%2?j%2?(v*2+d[w+p]+d[w+1])/4:(v+g[l-1][u+o])/2:j%2?(v+g[l-1][u+1])/2:v)+h*(Y-Math.random()),d[w]=6>d[w]?6:d[w]);o=i=p--;i--;r/=2;for(D[l]=[];i--;)for(j=p;j--;)for(k=2;k--;D[l][Z++]=[i*r-11,j*r-11,d[u=o*i+j],(i+k)*r-11,(j+1-k)*r-11,z=d[u+1+k*p],(i+1)*r-11,(j+1)*r-11,y=d[u+o+1],y-=x=2+(z-y)/r,"hsla("+[32*(6<z?R/z-Math.random():6),32*(6<z?x*2/y:y)+"%",32*(6<y-1?x/Y:6.05<z?x*x/7:6<z?x:2-Math.random()/3)]+"%,"]);}e=function(f,n){a.fillRect(0,0,h=c.width=innerWidth-18,r=c.height=innerHeight-21);m=-Math.cos(Z);w=-Math.sin(Z);o=Math.cos(Y);x=Math.sin(Y);g=[];h/=2;for(l in D[T]){g[l]=[];for(j=9;j;v=D[T][l][--j]-R*o,u=D[T][l][--j]-R*m,t=D[T][l][--j]-R*w,g[l].push(p=o*v+(k=m*u+w*t)*x,(v*x-o*k)/p*r+r,(w*u-m*t)/p*h+h));g[l][9]=D[T][l][10]}p=-l*p/40;g.sort(function(f,n){return f[3]-n[3]});for(l in g)a.strokeStyle=g[l][9]+l/p+")",a.beginPath(),a.moveTo(g[l][8],g[l][7]),a.lineTo(g[l][2],g[l][1]),a.lineTo(g[l][5],g[l][4]),a.lineTo(g[l][8],g[l][7]),a.stroke()};onmousedown=function(f,n){clearInterval(i),T=5,i=setInterval(e,9)};onmouseup=function(f,n){clearInterval(i),T=8,e()};onmousemove=function(f,n){Z=f.clientX/h;Y=f.clientY/r};onmousewheel=onwheel=function(f,n){R+=f.deltaY|-f.wheelDeltaY/80};e()
*/
/* Melting Snow Version:
d=[];g=[];R=r=21;Y=.6;g[l=0]=[o=2,5,Z=9,T=h=8];for(D=[];8>l;){i=p=2*o-1;d[++l]=[];h/=2;for(g[l]=[];i--;)for(j=p;j--;v=g[l-1][u=o*(i/2|0)+j/2|0],w=i*p+j,g[l][w]=d[l][w]=(i%2?j%2?(v*2+d[l][w+p]+d[l][w+1])/4:(v+g[l-1][u+o])/2:j%2?(v+g[l-1][u+1])/2:v)+h*(Y-Math.random()),d[l][w]=6>d[l][w]?6:d[l][w]);o=i=p--;i--;r/=2;for(D[l]=[];i--;)for(j=p;j--;){for(k=2;k--;D[l][Z++]=[i*r-9,j*r-9,d[l][u=o*i+j],(i+k)*r-9,(j+1-k)*r-9,d[l][u+1+k*p],(i+1)*r-9,(j+1)*r-9,d[l][u+o+1]]);}}e=function(f,n){a.fillRect(0,0,h=c.width=innerWidth-18,r=c.height=innerHeight-21);m=-Math.cos(Z);w=-Math.sin(Z);o=Math.cos(Y);x=Math.sin(Y);d=[];h/=2;for(l in D[T]){d[l]=[];for(j=9;j;v=D[T][l][--j]-R*o,u=D[T][l][--j]-R*m,t=D[T][l][--j]-R*w,d[l].push(p=o*v+(k=m*u+w*t)*x,(v*x-o*k)/p*r+r,(w*u-m*t)/p*h+h));v=D[T][l][5];u=D[T][l][8];u-=t=2+(v-u)*T;d[l][9]="hsl("+[32*(6<v?22/v:6),32*(6<v?t*2/u:u)+"%",32*(6<u-Math.sin(R/9)?t/Y:6.05<v?t*t/7:6<v?t:2-Math.random()/3)]+"%)"}d.sort(function(f,n){return f[3]-n[3]});for(l in d)a.strokeStyle=d[l][9],a.beginPath(),a.moveTo(d[l][8],d[l][7]),a.lineTo(d[l][2],d[l][1]),a.lineTo(d[l][5],d[l][4]),a.lineTo(d[l][8],d[l][7]),a.stroke()};b.onmousedown=function(f,n){T=5,clearInterval(i),i=setInterval(e,16)};b.onmouseup=function(f,n){clearInterval(i),e(T=8)};b.onmousemove=function(f,n){Z=2*f.clientX/h;Y=2*f.clientY/r};b.onmousewheel=b.onwheel=function(f,n){R+=f.deltaY|-f.wheelDeltaY/40};e()
*/
/* Mild Version:
d=[];g=[];R=r=22;Y=.6;g[T=l=0]=[o=2,4,Z=8,h=7];for(D=[];8>l;){i=p=2*o-1;d[++l]=[];h/=2;for(g[l]=[];i--;)for(j=p;j--;v=g[l-1][u=o*(i/2|0)+j/2|0],w=i*p+j,g[l][w]=d[l][w]=(i%2?j%2?(v*2+d[l][w+p]+d[l][w+1])/4:(v+g[l-1][u+o])/2:j%2?(v+g[l-1][u+1])/2:v)+h*(Y-Math.random()),d[l][w]=5>d[l][w]?5:d[l][w]);o=i=p--;i--;r/=2;for(D[l]=[];i--;)for(j=p;j--;){for(k=2;k--;D[l][Z++]=[i*r-11,j*r-11,d[l][u=o*i+j],(i+k)*r-11,(j+1-k)*r-11,z=d[l][u+1+k*p],(i+1)*r-11,(j+1)*r-11,y=d[l][u+o+1],y-=x=1+(z-y)/r*.7,"hsl("+[99*(5<z?6/z:2),55*(5<z?x/y*2:y)+"%",85*(6<y?x:5.04<z?x/3:5<z?x:.6-Math.random()/8)]+"%)"]);}}e=function(f,n){a.fillRect(0,0,h=c.width=innerWidth-18,r=c.height=innerHeight-21);m=-Math.cos(Z);w=-Math.sin(Z);o=Math.cos(Y);x=Math.sin(Y);d=[];h/=2;for(l in D[8-T]){d[l]=[];for(j=9;j;v=D[8-T][l][--j]-R*o,u=D[8-T][l][--j]-R*m,t=D[8-T][l][--j]-R*w,d[l].push(z=o*v+(k=m*u+w*t)*x,(v*x-o*k)/z*r+r,(w*u-m*t)/z*h+h));d[l][9]=D[8-T][l][10]}d.sort(function(f,n){return f[3]-n[3]});for(l in d)a.strokeStyle=d[l][9],a.beginPath(),a.moveTo(d[l][8],d[l][7]),a.lineTo(d[l][5],d[l][4]),a.lineTo(d[l][2],d[l][1]),a.lineTo(d[l][8],d[l][7]),a.stroke()};b.onmousedown=function(f,n){T=3,clearInterval(i),i=setInterval(e,16)};b.onmouseup=function(f,n){T=0,clearInterval(i),e()};b.onmousemove=function(f,n){Z=2*f.clientX/h;Y=2*f.clientY/r};b.onmousewheel=b.onwheel=function(f,n){R+=f.deltaY|-f.wheelDeltaY/40};e()
*/
/* High Contrast Version:
d=[];g=[];R=r=22;Y=.6;g[T=l=0]=[o=2,4,Z=8,h=7];for(D=[];8>l;){i=p=2*o-1;d[++l]=[];h/=2;for(g[l]=[];i--;)for(j=p;j--;v=g[l-1][u=o*(i/2|0)+j/2|0],w=i*p+j,g[l][w]=d[l][w]=(i%2?j%2?(v*2+d[l][w+p]+d[l][w+1])/4:(v+g[l-1][u+o])/2:j%2?(v+g[l-1][u+1])/2:v)+h*(Y-Math.random()),d[l][w]=5>d[l][w]?5:d[l][w]);o=i=p--;i--;r/=2;for(D[l]=[];i--;)for(j=p;j--;){for(k=2;k--;D[l][Z++]=[i*r-11,j*r-11,d[l][u=o*i+j],(i+k)*r-11,(j+1-k)*r-11,z=d[l][u+1+k*p],(i+1)*r-11,(j+1)*r-11,y=d[l][u+o+1],x=1+(z-y)/r,"hsl("+[99*(6<(y-=x)?1:5<z?7/z:2),64*(5<z?x*2/y:y)+"%",75*(6<y?x:5.04<z?x/3:5<z?x:Y-Math.random()/9)]+"%)"]);}}e=function(f,n){a.fillRect(0,0,h=c.width=innerWidth-18,r=c.height=innerHeight-21);m=-Math.cos(Z);w=-Math.sin(Z);o=Math.cos(Y);x=Math.sin(Y);d=[];h/=2;for(l in D[8-T]){d[l]=[];for(j=9;j;v=D[8-T][l][--j]-R*o,u=D[8-T][l][--j]-R*m,t=D[8-T][l][--j]-R*w,d[l].push(z=o*v+(k=m*u+w*t)*x,(v*x-o*k)/z*r+r,(w*u-m*t)/z*h+h));d[l][9]=D[8-T][l][10]}d.sort(function(f,n){return f[3]-n[3]});
for(l in d)a.strokeStyle=d[l][9],a.beginPath(),a.moveTo(d[l][8],d[l][7]),a.lineTo(d[l][5],d[l][4]),a.lineTo(d[l][2],d[l][1]),a.lineTo(d[l][8],d[l][7]),a.stroke()};b.onmousedown=function(f,n){T=3,clearInterval(i),i=setInterval(e,16)};b.onmouseup=function(f,n){T=0,clearInterval(i),e()};b.onmousemove=function(f,n){Z=2*f.clientX/h;Y=2*f.clientY/r};b.onmousewheel=b.onwheel=function(f,n){R+=f.deltaY|-f.wheelDeltaY/40};e()
*/
/* jsCrushed (1024B):*/
_='d g R=r2;Y=.6;=0[o,5,Z=9,T=h=8];D 8>l;){i=p*o@;d hg[++v=CQ_)+j_Aw=i*p+j,$wQ%2?&*2++p]+]!4:(v++o`&+`v)+h*(Y#6>]?6:]o=i=pirD[)k;kDZ++[ijECi+jAQ+kO-k)z=E+k*pAQO)y=E+oAy-=x+(z-y!r,"hsla("+[z?R/z:6#F*2/y:y)+"%",y@?x/Y:6.05<F*x/7:6<F:2/3)]+"%,"]}e^BfillRect(0,0,h=c.widUKWidU@8,r=c.hGKHG-21m=-LZw=-~Zo=LYx=~Yg hq){] j=9;j;vo,um,tw,].push(pCv+(k=m*u+w*t)*x,(v*x-o*k!p*r+r,(w*u-m*t!p*h+h)$910]}p=-l*p/40;g.sort(return f[3]-n[3]}qg)Style=$9]+l/p+")",BbegPaU(#BmovN87215487]#Vdown5,i=sete,9)up8,eVmove^ZX/h;YY/r=on^R+=f.dJ|-f.DJ/80};eVfunction(f,n){[l][-randomV]#BlN*r@1,for(};onmouse=--j]-R* =[];^cleari#T=MaU.l] i)j=p;j@][ud[w32*(6<A$);--;wheel/;Interval(=f.clientinBstroke+1g[lD[T]=2]=!)/#),$g&j%2?(v@-1A],Ba.C=o*Ed[uFz?xGeightJeltaYK=nerLcos(NeTo($O)(jQ(iUthV()^=_/2|0`]!2:ql ~s(';for(Y in $='~q`_^VUQONLKJGFECBA@&$#! ')with(_.split($[Y]))_=join(pop());eval(_)
</script>
</body>
</html>