littlejsengine 1.12.6 → 1.13.4

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 (102) hide show
  1. package/README.md +15 -12
  2. package/dist/littlejs.d.ts +466 -236
  3. package/dist/littlejs.esm.js +6126 -5370
  4. package/dist/littlejs.esm.min.js +4 -1
  5. package/dist/littlejs.js +6093 -5359
  6. package/dist/littlejs.min.js +4 -1
  7. package/dist/littlejs.release.js +5614 -4907
  8. package/examples/box2d/game.js +2 -2
  9. package/examples/box2d/gameObjects.js +8 -7
  10. package/examples/box2d/index.html +1 -1
  11. package/examples/box2d/scenes.js +8 -8
  12. package/examples/breakout/game.js +1 -1
  13. package/examples/breakout/gameObjects.js +2 -2
  14. package/examples/breakout/index.html +1 -1
  15. package/examples/breakoutTutorial/README.md +46 -43
  16. package/examples/breakoutTutorial/game.js +3 -3
  17. package/examples/breakoutTutorial/index.html +1 -1
  18. package/examples/electron/build.bat +7 -0
  19. package/examples/electron/build.js +124 -0
  20. package/examples/electron/electron.js +35 -0
  21. package/examples/electron/game.js +140 -0
  22. package/examples/electron/index.html +13 -0
  23. package/examples/electron/package.json +12 -0
  24. package/examples/electron/tiles.png +0 -0
  25. package/examples/empty/game.js +1 -0
  26. package/examples/empty/index.html +1 -1
  27. package/examples/htmlMenu/game.js +1 -1
  28. package/examples/htmlMenu/index.html +1 -1
  29. package/examples/index.html +486 -182
  30. package/examples/module/game.js +6 -3
  31. package/examples/module/index.html +1 -1
  32. package/examples/particles/index.html +12 -3
  33. package/examples/platformer/game.js +4 -4
  34. package/examples/platformer/gameCharacter.js +6 -6
  35. package/examples/platformer/gameEffects.js +74 -57
  36. package/examples/platformer/gameLevel.js +61 -70
  37. package/examples/platformer/gameObjects.js +4 -4
  38. package/examples/platformer/index.html +1 -1
  39. package/examples/puzzle/index.html +1 -1
  40. package/examples/shorts/base.html +23 -3
  41. package/examples/shorts/blending.js +9 -5
  42. package/examples/shorts/box2d.js +1 -1
  43. package/examples/shorts/box2dCar.js +9 -13
  44. package/examples/shorts/clock.js +1 -1
  45. package/examples/shorts/colors.js +2 -2
  46. package/examples/shorts/flappyGame.js +52 -0
  47. package/examples/shorts/helloWorld.js +6 -3
  48. package/examples/shorts/hillGlideGame.js +56 -0
  49. package/examples/shorts/landerGame.js +32 -0
  50. package/examples/shorts/maze.js +47 -0
  51. package/examples/shorts/medals.js +42 -0
  52. package/examples/shorts/nineSlice.js +21 -0
  53. package/examples/shorts/piano.js +52 -0
  54. package/examples/shorts/platformer.js +24 -20
  55. package/examples/shorts/{pong.js → pongGame.js} +1 -1
  56. package/examples/shorts/raycasting.js +71 -0
  57. package/examples/shorts/shapes.js +9 -9
  58. package/examples/shorts/slidingPuzzle.js +42 -0
  59. package/examples/shorts/spaceGame.js +56 -0
  60. package/examples/shorts/starfield.js +15 -0
  61. package/examples/shorts/systemFont.js +1 -1
  62. package/examples/shorts/tileLayer.js +3 -5
  63. package/examples/shorts/tiles.png +0 -0
  64. package/examples/shorts/tiltedView.js +8 -7
  65. package/examples/shorts/topDown.js +18 -26
  66. package/examples/shorts/uiSystem.js +5 -7
  67. package/examples/starter/build.bat +6 -1
  68. package/examples/starter/build.js +9 -8
  69. package/examples/starter/game.js +7 -4
  70. package/examples/starter/index.html +23 -13
  71. package/examples/stress/index.html +7 -8
  72. package/examples/style.css +139 -0
  73. package/examples/typescript/build.js +2 -3
  74. package/examples/typescript/game.js +4 -4
  75. package/examples/typescript/game.ts +6 -3
  76. package/examples/typescript/index.html +1 -1
  77. package/examples/uiSystem/game.js +10 -25
  78. package/examples/uiSystem/index.html +1 -1
  79. package/package.json +2 -2
  80. package/plugins/box2d.js +22 -97
  81. package/plugins/drawUtilities.js +132 -0
  82. package/plugins/newgrounds.js +1 -1
  83. package/plugins/pluginExport.js +7 -1
  84. package/plugins/postProcess.js +9 -2
  85. package/plugins/uiSystem.js +155 -67
  86. package/plugins/zzfxm.js +1 -1
  87. package/reference.md +10 -10
  88. package/src/engine.js +56 -30
  89. package/src/engineAudio.js +15 -6
  90. package/src/engineBuild.bat +6 -1
  91. package/src/engineBuild.js +20 -5
  92. package/src/engineDebug.js +55 -28
  93. package/src/engineDraw.js +321 -177
  94. package/src/engineExport.js +27 -9
  95. package/src/engineInput.js +110 -38
  96. package/src/engineMedals.js +4 -4
  97. package/src/engineObject.js +71 -70
  98. package/src/engineParticles.js +33 -9
  99. package/src/engineSettings.js +69 -23
  100. package/src/engineTileLayer.js +312 -153
  101. package/src/engineUtilities.js +187 -135
  102. package/src/engineWebGL.js +70 -39
