littlejsengine 1.15.9 → 1.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/littlejs.d.ts +154 -158
  2. package/dist/littlejs.esm.js +573 -570
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +553 -553
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +392 -396
  7. package/examples/box2d/game.js +1 -1
  8. package/examples/box2d/gameObjects.js +5 -5
  9. package/examples/breakout/game.js +3 -3
  10. package/examples/electron/game.js +1 -2
  11. package/examples/electron/index.html +2 -2
  12. package/examples/htmlMenu/game.js +0 -1
  13. package/examples/index.html +1 -1
  14. package/examples/logo.png +0 -0
  15. package/examples/logo2.png +0 -0
  16. package/examples/module/game.js +1 -2
  17. package/examples/particles/index.html +1 -1
  18. package/examples/platformer/game.js +4 -4
  19. package/examples/platformer/gameEffects.js +1 -1
  20. package/examples/puzzle/game.js +2 -2
  21. package/examples/shorts/base.html +4 -3
  22. package/examples/shorts/box2dPool.js +2 -2
  23. package/examples/shorts/empty.js +1 -1
  24. package/examples/shorts/{systemFont.js → fontImage.js} +3 -3
  25. package/examples/shorts/fps.js +1 -1
  26. package/examples/shorts/helloWorld.js +2 -2
  27. package/examples/shorts/nineSlice.js +2 -2
  28. package/examples/shorts/slidingPuzzle.js +1 -1
  29. package/examples/starter/game.js +1 -2
  30. package/examples/starter/index.html +3 -2
  31. package/examples/stress/index.html +1 -1
  32. package/examples/typescript/game.js +1 -2
  33. package/examples/typescript/game.ts +1 -2
  34. package/package.json +1 -1
  35. package/plugins/box2d.js +8 -8
  36. package/plugins/drawUtilities.js +6 -6
  37. package/plugins/postProcess.js +13 -20
  38. package/plugins/uiSystem.js +16 -4
  39. package/reference.md +9 -11
  40. package/src/engine.js +40 -54
  41. package/src/engineAudio.js +5 -7
  42. package/src/engineBuild.js +1 -0
  43. package/src/engineDebug.js +163 -161
  44. package/src/engineDraw.js +108 -130
  45. package/src/engineExport.js +20 -17
  46. package/src/engineInput.js +4 -7
  47. package/src/engineMath.js +1201 -0
  48. package/src/engineMedals.js +7 -7
  49. package/src/engineObject.js +5 -4
  50. package/src/engineRelease.js +2 -4
  51. package/src/engineSettings.js +22 -32
  52. package/src/engineTileLayer.js +9 -10
  53. package/src/engineUtilities.js +34 -1187
  54. package/src/engineWebGL.js +13 -15
