littlejsengine 1.12.4 → 1.13.1

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 (88) hide show
  1. package/dist/box2d.wasm.js +630 -0
  2. package/dist/box2d.wasm.wasm +0 -0
  3. package/dist/littlejs.d.ts +437 -219
  4. package/dist/littlejs.esm.js +5950 -5307
  5. package/dist/littlejs.esm.min.js +4 -1
  6. package/dist/littlejs.js +5921 -5295
  7. package/dist/littlejs.min.js +4 -1
  8. package/dist/littlejs.release.js +5485 -4886
  9. package/examples/box2d/game.js +37 -42
  10. package/examples/box2d/gameObjects.js +41 -37
  11. package/examples/box2d/index.html +2 -2
  12. package/examples/box2d/scenes.js +24 -20
  13. package/examples/box2d/tiles.png +0 -0
  14. package/examples/breakout/game.js +1 -1
  15. package/examples/breakout/gameObjects.js +2 -2
  16. package/examples/breakout/index.html +1 -1
  17. package/examples/breakoutTutorial/README.md +2 -2
  18. package/examples/breakoutTutorial/game.js +1 -1
  19. package/examples/breakoutTutorial/index.html +1 -1
  20. package/examples/empty/index.html +1 -1
  21. package/examples/htmlMenu/index.html +1 -1
  22. package/examples/index.html +410 -159
  23. package/examples/module/game.js +1 -1
  24. package/examples/module/index.html +1 -1
  25. package/examples/platformer/game.js +6 -15
  26. package/examples/platformer/gameCharacter.js +6 -6
  27. package/examples/platformer/gameEffects.js +74 -57
  28. package/examples/platformer/gameLevel.js +61 -70
  29. package/examples/platformer/gameObjects.js +4 -4
  30. package/examples/platformer/index.html +1 -1
  31. package/examples/puzzle/index.html +1 -1
  32. package/examples/shorts/base.html +24 -3
  33. package/examples/shorts/box2d.js +46 -0
  34. package/examples/shorts/box2dCar.js +45 -0
  35. package/examples/shorts/flappyGame.js +52 -0
  36. package/examples/shorts/helloWorld.js +6 -3
  37. package/examples/shorts/hillGlideGame.js +56 -0
  38. package/examples/shorts/landerGame.js +32 -0
  39. package/examples/shorts/maze.js +47 -0
  40. package/examples/shorts/medals.js +42 -0
  41. package/examples/shorts/nineSlice.js +21 -0
  42. package/examples/shorts/piano.js +52 -0
  43. package/examples/shorts/platformer.js +24 -20
  44. package/examples/shorts/{pong.js → pongGame.js} +1 -1
  45. package/examples/shorts/postProcess.js +45 -0
  46. package/examples/shorts/raycasting.js +71 -0
  47. package/examples/shorts/slidingPuzzle.js +42 -0
  48. package/examples/shorts/spaceGame.js +56 -0
  49. package/examples/shorts/starfield.js +17 -0
  50. package/examples/shorts/tileLayer.js +3 -5
  51. package/examples/shorts/tiles.png +0 -0
  52. package/examples/shorts/tiltedView.js +8 -7
  53. package/examples/shorts/topDown.js +18 -26
  54. package/examples/shorts/uiSystem.js +36 -0
  55. package/examples/starter/build.bat +6 -1
  56. package/examples/starter/game.js +4 -2
  57. package/examples/starter/index.html +23 -13
  58. package/examples/stress/index.html +2 -3
  59. package/examples/style.css +136 -0
  60. package/examples/typescript/build.js +1 -2
  61. package/examples/typescript/game.js +2 -2
  62. package/examples/typescript/game.ts +1 -1
  63. package/examples/typescript/index.html +1 -1
  64. package/examples/uiSystem/game.js +9 -25
  65. package/examples/uiSystem/index.html +1 -1
  66. package/package.json +1 -1
  67. package/plugins/box2d.js +29 -35
  68. package/plugins/drawUtilities.js +126 -0
  69. package/plugins/newgrounds.js +1 -1
  70. package/plugins/pluginExport.js +8 -2
  71. package/plugins/postProcess.js +2 -2
  72. package/plugins/uiSystem.js +162 -68
  73. package/plugins/zzfxm.js +1 -1
  74. package/reference.md +10 -10
  75. package/src/engine.js +59 -39
  76. package/src/engineAudio.js +38 -20
  77. package/src/engineBuild.bat +6 -1
  78. package/src/engineBuild.js +32 -7
  79. package/src/engineDebug.js +53 -26
  80. package/src/engineDraw.js +108 -57
  81. package/src/engineExport.js +22 -9
  82. package/src/engineInput.js +112 -40
  83. package/src/engineObject.js +68 -67
  84. package/src/engineParticles.js +26 -9
  85. package/src/engineSettings.js +15 -16
  86. package/src/engineTileLayer.js +312 -153
  87. package/src/engineUtilities.js +198 -130
  88. package/src/engineWebGL.js +58 -35