@@ -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,17 +121,24 @@ 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}
120
133
  * @memberof Utilities */
121
134
  function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
122
135
 
123
- /** Returns true if two axis aligned bounding boxes are overlapping
136
+ /** Returns true if two axis aligned bounding boxes are overlapping
137
+ * this can be used for simple collision detection between objects
124
138
  * @param {Vector2} posA - Center of box A
125
139
  * @param {Vector2} sizeA - Size of box A
126
140
  * @param {Vector2} posB - Center of box B
127
- * @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
141
+ * @param {Vector2} [sizeB=(0,0)] - Size of box B, uses a point if undefined
128
142
  * @return {boolean} - True if overlapping
129
143
  * @memberof Utilities */
130
144
  function isOverlapping(posA, sizeA, posB, sizeB=vec2())
@@ -178,10 +192,11 @@ function isIntersecting(start, end, pos, size)
178
192
  * @param {number} [frequency] - Frequency of the wave in Hz
179
193
  * @param {number} [amplitude] - Amplitude (max height) of the wave
180
194
  * @param {number} [t=time] - Value to use for time of the wave
195
+ * @param {number} [offset] - Value to use for time offset of the wave
181
196
  * @return {number} - Value waving between 0 and amplitude
182
197
  * @memberof Utilities */
183
- function wave(frequency=1, amplitude=1, t=time)
184
- { return amplitude/2 * (1 - Math.cos(t*frequency*2*PI)); }
198
+ function wave(frequency=1, amplitude=1, t=time, offset=0)
199
+ { return amplitude/2 * (1 - Math.cos(offset + t*frequency*2*PI)); }
185
200
 
186
201
  /** Formats seconds to mm:ss style for display purposes
187
202
  * @param {number} t - time in seconds
@@ -199,6 +214,14 @@ async function fetchJSON(url)
199
214
  return response.json();
200
215
  }
201
216
 
217
+ /**
218
+ * Check if object is a valid number, not NaN or undefined, but it may be infinite
219
+ * @param {any} n
220
+ * @return {boolean}
221
+ * @memberof Utilities
222
+ */
223
+ function isNumber(n) { return typeof n == 'number' && !isNaN(n); }
224
+
202
225
  ///////////////////////////////////////////////////////////////////////////////
203
226
 
204
227
  /** Random global functions
@@ -219,6 +242,12 @@ function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valu
219
242
  * @memberof Random */
220
243
  function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
221
244
 
245
+ /** Randomly returns true or false given the chance of true passed in
246
+ * @param {number} [chance]
247
+ * @return {boolean}
248
+ * @memberof Random */
249
+ function randBool(chance=.5) { return rand() < chance; }
250
+
222
251
  /** Randomly returns either -1 or 1
223
252
  * @return {number}
224
253
  * @memberof Random */
@@ -228,7 +257,7 @@ function randSign() { return randInt(2) * 2 - 1; }
228
257
  * @param {number} [length]
229
258
  * @return {Vector2}
230
259
  * @memberof Random */
