Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
unixpickle
GitHub Repository: unixpickle/kahoot-hack
Path: blob/master/site/assets/pentagons.js
10159 views
1
// pentagons version 0.1.0
2
//
3
// Copyright (c) 2014-2015, Alex Nichol and Jonathan Loeb.
4
// All rights reserved.
5
//
6
// Redistribution and use in source and binary forms, with or without
7
// modification, are permitted provided that the following conditions are met:
8
//
9
// 1. Redistributions of source code must retain the above copyright notice, this
10
// list of conditions and the following disclaimer.
11
// 2. Redistributions in binary form must reproduce the above copyright notice,
12
// this list of conditions and the following disclaimer in the documentation
13
// and/or other materials provided with the distribution.
14
//
15
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
//
26
(function() {
27
28
// These variables are used to take long pauses (i.e. garbage collection
29
// pauses, etc.) and turn them into short pauses.
30
var LAG_SMOOTH_THRESHOLD = 500;
31
var LAG_SMOOTH_ADJUSTED = 50;
32
33
function Animation(startInfo, endInfo, durationMilliseconds) {
34
this._startInfo = startInfo;
35
this._endInfo = endInfo;
36
this._startEndDifference = PentagonInfo.difference(endInfo, startInfo);
37
this._duration = durationMilliseconds;
38
this._startTime = unixMillisecondTime();
39
this._lastFrameTime = this._startTime;
40
this._isDone = false;
41
}
42
43
Animation.prototype.frame = function() {
44
if (this._duration === 0) {
45
this._isDone = true;
46
return this._endInfo;
47
}
48
49
var fraction = this._elapsed() / this._duration;
50
if (fraction >= 1) {
51
this._isDone = true;
52
return this._endInfo;
53
}
54
return PentagonInfo.sum(this._startEndDifference.scaled(fraction),
55
this._startInfo);
56
};
57
58
Animation.prototype.isDone = function() {
59
return this._isDone;
60
};
61
62
Animation.prototype._elapsed = function() {
63
var now = unixMillisecondTime();
64
if (now < this._lastFrameTime) {
65
// This may occur if the user sets back their clock.
66
this._startTime = now;
67
} else if (this._lastFrameTime + LAG_SMOOTH_THRESHOLD <= now) {
68
this._startTime += (now - this._lastFrameTime) - LAG_SMOOTH_ADJUSTED;
69
}
70
this._lastFrameTime = now;
71
return Math.max(now-this._startTime, 0);
72
};
73
74
function unixMillisecondTime() {
75
return new Date().getTime();
76
}
77
window.addEventListener('load', function() {
78
generatePentagons();
79
80
// I found that CanvasDrawView was faster in Firefox but CanvasImageView was
81
// faster in Chrome and Safari.
82
if (navigator.userAgent.indexOf('Firefox') !== -1) {
83
new CanvasDrawView();
84
} else {
85
new CanvasImageView();
86
}
87
});
88
function PentagonInfo(fields) {
89
this.x = fields.x;
90
this.y = fields.y;
91
this.radius = fields.radius;
92
this.rotation = fields.rotation;
93
this.opacity = fields.opacity;
94
}
95
96
PentagonInfo.difference = function(info1, info2) {
97
return PentagonInfo.sum(info1, info2.scaled(-1));
98
};
99
100
PentagonInfo.distanceSquared = function(info1, info2) {
101
return Math.pow(info1.x-info2.x, 2) + Math.pow(info1.y-info2.y, 2);
102
};
103
104
PentagonInfo.sum = function(info1, info2) {
105
return new PentagonInfo({
106
x: info1.x + info2.x,
107
y: info1.y + info2.y,
108
radius: info1.radius + info2.radius,
109
rotation: info1.rotation + info2.rotation,
110
opacity: info1.opacity + info2.opacity
111
});
112
};
113
114
PentagonInfo.prototype.clampedAngle = function() {
115
var rotation = this.rotation % (Math.PI * 2);
116
if (rotation < 0) {
117
rotation += Math.PI * 2;
118
}
119
var result = new PentagonInfo(this);
120
result.rotation = rotation;
121
return result;
122
};
123
124
PentagonInfo.prototype.scaled = function(scaler) {
125
return new PentagonInfo({
126
x: this.x * scaler,
127
y: this.y * scaler,
128
radius: this.radius * scaler,
129
rotation: this.rotation * scaler,
130
opacity: this.opacity * scaler
131
});
132
};
133
var PENTAGON_COUNT = 18;
134
135
function Pentagon() {
136
var start = new PentagonInfo({
137
radius: randomRadius(),
138
opacity: randomOpacity(),
139
x: Math.random(),
140
y: Math.random(),
141
rotation: Math.random() * Math.PI * 2
142
});
143
this._currentAnimation = new Animation(start, start, 1);
144
this._lastFrame = start;
145
}
146
147
Pentagon.MAX_RADIUS = 0.2;
148
149
Pentagon.allPentagons = [];
150
151
Pentagon.prototype.frame = function() {
152
var frame = this._currentAnimation.frame().clampedAngle();
153
this._lastFrame = frame;
154
if (this._currentAnimation.isDone()) {
155
this._generateNewAnimation();
156
}
157
return frame;
158
};
159
160
Pentagon.prototype._generateNewAnimation = function(animation) {
161
var info = new PentagonInfo({
162
x: this._gravityCoord('x'),
163
y: this._gravityCoord('y'),
164
radius: randomRadius(),
165
opacity: randomOpacity(),
166
rotation: Math.PI*(Math.random()-0.5) + this._lastFrame.rotation
167
});
168
this._currentAnimation = new Animation(this._lastFrame, info,
169
randomDuration());
170
};
171
172
Pentagon.prototype._gravityCoord = function(axis) {
173
var axisCoord = this._lastFrame[axis];
174
175
// Apply inverse-square forces from edges.
176
var force = 1/Math.pow(axisCoord+0.01, 2) - 1/Math.pow(1.01-axisCoord, 2);
177
178
// Apply inverse-square forces from other pentagons.
179
for (var i = 0, len = Pentagon.allPentagons.length; i < len; ++i) {
180
var pentagon = Pentagon.allPentagons[i];
181
if (pentagon === this) {
182
continue;
183
}
184
var d2 = PentagonInfo.distanceSquared(this._lastFrame, pentagon._lastFrame);
185
if (Math.abs(d2) < 0.00001) {
186
return Math.random();
187
}
188
var forceMag = 1 / d2;
189
var distance = Math.sqrt(d2);
190
force -= forceMag * (pentagon._lastFrame[axis] - axisCoord) / distance;
191
}
192
193
// Add a random component to the force.
194
force += (Math.random() - 0.5) * 20;
195
196
// Cap the force at +/- 0.2 and add it to the current coordinate.
197
force = Math.max(Math.min(force, 100), -100) / 500;
198
199
return Math.max(Math.min(axisCoord+force, 1), 0);
200
};
201
202
function generatePentagons() {
203
for (var i = 0; i < PENTAGON_COUNT; ++i) {
204
Pentagon.allPentagons.push(new Pentagon());
205
}
206
}
207
208
function randomDuration() {
209
return 30000 + 30000*Math.random();
210
}
211
212
function randomOpacity() {
213
return Math.random()*0.22 + 0.02;
214
}
215
216
function randomRadius() {
217
return 0.05 + (Math.pow(Math.random(), 15)+1)*0.075;
218
}
219
var ELEMENT_ID = 'pentagon-background'
220
221
// CanvasView is an abstract subclass for a view that draws everything into a
222
// canvas.
223
function CanvasView() {
224
this._canvas = document.createElement('canvas');
225
this._element = document.getElementById(ELEMENT_ID);
226
if (!this._element) {
227
this._element = document.createElement('div');
228
this._element.id = ELEMENT_ID;
229
document.body.insertBefore(this._element, document.body.childNodes[0] ||
230
null);
231
}
232
233
makeAbsoluteAndFullScreen(this._element);
234
makeAbsoluteAndFullScreen(this._canvas);
235
this._element.appendChild(this._canvas);
236
237
this._width = 0;
238
this._height = 0;
239
this._updateSize();
240
241
window.addEventListener('resize', this._handleResize.bind(this));
242
}
243
244
CanvasView.prototype.draw = function() {
245
throw new Error('override this in a subclass');
246
};
247
248
CanvasView.prototype.start = function() {
249
this._tick();
250
};
251
252
CanvasView.prototype._handleResize = function() {
253
this._updateSize();
254
this.draw();
255
};
256
257
CanvasView.prototype._requestAnimationFrame = function() {
258
setTimeout(this._tick.bind(this), 1000/24);
259
};
260
261
CanvasView.prototype._tick = function() {
262
this.draw();
263
this._requestAnimationFrame();
264
};
265
266
CanvasView.prototype._updateSize = function() {
267
this._width = window.innerWidth;
268
this._height = window.innerHeight;
269
this._canvas.width = this._width;
270
this._canvas.height = this._height;
271
};
272
273
// CanvasDrawView is a subclass of CanvasView that re-draws the pentagons in
274
// each frame using a path.
275
function CanvasDrawView() {
276
CanvasView.call(this);
277
this.start();
278
}
279
280
CanvasDrawView.prototype = Object.create(CanvasView.prototype);
281
282
CanvasDrawView.prototype.draw = function() {
283
var context = this._canvas.getContext('2d');
284
285
context.clearRect(0, 0, this._width, this._height);
286
287
var size = Math.max(this._width, this._height);
288
var xOffset = 0;
289
var yOffset = 0;
290
if (this._width < this._height) {
291
xOffset = -(this._height - this._width) / 2;
292
} else {
293
yOffset = -(this._width - this._height) / 2;
294
}
295
296
for (var i = 0, len = Pentagon.allPentagons.length; i < len; ++i) {
297
var frame = Pentagon.allPentagons[i].frame();
298
299
var centerX = frame.x*size + xOffset;
300
var centerY = frame.y*size + yOffset;
301
var radius = size * frame.radius;
302
303
context.fillStyle = 'rgba(255, 255, 255,' + frame.opacity.toPrecision(5) +
304
')';
305
context.beginPath();
306
for (var j = 0; j < 5; ++j) {
307
var x = Math.cos(frame.rotation + j*Math.PI*2/5)*radius + centerX;
308
var y = Math.sin(frame.rotation + j*Math.PI*2/5)*radius + centerY;
309
if (j === 0) {
310
context.moveTo(x, y);
311
} else {
312
context.lineTo(x, y);
313
}
314
}
315
context.closePath();
316
context.fill();
317
}
318
};
319
320
// CanvasImageView is a subclass of CanvasView that pre-generates an image of a
321
// pentagon and then scales/rotates/translates that image.
322
function CanvasImageView() {
323
CanvasView.call(this);
324
325
this._imageSize = 0;
326
this._imageCache = {};
327
this.start();
328
}
329
330
CanvasImageView.prototype = Object.create(CanvasView.prototype);
331
332
CanvasImageView.prototype.draw = function() {
333
var image = this._pentagonImage();
334
var context = this._canvas.getContext('2d');
335
336
context.clearRect(0, 0, this._width, this._height);
337
338
var size = Math.max(this._width, this._height);
339
var xOffset = 0;
340
var yOffset = 0;
341
if (this._width < this._height) {
342
xOffset = -(this._height - this._width) / 2;
343
} else {
344
yOffset = -(this._width - this._height) / 2;
345
}
346
347
for (var i = 0, len = Pentagon.allPentagons.length; i < len; ++i) {
348
var frame = Pentagon.allPentagons[i].frame();
349
350
var translateX = frame.x*size + xOffset;
351
var translateY = frame.y*size + yOffset;
352
var radius = size * frame.radius;
353
354
context.globalAlpha = frame.opacity;
355
context.translate(translateX, translateY);
356
context.rotate(frame.rotation);
357
context.drawImage(image, -radius, -radius, radius*2, radius*2);
358
// NOTE: save()/reset() are apparentlty slow, although this is mainly a
359
// premature optimization.
360
context.rotate(-frame.rotation);
361
context.translate(-translateX, -translateY);
362
}
363
};
364
365
CanvasImageView.prototype._pentagonImage = function() {
366
this._updateImageSize();
367
var imageSize = this._imageSize;
368
369
if (this._imageCache.hasOwnProperty('' + imageSize)) {
370
return this._imageCache['' + imageSize];
371
}
372
373
var canvas = document.createElement('canvas');
374
canvas.width = imageSize;
375
canvas.height = imageSize;
376
377
var context = canvas.getContext('2d');
378
context.fillStyle = 'white';
379
context.beginPath();
380
for (var angle = 0; angle < 360; angle += 360/5) {
381
var x = Math.cos(angle * Math.PI / 180)*imageSize/2 + imageSize/2;
382
var y = Math.sin(angle * Math.PI / 180)*imageSize/2 + imageSize/2;
383
if (angle === 0) {
384
context.moveTo(x, y);
385
} else {
386
context.lineTo(x, y);
387
}
388
}
389
context.closePath();
390
context.fill();
391
392
var image = document.createElement('img');
393
image.src = canvas.toDataURL('image/png');
394
this._imageCache['' + imageSize] = image;
395
return image;
396
};
397
398
CanvasImageView.prototype._updateImageSize = function() {
399
var maxRadius = Math.max(this._width, this._height) * Pentagon.MAX_RADIUS;
400
var maxRadiusLog = Math.ceil(Math.log(maxRadius) / Math.log(2));
401
var imageSize = Math.pow(2, 1+maxRadiusLog);
402
if (imageSize === this._imageSize) {
403
return;
404
}
405
this._imageSize = imageSize;
406
};
407
408
function makeAbsoluteAndFullScreen(element) {
409
element.style.position = 'fixed';
410
element.style.top = 0;
411
element.style.left = 0;
412
element.style.width = '100%';
413
element.style.height = '100%';
414
}
415
416
})();
417
418