@@ -22,19 +22,17 @@ const PI = Math.PI;
22
22
  * @memberof Utilities */
23
23
  function abs(value) { return Math.abs(value); }
24
24
 
25
- /** Returns lowest of two values passed in
26
- * @param {number} valueA
27
- * @param {number} valueB
25
+ /** Returns lowest value passed in
26
+ * @param {...number} values
28
27
  * @return {number}
29
28
  * @memberof Utilities */
30
- function min(valueA, valueB) { return Math.min(valueA, valueB); }
29
+ function min(...values) { return Math.min(...values); }
31
30
 
32
- /** Returns highest of two values passed in
33
- * @param {number} valueA
34
- * @param {number} valueB
31
+ /** Returns highest value passed in
32
+ * @param {...number} values
35
33
  * @return {number}
36
34
  * @memberof Utilities */
37
- function max(valueA, valueB) { return Math.max(valueA, valueB); }
35
+ function max(...values) { return Math.max(...values); }
38
36
 
39
37
  /** Returns the sign of value passed in
40
38
  * @param {number} value
@@ -67,12 +65,17 @@ function percent(value, valueA, valueB)
67
65
  { return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
68
66
 
69
67
  /** Linearly interpolates between values passed in using percent
70
- * @param {number} percent
71
68
  * @param {number} valueA
72
69
  * @param {number} valueB
70
+ * @param {number} percent
73
71
  * @return {number}
74
72
  * @memberof Utilities */
75
- function lerp(percent, valueA, valueB) { return valueA + clamp(percent) * (valueB-valueA); }
73
+ function lerp(valueA, valueB, percent)
74
+ {
75
+ if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
76
+ console.warn('lerp() parameter order changed! use lerp(start, end, p)');
77
+ return valueA + clamp(percent) * (valueB-valueA);
78
+ }
76
79
 
77
80
  /** Returns signed wrapped distance between the two values passed in
78
81
  * @param {number} valueA
@@ -84,14 +87,18 @@ function distanceWrap(valueA, valueB, wrapSize=1)
84
87
  { const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
85
88
 
86
89
  /** Linearly interpolates between values passed in with wrapping
87
- * @param {number} percent
88
90
  * @param {number} valueA
89
91
  * @param {number} valueB
92
+ * @param {number} percent
90
93
  * @param {number} [wrapSize]
91
94
  * @returns {number}
92
95
  * @memberof Utilities */
93
- function lerpWrap(percent, valueA, valueB, wrapSize=1)
94
- { return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
96
+ function lerpWrap(valueA, valueB, percent, wrapSize=1)
97
+ {
98
+ if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
99
+ console.warn('lerpWrap() parameter order changed! use lerpWrap(start, end, p)');
100
+ return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize);
101
+ }
95
102
 
96
103
  /** Returns signed wrapped distance between the two angles passed in
97
104
  * @param {number} angleA
@@ -101,12 +108,12 @@ function lerpWrap(percent, valueA, valueB, wrapSize=1)
101
108
  function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
102
109
 
103
110
  /** Linearly interpolates between the angles passed in with wrapping
104
- * @param {number} percent
105
111
  * @param {number} angleA
106
112
  * @param {number} angleB
113
+ * @param {number} percent
107
114
  * @returns {number}
108
115
  * @memberof Utilities */