231
- function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
260
+ function randVec2(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
232
261
 
233
262
  /** Returns a random Vector2 within a circular shape
234
263
  * @param {number} [radius]
@@ -236,7 +265,7 @@ function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length
236
265
  * @return {Vector2}
237
266
  * @memberof Random */
238
267
  function randInCircle(radius=1, minRadius=0)
239
- { return radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
268
+ { return radius > 0 ? randVec2(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
240
269
 
241
270
  /** Returns a random color between the two passed in colors, combine components if linear
242
271
  * @param {Color} [colorA=(1,1,1,1)]
@@ -265,8 +294,8 @@ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
265
294
  class RandomGenerator
266
295
  {
267
296
  /** Create a random number generator with the seed passed in
268
- * @param {number} seed - Starting seed */
269
- constructor(seed)
297
+ * @param {number} [seed] - Starting seed or engine default seed */
298
+ constructor(seed = 123456789)
270
299
  {
271
300
  /** @property {number} - random seed */
272
301
  this.seed = seed;
@@ -291,6 +320,11 @@ class RandomGenerator
291
320
  * @return {number} */
292
321
  int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
293
322
 
323
+ /** Randomly returns true or false given the chance of true passed in
324
+ * @param {number} [chance]
325
+ * @return {boolean} */
326
+ bool(chance=.5) { return this.float() < chance; }
327
+
294
328
  /** Randomly returns either -1 or 1 deterministically
295
329
  * @return {number} */
296
330
  sign() { return this.float() > .5 ? 1 : -1; }
@@ -300,11 +334,22 @@ class RandomGenerator
300
334
  * @param {number} [valueB]
301
335
  * @return {number} */
302
336
  floatSign(valueA=1, valueB=0) { return this.float(valueA, valueB) * this.sign(); }
337
+
338
+ /** Returns a random angle between -PI and PI
339
+ * @return {number} */
340
+ angle() { return this.float(-PI, PI); }
341
+
342
+ /** Returns a seeded vec2 with size between the two values passed in
343
+ * @param {number} valueA
344
+ * @param {number} [valueB]
345
+ * @return {Vector2} */
346
+ vec2(valueA=1, valueB=0)
347
+ { return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
303
348
  }
304
349
 
305
350
  ///////////////////////////////////////////////////////////////////////////////
306
351
 
307
- /**
352
+ /**
308
353
  * Create a 2d vector, can take 1 or 2 scalar values
309
354
  * @param {number} [x]
310
355
  * @param {number} [y] - if y is undefined, x is used for both
@@ -315,7 +360,7 @@ class RandomGenerator
315
360
  * b = vec2(); // set b to (0, 0)
316
361
  * @memberof Utilities
317
362
  */
318
- function vec2(x=0, y) { return new Vector2(x, y == undefined? x : y); }
363
+ function vec2(x=0, y) { return new Vector2(x, y === undefined ? x : y); }
319
364
 
320
365
  /**
321
366
  * Check if object is a valid Vector2
@@ -325,6 +370,15 @@ function vec2(x=0, y) { return new Vector2(x, y == undefined? x : y); }
325
370
  */
326
371
  function isVector2(v) { return v instanceof Vector2; }
327
372
 
373
+ // vector2 asserts
374
+ function ASSERT_VECTOR2_VALID(v) { ASSERT(isVector2(v) && v.isValid(), 'Vector2 is invalid.', v); }
375
+ function ASSERT_NUMBER_VALID(n) { ASSERT(isNumber(n), 'Number is invalid.', n); }
376
+ function ASSERT_VECTOR2_NORMAL(v)
377
+ {
378
+ ASSERT_VECTOR2_VALID(v);
379
+ ASSERT(abs(v.lengthSquared()-1) < .01, 'Vector2 is not normal.', v);
380
+ }
381
+
328
382
  /**
329
383
  * 2D Vector object with vector math library
330
384
  * - Functions do not change this so they can be chained together
@@ -345,7 +399,7 @@ class Vector2
345
399
  this.x = x;
346
400
  /** @property {number} - Y axis location */
347
401
  this.y = y;
348
- ASSERT(this.isValid());
402
+ ASSERT(this.isValid(), 'Constructed Vector2 is invalid.', this);
349
403
  }
350
404
 
351
405
  /** Sets values of this vector and returns self
@@ -356,7 +410,6 @@ class Vector2
356
410
  {
357
411
  this.x = x;
358
412
  this.y = y;
359
- ASSERT(this.isValid());
360
413
  return this;
361
414
  }
362
415
 
@@ -367,47 +420,27 @@ class Vector2
367
420
  /** Returns a copy of this vector plus the vector passed in
368
421
  * @param {Vector2} v - other vector
369
422
  * @return {Vector2} */
370
- add(v)
371
- {
372
- ASSERT(isVector2(v));
373
- return new Vector2(this.x + v.x, this.y + v.y);
374
- }
423
+ add(v) { return new Vector2(this.x + v.x, this.y + v.y);}
375
424
 
376
425
  /** Returns a copy of this vector minus the vector passed in
377
426
  * @param {Vector2} v - other vector
378
427
  * @return {Vector2} */
379
- subtract(v)
380
- {
381
- ASSERT(isVector2(v));
382
- return new Vector2(this.x - v.x, this.y - v.y);
383
- }
428
+ subtract(v) { return new Vector2(this.x - v.x, this.y - v.y); }
384
429
 
385
430
  /** Returns a copy of this vector times the vector passed in
386
431
  * @param {Vector2} v - other vector
387
432
  * @return {Vector2} */
388
- multiply(v)
389
- {
390
- ASSERT(isVector2(v));
391
- return new Vector2(this.x * v.x, this.y * v.y);
392
- }
433
+ multiply(v) { return new Vector2(this.x * v.x, this.y * v.y); }
393
434
 
394
435
  /** Returns a copy of this vector divided by the vector passed in
395
436
  * @param {Vector2} v - other vector
396
437
  * @return {Vector2} */
397
- divide(v)
398
- {
399
- ASSERT(isVector2(v));
400
- return new Vector2(this.x / v.x, this.y / v.y);
401
- }
438
+ divide(v) { return new Vector2(this.x / v.x, this.y / v.y); }
402
439
 
403
440
  /** Returns a copy of this vector scaled by the vector passed in
404
441
  * @param {number} s - scale
405
442
  * @return {Vector2} */
406
- scale(s)
407
- {
408
- ASSERT(!isVector2(s));
409
- return new Vector2(this.x * s, this.y * s);
410
- }
443
+ scale(s) { return new Vector2(this.x * s, this.y * s); }
411
444
 
412
445
  /** Returns the length of this vector
413
446
  * @return {number} */
@@ -420,20 +453,12 @@ class Vector2
420
453
  /** Returns the distance from this vector to vector passed in
421
454
  * @param {Vector2} v - other vector
422
455
  * @return {number} */
423
- distance(v)
424
- {
425
- ASSERT(isVector2(v));
426
- return this.distanceSquared(v)**.5;
427
- }
456
+ distance(v) { return this.distanceSquared(v)**.5; }
428
457
 
429
458
  /** Returns the distance squared from this vector to vector passed in
430
459
  * @param {Vector2} v - other vector
431
460
  * @return {number} */
432
- distanceSquared(v)
433
- {
434
- ASSERT(isVector2(v));
435
- return (this.x - v.x)**2 + (this.y - v.y)**2;
436
- }
461
+ distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
437
462
 
438
463
  /** Returns a new vector in same direction as this one with the length passed in
439
464
  * @param {number} [length]
@@ -456,20 +481,19 @@ class Vector2
456
481
  /** Returns the dot product of this and the vector passed in
457
482
  * @param {Vector2} v - other vector
458
483
  * @return {number} */
459
- dot(v)
460
- {
461
- ASSERT(isVector2(v));
462
- return this.x*v.x + this.y*v.y;
463
- }
484
+ dot(v) { return this.x*v.x + this.y*v.y; }
464
485
 
465
486
  /** Returns the cross product of this and the vector passed in
466
487
  * @param {Vector2} v - other vector
467
488
  * @return {number} */
468
- cross(v)
469
- {
470
- ASSERT(isVector2(v));
471
- return this.x*v.y - this.y*v.x;
472
- }
489
+ cross(v) { return this.x*v.y - this.y*v.x; }
490
+
491
+ /** Returns a copy this vector reflected by the surface normal
492
+ * @param {Vector2} normal - surface normal (should be normalized)
493
+ * @param {number} restitution - how much to bounce, 1 is perfect bounce, 0 is no bounce
494
+ * @return {Vector2} */
495
+ reflect(normal, restitution=1)
496
+ { return this.subtract(normal.scale((1+restitution)*this.dot(normal))); }
473
497
 
474
498
  /** Returns the clockwise angle of this vector, up is angle 0
475
499
  * @return {number} */
@@ -481,6 +505,8 @@ class Vector2
481
505
  * @return {Vector2} */
482
506
  setAngle(angle=0, length=1)
483
507
  {
508
+ ASSERT_NUMBER_VALID(angle);
509
+ ASSERT_NUMBER_VALID(length);
484
510
  this.x = length*Math.sin(angle);
485
511
  this.y = length*Math.cos(angle);
486
512
  return this;
@@ -490,7 +516,8 @@ class Vector2
490
516
  * @param {number} angle
491
517
  * @return {Vector2} */
492
518
  rotate(angle)
493
- {
519
+ {
520
+ ASSERT_NUMBER_VALID(angle);
494
521
  const c = Math.cos(-angle), s = Math.sin(-angle);
495
522
  return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
496
523
  }
@@ -500,8 +527,11 @@ class Vector2
500
527
  * @param {number} [length] */
501
528
  setDirection(direction, length=1)
502
529
  {
530
+ ASSERT_NUMBER_VALID(direction);
531
+ ASSERT_NUMBER_VALID(length);
503
532
  direction = mod(direction, 4);
504
- ASSERT(direction==0 || direction==1 || direction==2 || direction==3);
533
+ ASSERT(direction==0 || direction==1 || direction==2 || direction==3,
534
+ 'Vector2.setDirection() direction must be an integer between 0 and 3.');
505
535
  return vec2(direction%2 ? direction-1 ? -length : length : 0,
506
536
  direction%2 ? 0 : direction ? -length : length);
507
537
  }
@@ -515,10 +545,20 @@ class Vector2
515
545
  * @return {Vector2} */
516
546
  invert() { return new Vector2(this.y, -this.x); }
517
547
 
548
+ /** Returns a copy of this vector absolute values
549
+ * @return {Vector2} */
550
+ abs() { return new Vector2(abs(this.x), abs(this.y)); }
551
+
518
552
  /** Returns a copy of this vector with each axis floored
519
553
  * @return {Vector2} */
520
554
  floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
521
555
 
556
+ /** Returns new vec2 with modded values
557
+ * @param {number} [divisor]
558
+ * @return {Vector2} */
559
+ mod(divisor=1)
560
+ { return new Vector2(mod(this.x, divisor), mod(this.y, divisor)); }
561
+
522
562
  /** Returns the area this vector covers as a rectangle
523
563
  * @return {number} */
524
564
  area() { return abs(this.x * this.y); }
@@ -529,35 +569,36 @@ class Vector2
529
569
  * @return {Vector2} */
530
570
  lerp(v, percent)
531
571
  {
532
- ASSERT(isVector2(v));
533
- return this.add(v.subtract(this).scale(clamp(percent)));
572
+ ASSERT_VECTOR2_VALID(v);
573
+ ASSERT_NUMBER_VALID(percent);
574
+ const p = clamp(percent);
575
+ return new Vector2(v.x*p + this.x*(1-p), v.y*p + this.y*(1-p));
534
576
  }
535
577
 
536
578
  /** Returns true if this vector is within the bounds of an array size passed in
537
579
  * @param {Vector2} arraySize
538
580
  * @return {boolean} */
539
581
  arrayCheck(arraySize)
540
- {
541
- ASSERT(isVector2(arraySize));
542
- return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y;
543
- }
582
+ { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
544
583
 
545
584
  /** Returns this vector expressed as a string
546
585
  * @param {number} digits - precision to display
547
586
  * @return {string} */
548
587
  toString(digits=3)
549
588
  {
589
+ ASSERT_NUMBER_VALID(digits);
550
590
  if (debug)
551
- return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
591
+ {
592
+ if (this.isValid())
593
+ return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
594
+ else
595
+ return `(${this.x}, ${this.y})`;
596
+ }
552
597
  }
553
598
 
554
599
  /** Checks if this is a valid vector
555
600
  * @return {boolean} */
556
- isValid()
557
- {
558
- return typeof this.x == 'number' && !isNaN(this.x)
559
- && typeof this.y == 'number' && !isNaN(this.y);
560
- }
601
+ isValid() { return isNumber(this.x) && isNumber(this.y); }
561
602
  }
562
603
 
563
604
  ///////////////////////////////////////////////////////////////////////////////
@@ -592,13 +633,16 @@ function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
592
633
  */
593
634
  function isColor(c) { return c instanceof Color; }
594
635
 
636
+ // color asserts
637
+ function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c) && c.isValid(), 'Color is invalid.', c); }
638
+
595
639
  /**
596
640
  * Color object (red, green, blue, alpha) with some helpful functions
597
641
  * @example
598
642
  * let a = new Color; // white
599
643
  * let b = new Color(1, 0, 0); // red
600
644
  * let c = new Color(0, 0, 0, 0); // transparent black
601
- * let d = rgb(0, 0, 1); // blue using rgb color
645
+ * let d = rgb(0, 0, 1); // blue using rgb color
602
646
  * let e = hsl(.3, 1, .5); // green using hsl color
603
647
  */
604
648
  class Color
@@ -618,7 +662,7 @@ class Color
618
662
  this.b = b;
619
663
  /** @property {number} - Alpha */
620
664
  this.a = a;
621
- ASSERT(this.isValid());
665
+ ASSERT(this.isValid(), 'Constructed Color is invalid.', this);
622
666
  }
623
667
 
624
668
  /** Sets values of this color and returns self
@@ -633,7 +677,6 @@ class Color
633
677
  this.g = g;
634
678
  this.b = b;
635
679
  this.a = a;
636
- ASSERT(this.isValid());
637
680
  return this;
638
681
  }
639
682
 
@@ -644,38 +687,22 @@ class Color
644
687
  /** Returns a copy of this color plus the color passed in
645
688
  * @param {Color} c - other color
646
689
  * @return {Color} */
647
- add(c)
648
- {
649
- ASSERT(isColor(c));
650
- return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a);
651
- }
690
+ add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
652
691
 
653
692
  /** Returns a copy of this color minus the color passed in
654
693
  * @param {Color} c - other color
655
694
  * @return {Color} */
656
- subtract(c)
657
- {
658
- ASSERT(isColor(c));
659
- return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a);
660
- }
695
+ subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
661
696
 
