littlejsengine 1.8.8 → 1.9.0

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 (47) hide show
  1. package/README.md +17 -17
  2. package/build/littlejs.d.ts +316 -254
  3. package/build/littlejs.esm.js +727 -676
  4. package/build/littlejs.esm.min.js +1 -1
  5. package/build/littlejs.js +726 -676
  6. package/build/littlejs.min.js +1 -1
  7. package/build/littlejs.release.js +661 -612
  8. package/examples/breakout/game.js +4 -4
  9. package/examples/breakout/index.html +3 -3
  10. package/examples/breakoutTutorial/index.html +2 -2
  11. package/examples/electron/game.js +1 -1
  12. package/examples/electron/index.html +2 -2
  13. package/examples/favicon.png +0 -0
  14. package/examples/js13k/index.html +13 -13
  15. package/examples/logo.png +0 -0
  16. package/examples/module/game.js +1 -1
  17. package/examples/module/index.html +1 -1
  18. package/examples/particles/index.html +1 -1
  19. package/examples/platformer/game.js +6 -6
  20. package/examples/platformer/gameCharacter.js +293 -0
  21. package/examples/platformer/gameEffects.js +8 -5
  22. package/examples/platformer/gameObjects.js +13 -13
  23. package/examples/platformer/gamePlayer.js +9 -293
  24. package/examples/platformer/index.html +7 -6
  25. package/examples/puzzle/game.js +3 -2
  26. package/examples/puzzle/index.html +2 -2
  27. package/examples/starter/game.js +3 -3
  28. package/examples/starter/index.html +13 -13
  29. package/examples/starter/tiles.png +0 -0
  30. package/examples/stress/index.html +34 -26
  31. package/examples/typescript/index.html +1 -1
  32. package/package.json +1 -1
  33. package/src/engine.js +50 -47
  34. package/src/engineAudio.js +126 -112
  35. package/src/engineDebug.js +66 -65
  36. package/src/engineDraw.js +47 -56
  37. package/src/engineExport.js +1 -0
  38. package/src/engineInput.js +57 -40
  39. package/src/engineMedals.js +32 -29
  40. package/src/engineObject.js +41 -26
  41. package/src/engineParticles.js +101 -75
  42. package/src/engineRelease.js +1 -1
  43. package/src/engineSettings.js +22 -22
  44. package/src/engineTileLayer.js +34 -33
  45. package/src/engineUtilities.js +44 -44
  46. package/src/engineWebGL.js +105 -126
  47. package/src/jsconfig.json +10 -0
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
  let showWatermark = 0;
14
- let debugKey = 0;
14
+ let debugKey = '';
15
15
  const debug = 0;
16
16
  const debugOverlay = 0;
17
17
  const debugPhysics = 0;
@@ -79,15 +79,15 @@ function sign(value) { return Math.sign(value); }
79
79
 
80
80
  /** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
81
81
  * @param {Number} dividend
82
- * @param {Number} [divisor=1]
82
+ * @param {Number} [divisor]
83
83
  * @return {Number}
84
84
  * @memberof Utilities */
85
85
  function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
86
86
 
87
87
  /** Clamps the value beween max and min
88
88
  * @param {Number} value
89
- * @param {Number} [min=0]
90
- * @param {Number} [max=1]
89
+ * @param {Number} [min]
90
+ * @param {Number} [max]
91
91
  * @return {Number}
92
92
  * @memberof Utilities */
93
93
  function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