109
- function lerpAngle(percent, angleA, angleB) { return lerpWrap(percent, angleA, angleB, 2*PI); }
116
+ function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
110
117
 
111
118
  /** Applies smoothstep function to the percentage value
112
119
  * @param {number} percent
@@ -114,6 +121,12 @@ function lerpAngle(percent, angleA, angleB) { return lerpWrap(percent, angleA, a
114
121
  * @memberof Utilities */
115
122
  function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
116
123
 
124
+ /** Checks if the value passed in is a power of two
125
+ * @param {number} value
126
+ * @return {boolean}
127
+ * @memberof Utilities */
128
+ function isPowerOfTwo(value) { return !(value & (value - 1)); }
129
+
117
130
  /** Returns the nearest power of two not less then the value
118
131
  * @param {number} value
119
132
  * @return {number}
@@ -189,6 +202,24 @@ function wave(frequency=1, amplitude=1, t=time)
189
202
  * @memberof Utilities */
190
203
  function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
191
204
 
205
+ /** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
206
+ * @param {string} url - URL of JSON file
207
+ * @return {Promise<object>}
208
+ * @memberof Utilities */
209
+ async function fetchJSON(url)
210
+ {
211
+ const response = await fetch(url);
212
+ return response.json();
213
+ }
214
+
215
+ /**
216
+ * Check if object is a valid number, not NaN or undefined, but it may be infinite
217
+ * @param {any} n
218
+ * @return {boolean}
219
+ * @memberof Utilities
220
+ */
221
+ function isNumber(n) { return typeof n == 'number' && !isNaN(n); }
222
+
192
223
  ///////////////////////////////////////////////////////////////////////////////
193
224
 
194
225
  /** Random global functions
@@ -209,6 +240,12 @@ function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valu
209
240
  * @memberof Random */
210
241
  function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
211
242
 
243
+ /** Randomly returns true or false given the chance of true passed in
244
+ * @param {number} [chance]
245
+ * @return {boolean}
246
+ * @memberof Random */
247
+ function randBool(chance=.5) { return rand() < chance; }
248
+
212
249
  /** Randomly returns either -1 or 1
213
250
  * @return {number}
214
251
  * @memberof Random */
@@ -218,7 +255,7 @@ function randSign() { return randInt(2) * 2 - 1; }
218
255
  * @param {number} [length]
219
256
  * @return {Vector2}
220
257
  * @memberof Random */