662
697
  /** Returns a copy of this color times the color passed in
663
698
  * @param {Color} c - other color
664
699
  * @return {Color} */
665
- multiply(c)
666
- {
667
- ASSERT(isColor(c));
668
- return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a);
669
- }
700
+ multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
670
701
 
671
702
  /** Returns a copy of this color divided by the color passed in
672
703
  * @param {Color} c - other color
673
704
  * @return {Color} */
674
- divide(c)
675
- {
676
- ASSERT(isColor(c));
677
- return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a);
678
- }
705
+ divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
679
706
 
680
707
  /** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
681
708
  * @param {number} scale
@@ -694,8 +721,14 @@ class Color
694
721
  * @return {Color} */
695
722
  lerp(c, percent)
696
723
  {
697
- ASSERT(isColor(c));
698
- return this.add(c.subtract(this).scale(clamp(percent)));
724
+ ASSERT_COLOR_VALID(c);
725
+ ASSERT_NUMBER_VALID(percent);
726
+ const p = clamp(percent);
727
+ return new Color(
728
+ c.r*p + this.r*(1-p),
729
+ c.g*p + this.g*(1-p),
730
+ c.b*p + this.b*(1-p),
731
+ c.a*p + this.a*(1-p));
699
732
  }
700
733
 
701
734
  /** Sets this color given a hue, saturation, lightness, and alpha
@@ -718,7 +751,7 @@ class Color
718
751
  this.g = f(p, q, h);
719
752
  this.b = f(p, q, h - 1/3);
720
753
  this.a = a;
721
- ASSERT(this.isValid());
754
+ ASSERT_COLOR_VALID(this);
722
755
  return this;
723
756
  }
724
757
 
@@ -730,20 +763,19 @@ class Color
730
763
  const g = clamp(this.g);
731
764
  const b = clamp(this.b);
732
765
  const a = clamp(this.a);
733
- const max = Math.max(r, g, b);
734
- const min = Math.min(r, g, b);
735
- const l = (max + min) / 2;
736
-
766
+ const maxC = max(r, g, b);
767
+ const minC = min(r, g, b);
768
+ const l = (maxC + minC) / 2;
737
769
  let h = 0, s = 0;
738
- if (max != min)
770
+ if (maxC != minC)
739
771
  {
740
- let d = max - min;
741
- s = l > .5 ? d / (2 - max - min) : d / (max + min);
742
- if (r == max)
772
+ let d = maxC - minC;
773
+ s = l > .5 ? d / (2 - maxC - minC) : d / (maxC + minC);
774
+ if (r == maxC)
743
775
  h = (g - b) / d + (g < b ? 6 : 0);
744
- else if (g == max)
776
+ else if (g == maxC)
745
777
  h = (b - r) / d + 2;
746
- else if (b == max)
778
+ else if (b == maxC)
747
779
  h = (r - g) / d + 4;
748
780
  }
749
781
  return [h / 6, s, l, a];
@@ -755,6 +787,8 @@ class Color
755
787
  * @return {Color} */