@@ -112,7 +112,7 @@ function lerp(percent, valueA, valueB) { return valueA + clamp(percent) * (value
112
112
  /** Returns signed wrapped distance between the two values passed in
113
113
  * @param {Number} valueA
114
114
  * @param {Number} valueB
115
- * @param {Number} [wrapSize=1]
115
+ * @param {Number} [wrapSize]
116
116
  * @returns {Number}
117
117
  * @memberof Utilities */
118
118
  function distanceWrap(valueA, valueB, wrapSize=1)
@@ -122,7 +122,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
122
122
  * @param {Number} percent
123
123
  * @param {Number} valueA
124
124
  * @param {Number} valueB
125
- * @param {Number} [wrapSize=1]
125
+ * @param {Number} [wrapSize]
126
126
  * @returns {Number}
127
127
  * @memberof Utilities */
128
128
  function lerpWrap(percent, valueA, valueB, wrapSize=1)
@@ -133,7 +133,7 @@ function lerpWrap(percent, valueA, valueB, wrapSize=1)
133
133
  * @param {Number} angleB
134
134
  * @returns {Number}
135
135
  * @memberof Utilities */
136
- function distanceAngle(angleA, angleB) { distanceWrap(angleA, angleB, 2*PI); }
136
+ function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
137
137
 
138
138
  /** Linearly interpolates between the angles passed in with wrappping
139
139
  * @param {Number} percent
@@ -169,10 +169,10 @@ function isOverlapping(pointA, sizeA, pointB, sizeB)
169
169
  }
170
170
 
171
171
  /** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
172
- * @param {Number} [frequency=1] - Frequency of the wave in Hz
173
- * @param {Number} [amplitude=1] - Amplitude (max height) of the wave
174
- * @param {Number} [t=time] - Value to use for time of the wave
175
- * @return {Number} - Value waving between 0 and amplitude
172
+ * @param {Number} [frequency] - Frequency of the wave in Hz
173
+ * @param {Number} [amplitude] - Amplitude (max height) of the wave
174
+ * @param {Number} [t=time] - Value to use for time of the wave
175
+ * @return {Number} - Value waving between 0 and amplitude
176
176
  * @memberof Utilities */
177
177
  function wave(frequency=1, amplitude=1, t=time)
178
178
  { return amplitude/2 * (1 - Math.cos(t*frequency*2*PI)); }
@@ -189,15 +189,15 @@ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
189
189
  * @namespace Random */
190
190
 
191
191
  /** Returns a random value between the two values passed in
192
- * @param {Number} [valueA=1]
193
- * @param {Number} [valueB=0]
192
+ * @param {Number} [valueA]
193
+ * @param {Number} [valueB]
194
194
  * @return {Number}
195
195
  * @memberof Random */
196
196
  function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
197
197
 
198
198
  /** Returns a floored random value the two values passed in
199
199
  * @param {Number} valueA
200
- * @param {Number} [valueB=0]
200
+ * @param {Number} [valueB]
201
201
  * @return {Number}
202
202
  * @memberof Random */
203
203
  function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
@@ -208,14 +208,14 @@ function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
208
208
  function randSign() { return randInt(2) * 2 - 1; }
209
209
 
210
210
  /** Returns a random Vector2 with the passed in length
211
- * @param {Number} [length=1]
211
+ * @param {Number} [length]
212
212
  * @return {Vector2}
213
213
  * @memberof Random */
214
214
  function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
215
215
 
216
216
  /** Returns a random Vector2 within a circular shape
217
- * @param {Number} [radius=1]
218
- * @param {Number} [minRadius=0]
217
+ * @param {Number} [radius]
218
+ * @param {Number} [minRadius]
219
219
  * @return {Vector2}
220
220
  * @memberof Random */
221
221
  function randInCircle(radius=1, minRadius=0)
@@ -227,7 +227,7 @@ function randInCircle(radius=1, minRadius=0)
227
227
  * @param {Boolean} [linear]
228
228
  * @return {Color}
229
229
  * @memberof Random */
230
- function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
230
+ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
231
231
  {
232
232
  return linear ? colorA.lerp(colorB, rand()) :
233
233
  new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
@@ -256,8 +256,8 @@ class RandomGenerator
256
256
  }
257
257
 
258
258
  /** Returns a seeded random value between the two values passed in
259
- * @param {Number} [valueA=1]
260
- * @param {Number} [valueB=0]
259
+ * @param {Number} [valueA]
260
+ * @param {Number} [valueB]
261
261
  * @return {Number} */
262
262
  float(valueA=1, valueB=0)
263
263
  {
@@ -270,7 +270,7 @@ class RandomGenerator
270
270
 
271
271
  /** Returns a floored seeded random value the two values passed in
272
272
  * @param {Number} valueA
273
- * @param {Number} [valueB=0]
273
+ * @param {Number} [valueB]
274
274
  * @return {Number} */
275
275
  int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
276
276
 
@@ -283,8 +283,8 @@ class RandomGenerator
283
283
 
284
284
  /**
285
285
  * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
286
- * @param {(Number|Vector2)} [x=0]
287
- * @param {Number} [y=0]
286
+ * @param {(Number|Vector2)} [x]
287
+ * @param {Number} [y]
288
288
  * @return {Vector2}
289
289
  * @example
290
290
  * let a = vec2(0, 1); // vector with coordinates (0, 1)
@@ -294,15 +294,15 @@ class RandomGenerator
294
294
  * @memberof Utilities
295
295
  */
296
296
  function vec2(x=0, y)
297
- { return x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
297
+ { return typeof x === 'number'? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
298
298
 
299
299
  /**
300
300
  * Check if object is a valid Vector2
301
- * @param {Vector2} v
301
+ * @param {any} v
302
302
  * @return {Boolean}
303
303
  * @memberof Utilities
304
304
  */
305
- function isVector2(v) { return !isNaN(v.x) && !isNaN(v.y); }
305
+ function isVector2(v) { return typeof v === 'object' && typeof v.x === 'number' && typeof v.y === 'number'; }
306
306
 
307
307
  /**
308
308
  * 2D Vector object with vector math library
@@ -316,8 +316,8 @@ function isVector2(v) { return !isNaN(v.x) && !isNaN(v.y); }
316
316
  class Vector2
317
317
  {
318
318
  /** Create a 2D vector with the x and y passed in, can also be created with vec2()
319
- * @param {Number} [x=0] - X axis location
320
- * @param {Number} [y=0] - Y axis location */
319
+ * @param {Number} [x] - X axis location
320
+ * @param {Number} [y] - Y axis location */
321
321
  constructor(x=0, y=0)
322
322
  {
323
323
  /** @property {Number} - X axis location */
@@ -374,12 +374,12 @@ class Vector2
374
374
  distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
375
375
 
376
376
  /** Returns a new vector in same direction as this one with the length passed in
377
- * @param {Number} [length=1]
377
+ * @param {Number} [length]
378
378
  * @return {Vector2} */
379
379
  normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(0, length); }
380
380
 
381
381
  /** Returns a new vector clamped to length passed in
382
- * @param {Number} [length=1]
382
+ * @param {Number} [length]
383
383
  * @return {Vector2} */
384
384
  clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
385
385
 
@@ -398,8 +398,8 @@ class Vector2
398
398
  angle() { return Math.atan2(this.x, this.y); }
399
399
 
400
400
  /** Sets this vector with angle and length passed in
401
- * @param {Number} [angle=0]
402
- * @param {Number} [length=1]
401
+ * @param {Number} [angle]
402
+ * @param {Number} [length]
403
403
  * @return {Vector2} */
404
404
  setAngle(angle=0, length=1)
405
405
  { this.x = length*Math.sin(angle); this.y = length*Math.cos(angle); return this; }
@@ -484,10 +484,10 @@ function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
484
484
  class Color
485
485
  {
486
486
  /** Create a color with the rgba components passed in, white by default
487
- * @param {Number} [r=1] - red
488
- * @param {Number} [g=1] - green
489
- * @param {Number} [b=1] - blue
490
- * @param {Number} [a=1] - alpha*/
487
+ * @param {Number} [r] - red
488
+ * @param {Number} [g] - green
489
+ * @param {Number} [b] - blue
490
+ * @param {Number} [a] - alpha*/
491
491
  constructor(r=1, g=1, b=1, a=1)
492
492
  {
493
493
  /** @property {Number} - Red */
@@ -542,10 +542,10 @@ class Color
542
542
  lerp(c, percent) { return this.add(c.subtract(this).scale(clamp(percent))); }
543
543
 
544
544
  /** Sets this color given a hue, saturation, lightness, and alpha
545
- * @param {Number} [h=0] - hue
546
- * @param {Number} [s=0] - saturation
547
- * @param {Number} [l=1] - lightness
548
- * @param {Number} [a=1] - alpha
545
+ * @param {Number} [h] - hue
546
+ * @param {Number} [s] - saturation
547
+ * @param {Number} [l] - lightness
548
+ * @param {Number} [a] - alpha
549
549
  * @return {Color} */
550
550
  setHSLA(h=0, s=0, l=1, a=1)
551
551
  {
@@ -591,8 +591,8 @@ class Color
591
591
  }
592
592
 
593
593
  /** Returns a new color that has each component randomly adjusted
594
- * @param {Number} [amount=.05]
595
- * @param {Number} [alphaAmount=0]
594
+ * @param {Number} [amount]
595
+ * @param {Number} [alphaAmount]
596
596
  * @return {Color} */
597
597
  mutate(amount=.05, alphaAmount=0)
598
598
  {
@@ -606,9 +606,9 @@ class Color
606
606
  }
607
607
 
608
608
  /** Returns this color expressed as a hex color code
609
- * @param {Boolean} [useAlpha=1] - if alpha should be included in result
609
+ * @param {Boolean} [useAlpha] - if alpha should be included in result
610
610
  * @return {String} */
611
- toString(useAlpha = 1)
611
+ toString(useAlpha = true)
612
612
  {
613
613
  const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
614
614
  return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
@@ -657,7 +657,7 @@ class Timer
657
657
  constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
658
658
 
659
659
  /** Set the timer with seconds passed in
660
- * @param {Number} [timeLeft=0] - How much time left before the timer is elapsed in seconds */
660
+ * @param {Number} [timeLeft] - How much time left before the timer is elapsed in seconds */
661
661
  set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
662
662
 
663
663
  /** Unset the timer */
@@ -734,7 +734,7 @@ let canvasFixedSize = vec2();
734
734
  * @type {Boolean}
735
735
  * @default
736
736
  * @memberof Settings */
737
- let canvasPixelated = 1;
737
+ let canvasPixelated = true;
738
738
 
739
739
  /** Default font used for text rendering
740
740
  * @type {String}
@@ -746,7 +746,7 @@ let fontDefault = 'arial';
746
746
  * @type {Boolean}
747
747
  * @default
748
748
  * @memberof Settings */
749
- let showSplashScreen = 0;
749
+ let showSplashScreen = false;
750
750
 
751
751
  ///////////////////////////////////////////////////////////////////////////////
752
752
  // WebGL settings
@@ -755,13 +755,13 @@ let showSplashScreen = 0;
755
755
  * @type {Boolean}
756
756
  * @default
757
757
  * @memberof Settings */
758
- let glEnable = 1;
758
+ let glEnable = true;
759
759
 
760
760
  /** Fixes slow rendering in some browsers by not compositing the WebGL canvas
761
761
  * @type {Boolean}
762
762
  * @default
763
763
  * @memberof Settings */
764
- let glOverlay = 1;
764
+ let glOverlay = true;
765
765
 
766
766
  ///////////////////////////////////////////////////////////////////////////////
767
767
  // Tile sheet settings
@@ -785,7 +785,7 @@ let tileFixBleedScale = .3;
785
785
  * @type {Boolean}
786
786
  * @default
787
787
  * @memberof Settings */
788
- let enablePhysicsSolver = 1;
788
+ let enablePhysicsSolver = true;
789
789
 
790
790
  /** Default object mass for collison calcuations (how heavy objects are)
791
791
  * @type {Number}
@@ -807,7 +807,7 @@ let objectDefaultAngleDamping = 1;
807
807
 
808
808
  /** How much to bounce when a collision occurs (0-1)
809
809
  * @type {Number}
810
- * @default 0
810
+ * @default
811
811
  * @memberof Settings */
812
812
  let objectDefaultElasticity = 0;
813
813
 
@@ -825,7 +825,7 @@ let objectMaxSpeed = 1;
825
825
 
826
826
  /** How much gravity to apply to objects along the Y axis, negative is down
827
827
  * @type {Number}
828
- * @default 0
828
+ * @default
829
829
  * @memberof Settings */
830
830
  let gravity = 0;
831
831
 
@@ -842,33 +842,33 @@ let particleEmitRateScale = 1;
842
842
  * @type {Boolean}
843
843
  * @default
844
844
  * @memberof Settings */
845
- let gamepadsEnable = 1;
845
+ let gamepadsEnable = true;
846
846
 
847
847
  /** If true, the dpad input is also routed to the left analog stick (for better accessability)
848
848
  * @type {Boolean}
849
849
  * @default
850
850
  * @memberof Settings */
851
- let gamepadDirectionEmulateStick = 1;
851
+ let gamepadDirectionEmulateStick = true;
852
852
 
853
853
  /** If true the WASD keys are also routed to the direction keys (for better accessability)
854
854
  * @type {Boolean}
855
855
  * @default
856
856
  * @memberof Settings */
857
- let inputWASDEmulateDirection = 1;
857
+ let inputWASDEmulateDirection = true;
858
858
 
859
859
  /** True if touch gamepad should appear on mobile devices
860
860
  * - Supports left analog stick, 4 face buttons and start button (button 9)
861
861
  * - Must be set by end of gameInit to be activated
862
862
  * @type {Boolean}
863
- * @default 0
863
+ * @default
864
864
  * @memberof Settings */
865
- let touchGamepadEnable = 0;
865
+ let touchGamepadEnable = false;
866
866
 
867
867
  /** True if touch gamepad should be analog stick or false to use if 8 way dpad
868
868
  * @type {Boolean}
869
869
  * @default
870
870
  * @memberof Settings */
871
- let touchGamepadAnalog = 1;
871
+ let touchGamepadAnalog = true;
872
872
 
873
873
  /** Size of virutal gamepad for touch devices in pixels
874
874
  * @type {Number}
@@ -886,7 +886,7 @@ let touchGamepadAlpha = .3;
886
886
  * @type {Boolean}
887
887
  * @default
888
888
  * @memberof Settings */
889
- let vibrateEnable = 1;
889
+ let vibrateEnable = true;
890
890
 
891
891
  ///////////////////////////////////////////////////////////////////////////////
892
892
  // Audio settings
@@ -895,7 +895,7 @@ let vibrateEnable = 1;
895
895
  * @type {Boolean}
896
896
  * @default
897
897
  * @memberof Settings */
898
- let soundEnable = 1;
898
+ let soundEnable = true;
899
899
 
900
900
  /** Volume scale to apply to all sound, music and speech
901
901
  * @type {Number}
@@ -944,9 +944,9 @@ let medalDisplayIconSize = 50;
944
944
 
945
945
  /** Set to stop medals from being unlockable (like if cheats are enabled)
946
946
  * @type {Boolean}
947
- * @default 0
947
+ * @default
948
948
  * @memberof Settings */
949
- let medalsPreventUnlock;
949
+ let medalsPreventUnlock = false;
950
950
 
951
951
  ///////////////////////////////////////////////////////////////////////////////
952
952
  // Setters for global variables
@@ -1017,12 +1017,12 @@ function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
1017
1017
  function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
1018
1018
 
1019
1019
  /** Set how much to slow velocity by each frame
1020
- * @param {Number} damping
1020
+ * @param {Number} damp
1021
1021
  * @memberof Settings */
1022
1022
  function setObjectDefaultDamping(damp) { objectDefaultDamping = damp; }
1023
1023
 
1024
1024
  /** Set how much to slow angular velocity each frame
1025
- * @param {Number} damping
1025
+ * @param {Number} damp
1026
1026
  * @memberof Settings */
1027
1027
  function setObjectDefaultAngleDamping(damp) { objectDefaultAngleDamping = damp; }
1028
1028
 
@@ -1042,9 +1042,9 @@ function setObjectDefaultFriction(friction) { objectDefaultFriction = friction;
1042
1042
  function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
1043
1043
 
1044
1044
  /** Set how much gravity to apply to objects along the Y axis
1045
- * @param {Number} gravity
1045
+ * @param {Number} newGravity
1046
1046
  * @memberof Settings */
1047
- function setGravity(g) { gravity = g; }
1047
+ function setGravity(newGravity) { gravity = newGravity; }
1048
1048
 
1049
1049
  /** Set to scales emit rate of particles
1050
1050
  * @param {Number} scale
@@ -1142,7 +1142,7 @@ function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUn
1142
1142
  function setShowWatermark(show) { showWatermark = show; }
1143
1143
 
1144
1144
  /** Set key code used to toggle debug mode, Esc by default
1145
- * @param {Number} key
1145
+ * @param {String} key
1146
1146
  * @memberof Debug */
1147
1147
  function setDebugKey(key) { debugKey = key; }
1148
1148
  /**
@@ -1179,27 +1179,25 @@ function setDebugKey(key) { debugKey = key; }
1179
1179
  class EngineObject
1180
1180
  {
1181
1181
  /** Create an engine object and adds it to the list of objects
1182
- * @param {Vector2} [pos=Vector2()] - World space position of the object
1183
- * @param {Vector2} [size=Vector2(1,1)] - World space size of the object
1184
- * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
1185
- * @param {Number} [angle=0] - Angle the object is rotated by
1186
- * @param {Color} [color=Color()] - Color to apply to tile when rendered
1187
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
1182
+ * @param {Vector2} [pos=Vector2()] - World space position of the object
1183
+ * @param {Vector2} [size=Vector2(1,1)] - World space size of the object
1184
+ * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
1185
+ * @param {Number} [angle] - Angle the object is rotated by
1186
+ * @param {Color} [color=Color()] - Color to apply to tile when rendered
1187
+ * @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
1188
1188
  */
1189
1189
  constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color, renderOrder=0)
1190
1190
  {
1191
1191
  // set passed in params
1192
- ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
1193
- ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
1194
- ASSERT(!(renderOrder instanceof Color)); // prevent old style calls
1195
- // to fix old calls, replace with tile(tileIndex, tileSize)
1192
+ ASSERT(isVector2(pos) && isVector2(size), 'ensure pos and size are vec2s');
1193
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo, 'old style tile setup');
1196
1194
 
1197
1195
  /** @property {Vector2} - World space position of the object */
1198
1196
  this.pos = pos.copy();
1199
1197
  /** @property {Vector2} - World space width and height of the object */
1200
1198
  this.size = size;
1201
1199
  /** @property {Vector2} - Size of object used for drawing, uses size if not set */
1202
- this.drawSize;
1200
+ this.drawSize = undefined;
1203
1201
  /** @property {TileInfo} - Tile info to render object (undefined is untextured) */
1204
1202
  this.tileInfo = tileInfo;
1205
1203
  /** @property {Number} - Angle to rotate the object */
@@ -1207,9 +1205,11 @@ class EngineObject
1207
1205
  /** @property {Color} - Color to apply when rendered */
1208
1206
  this.color = color;
1209
1207
  /** @property {Color} - Additive color to apply when rendered */
1210
- this.additiveColor;
1208
+ this.additiveColor = undefined;
1209
+ /** @property {Boolean} - Should it flip along y axis when rendered */
1210
+ this.mirror = false;
1211
1211
 
1212
- // set object defaults
1212
+ // physical properties
1213
1213
  /** @property {Number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */
1214
1214
  this.mass = objectDefaultMass;
1215
1215
  /** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
@@ -1220,19 +1220,34 @@ class EngineObject
1220
1220
  this.elasticity = objectDefaultElasticity;
1221
1221
  /** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
1222
1222
  this.friction = objectDefaultFriction;
1223
- /** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
1223
+ /** @property {Number} - How much to scale gravity by for this object */
1224
1224
  this.gravityScale = 1;
1225
- /** @property {Number} [renderOrder=0] - Objects are sorted by render order */
1225
+ /** @property {Number} - Objects are sorted by render order */
1226
1226
  this.renderOrder = renderOrder;
1227
- /** @property {Vector2} [velocity=Vector2()] - Velocity of the object */
1227
+ /** @property {Vector2} - Velocity of the object */
1228
1228
  this.velocity = vec2();
1229
- /** @property {Number} [angleVelocity=0] - Angular velocity of the object */
1229
+ /** @property {Number} - Angular velocity of the object */
1230
1230
  this.angleVelocity = 0;
1231
-
1232
- // init other internal object stuff
1231
+ /** @property {Number} - Track when object was created */
1233
1232
  this.spawnTime = time;
1233
+ /** @property {Array} - List of children of this object */
1234
1234
  this.children = [];
1235
+
1236
+ // parent child system
1237
+ /** @property {EngineObject} - Parent of object if in local space */
1238
+ this.parent = undefined;
1239
+ /** @property {Vector2} - Local position if child */
1240
+ this.localPos = vec2();
1241
+ /** @property {Number} - Local angle if child */
1242
+ this.localAngle = 0;
1243
+
1244
+ // collision flags
1245
+ /** @property {Boolean} - Object collides with the tile collision */
1235
1246
  this.collideTiles = false;
1247
+ /** @property {Boolean} - Object collides with solid objects */
1248
+ this.collideSolidObjects = false;
1249
+ /** @property {Boolean} - Object collides with and blocks other objects */
1250
+ this.isSolid = false;
1236
1251
 
1237
1252
  // add to list of objects
1238
1253
  engineObjects.push(this);
@@ -1449,7 +1464,7 @@ class EngineObject
1449
1464
  * @param {EngineObject} object - the object to test against
1450
1465
  * @return {Boolean} - true if the collision should be resolved
1451
1466
  */
1452
- collideWithObject(object) { return 1; }
1467
+ collideWithObject(object) { return true; }
1453
1468
 
1454
1469
  /** How long since the object was created
1455
1470
  * @return {Number} */
@@ -1470,7 +1485,7 @@ class EngineObject
1470
1485
  /** Attaches a child to this with a given local transform
1471
1486
  * @param {EngineObject} child
1472
1487
  * @param {Vector2} [localPos=Vector2()]
1473
- * @param {Number} [localAngle=0] */
1488
+ * @param {Number} [localAngle] */
1474
1489
  addChild(child, localPos=vec2(), localAngle=0)
1475
1490
  {
1476
1491
  ASSERT(!child.parent && !this.children.includes(child));
@@ -1490,12 +1505,12 @@ class EngineObject
1490
1505
  }
1491
1506
 
1492
1507
  /** Set how this object collides
1493
- * @param {Boolean} [collideSolidObjects=1] - Does it collide with solid objects
1494
- * @param {Boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers)
1495
- * @param {Boolean} [collideTiles=1] - Does it collide with the tile collision */
1496
- setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1)
1508
+ * @param {Boolean} [collideSolidObjects] - Does it collide with solid objects
1509
+ * @param {Boolean} [isSolid] - Does it collide with and block other objects (expensive in large numbers)
1510
+ * @param {Boolean} [collideTiles] - Does it collide with the tile collision */
1511
+ setCollision(collideSolidObjects=true, isSolid=true, collideTiles=true)
1497
1512
  {
1498
- ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
1513
+ ASSERT(collideSolidObjects || !isSolid, 'solid objects must be set to collide');
1499
1514
 
1500
1515
  this.collideSolidObjects = collideSolidObjects;
1501
1516
  this.isSolid = isSolid;
@@ -1586,9 +1601,9 @@ let drawCount;
1586
1601
  * Create a tile info object
1587
1602
  * - This can take vecs or floats for easier use and conversion
1588
1603
  * - If an index is passed in, the tile size and index will determine the position
1589
- * @param {(Number|Vector2)} [pos=Vector2()] - Top left corner of tile in pixels or index
1590
- * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
1591
- * @param {Number} [textureIndex=0] - Texture index to use
1604
+ * @param {(Number|Vector2)} [pos=Vector2()] - Top left corner of tile in pixels or index
1605
+ * @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
1606
+ * @param {Number} [textureIndex] - Texture index to use
1592
1607
  * @return {TileInfo}
1593
1608
  * @example
1594
1609
  * tile(2) // a tile at index 2 using the default tile size of 16
@@ -1600,14 +1615,14 @@ let drawCount;
1600
1615
  function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1601
1616
  {
1602
1617
  // if size is a number, make it a vector
1603
- if (size.x == undefined)
1618
+ if (typeof size === 'number')
1604
1619
  {
1605
1620
  ASSERT(size > 0);
1606
1621
  size = vec2(size);
1607
1622
  }
1608
1623
 
1609
1624
  // if pos is a number, use it as a tile index
1610
- if (pos.x == undefined)
1625
+ if (typeof pos === 'number')
1611
1626
  {
1612
1627
  const textureInfo = textureInfos[textureIndex];
1613
1628
  if (textureInfo)
@@ -1631,7 +1646,7 @@ class TileInfo
1631
1646
  /** Create a tile info object
1632
1647
  * @param {Vector2} [pos=Vector2()] - Top left corner of tile in pixels
1633
1648
  * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
1634
- * @param {Number} [textureIndex=0] - Texture index to use
1649
+ * @param {Number} [textureIndex] - Texture index to use
1635
1650
  */
1636
1651
  constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
1637
1652
  {
@@ -1660,10 +1675,13 @@ class TileInfo
1660
1675
  /** Texture Info - Stores info about each texture */
1661
1676
  class TextureInfo
1662
1677
  {
1663
- // create a TextureInfo, called automatically by the engine
1678
+ /**
1679
+ * Create a TextureInfo, called automatically by the engine
1680
+ * @param {HTMLImageElement} image
1681
+ */
1664
1682
  constructor(image)
1665
1683
  {
1666
- /** @property {CanvasImageSource} - image source */
1684
+ /** @property {HTMLImageElement} - image source */
1667
1685
  this.image = image;
1668
1686
  /** @property {Vector2} - size of the image */
1669
1687
  this.size = vec2(image.width, image.height);
@@ -1706,23 +1724,21 @@ function worldToScreen(worldPos)
1706
1724
  * @param {Vector2} pos - Center of the tile in world space
1707
1725
  * @param {Vector2} [size=Vector2(1,1)] - Size of the tile in world space
1708
1726
  * @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
1709
- * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1710
1727
  * @param {Color} [color=Color()] - Color to modulate with
1711
- * @param {Number} [angle=0] - Angle to rotate by
1712
- * @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
1728
+ * @param {Number} [angle] - Angle to rotate by
1729
+ * @param {Boolean} [mirror] - If true image is flipped along the Y axis
1713
1730
  * @param {Color} [additiveColor=Color(0,0,0,0)] - Additive color to be applied
1714
1731
  * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
1715
- * @param {Boolean} [screenSpace=0] - If true the pos and size are in screen space
1732
+ * @param {Boolean} [screenSpace] - If true the pos and size are in screen space
1716
1733
  * @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
1717
1734
  * @memberof Draw */
1718
1735
  function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
1719
1736
  angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
1720
1737
  {
1721
- ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
1722
- ASSERT(typeof tileInfo !== 'number' || !tileInfo); // prevent old style calls
1723
- // to fix old calls, replace with tile(tileIndex, tileSize)
1738
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
1739
+ ASSERT(typeof tileInfo !== 'number' || !tileInfo,
1740
+ 'this is an old style calls, to fix replace it with tile(tileIndex, tileSize)');
1724
1741
 
1725
- showWatermark && ++drawCount;
1726
1742
  const textureInfo = tileInfo && tileInfo.getTextureInfo();
1727
1743
  if (useWebGL)
1728
1744
  {
@@ -1756,6 +1772,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
1756
1772
  else
1757
1773
  {
1758
1774
  // normal canvas 2D rendering method (slower)
1775
+ showWatermark && ++drawCount;
1759
1776
  drawCanvas2D(pos, size, angle, mirror, (context)=>
1760
1777
  {
1761
1778
  if (textureInfo)
@@ -1783,49 +1800,38 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
1783
1800
  * @param {Vector2} pos
1784
1801
  * @param {Vector2} [size=Vector2(1,1)]
1785
1802
  * @param {Color} [color=Color()]
1786
- * @param {Number} [angle=0]
1803
+ * @param {Number} [angle]
1787
1804
  * @param {Boolean} [useWebGL=glEnable]
1788
- * @param {Boolean} [screenSpace=0]
1805
+ * @param {Boolean} [screenSpace]
1789
1806
  * @param {CanvasRenderingContext2D} [context]
1790
1807
  * @memberof Draw */
1791
1808
  function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
1792
1809
  {
1793
- drawTile(pos, size, undefined, color, angle, 0, undefined, useWebGL, screenSpace, context);
1810
+ drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
1794
1811
  }
1795
1812
 
1796
1813
  /** Draw colored polygon using passed in points
1797
1814
  * @param {Array} points - Array of Vector2 points
1798
1815
  * @param {Color} [color=Color()]
1799
- * @param {Boolean} [useWebGL=glEnable]
1800
- * @param {Boolean} [screenSpace=0]
1801
- * @param {CanvasRenderingContext2D} [context]
1816
+ * @param {Boolean} [screenSpace]
1817
+ * @param {CanvasRenderingContext2D} [context=mainContext]
1802
1818
  * @memberof Draw */
1803
- function drawPoly(points, color=new Color, useWebGL=glEnable, screenSpace, context)
1819
+ function drawPoly(points, color=new Color, screenSpace, context=mainContext)
1804
1820
  {
1805
- ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
1806
-
1807
- if (useWebGL)
1808
- glDrawPoints(screenSpace ? points.map(screenToWorld) : points, color.rgbaInt());
1809
- else
1810
- {
1811
- // draw using canvas
1812
- if (!context)
1813
- context = mainContext;
1814
- context.fillStyle = color;
1815
- context.beginPath();
1816
- for (const point of screenSpace ? points : points.map(worldToScreen))
1817
- context.lineTo(point.x, point.y);
1818
- context.fill();
1819
- }
1821
+ context.fillStyle = color.toString();
1822
+ context.beginPath();
1823
+ for (const point of screenSpace ? points : points.map(worldToScreen))
1824
+ context.lineTo(point.x, point.y);
1825
+ context.fill();
1820
1826
  }
1821
1827
 
1822
1828
  /** Draw colored line between two points
1823
1829
  * @param {Vector2} posA
1824
1830
  * @param {Vector2} posB
1825
- * @param {Number} [thickness=.1]
1831
+ * @param {Number} [thickness]
1826
1832
  * @param {Color} [color=Color()]
1827
1833
  * @param {Boolean} [useWebGL=glEnable]
1828
- * @param {Boolean} [screenSpace=0]
1834
+ * @param {Boolean} [screenSpace]
1829
1835
  * @param {CanvasRenderingContext2D} [context]
1830
1836
  * @memberof Draw */
1831
1837
  function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
@@ -1841,7 +1847,7 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
1841
1847
  * @param {Number} angle
1842
1848
  * @param {Boolean} mirror
1843
1849
  * @param {Function} drawFunction
1844
- * @param {Boolean} [screenSpace=0]
1850
+ * @param {Boolean} [screenSpace]
1845
1851
  * @param {CanvasRenderingContext2D} [context=mainContext]
1846
1852
  * @memberof Draw */
1847
1853
  function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
@@ -1861,13 +1867,13 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
1861
1867
  }
1862
1868
 
1863
1869
  /** Enable normal or additive blend mode
1864
- * @param {Boolean} [additive=0]
1870
+ * @param {Boolean} [additive]
1865
1871
  * @param {Boolean} [useWebGL=glEnable]
1866
1872
  * @param {CanvasRenderingContext2D} [context=mainContext]
1867
1873
  * @memberof Draw */
1868
1874
  function setBlendMode(additive, useWebGL=glEnable, context)
1869
1875
  {
1870
- ASSERT(!context || !useWebGL); // context only supported in canvas 2D mode
1876
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
1871
1877
  if (useWebGL)
1872
1878
  glAdditive = additive;
1873
1879
  else
@@ -1882,11 +1888,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
1882
1888
  * Automatically splits new lines into rows
1883
1889
  * @param {String} text
1884
1890
  * @param {Vector2} pos
1885
- * @param {Number} [size=1]
1891
+ * @param {Number} [size]
1886
1892
  * @param {Color} [color=Color()]
1887
- * @param {Number} [lineWidth=0]
1893
+ * @param {Number} [lineWidth]
1888
1894
  * @param {Color} [lineColor=Color(0,0,0)]
1889
- * @param {String} [textAlign='center']
1895
+ * @param {CanvasTextAlign} [textAlign='center']
1890
1896
  * @param {String} [font=fontDefault]
1891
1897
  * @param {CanvasRenderingContext2D} [context=overlayContext]
1892
1898
  * @memberof Draw */
@@ -1899,19 +1905,19 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
1899
1905
  * Automatically splits new lines into rows
1900
1906
  * @param {String} text
1901
1907
  * @param {Vector2} pos
1902
- * @param {Number} [size=1]
1908
+ * @param {Number} [size]
1903
1909
  * @param {Color} [color=Color()]
1904
- * @param {Number} [lineWidth=0]
1910
+ * @param {Number} [lineWidth]
1905
1911
  * @param {Color} [lineColor=Color(0,0,0)]
1906
- * @param {String} [textAlign='center']
1912
+ * @param {CanvasTextAlign} [textAlign]
1907
1913
  * @param {String} [font=fontDefault]
1908
1914
  * @param {CanvasRenderingContext2D} [context=overlayContext]
1909
1915
  * @memberof Draw */
1910
1916
  function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
1911
1917
  {
1912
- context.fillStyle = color;
1918
+ context.fillStyle = color.toString();
1913
1919
  context.lineWidth = lineWidth;
1914
- context.strokeStyle = lineColor;
1920
+ context.strokeStyle = lineColor.toString();
1915
1921
  context.textAlign = textAlign;
1916
1922
  context.font = size + 'px '+ font;
1917
1923
  context.textBaseline = 'middle';
@@ -1946,8 +1952,8 @@ class FontImage
1946
1952
  {
1947
1953
  /** Create an image font
1948
1954
  * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
1949
- * @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles
1950
- * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
1955
+ * @param {Vector2} [tileSize=Vector2(8)] - Size of the font source tiles
1956
+ * @param {Vector2} [paddingSize=Vector2(0,1)] - How much extra space to add between characters
1951
1957
  * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
1952
1958
  */
1953
1959
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
@@ -1976,7 +1982,7 @@ class FontImage
1976
1982
  /** Draw text in screen space using the image font
1977
1983
  * @param {String} text
1978
1984
  * @param {Vector2} pos
1979
- * @param {Number} [scale=4]
1985
+ * @param {Number} [scale]
1980
1986
  * @param {Boolean} [center]
1981
1987
  */
1982
1988
  drawTextScreen(text, pos, scale=4, center)
@@ -1994,7 +2000,7 @@ class FontImage
1994
2000
  for(let j=line.length; j--;)
1995
2001
  {
1996
2002
  // draw each character
1997
- let charCode = line[j].charCodeAt();
2003
+ let charCode = line[j].charCodeAt(0);
1998
2004
  if (charCode < 32 || charCode > 127)
1999
2005
  charCode = 127; // unknown character
2000
2006
 
@@ -2018,7 +2024,7 @@ class FontImage
2018
2024
  /** Returns true if fullscreen mode is active
2019
2025
  * @return {Boolean}
2020
2026
  * @memberof Draw */
2021
- function isFullscreen() { return document.fullscreenElement; }
2027
+ function isFullscreen() { return !!document.fullscreenElement; }
2022
2028
 
2023
2029
  /** Toggle fullsceen mode
2024
2030
  * @memberof Draw */
@@ -2044,28 +2050,37 @@ function toggleFullscreen()
2044
2050
 
2045
2051
 
2046
2052
  /** Returns true if device key is down
2047
- * @param {Number} key
2048
- * @param {Number} [device=0]
2053
+ * @param {String|Number} key
2054
+ * @param {Number} [device]
2049
2055
  * @return {Boolean}
2050
2056
  * @memberof Input */
2051
2057
  function keyIsDown(key, device=0)
2052
- { return inputData[device] && inputData[device][key] & 1; }
2058
+ {
2059
+ ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
2060
+ return inputData[device] && !!(inputData[device][key] & 1);
2061
+ }
2053
2062
 
2054
2063
  /** Returns true if device key was pressed this frame
2055
- * @param {Number} key
2056
- * @param {Number} [device=0]
2064
+ * @param {String|Number} key
2065
+ * @param {Number} [device]
2057
2066
  * @return {Boolean}
2058
2067
  * @memberof Input */
2059
2068
  function keyWasPressed(key, device=0)
2060
- { return inputData[device] && inputData[device][key] & 2 ? 1 : 0; }
2069
+ {
2070
+ ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
2071
+ return inputData[device] && !!(inputData[device][key] & 2);
2072
+ }
2061
2073
 
2062
2074
  /** Returns true if device key was released this frame
2063
- * @param {Number} key
2064
- * @param {Number} [device=0]
2075
+ * @param {String|Number} key
2076
+ * @param {Number} [device]
2065
2077
  * @return {Boolean}
2066
2078
  * @memberof Input */
2067
2079
  function keyWasReleased(key, device=0)
2068
- { return inputData[device] && inputData[device][key] & 4 ? 1 : 0; }
2080
+ {
2081
+ ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
2082
+ return inputData[device] && !!(inputData[device][key] & 4);
2083
+ }
2069
2084
 
2070
2085
  /** Clears all input
2071
2086
  * @memberof Input */
@@ -2110,16 +2125,16 @@ let mouseWheel = 0;
2110
2125
  /** Returns true if user is using gamepad (has more recently pressed a gamepad button)
2111
2126
  * @type {Boolean}
2112
2127
  * @memberof Input */
2113
- let isUsingGamepad = 0;
2128
+ let isUsingGamepad = false;
2114
2129
 
2115
2130
  /** Prevents input continuing to the default browser handling (false by default)
2116
2131
  * @type {Boolean}
2117
2132
  * @memberof Input */
2118
- let preventDefaultInput = 0;
2133
+ let preventDefaultInput = false;
2119
2134
 
2120
2135
  /** Returns true if gamepad button is down
2121
2136
  * @param {Number} button
2122
- * @param {Number} [gamepad=0]
2137
+ * @param {Number} [gamepad]
2123
2138
  * @return {Boolean}
2124
2139
  * @memberof Input */
2125
2140
  function gamepadIsDown(button, gamepad=0)
@@ -2127,7 +2142,7 @@ function gamepadIsDown(button, gamepad=0)
2127
2142
 
2128
2143
  /** Returns true if gamepad button was pressed
2129
2144
  * @param {Number} button
2130
- * @param {Number} [gamepad=0]
2145
+ * @param {Number} [gamepad]
2131
2146
  * @return {Boolean}
2132
2147
  * @memberof Input */
2133
2148
  function gamepadWasPressed(button, gamepad=0)
@@ -2135,7 +2150,7 @@ function gamepadWasPressed(button, gamepad=0)
2135
2150
 
2136
2151
  /** Returns true if gamepad button was released
2137
2152
  * @param {Number} button
2138
- * @param {Number} [gamepad=0]
2153
+ * @param {Number} [gamepad]
2139
2154
  * @return {Boolean}
2140
2155
  * @memberof Input */
2141
2156
  function gamepadWasReleased(button, gamepad=0)
@@ -2143,7 +2158,7 @@ function gamepadWasReleased(button, gamepad=0)
2143
2158
 
2144
2159
  /** Returns gamepad stick value
2145
2160
  * @param {Number} stick
2146
- * @param {Number} [gamepad=0]
2161
+ * @param {Number} [gamepad]
2147
2162
  * @return {Vector2}
2148
2163
  * @memberof Input */
2149
2164
  function gamepadStick(stick, gamepad=0)
@@ -2186,9 +2201,10 @@ function inputUpdatePost()
2186
2201
  if (debug && e.target != document.body) return;
2187
2202
  if (!e.repeat)
2188
2203
  {
2189
- inputData[isUsingGamepad = 0][e.which] = 3;
2204
+ isUsingGamepad = false;
2205
+ inputData[0][e.code] = 3;
2190
2206
  if (inputWASDEmulateDirection)
2191
- inputData[0][remapKey(e.which)] = 3;
2207
+ inputData[0][remapKey(e.code)] = 3;
2192
2208
  }
2193
2209
  preventDefaultInput && e.preventDefault();
2194
2210
  }
@@ -2196,26 +2212,29 @@ function inputUpdatePost()
2196
2212
  onkeyup = (e)=>
2197
2213
  {
2198
2214
  if (debug && e.target != document.body) return;
2199
- inputData[0][e.which] = 4;
2215
+ inputData[0][e.code] = 4;
2200
2216
  if (inputWASDEmulateDirection)
2201
- inputData[0][remapKey(e.which)] = 4;
2217
+ inputData[0][remapKey(e.code)] = 4;
2202
2218
  }
2203
2219
 
2204
2220
  // handle remapping wasd keys to directions
2205
2221
  function remapKey(c)
2206
- {
2222
+ {
2207
2223
  return inputWASDEmulateDirection ?
2208
- c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
2224
+ c == 'KeyW' ? 'ArrowUp' :
2225
+ c == 'KeyS' ? 'ArrowDown' :
2226
+ c == 'KeyA' ? 'ArrowLeft' :
2227
+ c == 'KeyD' ? 'ArrowRight' : c : c;
2209
2228
  }
2210
2229
  }
2211
2230
 
2212
2231
  ///////////////////////////////////////////////////////////////////////////////
2213
2232
  // Mouse event handlers
2214
2233
 
2215
- onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
2234
+ onmousedown = (e)=> {isUsingGamepad = false; inputData[0][e.button] = 3; window.onmousemove(e); e.button && e.preventDefault();}
2216
2235
  onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
2217
2236
  onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
2218
- onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
2237
+ onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
2219
2238
  oncontextmenu = (e)=> false; // prevent right click menu
2220
2239
 
2221
2240
  // convert a mouse or touch event position to screen space
@@ -2253,7 +2272,7 @@ function gamepadsUpdate()
2253
2272
  for (let i=10; i--;)
2254
2273
  {
2255
2274
  const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
2256
- data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
2275
+ data[j] = touchGamepadButtons[i] ? gamepadIsDown(j,0) ? 1 : 3 : gamepadIsDown(j,0) ? 4 : 0;
2257
2276
  }
2258
2277
  }
2259
2278
  }
@@ -2285,18 +2304,23 @@ function gamepadsUpdate()
2285
2304
  for (let j = gamepad.buttons.length; j--;)
2286
2305
  {
2287
2306
  const button = gamepad.buttons[j];
2288
- data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
2289
- isUsingGamepad |= !i && button.pressed;
2290
- touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
2307
+ const wasDown = gamepadIsDown(j,i);
2308
+ data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
2309
+ isUsingGamepad ||= !i && button.pressed;
2291
2310
  }
2292
2311
 
2293
2312
  if (gamepadDirectionEmulateStick)
2294
2313
  {
2295
2314
  // copy dpad to left analog stick when pressed
2296
- const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
2315
+ const dpad = vec2(
2316
+ (gamepadIsDown(15,i)&&1) - (gamepadIsDown(14,i)&&1),
2317
+ (gamepadIsDown(12,i)&&1) - (gamepadIsDown(13,i)&&1));
2297
2318
  if (dpad.lengthSquared())
2298
2319
  sticks[0] = dpad.clampLength();
2299
2320
  }
2321
+
2322
+ // disable touch gamepad if using real gamepad
2323
+ touchGamepadEnable && isUsingGamepad && touchGamepadTimer.unset();
2300
2324
  }
2301
2325
  }
2302
2326
  }
@@ -2304,9 +2328,9 @@ function gamepadsUpdate()
2304
2328
  ///////////////////////////////////////////////////////////////////////////////
2305
2329
 
2306
2330
  /** Pulse the vibration hardware if it exists
2307
- * @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
2331
+ * @param {Number} [pattern] - a single value in miliseconds or vibration interval array
2308
2332
  * @memberof Input */
2309
- function vibrate(pattern)
2333
+ function vibrate(pattern=100)
2310
2334
  { vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
2311
2335
 
2312
2336
  /** Cancel any ongoing vibration
@@ -2324,29 +2348,28 @@ const isTouchDevice = window.ontouchstart !== undefined;
2324
2348
  if (isTouchDevice)
2325
2349
  {
2326
2350
  // override mouse events
2327
- let wasTouching, mouseDown = onmousedown, mouseUp = onmouseup;
2351
+ let wasTouching;
2328
2352
  onmousedown = onmouseup = ()=> 0;
2329
2353
 
2330
2354
  // handle all touch events the same way
2331
2355
  ontouchstart = ontouchmove = ontouchend = (e)=>
2332
2356
  {
2333
- e.button = 0; // all touches are left click
2334
-
2335
2357
  // fix stalled audio on mobile
2336
2358
  if (soundEnable)
2337
2359
  audioContext ? audioContext.resume() : zzfx(0);
2338
2360
 
2339
2361
  // check if touching and pass to mouse events
2340
2362
  const touching = e.touches.length;
2363
+ const button = 0; // all touches are left mouse button
2341
2364
  if (touching)
2342
2365
  {
2343
2366
  // set event pos and pass it along
2344
- e.x = e.touches[0].clientX;
2345
- e.y = e.touches[0].clientY;
2346
- wasTouching ? onmousemove(e) : mouseDown(e);
2367
+ const p = vec2(e.touches[0].clientX, e.touches[0].clientY);
2368
+ mousePosScreen = mouseToScreen(p);
2369
+ wasTouching ? isUsingGamepad = false : inputData[0][button] = 3;
2347
2370
  }
2348
2371
  else if (wasTouching)
2349
- mouseUp(e);
2372
+ inputData[0][button] = inputData[0][button] & 2 | 4;
2350
2373
 
2351
2374
  // set was touching
2352
2375
  wasTouching = touching;
@@ -2427,8 +2450,8 @@ function createTouchGamepad()
2427
2450
  }
2428
2451
 
2429
2452
  // call default touch handler and set to using gamepad
2430
- touchHandler(e);
2431
- isUsingGamepad = 1;
2453
+ touchHandler.bind(window)(e);
2454
+ isUsingGamepad = true;
2432
2455
 
2433
2456
  // must return true so the document will get focus
2434
2457
  return true;
@@ -2442,7 +2465,7 @@ function touchGamepadRender()
2442
2465
  return;
2443
2466
 
2444
2467
  // fade off when not touching or paused
2445
- const alpha = percent(touchGamepadTimer, 4, 3);
2468
+ const alpha = percent(touchGamepadTimer.get(), 4, 3);
2446
2469
  if (!alpha || paused)
2447
2470
  return;
2448
2471
 
@@ -2546,13 +2569,13 @@ class Sound
2546
2569
 
2547
2570
  /** Play the sound
2548
2571
  * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
2549
- * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2550
- * @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
2551
- * @param {Number} [randomnessScale=1] - How much to scale randomness
2552
- * @param {Boolean} [loop=0] - Should the sound loop
2572
+ * @param {Number} [volume] - How much to scale volume by (in addition to range fade)
2573
+ * @param {Number} [pitch] - How much to scale pitch by (also adjusted by this.randomness)
2574
+ * @param {Number} [randomnessScale] - How much to scale randomness
2575
+ * @param {Boolean} [loop] - Should the sound loop
2553
2576
  * @return {AudioBufferSourceNode} - The audio source node
2554
2577
  */
2555
- play(pos, volume=1, pitch=1, randomnessScale=1, loop=0)
2578
+ play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
2556
2579
  {
2557
2580
  if (!soundEnable || !this.sampleChannels) return;
2558
2581
 
@@ -2585,8 +2608,13 @@ class Sound
2585
2608
  {
2586
2609
  if (this.source)
2587
2610
  this.source.stop();
2588
- this.source = 0;
2611
+ this.source = undefined;
2589
2612
  }
2613
+
2614
+ /** Get source of most recent instance of this sound that was played
2615
+ * @return {AudioBufferSourceNode}
2616
+ */
2617
+ getSource() { return this.source; }
2590
2618
 
2591
2619
  /** Play the sound as a note with a semitone offset
2592
2620
  * @param {Number} semitoneOffset - How many semitones to offset pitch
@@ -2602,12 +2630,7 @@ class Sound
2602
2630
  */
2603
2631
  getDuration()
2604
2632
  { return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
2605
-
2606
- /** Check if the last instance of this sound is playing
2607
- * @return {Boolean} - True if the sound is playing
2608
- */
2609
- isPlaying() { return this.source && !this.source.ended; }
2610
-
2633
+
2611
2634
  /** Check if sound is loading, for sounds fetched from a url
2612
2635
  * @return {Boolean} - True if sound is loading and not ready to play
2613
2636
  */
@@ -2628,13 +2651,13 @@ class SoundWave extends Sound
2628
2651
  {
2629
2652
  /** Create a sound object and cache the wave file for later use
2630
2653
  * @param {String} filename - Filename of audio file to load
2631
- * @param {Number} [randomness=0] - How much to randomize frequency each time sound plays
2654
+ * @param {Number} [randomness] - How much to randomize frequency each time sound plays
2632
2655
  * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
2633
2656
  * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
2634
2657
  */
2635
2658
  constructor(filename, randomness=0, range, taper)
2636
2659
  {
2637
- super(0, range, taper);
2660
+ super(undefined, range, taper);
2638
2661
  this.randomness = randomness;
2639
2662
 
2640
2663
  if (!soundEnable) return;
@@ -2688,11 +2711,11 @@ let soundDecoderContext; // audio context used only to decode audio files
2688
2711
  class Music extends Sound
2689
2712
  {
2690
2713
  /** Create a music object and cache the zzfx music samples for later use
2691
- * @param {Array} zzfxMusic - Array of zzfx music parameters
2714
+ * @param {[Array, Array, Array, Number]} zzfxMusic - Array of zzfx music parameters
2692
2715
  */
2693
2716
  constructor(zzfxMusic)
2694
2717
  {
2695
- super();
2718
+ super(undefined);
2696
2719
 
2697
2720
  if (!soundEnable) return;
2698
2721
  this.randomness = 0;
@@ -2705,17 +2728,17 @@ class Music extends Sound
2705
2728
  * @param {Boolean} [loop=1] - True if the music should loop
2706
2729
  * @return {AudioBufferSourceNode} - The audio source node
2707
2730
  */
2708
- playMusic(volume, loop = 1)
2709
- { return super.play(0, volume, 1, 1, loop); }
2731
+ playMusic(volume, loop = false)
2732
+ { return super.play(undefined, volume, 1, 1, loop); }
2710
2733
  }
2711
2734
 
2712
2735
  /** Play an mp3, ogg, or wav audio from a local file or url
2713
2736
  * @param {String} url - Location of sound file to play
2714
- * @param {Number} [volume=1] - How much to scale volume by
2715
- * @param {Boolean} [loop=1] - True if the music should loop
2737
+ * @param {Number} [volume] - How much to scale volume by
2738
+ * @param {Boolean} [loop] - True if the music should loop
2716
2739
  * @return {HTMLAudioElement} - The audio element for this sound
2717
2740
  * @memberof Audio */
2718
- function playAudioFile(url, volume=1, loop=1)
2741
+ function playAudioFile(url, volume=1, loop=false)
2719
2742
  {
2720
2743
  if (!soundEnable) return;
2721
2744
 
@@ -2729,9 +2752,9 @@ function playAudioFile(url, volume=1, loop=1)
2729
2752
  /** Speak text with passed in settings
2730
2753
  * @param {String} text - The text to speak
2731
2754
  * @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
2732
- * @param {Number} [volume=1] - How much to scale volume by
2733
- * @param {Number} [rate=1] - How quickly to speak
2734
- * @param {Number} [pitch=1] - How much to change the pitch by
2755
+ * @param {Number} [volume] - How much to scale volume by
2756
+ * @param {Number} [rate] - How quickly to speak
2757
+ * @param {Number} [pitch] - How much to change the pitch by
2735
2758
  * @return {SpeechSynthesisUtterance} - The utterance that was spoken
2736
2759
  * @memberof Audio */
2737
2760
  function speak(text, language='', volume=1, rate=1, pitch=1)
@@ -2758,7 +2781,7 @@ function speakStop() {speechSynthesis && speechSynthesis.cancel();}
2758
2781
 
2759
2782
  /** Get frequency of a note on a musical scale
2760
2783
  * @param {Number} semitoneOffset - How many semitones away from the root note
2761
- * @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0
2784
+ * @param {Number} [rootFrequency=220] - Frequency at semitone offset 0
2762
2785
  * @return {Number} - The frequency of the note
2763
2786
  * @memberof Audio */
2764
2787
  function getNoteFrequency(semitoneOffset, rootFrequency=220)
@@ -2772,14 +2795,14 @@ let audioContext;
2772
2795
 
2773
2796
  /** Play cached audio samples with given settings
2774
2797
  * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
2775
- * @param {Number} [volume=1] - How much to scale volume by
2776
- * @param {Number} [rate=1] - The playback rate to use
2777
- * @param {Number} [pan=0] - How much to apply stereo panning
2778
- * @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end
2798
+ * @param {Number} [volume] - How much to scale volume by
2799
+ * @param {Number} [rate] - The playback rate to use
2800
+ * @param {Number} [pan] - How much to apply stereo panning
2801
+ * @param {Boolean} [loop] - True if the sound should loop when it reaches the end
2779
2802
  * @param {Number} [sampleRate=44100] - Sample rate for the sound
2780
2803
  * @return {AudioBufferSourceNode} - The audio node of the sound played
2781
2804
  * @memberof Audio */
2782
- function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0, sampleRate=zzfxR)
2805
+ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
2783
2806
  {
2784
2807
  if (!soundEnable) return;
2785
2808
 
@@ -2819,7 +2842,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0, sampleRate
2819
2842
  }
2820
2843
 
2821
2844
  ///////////////////////////////////////////////////////////////////////////////
2822
- // ZzFXMicro - Zuper Zmall Zound Zynth - v1.2.0 by Frank Force
2845
+ // ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.1 by Frank Force
2823
2846
 
2824
2847
  /** Generate and play a ZzFX sound
2825
2848
  *
@@ -2835,26 +2858,27 @@ function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
2835
2858
  const zzfxR = 44100;
2836
2859
 
2837
2860
  /** Generate samples for a ZzFX sound
2838
- * @param {Number} [volume=1] - Volume scale (percent)
2839
- * @param {Number} [randomness=.05] - How much to randomize frequency (percent Hz)
2840
- * @param {Number} [frequency=220] - Frequency of sound (Hz)
2841
- * @param {Number} [attack=0] - Attack time, how fast sound starts (seconds)
2842
- * @param {Number} [sustain=0] - Sustain time, how long sound holds (seconds)
2843
- * @param {Number} [release=.1] - Release time, how fast sound fades out (seconds)
2844
- * @param {Number} [shape=0] - Shape of the sound wave
2845
- * @param {Number} [shapeCurve=1] - Squarenes of wave (0=square, 1=normal, 2=pointy)
2846
- * @param {Number} [slide=0] - How much to slide frequency (kHz/s)
2847
- * @param {Number} [deltaSlide=0] - How much to change slide (kHz/s/s)
2848
- * @param {Number} [pitchJump=0] - Frequency of pitch jump (Hz)
2849
- * @param {Number} [pitchJumpTime=0] - Time of pitch jump (seconds)
2850
- * @param {Number} [repeatTime=0] - Resets some parameters periodically (seconds)
2851
- * @param {Number} [noise=0] - How much random noise to add (percent)
2852
- * @param {Number} [modulation=0] - Frequency of modulation wave, negative flips phase (Hz)
2853
- * @param {Number} [bitCrush=0] - Resamples at a lower frequency in (samples*100)
2854
- * @param {Number} [delay=0] - Overlap sound with itself for reverb and flanger effects (seconds)
2855
- * @param {Number} [sustainVolume=1] - Volume level for sustain (percent)
2856
- * @param {Number} [decay=0] - Decay time, how long to reach sustain after attack (seconds)
2857
- * @param {Number} [tremolo=0] - Trembling effect, rate controlled by repeat time (precent)
2861
+ * @param {Number} [volume] - Volume scale (percent)
2862
+ * @param {Number} [randomness] - How much to randomize frequency (percent Hz)
2863
+ * @param {Number} [frequency] - Frequency of sound (Hz)
2864
+ * @param {Number} [attack] - Attack time, how fast sound starts (seconds)
2865
+ * @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
2866
+ * @param {Number} [release] - Release time, how fast sound fades out (seconds)
2867
+ * @param {Number} [shape] - Shape of the sound wave
2868
+ * @param {Number} [shapeCurve] - Squarenes of wave (0=square, 1=normal, 2=pointy)
2869
+ * @param {Number} [slide] - How much to slide frequency (kHz/s)
2870
+ * @param {Number} [deltaSlide] - How much to change slide (kHz/s/s)
2871
+ * @param {Number} [pitchJump] - Frequency of pitch jump (Hz)
2872
+ * @param {Number} [pitchJumpTime] - Time of pitch jump (seconds)
2873
+ * @param {Number} [repeatTime] - Resets some parameters periodically (seconds)
2874
+ * @param {Number} [noise] - How much random noise to add (percent)
2875
+ * @param {Number} [modulation] - Frequency of modulation wave, negative flips phase (Hz)
2876
+ * @param {Number} [bitCrush] - Resamples at a lower frequency in (samples*100)
2877
+ * @param {Number} [delay] - Overlap sound with itself for reverb and flanger effects (seconds)
2878
+ * @param {Number} [sustainVolume] - Volume level for sustain (percent)
2879
+ * @param {Number} [decay] - Decay time, how long to reach sustain after attack (seconds)
2880
+ * @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (precent)
2881
+ * @param {Number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
2858
2882
  * @return {Array} - Array of audio samples
2859
2883
  * @memberof Audio
2860
2884
  */
@@ -2864,78 +2888,91 @@ function zzfxG
2864
2888
  volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
2865
2889
  release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
2866
2890
  pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
2867
- bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0
2891
+ bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
2868
2892
  )
2869
2893
  {
2870
- // locals
2871
- let PI2 = PI*2, startSlide = slide *= 500 * PI2 / zzfxR / zzfxR, b=[],
2872
- startFrequency = frequency *= (1 + randomness*rand(-1,1)) * PI2 / zzfxR,
2873
- t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length
2894
+ // init parameters
2895
+ let PI2 = PI*2, sampleRate = zzfxR,
2896
+ startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
2897
+ startFrequency = frequency *=
2898
+ rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
2899
+ b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
2900
+
2901
+ // biquad LP/HP filter
2902
+ quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
2903
+ cos = Math.cos(w), alpha = Math.sin(w) / 2 / quality,
2904
+ a0 = 1 + alpha, a1 = -2*cos / a0, a2 = (1 - alpha) / a0,
2905
+ b0 = (1 + sign(filter) * cos) / 2 / a0,
2906
+ b1 = -(sign(filter) + cos) / a0, b2 = b0,
2907
+ x2 = 0, x1 = 0, y2 = 0, y1 = 0;
2874
2908
 
2875
2909
  // scale by sample rate
2876
- attack = attack * zzfxR + 9; // minimum attack to prevent pop
2877
- decay *= zzfxR;
2878
- sustain *= zzfxR;
2879
- release *= zzfxR;
2880
- delay *= zzfxR;
2881
- deltaSlide *= 500 * PI2 / zzfxR**3;
2882
- modulation *= PI2 / zzfxR;
2883
- pitchJump *= PI2 / zzfxR;
2884
- pitchJumpTime *= zzfxR;
2885
- repeatTime = repeatTime * zzfxR | 0;
2910
+ attack = attack * sampleRate + 9; // minimum attack to prevent pop
2911
+ decay *= sampleRate;
2912
+ sustain *= sampleRate;
2913
+ release *= sampleRate;
2914
+ delay *= sampleRate;
2915
+ deltaSlide *= 500 * PI2 / sampleRate**3;
2916
+ modulation *= PI2 / sampleRate;
2917
+ pitchJump *= PI2 / sampleRate;
2918
+ pitchJumpTime *= sampleRate;
2919
+ repeatTime = repeatTime * sampleRate | 0;
2920
+ volume *= soundVolume;
2886
2921
 
2887
2922
  // generate waveform
2888
- for (length = attack + decay + sustain + release + delay | 0;
2889
- i < length; b[i++] = s)
2923
+ for(length = attack + decay + sustain + release + delay | 0;
2924
+ i < length; b[i++] = s * volume) // sample
2890
2925
  {
2891
- if (!(++c%(bitCrush*100|0))) // bit crush
2926
+ if (!(++c%(bitCrush*100|0))) // bit crush
2892
2927
  {
2893
- s = shape? shape>1? shape>2? shape>3? // wave shape
2894
- Math.sin((t%PI2)**3) : // 4 noise
2895
- max(min(Math.tan(t),1),-1): // 3 tan
2896
- 1-(2*t/PI2%2+2)%2: // 2 saw
2897
- 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
2898
- Math.sin(t); // 0 sin
2899
-
2928
+ s = shape? shape>1? shape>2? shape>3? // wave shape
2929
+ Math.sin(t**3) : // 4 noise
2930
+ clamp(Math.tan(t),1,-1): // 3 tan
2931
+ 1-(2*t/PI2%2+2)%2: // 2 saw
2932
+ 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
2933
+ Math.sin(t); // 0 sin
2934
+
2900
2935
  s = (repeatTime ?
2901
2936
  1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
2902
2937
  : 1) *
2903
- sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
2904
- volume * soundVolume * ( // envelope
2905
- i < attack ? i/attack : // attack
2906
- i < attack + decay ? // decay
2907
- 1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
2908
- i < attack + decay + sustain ? // sustain
2909
- sustainVolume : // sustain volume
2910
- i < length - delay ? // release
2911
- (length - i - delay)/release * // release falloff
2912
- sustainVolume : // release volume
2913
- 0); // post release
2914
-
2915
- s = delay ? s/2 + (delay > i ? 0 : // delay
2916
- (i<length-delay? 1 : (length-i)/delay) * // release delay
2917
- b[i-delay|0]/2) : s; // sample delay
2918
- }
2919
-
2920
- f = (frequency += slide += deltaSlide) * // frequency
2921
- Math.cos(modulation*tm++); // modulation
2922
- t += f - f*noise*(1 - (Math.sin(i)+1)*1e9%2); // noise
2923
-
2924
- if (j && ++j > pitchJumpTime) // pitch jump
2925
- {
2926
- frequency += pitchJump; // apply pitch jump
2927
- startFrequency += pitchJump; // also apply to start
2928
- j = 0; // reset pitch jump time
2938
+ sign(s)*(abs(s)**shapeCurve) * // curve
2939
+ (i < attack ? i/attack : // attack
2940
+ i < attack + decay ? // decay
2941
+ 1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
2942
+ i < attack + decay + sustain ? // sustain
2943
+ sustainVolume : // sustain volume
2944
+ i < length - delay ? // release
2945
+ (length - i - delay)/release * // release falloff
2946
+ sustainVolume : // release volume
2947
+ 0); // post release
2948
+
2949
+ s = delay ? s/2 + (delay > i ? 0 : // delay
2950
+ (i<length-delay? 1 : (length-i)/delay) * // release delay
2951
+ b[i-delay|0]/2/volume) : s; // sample delay
2952
+
2953
+ if (filter) // apply filter
2954
+ s = y1 = b2*x2 + b1*(x2=x1) + b0*(x1=s) - a2*y2 - a1*(y2=y1);
2929
2955
  }
2930
2956
 
2931
- if (repeatTime && !(++r % repeatTime)) // repeat
2932
- {
2933
- frequency = startFrequency; // reset frequency
2934
- slide = startSlide; // reset slide
2935
- j ||= 1; // reset pitch jump time
2957
+ f = (frequency += slide += deltaSlide) *// frequency
2958
+ Math.cos(modulation*tm++); // modulation
2959
+ t += f + f*noise*Math.sin(i**5); // noise
2960
+
2961
+ if (j && ++j > pitchJumpTime) // pitch jump
2962
+ {
2963
+ frequency += pitchJump; // apply pitch jump
2964
+ startFrequency += pitchJump; // also apply to start
2965
+ j = 0; // stop pitch jump time
2966
+ }
2967
+
2968
+ if (repeatTime && !(++r % repeatTime)) // repeat
2969
+ {
2970
+ frequency = startFrequency; // reset frequency
2971
+ slide = startSlide; // reset slide
2972
+ j = j || 1; // reset pitch jump time
2936
2973
  }
2937
2974
  }
2938
-
2975
+
2939
2976
  return b;
2940
2977
  }
2941
2978
 
@@ -2946,7 +2983,7 @@ function zzfxG
2946
2983
  * @param {Array} instruments - Array of ZzFX sound paramaters
2947
2984
  * @param {Array} patterns - Array of pattern data
2948
2985
  * @param {Array} sequence - Array of pattern indexes
2949
- * @param {Number} [BPM=125] - Playback speed of the song in BPM
2986
+ * @param {Number} [BPM] - Playback speed of the song in BPM
2950
2987
  * @return {Array} - Left and right channel sample data
2951
2988
  * @memberof Audio */
2952
2989
  function zzfxM(instruments, patterns, sequence, BPM = 125)
@@ -2985,10 +3022,10 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
2985
3022
  patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
2986
3023
 
2987
3024
  // check if there are more channels
2988
- hasMore ||= !!patterns[patternIndex][channelIndex];
3025
+ hasMore |= patterns[patternIndex][channelIndex]&&1;
2989
3026
 
2990
3027
  // get next offset, use the length of first channel
2991
- nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - !notFirstBeat) * beatLength;
3028
+ nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
2992
3029
  // for each beat in pattern, plus one extra if end of sequence
2993
3030
  isSequenceEnd = sequenceIndex == sequence.length - 1;
2994
3031
  for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
@@ -3004,7 +3041,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3004
3041
  for (j = 0; j < beatLength && notFirstBeat;
3005
3042
 
3006
3043
  // fade off attenuation at end of beat if stopping note, prevents clicking
3007
- j++ > beatLength - 99 && stop ? attenuation += (attenuation < 1) / 99 : 0
3044
+ j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
3008
3045
  ) {
3009
3046
  // copy sample to stereo buffers with panning
3010
3047
  sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
@@ -3080,7 +3117,7 @@ function initTileCollision(size)
3080
3117
 
3081
3118
  /** Set tile collision data
3082
3119
  * @param {Vector2} pos
3083
- * @param {Number} [data=0]
3120
+ * @param {Number} [data]
3084
3121
  * @memberof TileCollision */
3085
3122
  function setTileCollisionData(pos, data=0)
3086
3123
  {
@@ -3113,7 +3150,7 @@ function tileCollisionTest(pos, size=vec2(), object)
3113
3150
  {
3114
3151
  const tileData = tileCollision[y*tileCollisionSize.x+x];
3115
3152
  if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
3116
- return 1;
3153
+ return true;
3117
3154
  }
3118
3155
  }
3119
3156
 
@@ -3179,11 +3216,11 @@ function tileCollisionRaycast(posStart, posEnd, object)
3179
3216
  class TileLayerData
3180
3217
  {
3181
3218
  /** Create a tile layer data object, one for each tile in a TileLayer
3182
- * @param {Number} [tile] - The tile to use, untextured if undefined
3183
- * @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
3184
- * @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
3185
- * @param {Color} [color=Color()] - Color of the tile */
3186
- constructor(tile, direction=0, mirror=0, color=new Color())
3219
+ * @param {Number} [tile] - The tile to use, untextured if undefined
3220
+ * @param {Number} [direction] - Integer direction of tile, in 90 degree increments
3221
+ * @param {Boolean} [mirror] - If the tile should be mirrored along the x axis
3222
+ * @param {Color} [color] - Color of the tile */
3223
+ constructor(tile, direction=0, mirror=false, color=new Color())
3187
3224
  {
3188
3225
  /** @property {Number} - The tile to use, untextured if undefined */
3189
3226
  this.tile = tile;
@@ -3196,7 +3233,7 @@ class TileLayerData
3196
3233
  }
3197
3234
 
3198
3235
  /** Set this tile to clear, it will not be rendered */
3199
- clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
3236
+ clear() { this.tile = this.direction = 0; this.mirror = false; this.color = new Color; }
3200
3237
  }
3201
3238
 
3202
3239
  /**
@@ -3213,25 +3250,25 @@ class TileLayerData
3213
3250
  */
3214
3251
  class TileLayer extends EngineObject
3215
3252
  {
3216
- /** Create a tile layer object
3217
- * @param {Vector2} [position=Vector2()] - World space position
3218
- * @param {Vector2} [size=tileCollisionSize] - World space size
3219
- * @param {TileInfo} [tileInfo] - Tile info for layer
3220
- * @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
3221
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
3253
+ /** Create a tile layer object
3254
+ * @param {Vector2} [position=Vector2()] - World space position
3255
+ * @param {Vector2} [size=tileCollisionSize] - World space size
3256
+ * @param {TileInfo} [tileInfo] - Tile info for layer
3257
+ * @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
3258
+ * @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
3222
3259
  */
3223
- constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
3260
+ constructor(position, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
3224
3261
  {
3225
- super(pos, size, tileInfo, 0, undefined, renderOrder);
3262
+ super(position, size, tileInfo, 0, undefined, renderOrder);
3226
3263
 
3227
- /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
3264
+ /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
3228
3265
  this.canvas = document.createElement('canvas');
3229
3266
  /** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
3230
3267
  this.context = this.canvas.getContext('2d');
3231
- /** @property {Vector2} - How much to scale this layer when rendered */
3268
+ /** @property {Vector2} - How much to scale this layer when rendered */
3232
3269
  this.scale = scale;
3233
- /** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
3234
- this.isOverlay;
3270
+ /** @property {Boolean} - If true this layer will render to overlay canvas and appear above all objects */
3271
+ this.isOverlay = false;
3235
3272
 
3236
3273
  // init tile data
3237
3274
  this.data = [];
@@ -3240,10 +3277,10 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3240
3277
  }
3241
3278
 
3242
3279
  /** Set data at a given position in the array
3243
- * @param {Vector2} position - Local position in array
3244
- * @param {TileLayerData} data - Data to set
3245
- * @param {Boolean} [redraw=0] - Force the tile to redraw if true */
3246
- setData(layerPos, data, redraw)
3280
+ * @param {Vector2} layerPos - Local position in array
3281
+ * @param {TileLayerData} data - Data to set
3282
+ * @param {Boolean} [redraw] - Force the tile to redraw if true */
3283
+ setData(layerPos, data, redraw=false)
3247
3284
  {
3248
3285
  if (layerPos.arrayCheck(this.size))
3249
3286
  {
@@ -3264,7 +3301,7 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3264
3301
  // Render the tile layer, called automatically by the engine
3265
3302
  render()
3266
3303
  {
3267
- ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
3304
+ ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
3268
3305
 
3269
3306
  // flush and copy gl canvas because tile canvas does not use webgl
3270
3307
  glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
@@ -3283,16 +3320,17 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3283
3320
  */
3284
3321
  redraw()
3285
3322
  {
3286
- this.redrawStart(1);
3323
+ this.redrawStart(true);
3287
3324
  this.drawAllTileData();
3288
3325
  this.redrawEnd();
3289
3326
  }
3290
3327
 
3291
3328
  /** Call to start the redraw process
3292
- * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
3293
- redrawStart(clear = 0)
3329
+ * @param {Boolean} [clear] - Should it clear the canvas before drawing */
3330
+ redrawStart(clear=false)
3294
3331
  {
3295
3332
  // save current render settings
3333
+ /** @type {[HTMLCanvasElement, CanvasRenderingContext2D, Vector2, Vector2, number]} */
3296
3334
  this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
3297
3335
 
3298
3336
  // hack: use normal rendering system to render the tiles
@@ -3315,8 +3353,8 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3315
3353
  /** Call to end the redraw process */
3316
3354
  redrawEnd()
3317
3355
  {
3318
- ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3319
- glEnable && glCopyToContext(mainContext, 1);
3356
+ ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
3357
+ glEnable && glCopyToContext(mainContext, true);
3320
3358
  //debugSaveCanvas(this.canvas);
3321
3359
 
3322
3360
  // set stuff back to normal
@@ -3329,13 +3367,13 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3329
3367
  {
3330
3368
  // first clear out where the tile was
3331
3369
  const pos = layerPos.floor().add(this.pos).add(vec2(.5));
3332
- this.drawCanvas2D(pos, vec2(1), 0, 0, (context)=>context.clearRect(-.5, -.5, 1, 1));
3370
+ this.drawCanvas2D(pos, vec2(1), 0, false, (context)=>context.clearRect(-.5, -.5, 1, 1));
3333
3371
 
3334
3372
  // draw the tile if not undefined
3335
3373
  const d = this.getData(layerPos);
3336
3374
  if (d.tile != undefined)
3337
3375
  {
3338
- ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3376
+ ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
3339
3377
  const tileInfo = tile(d.tile, this.tileInfo.size, this.tileInfo.textureIndex);
3340
3378
  drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
3341
3379
  }
@@ -3403,7 +3441,7 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3403
3441
  * @param {Color} [color=Color()]
3404
3442
  * @param {Number} [angle=0] */
3405
3443
  drawRect(pos, size, color, angle)
3406
- { this.drawTile(pos, size, -1, 0, color, angle); }
3444
+ { this.drawTile(pos, size, undefined, color, angle); }
3407
3445
  }
3408
3446
  /**
3409
3447
  * LittleJS Particle System
@@ -3431,37 +3469,37 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
3431
3469
  class ParticleEmitter extends EngineObject
3432
3470
  {
3433
3471
  /** Create a particle system with the given settings
3434
- * @param {Vector2} position - World space position of the emitter
3435
- * @param {Number} [angle=0] - Angle to emit the particles
3436
- * @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
3437
- * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3438
- * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3439
- * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3440
- * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3441
- * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3442
- * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3443
- * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
3444
- * @param {Color} [colorEndB=Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
3445
- * @param {Number} [particleTime=.5] - How long particles live
3446
- * @param {Number} [sizeStart=.1] - How big are particles at start
3447
- * @param {Number} [sizeEnd=1] - How big are particles at end
3448
- * @param {Number} [speed=.1] - How fast are particles when spawned
3449
- * @param {Number} [angleSpeed=.05] - How fast are particles rotating
3450
- * @param {Number} [damping=1] - How much to dampen particle speed
3451
- * @param {Number} [angleDamping=1] - How much to dampen particle angular speed
3452
- * @param {Number} [gravityScale=0] - How much does gravity effect particles
3453
- * @param {Number} [particleConeAngle=PI] - Cone for start particle angle
3454
- * @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
3455
- * @param {Number} [randomness=.2] - Apply extra randomness percent
3456
- * @param {Boolean} [collideTiles=0] - Do particles collide against tiles
3457
- * @param {Boolean} [additive=0] - Should particles use addtive blend
3458
- * @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
3459
- * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
3460
- * @param {Boolean} [localSpace=0] - Should it be in local space of emitter (world space is default)
3472
+ * @param {Vector2} position - World space position of the emitter
3473
+ * @param {Number} [angle] - Angle to emit the particles
3474
+ * @param {Number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
3475
+ * @param {Number} [emitTime] - How long to stay alive (0 is forever)
3476
+ * @param {Number} [emitRate] - How many particles per second to spawn, does not emit if 0
3477
+ * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3478
+ * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3479
+ * @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
3480
+ * @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
3481
+ * @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
3482
+ * @param {Color} [colorEndB=Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
3483
+ * @param {Number} [particleTime] - How long particles live
3484
+ * @param {Number} [sizeStart] - How big are particles at start
3485
+ * @param {Number} [sizeEnd] - How big are particles at end
3486
+ * @param {Number} [speed] - How fast are particles when spawned
3487
+ * @param {Number} [angleSpeed] - How fast are particles rotating
3488
+ * @param {Number} [damping] - How much to dampen particle speed
3489
+ * @param {Number} [angleDamping] - How much to dampen particle angular speed
3490
+ * @param {Number} [gravityScale] - How much gravity effect particles
3491
+ * @param {Number} [particleConeAngle] - Cone for start particle angle
3492
+ * @param {Number} [fadeRate] - How quick to fade particles at start/end in percent of life
3493
+ * @param {Number} [randomness] - Apply extra randomness percent
3494
+ * @param {Boolean} [collideTiles] - Do particles collide against tiles
3495
+ * @param {Boolean} [additive] - Should particles use addtive blend
3496
+ * @param {Boolean} [randomColorLinear] - Should color be randomized linearly or across each component
3497
+ * @param {Number} [renderOrder] - Render order for particles (additive is above other stuff by default)
3498
+ * @param {Boolean} [localSpace] - Should it be in local space of emitter (world space is default)
3461
3499
  */
3462
3500
  constructor
3463
3501
  (
3464
- pos,
3502
+ position,
3465
3503
  angle,
3466
3504
  emitSize = 0,
3467
3505
  emitTime = 0,
@@ -3483,17 +3521,17 @@ class ParticleEmitter extends EngineObject
3483
3521
  particleConeAngle = PI,
3484
3522
  fadeRate = .1,
3485
3523
  randomness = .2,
3486
- collideTiles,
3487
- additive,
3488
- randomColorLinear = 1,
3524
+ collideTiles = false,
3525
+ additive = false,
3526
+ randomColorLinear = true,
3489
3527
  renderOrder = additive ? 1e9 : 0,
3490
- localSpace
3528
+ localSpace = false
3491
3529
  )
3492
3530
  {
3493
- super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
3531
+ super(position, vec2(), tileInfo, angle, undefined, renderOrder);
3494
3532
 
3495
3533
  // emitter settings
3496
- /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
3534
+ /** @property {Number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
3497
3535
  this.emitSize = emitSize
3498
3536
  /** @property {Number} - How long to stay alive (0 is forever) */
3499
3537
  this.emitTime = emitTime;
@@ -3539,14 +3577,17 @@ class ParticleEmitter extends EngineObject
3539
3577
  this.randomness = randomness;
3540
3578
  /** @property {Boolean} - Do particles collide against tiles */
3541
3579
  this.collideTiles = collideTiles;
3542
- /** @property {Number} - Should particles use addtive blend */
3580
+ /** @property {Boolean} - Should particles use addtive blend */
3543
3581
  this.additive = additive;
3544
3582
  /** @property {Boolean} - Should it be in local space of emitter */
3545
- this.localSpace = localSpace;
3546
- /** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
3583
+ this.localSpace = localSpace;
3584
+ /** @property {Number} - If non zero the partile is drawn as a trail, stretched in the drection of velocity */
3547
3585
  this.trailScale = 0;
3548
-
3549
- // internal variables
3586
+ /** @property {Function} - Callback when particle is destroyed */
3587
+ this.particleDestroyCallback = undefined;
3588
+ /** @property {Function} - Callback when particle is created */
3589
+ this.particleCreateCallback = undefined;
3590
+ /** @property {Number} - Track particle emit time */
3550
3591
  this.emitTimeBuffer = 0;
3551
3592
  }
3552
3593
 
@@ -3578,18 +3619,16 @@ class ParticleEmitter extends EngineObject
3578
3619
  emitParticle()
3579
3620
  {
3580
3621
  // spawn a particle
3581
- let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
3582
- vec2(rand(-.5,.5), rand(-.5,.5))
3583
- .multiply(this.emitSize).rotate(this.angle) // box emitter
3584
- : randInCircle(this.emitSize/2); // circle emitter
3622
+ let pos = typeof this.emitSize === 'number' ? // check if number was used
3623
+ randInCircle(this.emitSize/2) // circle emitter
3624
+ : vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
3625
+ .multiply(this.emitSize).rotate(this.angle)
3585
3626
  let angle = rand(this.particleConeAngle, -this.particleConeAngle);
3586
3627
  if (!this.localSpace)
3587
3628
  {
3588
3629
  pos = this.pos.add(pos);
3589
3630
  angle += this.angle;
3590
3631
  }
3591
-
3592
- const particle = new Particle(pos, this.tileInfo, this.tileSize, angle);
3593
3632
 
3594
3633
  // randomness scales each paremeter by a percentage
3595
3634
  const randomness = this.randomness;
@@ -3605,30 +3644,21 @@ class ParticleEmitter extends EngineObject
3605
3644
  const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
3606
3645
  const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
3607
3646
  const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
3608
-
3609
- // build particle settings
3610
- particle.colorStart = colorStart;
3611
- particle.colorEndDelta = colorEnd.subtract(colorStart);
3612
- particle.velocity = vec2().setAngle(velocityAngle, speed);
3613
- particle.angleVelocity = angleSpeed;
3614
- particle.lifeTime = particleTime;
3615
- particle.sizeStart = sizeStart;
3616
- particle.sizeEndDelta = sizeEnd - sizeStart;
3617
- particle.fadeRate = this.fadeRate;
3618
- particle.damping = this.damping;
3619
- particle.angleDamping = this.angleDamping;
3620
- particle.elasticity = this.elasticity;
3621
- particle.friction = this.friction;
3622
- particle.gravityScale = this.gravityScale;
3623
- particle.collideTiles = this.collideTiles;
3624
- particle.additive = this.additive;
3625
- particle.renderOrder = this.renderOrder;
3626
- particle.trailScale = this.trailScale;
3627
- particle.mirror = randInt(2);
3628
- particle.localSpaceEmitter = this.localSpace && this;
3629
-
3630
- // setup callbacks for particles
3631
- particle.destroyCallback = this.particleDestroyCallback;
3647
+
3648
+ // build particle
3649
+ const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
3650
+ particle.velocity = vec2().setAngle(velocityAngle, speed);
3651
+ particle.fadeRate = this.fadeRate;
3652
+ particle.damping = this.damping;
3653
+ particle.angleDamping = this.angleDamping;
3654
+ particle.elasticity = this.elasticity;
3655
+ particle.friction = this.friction;
3656
+ particle.gravityScale = this.gravityScale;
3657
+ particle.collideTiles = this.collideTiles;
3658
+ particle.renderOrder = this.renderOrder;
3659
+ particle.mirror = !!randInt(2);
3660
+
3661
+ // call particle create callaback
3632
3662
  this.particleCreateCallback && this.particleCreateCallback(particle);
3633
3663
 
3634
3664
  // return the newly created particle
@@ -3647,13 +3677,47 @@ class ParticleEmitter extends EngineObject
3647
3677
  class Particle extends EngineObject
3648
3678
  {
3649
3679
  /**
3650
- * Create a particle with the given settings
3651
- * @param {Vector2} position - World space position of the particle
3652
- * @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
3653
- * @param {Number} [angle=0] - Angle to rotate the particle
3680
+ * Create a particle with the given shis.colorStart = undefined;ettings
3681
+ * @param {Vector2} position - World space position of the particle
3682
+ * @param {TileInfo} [tileInfo] - Tile info to render particles
3683
+ * @param {Number} [angle] - Angle to rotate the particle
3684
+ * @param {Color} [colorStart] - Color at start of life
3685
+ * @param {Color} [colorEnd] - Color at end of life
3686
+ * @param {Number} [lifeTime] - How long to live for
3687
+ * @param {Number} [sizeStart] - Angle to rotate the particle
3688
+ * @param {Number} [sizeEnd] - Angle to rotate the particle
3689
+ * @param {Number} [fadeRate] - Angle to rotate the particle
3690
+ * @param {Boolean} [additive] - Angle to rotate the particle
3691
+ * @param {Number} [trailScale] - If a trail, how long to make it
3692
+ * @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
3693
+ * @param {Function} [destroyCallback] - Called when particle dies
3654
3694
  */
3655
- constructor(pos, tileInfo, angle)
3656
- { super(pos, vec2(), tileInfo, angle); }
3695
+ constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
3696
+ )
3697
+ {
3698
+ super(position, vec2(), tileInfo, angle);
3699
+
3700
+ /** @property {Color} - Color at start of life */
3701
+ this.colorStart = colorStart;
3702
+ /** @property {Color} - Calculated change in color */
3703
+ this.colorEndDelta = colorEnd.subtract(colorStart);
3704
+ /** @property {Number} - How long to live for */
3705
+ this.lifeTime = lifeTime;
3706
+ /** @property {Number} - Size at start of life */
3707
+ this.sizeStart = sizeStart;
3708
+ /** @property {Number} - Calculated change in size */
3709
+ this.sizeEndDelta = sizeEnd - sizeStart;
3710
+ /** @property {Number} - How quick to fade in/out */
3711
+ this.fadeRate = fadeRate;
3712
+ /** @property {Boolean} - Is it additive */
3713
+ this.additive = additive;
3714
+ /** @property {Number} - If a trail, how long to make it */
3715
+ this.trailScale = trailScale;
3716
+ /** @property {ParticleEmitter} - Parent emitter if local space */
3717
+ this.localSpaceEmitter = localSpaceEmitter;
3718
+ /** @property {Function} - Called when particle dies */
3719
+ this.destroyCallback = destroyCallback;
3720
+ }
3657
3721
 
3658
3722
  /** Render the particle, automatically called each frame, sorted by renderOrder */
3659
3723
  render()
@@ -3671,7 +3735,7 @@ class Particle extends EngineObject
3671
3735
  (p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
3672
3736
 
3673
3737
  // draw the particle
3674
- this.additive && setBlendMode(1);
3738
+ this.additive && setBlendMode(true);
3675
3739
 
3676
3740
  let pos = this.pos, angle = this.angle;
3677
3741
  if (this.localSpaceEmitter)
@@ -3761,7 +3825,7 @@ class Medal
3761
3825
  * @param {Number} id - The unique identifier of the medal
3762
3826
  * @param {String} name - Name of the medal
3763
3827
  * @param {String} [description] - Description of the medal
3764
- * @param {String} [icon='🏆'] - Icon for the medal
3828
+ * @param {String} [icon] - Icon for the medal
3765
3829
  * @param {String} [src] - Image location for the medal
3766
3830
  */
3767
3831
  constructor(id, name, description='', icon='🏆', src)
@@ -3784,14 +3848,14 @@ class Medal
3784
3848
  return;
3785
3849
 
3786
3850
  // save the medal
3787
- ASSERT(medalsSaveName); // save name must be set
3851
+ ASSERT(medalsSaveName, 'save name must be set');
3788
3852
  localStorage[this.storageKey()] = this.unlocked = 1;
3789
3853
  medalsDisplayQueue.push(this);
3790
3854
  newgrounds && newgrounds.unlockMedal(this.id);
3791
3855
  }
3792
3856
 
3793
3857
  /** Render a medal
3794
- * @param {Number} [hidePercent=0] - How much to slide the medal off screen
3858
+ * @param {Number} [hidePercent] - How much to slide the medal off screen
3795
3859
  */
3796
3860
  render(hidePercent=0)
3797
3861
  {
@@ -3803,25 +3867,25 @@ class Medal
3803
3867
  // draw containing rect and clip to that region
3804
3868
  context.save();
3805
3869
  context.beginPath();
3806
- context.fillStyle = new Color(.9,.9,.9);
3807
- context.strokeStyle = new Color(0,0,0);
3870
+ context.fillStyle = rgb(.9,.9,.9).toString();
3871
+ context.strokeStyle = rgb(0,0,0).toString();
3808
3872
  context.lineWidth = 3;
3809
- context.fill(context.rect(x, y, width, medalDisplaySize.y));
3873
+ context.rect(x, y, width, medalDisplaySize.y);
3874
+ context.fill();
3810
3875
  context.stroke();
3811
3876
  context.clip();
3812
3877
 
3813
3878
  // draw the icon and text
3814
3879
  this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplaySize.y/2));
3815
3880
  const pos = vec2(x+medalDisplayIconSize+30, y+28);
3816
- drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, 0, 'left');
3881
+ drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, undefined, 'left');
3817
3882
  pos.y += 32;
3818
- drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, 0, 'left');
3883
+ drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, undefined, 'left');
3819
3884
  context.restore();
3820
3885
  }
3821
3886
 
3822
3887
  /** Render the icon for a medal
3823
- * @param {Number} x - Screen space X position
3824
- * @param {Number} y - Screen space Y position
3888
+ * @param {Vector2} pos - Screen space position
3825
3889
  * @param {Number} [size=medalDisplayIconSize] - Screen space size
3826
3890
  */
3827
3891
  renderIcon(pos, size=medalDisplayIconSize)
@@ -3849,7 +3913,10 @@ function medalsRender()
3849
3913
  if (!medalsDisplayTimeLast)
3850
3914
  medalsDisplayTimeLast = timeReal;
3851
3915
  else if (time > medalDisplayTime)
3852
- medalsDisplayQueue.shift(medalsDisplayTimeLast = 0);
3916
+ {
3917
+ medalsDisplayTimeLast = 0;
3918
+ medalsDisplayQueue.shift();
3919
+ }
3853
3920
  else
3854
3921
  {
3855
3922
  // slide on/off medals
@@ -3889,8 +3956,8 @@ class Newgrounds
3889
3956
  * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
3890
3957
  constructor(app_id, cipher, cryptoJS)
3891
3958
  {
3892
- ASSERT(!newgrounds && app_id); // can only be one newgrounds object
3893
- ASSERT(!cipher || cryptoJS); // must provide cryptojs if there is a cipher
3959
+ ASSERT(!newgrounds && app_id>0, 'there can only be one newgrounds object');
3960
+ ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
3894
3961
 
3895
3962
  this.app_id = app_id;
3896
3963
  this.cipher = cipher;
@@ -3933,39 +4000,39 @@ class Newgrounds
3933
4000
  debugMedals && console.log(this.scoreboards);
3934
4001
 
3935
4002
  const keepAliveMS = 5 * 60 * 1e3;
3936
- setInterval(()=>this.call('Gateway.ping', 0, 1), keepAliveMS);
4003
+ setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
3937
4004
  }
3938
4005
 
3939
4006
  /** Send message to unlock a medal by id
3940
4007
  * @param {Number} id - The medal id */
3941
- unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
4008
+ unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
3942
4009
 
3943
4010
  /** Send message to post score
3944
4011
  * @param {Number} id - The scoreboard id
3945
4012
  * @param {Number} value - The score value */
3946
- postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
4013
+ postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
3947
4014
 
3948
4015
  /** Get scores from a scoreboard
3949
- * @param {Number} id - The scoreboard id
3950
- * @param {String} [user=0] - A user's id or name
3951
- * @param {Number} [social=0] - If true, only social scores will be loaded
3952
- * @param {Number} [skip=0] - Number of scores to skip before start
3953
- * @param {Number} [limit=10] - Number of scores to include in the list
3954
- * @return {Object} - The response JSON object
4016
+ * @param {Number} id - The scoreboard id
4017
+ * @param {String} [user] - A user's id or name
4018
+ * @param {Number} [social] - If true, only social scores will be loaded
4019
+ * @param {Number} [skip] - Number of scores to skip before start
4020
+ * @param {Number} [limit] - Number of scores to include in the list
4021
+ * @return {Object} - The response JSON object
3955
4022
  */
3956
- getScores(id, user=0, social=0, skip=0, limit=10)
4023
+ getScores(id, user, social=0, skip=0, limit=10)
3957
4024
  { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
3958
4025
 
3959
4026
  /** Send message to log a view */
3960
- logView() { return this.call('App.logView', {'host':this.host}, 1); }
4027
+ logView() { return this.call('App.logView', {'host':this.host}, true); }
3961
4028
 
3962
4029
  /** Send a message to call a component of the Newgrounds API
3963
- * @param {String} component - Name of the component
3964
- * @param {Object} [parameters=0] - Parameters to use for call
3965
- * @param {Boolean} [async=0] - If true, don't wait for response before continuing (avoid stall)
3966
- * @return {Object} - The response JSON object
4030
+ * @param {String} component - Name of the component
4031
+ * @param {Object} [parameters] - Parameters to use for call
4032
+ * @param {Boolean} [async] - If true, don't wait for response before continuing
4033
+ * @return {Object} - The response JSON object
3967
4034
  */
3968
- call(component, parameters=0, async=0)
4035
+ call(component, parameters, async=false)
3969
4036
  {
3970
4037
  const call = {'component':component, 'parameters':parameters};
3971
4038
  if (this.cipher)
@@ -4000,7 +4067,7 @@ class Newgrounds
4000
4067
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
4001
4068
  }
4002
4069
  }
4003
- /**
4070
+ /**
4004
4071
  * LittleJS WebGL Interface
4005
4072
  * - All webgl used by the engine is wrapped up here
4006
4073
  * - For normal stuff you won't need to see or call anything in this file
@@ -4019,13 +4086,13 @@ class Newgrounds
4019
4086
  * @memberof WebGL */
4020
4087
  let glCanvas;
4021
4088
 
4022
- /** 2d context for glCanvas
4023
- * @type {WebGLRenderingContext}
4089
+ /** 2d context for glCanvas
4090
+ * @type {WebGL2RenderingContext}
4024
4091
  * @memberof WebGL */
4025
4092
  let glContext;
4026
4093
 
4027
4094
  // WebGL internal variables not exposed to documentation
4028
- let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
4095
+ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
4029
4096
 
4030
4097
  ///////////////////////////////////////////////////////////////////////////////
4031
4098
 
@@ -4041,73 +4108,89 @@ function glInit()
4041
4108
 
4042
4109
  // setup vertex and fragment shaders
4043
4110
  glShader = glCreateProgram(
4044
- '#version 300 es\n' + // specify GLSL ES version
4045
- 'precision highp float;'+ // use highp for better accuracy
4046
- 'uniform mat4 m;'+ // transform matrix
4047
- 'in vec4 p,c,a;'+ // position, uv, color, additiveColor
4048
- 'out vec4 v,d,e;'+ // return uv, color, additiveColor
4049
- 'void main(){'+ // shader entry point
4050
- 'gl_Position=m*vec4(p.xy,1,1);'+ // transform position
4051
- 'v=p;d=c;e=a;'+ // pass stuff to fragment shader
4052
- '}' // end of shader
4111
+ '#version 300 es\n' + // specify GLSL ES version
4112
+ 'precision highp float;'+ // use highp for better accuracy
4113
+ 'uniform mat4 m;'+ // transform matrix
4114
+ 'in vec2 g;'+ // geometry
4115
+ 'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
4116
+ 'in float r;'+ // rotation
4117
+ 'out vec2 v;'+ // return uv, color, additiveColor
4118
+ 'out vec4 d,e;'+ // return uv, color, additiveColor
4119
+ 'void main(){'+ // shader entry point
4120
+ 'vec2 s=(g-.5)*p.zw;'+ // get size offset
4121
+ 'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
4122
+ 'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
4123
+ 'd=c;e=a;'+ // pass colors to fragment shader
4124
+ '}' // end of shader
4053
4125
  ,
4054
- '#version 300 es\n' + // specify GLSL ES version
4055
- 'precision highp float;'+ // use highp for better accuracy
4056
- 'in vec4 v,d,e;'+ // position, uv, color, additiveColor
4057
- 'uniform sampler2D s;'+ // texture
4058
- 'out vec4 c;'+ // out color
4059
- 'void main(){'+ // shader entry point
4060
- 'c=texture(s,v.zw)*d+e;'+ // modulate texture by color plus additive
4061
- '}' // end of shader
4126
+ '#version 300 es\n' + // specify GLSL ES version
4127
+ 'precision highp float;'+ // use highp for better accuracy
4128
+ 'in vec2 v;'+ // uv
4129
+ 'in vec4 d,e;'+ // color, additiveColor
4130
+ 'uniform sampler2D s;'+ // texture
4131
+ 'out vec4 c;'+ // out color
4132
+ 'void main(){'+ // shader entry point
4133
+ 'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
4134
+ '}' // end of shader
4062
4135
  );
4063
4136
 
4064
4137
  // init buffers
4065
- glVertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
4066
- glPositionData = new Float32Array(glVertexData);
4067
- glColorData = new Uint32Array(glVertexData);
4138
+ const glInstanceData = new ArrayBuffer(gl_INSTANCE_BUFFER_SIZE);
4139
+ glPositionData = new Float32Array(glInstanceData);
4140
+ glColorData = new Uint32Array(glInstanceData);
4068
4141
  glArrayBuffer = glContext.createBuffer();
4069
- glBatchCount = 0;
4142
+ glGeometryBuffer = glContext.createBuffer();
4143
+
4144
+ // create the geometry buffer, triangle strip square
4145
+ const geometry = new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);
4146
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4147
+ glContext.bufferData(gl_ARRAY_BUFFER, geometry, gl_STATIC_DRAW);
4070
4148
  }
4071
4149
 
4072
4150
  // Setup render each frame, called automatically by engine
4073
4151
  function glPreRender()
4074
4152
  {
4075
4153
  // clear and set to same size as main canvas
4076
- glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4154
+ glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
4077
4155
  glContext.clear(gl_COLOR_BUFFER_BIT);
4078
4156
 
4079
4157
  // set up the shader
4080
4158
  glContext.useProgram(glShader);
4081
4159
  glContext.activeTexture(gl_TEXTURE0);
4082
4160
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
4083
- glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4084
- glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4085
- glAdditive = 0;
4086
-
4161
+
4087
4162
  // set vertex attributes
4088
- let offset = 0;
4089
- const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
4163
+ let offset = glAdditive = glBatchAdditive = 0;
4164
+ let initVertexAttribArray = (name, type, typeSize, size)=>
4090
4165
  {
4091
4166
  const location = glContext.getAttribLocation(glShader, name);
4167
+ const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
4168
+ const divisor = typeSize && 1; // only if not geometry
4169
+ const normalize = typeSize==1; // only if color
4092
4170
  glContext.enableVertexAttribArray(location);
4093
- glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
4171
+ glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
4172
+ glContext.vertexAttribDivisor(location, divisor);
4094
4173
  offset += size*typeSize;
4095
4174
  }
4096
- initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & texture
4097
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
4098
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
4175
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4176
+ initVertexAttribArray('g', gl_FLOAT, 0, 2); // geometry
4177
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4178
+ glContext.bufferData(gl_ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4179
+ initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & size
4180
+ initVertexAttribArray('u', gl_FLOAT, 4, 4); // texture coords
4181
+ initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4); // color
4182
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4); // additiveColor
4183
+ initVertexAttribArray('r', gl_FLOAT, 4, 1); // rotation
4099
4184
 
4100
4185
  // build the transform matrix
4101
- const sx = 2 * cameraScale / mainCanvas.width;
4102
- const sy = 2 * cameraScale / mainCanvas.height;
4103
- const cx = -1 - sx*cameraPos.x;
4104
- const cy = -1 - sy*cameraPos.y;
4105
- glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
4186
+ const s = vec2(2*cameraScale).divide(mainCanvasSize);
4187
+ const p = vec2(-1).subtract(cameraPos.multiply(s));
4188
+ glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
4106
4189
  new Float32Array([
4107
- sx, 0, 0, 0,
4108
- 0, sy, 0, 0,
4109
- 1, 1, -1, 1,
4110
- cx, cy, 0, 0
4190
+ s.x, 0, 0, 0,
4191
+ 0, s.y, 0, 0,
4192
+ 1, 1, 1, 1,
4193
+ p.x, p.y, 0, 0
4111
4194
  ])
4112
4195
  );
4113
4196
  }
@@ -4128,7 +4211,7 @@ function glSetTexture(texture)
4128
4211
 
4129
4212
  /** Compile WebGL shader of the given type, will throw errors if in debug mode
4130
4213
  * @param {String} source
4131
- * @param type
4214
+ * @param {Number} type
4132
4215
  * @return {WebGLShader}
4133
4216
  * @memberof WebGL */
4134
4217
  function glCompileShader(source, type)
@@ -4145,8 +4228,8 @@ function glCompileShader(source, type)
4145
4228
  }
4146
4229
 
4147
4230
  /** Create WebGL program with given shaders
4148
- * @param {WebGLShader} vsSource
4149
- * @param {WebGLShader} fsSource
4231
+ * @param {String} vsSource
4232
+ * @param {String} fsSource
4150
4233
  * @return {WebGLProgram}
4151
4234
  * @memberof WebGL */
4152
4235
  function glCreateProgram(vsSource, fsSource)
@@ -4164,7 +4247,7 @@ function glCreateProgram(vsSource, fsSource)
4164
4247
  }
4165
4248
 
4166
4249
  /** Create WebGL texture from an image and init the texture settings
4167
- * @param {Image} image
4250
+ * @param {HTMLImageElement} image
4168
4251
  * @return {WebGLTexture}
4169
4252
  * @memberof WebGL */
4170
4253
  function glCreateTexture(image)
@@ -4174,7 +4257,7 @@ function glCreateTexture(image)
4174
4257
  glContext.bindTexture(gl_TEXTURE_2D, texture);
4175
4258
  if (image)
4176
4259
  glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
4177
-
4260
+
4178
4261
  // use point filtering for pixelated rendering
4179
4262
  const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
4180
4263
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
@@ -4189,29 +4272,31 @@ function glCreateTexture(image)
4189
4272
  * @memberof WebGL */
4190
4273
  function glFlush()
4191
4274
  {
4192
- if (!glBatchCount) return;
4275
+ if (!glInstanceCount) return;
4193
4276
 
4194
4277
  const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
4195
4278
  glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
4196
4279
  glContext.enable(gl_BLEND);
4197
4280
 
4198
4281
  // draw all the sprites in the batch and reset the buffer
4199
- glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glVertexData);
4200
- glContext.drawArrays(gl_TRIANGLE_STRIP, 0, glBatchCount);
4201
- glBatchCount = 0;
4282
+ glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glPositionData);
4283
+ glContext.drawArraysInstanced(gl_TRIANGLE_STRIP, 0, 4, glInstanceCount);
4284
+ if (showWatermark)
4285
+ drawCount += glInstanceCount;
4286
+ glInstanceCount = 0;
4202
4287
  glBatchAdditive = glAdditive;
4203
4288
  }
4204
4289
 
4205
4290
  /** Draw any sprites still in the buffer, copy to main canvas and clear
4206
4291
  * @param {CanvasRenderingContext2D} context
4207
- * @param {Boolean} [forceDraw=0]
4292
+ * @param {Boolean} [forceDraw]
4208
4293
  * @memberof WebGL */
4209
- function glCopyToContext(context, forceDraw)
4294
+ function glCopyToContext(context, forceDraw=false)
4210
4295
  {
4211
- if (!glBatchCount && !forceDraw) return;
4212
-
4296
+ if (!glInstanceCount && !forceDraw) return;
4297
+
4213
4298
  glFlush();
4214
-
4299
+
4215
4300
  // do not draw in overlay mode because the canvas is visible
4216
4301
  if (!glOverlay || forceDraw)
4217
4302
  context.drawImage(glCanvas, 0, 0);
@@ -4233,60 +4318,22 @@ function glCopyToContext(context, forceDraw)
4233
4318
  function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
4234
4319
  {
4235
4320
  // flush if there is not enough room or if different blend mode
4236
- const vertCount = 6;
4237
- if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
4321
+ if (glInstanceCount >= gl_MAX_INSTANCES-1 || glBatchAdditive != glAdditive)
4238
4322
  glFlush();
4239
4323
 
4240
- // prepare to create the verts from size and angle
4241
- const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
4242
- const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
4243
- const positionData =
4244
- [
4245
- x-cx+sy, y+cy+sx, uv0X, uv0Y,
4246
- x-cx-sy, y-cy+sx, uv0X, uv1Y,
4247
- x+cx+sy, y+cy-sx, uv1X, uv0Y,
4248
- x+cx-sy, y-cy-sx, uv1X, uv1Y,
4249
- ];
4250
-
4251
- // setup 2 triangle strip quad
4252
- for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
4253
- {
4254
- const j = clamp(i-1, 0, 3)*4; // degenerate tri at ends
4255
- glPositionData[offset++] = positionData[j+0];
4256
- glPositionData[offset++] = positionData[j+1];
4257
- glPositionData[offset++] = positionData[j+2];
4258
- glPositionData[offset++] = positionData[j+3];
4259
- glColorData[offset++] = rgba;
4260
- glColorData[offset++] = rgbaAdditive;
4261
- }
4262
- glBatchCount += vertCount;
4263
- }
4264
-
4265
- /** Add a convex polygon to the gl draw list
4266
- * @param {Array} points - Array of Vector2 points
4267
- * @param {Number} rgba - Color of the polygon
4268
- * @memberof WebGL */
4269
- function glDrawPoints(points, rgba)
4270
- {
4271
- // flush if there is not enough room or if different blend mode
4272
- const vertCount = points.length + 2;
4273
- if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
4274
- glFlush();
4275
-
4276
- // setup triangle strip from list of points
4277
- for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
4278
- {
4279
- const j = clamp(i-1, 0, vertCount-3); // degenerate tri at ends
4280
- const h = j>>1;
4281
- const point = points[j%2? h : vertCount-3-h];
4282
- glPositionData[offset++] = point.x;
4283
- glPositionData[offset++] = point.y;
4284
- glPositionData[offset++] = 0; // uvx
4285
- glPositionData[offset++] = 0; // uvy
4286
- glColorData[offset++] = 0; // nothing to tint
4287
- glColorData[offset++] = rgba; // apply rgba via additive
4288
- }
4289
- glBatchCount += vertCount;
4324
+ let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
4325
+ glPositionData[offset++] = x;
4326
+ glPositionData[offset++] = y;
4327
+ glPositionData[offset++] = sizeX;
4328
+ glPositionData[offset++] = sizeY;
4329
+ glPositionData[offset++] = uv0X;
4330
+ glPositionData[offset++] = uv0Y;
4331
+ glPositionData[offset++] = uv1X;
4332
+ glPositionData[offset++] = uv1Y;
4333
+ glColorData[offset++] = rgba;
4334
+ glColorData[offset++] = rgbaAdditive;
4335
+ glPositionData[offset++] = angle;
4336
+ glInstanceCount++;
4290
4337
  }
4291
4338
 
4292
4339
  ///////////////////////////////////////////////////////////////////////////////
@@ -4300,7 +4347,7 @@ let glPostShader, glPostArrayBuffer, glPostTexture, glPostIncludeOverlay;
4300
4347
  * @memberof WebGL */
4301
4348
  function glInitPostProcess(shaderCode, includeOverlay)
4302
4349
  {
4303
- ASSERT(!glPostShader); // can only have 1 post effects shader
4350
+ ASSERT(!glPostShader, 'can only have 1 post effects shader');
4304
4351
 
4305
4352
  if (!shaderCode) // default shader pass through
4306
4353
  shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
@@ -4311,14 +4358,14 @@ function glInitPostProcess(shaderCode, includeOverlay)
4311
4358
  'precision highp float;'+ // use highp for better accuracy
4312
4359
  'in vec2 p;'+ // position
4313
4360
  'void main(){'+ // shader entry point
4314
- 'gl_Position=vec4(p,1,1);'+ // set position
4361
+ 'gl_Position=vec4(p+p-1.,1,1);'+ // set position
4315
4362
  '}' // end of shader
4316
4363
  ,
4317
4364
  '#version 300 es\n' + // specify GLSL ES version
4318
4365
  'precision highp float;'+ // use highp for better accuracy
4319
4366
  'uniform sampler2D iChannel0;'+ // input texture
4320
4367
  'uniform vec3 iResolution;'+ // size of output texture
4321
- 'uniform float iTime;'+ // time passed
4368
+ 'uniform float iTime;'+ // time
4322
4369
  'out vec4 c;'+ // out color
4323
4370
  '\n' + shaderCode + '\n'+ // insert custom shader code
4324
4371
  'void main(){'+ // shader entry point
@@ -4329,7 +4376,7 @@ function glInitPostProcess(shaderCode, includeOverlay)
4329
4376
 
4330
4377
  // create buffer and texture
4331
4378
  glPostArrayBuffer = glContext.createBuffer();
4332
- glPostTexture = glCreateTexture();
4379
+ glPostTexture = glCreateTexture(undefined);
4333
4380
  glPostIncludeOverlay = includeOverlay;
4334
4381
 
4335
4382
  // hide the original 2d canvas
@@ -4365,10 +4412,9 @@ function glRenderPostProcess()
4365
4412
 
4366
4413
  // setup shader program to draw one triangle
4367
4414
  glContext.useProgram(glPostShader);
4415
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4416
+ glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
4368
4417
  glContext.disable(gl_BLEND);
4369
- glContext.bindBuffer(gl_ARRAY_BUFFER, glPostArrayBuffer);
4370
- glContext.bufferData(gl_ARRAY_BUFFER, new Float32Array([-3,1,1,-3,1,1]), gl_STATIC_DRAW);
4371
- glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, true);
4372
4418
 
4373
4419
  // set textures, pass in the 2d canvas and gl canvas in separate texture channels
4374
4420
  glContext.activeTexture(gl_TEXTURE0);
@@ -4379,19 +4425,19 @@ function glRenderPostProcess()
4379
4425
  const vertexByteStride = 8;
4380
4426
  const pLocation = glContext.getAttribLocation(glPostShader, 'p');
4381
4427
  glContext.enableVertexAttribArray(pLocation);
4382
- glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, 0, vertexByteStride, 0);
4428
+ glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
4383
4429
 
4384
4430
  // set uniforms and draw
4385
4431
  const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
4386
4432
  glContext.uniform1i(uniformLocation('iChannel0'), 0);
4387
4433
  glContext.uniform1f(uniformLocation('iTime'), time);
4388
4434
  glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
4389
- glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 3);
4435
+ glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
4390
4436
  }
4391
4437
 
4392
4438
  ///////////////////////////////////////////////////////////////////////////////
4393
4439
  // store gl constants as integers so their name doesn't use space in minifed
4394
- const
4440
+ const
4395
4441
  gl_ONE = 1,
4396
4442
  gl_TRIANGLE_STRIP = 5,
4397
4443
  gl_SRC_ALPHA = 770,
@@ -4413,17 +4459,17 @@ gl_TEXTURE0 = 33984,
4413
4459
  gl_ARRAY_BUFFER = 34962,
4414
4460
  gl_STATIC_DRAW = 35044,
4415
4461
  gl_DYNAMIC_DRAW = 35048,
4416
- gl_FRAGMENT_SHADER = 35632,
4462
+ gl_FRAGMENT_SHADER = 35632,
4417
4463
  gl_VERTEX_SHADER = 35633,
4418
4464
  gl_COMPILE_STATUS = 35713,
4419
4465
  gl_LINK_STATUS = 35714,
4420
4466
  gl_UNPACK_FLIP_Y_WEBGL = 37440,
4421
4467
 
4422
4468
  // constants for batch rendering
4423
- gl_INDICIES_PER_VERT = 6,
4424
- gl_MAX_BATCH = 1e5,
4425
- gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
4426
- gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTEX_BYTE_STRIDE;
4469
+ gl_INDICIES_PER_INSTANCE = 11,
4470
+ gl_MAX_INSTANCES = 1e4,
4471
+ gl_INSTANCE_BYTE_STRIDE = gl_INDICIES_PER_INSTANCE * 4, // 11 * 4
4472
+ gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
4427
4473
  /**
4428
4474
  * LittleJS - The Tiny JavaScript Game Engine That Can!
4429
4475
  * MIT License - Copyright 2021 Frank Force
@@ -4456,7 +4502,7 @@ const engineName = 'LittleJS';
4456
4502
  * @type {String}
4457
4503
  * @default
4458
4504
  * @memberof Engine */
4459
- const engineVersion = '1.8.8';
4505
+ const engineVersion = '1.9.0';
4460
4506
 
4461
4507
  /** Frames per second to update objects
4462
4508
  * @type {Number}
@@ -4497,14 +4543,14 @@ let timeReal = 0;
4497
4543
 
4498
4544
  /** Is the game paused? Causes time and objects to not be updated
4499
4545
  * @type {Boolean}
4500
- * @default 0
4546
+ * @default false
4501
4547
  * @memberof Engine */
4502
- let paused = 0;
4548
+ let paused = false;
4503
4549
 
4504
4550
  /** Set if game is paused
4505
- * @param {Boolean} paused
4551
+ * @param {Boolean} isPaused
4506
4552
  * @memberof Engine */
4507
- function setPaused(_paused) { paused = _paused; }
4553
+ function setPaused(isPaused) { paused = isPaused; }
4508
4554
 
4509
4555
  // Frame time tracking
4510
4556
  let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
@@ -4521,7 +4567,7 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4521
4567
  * @memberof Engine */
4522
4568
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
4523
4569
  {
4524
- ASSERT(Array.isArray(imageSources)); // pass in images as array
4570
+ ASSERT(Array.isArray(imageSources), 'pass in images as array');
4525
4571
 
4526
4572
  // internal update loop for engine
4527
4573
  function engineUpdate(frameTimeMS=0)
@@ -4531,12 +4577,12 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4531
4577
  frameTimeLastMS = frameTimeMS;
4532
4578
  if (debug || showWatermark)
4533
4579
  averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
4534
- const debugSpeedUp = debug && keyIsDown(107); // +
4535
- const debugSpeedDown = debug && keyIsDown(109); // -
4580
+ const debugSpeedUp = debug && keyIsDown('Equal'); // +
4581
+ const debugSpeedDown = debug && keyIsDown('Minus'); // -
4536
4582
  if (debug) // +/- to speed/slow time
4537
4583
  frameTimeDeltaMS *= debugSpeedUp ? 5 : debugSpeedDown ? .2 : 1;
4538
4584
  timeReal += frameTimeDeltaMS / 1e3;
4539
- frameTimeBufferMS += !paused * frameTimeDeltaMS;
4585
+ frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
4540
4586
  if (!debugSpeedUp)
4541
4587
  frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp incase of slow framerate
4542
4588
 
@@ -4646,7 +4692,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4646
4692
  'user-select:none;' + // prevent mobile hold to select
4647
4693
  '-webkit-user-select:none;' + // compatibility for ios
4648
4694
  '-webkit-touch-callout:none'; // compatibility for ios
4649
- document.body.style = styleBody;
4695
+ document.body.style.cssText = styleBody;
4650
4696
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
4651
4697
  mainContext = mainCanvas.getContext('2d');
4652
4698
 
@@ -4659,10 +4705,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4659
4705
  overlayContext = overlayCanvas.getContext('2d');
4660
4706
 
4661
4707
  // set canvas style
4662
- const styleCanvas =
4663
- 'position:absolute;' + // position
4708
+ const styleCanvas = 'position:absolute;' + // position
4664
4709
  'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
4665
- (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
4710
+ (glCanvas||mainCanvas).style.cssText = mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
4666
4711
 
4667
4712
  // create promises for loading images
4668
4713
  const promises = imageSources.map((src, textureIndex)=>
@@ -4682,13 +4727,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4682
4727
  showSplashScreen && promises.push(new Promise(resolve =>
4683
4728
  {
4684
4729
  let t = 0;
4685
- console.log(`LittleJS Engine v${engineVersion}`);
4730
+ console.log(`${engineName} Engine v${engineVersion}`);
4686
4731
  updateSplash();
4687
4732
  function updateSplash()
4688
4733
  {
4689
4734
  clearInput();
4690
4735
  drawEngineSplashScreen(t+=.01);
4691
- t>1 ? resolve() : setTimeout(updateSplash,16);
4736
+ t>1 ? resolve() : setTimeout(updateSplash, 16);
4692
4737
  }
4693
4738
  }));
4694
4739
 
@@ -4749,7 +4794,7 @@ function engineObjectsDestroy()
4749
4794
 
4750
4795
  /** Triggers a callback for each object within a given area
4751
4796
  * @param {Vector2} [pos] - Center of test area
4752
- * @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
4797
+ * @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
4753
4798
  * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
4754
4799
  * @param {Array} [objects=engineObjects] - List of objects to check
4755
4800
  * @memberof Engine */
@@ -4760,7 +4805,7 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
4760
4805
  for (const o of objects)
4761
4806
  callbackFunction(o);
4762
4807
  }
4763
- else if (size.x != undefined) // bounding box test
4808
+ else if (typeof size === 'object') // bounding box test
4764
4809
  {
4765
4810
  for (const o of objects)
4766
4811
  isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
@@ -4778,21 +4823,23 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
4778
4823
 
4779
4824
  function drawEngineSplashScreen(t)
4780
4825
  {
4781
- // background
4782
- const grayscale = 0;
4783
- const x = mainContext;
4784
- const w = mainCanvas.width = innerWidth;
4785
- const h = mainCanvas.height = innerHeight;
4786
- const p3 = percent(t, 1, .8);
4787
- const p4 = percent(t, 0, .5);
4788
- const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
4789
- g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3));
4790
- g.addColorStop(1,hsl(0,0,0,p3));
4791
- x.save();
4792
- x.fillStyle = g;
4793
- x.fillRect(0,0,w,h);
4794
-
4795
- // logo - fade in and out
4826
+ const x = overlayContext;
4827
+ const w = overlayCanvas.width = innerWidth;
4828
+ const h = overlayCanvas.height = innerHeight;
4829
+
4830
+ {
4831
+ // background
4832
+ const p3 = percent(t, 1, .8);
4833
+ const p4 = percent(t, 0, .5);
4834
+ const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
4835
+ g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3).toString());
4836
+ g.addColorStop(1,hsl(0,0,0,p3).toString());
4837
+ x.save();
4838
+ x.fillStyle = g;
4839
+ x.fillRect(0,0,w,h);
4840
+ }
4841
+
4842
+ // draw LittleJS logo...
4796
4843
  const rect = (X, Y, W, H, C)=>
4797
4844
  {
4798
4845
  x.beginPath();
@@ -4817,7 +4864,7 @@ function drawEngineSplashScreen(t)
4817
4864
  C ? x.fill() : x.stroke();
4818
4865
  };
4819
4866
  const color = (c=0, l=0) =>
4820
- hsl([.98,.3,.57,.14][c%4]-10,grayscale?0:.8,[0,.3,.5,.8,.9][l]);
4867
+ hsl([.98,.3,.57,.14][c%4]-10,.8,[0,.3,.5,.8,.9][l]).toString();
4821
4868
  const alpha = wave(1,1,t);
4822
4869
  const p = percent(alpha, .1, .5);
4823
4870
 
@@ -4827,7 +4874,7 @@ function drawEngineSplashScreen(t)
4827
4874
  x.scale(size,size);
4828
4875
  x.translate(-40,-35);
4829
4876
  x.lineJoin = x.lineCap = 'round';
4830
- x.lineWidth = 1+p;
4877
+ x.lineWidth = .1 + p*1.9;
4831
4878
 
4832
4879
  // drawing effect
4833
4880
  const p2 = percent(alpha,.1,1);
@@ -4852,7 +4899,7 @@ function drawEngineSplashScreen(t)
4852
4899
 
4853
4900
  // little stack
4854
4901
  rect(37,14,9,6,color(3,2));
4855
- rect(37,14,4,6,color(3,3));
4902
+ rect(37,14,4.5,6,color(3,3));
4856
4903
  rect(37,14,9,6);
4857
4904
 
4858
4905
  // big stack
@@ -4860,9 +4907,9 @@ function drawEngineSplashScreen(t)
4860
4907
  rect(50,20,6,-10,color(0,2));
4861
4908
  rect(50,20,3,-10,color(0,3));
4862
4909
  rect(50,10,10,10);
4863
- circle(55,2,11,.5,PI-.5,color(3,3));
4864
- circle(55,2,11,.5,PI/2,color(3,2),1);
4865
- circle(55,2,11,.5,PI-.5);
4910
+ circle(55,2,11.4,.5,PI-.5,color(3,3));
4911
+ circle(55,2,11.4,.5,PI/2,color(3,2),1);
4912
+ circle(55,2,11.4,.5,PI-.5);
4866
4913
  rect(45,7,20,-7,color(0,2));
4867
4914
  rect(45,0,20,3,color(0,3));
4868
4915
  rect(45,0,20,7);
@@ -4896,12 +4943,14 @@ function drawEngineSplashScreen(t)
4896
4943
  x.lineTo(53+(1+i*2.9)*p,40);
4897
4944
  x.lineTo(53+(4+i*3.5)*p,54);
4898
4945
  x.fillStyle = color(0,i%2+2);
4899
- x.fill() || i%2 && x.stroke();
4946
+ x.fill();
4947
+ i%2 && x.stroke();
4900
4948
  }
4901
4949
 
4902
4950
  // wheels
4903
- rect(5,40,9,6,color());
4904
- rect(15,54,38,-14,color())
4951
+ rect(6,40,5,5);
4952
+ rect(6,40,5,5,color());
4953
+ rect(15,54,38,-14,color());
4905
4954
  for (let i=3; i--;)
4906
4955
  for (let j=2; j--;)
4907
4956
  {
@@ -4910,26 +4959,26 @@ function drawEngineSplashScreen(t)
4910
4959
  circle(15*i+15,47,j?7:1,0,PI,color(i,2));
4911
4960
  x.stroke();
4912
4961
  }
4913
- line(6,40,68,40) // center
4914
- line(77,54,4,54) // bottom
4962
+ line(6,40,68,40); // center
4963
+ line(77,54,4,54); // bottom
4915
4964
 
4916
- // text
4965
+ // draw engine name
4917
4966
  const s = engineName;
4918
4967
  x.font = '900 16px arial';
4919
4968
  x.textAlign = 'center';
4920
4969
  x.textBaseline = 'top';
4921
- x.lineWidth = 1+p*3
4970
+ x.lineWidth = .1+p*3.9;
4922
4971
  let w2 = 0;
4923
4972
  for (let i=0; i<s.length; ++i)
4924
4973
  w2 += x.measureText(s[i]).width;
4925
4974
  for (let j=2; j--;)
4926
4975
  for (let i=0, X=41-w2/2; i<s.length; ++i)
4927
4976
  {
4928
- x.fillStyle = color(i,grayscale?3:2);
4977
+ x.fillStyle = color(i,2);
4929
4978
  const w = x.measureText(s[i]).width;
4930
4979
  x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
4931
4980
  X += w;
4932
4981
  }
4933
-
4982
+
4934
4983
  x.restore();
4935
4984
  }