221
- function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
258
+ function randVec2(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
222
259
 
223
260
  /** Returns a random Vector2 within a circular shape
224
261
  * @param {number} [radius]
@@ -226,7 +263,7 @@ function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length
226
263
  * @return {Vector2}
227
264
  * @memberof Random */
228
265
  function randInCircle(radius=1, minRadius=0)
229
- { return radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
266
+ { return radius > 0 ? randVec2(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
230
267
 
231
268
  /** Returns a random color between the two passed in colors, combine components if linear
232
269
  * @param {Color} [colorA=(1,1,1,1)]
@@ -255,8 +292,8 @@ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
255
292
  class RandomGenerator
256
293
  {
257
294
  /** Create a random number generator with the seed passed in
258
- * @param {number} seed - Starting seed */
259
- constructor(seed)
295
+ * @param {number} [seed] - Starting seed or engine default seed */
296
+ constructor(seed = 123456789)
260
297
  {
261
298
  /** @property {number} - random seed */
262
299
  this.seed = seed;
@@ -281,14 +318,36 @@ class RandomGenerator
281
318
  * @return {number} */
282
319
  int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
283
320
 
321
+ /** Randomly returns true or false given the chance of true passed in
322
+ * @param {number} [chance]
323
+ * @return {boolean} */
324
+ bool(chance=.5) { return this.float() < chance; }
325
+
284
326
  /** Randomly returns either -1 or 1 deterministically
285
327
  * @return {number} */
286
328
  sign() { return this.float() > .5 ? 1 : -1; }
329
+
330
+ /** Returns a seeded random value between the two values passed in with a random sign
331
+ * @param {number} [valueA]
332
+ * @param {number} [valueB]
333
+ * @return {number} */
334
+ floatSign(valueA=1, valueB=0) { return this.float(valueA, valueB) * this.sign(); }
335
+
336
+ /** Returns a random angle between -PI and PI
337
+ * @return {number} */
338
+ angle() { return this.float(-PI, PI); }
339
+
340
+ /** Returns a seeded vec2 with size between the two values passed in
341
+ * @param {number} valueA
342
+ * @param {number} [valueB]
343
+ * @return {Vector2} */
344
+ vec2(valueA=1, valueB=0)
345
+ { return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
287
346
  }
288
347
 
289
348
  ///////////////////////////////////////////////////////////////////////////////
290
349
 
291
- /**
350
+ /**
292
351
  * Create a 2d vector, can take 1 or 2 scalar values
293
352
  * @param {number} [x]
294
353
  * @param {number} [y] - if y is undefined, x is used for both
@@ -299,7 +358,7 @@ class RandomGenerator
299
358
  * b = vec2(); // set b to (0, 0)
300
359
  * @memberof Utilities
301
360
  */
302
- function vec2(x=0, y) { return new Vector2(x, y == undefined? x : y); }
361
+ function vec2(x=0, y) { return new Vector2(x, y === undefined ? x : y); }
303
362
 
304
363
  /**
305
364
  * Check if object is a valid Vector2
@@ -309,6 +368,15 @@ function vec2(x=0, y) { return new Vector2(x, y == undefined? x : y); }
309
368
  */
310
369
  function isVector2(v) { return v instanceof Vector2; }
311
370
 
371
+ // vector2 asserts
372
+ function ASSERT_VECTOR2_VALID(v) { ASSERT(isVector2(v) && v.isValid(), 'Vector2 is invalid.', v); }
373
+ function ASSERT_NUMBER_VALID(n) { ASSERT(isNumber(n), 'Number is invalid.', n); }
374
+ function ASSERT_VECTOR2_NORMAL(v)
375
+ {
376
+ ASSERT_VECTOR2_VALID(v);
377
+ ASSERT(abs(v.lengthSquared()-1) < .01, 'Vector2 is not normal.', v);
378
+ }
379
+
312
380
  /**
313
381
  * 2D Vector object with vector math library
314
382
  * - Functions do not change this so they can be chained together
@@ -329,7 +397,7 @@ class Vector2
329
397
  this.x = x;
330
398
  /** @property {number} - Y axis location */
331
399
  this.y = y;
332
- ASSERT(this.isValid());
400
+ ASSERT(this.isValid(), 'Constructed Vector2 is invalid.', this);
333
401
  }
334
402
 
335
403
  /** Sets values of this vector and returns self
@@ -340,7 +408,6 @@ class Vector2
340
408
  {
341
409
  this.x = x;
342
410
  this.y = y;
343
- ASSERT(this.isValid());
344
411
  return this;
345
412
  }
346
413
 
@@ -351,47 +418,27 @@ class Vector2
351
418
  /** Returns a copy of this vector plus the vector passed in
352
419
  * @param {Vector2} v - other vector
353
420
  * @return {Vector2} */
354
- add(v)
355
- {
356
- ASSERT(isVector2(v));
357
- return new Vector2(this.x + v.x, this.y + v.y);
358
- }
421
+ add(v) { return new Vector2(this.x + v.x, this.y + v.y);}
359
422
 
360
423
  /** Returns a copy of this vector minus the vector passed in
361
424
  * @param {Vector2} v - other vector
362
425
  * @return {Vector2} */
363
- subtract(v)
364
- {
365
- ASSERT(isVector2(v));
366
- return new Vector2(this.x - v.x, this.y - v.y);
367
- }
426
+ subtract(v) { return new Vector2(this.x - v.x, this.y - v.y); }
368
427
 
369
428
  /** Returns a copy of this vector times the vector passed in
370
429
  * @param {Vector2} v - other vector
371
430
  * @return {Vector2} */
372
- multiply(v)
373
- {
374
- ASSERT(isVector2(v));
375
- return new Vector2(this.x * v.x, this.y * v.y);
376
- }
431
+ multiply(v) { return new Vector2(this.x * v.x, this.y * v.y); }
377
432
 
378
433
  /** Returns a copy of this vector divided by the vector passed in
379
434
  * @param {Vector2} v - other vector
380
435
  * @return {Vector2} */
381
- divide(v)
382
- {
383
- ASSERT(isVector2(v));
384
- return new Vector2(this.x / v.x, this.y / v.y);
385
- }
436
+ divide(v) { return new Vector2(this.x / v.x, this.y / v.y); }
386
437
 
387
438
  /** Returns a copy of this vector scaled by the vector passed in
388
439
  * @param {number} s - scale
389
440
  * @return {Vector2} */
390
- scale(s)
391
- {
392
- ASSERT(!isVector2(s));
393
- return new Vector2(this.x * s, this.y * s);
394
- }
441
+ scale(s) { return new Vector2(this.x * s, this.y * s); }
395
442
 
396
443
  /** Returns the length of this vector
397
444
  * @return {number} */
@@ -404,20 +451,12 @@ class Vector2
404
451
  /** Returns the distance from this vector to vector passed in
405
452
  * @param {Vector2} v - other vector
406
453
  * @return {number} */
407
- distance(v)
408
- {
409
- ASSERT(isVector2(v));
410
- return this.distanceSquared(v)**.5;
411
- }
454
+ distance(v) { return this.distanceSquared(v)**.5; }
412
455
 
413
456
  /** Returns the distance squared from this vector to vector passed in
414
457
  * @param {Vector2} v - other vector
415
458
  * @return {number} */
416
- distanceSquared(v)
417
- {
418
- ASSERT(isVector2(v));
419
- return (this.x - v.x)**2 + (this.y - v.y)**2;
420
- }
459
+ distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
421
460
 
422
461
  /** Returns a new vector in same direction as this one with the length passed in
423
462
  * @param {number} [length]
@@ -440,20 +479,19 @@ class Vector2
440
479
  /** Returns the dot product of this and the vector passed in
441
480
  * @param {Vector2} v - other vector
442
481
  * @return {number} */
443
- dot(v)
444
- {
445
- ASSERT(isVector2(v));
446
- return this.x*v.x + this.y*v.y;
447
- }
482
+ dot(v) { return this.x*v.x + this.y*v.y; }
448
483
 
449
484
  /** Returns the cross product of this and the vector passed in
450
485
  * @param {Vector2} v - other vector
451
486
  * @return {number} */
452
- cross(v)
453
- {
454
- ASSERT(isVector2(v));
455
- return this.x*v.y - this.y*v.x;
456
- }
487
+ cross(v) { return this.x*v.y - this.y*v.x; }
488
+
489
+ /** Returns a copy this vector reflected by the surface normal
490
+ * @param {Vector2} normal - surface normal (should be normalized)
491
+ * @param {number} restitution - how much to bounce, 1 is perfect bounce, 0 is no bounce
492
+ * @return {Vector2} */
493
+ reflect(normal, restitution=1)
494
+ { return this.subtract(normal.scale((1+restitution)*this.dot(normal))); }
457
495
 
458
496
  /** Returns the clockwise angle of this vector, up is angle 0
459
497
  * @return {number} */
@@ -465,6 +503,8 @@ class Vector2
465
503
  * @return {Vector2} */
466
504
  setAngle(angle=0, length=1)
467
505
  {
506
+ ASSERT_NUMBER_VALID(angle);
507
+ ASSERT_NUMBER_VALID(length);
468
508
  this.x = length*Math.sin(angle);
469
509
  this.y = length*Math.cos(angle);
470
510
  return this;
@@ -474,7 +514,8 @@ class Vector2
474
514
  * @param {number} angle
475
515
  * @return {Vector2} */
476
516
  rotate(angle)
477
- {
517
+ {
518
+ ASSERT_NUMBER_VALID(angle);
478
519
  const c = Math.cos(-angle), s = Math.sin(-angle);
479
520
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
480
521
  }
@@ -484,8 +525,11 @@ class Vector2
484
525
  * @param {number} [length] */
485
526
  setDirection(direction, length=1)
486
527
  {
528
+ ASSERT_NUMBER_VALID(direction);
529
+ ASSERT_NUMBER_VALID(length);
487
530
  direction = mod(direction, 4);
488
- ASSERT(direction==0 || direction==1 || direction==2 || direction==3);
531
+ ASSERT(direction==0 || direction==1 || direction==2 || direction==3,
532
+ 'Vector2.setDirection() direction must be an integer between 0 and 3.');
489
533
  return vec2(direction%2 ? direction-1 ? -length : length : 0,
490
534
  direction%2 ? 0 : direction ? -length : length);
491
535
  }
@@ -499,10 +543,20 @@ class Vector2
499
543
  * @return {Vector2} */
500
544
  invert() { return new Vector2(this.y, -this.x); }
501
545
 
546
+ /** Returns a copy of this vector absolute values
547
+ * @return {Vector2} */
548
+ abs() { return new Vector2(abs(this.x), abs(this.y)); }
549
+
502
550
  /** Returns a copy of this vector with each axis floored
503
551
  * @return {Vector2} */
504
552
  floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
505
553
 
554
+ /** Returns new vec2 with modded values
555
+ * @param {number} [divisor]
556
+ * @return {Vector2} */
557
+ mod(divisor=1)
558
+ { return new Vector2(mod(this.x, divisor), mod(this.y, divisor)); }
559
+
506
560
  /** Returns the area this vector covers as a rectangle
507
561
  * @return {number} */
508
562
  area() { return abs(this.x * this.y); }
@@ -513,35 +567,36 @@ class Vector2
513
567
  * @return {Vector2} */
514
568
  lerp(v, percent)
515
569
  {
516
- ASSERT(isVector2(v));
517
- return this.add(v.subtract(this).scale(clamp(percent)));
570
+ ASSERT_VECTOR2_VALID(v);
571
+ ASSERT_NUMBER_VALID(percent);
572
+ const p = clamp(percent);
573
+ return new Vector2(v.x*p + this.x*(1-p), v.y*p + this.y*(1-p));
518
574
  }
519
575
 
520
576
  /** Returns true if this vector is within the bounds of an array size passed in
521
577
  * @param {Vector2} arraySize
522
578
  * @return {boolean} */
523
579
  arrayCheck(arraySize)
524
- {
525
- ASSERT(isVector2(arraySize));
526
- return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y;
527
- }
580
+ { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
528
581
 
529
582
  /** Returns this vector expressed as a string
530
583
  * @param {number} digits - precision to display
531
584
  * @return {string} */
532
585
  toString(digits=3)
533
586
  {
587
+ ASSERT_NUMBER_VALID(digits);
534
588
  if (debug)
535
- return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
589
+ {
590
+ if (this.isValid())
591
+ return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
592
+ else
593
+ return `(${this.x}, ${this.y})`;
594
+ }
536
595
  }
537
596
 
538
597
  /** Checks if this is a valid vector
539
598
  * @return {boolean} */
540
- isValid()
541
- {
542
- return typeof this.x == 'number' && !isNaN(this.x)
543
- && typeof this.y == 'number' && !isNaN(this.y);
544
- }
599
+ isValid() { return isNumber(this.x) && isNumber(this.y); }
545
600
  }
546
601
 
547
602
  ///////////////////////////////////////////////////////////////////////////////
@@ -576,13 +631,16 @@ function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
576
631
  */
577
632
  function isColor(c) { return c instanceof Color; }
578
633
 
634
+ // color asserts
635
+ function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c) && c.isValid(), 'Color is invalid.', c); }
636
+
579
637
  /**
580
638
  * Color object (red, green, blue, alpha) with some helpful functions
581
639
  * @example
582
640
  * let a = new Color; // white
583
641
  * let b = new Color(1, 0, 0); // red
584
642
  * let c = new Color(0, 0, 0, 0); // transparent black
585
- * let d = rgb(0, 0, 1); // blue using rgb color
643
+ * let d = rgb(0, 0, 1); // blue using rgb color
586
644
  * let e = hsl(.3, 1, .5); // green using hsl color
587
645
  */
588
646
  class Color
@@ -602,7 +660,7 @@ class Color
602
660
  this.b = b;
603
661
  /** @property {number} - Alpha */
604
662
  this.a = a;
605
- ASSERT(this.isValid());
663
+ ASSERT(this.isValid(), 'Constructed Color is invalid.', this);
606
664
  }
607
665
 
608
666
  /** Sets values of this color and returns self
@@ -617,7 +675,6 @@ class Color
617
675
  this.g = g;
618
676
  this.b = b;
619
677
  this.a = a;
620
- ASSERT(this.isValid());
621
678
  return this;
622
679
  }
623
680
 
@@ -628,38 +685,22 @@ class Color
628
685
  /** Returns a copy of this color plus the color passed in
629
686
  * @param {Color} c - other color
630
687
  * @return {Color} */
631
- add(c)
632
- {
633
- ASSERT(isColor(c));
634
- return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a);
635
- }
688
+ add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
636
689
 
637
690
  /** Returns a copy of this color minus the color passed in
638
691
  * @param {Color} c - other color
639
692
  * @return {Color} */
640
- subtract(c)
641
- {
642
- ASSERT(isColor(c));
643
- return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a);
644
- }
693
+ subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
645
694
 