756
788
  mutate(amount=.05, alphaAmount=0)
757
789
  {
790
+ ASSERT_NUMBER_VALID(amount);
791
+ ASSERT_NUMBER_VALID(alphaAmount);
758
792
  return new Color
759
793
  (
760
794
  this.r + rand(amount, -amount),
@@ -768,7 +802,10 @@ class Color
768
802
  * @param {boolean} [useAlpha] - if alpha should be included in result
769
803
  * @return {string} */
770
804
  toString(useAlpha = true)
771
- {
805
+ {
806
+ ASSERT(typeof useAlpha == 'boolean', 'Use alpha boolean is invalid.', useAlpha);
807
+ if (debug && !this.isValid())
808
+ return `#000`;
772
809
  const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
773
810
  return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
774
811
  }
@@ -778,7 +815,7 @@ class Color
778
815
  * @return {Color} */
779
816
  setHex(hex)
780
817
  {
781
- ASSERT(typeof hex == 'string' && hex[0] == '#');
818
+ ASSERT(typeof hex == 'string' && hex[0] == '#', 'Color hex code must be a string starting with #');
782
819
  ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
783
820
 
784
821
  if (hex.length < 6)
@@ -798,7 +835,7 @@ class Color
798
835
  this.a = hex.length == 9 ? fromHex(7) : 1;
799
836
  }
800
837
 
801
- ASSERT(this.isValid());
838
+ ASSERT_COLOR_VALID(this);
802
839
  return this;
803
840
  }
804
841
 
@@ -816,12 +853,7 @@ class Color
816
853
  /** Checks if this is a valid color
817
854
  * @return {boolean} */
818
855
  isValid()
819
- {
820
- return typeof this.r == 'number' && !isNaN(this.r)
821
- && typeof this.g == 'number' && !isNaN(this.g)
822
- && typeof this.b == 'number' && !isNaN(this.b)
823
- && typeof this.a == 'number' && !isNaN(this.a);
824
- }
856
+ { return isNumber(this.r) && isNumber(this.g) && isNumber(this.b) && isNumber(this.a); }
825
857
  }
826
858
 
827
859
  ///////////////////////////////////////////////////////////////////////////////
@@ -832,11 +864,21 @@ class Color
832
864
  * @memberof Utilities */
833
865
  const WHITE = rgb();
834
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
+
835
872
  /** Color - Black #000000
836
873
  * @type {Color}
837
874
  * @memberof Utilities */
838
875
  const BLACK = rgb(0,0,0);
839
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
+
840
882
  /** Color - Gray #808080
841
883
  * @type {Color}
842
884
  * @memberof Utilities */
@@ -897,18 +939,28 @@ class Timer
897
939
  {
898
940
  /** Create a timer object set time passed in
899
941
  * @param {number} [timeLeft] - How much time left before the timer elapses in seconds */
900
- 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
+ }
901
948
 
902
949
  /** Set the timer with seconds passed in
903
950
  * @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */
904
- 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
+ }
905
957
 
906
958
  /** Unset the timer */
907
959
  unset() { this.time = undefined; }
908
960
 
909
961
  /** Returns true if set
910
962
  * @return {boolean} */
911
- isSet() { return this.time != undefined; }
963
+ isSet() { return this.time !== undefined; }
912
964
 
913
965
  /** Returns true if set and has not elapsed
914
966
  * @return {boolean} */
@@ -932,5 +984,5 @@ class Timer
932
984
 
933
985
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
934
986
  * @return {number} */
935
- valueOf() { return this.get(); }
987
+ valueOf() { return this.get(); }
936
988
  }