@@ -10,264 +10,6 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- /** The value of PI
14
- * @type {number}
15
- * @default Math.PI
16
- * @memberof Utilities */
17
- const PI = Math.PI;
18
-
19
- /** Returns absolute value of value passed in
20
- * @param {number} value
21
- * @return {number}
22
- * @memberof Utilities */
23
- const abs = Math.abs;
24
-
25
- /** Returns floored value of value passed in
26
- * @param {number} value
27
- * @return {number}
28
- * @memberof Utilities */
29
- const floor = Math.floor;
30
-
31
- /** Returns ceiled value of value passed in
32
- * @param {number} value
33
- * @return {number}
34
- * @memberof Utilities */
35
- const ceil = Math.ceil;
36
-
37
- /** Returns rounded value passed in
38
- * @param {number} value
39
- * @return {number}
40
- * @memberof Utilities */
41
- const round = Math.round;
42
-
43
- /** Returns lowest value passed in
44
- * @param {...number} values
45
- * @return {number}
46
- * @memberof Utilities */
47
- const min = Math.min;
48
-
49
- /** Returns highest value passed in
50
- * @param {...number} values
51
- * @return {number}
52
- * @memberof Utilities */
53
- const max = Math.max;
54
-
55
- /** Returns the sign of value passed in
56
- * @param {number} value
57
- * @return {number}
58
- * @memberof Utilities */
59
- const sign = Math.sign;
60
-
61
- /** Returns hypotenuse of values passed in
62
- * @param {...number} values
63
- * @return {number}
64
- * @memberof Utilities */
65
- const hypot = Math.hypot;
66
-
67
- /** Returns log2 of value passed in
68
- * @param {number} value
69
- * @return {number}
70
- * @memberof Utilities */
71
- const log2 = Math.log2;
72
-
73
- /** Returns sin of value passed in
74
- * @param {number} value
75
- * @return {number}
76
- * @memberof Utilities */
77
- const sin = Math.sin;
78
-
79
- /** Returns cos of value passed in
80
- * @param {number} value
81
- * @return {number}
82
- * @memberof Utilities */
83
- const cos = Math.cos;
84
-
85
- /** Returns tan of value passed in
86
- * @param {number} value
87
- * @return {number}
88
- * @memberof Utilities */
89
- const tan = Math.tan;
90
-
91
- /** Returns atan2 of values passed in
92
- * @param {number} y
93
- * @param {number} x
94
- * @return {number}
95
- * @memberof Utilities */
96
- const atan2 = Math.atan2;
97
-
98
- /** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
99
- * @param {number} dividend
100
- * @param {number} [divisor]
101
- * @return {number}
102
- * @memberof Utilities */
103
- function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
104
-
105
- /** Clamps the value between max and min
106
- * @param {number} value
107
- * @param {number} [min]
108
- * @param {number} [max]
109
- * @return {number}
110
- * @memberof Utilities */
111
- function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
112
-
113
- /** Returns what percentage the value is between valueA and valueB
114
- * @param {number} value
115
- * @param {number} valueA
116
- * @param {number} valueB
117
- * @return {number}
118
- * @memberof Utilities */
119
- function percent(value, valueA, valueB)
120
- { return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
121
-
122
- /** Linearly interpolates between values passed in using percent
123
- * @param {number} valueA
124
- * @param {number} valueB
125
- * @param {number} percent
126
- * @return {number}
127
- * @memberof Utilities */
128
- function lerp(valueA, valueB, percent)
129
- {
130
- if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
131
- console.warn('lerp() parameter order changed! use lerp(start, end, p)');
132
- return valueA + clamp(percent) * (valueB-valueA);
133
- }
134
-
135
- /** Gets percent between percentA and percentB and linearly interpolates between lerpA and lerpB
136
- * A shortcut for lerp(lerpA, lerpB, percent(value, percentA, percentB))
137
- * @param {number} value
138
- * @param {number} percentA
139
- * @param {number} percentB
140
- * @param {number} lerpA
141
- * @param {number} lerpB
142
- * @return {number}
143
- * @memberof Utilities */
144
- function percentLerp(value, percentA, percentB, lerpA, lerpB)
145
- { return lerp(lerpA, lerpB, percent(value, percentA, percentB)); }
146
-
147
- /** Returns signed wrapped distance between the two values passed in
148
- * @param {number} valueA
149
- * @param {number} valueB
150
- * @param {number} [wrapSize]
151
- * @return {number}
152
- * @memberof Utilities */
153
- function distanceWrap(valueA, valueB, wrapSize=1)
154
- { const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
155
-
156
- /** Linearly interpolates between values passed in with wrapping
157
- * @param {number} valueA
158
- * @param {number} valueB
159
- * @param {number} percent
160
- * @param {number} [wrapSize]
161
- * @return {number}
162
- * @memberof Utilities */
163
- function lerpWrap(valueA, valueB, percent, wrapSize=1)
164
- {
165
- if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
166
- console.warn('lerpWrap() parameter order changed! use lerpWrap(start, end, p)');
167
- return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize);
168
- }
169
-
170
- /** Returns signed wrapped distance between the two angles passed in
171
- * @param {number} angleA
172
- * @param {number} angleB
173
- * @return {number}
174
- * @memberof Utilities */
175
- function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
176
-
177
- /** Linearly interpolates between the angles passed in with wrapping
178
- * @param {number} angleA
179
- * @param {number} angleB
180
- * @param {number} percent
181
- * @return {number}
182
- * @memberof Utilities */
183
- function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
184
-
185
- /** Applies smoothstep function to the percentage value
186
- * @param {number} percent
187
- * @return {number}
188
- * @memberof Utilities */
189
- function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
190
-
191
- /** Checks if the value passed in is a power of two
192
- * @param {number} value
193
- * @return {boolean}
194
- * @memberof Utilities */
195
- function isPowerOfTwo(value) { return !(value & (value - 1)); }
196
-
197
- /** Returns the nearest power of two not less than the value
198
- * @param {number} value
199
- * @return {number}
200
- * @memberof Utilities */
201
- function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
202
-
203
- /** Returns true if two axis aligned bounding boxes are overlapping
204
- * this can be used for simple collision detection between objects
205
- * @param {Vector2} posA - Center of box A
206
- * @param {Vector2} sizeA - Size of box A
207
- * @param {Vector2} posB - Center of box B
208
- * @param {Vector2} [sizeB=(0,0)] - Size of box B, uses a point if undefined
209
- * @return {boolean} - True if overlapping
210
- * @memberof Utilities */
211
- function isOverlapping(posA, sizeA, posB, sizeB=vec2())
212
- {
213
- const dx = (posA.x - posB.x)*2;
214
- const dy = (posA.y - posB.y)*2;
215
- const sx = sizeA.x + sizeB.x;
216
- const sy = sizeA.y + sizeB.y;
217
- return dx >= -sx && dx < sx && dy >= -sy && dy < sy;
218
- }
219
-
220
- /** Returns true if a line segment is intersecting an axis aligned box
221
- * @param {Vector2} start - Start of raycast
222
- * @param {Vector2} end - End of raycast
223
- * @param {Vector2} pos - Center of box
224
- * @param {Vector2} size - Size of box
225
- * @return {boolean} - True if intersecting
226
- * @memberof Utilities */
227
- function isIntersecting(start, end, pos, size)
228
- {
229
- // Liang-Barsky algorithm
230
- const boxMin = pos.subtract(size.scale(.5));
231
- const boxMax = boxMin.add(size);
232
- const delta = end.subtract(start);
233
- const a = start.subtract(boxMin);
234
- const b = start.subtract(boxMax);
235
- const p = [-delta.x, delta.x, -delta.y, delta.y];
236
- const q = [a.x, -b.x, a.y, -b.y];
237
- let tMin = 0, tMax = 1;
238
- for (let i = 4; i--;)
239
- {
240
- if (p[i])
241
- {
242
- const t = q[i] / p[i];
243
- if (p[i] < 0)
244
- {
245
- if (t > tMax) return false;
246
- tMin = max(t, tMin);
247
- }
248
- else
249
- {
250
- if (t < tMin) return false;
251
- tMax = min(t, tMax);
252
- }
253
- }
254
- else if (q[i] < 0)
255
- return false;
256
- }
257
-
258
- return true;
259
- }
260
-
261
- /** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
262
- * @param {number} [frequency] - Frequency of the wave in Hz
263
- * @param {number} [amplitude] - Amplitude (max height) of the wave
264
- * @param {number} [t=time] - Value to use for time of the wave
265
- * @param {number} [offset] - Value to use for time offset of the wave
266
- * @return {number} - Value waving between 0 and amplitude
267
- * @memberof Utilities */
268
- function wave(frequency=1, amplitude=1, t=time, offset=0)
269
- { return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
270
-
271
13
  /** Formats seconds to mm:ss style for display purposes
272
14
  * @param {number} t - time in seconds
273
15
  * @return {string}
@@ -291,943 +33,48 @@ async function fetchJSON(url)
291
33
  return response.json();
292
34
  }
293
35
 
294
- /**
295
- * Check if object is a valid number, not NaN or undefined, but it may be infinite
296
- * @param {any} n
297
- * @return {boolean}
298
- * @memberof Utilities */
299
- function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
300
-
301
- /**
302
- * Check if object is a valid string or can be converted to one
303
- * @param {any} s
304
- * @return {boolean}
305
- * @memberof Utilities */
306
- function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
307
-
308
- /**
309
- * Check if object is an array
310
- * @param {any} a
311
- * @return {boolean}
312
- * @memberof Utilities */
313
- function isArray(a) { return Array.isArray(a); }
314
-
315
- /**
316
- * @callback LineTestFunction - Checks if a position is colliding
317
- * @param {Vector2} pos
318
- * @memberof Draw
319
- */
320
-
321
- /**
322
- * Casts a ray and returns position of the first collision found, or undefined if none are found
323
- * @param {Vector2} posStart
324
- * @param {Vector2} posEnd
325
- * @param {LineTestFunction} testFunction - Check if colliding
326
- * @param {Vector2} [normal] - Optional vector to store the normal
327
- * @return {Vector2|undefined} - Position of the collision or undefined if none found
328
- * @memberof Utilities */
329
- function lineTest(posStart, posEnd, testFunction, normal)
330
- {
331
- ASSERT(isVector2(posStart), 'posStart must be a vec2');
332
- ASSERT(isVector2(posEnd), 'posEnd must be a vec2');
333
- ASSERT(typeof testFunction === 'function', 'testFunction must be a function');
334
- ASSERT(!normal || isVector2(normal), 'normal must be a vec2');
335
-
336
- // get ray direction and length
337
- const dx = posEnd.x - posStart.x;
338
- const dy = posEnd.y - posStart.y;
339
- const totalLength = hypot(dx, dy);
340
- if (!totalLength)
341
- return;
342
-
343
- // current integer cell we are in
344
- const pos = posStart.floor();
345
-
346
- // normalize ray direction
347
- const dirX = dx / totalLength;
348
- const dirY = dy / totalLength;
349
-
350
- // step direction in grid
351
- const stepX = sign(dirX);
352
- const stepY = sign(dirY);
353
-
354
- // distance along the ray to cross one full cell in X or Y
355
- const tDeltaX = dirX ? abs(1 / dirX) : Infinity;
356
- const tDeltaY = dirY ? abs(1 / dirY) : Infinity;
357
-
358
- // distance along the ray from start to the first grid boundary
359
- const nextGridX = stepX > 0 ? pos.x + 1 : pos.x;
360
- const nextGridY = stepY > 0 ? pos.y + 1 : pos.y;
361
- const tMaxX = dirX ? (nextGridX - posStart.x) / dirX : Infinity;
362
- const tMaxY = dirY ? (nextGridY - posStart.y) / dirY : Infinity;
363
-
364
- // use line drawing algorithm to test for collisions
365
- let t = 0, tX = tMaxX, tY = tMaxY, wasX = tDeltaX < tDeltaY;
366
- while (t < totalLength)
367
- {
368
- if (testFunction(pos))
369
- {
370
- // set hit point
371
- const hitPos = vec2(posStart.x + dirX*t, posStart.y + dirY*t);
372
-
373
- // move inside of tile if on positive edge
374
- const e = 1e-9;
375
- if (wasX)
376
- {
377
- if (stepX < 0)
378
- hitPos.x -= e;
379
- }
380
- if (stepY < 0)
381
- hitPos.y -= e;
382
-
383
- // set normal
384
- if (normal)
385
- wasX ? normal.set(-stepX,0) : normal.set(0,-stepY);
386
- return hitPos;
387
- }
388
-
389
- // advance to the next grid boundary
390
- if (wasX = tX < tY)
391
- {
392
- pos.x += stepX;
393
- t = tX;
394
- tX += tDeltaX;
395
- }
396
- else
397
- {
398
- pos.y += stepY;
399
- t = tY;
400
- tY += tDeltaY;
401
- }
402
- }
403
- }
404
-
405
- ///////////////////////////////////////////////////////////////////////////////
406
-
407
- /** Random global functions
408
- * @namespace Random */
409
-
410
- /** Returns a random value between the two values passed in
411
- * @param {number} [valueA]
412
- * @param {number} [valueB]
413
- * @return {number}
414
- * @memberof Random */
415
- function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
416
-
417
- /** Returns a floored random value between the two values passed in
418
- * The upper bound is exclusive. (If 2 is passed in, result will be 0 or 1)
419
- * @param {number} valueA
420
- * @param {number} [valueB]
421
- * @return {number}
422
- * @memberof Random */
423
- function randInt(valueA, valueB=0) { return floor(rand(valueA,valueB)); }
424
-
425
- /** Randomly returns true or false given the chance of true passed in
426
- * @param {number} [chance]
427
- * @return {boolean}
428
- * @memberof Random */
429
- function randBool(chance=.5) { return rand() < chance; }
430
-
431
- /** Randomly returns either -1 or 1
432
- * @return {number}
433
- * @memberof Random */
434
- function randSign() { return randInt(2) * 2 - 1; }
435
-
436
- /** Returns a random Vector2 with the passed in length
437
- * @param {number} [length]
438
- * @return {Vector2}
439
- * @memberof Random */
440
- function randVec2(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
441
-
442
- /** Returns a random Vector2 within a circular shape
443
- * @param {number} [radius]
444
- * @param {number} [minRadius]
445
- * @return {Vector2}
446
- * @memberof Random */
447
- function randInCircle(radius=1, minRadius=0)
448
- { return radius > 0 ? randVec2(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
449
-
450
- /** Returns a random color between the two passed in colors, combine components if linear
451
- * @param {Color} [colorA=(1,1,1,1)]
452
- * @param {Color} [colorB=(0,0,0,1)]
453
- * @param {boolean} [linear]
454
- * @return {Color}
455
- * @memberof Random */
456
- function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
457
- {
458
- return linear ? colorA.lerp(colorB, rand()) :
459
- new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
460
- }
461
-
462
- ///////////////////////////////////////////////////////////////////////////////
463
-
464
- /**
465
- * Seeded random number generator
466
- * - Can be used to create a deterministic random number sequence
467
- * @memberof Engine
468
- * @example
469
- * let r = new RandomGenerator(123); // random number generator with seed 123
470
- * let a = r.float(); // random value between 0 and 1
471
- * let b = r.int(10); // random integer between 0 and 9
472
- * r.seed = 123; // reset the seed
473
- * let c = r.float(); // the same value as a
474
- */
475
- class RandomGenerator
476
- {
477
- /** Create a random number generator with the seed passed in
478
- * @param {number} [seed] - Starting seed or engine default seed */
479
- constructor(seed = 123456789)
480
- {
481
- /** @property {number} - random seed */
482
- this.seed = seed;
483
- }
484
-
485
- /** Returns a seeded random value between the two values passed in
486
- * @param {number} [valueA]
487
- * @param {number} [valueB]
488
- * @return {number} */
489
- float(valueA=1, valueB=0)
490
- {
491
- // xorshift algorithm
492
- this.seed ^= this.seed << 13;
493
- this.seed ^= this.seed >>> 17;
494
- this.seed ^= this.seed << 5;
495
- return valueB + (valueA - valueB) * ((this.seed >>> 0) / 2**32);
496
- }
497
-
498
- /** Returns a floored seeded random value the two values passed in
499
- * @param {number} valueA
500
- * @param {number} [valueB]
501
- * @return {number} */
502
- int(valueA, valueB=0) { return floor(this.float(valueA, valueB)); }
503
-
504
- /** Randomly returns true or false given the chance of true passed in
505
- * @param {number} [chance]
506
- * @return {boolean} */
507
- bool(chance=.5) { return this.float() < chance; }
508
-
509
- /** Randomly returns either -1 or 1 deterministically
510
- * @return {number} */
511
- sign() { return this.float() > .5 ? 1 : -1; }
512
-
513
- /** Returns a seeded random value between the two values passed in with a random sign
514
- * @param {number} [valueA]
515
- * @param {number} [valueB]
516
- * @return {number} */
517
- floatSign(valueA=1, valueB=0) { return this.float(valueA, valueB) * this.sign(); }
518
-
519
- /** Returns a random angle between -PI and PI
520
- * @return {number} */
521
- angle() { return this.float(-PI, PI); }
522
-
523
- /** Returns a seeded vec2 with size between the two values passed in
524
- * @param {number} valueA
525
- * @param {number} [valueB]
526
- * @return {Vector2} */
527
- vec2(valueA=1, valueB=0)
528
- { return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
529
-
530
- /** Returns a random color between the two passed in colors, combine components if linear
531
- * @param {Color} [colorA=(1,1,1,1)]
532
- * @param {Color} [colorB=(0,0,0,1)]
533
- * @param {boolean} [linear]
534
- * @return {Color} */
535
- randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
536
- {
537
- return linear ? colorA.lerp(colorB, this.float()) :
538
- new Color(
539
- this.float(colorA.r,colorB.r),
540
- this.float(colorA.g,colorB.g),
541
- this.float(colorA.b,colorB.b),
542
- this.float(colorA.a,colorB.a));
543
- }
544
-
545
- /** Returns a new color that has each component randomly adjusted
546
- * @param {Color} color
547
- * @param {number} [amount]
548
- * @param {number} [alphaAmount]
549
- * @return {Color} */
550
- mutateColor(color, amount=.05, alphaAmount=0)
551
- {
552
- ASSERT_NUMBER_VALID(amount);
553
- ASSERT_NUMBER_VALID(alphaAmount);
554
- return new Color
555
- (
556
- color.r + this.float(amount, -amount),
557
- color.g + this.float(amount, -amount),
558
- color.b + this.float(amount, -amount),
559
- color.a + this.float(alphaAmount, -alphaAmount)
560
- ).clamp();
561
- }
562
- }
563
-
564
- ///////////////////////////////////////////////////////////////////////////////
565
-
566
- /**
567
- * Create a 2d vector, can take 1 or 2 scalar values
568
- * @param {number} [x]
569
- * @param {number} [y] - if y is undefined, x is used for both
570
- * @return {Vector2}
571
- * @example
572
- * let a = vec2(0, 1); // vector with coordinates (0, 1)
573
- * a = vec2(5); // set a to (5, 5)
574
- * b = vec2(); // set b to (0, 0)
575
- * @memberof Utilities */
576
- function vec2(x=0, y) { return new Vector2(x, y === undefined ? x : y); }
577
-
578
- /**
579
- * Check if object is a valid Vector2
580
- * @param {any} v
581
- * @return {boolean}
582
- * @memberof Utilities */
583
- function isVector2(v) { return v instanceof Vector2 && v.isValid(); }
584
-
585
- // vector2 asserts
586
- function ASSERT_VECTOR2_VALID(v) { ASSERT(isVector2(v), 'Vector2 is invalid.', v); }
587
- function ASSERT_NUMBER_VALID(n) { ASSERT(isNumber(n), 'Number is invalid.', n); }
588
- function ASSERT_VECTOR2_NORMAL(v)
589
- {
590
- ASSERT_VECTOR2_VALID(v);
591
- ASSERT(abs(v.lengthSquared()-1) < .01, 'Vector2 is not normal.', v);
592
- }
593
-
594
- /**
595
- * 2D Vector object with vector math library
596
- * - Functions do not change this so they can be chained together
597
- * @memberof Engine
598
- * @example
599
- * let a = new Vector2(2, 3); // vector with coordinates (2, 3)
600
- * let b = new Vector2; // vector with coordinates (0, 0)
601
- * let c = vec2(4, 2); // use the vec2 function to make a Vector2
602
- * let d = a.add(b).scale(5); // operators can be chained
603
- */
604
- class Vector2
605
- {
606
- /** Create a 2D vector with the x and y passed in, can also be created with vec2()
607
- * @param {number} [x] - X axis location
608
- * @param {number} [y] - Y axis location */
609
- constructor(x=0, y=0)
610
- {
611
- /** @property {number} - X axis location */
612
- this.x = x;
613
- /** @property {number} - Y axis location */
614
- this.y = y;
615
- ASSERT(this.isValid(), 'Constructed Vector2 is invalid.', this);
616
- }
617
-
618
- /** Sets values of this vector and returns self
619
- * @param {number} [x] - X axis location
620
- * @param {number} [y] - Y axis location
621
- * @return {Vector2} */
622
- set(x=0, y=0)
623
- {
624
- this.x = x;
625
- this.y = y;
626
- ASSERT_VECTOR2_VALID(this);
627
- return this;
628
- }
629
-
630
- /** Sets this vector from another vector and returns self
631
- * @param {Vector2} v - other vector
632
- * @return {Vector2} */
633
- setFrom(v) { return this.set(v.x, v.y); }
634
-
635
- /** Returns a new vector that is a copy of this
636
- * @return {Vector2} */
637
- copy() { return new Vector2(this.x, this.y); }
638
-
639
- /** Returns a copy of this vector plus the vector passed in
640
- * @param {Vector2} v - other vector
641
- * @return {Vector2} */
642
- add(v) { return new Vector2(this.x + v.x, this.y + v.y);}
643
-
644
- /** Returns a copy of this vector minus the vector passed in
645
- * @param {Vector2} v - other vector
646
- * @return {Vector2} */
647
- subtract(v) { return new Vector2(this.x - v.x, this.y - v.y); }
648
-
649
- /** Returns a copy of this vector times the vector passed in
650
- * @param {Vector2} v - other vector
651
- * @return {Vector2} */
652
- multiply(v) { return new Vector2(this.x * v.x, this.y * v.y); }
653
-
654
- /** Returns a copy of this vector divided by the vector passed in
655
- * @param {Vector2} v - other vector
656
- * @return {Vector2} */
657
- divide(v) { return new Vector2(this.x / v.x, this.y / v.y); }
658
-
659
- /** Returns a copy of this vector scaled by the vector passed in
660
- * @param {number} s - scale
661
- * @return {Vector2} */
662
- scale(s) { return new Vector2(this.x * s, this.y * s); }
663
-
664
- /** Returns the length of this vector
665
- * @return {number} */
666
- length() { return this.lengthSquared()**.5; }
667
-
668
- /** Returns the length of this vector squared
669
- * @return {number} */
670
- lengthSquared() { return this.x**2 + this.y**2; }
671
-
672
- /** Returns the distance from this vector to vector passed in
673
- * @param {Vector2} v - other vector
674
- * @return {number} */
675
- distance(v) { return this.distanceSquared(v)**.5; }
676
-
677
- /** Returns the distance squared from this vector to vector passed in
678
- * @param {Vector2} v - other vector
679
- * @return {number} */
680
- distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
681
-
682
- /** Returns a new vector in same direction as this one with the length passed in
683
- * @param {number} [length]
684
- * @return {Vector2} */
685
- normalize(length=1)
686
- {
687
- const l = this.length();
688
- return l ? this.scale(length/l) : new Vector2(0, length);
689
- }
690
-
691
- /** Returns a new vector clamped to length passed in
692
- * @param {number} [length]
693
- * @return {Vector2} */
694
- clampLength(length=1)
695
- {
696
- const l = this.length();
697
- return l > length ? this.scale(length/l) : this.copy();
698
- }
699
-
700
- /** Returns the dot product of this and the vector passed in
701
- * @param {Vector2} v - other vector
702
- * @return {number} */
703
- dot(v) { return this.x*v.x + this.y*v.y; }
704
-
705
- /** Returns the cross product of this and the vector passed in
706
- * @param {Vector2} v - other vector
707
- * @return {number} */
708
- cross(v) { return this.x*v.y - this.y*v.x; }
709
-
710
- /** Returns a copy this vector reflected by the surface normal
711
- * @param {Vector2} normal - surface normal (should be normalized)
712
- * @param {number} restitution - how much to bounce, 1 is perfect bounce, 0 is no bounce
713
- * @return {Vector2} */
714
- reflect(normal, restitution=1)
715
- { return this.subtract(normal.scale((1+restitution)*this.dot(normal))); }
716
-
717
- /** Returns the clockwise angle of this vector, up is angle 0
718
- * @return {number} */
719
- angle() { return atan2(this.x, this.y); }
720
-
721
- /** Sets this vector with clockwise angle and length passed in
722
- * @param {number} [angle]
723
- * @param {number} [length]
724
- * @return {Vector2} */
725
- setAngle(angle=0, length=1)
726
- {
727
- ASSERT_NUMBER_VALID(angle);
728
- ASSERT_NUMBER_VALID(length);
729
- this.x = length*sin(angle);
730
- this.y = length*cos(angle);
731
- return this;
732
- }
733
-
734
- /** Returns copy of this vector rotated by the clockwise angle passed in
735
- * @param {number} angle
736
- * @return {Vector2} */
737
- rotate(angle)
738
- {
739
- ASSERT_NUMBER_VALID(angle);
740
- const c = cos(-angle), s = sin(-angle);
741
- return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
742
- }
743
-
744
- /** Sets this this vector to point in the specified integer direction (0-3), corresponding to multiples of 90 degree rotation
745
- * @param {number} [direction]
746
- * @param {number} [length]
747
- * @return {Vector2} */
748
- setDirection(direction, length=1)
749
- {
750
- ASSERT_NUMBER_VALID(direction);
751
- ASSERT_NUMBER_VALID(length);
752
- direction = mod(direction, 4);
753
- ASSERT(direction===0 || direction===1 || direction===2 || direction===3,
754
- 'Vector2.setDirection() direction must be an integer between 0 and 3.');
755
-
756
- this.x = direction%2 ? direction-1 ? -length : length : 0;
757
- this.y = direction%2 ? 0 : direction ? -length : length;
758
- return this;
759
- }
760
-
761
- /** Returns the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
762
- * @return {number} */
763
- direction()
764
- { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
765
-
766
- /** Returns a copy of this vector with absolute values
767
- * @return {Vector2} */
768
- abs() { return new Vector2(abs(this.x), abs(this.y)); }
769
-
770
- /** Returns a copy of this vector with each axis floored
771
- * @return {Vector2} */
772
- floor() { return new Vector2(floor(this.x), floor(this.y)); }
773
-
774
- /** Returns new vec2 with modded values
775
- * @param {number} [divisor]
776
- * @return {Vector2} */
777
- mod(divisor=1)
778
- { return new Vector2(mod(this.x, divisor), mod(this.y, divisor)); }
779
-
780
- /** Returns the area this vector covers as a rectangle
781
- * @return {number} */
782
- area() { return abs(this.x * this.y); }
783
-
784
- /** Returns true if this vector is (0,0)
785
- * @return {boolean} */
786
- isZero() { return !this.x && !this.y; }
787
-
788
- /** Returns a new vector that is p percent between this and the vector passed in
789
- * @param {Vector2} v - other vector
790
- * @param {number} percent
791
- * @return {Vector2} */
792
- lerp(v, percent)
793
- {
794
- ASSERT_VECTOR2_VALID(v);
795
- ASSERT_NUMBER_VALID(percent);
796
- const p = clamp(percent);
797
- return new Vector2(v.x*p + this.x*(1-p), v.y*p + this.y*(1-p));
798
- }
799
-
800
- /** Returns true if this vector is within the bounds of an array size passed in
801
- * @param {Vector2} arraySize
802
- * @return {boolean} */
803
- arrayCheck(arraySize)
804
- { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
805
-
806
- /** Returns this vector expressed as a string
807
- * @param {number} digits - precision to display
808
- * @return {string} */
809
- toString(digits=3)
810
- {
811
- ASSERT_NUMBER_VALID(digits);
812
- if (this.isValid())
813
- return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
814
- else
815
- return `(${this.x}, ${this.y})`;
816
- }
817
-
818
- /** Checks if this is a valid vector
819
- * @return {boolean} */
820
- isValid() { return isNumber(this.x) && isNumber(this.y); }
821
- }
822
-
823
36
  ///////////////////////////////////////////////////////////////////////////////
824
37
 
825
- /**
826
- * Create a color object with RGBA values, white by default
827
- * @param {number} [r=1] - red
828
- * @param {number} [g=1] - green
829
- * @param {number} [b=1] - blue
830
- * @param {number} [a=1] - alpha
831
- * @return {Color}
832
- * @memberof Utilities
833
- */
834
- function rgb(r, g, b, a) { return new Color(r, g, b, a); }
835
-
836
- /**
837
- * Create a color object with HSLA values, white by default
838
- * @param {number} [h=0] - hue
839
- * @param {number} [s=0] - saturation
840
- * @param {number} [l=1] - lightness
841
- * @param {number} [a=1] - alpha
842
- * @return {Color}
843
- * @memberof Utilities */
844
- function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
845
-
846
- /**
847
- * Check if object is a valid Color
848
- * @param {any} c
849
- * @return {boolean}
850
- * @memberof Utilities */
851
- function isColor(c) { return c instanceof Color && c.isValid(); }
852
-
853
- // color asserts
854
- function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c), 'Color is invalid.', c); }
38
+ /** Save a text file to disk
39
+ * @param {string} text
40
+ * @param {string} [filename]
41
+ * @param {string} [type]
42
+ * @memberof Utilities */
43
+ function saveText(text, filename='text', type='text/plain')
44
+ { saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
855
45
 
856
- /**
857
- * Color object (red, green, blue, alpha) with some helpful functions
858
- * @memberof Engine
859
- * @example
860
- * let a = new Color; // white
861
- * let b = new Color(1, 0, 0); // red
862
- * let c = new Color(0, 0, 0, 0); // transparent black
863
- * let d = rgb(0, 0, 1); // blue using rgb color
864
- * let e = hsl(.3, 1, .5); // green using hsl color
865
- */
866
- class Color
46
+ /** Save a canvas to disk
47
+ * @param {HTMLCanvasElement|OffscreenCanvas} canvas
48
+ * @param {string} [filename]
49
+ * @param {string} [type]
50
+ * @memberof Utilities */
51
+ function saveCanvas(canvas, filename='screenshot', type='image/png')
867
52
  {
868
- /** Create a color with the rgba components passed in, white by default
869
- * @param {number} [r] - red
870
- * @param {number} [g] - green
871
- * @param {number} [b] - blue
872
- * @param {number} [a] - alpha*/
873
- constructor(r=1, g=1, b=1, a=1)
874
- {
875
- /** @property {number} - Red */
876
- this.r = r;
877
- /** @property {number} - Green */
878
- this.g = g;
879
- /** @property {number} - Blue */
880
- this.b = b;
881
- /** @property {number} - Alpha */
882
- this.a = a;
883
- ASSERT(this.isValid(), 'Constructed Color is invalid.', this);
884
- }
885
-
886
- /** Sets values of this color and returns self
887
- * @param {number} [r] - red
888
- * @param {number} [g] - green
889
- * @param {number} [b] - blue
890
- * @param {number} [a] - alpha
891
- * @return {Color} */
892
- set(r=1, g=1, b=1, a=1)
893
- {
894
- this.r = r;
895
- this.g = g;
896
- this.b = b;
897
- this.a = a;
898
- ASSERT_COLOR_VALID(this);
899
- return this;
900
- }
901
-
902
- /** Sets this color from another color and returns self
903
- * @param {Color} c - other color
904
- * @return {Color} */
905
- setFrom(c) { return this.set(c.r, c.g, c.b, c.a); }
906
-
907
- /** Returns a new color that is a copy of this
908
- * @return {Color} */
909
- copy() { return new Color(this.r, this.g, this.b, this.a); }
910
-
911
- /** Returns a copy of this color plus the color passed in
912
- * @param {Color} c - other color
913
- * @return {Color} */
914
- add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
915
-
916
- /** Returns a copy of this color minus the color passed in
917
- * @param {Color} c - other color
918
- * @return {Color} */
919
- subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
920
-
921
- /** Returns a copy of this color times the color passed in
922
- * @param {Color} c - other color
923
- * @return {Color} */
924
- multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
925
-
926
- /** Returns a copy of this color divided by the color passed in
927
- * @param {Color} c - other color
928
- * @return {Color} */
929
- divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
930
-
931
- /** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
932
- * @param {number} scale
933
- * @param {number} [alphaScale=scale]
934
- * @return {Color} */
935
- scale(scale, alphaScale=scale)
936
- { return new Color(this.r*scale, this.g*scale, this.b*scale, this.a*alphaScale); }
937
-
938
- /** Returns a copy of this color clamped to the valid range between 0 and 1
939
- * @return {Color} */
940
- clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
941
-
942
- /** Returns a new color that is p percent between this and the color passed in
943
- * @param {Color} c - other color
944
- * @param {number} percent
945
- * @return {Color} */
946
- lerp(c, percent)
947
- {
948
- ASSERT_COLOR_VALID(c);
949
- ASSERT_NUMBER_VALID(percent);
950
- const p = clamp(percent);
951
- return new Color(
952
- c.r*p + this.r*(1-p),
953
- c.g*p + this.g*(1-p),
954
- c.b*p + this.b*(1-p),
955
- c.a*p + this.a*(1-p));
956
- }
957
-
958
- /** Sets this color given a hue, saturation, lightness, and alpha
959
- * @param {number} [h] - hue
960
- * @param {number} [s] - saturation
961
- * @param {number} [l] - lightness
962
- * @param {number} [a] - alpha
963
- * @return {Color} */
964
- setHSLA(h=0, s=0, l=1, a=1)
965
- {
966
- h = mod(h,1);
967
- s = clamp(s);
968
- l = clamp(l);
969
- const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
970
- f = (p, q, t)=>
971
- (t = mod(t,1))*6 < 1 ? p+(q-p)*6*t :
972
- t*2 < 1 ? q :
973
- t*3 < 2 ? p+(q-p)*(4-t*6) : p;
974
- this.r = f(p, q, h + 1/3);
975
- this.g = f(p, q, h);
976
- this.b = f(p, q, h - 1/3);
977
- this.a = a;
978
- ASSERT_COLOR_VALID(this);
979
- return this;
980
- }
981
-
982
- /** Returns this color expressed in hsla format
983
- * @return {Array<number>} */
984
- HSLA()
985
- {
986
- const r = clamp(this.r);
987
- const g = clamp(this.g);
988
- const b = clamp(this.b);
989
- const a = clamp(this.a);
990
- const maxC = max(r, g, b);
991
- const minC = min(r, g, b);
992
- const l = (maxC + minC) / 2;
993
- let h = 0, s = 0;
994
- if (maxC !== minC)
995
- {
996
- let d = maxC - minC;
997
- s = l > .5 ? d / (2 - maxC - minC) : d / (maxC + minC);
998
- if (r === maxC)
999
- h = (g - b) / d + (g < b ? 6 : 0);
1000
- else if (g === maxC)
1001
- h = (b - r) / d + 2;
1002
- else if (b === maxC)
1003
- h = (r - g) / d + 4;
1004
- }
1005
- return [h / 6, s, l, a];
1006
- }
1007
-
1008
- /** Returns a new color that has each component randomly adjusted
1009
- * @param {number} [amount]
1010
- * @param {number} [alphaAmount]
1011
- * @return {Color} */
1012
- mutate(amount=.05, alphaAmount=0)
1013
- {
1014
- ASSERT_NUMBER_VALID(amount);
1015
- ASSERT_NUMBER_VALID(alphaAmount);
1016
- return new Color
1017
- (
1018
- this.r + rand(amount, -amount),
1019
- this.g + rand(amount, -amount),
1020
- this.b + rand(amount, -amount),
1021
- this.a + rand(alphaAmount, -alphaAmount)
1022
- ).clamp();
1023
- }
1024
-
1025
- /** Returns this color expressed as a hex color code
1026
- * @param {boolean} [useAlpha] - if alpha should be included in result
1027
- * @return {string} */
1028
- toString(useAlpha = true)
1029
- {
1030
- if (debug && !this.isValid())
1031
- return `#000`;
1032
- const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
1033
- return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
1034
- }
1035
-
1036
- /** Set this color from a hex code
1037
- * @param {string} hex - html hex code
1038
- * @return {Color} */
1039
- setHex(hex)
1040
- {
1041
- ASSERT(isString(hex), 'Color hex code must be a string');
1042
- ASSERT(hex[0] === '#', 'Color hex code must start with #');
1043
- ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
1044
-
1045
- if (hex.length < 6)
1046
- {
1047
- const fromHex = (c)=> clamp(parseInt(hex[c],16)/15);
1048
- this.r = fromHex(1);
1049
- this.g = fromHex(2);
1050
- this.b = fromHex(3);
1051
- this.a = hex.length === 5 ? fromHex(4) : 1;
1052
- }
1053
- else
1054
- {
1055
- const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
1056
- this.r = fromHex(1);
1057
- this.g = fromHex(3);
1058
- this.b = fromHex(5);
1059
- this.a = hex.length === 9 ? fromHex(7) : 1;
1060
- }
1061
-
1062
- ASSERT_COLOR_VALID(this);
1063
- return this;
1064
- }
1065
-
1066
- /** Returns this color expressed as 32 bit RGBA value
1067
- * @return {number} */
1068
- rgbaInt()
53
+ if (canvas instanceof OffscreenCanvas)
1069
54
  {
1070
- const r = clamp(this.r)*255|0;
1071
- const g = clamp(this.g)*255<<8;
1072
- const b = clamp(this.b)*255<<16;
1073
- const a = clamp(this.a)*255<<24;
1074
- return r + g + b + a;
55
+ // copy to temporary canvas and save
56
+ const saveCanvas = document.createElement('canvas');
57
+ saveCanvas.width = canvas.width;
58
+ saveCanvas.height = canvas.height;
59
+ saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
60
+ saveDataURL(saveCanvas.toDataURL(type), filename);
1075
61
  }
1076
-
1077
- /** Checks if this is a valid color
1078
- * @return {boolean} */
1079
- isValid()
1080
- { return isNumber(this.r) && isNumber(this.g) && isNumber(this.b) && isNumber(this.a); }
62
+ else
63
+ saveDataURL(canvas.toDataURL(type), filename);
1081
64
  }
1082
65
 
1083
- ///////////////////////////////////////////////////////////////////////////////
1084
- // Default Colors
1085
-
1086
- /** Color - White #ffffff
1087
- * @type {Color}
1088
- * @memberof Utilities */
1089
- const WHITE = debugProtectConstant(rgb());
1090
-
1091
- /** Color - Clear White #757474ff with 0 alpha
1092
- * @type {Color}
1093
- * @memberof Utilities */
1094
- const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
1095
-
1096
- /** Color - Black #000000
1097
- * @type {Color}
1098
- * @memberof Utilities */
1099
- const BLACK = debugProtectConstant(rgb(0,0,0));
1100
-
1101
- /** Color - Clear Black #000000 with 0 alpha
1102
- * @type {Color}
1103
- * @memberof Utilities */
1104
- const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
1105
-
1106
- /** Color - Gray #808080
1107
- * @type {Color}
1108
- * @memberof Utilities */
1109
- const GRAY = debugProtectConstant(rgb(.5,.5,.5));
1110
-
1111
- /** Color - Red #ff0000
1112
- * @type {Color}
1113
- * @memberof Utilities */
1114
- const RED = debugProtectConstant(rgb(1,0,0));
1115
-
1116
- /** Color - Orange #ff8000
1117
- * @type {Color}
1118
- * @memberof Utilities */
1119
- const ORANGE = debugProtectConstant(rgb(1,.5,0));
1120
-
1121
- /** Color - Yellow #ffff00
1122
- * @type {Color}
1123
- * @memberof Utilities */
1124
- const YELLOW = debugProtectConstant(rgb(1,1,0));
1125
-
1126
- /** Color - Green #00ff00
1127
- * @type {Color}
1128
- * @memberof Utilities */
1129
- const GREEN = debugProtectConstant(rgb(0,1,0));
1130
-
1131
- /** Color - Cyan #00ffff
1132
- * @type {Color}
1133
- * @memberof Utilities */
1134
- const CYAN = debugProtectConstant(rgb(0,1,1));
1135
-
1136
- /** Color - Blue #0000ff
1137
- * @type {Color}
66
+ /** Save a data url to disk
67
+ * @param {string} url
68
+ * @param {string} [filename]
69
+ * @param {number} [revokeTime] - how long before revoking the url
1138
70
  * @memberof Utilities */
1139
- const BLUE = debugProtectConstant(rgb(0,0,1));
1140
-
1141
- /** Color - Purple #8000ff
1142
- * @type {Color}
1143
- * @memberof Utilities */
1144
- const PURPLE = debugProtectConstant(rgb(.5,0,1));
1145
-
1146
- /** Color - Magenta #ff00ff
1147
- * @type {Color}
1148
- * @memberof Utilities */
1149
- const MAGENTA = debugProtectConstant(rgb(1,0,1));
1150
-
1151
- ///////////////////////////////////////////////////////////////////////////////
1152
-
1153
- /**
1154
- * Timer object tracks how long has passed since it was set
1155
- * @memberof Engine
1156
- * @example
1157
- * let a = new Timer; // creates a timer that is not set
1158
- * a.set(3); // sets the timer to 3 seconds
1159
- *
1160
- * let b = new Timer(1); // creates a timer with 1 second left
1161
- * b.unset(); // unset the timer
1162
- */
1163
- class Timer
71
+ function saveDataURL(url, filename='download', revokeTime)
1164
72
  {
1165
- /** Create a timer object set time passed in
1166
- * @param {number} [timeLeft] - How much time left before the timer
1167
- * @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
1168
- constructor(timeLeft, useRealTime=false)
1169
- {
1170
- ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
1171
- this.useRealTime = useRealTime;
1172
- const globalTime = this.getGlobalTime();
1173
- this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
1174
- this.setTime = timeLeft;
1175
- }
1176
-
1177
- /** Set the timer with seconds passed in
1178
- * @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
1179
- set(timeLeft=0)
1180
- {
1181
- ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
1182
- const globalTime = this.getGlobalTime();
1183
- this.time = globalTime + timeLeft;
1184
- this.setTime = timeLeft;
1185
- }
1186
-
1187
- /** Set if the timer should keep running even when the game is paused
1188
- * @param {boolean} [useRealTime] */
1189
- setUseRealTime(useRealTime=true)
1190
- {
1191
- ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
1192
- this.useRealTime = useRealTime;
1193
- }
1194
-
1195
- /** Unset the timer */
1196
- unset() { this.time = undefined; }
1197
-
1198
- /** Returns true if set
1199
- * @return {boolean} */
1200
- isSet() { return this.time !== undefined; }
1201
-
1202
- /** Returns true if set and has not elapsed
1203
- * @return {boolean} */
1204
- active() { return this.getGlobalTime() < this.time; }
1205
-
1206
- /** Returns true if set and elapsed
1207
- * @return {boolean} */
1208
- elapsed() { return this.getGlobalTime() >= this.time; }
1209
-
1210
- /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
1211
- * @return {number} */
1212
- get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
1213
-
1214
- /** Get percentage elapsed based on time it was set to, returns 0 if not set
1215
- * @return {number} */
1216
- getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
1217
-
1218
- /** Get the time this timer was set to, returns 0 if not set
1219
- * @return {number} */
1220
- getSetTime() { return this.isSet() ? this.setTime : 0; }
1221
-
1222
- /** Get the current global time this timer is based on
1223
- * @return {number} */
1224
- getGlobalTime() { return this.useRealTime ? timeReal : time; }
1225
-
1226
- /** Returns this timer expressed as a string
1227
- * @return {string} */
1228
- toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
1229
-
1230
- /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
1231
- * @return {number} */
1232
- valueOf() { return this.get(); }
73
+ // create link for saving screenshots
74
+ const link = document.createElement('a');
75
+ link.download = filename;
76
+ link.href = url;
77
+ link.click();
78
+ if (revokeTime !== undefined)
79
+ setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
1233
80
  }