646
695
  /** Returns a copy of this color times the color passed in
647
696
  * @param {Color} c - other color
648
697
  * @return {Color} */
649
- multiply(c)
650
- {
651
- ASSERT(isColor(c));
652
- return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a);
653
- }
698
+ multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
654
699
 
655
700
  /** Returns a copy of this color divided by the color passed in
656
701
  * @param {Color} c - other color
657
702
  * @return {Color} */
658
- divide(c)
659
- {
660
- ASSERT(isColor(c));
661
- return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a);
662
- }
703
+ divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
663
704
 
664
705
  /** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
665
706
  * @param {number} scale
@@ -678,8 +719,14 @@ class Color
678
719
  * @return {Color} */
679
720
  lerp(c, percent)
680
721
  {
681
- ASSERT(isColor(c));
682
- return this.add(c.subtract(this).scale(clamp(percent)));
722
+ ASSERT_COLOR_VALID(c);
723
+ ASSERT_NUMBER_VALID(percent);
724
+ const p = clamp(percent);
725
+ return new Color(
726
+ c.r*p + this.r*(1-p),
727
+ c.g*p + this.g*(1-p),
728
+ c.b*p + this.b*(1-p),
729
+ c.a*p + this.a*(1-p));
683
730
  }
684
731
 
685
732
  /** Sets this color given a hue, saturation, lightness, and alpha
@@ -702,7 +749,7 @@ class Color
702
749
  this.g = f(p, q, h);
703
750
  this.b = f(p, q, h - 1/3);
704
751
  this.a = a;
705
- ASSERT(this.isValid());
752
+ ASSERT_COLOR_VALID(this);
706
753
  return this;
707
754
  }
708
755
 
@@ -714,20 +761,19 @@ class Color
714
761
  const g = clamp(this.g);
715
762
  const b = clamp(this.b);
716
763
  const a = clamp(this.a);
717
- const max = Math.max(r, g, b);
718
- const min = Math.min(r, g, b);
719
- const l = (max + min) / 2;
720
-
764
+ const maxC = max(r, g, b);
765
+ const minC = min(r, g, b);
766
+ const l = (maxC + minC) / 2;
721
767
  let h = 0, s = 0;
722
- if (max != min)
768
+ if (maxC != minC)
723
769
  {
724
- let d = max - min;
725
- s = l > .5 ? d / (2 - max - min) : d / (max + min);
726
- if (r == max)
770
+ let d = maxC - minC;
771
+ s = l > .5 ? d / (2 - maxC - minC) : d / (maxC + minC);
772
+ if (r == maxC)
727
773
  h = (g - b) / d + (g < b ? 6 : 0);
728
- else if (g == max)
774
+ else if (g == maxC)
729
775
  h = (b - r) / d + 2;
730
- else if (b == max)
776
+ else if (b == maxC)
731
777
  h = (r - g) / d + 4;
732
778
  }
733
779
  return [h / 6, s, l, a];
@@ -739,6 +785,8 @@ class Color
739
785
  * @return {Color} */
740
786
  mutate(amount=.05, alphaAmount=0)
741
787
  {
788
+ ASSERT_NUMBER_VALID(amount);
789
+ ASSERT_NUMBER_VALID(alphaAmount);
742
790
  return new Color
743
791
  (
744
792
  this.r + rand(amount, -amount),
@@ -752,7 +800,10 @@ class Color
752
800
  * @param {boolean} [useAlpha] - if alpha should be included in result
753
801
  * @return {string} */
754
802
  toString(useAlpha = true)
755
- {
803
+ {
804
+ ASSERT(typeof useAlpha == 'boolean', 'Use alpha boolean is invalid.', useAlpha);
805
+ if (debug && !this.isValid())
806
+ return `#000`;
756
807
  const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
757
808
  return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
758
809
  }
@@ -762,7 +813,7 @@ class Color
762
813
  * @return {Color} */
763
814
  setHex(hex)
764
815
  {
765
- ASSERT(typeof hex == 'string' && hex[0] == '#');
816
+ ASSERT(typeof hex == 'string' && hex[0] == '#', 'Color hex code must be a string starting with #');
766
817
  ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
767
818
 
768
819
  if (hex.length < 6)
@@ -782,7 +833,7 @@ class Color
782
833
  this.a = hex.length == 9 ? fromHex(7) : 1;
783
834
  }
784
835
 
785
- ASSERT(this.isValid());
836
+ ASSERT_COLOR_VALID(this);
786
837
  return this;
787
838
  }
788
839
 
@@ -800,11 +851,8 @@ class Color
800
851
  /** Checks if this is a valid color
801
852
  * @return {boolean} */
802
853
  isValid()
803
- {
804
- return typeof this.r == 'number' && !isNaN(this.r)
805
- && typeof this.g == 'number' && !isNaN(this.g)
806
- && typeof this.b == 'number' && !isNaN(this.b)
807
- && typeof this.a == 'number' && !isNaN(this.a);
854
+ {
855
+ return isNumber(this.r) && isNumber(this.g) && isNumber(this.b) && isNumber(this.a);
808
856
  }
809
857
  }
810
858
 
@@ -816,11 +864,21 @@ class Color
816
864
  * @memberof Utilities */
817
865
  const WHITE = rgb();
818
866
 
867
+ /** Color - Clear White #ffffff with 0 alpha
868
+ * @type {Color}
869
+ * @memberof Utilities */
870
+ const CLEAR_WHITE = rgb(1,1,1,0);
871
+
819
872
  /** Color - Black #000000
820
873
  * @type {Color}
821
874
  * @memberof Utilities */
822
875
  const BLACK = rgb(0,0,0);
823
876
 
877
+ /** Color - Clear Black #000000 with 0 alpha
878
+ * @type {Color}
879
+ * @memberof Utilities */
880
+ const CLEAR_BLACK = rgb(0,0,0,0);
881
+
824
882
  /** Color - Gray #808080
825
883
  * @type {Color}
826
884
  * @memberof Utilities */
@@ -881,18 +939,28 @@ class Timer
881
939
  {
882
940
  /** Create a timer object set time passed in
883
941
  * @param {number} [timeLeft] - How much time left before the timer elapses in seconds */
884
- constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
942
+ constructor(timeLeft)
943
+ {
944
+ ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
945
+ this.time = timeLeft === undefined ? undefined : time + timeLeft;
946
+ this.setTime = timeLeft;
947
+ }
885
948
 
886
949
  /** Set the timer with seconds passed in
887
950
  * @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
888
- set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
951
+ set(timeLeft=0)
952
+ {
953
+ ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
954
+ this.time = time + timeLeft;
955
+ this.setTime = timeLeft;
956
+ }
889
957
 
890
958
  /** Unset the timer */
891
959
  unset() { this.time = undefined; }
892
960
 
893
961
  /** Returns true if set
894
962
  * @return {boolean} */
895
- isSet() { return this.time != undefined; }
963
+ isSet() { return this.time !== undefined; }
896
964
 
897
965
  /** Returns true if set and has not elapsed
898
966
  * @return {boolean} */
@@ -916,5 +984,5 @@ class Timer
916
984
 
917
985
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
918
986
  * @return {number} */
919
- valueOf() { return this.get(); }
987
+ valueOf() { return this.get(); }
920
988
  }