littlejsengine 1.0.14 → 1.1.8

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 (99) hide show
  1. package/build.bat +5 -1
  2. package/docs/Audio.html +2268 -0
  3. package/docs/Color.html +2989 -0
  4. package/docs/Debug.html +2933 -0
  5. package/docs/Draw.html +4092 -0
  6. package/docs/EngineObject.html +4458 -0
  7. package/docs/Input.html +2482 -0
  8. package/docs/Medal.html +1007 -0
  9. package/docs/Medals.html +628 -0
  10. package/docs/Music.html +584 -0
  11. package/docs/Newgrounds.html +1399 -0
  12. package/docs/Particle.html +4535 -0
  13. package/docs/ParticleEmitter.html +7770 -0
  14. package/docs/Random.html +1774 -0
  15. package/docs/Settings.html +2679 -0
  16. package/docs/Sound.html +1249 -0
  17. package/docs/TileCollision.html +1409 -0
  18. package/docs/TileLayer.html +6948 -0
  19. package/docs/TileLayerData.html +1052 -0
  20. package/docs/Timer.html +1183 -0
  21. package/docs/Utilities.html +3281 -0
  22. package/docs/Vector2.html +4207 -0
  23. package/docs/WebGL.html +2327 -0
  24. package/docs/engine.js.html +429 -0
  25. package/docs/engineAudio.js.html +609 -0
  26. package/docs/engineDebug.js.html +725 -0
  27. package/docs/engineDraw.js.html +419 -0
  28. package/docs/engineInput.js.html +401 -0
  29. package/docs/engineMedals.js.html +461 -0
  30. package/docs/engineObject.js.html +518 -0
  31. package/docs/engineParticles.js.html +441 -0
  32. package/docs/engineSettings.js.html +349 -0
  33. package/docs/engineTileLayer.js.html +513 -0
  34. package/docs/engineUtilities.js.html +647 -0
  35. package/docs/engineWebGL.js.html +522 -0
  36. package/docs/fonts/MavenPro-Regular.ttf +0 -0
  37. package/docs/fonts/Montserrat-Regular.ttf +0 -0
  38. package/docs/fonts/Muli-Black.ttf +0 -0
  39. package/docs/fonts/OFL-hind.txt +93 -0
  40. package/docs/fonts/OFL-montserrat.txt +93 -0
  41. package/docs/global.html +1691 -0
  42. package/docs/index.css +6 -0
  43. package/docs/index.html +247 -0
  44. package/docs/scripts/fix-code-block.js +53 -0
  45. package/docs/scripts/fix-navbar.js +25 -0
  46. package/docs/scripts/linenumber.js +25 -0
  47. package/docs/scripts/misc.js +217 -0
  48. package/docs/scripts/resize.js +85 -0
  49. package/docs/scripts/search.js +83 -0
  50. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  51. package/docs/scripts/third-party/fuse.js +9 -0
  52. package/docs/scripts/third-party/lang-css.js +2 -0
  53. package/docs/scripts/third-party/prettify.js +28 -0
  54. package/docs/static/favicon.png +0 -0
  55. package/docs/static/index.css +4 -0
  56. package/docs/styles/clean-jsdoc-theme-base.css +393 -0
  57. package/docs/styles/clean-jsdoc-theme-dark.css +324 -0
  58. package/docs/styles/clean-jsdoc-theme-light.css +319 -0
  59. package/docs/styles/reset.css +346 -0
  60. package/docs/styles/third-party/ionicons.min.css +11 -0
  61. package/docs/styles/third-party/prettify-jsdoc.css +111 -0
  62. package/docs/styles/third-party/prettify-tomorrow.css +5 -0
  63. package/engine/engine.all.js +1997 -744
  64. package/engine/engine.all.min.js +1 -1
  65. package/engine/engine.all.release.js +1743 -569
  66. package/engine/engine.js +107 -39
  67. package/engine/engineAudio.js +152 -34
  68. package/engine/{build.bat → engineBuild.bat} +10 -10
  69. package/engine/{buildSetup.bat → engineBuildSetup.bat} +0 -0
  70. package/engine/engineDebug.js +105 -32
  71. package/engine/engineDraw.js +137 -32
  72. package/engine/engineInput.js +111 -37
  73. package/engine/{engineMedal.js → engineMedals.js} +129 -56
  74. package/engine/engineObject.js +117 -67
  75. package/engine/engineParticles.js +282 -0
  76. package/engine/engineRelease.js +3 -9
  77. package/engine/engineSettings.js +190 -0
  78. package/engine/engineTileLayer.js +147 -44
  79. package/engine/engineUtilities.js +488 -0
  80. package/engine/engineWebGL.js +113 -74
  81. package/examples/breakout/game.js +1 -1
  82. package/examples/breakout/gameObjects.js +3 -3
  83. package/examples/breakout/index.html +3 -3
  84. package/examples/platformer/game.js +5 -7
  85. package/examples/platformer/gameEffects.js +7 -7
  86. package/examples/platformer/gameLevel.js +2 -2
  87. package/examples/platformer/gameObjects.js +4 -5
  88. package/examples/platformer/gamePlayer.js +3 -3
  89. package/examples/platformer/index.html +6 -6
  90. package/examples/puzzle/game.js +1 -2
  91. package/examples/puzzle/index.html +2 -2
  92. package/examples/stress/index.html +2 -8
  93. package/game.js +5 -11
  94. package/index.html +13 -13
  95. package/package.json +4 -4
  96. package/tiles.png +0 -0
  97. package/engine/engineConfig.js +0 -64
  98. package/engine/engineParticle.js +0 -199
  99. package/engine/engineUtil.js +0 -147
@@ -1,15 +1,9 @@
1
1
  /*
2
- LittleJS - The Tiny JavaScript Game Engine That Can
3
- MIT License - Copyright 2019 Frank Force
4
- */
5
- /*
6
- LittleJS - Build include file
7
- By Frank Force 2021
8
-
9
- This file is used for release builds in place of engineDebug.js
2
+ LittleJS - Release build include file
3
+ - This file is used for release builds in place of engineDebug.js
4
+ - Debug functionality will be disabled to lower size and increase performance
10
5
  */
11
6
 
12
-
13
7
  'use strict';
14
8
 
15
9
  const debug = 0;
@@ -34,104 +28,422 @@ const debugLine = ()=> {}
34
28
  const debugAABB = ()=> {}
35
29
  const debugClear = ()=> {}
36
30
  const debugSaveCanvas = ()=> {}
37
-
38
- /*
39
- LittleJS Utility Classes and Functions
40
- - Vector2 - fast, simple, easy vector class
41
- - Color - holds a rgba color with math functions
42
- - Timer - tracks time automatically
43
- - Small math lib
44
- */
31
+ /**
32
+ * LittleJS Utility Classes and Functions
33
+ * <br> - General purpose math library
34
+ * <br> - Vector2 - fast, simple, easy 2D vector class
35
+ * <br> - Color - holds a rgba color with some math functions
36
+ * <br> - Timer - tracks time automatically
37
+ * @namespace Utilities
38
+ */
45
39
 
46
40
  'use strict';
47
41
 
42
+ /** A shortcut to get Math.PI
43
+ * @const
44
+ * @memberof Utilities */
45
+ const PI = Math.PI;
46
+
47
+ /** True if running a Chromium based browser
48
+ * @const
49
+ * @memberof Utilities */
50
+ const isChrome = window['chrome'];
51
+
52
+ /** Returns absoulte value of value passed in
53
+ * @param {Number} value
54
+ * @return {Number}
55
+ * @memberof Utilities */
56
+ const abs = (a)=> a < 0 ? -a : a;
57
+
58
+ /** Returns the sign of value passed in
59
+ * @param {Number} value
60
+ * @return {Number}
61
+ * @memberof Utilities */
62
+ const sign = (a)=> a < 0 ? -1 : 1;
63
+
64
+ /** Returns lowest of two values passed in
65
+ * @param {Number} valueA
66
+ * @param {Number} valueB
67
+ * @return {Number}
68
+ * @memberof Utilities */
69
+ const min = (a, b)=> a < b ? a : b;
70
+
71
+ /** Returns highest of two values passed in
72
+ * @param {Number} valueA
73
+ * @param {Number} valueB
74
+ * @return {Number}
75
+ * @memberof Utilities */
76
+ const max = (a, b)=> a > b ? a : b;
77
+
78
+ /** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
79
+ * @param {Number} dividend
80
+ * @param {Number} divisor
81
+ * @return {Number}
82
+ * @memberof Utilities */
83
+ const mod = (a, b)=> ((a % b) + b) % b;
84
+
85
+ /** Clamps the value beween max and min
86
+ * @param {Number} value
87
+ * @param {Number} [max=1]
88
+ * @param {Number} [min=0]
89
+ * @return {Number}
90
+ * @memberof Utilities */
91
+ const clamp = (v, max=1, min=0)=> (ASSERT(max > min), v < min ? min : v > max ? max : v);
92
+
93
+ /** Returns what percentage the value is between max and min
94
+ * @param {Number} value
95
+ * @param {Number} [max=1]
96
+ * @param {Number} [min=0]
97
+ * @return {Number}
98
+ * @memberof Utilities */
99
+ const percent = (v, max=1, min=0)=> max-min ? clamp((v-min) / (max-min)) : 0;
100
+
101
+ /** Linearly interpolates the percent value between max and min
102
+ * @param {Number} percent
103
+ * @param {Number} [max=1]
104
+ * @param {Number} [min=0]
105
+ * @return {Number}
106
+ * @memberof Utilities */
107
+ const lerp = (p, max=1, min=0)=> min + clamp(p) * (max-min);
108
+
109
+ /** Formats seconds to mm:ss style for display purposes
110
+ * @param {Number} t - time in seconds
111
+ * @return {String}
112
+ * @memberof Utilities */
113
+ const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
114
+
115
+ /** Returns the nearest power of two not less then the value
116
+ * @param {Number} value
117
+ * @return {Number}
118
+ * @memberof Utilities */
119
+ const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
120
+
121
+ /** Applies smoothstep function to the percentage value
122
+ * @param {Number} value
123
+ * @return {Number}
124
+ * @memberof Utilities */
125
+ const smoothStep = (p)=> p * p * (3 - 2 * p);
126
+
127
+ /** Returns true if two axis aligned bounding boxes are overlapping
128
+ * @param {Vector2} pointA - Center of box A
129
+ * @param {Vector2} sizeA - Size of box A
130
+ * @param {Vector2} pointB - Center of box B
131
+ * @param {Vector2} sizeB - Size of box B
132
+ * @return {Boolean} - True if overlapping
133
+ * @memberof Utilities */
134
+ const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(pA.y - pB.y)*2 < sA.y + sB.y;
135
+
136
+ /** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
137
+ * @param {Number} [frequency=1] - Frequency of the wave in Hz
138
+ * @param {Number} [amplitude=1] - Amplitude (max height) of the wave
139
+ * @param {Number} [t=time] - Value to use for time of the wave
140
+ * @return {Number} - Value waving between 0 and amplitude
141
+ * @memberof Utilities */
142
+ const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*frequency*2*PI));
143
+
48
144
  ///////////////////////////////////////////////////////////////////////////////
49
- // helper functions
50
-
51
- const PI = Math.PI;
52
- const isChrome = window['chrome'];
53
- const abs = (a)=> a < 0 ? -a : a;
54
- const sign = (a)=> a < 0 ? -1 : 1;
55
- const min = (a, b)=> a < b ? a : b;
56
- const max = (a, b)=> a > b ? a : b;
57
- const mod = (a, b)=> ((a % b) + b) % b;
58
- const clamp = (v, max=1, min=0)=> (ASSERT(max > min), v < min ? min : v > max ? max : v);
59
- const percent = (v, max=1, min=0)=> max-min ? clamp((v-min) / (max-min)) : 0;
60
- const lerp = (p, max=1, min=0)=> min + clamp(p) * (max-min);
61
- const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
62
- const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(pA.y - pB.y)*2 < sA.y + sB.y;
63
- const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
64
- const wave = (f=1,a=1,t=time)=> a/2 * (1 - Math.cos(t*f*2*PI));
65
- const smoothStep = (p)=> p * p * (3 - 2 * p);
66
-
67
- // random functions
68
- const rand = (a=1, b=0)=> b + (a-b)*Math.random();
69
- const randInt = (a=1, b=0)=> rand(a,b)|0;
70
- const randSign = ()=> (rand(2)|0)*2-1;
145
+
146
+ /** Random global functions
147
+ * @namespace Random */
148
+
149
+ /** Returns a random value between the two values passed in
150
+ * @param {Number} [valueA=1]
151
+ * @param {Number} [valueB=0]
152
+ * @return {Number}
153
+ * @memberof Random */
154
+ const rand = (a=1, b=0)=> b + (a-b)*Math.random();
155
+
156
+ /** Returns a floored random value the two values passed in
157
+ * @param {Number} [valueA=1]
158
+ * @param {Number} [valueB=0]
159
+ * @return {Number}
160
+ * @memberof Random */
161
+ const randInt = (a=1, b=0)=> rand(a,b)|0;
162
+
163
+ /** Randomly returns either -1 or 1
164
+ * @return {Number}
165
+ * @memberof Random */
166
+ const randSign = ()=> (rand(2)|0)*2-1;
167
+
168
+ /** Returns a random Vector2 within a circular shape
169
+ * @param {Number} [radius=1]
170
+ * @param {Number} [minRadius=0]
171
+ * @return {Vector2}
172
+ * @memberof Random */
71
173
  const randInCircle = (radius=1, minRadius=0)=> radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2;
72
- const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
73
- const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
74
- linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
75
174
 
76
- // seeded random numbers using xorshift
77
- let randSeed = 1;
175
+ /** Returns a random Vector2 with the passed in length
176
+ * @param {Number} [length=1]
177
+ * @return {Vector2}
178
+ * @memberof Random */
179
+ const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
180
+
181
+ /** Returns a random color between the two passed in colors, combine components if linear
182
+ * @param {Color} [colorA=new Color(1,1,1,1)]
183
+ * @param {Color} [colorB=new Color(0,0,0,1)]
184
+ * @param {Boolean} [linear]
185
+ * @return {Color}
186
+ * @memberof Random */
187
+ const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
188
+ linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
189
+
190
+ /** The seed used by the randSeeded function, should not be 0
191
+ * @memberof Random */
192
+ let randSeed = 1;
193
+
194
+ /** Returns a seeded random value between the two values passed in using randSeed
195
+ * @param {Number} [valueA=1]
196
+ * @param {Number} [valueB=0]
197
+ * @return {Number}
198
+ * @memberof Random */
78
199
  const randSeeded = (a=1, b=0)=>
79
200
  {
80
- randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5;
201
+ randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5; // xorshift
81
202
  return b + (a-b)*abs(randSeed % 1e9)/1e9;
82
203
  }
83
204
 
84
- // create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
85
- const vec2 = (x=0, y)=> x.x == undefined? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
86
-
87
205
  ///////////////////////////////////////////////////////////////////////////////
206
+
207
+ /**
208
+ * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
209
+ * @param {Number} [x=0]
210
+ * @param {Number} [y=0]
211
+ * @return {Vector2}
212
+ * @example
213
+ * let a = vec2(0, 1); // vector with coordinates (0, 1)
214
+ * let b = vec2(a); // copy a into b
215
+ * a = vec2(5); // set a to (5, 5)
216
+ * b = vec2(); // set b to (0, 0)
217
+ * @memberof Utilities
218
+ */
219
+ const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
220
+
221
+ /**
222
+ * 2D Vector object with vector math library
223
+ * @example
224
+ * let a = new Vector2(2, 3); // vector with coordinates (2, 3)
225
+ * let b = new Vector2; // vector with coordinates (0, 0)
226
+ * let c = vec2(4, 2); // use the vec2 function to make a Vector2
227
+ */
88
228
  class Vector2
89
229
  {
90
- constructor(x=0, y=0) { this.x = x; this.y = y; }
91
-
92
- // basic math operators, a vector or scaler can be passed in
93
- copy() { return new Vector2(this.x, this.y); }
94
- scale(s) { ASSERT(s.x==undefined); return new Vector2(this.x * s, this.y * s); }
95
- add(v) { ASSERT(v.x!=undefined); return new Vector2(this.x + v.x, this.y + v.y); }
96
- subtract(v) { ASSERT(v.x!=undefined); return new Vector2(this.x - v.x, this.y - v.y); }
97
- multiply(v) { ASSERT(v.x!=undefined); return new Vector2(this.x * v.x, this.y * v.y); }
98
- divide(v) { ASSERT(v.x!=undefined); return new Vector2(this.x / v.x, this.y / v.y); }
99
-
100
- // vector math operators
101
- length() { return this.lengthSquared()**.5; }
102
- lengthSquared() { return this.x**2 + this.y**2; }
103
- distance(p) { return this.distanceSquared(p)**.5; }
104
- distanceSquared(p) { return (this.x - p.x)**2 + (this.y - p.y)**2; }
105
- normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(length); }
230
+ /** Create a 2D vector with the x and y passed in, can also be created with vec2()
231
+ * @param {Number} [x=0] - X axis location
232
+ * @param {Number} [y=0] - Y axis location */
233
+ constructor(x=0, y=0)
234
+ {
235
+ /** @property {Number} - X axis location */
236
+ this.x = x;
237
+ /** @property {Number} - Y axis location */
238
+ this.y = y;
239
+ }
240
+
241
+ /** Returns a new vector that is a copy of this
242
+ * @return {Vector2} */
243
+ copy() { return new Vector2(this.x, this.y); }
244
+
245
+ /** Returns a copy of this vector plus the vector passed in
246
+ * @param {Vector2} vector
247
+ * @return {Vector2} */
248
+ add(v) { ASSERT(v.x!=undefined); return new Vector2(this.x + v.x, this.y + v.y); }
249
+
250
+ /** Returns a copy of this vector minus the vector passed in
251
+ * @param {Vector2} vector
252
+ * @return {Vector2} */
253
+ subtract(v) { ASSERT(v.x!=undefined); return new Vector2(this.x - v.x, this.y - v.y); }
254
+
255
+ /** Returns a copy of this vector times the vector passed in
256
+ * @param {Vector2} vector
257
+ * @return {Vector2} */
258
+ multiply(v) { ASSERT(v.x!=undefined); return new Vector2(this.x * v.x, this.y * v.y); }
259
+
260
+ /** Returns a copy of this vector divided by the vector passed in
261
+ * @param {Vector2} vector
262
+ * @return {Vector2} */
263
+ divide(v) { ASSERT(v.x!=undefined); return new Vector2(this.x / v.x, this.y / v.y); }
264
+
265
+ /** Returns a copy of this vector scaled by the vector passed in
266
+ * @param {Number} scale
267
+ * @return {Vector2} */
268
+ scale(s) { ASSERT(s.x==undefined); return new Vector2(this.x * s, this.y * s); }
269
+
270
+ /** Returns the length of this vector
271
+ * @return {Number} */
272
+ length() { return this.lengthSquared()**.5; }
273
+
274
+ /** Returns the length of this vector squared
275
+ * @return {Number} */
276
+ lengthSquared() { return this.x**2 + this.y**2; }
277
+
278
+ /** Returns the distance from this vector to vector passed in
279
+ * @param {Vector2} vector
280
+ * @return {Number} */
281
+ distance(v) { return this.distanceSquared(v)**.5; }
282
+
283
+ /** Returns the distance squared from this vector to vector passed in
284
+ * @param {Vector2} vector
285
+ * @return {Number} */
286
+ distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
287
+
288
+ /** Returns a new vector in same direction as this one with the length passed in
289
+ * @param {Number} [length=1]
290
+ * @return {Vector2} */
291
+ normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(length); }
292
+
293
+ /** Returns a new vector clamped to length passed in
294
+ * @param {Number} [length=1]
295
+ * @return {Vector2} */
106
296
  clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
107
- dot(v) { ASSERT(v.x!=undefined); return this.x*v.x + this.y*v.y; }
108
- cross(v) { ASSERT(v.x!=undefined); return this.x*v.y - this.y*v.x; }
109
- angle() { return Math.atan2(this.x, this.y); }
110
- setAngle(a, length=1) { this.x = length*Math.sin(a); this.y = length*Math.cos(a); return this; }
111
- rotate(a) { const c = Math.cos(a), s = Math.sin(a); return new Vector2(this.x*c-this.y*s, this.x*s+this.y*c); }
112
- direction() { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
113
- flip() { return new Vector2(this.y, this.x); }
114
- invert() { return new Vector2(this.y, -this.x); }
115
- round() { return new Vector2(Math.round(this.x), Math.round(this.y)); }
116
- floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
117
- int() { return new Vector2(this.x|0, this.y|0); }
118
- lerp(v, p) { ASSERT(v.x!=undefined); return this.add(v.subtract(this).scale(clamp(p))); }
119
- area() { return this.x * this.y; }
297
+
298
+ /** Returns the dot product of this and the vector passed in
299
+ * @param {Vector2} vector
300
+ * @return {Number} */
301
+ dot(v) { ASSERT(v.x!=undefined); return this.x*v.x + this.y*v.y; }
302
+
303
+ /** Returns the cross product of this and the vector passed in
304
+ * @param {Vector2} vector
305
+ * @return {Number} */
306
+ cross(v) { ASSERT(v.x!=undefined); return this.x*v.y - this.y*v.x; }
307
+
308
+ /** Returns the angle of this vector, up is angle 0
309
+ * @return {Number} */
310
+ angle() { return Math.atan2(this.x, this.y); }
311
+
312
+ /** Sets this vector with angle and length passed in
313
+ * @param {Number} [angle=0]
314
+ * @param {Number} [length=1] */
315
+ setAngle(a=0, length=1) { this.x = length*Math.sin(a); this.y = length*Math.cos(a); return this; }
316
+
317
+ /** Returns copy of this vector rotated by the angle passed in
318
+ * @param {Number} angle
319
+ * @return {Vector2} */
320
+ rotate(a) { const c = Math.cos(a), s = Math.sin(a); return new Vector2(this.x*c-this.y*s, this.x*s+this.y*c); }
321
+
322
+ /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
323
+ * @return {Number} */
324
+ direction() { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
325
+
326
+ /** Returns a copy of this vector that has been inverted
327
+ * @return {Vector2} */
328
+ invert() { return new Vector2(this.y, -this.x); }
329
+
330
+ /** Returns a copy of this vector with the axies flipped
331
+ * @return {Vector2} */
332
+ flip() { return new Vector2(this.y, this.x); }
333
+
334
+ /** Returns a copy of this vector with each axis floored
335
+ * @return {Vector2} */
336
+ floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
337
+
338
+ /** Returns the area this vector covers as a rectangle
339
+ * @return {Number} */
340
+ area() { return this.x * this.y; }
341
+
342
+ /** Returns a new vector that is p percent between this and the vector passed in
343
+ * @param {Vector2} vector
344
+ * @param {Number} percent
345
+ * @return {Vector2} */
346
+ lerp(v, p) { ASSERT(v.x!=undefined); return this.add(v.subtract(this).scale(clamp(p))); }
347
+
348
+ /** Returns true if this vector is within the bounds of an array size passed in
349
+ * @param {Vector2} arraySize
350
+ * @return {Boolean} */
120
351
  arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
121
352
  }
122
353
 
123
354
  ///////////////////////////////////////////////////////////////////////////////
355
+
356
+ /**
357
+ * Color object (red, green, blue, alpha) with some helpful functions
358
+ * @example
359
+ * let a = new Color; // white
360
+ * let b = new Color(1, 0, 0); // red
361
+ * let c = new Color(0, 0, 0, 0); // transparent black
362
+ */
124
363
  class Color
125
364
  {
126
- constructor(r=1, g=1, b=1, a=1) { this.r=r; this.g=g; this.b=b; this.a=a; }
365
+ /** Create a color with the components passed in, white by default
366
+ * @param {Number} [red=1]
367
+ * @param {Number} [green=1]
368
+ * @param {Number} [blue=1]
369
+ * @param {Number} [alpha=1] */
370
+ constructor(r=1, g=1, b=1, a=1)
371
+ {
372
+ /** @property {Number} - Red */
373
+ this.r = r;
374
+ /** @property {Number} - Green */
375
+ this.g = g;
376
+ /** @property {Number} - Blue */
377
+ this.b = b;
378
+ /** @property {Number} - Alpha */
379
+ this.a = a;
380
+ }
381
+
382
+ /** Returns a new color that is a copy of this
383
+ * @return {Color} */
384
+ copy() { return new Color(this.r, this.g, this.b, this.a); }
385
+
386
+ /** Returns a copy of this color plus the color passed in
387
+ * @param {Color} color
388
+ * @return {Color} */
389
+ add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
127
390
 
128
- copy(c) { return new Color(this.r, this.g, this.b, this.a); }
129
- add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
391
+ /** Returns a copy of this color minus the color passed in
392
+ * @param {Color} color
393
+ * @return {Color} */
130
394
  subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
395
+
396
+ /** Returns a copy of this color times the color passed in
397
+ * @param {Color} color
398
+ * @return {Color} */
131
399
  multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
132
- scale(s,a=s){ return new Color(this.r*s, this.g*s, this.b*s, this.a*a); }
133
- clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
134
- lerp(c, p) { return this.add(c.subtract(this).scale(clamp(p))); }
400
+
401
+ /** Returns a copy of this color divided by the color passed in
402
+ * @param {Color} color
403
+ * @return {Color} */
404
+ divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
405
+
406
+ /** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
407
+ * @param {Number} scale
408
+ * @param {Number} [alphaScale=scale]
409
+ * @return {Color} */
410
+ scale(s, a=s) { return new Color(this.r*s, this.g*s, this.b*s, this.a*a); }
411
+
412
+ /** Returns a copy of this color clamped to the valid range between 0 and 1
413
+ * @return {Color} */
414
+ clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
415
+
416
+ /** Returns a new color that is p percent between this and the color passed in
417
+ * @param {Color} color
418
+ * @param {Number} percent
419
+ * @return {Color} */
420
+ lerp(c, p) { return this.add(c.subtract(this).scale(clamp(p))); }
421
+
422
+ /** Sets this color given a hue, saturation, lightness , and alpha
423
+ * @param {Number} [hue=0]
424
+ * @param {Number} [saturation=0]
425
+ * @param {Number} [lightness=1]
426
+ * @param {Number} [alpha=1]
427
+ * @return {Color} */
428
+ setHSLA(h=0, s=0, l=1, a=1)
429
+ {
430
+ const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
431
+ f = (p, q, t)=>
432
+ (t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
433
+ t < 1/2 ? q :
434
+ t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
435
+
436
+ this.r = f(p, q, h + 1/3);
437
+ this.g = f(p, q, h);
438
+ this.b = f(p, q, h - 1/3);
439
+ this.a = a;
440
+ return this;
441
+ }
442
+
443
+ /** Returns a new color that has each component randomly adjusted
444
+ * @param {Number} [amount=.05]
445
+ * @param {Number} [alphaAmount=0]
446
+ * @return {Color} */
135
447
  mutate(amount=.05, alphaAmount=0)
136
448
  {
137
449
  return new Color
@@ -142,117 +454,264 @@ class Color
142
454
  this.a + rand(alphaAmount, -alphaAmount)
143
455
  ).clamp();
144
456
  }
457
+
458
+ /** Returns this color expressed as an rgba string
459
+ * @return {String} */
145
460
  rgba()
146
461
  {
147
462
  ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
148
463
  return `rgb(${this.r*255|0},${this.g*255|0},${this.b*255|0},${this.a})`;
149
464
  }
465
+
466
+ /** Returns this color expressed as 32 bit integer value
467
+ * @return {Number} */
150
468
  rgbaInt()
151
469
  {
152
470
  ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
153
471
  return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
154
472
  }
155
- setHSLA(h=0, s=0, l=1, a=1)
156
- {
157
- const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
158
- f = (p, q, t)=>
159
- (t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
160
- t < 1/2 ? q :
161
- t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
162
-
163
- this.r = f(p, q, h + 1/3);
164
- this.g = f(p, q, h);
165
- this.b = f(p, q, h - 1/3);
166
- this.a = a;
167
- return this;
168
- }
169
473
  }
170
474
 
171
475
  ///////////////////////////////////////////////////////////////////////////////
476
+
477
+ /**
478
+ * Timer object tracks how long has passed since it was set
479
+ * @example
480
+ * let a = new Timer; // creates a timer that is not set
481
+ * a.set(3); // sets the timer to 3 seconds
482
+ *
483
+ * let b = new Timer(1); // creates a timer with 1 second left
484
+ * b.unset(); // unsets the timer
485
+ */
172
486
  class Timer
173
487
  {
174
- constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
488
+ /** Create a timer object set time passed in
489
+ * @param {Number} [timeLeft] - How much time left before the timer elapses in seconds */
490
+ constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
175
491
 
492
+ /** Set the timer with seconds passed in
493
+ * @param {Number} [timeLeft=0] - How much time left before the timer is elapsed in seconds */
176
494
  set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
177
- unset() { this.time = undefined; }
178
- isSet() { return this.time != undefined; }
179
- active() { return time <= this.time; }
180
- elapsed() { return time > this.time; }
181
- get() { return this.isSet()? time - this.time : 0; }
182
- getPercent() { return this.isSet()? percent(this.time - time, 0, this.setTime) : 0; }
495
+
496
+ /** Unset the timer */
497
+ unset() { this.time = undefined; }
498
+
499
+ /** Returns true if set
500
+ * @return {Boolean} */
501
+ isSet() { return this.time != undefined; }
502
+
503
+ /** Returns true if set and has not elapsed
504
+ * @return {Boolean} */
505
+ active() { return time <= this.time; }
506
+
507
+ /** Returns true if set and elapsed
508
+ * @return {Boolean} */
509
+ elapsed() { return time > this.time; }
510
+
511
+ /** Get how long since elapsed, returns 0 if not set
512
+ * @return {Number} */
513
+ get() { return this.isSet()? time - this.time : 0; }
514
+
515
+ /** Get percentage elapsed based on time it was set to, returns 0 if not set
516
+ * @return {Number} */
517
+ getPercent() { return this.isSet()? percent(this.time - time, 0, this.setTime) : 0; }
183
518
  }
184
- /*
185
- LittleJS Engine Configuration
186
- */
519
+ /**
520
+ * LittleJS Engine Settings
521
+ * @namespace Settings
522
+ */
187
523
 
188
- ///////////////////////////////////////////////////////////////////////////////
189
- // display settings
524
+ 'use strict';
190
525
 
191
- const maxWidth = 1920, maxHeight = 1200; // up to 1080p and 16:10
192
- let defaultFont = 'arial'; // font used for text rendering
193
- let fixedWidth = 0, fixedHeight = 0; // use native resolution
194
- let fixedFitToWindow = 1; // stretch canvas to fit window
195
- //const fixedWidth = 1280, fixedHeight = 720; // 720p
196
- //const fixedWidth = 1920, fixedHeight = 1080; // 1080p
197
- //const fixedWidth = 128, fixedHeight = 128; // PICO-8
198
- //const fixedWidth = 240, fixedHeight = 136; // TIC-80
526
+ ///////////////////////////////////////////////////////////////////////////////
527
+ // Display settings
528
+
529
+ /** The max size of the canvas, centered if window is larger
530
+ * @type {Vector2}
531
+ * @default
532
+ * @memberof Settings */
533
+ const maxSize = vec2(1920, 1200);
534
+
535
+ /** Fixed size of the canvas, if enabled cavnvas size never changes
536
+ * @type {Vector2}
537
+ * @default
538
+ * @memberof Settings */
539
+ let fixedSize = vec2();
540
+
541
+ /** Default font used for text rendering
542
+ * @default
543
+ * @memberof Settings */
544
+ let fontDefault = 'arial';
545
+
546
+ /** Use crisp pixels for pixel art if true
547
+ * @default
548
+ * @memberof Settings */
549
+ let pixelated = 1;
199
550
 
200
551
  ///////////////////////////////////////////////////////////////////////////////
201
- // tile sheet settings
552
+ // Tile sheet settings
202
553
 
203
- const defaultTileSize = vec2(16); // default size of tiles in pixels
204
- const tileBleedShrinkFix = .3; // prevent tile bleeding from neighbors
205
- let pixelated = 1; // use crisp pixels for pixel art
554
+ /** Default size of tiles in pixels
555
+ * @type {Vector2}
556
+ * @default
557
+ * @memberof Settings */
558
+ const tileSizeDefault = vec2(16);
206
559
 
207
- ///////////////////////////////////////////////////////////////////////////////
208
- // webgl config
560
+ /** Prevent tile bleeding from neighbors in pixels
561
+ * @default
562
+ * @memberof Settings */
563
+ const tileBleedShrinkFix = .3;
209
564
 
210
- const glEnable = 1; // can run without gl (texured coloring will be disabled)
211
- let glOverlay = 0; // fix slow rendering in some browsers by not compositing the WebGL canvas
565
+ ///////////////////////////////////////////////////////////////////////////////
566
+ // Object settings
567
+
568
+ /** Default size of objects
569
+ * @type {Vector2}
570
+ * @default
571
+ * @memberof Settings */
572
+ const objectDefaultSize = vec2(1);
573
+
574
+ /** Default object mass for collison calcuations (how heavy objects are)
575
+ * @default
576
+ * @memberof Settings */
577
+ const objectDefaultMass = 1;
578
+
579
+ /** How much to slow velocity by each frame (0-1)
580
+ * @default
581
+ * @memberof Settings */
582
+ const objectDefaultDamping = .99;
583
+
584
+ /** How much to slow angular velocity each frame (0-1)
585
+ * @default
586
+ * @memberof Settings */
587
+ const objectDefaultAngleDamping = .99;
588
+
589
+ /** How much to bounce when a collision occurs (0-1)
590
+ * @default
591
+ * @memberof Settings */
592
+ const objectDefaultElasticity = 0;
593
+
594
+ /** How much to slow when touching (0-1)
595
+ * @default
596
+ * @memberof Settings */
597
+ const objectDefaultFriction = .8;
598
+
599
+ /** Clamp max speed to avoid fast objects missing collisions
600
+ * @default
601
+ * @memberof Settings */
602
+ const objectMaxSpeed = 1;
603
+
604
+ /** How much gravity to apply to objects along the Y axis, negative is down
605
+ * @default
606
+ * @memberof Settings */
607
+ let gravity = 0;
212
608
 
213
609
  ///////////////////////////////////////////////////////////////////////////////
214
- // object config
610
+ // Camera settings
611
+
612
+ /** Position of camera in world space
613
+ * @type {Vector2}
614
+ * @default
615
+ * @memberof Settings */
616
+ let cameraPos = vec2();
215
617
 
216
- const defaultObjectSize = vec2(.999); // size of objecs, tiny bit less then 1 to fit in holes
217
- const defaultObjectMass = 1; // how heavy are objects for collison calcuations
218
- const defaultObjectDamping = .99; // how much to slow velocity by each frame 0-1
219
- const defaultObjectAngleDamping = .99; // how much to slow angular velocity each frame 0-1
220
- const defaultObjectElasticity = 0; // how much to bounce 0-1
221
- const defaultObjectFriction = .8; // how much to slow when touching 0-1
222
- const maxObjectSpeed = 1; // camp max speed to avoid fast objects missing collisions
618
+ /** Scale of camera in world space
619
+ * @default
620
+ * @memberof Settings */
621
+ let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
223
622
 
224
623
  ///////////////////////////////////////////////////////////////////////////////
225
- // input config
624
+ // WebGL settings
226
625
 
227
- const gamepadsEnable = 1; // should gamepads be allowed
228
- const touchInputEnable = 1; // touch input is routed to mouse
229
- const copyGamepadDirectionToStick = 1; // allow players to use dpad as analog stick
230
- const copyWASDToDpad = 1; // allow players to use WASD as direction keys
626
+ /** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
627
+ * @default
628
+ * @memberof Settings */
629
+ const glEnable = 1;
630
+
631
+ /** Fixes slow rendering in some browsers by not compositing the WebGL canvas
632
+ * @default
633
+ * @memberof Settings */
634
+ let glOverlay = 1;
231
635
 
232
636
  ///////////////////////////////////////////////////////////////////////////////
233
- // audio config
637
+ // Input settings
638
+
639
+ /** Should gamepads be allowed
640
+ * @default
641
+ * @memberof Settings */
642
+ const gamepadsEnable = 1;
234
643
 
235
- const soundEnable = 1; // all audio can be disabled
236
- let audioVolume = .4; // volume for sound, music and speech
237
- const defaultSoundRange = 30; // range where sound no longer plays
238
- const defaultSoundTaper = .7; // what range percent to start tapering off sound 0-1
644
+ /** If true, the dpad input is also routed to the left analog stick (for better accessability)
645
+ * @default
646
+ * @memberof Settings */
647
+ const gamepadDirectionEmulateStick = 1;
648
+
649
+ /** If true touch input is routed to mouse functions
650
+ * @default
651
+ * @memberof Settings */
652
+ const inputTouchEnable = 1;
653
+
654
+ /** If true the WASD keys are also routed to the direction keys (for better accessability)
655
+ * @default
656
+ * @memberof Settings */
657
+ const inputWASDEmulateDirection = 1;
239
658
 
240
659
  ///////////////////////////////////////////////////////////////////////////////
241
- // medals config
660
+ // Audio settings
661
+
662
+ /** Volume scale to apply to all sound, music and speech
663
+ * @default
664
+ * @memberof Settings */
665
+ let soundVolume = .5;
666
+
667
+ /** All audio code can be disabled and removed from build
668
+ * @default
669
+ * @memberof Settings */
670
+ const soundEnable = 1;
242
671
 
243
- const medalDisplayTime = 5; // how long to show medals
244
- const medalDisplaySlideTime = .5; // how quick to slide on/off medals
245
- const medalDisplayWidth = 640; // width of medal display
246
- const medalDisplayHeight = 99; // height of medal display
247
- const medalDisplayIconSize = 80; // size of icon in medal display
672
+ /** Default range where sound no longer plays
673
+ * @default
674
+ * @memberof Settings */
675
+ const soundDefaultRange = 30;
676
+
677
+ /** Default range percent to start tapering off sound (0-1)
678
+ * @default
679
+ * @memberof Settings */
680
+ const soundDefaultTaper = .7;
681
+
682
+ ///////////////////////////////////////////////////////////////////////////////
683
+ // Medals settings
684
+
685
+ /** How long to show medals for in seconds
686
+ * @default
687
+ * @memberof Settings */
688
+ const medalDisplayTime = 5;
689
+
690
+ /** How quickly to slide on/off medals in seconds
691
+ * @default
692
+ * @memberof Settings */
693
+ const medalDisplaySlideTime = .5;
694
+
695
+ /** Width of medal display
696
+ * @default
697
+ * @memberof Settings */
698
+ const medalDisplayWidth = 640;
699
+
700
+ /** Height of medal display
701
+ * @default
702
+ * @memberof Settings */
703
+ const medalDisplayHeight = 99;
704
+
705
+ /** Size of icon in medal display
706
+ * @default
707
+ * @memberof Settings */
708
+ const medalDisplayIconSize = 80;
248
709
  /*
249
- LittleJS - The Tiny JavaScript Game Engine That Can
250
- MIT License - Copyright 2019 Frank Force
710
+ LittleJS - The Tiny JavaScript Game Engine That Can!
711
+ MIT License - Copyright 2021 Frank Force
251
712
 
252
713
  Engine Features
253
- - Engine and debug system are separate from game code
254
- - Object oriented with base class engine object
255
- - Engine handles core update loop
714
+ - Object oriented system with base class engine object
256
715
  - Base class object handles update, physics, collision, rendering, etc
257
716
  - Engine helper classes and functions like Vector2, Color, and Timer
258
717
  - Super fast rendering system for tile sheets
@@ -260,30 +719,63 @@ const medalDisplayIconSize = 80; // size of icon in medal display
260
719
  - Input processing system with gamepad and touchscreen support
261
720
  - Tile layer rendering and collision system
262
721
  - Particle effect system
263
- - Automatically calls gameInit(), gameUpdate(), gameUpdatePost(), gameRender(), gameRenderPost()
722
+ - Medal system tracks and displays achievements
264
723
  - Debug tools and debug rendering system
265
724
  - Call engineInit() to start it up!
266
725
  */
267
726
 
268
727
  'use strict';
269
728
 
729
+ /** Name of engine */
270
730
  const engineName = 'LittleJS';
271
- const engineVersion = '1.0.14';
272
- const FPS = 60, timeDelta = 1/FPS; // engine uses a fixed time step
273
- const tileImage = new Image(); // everything uses the same tile sheet
274
-
275
- // core engine variables
276
- let mainCanvas, mainContext, overlayCanvas, overlayContext, mainCanvasSize=vec2(),
277
- engineObjects=[], engineCollideObjects=[],
278
- cameraPos=vec2(), cameraScale=max(defaultTileSize.x, defaultTileSize.y),
279
- frame=0, time=0, realTime=0, paused=0, frameTimeLastMS=0, frameTimeBufferMS=0, debugFPS=0, gravity=0,
280
- tileImageSize, tileImageSizeInverse, shrinkTilesX, shrinkTilesY, drawCount;
281
-
282
- // call this function to start the engine
731
+
732
+ /** Version of engine */
733
+ const engineVersion = '1.1.8';
734
+
735
+ /** Frames per second to update objects
736
+ * @default */
737
+ const FPS = 60;
738
+
739
+ /** How many seconds each frame lasts, engine uses a fixed time step
740
+ * @default 1/60 */
741
+ const timeDelta = 1/FPS;
742
+
743
+ /** Array containing all engine objects */
744
+ let engineObjects = [];
745
+
746
+ /** Array containing only objects that are set to collide with other objects (for optimization) */
747
+ let engineCollideObjects = [];
748
+
749
+ /** Current update frame, used to calculate time */
750
+ let frame = 0;
751
+
752
+ /** Current engine time since start in seconds, derived from frame */
753
+ let time = 0;
754
+
755
+ /** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
756
+ let timeReal = 0;
757
+
758
+ /** Is the game paused? Causes time and objects to not be updated. */
759
+ let paused = 0;
760
+
761
+ // Engine internal variables not exposed to documentation
762
+ let frameTimeLastMS = 0, frameTimeBufferMS = 0, debugFPS = 0,
763
+ shrinkTilesX, shrinkTilesY, drawCount, tileImageSize, tileImageSizeInverse;
764
+
765
+ ///////////////////////////////////////////////////////////////////////////////
766
+
767
+ /** Start up LittleJS engine with your callback functions
768
+ * @param {Function} gameInit - Called once after the engine starts up, setup the game
769
+ * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
770
+ * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
771
+ * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
772
+ * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
773
+ * @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
774
+ */
283
775
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
284
776
  {
285
- // init engine when tiles load
286
- tileImage.onload = ()=>
777
+ // init engine when tiles load or fail to load
778
+ tileImage.onerror = tileImage.onload = ()=>
287
779
  {
288
780
  // save tile image info
289
781
  tileImageSizeInverse = vec2(1).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
@@ -323,7 +815,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
323
815
  debugFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), debugFPS);
324
816
  if (debug)
325
817
  frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
326
- realTime += frameTimeDeltaMS / 1e3;
818
+ timeReal += frameTimeDeltaMS / 1e3;
327
819
  frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
328
820
 
329
821
  if (paused)
@@ -351,7 +843,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
351
843
  // update game and objects
352
844
  inputUpdate();
353
845
  gameUpdate();
354
- engineUpdateObjects();
846
+ engineObjectsUpdate();
355
847
 
356
848
  // do post update
357
849
  debugUpdate();
@@ -363,31 +855,28 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
363
855
  frameTimeBufferMS += deltaSmooth;
364
856
  }
365
857
 
366
- if (fixedWidth)
858
+ if (fixedSize.x)
367
859
  {
368
860
  // clear set fixed size
369
- mainCanvas.width = fixedWidth;
370
- mainCanvas.height = fixedHeight;
861
+ mainCanvas.width = fixedSize.x;
862
+ mainCanvas.height = fixedSize.y;
371
863
 
372
- if (fixedFitToWindow)
864
+ // fit to window by adding space on top or bottom if necessary
865
+ const aspect = innerWidth / innerHeight;
866
+ const fixedAspect = mainCanvas.width / mainCanvas.height;
867
+ mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
868
+ mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
869
+ if (glCanvas)
373
870
  {
374
- // fit to window by adding space on top or bottom if necessary
375
- const aspect = innerWidth / innerHeight;
376
- const fixedAspect = fixedWidth / fixedHeight;
377
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
378
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
379
- if (glCanvas)
380
- {
381
- glCanvas.style.width = mainCanvas.style.width;
382
- glCanvas.style.height = mainCanvas.style.height;
383
- }
871
+ glCanvas.style.width = mainCanvas.style.width;
872
+ glCanvas.style.height = mainCanvas.style.height;
384
873
  }
385
874
  }
386
875
  else
387
876
  {
388
877
  // clear and set size to same as window
389
- mainCanvas.width = min(innerWidth, maxWidth);
390
- mainCanvas.height = min(innerHeight, maxHeight);
878
+ mainCanvas.width = min(innerWidth, maxSize.x);
879
+ mainCanvas.height = min(innerHeight, maxSize.y);
391
880
  }
392
881
 
393
882
  // save canvas size and clear overlay canvas
@@ -425,7 +914,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
425
914
  tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
426
915
  }
427
916
 
428
- function engineUpdateObjects()
917
+
918
+ ///////////////////////////////////////////////////////////////////////////////
919
+
920
+ /** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
921
+ function engineObjectsUpdate()
429
922
  {
430
923
  // recursive object update
431
924
  const updateObject = (o)=>
@@ -447,52 +940,125 @@ function engineUpdateObjects()
447
940
  // increment frame and update time
448
941
  time = ++frame / FPS;
449
942
  }
943
+
944
+ /** Detroy and remove all objects that are not persistent or descendants of a persistent object */
945
+ function engineObjectsDestroy()
946
+ {
947
+ for (const o of engineObjects)
948
+ o.persistent || o.parent || o.destroy();
949
+ engineObjects = engineObjects.filter(o=>!o.destroyed);
950
+ }
951
+
952
+ /** Triggers a callback for each object within a given area
953
+ * @param {Vector2} [pos] - Center of test area
954
+ * @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
955
+ * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
956
+ * @param {Array} [objects=engineObjects] - List of objects to check */
957
+ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
958
+ {
959
+ if (!pos)
960
+ {
961
+ // all objects
962
+ for (const o of objects)
963
+ callbackFunction(o);
964
+ }
965
+ else if (size.x != undefined)
966
+ {
967
+ // aabb test
968
+ for (const o of objects)
969
+ isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
970
+ }
971
+ else
972
+ {
973
+ // circle test
974
+ const sizeSquared = size*size;
975
+ for (const o of objects)
976
+ pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
977
+ }
978
+ }
450
979
  /*
451
- LittleJS Object Base Class
452
- - Base object class used by the engine
453
- - Automatically adds self to object list
454
- - Will be updated and rendered each frame
455
- - Renders as a sprite from a tilesheet by default
456
- - Can have color and addtive color applied
457
- - 2d Physics and collision system
458
- - Sorted by renderOrder
459
- - Objects can have children attached
460
- - Parents are updated before children, and set child transform
461
- - Call destroy() to get rid of objects
980
+ LittleJS Object System
462
981
  */
463
982
 
464
983
  'use strict';
465
984
 
985
+ /**
986
+ * LittleJS Object Base Object Class
987
+ * <br> - Base object class used by the engine
988
+ * <br> - Automatically adds self to object list
989
+ * <br> - Will be updated and rendered each frame
990
+ * <br> - Renders as a sprite from a tilesheet by default
991
+ * <br> - Can have color and addtive color applied
992
+ * <br> - 2d Physics and collision system
993
+ * <br> - Sorted by renderOrder
994
+ * <br> - Objects can have children attached
995
+ * <br> - Parents are updated before children, and set child transform
996
+ * <br> - Call destroy() to get rid of objects
997
+ * @example
998
+ * // create an engine object, normally you would first extend the class with your own
999
+ * const pos = vec2(2,3);
1000
+ * const object = new EngineObject(pos);
1001
+ */
466
1002
  class EngineObject
467
1003
  {
468
- constructor(pos, size=defaultObjectSize, tileIndex=-1, tileSize=defaultTileSize, angle=0, color)
1004
+ /** Create an engine object and adds it to the list of objects
1005
+ * @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
1006
+ * @param {Vector2} [size=objectDefaultSize] - World space size of the object
1007
+ * @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
1008
+ * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
1009
+ * @param {Number} [angle=0] - Angle to rotate the object
1010
+ * @param {Color} [color] - Color to apply to tile when rendered
1011
+ * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
1012
+ */
1013
+ constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
469
1014
  {
470
1015
  // set passed in params
471
1016
  ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
1017
+
1018
+ /** @property {Vector2} - World space position of the object */
472
1019
  this.pos = pos.copy();
1020
+ /** @property {Vector2} - World space width and height of the object */
473
1021
  this.size = size;
1022
+ /** @property {Vector2} - Size of object used for drawing, uses size if not set */
1023
+ this.drawSize;
1024
+ /** @property {Number} - Tile to use to render object, untextured if -1 */
474
1025
  this.tileIndex = tileIndex;
1026
+ /** @property {Vector2} - Size of tile in source pixels */
475
1027
  this.tileSize = tileSize;
1028
+ /** @property {Number} - Angle to rotate the object */
476
1029
  this.angle = angle;
1030
+ /** @property {Color} - Color to apply when rendered */
477
1031
  this.color = color;
478
-
479
- // set physics defaults
480
- this.mass = defaultObjectMass;
481
- this.damping = defaultObjectDamping;
482
- this.angleDamping = defaultObjectAngleDamping;
483
- this.elasticity = defaultObjectElasticity;
484
- this.friction = defaultObjectFriction;
485
-
486
- // init other object stuff
1032
+ /** @property {Color} - Additive color to apply when rendered */
1033
+ this.additiveColor;
1034
+
1035
+ // set object defaults
1036
+ /** @property {Number} [mass=objectDefaultMass] - How heavy the object is */
1037
+ this.mass = objectDefaultMass;
1038
+ /** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
1039
+ this.damping = objectDefaultDamping;
1040
+ /** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
1041
+ this.angleDamping = objectDefaultAngleDamping;
1042
+ /** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */
1043
+ this.elasticity = objectDefaultElasticity;
1044
+ /** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
1045
+ this.friction = objectDefaultFriction;
1046
+ /** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
1047
+ this.gravityScale = 1;
1048
+ /** @property {Number} [renderOrder=0] - Objects are sorted by render order */
1049
+ this.renderOrder = renderOrder;
1050
+
1051
+ // init other internal object stuff
487
1052
  this.spawnTime = time;
488
- this.velocity = vec2(this.collideSolidObjects = this.renderOrder = this.angleVelocity = 0);
489
- this.collideTiles = this.gravityScale = 1;
1053
+ this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
1054
+ this.collideTiles = 1;
490
1055
  this.children = [];
491
1056
 
492
1057
  // add to list of objects
493
1058
  engineObjects.push(this);
494
1059
  }
495
1060
 
1061
+ /** Update the object transform and physics, called automatically by engine once each frame */
496
1062
  update()
497
1063
  {
498
1064
  const parent = this.parent;
@@ -505,8 +1071,8 @@ class EngineObject
505
1071
  }
506
1072
 
507
1073
  // limit max speed to prevent missing collisions
508
- this.velocity.x = clamp(this.velocity.x, maxObjectSpeed, -maxObjectSpeed);
509
- this.velocity.y = clamp(this.velocity.y, maxObjectSpeed, -maxObjectSpeed);
1074
+ this.velocity.x = clamp(this.velocity.x, objectMaxSpeed, -objectMaxSpeed);
1075
+ this.velocity.y = clamp(this.velocity.y, objectMaxSpeed, -objectMaxSpeed);
510
1076
 
511
1077
  // apply physics
512
1078
  const oldPos = this.pos.copy();
@@ -560,7 +1126,7 @@ class EngineObject
560
1126
  if (o.mass) // push away if not fixed
561
1127
  o.velocity = o.velocity.subtract(velocity);
562
1128
 
563
- debugPhysics && debugAABB(this.pos, o.pos, this.size, o.size, '#f00');
1129
+ debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f00');
564
1130
  continue;
565
1131
  }
566
1132
 
@@ -626,7 +1192,7 @@ class EngineObject
626
1192
  this.velocity.x *= -this.elasticity;
627
1193
  }
628
1194
 
629
- debugPhysics && debugAABB(this.pos, o.pos, this.size, o.size, '#f0f');
1195
+ debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
630
1196
  }
631
1197
  }
632
1198
  if (this.collideTiles)
@@ -634,8 +1200,6 @@ class EngineObject
634
1200
  // check collision against tiles
635
1201
  if (tileCollisionTest(this.pos, this.size, this))
636
1202
  {
637
- //debugPhysics && debugRect(this.pos, this.size, '#ff0');
638
-
639
1203
  // if already was stuck in collision, don't do anything
640
1204
  // this should not happen unless something starts in collision
641
1205
  if (!tileCollisionTest(oldPos, this.size, this))
@@ -648,13 +1212,20 @@ class EngineObject
648
1212
  // set if landed on ground
649
1213
  this.groundObject = wasMovingDown;
650
1214
 
651
- // push out of collision and bounce
652
- this.pos.y = oldPos.y;
1215
+ // bounce velocity
653
1216
  this.velocity.y *= -this.elasticity;
1217
+
1218
+ // adjust next velocity to settle on ground
1219
+ const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
1220
+ if (o < 0 && o > -1 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
1221
+ this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
1222
+
1223
+ // move to previous position
1224
+ this.pos.y = oldPos.y;
654
1225
  }
655
1226
  if (isBlockedX)
656
1227
  {
657
- // push out of collision and bounce
1228
+ // move to previous position and bounce
658
1229
  this.pos.x = oldPos.x;
659
1230
  this.velocity.x *= -this.elasticity;
660
1231
  }
@@ -663,12 +1234,14 @@ class EngineObject
663
1234
  }
664
1235
  }
665
1236
 
1237
+ /** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
666
1238
  render()
667
1239
  {
668
1240
  // default object render
669
1241
  drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
670
1242
  }
671
1243
 
1244
+ /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
672
1245
  destroy()
673
1246
  {
674
1247
  if (this.destroyed)
@@ -680,15 +1253,45 @@ class EngineObject
680
1253
  for (const child of this.children)
681
1254
  child.destroy(child.parent = 0);
682
1255
  }
683
- collideWithTile(data, pos) { return data > 0; }
684
- collideWithTileRaycast(data, pos) { return data > 0; }
1256
+
1257
+ /** Called to check if a tile collision should be resolved
1258
+ * @param {Number} tileData - the value of the tile at the position
1259
+ * @param {Vector2} pos - tile where the collision occured
1260
+ * @return {Boolean} true if the collision should be resolved */
1261
+ collideWithTile(tileData, pos) { return tileData > 0; }
1262
+
1263
+ /** Called to check if a tile raycast hit
1264
+ * @param {Number} tileData - the value of the tile at the position
1265
+ * @param {Vector2} pos - tile where the raycast is
1266
+ * @return {Boolean} true if the raycast should hit */
1267
+ collideWithTileRaycast(tileData, pos) { return tileData > 0; }
1268
+
1269
+ /** Called to check if a tile raycast hit
1270
+ * @param {EngineObject} object - the object to test against
1271
+ * @return {Boolean} true if the collision should be resolved
1272
+ */
685
1273
  collideWithObject(o) { return 1; }
1274
+
1275
+ /** How long since the object was created
1276
+ * @return {Number} */
686
1277
  getAliveTime() { return time - this.spawnTime; }
687
- applyAcceleration(a) { ASSERT(!this.isFixed()); this.velocity = this.velocity.add(a); }
688
- applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
689
- isFixed() { return !this.mass; }
690
- getMirrorSign(s=1) { return this.mirror ? -s : s; }
691
1278
 
1279
+ /** Apply acceleration to this object (adjust velocity, not affected by mass)
1280
+ * @param {Vector2} acceleration */
1281
+ applyAcceleration(a) { if (this.mass) this.velocity = this.velocity.add(a); }
1282
+
1283
+ /** Apply force to this object (adjust velocity, affected by mass)
1284
+ * @param {Vector2} force */
1285
+ applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
1286
+
1287
+ /** Get the direction of the mirror
1288
+ * @return {Number} -1 if this.mirror is true, or 1 if not mirrored */
1289
+ getMirrorSign() { return this.mirror ? -1 : 1; }
1290
+
1291
+ /** Attaches a child to this with a given local transform
1292
+ * @param {EngineObject} child
1293
+ * @param {Vector2} [localPos=new Vector2]
1294
+ * @param {Number} [localAngle=0] */
692
1295
  addChild(child, localPos=vec2(), localAngle=0)
693
1296
  {
694
1297
  ASSERT(!child.parent && !this.children.includes(child));
@@ -697,6 +1300,9 @@ class EngineObject
697
1300
  child.localPos = localPos.copy();
698
1301
  child.localAngle = localAngle;
699
1302
  }
1303
+
1304
+ /** Removes a child from this one
1305
+ * @param {EngineObject} child */
700
1306
  removeChild(child)
701
1307
  {
702
1308
  ASSERT(child.parent == this && this.children.includes(child));
@@ -704,7 +1310,11 @@ class EngineObject
704
1310
  child.parent = 0;
705
1311
  }
706
1312
 
707
- setCollision(collideSolidObjects, isSolid, collideTiles=1)
1313
+ /** Set how this object collides
1314
+ * @param {boolean} [collideSolidObjects=0] - Does it collide with solid objects
1315
+ * @param {boolean} [isSolid=0] - Does it collide with and block other objects (expensive in large numbers)
1316
+ * @param {boolean} [collideTiles=1] - Does it collide with the tile collision */
1317
+ setCollision(collideSolidObjects=0, isSolid=0, collideTiles=1)
708
1318
  {
709
1319
  ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
710
1320
 
@@ -725,62 +1335,81 @@ class EngineObject
725
1335
  this.collideTiles = collideTiles;
726
1336
  }
727
1337
  }
728
-
729
- function destroyAllObjects()
730
- {
731
- // remove all objects that are not persistent or are descendants of something persistent
732
- for (const o of engineObjects)
733
- o.persistent || o.parent || o.destroy();
734
- engineObjects = engineObjects.filter(o=>!o.destroyed);
735
- }
736
-
737
- function forEachObject(pos, size, callbackFunction, objects=engineObjects)
738
- {
739
- if (!pos)
740
- {
741
- // all objects
742
- for (const o of objects)
743
- callbackFunction(o);
744
- }
745
- else if (size.x != undefined)
746
- {
747
- // aabb test
748
- for (const o of objects)
749
- isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
750
- }
751
- else
752
- {
753
- // circle test
754
- const sizeSquared = size*size;
755
- for (const o of objects)
756
- pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
757
- }
758
- }
759
- /*
760
- LittleJS Drawing System
761
-
762
- - Super fast tile sheet rendering
763
- - Utility functions for webgl
764
- */
1338
+ /**
1339
+ * LittleJS Drawing System
1340
+ * <br> - Hybrid with both Canvas2D and WebGL available
1341
+ * <br> - Super fast tile sheet rendering with WebGL
1342
+ * <br> - Can apply rotation, mirror, color and additive color
1343
+ * <br> - Many useful utility functions
1344
+ * @namespace Draw
1345
+ */
765
1346
 
766
1347
  'use strict';
767
1348
 
768
- // convert between screen and world coordinates
1349
+ /** Tile sheet for batch rendering system
1350
+ * @type {Image}
1351
+ * @memberof Draw */
1352
+ const tileImage = new Image();
1353
+
1354
+ /** The primary 2D canvas visible to the user
1355
+ * @type {HTMLCanvasElement}
1356
+ * @memberof Draw */
1357
+ let mainCanvas;
1358
+
1359
+ /** 2d context for mainCanvas
1360
+ * @type {CanvasRenderingContext2D}
1361
+ * @memberof Draw */
1362
+ let mainContext;
1363
+
1364
+ /** A canvas that appears on top of everything the same size as mainCanvas
1365
+ * @type {HTMLCanvasElement}
1366
+ * @memberof Draw */
1367
+ let overlayCanvas;
1368
+
1369
+ /** 2d context for overlayCanvas
1370
+ * @type {CanvasRenderingContext2D}
1371
+ * @memberof Draw */
1372
+ let overlayContext;
1373
+
1374
+ /** The size of the main canvas (and other secondary canvases)
1375
+ * @type {Vector2}
1376
+ * @memberof Draw */
1377
+ let mainCanvasSize = vec2();
1378
+
1379
+ /** Convert from screen to world space coordinates
1380
+ * @param {Vector2} screenPos
1381
+ * @return {Vector2}
1382
+ * @memberof Draw */
769
1383
  const screenToWorld = (screenPos)=>
770
1384
  screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
1385
+
1386
+ /** Convert from world to screen space coordinates
1387
+ * @param {Vector2} worldPos
1388
+ * @return {Vector2}
1389
+ * @memberof Draw */
771
1390
  const worldToScreen = (worldPos)=>
772
1391
  worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
773
1392
 
774
- // draw textured tile centered on pos
775
- function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, color=new Color, angle=0, mirror,
776
- additiveColor=new Color(0,0,0,0))
1393
+ /** Draw textured tile centered in world space, with color applied if using WebGL
1394
+ * @param {Vector2} pos - Center of the tile in world space
1395
+ * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
1396
+ * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1397
+ * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1398
+ * @param {Color} [color=new Color(1,1,1)] - Color to modulate with
1399
+ * @param {Number} [angle=0] - Angle to rotate by
1400
+ * @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
1401
+ * @param {Color} [additiveColor=new Color(0,0,0,0)] - Additive color to be applied
1402
+ * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
1403
+ * @memberof Draw */
1404
+ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror,
1405
+ additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
777
1406
  {
778
1407
  showWatermark && ++drawCount;
779
- if (glEnable)
1408
+ if (glEnable && useWebGL)
780
1409
  {
781
- if (tileIndex < 0)
1410
+ if (tileIndex < 0 || !tileImage.width)
782
1411
  {
783
- // if negative tile index, force untextured
1412
+ // if negative tile index or image not found, force untextured
784
1413
  glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
785
1414
  }
786
1415
  else
@@ -827,33 +1456,68 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, col
827
1456
  }
828
1457
  }
829
1458
 
830
- // draw a colored untextured rect centered on pos
831
- function drawRect(pos, size, color, angle)
1459
+ /** Draw colored rect centered on pos
1460
+ * @param {Vector2} pos
1461
+ * @param {Vector2} [size=new Vector2(1,1)]
1462
+ * @param {Color} [color=new Color(1,1,1)]
1463
+ * @param {Number} [angle=0]
1464
+ * @param {Boolean} [useWebGL=glEnable]
1465
+ * @memberof Draw */
1466
+ function drawRect(pos, size, color, angle, useWebGL)
832
1467
  {
833
- drawTile(pos, size, -1, defaultTileSize, color, angle);
1468
+ drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
834
1469
  }
835
1470
 
836
- // draw textured tile centered on pos in screen space
837
- function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor)
1471
+ /** Draw textured tile centered on pos in screen space
1472
+ * @param {Vector2} pos - Center of the tile
1473
+ * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile
1474
+ * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1475
+ * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1476
+ * @param {Color} [color=new Color]
1477
+ * @param {Number} [angle=0]
1478
+ * @param {Boolean} [mirror=0]
1479
+ * @param {Color} [additiveColor=new Color(0,0,0,0)]
1480
+ * @param {Boolean} [useWebGL=glEnable]
1481
+ * @memberof Draw */
1482
+ function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL)
838
1483
  {
839
- drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor);
1484
+ drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL);
840
1485
  }
841
1486
 
842
- // draw a colored untextured rect in screen space
843
- function drawRectScreenSpace(pos, size, color, angle)
1487
+ /** Draw colored rectangle in screen space
1488
+ * @param {Vector2} pos
1489
+ * @param {Vector2} [size=new Vector2(1,1)]
1490
+ * @param {Color} [color=new Color(1,1,1)]
1491
+ * @param {Number} [angle=0]
1492
+ * @param {Boolean} [useWebGL=glEnable]
1493
+ * @memberof Draw */
1494
+ function drawRectScreenSpace(pos, size, color, angle, useWebGL)
844
1495
  {
845
- drawTileScreenSpace(pos, size, -1, defaultTileSize, color, angle);
1496
+ drawTileSrceenSpace(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
846
1497
  }
847
1498
 
848
- // draw a colored line between two points
849
- function drawLine(posA, posB, thickness=.1, color)
1499
+ /** Draw colored line between two points
1500
+ * @param {Vector2} posA
1501
+ * @param {Vector2} posB
1502
+ * @param {Number} [thickness=.1]
1503
+ * @param {Color} [color=new Color(1,1,1)]
1504
+ * @param {Boolean} [useWebGL=glEnable]
1505
+ * @memberof Draw */
1506
+ function drawLine(posA, posB, thickness=.1, color, useWebGL)
850
1507
  {
851
1508
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
852
1509
  const size = vec2(thickness, halfDelta.length()*2);
853
- drawRect(posA.add(halfDelta), size, color, halfDelta.angle());
1510
+ drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), 0, 0, useWebGL);
854
1511
  }
855
1512
 
856
- // draw directly to the 2d canvas in world space (bipass webgl)
1513
+ /** Draw directly to a 2d canvas context in world space
1514
+ * @param {Vector2} pos
1515
+ * @param {Vector2} size
1516
+ * @param {Number} angle
1517
+ * @param {Boolean} mirror
1518
+ * @param {Function} drawFunction
1519
+ * @param {CanvasRenderingContext2D} [context=mainContext]
1520
+ * @memberof Draw */
857
1521
  function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainContext)
858
1522
  {
859
1523
  // create canvas transform from world space to screen space
@@ -867,8 +1531,16 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
867
1531
  context.restore();
868
1532
  }
869
1533
 
870
- // draw text on overlay canvas in world space
871
- function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=defaultFont)
1534
+ /** Draw text on overlay canvas in world space
1535
+ * @param {String} text
1536
+ * @param {Vector2} pos
1537
+ * @param {Number} [size=1]
1538
+ * @param {Color} [color=new Color(1,1,1)]
1539
+ * @param {Number} [lineWidth=0]
1540
+ * @param {Color} [lineColor=new Color(0,0,0)]
1541
+ * @param {String} [textAlign='center']
1542
+ * @memberof Draw */
1543
+ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
872
1544
  {
873
1545
  pos = worldToScreen(pos);
874
1546
  overlayContext.font = size*cameraScale + 'px '+ font;
@@ -884,16 +1556,28 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
884
1556
  overlayContext.fillText(text, pos.x, pos.y);
885
1557
  }
886
1558
 
887
- // enable additive or regular blend mode
888
- function setBlendMode(additive)
1559
+ /** Enable normal or additive blend mode
1560
+ * @param {Boolean} [additive=0]
1561
+ * @param {Boolean} [useWebGL=glEnable]
1562
+ * @memberof Draw */
1563
+ function setBlendMode(additive, useWebGL=glEnable)
889
1564
  {
890
- glEnable ? glSetBlendMode(additive) : mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1565
+ if (glEnable && useWebGL)
1566
+ glSetBlendMode(additive);
1567
+ else
1568
+ mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
891
1569
  }
892
1570
 
893
1571
  ///////////////////////////////////////////////////////////////////////////////
894
- // fullscreen mode
1572
+ // Fullscreen mode
895
1573
 
1574
+ /** Returns true if fullscreen mode is active
1575
+ * @return {Boolean}
1576
+ * @memberof Draw */
896
1577
  const isFullscreen =()=> document.fullscreenElement;
1578
+
1579
+ /** Toggle fullsceen mode
1580
+ * @memberof Draw */
897
1581
  function toggleFullscreen()
898
1582
  {
899
1583
  if (isFullscreen())
@@ -911,39 +1595,107 @@ function toggleFullscreen()
911
1595
  document.body.mozRequestFullScreen();
912
1596
  }
913
1597
  }
914
- /*
915
- LittleJS Input System
916
- - Tracks key down, pressed, and released
917
- - Also tracks mouse buttons, position, and wheel
918
- - Supports multiple gamepads
919
- */
1598
+ /**
1599
+ * LittleJS Input System
1600
+ * <br> - Tracks key down, pressed, and released
1601
+ * <br> - Also tracks mouse buttons, position, and wheel
1602
+ * <br> - Supports multiple gamepads
1603
+ * @namespace Input
1604
+ */
920
1605
 
921
1606
  'use strict';
922
1607
 
923
- // input for all devices including keyboard, mouse, and gamepad
924
- let hadInput = 0;
925
- const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
926
- const keyWasPressed = (key, device=0)=> inputData[device] && inputData[device][key] & 2 ? 1 : 0;
1608
+ /** Returns true if device key is down
1609
+ * @param {Number} key
1610
+ * @param {Number} [device=0]
1611
+ * @return {Boolean}
1612
+ * @memberof Input */
1613
+ const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
1614
+
1615
+ /** Returns true if device key was pressed this frame
1616
+ * @param {Number} key
1617
+ * @param {Number} [device=0]
1618
+ * @return {Boolean}
1619
+ * @memberof Input */
1620
+ const keyWasPressed = (key, device=0)=> inputData[device] && inputData[device][key] & 2 ? 1 : 0;
1621
+
1622
+ /** Returns true if device key was released this frame
1623
+ * @param {Number} key
1624
+ * @param {Number} [device=0]
1625
+ * @return {Boolean}
1626
+ * @memberof Input */
927
1627
  const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][key] & 4 ? 1 : 0;
928
- const clearInput = ()=> inputData[0] = [];
929
1628
 
930
- // mouse input
931
- const mouseIsDown = keyIsDown;
932
- const mouseWasPressed = keyWasPressed;
1629
+ /** Clears all input
1630
+ * @memberof Input */
1631
+ const clearInput = ()=> inputData[0] = [];
1632
+
1633
+ /** Returns true if mouse button is down
1634
+ * @param {Number} button
1635
+ * @return {Boolean}
1636
+ * @memberof Input */
1637
+ const mouseIsDown = keyIsDown;
1638
+
1639
+ /** Returns true if mouse button was pressed
1640
+ * @param {Number} button
1641
+ * @return {Boolean}
1642
+ * @memberof Input */
1643
+ const mouseWasPressed = keyWasPressed;
1644
+
1645
+ /** Returns true if mouse button was released
1646
+ * @param {Number} button
1647
+ * @return {Boolean}
1648
+ * @memberof Input */
933
1649
  const mouseWasReleased = keyWasReleased;
934
- let mousePos = vec2();
935
- let mousePosScreen = vec2();
936
- let mouseWheel = 0;
937
-
938
- // gamepad input
939
- let usingGamepad = 0;
940
- const gamepadIsDown = (button, gamepad=0)=> keyIsDown (button, gamepad+1);
941
- const gamepadWasPressed = (button, gamepad=0)=> keyWasPressed (button, gamepad+1);
1650
+
1651
+ /** Mouse pos in world space
1652
+ * @type {Vector2}
1653
+ * @memberof Input */
1654
+ let mousePos = vec2();
1655
+
1656
+ /** Mouse pos in screen space
1657
+ * @type {Vector2}
1658
+ * @memberof Input */
1659
+ let mousePosScreen = vec2();
1660
+
1661
+ /** Mouse wheel delta this frame
1662
+ * @memberof Input */
1663
+ let mouseWheel = 0;
1664
+
1665
+ /** Returns true if user is using gamepad (has more recently pressed a gamepad button)
1666
+ * @memberof Input */
1667
+ let isUsingGamepad = 0;
1668
+
1669
+ /** Returns true if gamepad button is down
1670
+ * @param {Number} button
1671
+ * @param {Number} [gamepad=0]
1672
+ * @return {Boolean}
1673
+ * @memberof Input */
1674
+ const gamepadIsDown = (button, gamepad=0)=> keyIsDown(button, gamepad+1);
1675
+
1676
+ /** Returns true if gamepad button was pressed
1677
+ * @param {Number} button
1678
+ * @param {Number} [gamepad=0]
1679
+ * @return {Boolean}
1680
+ * @memberof Input */
1681
+ const gamepadWasPressed = (button, gamepad=0)=> keyWasPressed(button, gamepad+1);
1682
+
1683
+ /** Returns true if gamepad button was released
1684
+ * @param {Number} button
1685
+ * @param {Number} [gamepad=0]
1686
+ * @return {Boolean}
1687
+ * @memberof Input */
942
1688
  const gamepadWasReleased = (button, gamepad=0)=> keyWasReleased(button, gamepad+1);
943
- const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2();
1689
+
1690
+ /** Returns gamepad stick value
1691
+ * @param {Number} stick
1692
+ * @param {Number} [gamepad=0]
1693
+ * @return {Vector2}
1694
+ * @memberof Input */
1695
+ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2();
944
1696
 
945
1697
  ///////////////////////////////////////////////////////////////////////////////
946
- // input update called by engine
1698
+ // Input update called by engine
947
1699
 
948
1700
  const inputData = [[]];
949
1701
 
@@ -969,23 +1721,25 @@ function inputUpdatePost()
969
1721
  }
970
1722
 
971
1723
  ///////////////////////////////////////////////////////////////////////////////
972
- // keyboard event handlers
1724
+ // Keyboard event handlers
1725
+
973
1726
  onkeydown = e=>
974
1727
  {
975
1728
  if (debug && e.target != document.body) return;
976
- e.repeat || (inputData[usingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
977
- hadInput = 1;
1729
+ e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
1730
+ debug || e.preventDefault();
978
1731
  }
979
1732
  onkeyup = e=>
980
1733
  {
981
1734
  if (debug && e.target != document.body) return;
982
1735
  inputData[0][remapKeyCode(e.keyCode)] = 4;
983
1736
  }
984
- const remapKeyCode = c=> copyWASDToDpad ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
1737
+ const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
985
1738
 
986
1739
  ///////////////////////////////////////////////////////////////////////////////
987
- // mouse event handlers
988
- onmousedown = e=> (inputData[usingGamepad = 0][e.button] = 3, hadInput = 1, onmousemove(e));
1740
+ // Mouse event handlers
1741
+
1742
+ onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
989
1743
  onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
990
1744
  onmousemove = e=>
991
1745
  {
@@ -999,7 +1753,7 @@ onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
999
1753
  oncontextmenu = e=> !1; // prevent right click menu
1000
1754
 
1001
1755
  ///////////////////////////////////////////////////////////////////////////////
1002
- // gamepad input
1756
+ // Gamepad input
1003
1757
 
1004
1758
  const stickData = [];
1005
1759
  function gamepadsUpdate()
@@ -1033,10 +1787,10 @@ function gamepadsUpdate()
1033
1787
  {
1034
1788
  const button = gamepad.buttons[j];
1035
1789
  data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
1036
- usingGamepad |= !i && button.pressed;
1790
+ isUsingGamepad |= !i && button.pressed;
1037
1791
  }
1038
1792
 
1039
- if (copyGamepadDirectionToStick)
1793
+ if (gamepadDirectionEmulateStick)
1040
1794
  {
1041
1795
  // copy dpad to left analog stick when pressed
1042
1796
  const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
@@ -1048,12 +1802,16 @@ function gamepadsUpdate()
1048
1802
  }
1049
1803
 
1050
1804
  ///////////////////////////////////////////////////////////////////////////////
1051
- // touch input
1052
- const isTouchDevice = touchInputEnable && window.ontouchstart !== undefined;
1805
+ // Touch input
1806
+
1807
+ /** True if a touch device has been detected
1808
+ * @const {boolean}
1809
+ * @memberof Input */
1810
+ const isTouchDevice = inputTouchEnable && window.ontouchstart !== undefined;
1053
1811
  if (isTouchDevice)
1054
1812
  {
1055
1813
  // handle all touch events the same way
1056
- let wasTouching;
1814
+ let wasTouching, hadTouchInput;
1057
1815
  ontouchstart = ontouchmove = ontouchend = e=>
1058
1816
  {
1059
1817
  e.button = 0; // all touches are left click
@@ -1062,7 +1820,7 @@ if (isTouchDevice)
1062
1820
  const touching = e.touches.length;
1063
1821
  if (touching)
1064
1822
  {
1065
- hadInput || zzfx(0) ; // fix mobile audio, force it to play a sound the first time
1823
+ hadTouchInput || zzfx(0, hadTouchInput=1) ; // fix mobile audio, force it to play a sound the first time
1066
1824
 
1067
1825
  // set event pos and pass it along
1068
1826
  e.x = e.touches[0].clientX;
@@ -1079,27 +1837,45 @@ if (isTouchDevice)
1079
1837
  return !e.cancelable;
1080
1838
  }
1081
1839
  }
1082
- /*
1083
- LittleJS Audio System
1084
- - ZzFX Sound Effects and ZzFXM Music
1085
- - Caches sounds and music for fast playback
1086
- - Can attenuate and apply stereo panning to sounds
1087
- - Ability to play mp3, ogg, and wave files
1088
- - Speech Synthesis wrapper functions
1089
- */
1840
+ /**
1841
+ * LittleJS Audio System
1842
+ * <br> - ZzFX Sound Effects and ZzFXM Music
1843
+ * <br> - Caches sounds and music for fast playback
1844
+ * <br> - Can attenuate and apply stereo panning to sounds
1845
+ * <br> - Ability to play mp3, ogg, and wave files
1846
+ * <br> - Speech synthesis wrapper functions
1847
+ * @namespace Audio
1848
+ */
1090
1849
 
1091
1850
  'use strict';
1092
1851
 
1852
+ /**
1853
+ * Sound Object - Stores a zzfx sound for later use and can be played positionally
1854
+ * @example
1855
+ * // create a sound
1856
+ * const sound_example = new Sound([.5,.5]);
1857
+ *
1858
+ * // play the sound
1859
+ * sound_example.play();
1860
+ */
1093
1861
  class Sound
1094
1862
  {
1095
- constructor(zzfxSound, range=defaultSoundRange, taper=defaultSoundTaper)
1863
+ /** Create a sound object and cache the zzfx samples for later use
1864
+ * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
1865
+ * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
1866
+ * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
1867
+ */
1868
+ constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
1096
1869
  {
1097
1870
  if (!soundEnable) return;
1098
1871
 
1872
+ /** @property {Number} - World space max range of sound, will not play if camera is farther away */
1099
1873
  this.range = range;
1874
+
1875
+ /** @property {Number} - At what percentage of range should it start tapering off */
1100
1876
  this.taper = taper;
1101
1877
 
1102
- // get randomness from sound to apply when played
1878
+ // get randomness from sound parameters
1103
1879
  this.randomness = zzfxSound[1] || 0;
1104
1880
  zzfxSound[1] = 0;
1105
1881
 
@@ -1107,7 +1883,14 @@ class Sound
1107
1883
  this.cachedSamples = zzfxG(...zzfxSound);
1108
1884
  }
1109
1885
 
1110
- play(pos, volumeScale=1, pitchScale=1)
1886
+ /** Play the sound
1887
+ * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
1888
+ * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
1889
+ * @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
1890
+ * @param {Number} [randomnessScale=1] - How much to scale randomness
1891
+ * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
1892
+ */
1893
+ play(pos, volume=1, pitch=1, randomnessScale=1)
1111
1894
  {
1112
1895
  if (!soundEnable) return;
1113
1896
 
@@ -1123,7 +1906,7 @@ class Sound
1123
1906
  return; // out of range
1124
1907
 
1125
1908
  // attenuate volume by distance
1126
- volumeScale *= percent(lengthSquared**.5, range*this.taper, range);
1909
+ volume *= percent(lengthSquared**.5, range*this.taper, range);
1127
1910
  }
1128
1911
 
1129
1912
  // get pan from screen space coords
@@ -1131,13 +1914,57 @@ class Sound
1131
1914
  }
1132
1915
 
1133
1916
  // play the sound
1134
- const playbackRate = pitchScale + pitchScale * this.randomness*rand(-1,1);
1135
- return playSamples([this.cachedSamples], volumeScale, playbackRate, pan);
1917
+ const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
1918
+ return playSamples([this.cachedSamples], volume, playbackRate, pan);
1919
+ }
1920
+
1921
+ /** Play the sound as a note with a semitone offset
1922
+ * @param {Number} semitoneOffset - How many semitones to offset pitch
1923
+ * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
1924
+ * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
1925
+ * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
1926
+ */
1927
+ playNote(semitoneOffset, pos, volume=1)
1928
+ {
1929
+ if (!soundEnable) return;
1930
+
1931
+ return this.play(pos, volume, 2**(semitoneOffset/12), 0);
1136
1932
  }
1137
1933
  }
1138
1934
 
1935
+ /**
1936
+ * Music Object - Stores a zzfx music track for later use
1937
+ * @example
1938
+ * // create some music
1939
+ * const music_example = new Music(
1940
+ * [
1941
+ * [ // instruments
1942
+ * [,0,400] // simple note
1943
+ * ],
1944
+ * [ // patterns
1945
+ * [ // pattern 1
1946
+ * [ // channel 0
1947
+ * 0, -1, // instrument 0, left speaker
1948
+ * 1, 0, 9, 1 // channel notes
1949
+ * ],
1950
+ * [ // channel 1
1951
+ * 0, 1, // instrument 1, right speaker
1952
+ * 0, 12, 17, -1 // channel notes
1953
+ * ]
1954
+ * ],
1955
+ * ],
1956
+ * [0, 0, 0, 0], // sequence, play pattern 0 four times
1957
+ * 90 // BPM
1958
+ * ]);
1959
+ *
1960
+ * // play the music
1961
+ * music_example.play();
1962
+ */
1139
1963
  class Music
1140
1964
  {
1965
+ /** Create a music object and cache the zzfx music samples for later use
1966
+ * @param {Array} zzfxMusic - Array of zzfx music parameters
1967
+ */
1141
1968
  constructor(zzfxMusic)
1142
1969
  {
1143
1970
  if (!soundEnable) return;
@@ -1145,29 +1972,44 @@ class Music
1145
1972
  this.cachedSamples = zzfxM(...zzfxMusic);
1146
1973
  }
1147
1974
 
1148
- play(volumeScale = 1, loop = 1)
1975
+ /** Play the music
1976
+ * @param {Number} [volume=1] - How much to scale volume by
1977
+ * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
1978
+ * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
1979
+ */
1980
+ play(volume = 1, loop = 1)
1149
1981
  {
1150
1982
  if (!soundEnable) return;
1151
1983
 
1152
- return playSamples(this.cachedSamples, volumeScale, 1, 0, loop);
1984
+ return playSamples(this.cachedSamples, volume, 1, 0, loop);
1153
1985
  }
1154
1986
  }
1155
1987
 
1156
- ///////////////////////////////////////////////////////////////////////////////
1157
-
1158
- // play mp3 or wav audio from a local file or url
1159
- function playAudioFile(url, volumeScale=1, loop=1)
1988
+ /** Play an mp3 or wav audio from a local file or url
1989
+ * @param {String} url - Location of sound file to play
1990
+ * @param {Number} [volume=1] - How much to scale volume by
1991
+ * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
1992
+ * @return {HTMLAudioElement} - The audio element for this sound
1993
+ * @memberof Audio */
1994
+ function playAudioFile(url, volume=1, loop=1)
1160
1995
  {
1161
1996
  if (!soundEnable) return;
1162
1997
 
1163
1998
  const audio = new Audio(url);
1164
- audio.volume = audioVolume * volumeScale;
1999
+ audio.volume = soundVolume * volume;
1165
2000
  audio.loop = loop;
1166
2001
  audio.play();
1167
2002
  return audio;
1168
2003
  }
1169
2004
 
1170
- // speak text with passed in settings
2005
+ /** Speak text with passed in settings
2006
+ * @param {String} text - The text to speak
2007
+ * @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
2008
+ * @param {Number} [volume=1] - How much to scale volume by
2009
+ * @param {Number} [rate=1] - How quickly to speak
2010
+ * @param {Number} [pitch=1] - How much to change the pitch by
2011
+ * @return {SpeechSynthesisUtterance} - The utterance that was spoken
2012
+ * @memberof Audio */
1171
2013
  function speak(text, language='', volume=1, rate=1, pitch=1)
1172
2014
  {
1173
2015
  if (!soundEnable || !speechSynthesis) return;
@@ -1179,22 +2021,39 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
1179
2021
  // build utterance and speak
1180
2022
  const utterance = new SpeechSynthesisUtterance(text);
1181
2023
  utterance.lang = language;
1182
- utterance.volume = volume*audioVolume*3;
2024
+ utterance.volume = 2*volume*soundVolume;
1183
2025
  utterance.rate = rate;
1184
2026
  utterance.pitch = pitch;
1185
2027
  speechSynthesis.speak(utterance);
1186
2028
  return utterance;
1187
2029
  }
1188
2030
 
1189
- // stop all queued speech
2031
+ /** Stop all queued speech
2032
+ * @memberof Audio */
1190
2033
  const stopSpeech = ()=> speechSynthesis && speechSynthesis.cancel();
1191
2034
 
1192
- ///////////////////////////////////////////////////////////////////////////////
2035
+ /** Get frequency of a note on a musical scale
2036
+ * @param {Number} semitoneOffset - How many semitones away from the root note
2037
+ * @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0
2038
+ * @return {Number} - The frequency of the note
2039
+ * @memberof Audio */
2040
+ const getNoteFrequency = (semitoneOffset, rootFrequency=220)=> rootFrequency * 2**(semitoneOffset/12);
1193
2041
 
1194
- let audioContext; // audio context used by the engine
2042
+ ///////////////////////////////////////////////////////////////////////////////
1195
2043
 
1196
- // play cached samples with given settings
1197
- function playSamples(sampleChannels, volume=1, playbackRate=1, pan=0, loop=0)
2044
+ /** Audio context used by the engine
2045
+ * @memberof Audio */
2046
+ let audioContext;
2047
+
2048
+ /** Play cached audio samples with given settings
2049
+ * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
2050
+ * @param {Number} [volume=1] - How much to scale volume by
2051
+ * @param {Number} [rate=1] - The playback rate to use
2052
+ * @param {Number} [pan=0] - How much to apply stereo panning
2053
+ * @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end
2054
+ * @return {AudioBufferSourceNode} - The audio node of the sound played
2055
+ * @memberof Audio */
2056
+ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
1198
2057
  {
1199
2058
  if (!soundEnable) return;
1200
2059
 
@@ -1209,13 +2068,13 @@ function playSamples(sampleChannels, volume=1, playbackRate=1, pan=0, loop=0)
1209
2068
  // copy samples to buffer and setup source
1210
2069
  sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
1211
2070
  source.buffer = buffer;
1212
- source.playbackRate.value = playbackRate;
2071
+ source.playbackRate.value = rate;
1213
2072
  source.loop = loop;
1214
2073
 
1215
2074
  // create pan and gain nodes
1216
2075
  source
1217
2076
  .connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, 1, -1)}))
1218
- .connect(new GainNode(audioContext, {'gain':audioVolume*volume}))
2077
+ .connect(new GainNode(audioContext, {'gain':soundVolume*volume}))
1219
2078
  .connect(audioContext.destination);
1220
2079
 
1221
2080
  // play and return sound
@@ -1226,10 +2085,20 @@ function playSamples(sampleChannels, volume=1, playbackRate=1, pan=0, loop=0)
1226
2085
  ///////////////////////////////////////////////////////////////////////////////
1227
2086
  // ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8 by Frank Force
1228
2087
 
1229
- const zzfxR = 44100; // sample rate
1230
- const zzfx = (...z) => playSamples([zzfxG(...z)]); // generate and play sound
2088
+ /** Generate and play a ZzFX sound
2089
+ * @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
2090
+ * @return {Array} - Array of audio samples
2091
+ * @memberof Audio */
2092
+ const zzfx = (...zzfxSound) => playSamples([zzfxG(...zzfxSound)]);
2093
+
2094
+ /** Sample rate used for all ZzFX sounds
2095
+ * @default 44100
2096
+ * @memberof Audio */
2097
+ const zzfxR = 44100;
1231
2098
 
1232
- function zzfxG // generate samples
2099
+ /** Generate samples for a ZzFX sound
2100
+ * @memberof Audio */
2101
+ function zzfxG
1233
2102
  (
1234
2103
  // parameters
1235
2104
  volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
@@ -1273,7 +2142,7 @@ function zzfxG // generate samples
1273
2142
  1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
1274
2143
  : 1) *
1275
2144
  sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
1276
- volume * audioVolume * ( // envelope
2145
+ volume * soundVolume * ( // envelope
1277
2146
  i < attack ? i/attack : // attack
1278
2147
  i < attack + decay ? // decay
1279
2148
  1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
@@ -1314,6 +2183,13 @@ function zzfxG // generate samples
1314
2183
  ///////////////////////////////////////////////////////////////////////////////
1315
2184
  // ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
1316
2185
 
2186
+ /** Generate samples for a ZzFM song with given parameters
2187
+ * @param {Array} instruments - Array of ZzFX sound paramaters
2188
+ * @param {Array} patterns - Array of pattern data
2189
+ * @param {Array} sequence - Array of pattern indexes
2190
+ * @param {Number} [BPM=125] - Playback speed of the song in BPM
2191
+ * @returns {Array} - Left and right channel sample data
2192
+ * @memberof Audio */
1317
2193
  function zzfxM(instruments, patterns, sequence, BPM = 125)
1318
2194
  {
1319
2195
  let instrumentParameters;
@@ -1411,25 +2287,31 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
1411
2287
 
1412
2288
  return [leftChannelBuffer, rightChannelBuffer];
1413
2289
  }
1414
- /*
1415
- LittleJS Tile Layer System
1416
- - Caches arrays of tiles to offscreen canvas for fast rendering
1417
- - Unlimted numbers of layers, allocates canvases as needed
1418
- - Interfaces with EngineObject for collision
1419
- - Collision layer is separate from visible layers
1420
- - Tile layers can be drawn to using their context with canvas2d
1421
- - It is recommended to have a visible layer that matches the collision
1422
- */
2290
+ /**
2291
+ * LittleJS Tile Layer System
2292
+ * <br> - Caches arrays of tiles to offscreen canvas for fast rendering
2293
+ * <br> - Unlimted numbers of layers, allocates canvases as needed
2294
+ * <br> - Interfaces with EngineObject for collision
2295
+ * <br> - Collision layer is separate from visible layers
2296
+ * <br> - Tile layers can be drawn to using their context with canvas2d
2297
+ * <br> - It is recommended to have a visible layer that matches the collision
2298
+ * @namespace TileCollision
2299
+ */
1423
2300
 
1424
2301
  'use strict';
1425
2302
 
1426
- ///////////////////////////////////////////////////////////////////////////////
1427
- // Tile Collision
1428
-
2303
+ /** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
2304
+ * @memberof TileCollision */
1429
2305
  let tileCollision = [];
2306
+
2307
+ /** Size of the tile collision layer
2308
+ * @type {Vector2}
2309
+ * @memberof TileCollision */
1430
2310
  let tileCollisionSize = vec2();
1431
- const tileLayerCanvasCache = [];
1432
2311
 
2312
+ /** Clear and initialize tile collision
2313
+ * @param {Vector2} size
2314
+ * @memberof TileCollision */
1433
2315
  function initTileCollision(size)
1434
2316
  {
1435
2317
  tileCollisionSize = size;
@@ -1438,21 +2320,34 @@ function initTileCollision(size)
1438
2320
  tileCollision[i] = 0;
1439
2321
  }
1440
2322
 
1441
- // set and get collision data
2323
+ /** Set tile collision data
2324
+ * @param {Vector2} pos
2325
+ * @param {Number} [data=0]
2326
+ * @memberof TileCollision */
1442
2327
  const setTileCollisionData = (pos, data=0)=>
1443
2328
  pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
2329
+
2330
+ /** Get tile collision data
2331
+ * @param {Vector2} pos
2332
+ * @return {Number}
2333
+ * @memberof TileCollision */
1444
2334
  const getTileCollisionData = (pos)=>
1445
2335
  pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
1446
2336
 
1447
- // check if there is collision in a given area
2337
+ /** Check if collision with another object should occur
2338
+ * @param {Vector2} pos
2339
+ * @param {Vector2} [size=new Vector2(1,1)]
2340
+ * @param {EngineObject} [object]
2341
+ * @return {Boolean}
2342
+ * @memberof TileCollision */
1448
2343
  function tileCollisionTest(pos, size=vec2(), object)
1449
2344
  {
1450
2345
  const minX = max(Math.floor(pos.x - size.x/2), 0);
1451
2346
  const minY = max(Math.floor(pos.y - size.y/2), 0);
1452
- const maxX = min(Math.floor(pos.x + size.x/2), tileCollisionSize.x-1);
1453
- const maxY = min(Math.floor(pos.y + size.y/2), tileCollisionSize.y-1);
1454
- for (let y = minY; y <= maxY; ++y)
1455
- for (let x = minX; x <= maxX; ++x)
2347
+ const maxX = min(pos.x + size.x/2, tileCollisionSize.x-1);
2348
+ const maxY = min(pos.y + size.y/2, tileCollisionSize.y-1);
2349
+ for (let y = minY; y < maxY; ++y)
2350
+ for (let x = minX; x < maxX; ++x)
1456
2351
  {
1457
2352
  const tileData = tileCollision[y*tileCollisionSize.x+x];
1458
2353
  if (tileData && (!object || object.collideWithTile(tileData, new Vector2(x, y))))
@@ -1460,13 +2355,18 @@ function tileCollisionTest(pos, size=vec2(), object)
1460
2355
  }
1461
2356
  }
1462
2357
 
1463
- // return the center of tile if any that is hit (this does not return the exact hit point)
1464
- // todo: a way to get the exact hit point, it must still register as inside the hit tile
2358
+ /** Return the center of tile if any that is hit (this does not return the exact hit point)
2359
+ * @param {Vector2} posStart
2360
+ * @param {Vector2} posEnd
2361
+ * @param {EngineObject} [object]
2362
+ * @return {Vector2}
2363
+ * @memberof TileCollision */
1465
2364
  function tileCollisionRaycast(posStart, posEnd, object)
1466
2365
  {
1467
2366
  // test if a ray collides with tiles from start to end
1468
- posStart = posStart.int();
1469
- posEnd = posEnd.int();
2367
+ // todo: a way to get the exact hit point, it must still register as inside the hit tile
2368
+ posStart = posStart.floor();
2369
+ posEnd = posEnd.floor();
1470
2370
  const posDelta = posEnd.subtract(posStart);
1471
2371
  const dx = abs(posDelta.x), dy = -abs(posDelta.y);
1472
2372
  const sx = sign(posDelta.x), sy = sign(posDelta.y);
@@ -1494,32 +2394,74 @@ function tileCollisionRaycast(posStart, posEnd, object)
1494
2394
  ///////////////////////////////////////////////////////////////////////////////
1495
2395
  // Tile Layer Rendering System
1496
2396
 
2397
+ // Reuse canvas autmatically when destroyed
2398
+ const tileLayerCanvasCache = [];
2399
+
2400
+ /**
2401
+ * Tile layer data object stores info about how to render a tile
2402
+ * @example
2403
+ * // create tile layer data with tile index 0 and random orientation and color
2404
+ * const tileIndex = 0;
2405
+ * const direction = randInt(4)
2406
+ * const mirror = randInt(2);
2407
+ * const color = randColor();
2408
+ * const data = new TileLayerData(tileIndex, direction, mirror, color);
2409
+ */
1497
2410
  class TileLayerData
1498
2411
  {
1499
- constructor(tile=-1, direction=0, mirror=0, color=new Color)
2412
+ /** Create a tile layer data object, one for each tile in a TileLayer
2413
+ * @param {Number} [tile] - The tile to use, untextured if undefined
2414
+ * @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
2415
+ * @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
2416
+ * @param {Color} [color=new Color(1,1,1)] - Color of the tile */
2417
+ constructor(tile, direction=0, mirror=0, color=new Color)
1500
2418
  {
2419
+ /** @property {Number} - The tile to use, untextured if undefined */
1501
2420
  this.tile = tile;
2421
+ /** @property {Number} - Integer direction of tile, in 90 degree increments */
1502
2422
  this.direction = direction;
2423
+ /** @property {Boolean} - If the tile should be mirrored along the x axis */
1503
2424
  this.mirror = mirror;
2425
+ /** @property {Color} - Color of the tile */
1504
2426
  this.color = color;
1505
2427
  }
2428
+
2429
+ /** Set this tile to clear, it will not be rendered */
1506
2430
  clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
1507
2431
  }
1508
2432
 
2433
+ /**
2434
+ * Tile layer object - cached rendering system for tile layers
2435
+ * <br> - Each Tile layer is rendered to an off screen canvas
2436
+ * <br> - Tile layers are not rendered using WebGL to allow modifications at run time
2437
+ * <br> - Tile layers are sorted
2438
+ * @extends EngineObject
2439
+ * @example
2440
+ * // create tile collision and visible tile layer
2441
+ * initTileCollision(vec2(200,100));
2442
+ * const tileLayer = new TileLayer();
2443
+ */
1509
2444
  class TileLayer extends EngineObject
1510
2445
  {
1511
- constructor(pos, size, scale=vec2(1), layer=0)
2446
+ /** Create a tile layer object
2447
+ * @param {Vector2} [position=new Vector2(0,0)] - World space position
2448
+ * @param {Vector2} [size=objectDefaultSize] - World space size
2449
+ * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
2450
+ * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
2451
+ * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
2452
+ */
2453
+ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
1512
2454
  {
1513
- super(pos, size);
2455
+ super(pos, size, -1, tileSize, 0, undefined, renderOrder);
1514
2456
 
1515
- // create new canvas if necessary
2457
+ /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
1516
2458
  this.canvas = tileLayerCanvasCache.length ? tileLayerCanvasCache.pop() : document.createElement('canvas');
2459
+ /** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
1517
2460
  this.context = this.canvas.getContext('2d');
2461
+ /** @property {Vector2} - How much to scale this layer when rendered */
1518
2462
  this.scale = scale;
1519
- this.tileSize = defaultTileSize.copy();
1520
- this.layer = layer;
1521
- this.renderOrder = layer;
1522
- this.flushGLBeforeRender = 1;
2463
+ /** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
2464
+ this.isOverlay;
1523
2465
 
1524
2466
  // init tile data
1525
2467
  this.data = [];
@@ -1527,6 +2469,7 @@ class TileLayer extends EngineObject
1527
2469
  this.data.push(new TileLayerData());
1528
2470
  }
1529
2471
 
2472
+ /** Destroy this tile layer */
1530
2473
  destroy()
1531
2474
  {
1532
2475
  // add canvas back to the cache
@@ -1534,6 +2477,10 @@ class TileLayer extends EngineObject
1534
2477
  super.destroy();
1535
2478
  }
1536
2479
 
2480
+ /** Set data at a given position in the array
2481
+ * @param {Vector2} position - Local position in array
2482
+ * @param {TileLayerData} data - Data to set
2483
+ * @param {Boolean} [redraw=0] - Force the tile to redraw if true */
1537
2484
  setData(layerPos, data, redraw)
1538
2485
  {
1539
2486
  if (layerPos.arrayCheck(this.size))
@@ -1543,38 +2490,46 @@ class TileLayer extends EngineObject
1543
2490
  }
1544
2491
  }
1545
2492
 
2493
+ /** Get data at a given position in the array
2494
+ * @param {Vector2} layerPos - Local position in array
2495
+ * @return {TileLayerData} */
1546
2496
  getData(layerPos)
1547
2497
  { return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
1548
2498
 
1549
- update() {} // tile layers are not updated
2499
+ // Tile layers are not updated
2500
+ update() {}
2501
+
2502
+ // Render the tile layer, called automatically by the engine
1550
2503
  render()
1551
2504
  {
1552
2505
  ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
1553
2506
 
1554
- // flush and copy gl canvas because tile canvas does not use gl
1555
- this.flushGLBeforeRender && glEnable && glCopyToContext(mainContext);
2507
+ // flush and copy gl canvas because tile canvas does not use webgl
2508
+ glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
1556
2509
 
1557
2510
  // draw the entire cached level onto the main canvas
1558
2511
  const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
1559
- mainContext.drawImage
2512
+ (this.isOverlay ? overlayContext : mainContext).drawImage
1560
2513
  (
1561
2514
  this.canvas, pos.x, pos.y,
1562
2515
  cameraScale*this.size.x*this.scale.x, cameraScale*this.size.y*this.scale.y
1563
2516
  );
1564
2517
  }
1565
2518
 
2519
+ /** Draw all the tile data to an offscreen canvas using webgl if possible */
1566
2520
  redraw()
1567
2521
  {
1568
- // draw all the tile data to an offscreen canvas using webgl if possible
1569
2522
  this.redrawStart();
1570
2523
  this.drawAllTileData();
1571
2524
  this.redrawEnd();
1572
2525
  }
1573
2526
 
2527
+ /** Call to start the redraw process
2528
+ * @param {Boolean} [clear=1] - Should it clear the canvas before drawing */
1574
2529
  redrawStart(clear = 1)
1575
2530
  {
1576
2531
  // clear and set size
1577
- const width = this.size.x * this.tileSize.x;
2532
+ const width = this.size.x * this.tileSize.x;
1578
2533
  const height = this.size.y * this.tileSize.y;
1579
2534
  if (clear)
1580
2535
  {
@@ -1595,6 +2550,7 @@ class TileLayer extends EngineObject
1595
2550
  glPreRender(width, height);
1596
2551
  }
1597
2552
 
2553
+ /** Call to end the redraw process */
1598
2554
  redrawEnd()
1599
2555
  {
1600
2556
  ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
@@ -1605,18 +2561,24 @@ class TileLayer extends EngineObject
1605
2561
  [mainCanvasSize, mainCanvas, mainContext, cameraScale, cameraPos] = this.savedRenderSettings;
1606
2562
  }
1607
2563
 
2564
+ /** Draw the tile at a given position
2565
+ * @param {Vector2} layerPos */
1608
2566
  drawTileData(layerPos)
1609
2567
  {
1610
2568
  // first clear out where the tile was
1611
- const pos = layerPos.int().add(this.pos).add(vec2(.5));
2569
+ const pos = layerPos.floor().add(this.pos).add(vec2(.5));
1612
2570
  this.drawCanvas2D(pos, vec2(1), 0, 0, (context)=>context.clearRect(-.5, -.5, 1, 1));
1613
2571
 
1614
- // draw the tile
2572
+ // draw the tile if not undefined
1615
2573
  const d = this.getData(layerPos);
1616
- ASSERT(d.tile < 0 || mainContext == this.context); // must call redrawStart() before drawing tiles
1617
- d.tile < 0 || drawTile(pos, vec2(1), d.tile || -1, this.tileSize, d.color, d.direction*PI/2, d.mirror);
2574
+ if (d.tile != undefined)
2575
+ {
2576
+ ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
2577
+ drawTile(pos, vec2(1), d.tile, this.tileSize, d.color, d.direction*PI/2, d.mirror);
2578
+ }
1618
2579
  }
1619
2580
 
2581
+ /** Draw all the tiles in this layer */
1620
2582
  drawAllTileData()
1621
2583
  {
1622
2584
  for (let x = this.size.x; x--;)
@@ -1624,7 +2586,12 @@ class TileLayer extends EngineObject
1624
2586
  this.drawTileData(vec2(x,y));
1625
2587
  }
1626
2588
 
1627
- // draw directly to the 2d canvas in world space (bipass webgl)
2589
+ /** Draw directly to the 2d canvas in world space (bipass webgl)
2590
+ * @param {Vector2} pos
2591
+ * @param {Vector2} size
2592
+ * @param {Number} angle
2593
+ * @param {Boolean} mirror
2594
+ * @param {Function} drawFunction */
1628
2595
  drawCanvas2D(pos, size, angle, mirror, drawFunction)
1629
2596
  {
1630
2597
  const context = this.context;
@@ -1638,8 +2605,15 @@ class TileLayer extends EngineObject
1638
2605
  context.restore();
1639
2606
  }
1640
2607
 
1641
- // draw a tile directly onto the layer canvas
1642
- drawTile(pos, size=vec2(1), tileIndex=0, tileSize=defaultTileSize, color=new Color, angle=0, mirror)
2608
+ /** Draw a tile directly onto the layer canvas
2609
+ * @param {Vector2} pos
2610
+ * @param {Vector2} [size=new Vector2(1,1)]
2611
+ * @param {Number} [tileIndex=-1]
2612
+ * @param {Vector2} [tileSize=tileSizeDefault]
2613
+ * @param {Color} [color=new Color(1,1,1)]
2614
+ * @param {Number} [angle=0]
2615
+ * @param {Boolean} [mirror=0] */
2616
+ drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror)
1643
2617
  {
1644
2618
  this.drawCanvas2D(pos, size, angle, mirror, (context)=>
1645
2619
  {
@@ -1660,6 +2634,11 @@ class TileLayer extends EngineObject
1660
2634
  });
1661
2635
  }
1662
2636
 
2637
+ /** Draw a rectangle directly onto the layer canvas
2638
+ * @param {Vector2} pos
2639
+ * @param {Vector2} [size=new Vector2(1,1)]
2640
+ * @param {Color} [color=new Color(1,1,1)]
2641
+ * @param {Number} [angle=0] */
1663
2642
  drawRect(pos, size, color, angle) { this.drawTile(pos, size, -1, 0, color, angle, 0); }
1664
2643
  }
1665
2644
  /*
@@ -1671,72 +2650,142 @@ class TileLayer extends EngineObject
1671
2650
 
1672
2651
  'use strict';
1673
2652
 
2653
+ /**
2654
+ * Particle Emitter - Spawns particles with the given settings
2655
+ * @extends EngineObject
2656
+ * @example
2657
+ * // create a particle emitter
2658
+ * let pos = vec2(2,3);
2659
+ * let particleEmiter = new ParticleEmitter
2660
+ * (
2661
+ * pos, 1, 0, 500, PI, // pos, emitSize, emitTime, emitRate, emiteCone
2662
+ * 0, vec2(16), // tileIndex, tileSize
2663
+ * new Color, new Color(0,0,0), // colorStartA, colorStartB
2664
+ * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
2665
+ * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
2666
+ * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
2667
+ * .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
2668
+ * );
2669
+ */
1674
2670
  class ParticleEmitter extends EngineObject
1675
2671
  {
2672
+ /** Create a particle system with the given settings
2673
+ * @param {Vector2} position - World space position of the emitter
2674
+ * @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
2675
+ * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
2676
+ * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
2677
+ * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
2678
+ * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
2679
+ * @param {Number} [tileSize=tileSizeDefault] - Tile size for particles
2680
+ * @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors
2681
+ * @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors
2682
+ * @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
2683
+ * @param {Color} [colorEndB=new Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
2684
+ * @param {Number} [particleTime=.5] - How long particles live
2685
+ * @param {Number} [sizeStart=.1] - How big are particles at start
2686
+ * @param {Number} [sizeEnd=1] - How big are particles at end
2687
+ * @param {Number} [speed=.1] - How fast are particles when spawned
2688
+ * @param {Number} [angleSpeed=.05] - How fast are particles rotating
2689
+ * @param {Number} [damping=1] - How much to dampen particle speed
2690
+ * @param {Number} [angleDamping=1] - How much to dampen particle angular speed
2691
+ * @param {Number} [gravityScale=0] - How much does gravity effect particles
2692
+ * @param {Number} [particleConeAngle=PI] - Cone for start particle angle
2693
+ * @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
2694
+ * @param {Number} [randomness=.2] - Apply extra randomness percent
2695
+ * @param {Boolean} [collideTiles=0] - Do particles collide against tiles
2696
+ * @param {Boolean} [additive=0] - Should particles use addtive blend
2697
+ * @param {Boolean} [randomColorLinear=0] - Should color be randomized linearly or across each component
2698
+ * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
2699
+ */
1676
2700
  constructor
1677
2701
  (
1678
- pos, // world space position of emitter
1679
- emitSize = 0, // size of emitter (float for circle diameter, vec2 for rect)
1680
- emitTime = 0, // how long to stay alive (0 is forever)
1681
- emitRate = 100, // how many particles per second to spawn
1682
- emitConeAngle = PI, // local angle to apply velocity to particles from emitter
1683
- tileIndex = -1, // index into tile sheet, if <0 no texture is applied
1684
- tileSize = defaultTileSize, // tile size for particles
1685
- colorStartA = new Color, // color at start of life
1686
- colorStartB = new Color, // randomized between start colors
1687
- colorEndA = new Color(1,1,1,0), // color at end of life
1688
- colorEndB = new Color(1,1,1,0), // randomized between end colors
1689
- particleTime = .5, // how long particles live
1690
- sizeStart = .1, // how big are particles at start
1691
- sizeEnd = 1, // how big are particles at end
1692
- speed = .1, // how fast are particles when spawned
1693
- angleSpeed = .05, // how fast are particles rotating
1694
- damping = 1, // how much to dampen particle speed
1695
- angleDamping = 1, // how much to dampen particle angular speed
1696
- gravityScale = 0, // how much does gravity effect particles
1697
- particleConeAngle = PI, // cone for start particle angle
1698
- fadeRate = .1, // how quick to fade in particles at start/end in percent of life
1699
- randomness = .2, // apply extra randomness percent
1700
- collideTiles, // do particles collide against tiles
1701
- additive, // should particles use addtive blend
1702
- randomColorLinear = 1, // should color be randomized linearly or across each component
1703
- renderOrder = additive ? 1e9 : 0// render order for particles (additive is above other stuff by default)
2702
+ pos,
2703
+ emitSize = 0,
2704
+ emitTime = 0,
2705
+ emitRate = 100,
2706
+ emitConeAngle = PI,
2707
+ tileIndex = -1,
2708
+ tileSize = tileSizeDefault,
2709
+ colorStartA = new Color,
2710
+ colorStartB = new Color,
2711
+ colorEndA = new Color(1,1,1,0),
2712
+ colorEndB = new Color(1,1,1,0),
2713
+ particleTime = .5,
2714
+ sizeStart = .1,
2715
+ sizeEnd = 1,
2716
+ speed = .1,
2717
+ angleSpeed = .05,
2718
+ damping = 1,
2719
+ angleDamping = 1,
2720
+ gravityScale = 0,
2721
+ particleConeAngle = PI,
2722
+ fadeRate = .1,
2723
+ randomness = .2,
2724
+ collideTiles,
2725
+ additive,
2726
+ randomColorLinear = 1,
2727
+ renderOrder = additive ? 1e9 : 0
1704
2728
  )
1705
2729
  {
1706
- super(pos, new Vector2, tileIndex, tileSize);
2730
+ super(pos, new Vector2, tileIndex, tileSize, 0, undefined, renderOrder);
1707
2731
 
1708
2732
  // emitter settings
2733
+ /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
1709
2734
  this.emitSize = emitSize
2735
+ /** @property {Number} - How long to stay alive (0 is forever) */
1710
2736
  this.emitTime = emitTime;
2737
+ /** @property {Number} - How many particles per second to spawn, does not emit if 0 */
1711
2738
  this.emitRate = emitRate;
2739
+ /** @property {Number} - Local angle to apply velocity to particles from emitter */
1712
2740
  this.emitConeAngle = emitConeAngle;
1713
2741
 
1714
2742
  // color settings
2743
+ /** @property {Color} - Color at start of life 1, randomized between start colors */
1715
2744
  this.colorStartA = colorStartA;
2745
+ /** @property {Color} - Color at start of life 2, randomized between start colors */
1716
2746
  this.colorStartB = colorStartB;
2747
+ /** @property {Color} - Color at end of life 1, randomized between end colors */
1717
2748
  this.colorEndA = colorEndA;
2749
+ /** @property {Color} - Color at end of life 2, randomized between end colors */
1718
2750
  this.colorEndB = colorEndB;
2751
+ /** @property {Boolean} - Should color be randomized linearly or across each component */
1719
2752
  this.randomColorLinear = randomColorLinear;
1720
2753
 
1721
2754
  // particle settings
2755
+ /** @property {Number} - How long particles live */
1722
2756
  this.particleTime = particleTime;
2757
+ /** @property {Number} - How big are particles at start */
1723
2758
  this.sizeStart = sizeStart;
2759
+ /** @property {Number} - How big are particles at end */
1724
2760
  this.sizeEnd = sizeEnd;
2761
+ /** @property {Number} - How fast are particles when spawned */
1725
2762
  this.speed = speed;
2763
+ /** @property {Number} - How fast are particles rotating */
1726
2764
  this.angleSpeed = angleSpeed;
2765
+ /** @property {Number} - How much to dampen particle speed */
1727
2766
  this.damping = damping;
2767
+ /** @property {Number} - How much to dampen particle angular speed */
1728
2768
  this.angleDamping = angleDamping;
2769
+ /** @property {Number} - How much does gravity effect particles */
1729
2770
  this.gravityScale = gravityScale;
2771
+ /** @property {Number} - Cone for start particle angle */
1730
2772
  this.particleConeAngle = particleConeAngle;
2773
+ /** @property {Number} - How quick to fade in particles at start/end in percent of life */
1731
2774
  this.fadeRate = fadeRate;
2775
+ /** @property {Number} - Apply extra randomness percent */
1732
2776
  this.randomness = randomness;
2777
+ /** @property {Number} - Do particles collide against tiles */
1733
2778
  this.collideTiles = collideTiles;
2779
+ /** @property {Number} - Should particles use addtive blend */
1734
2780
  this.additive = additive;
1735
- this.renderOrder = renderOrder;
1736
- this.trailScale =
2781
+ /** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
2782
+ this.trailScale = 0;
2783
+
2784
+ // internal variables
1737
2785
  this.emitTimeBuffer = 0;
1738
2786
  }
1739
2787
 
2788
+ /** Update the emitter to spawn particles, called automatically by engine once each frame */
1740
2789
  update()
1741
2790
  {
1742
2791
  // only do default update to apply parent transforms
@@ -1759,6 +2808,8 @@ class ParticleEmitter extends EngineObject
1759
2808
  debugParticles && debugRect(this.pos, vec2(this.emitSize), '#0f0', 0, this.angle);
1760
2809
  }
1761
2810
 
2811
+ /** Spawn one particle
2812
+ * @return {Particle} */
1762
2813
  emitParticle()
1763
2814
  {
1764
2815
  // spawn a particle
@@ -1810,16 +2861,27 @@ class ParticleEmitter extends EngineObject
1810
2861
  return particle;
1811
2862
  }
1812
2863
 
1813
- render() {} // emitters are not rendered
2864
+ // Particle emitters are not rendered, only the particles are
2865
+ render() {}
1814
2866
  }
1815
2867
 
1816
2868
  ///////////////////////////////////////////////////////////////////////////////
1817
- // particle object
1818
-
2869
+ /**
2870
+ * Particle Object - Created automatically by Particle Emitters
2871
+ * @extends EngineObject
2872
+ */
1819
2873
  class Particle extends EngineObject
1820
2874
  {
2875
+ /**
2876
+ * Create a particle with the given settings
2877
+ * @param {Vector2} position - World space position of the particle
2878
+ * @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
2879
+ * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
2880
+ * @param {Number} [angle=0] - Angle to rotate the particle
2881
+ */
1821
2882
  constructor(pos, tileIndex, tileSize, angle) { super(pos, new Vector2, tileIndex, tileSize, angle); }
1822
2883
 
2884
+ /** Render the particle, automatically called each frame, sorted by renderOrder */
1823
2885
  render()
1824
2886
  {
1825
2887
  // modulate size and color
@@ -1861,27 +2923,64 @@ class Particle extends EngineObject
1861
2923
  }
1862
2924
  }
1863
2925
  }
1864
- /*
1865
- LittleJS Medal System
1866
- - Tracks and displays medals
1867
- - Saves medals to local storage
1868
- - Newgrounds and OS13k integration
1869
- */
2926
+ /**
2927
+ * LittleJS Medal System
2928
+ * <br> - Tracks and displays medals
2929
+ * <br> - Saves medals to local storage
2930
+ * <br> - Newgrounds and OS13k integration
2931
+ * @namespace Medals
2932
+ */
1870
2933
 
1871
2934
  'use strict';
1872
2935
 
1873
- const medals = [], medalsDisplayQueue = [];
1874
- let medalsGameName, medalsPreventUnlock, medalsDisplayTimer, newgrounds;
2936
+ /** List of all medals
2937
+ * @memberof Medals */
2938
+ const medals = [];
2939
+
2940
+ /** Set to stop medals from being unlockable (like if cheats are enabled)
2941
+ * @memberof Medals */
2942
+ let medalsPreventUnlock;
2943
+
2944
+ /** This can used to enable Newgrounds functionality
2945
+ * @type {Newgrounds}
2946
+ * @memberof Medals */
2947
+ let newgrounds;
1875
2948
 
1876
- function medalsInit(gameName)
2949
+ // Engine internal variables not exposed to documentation
2950
+ let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimer;
2951
+
2952
+ ///////////////////////////////////////////////////////////////////////////////
2953
+
2954
+ /** Initialize medals with a save name used for storage
2955
+ * <br> - Checks if medals are unlocked
2956
+ * <br> - Call this after creating all medals
2957
+ * @param {String} saveName
2958
+ * @memberof Medals */
2959
+ function medalsInit(saveName)
1877
2960
  {
1878
2961
  // check if medals are unlocked
1879
- medalsGameName = gameName;
2962
+ medalsSaveName = saveName;
1880
2963
  debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
1881
2964
  }
1882
2965
 
2966
+ /**
2967
+ * Medal Object - Tracks an unlockable medal
2968
+ * @example
2969
+ * // create a medal
2970
+ * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
2971
+ *
2972
+ * // unlock the medal
2973
+ * medal_example.unlock();
2974
+ */
1883
2975
  class Medal
1884
2976
  {
2977
+ /** Create an medal object and adds it to the list of medals
2978
+ * @param {Number} id - The unique identifier of the medal
2979
+ * @param {String} name - Name of the medal
2980
+ * @param {String} [description] - Description of the medal
2981
+ * @param {String} [icon='🏆'] - Icon for the medal
2982
+ * @param {String} [src] - Image location for the medal
2983
+ */
1885
2984
  constructor(id, name, description='', icon='🏆', src)
1886
2985
  {
1887
2986
  ASSERT(id >= 0 && !medals[id]);
@@ -1900,26 +2999,25 @@ class Medal
1900
2999
  }
1901
3000
  }
1902
3001
 
3002
+ /** Unlocks a medal if not already unlocked */
1903
3003
  unlock()
1904
3004
  {
1905
3005
  if (medalsPreventUnlock || this.unlocked)
1906
3006
  return;
1907
3007
 
1908
3008
  // save the medal
1909
- ASSERT(medalsGameName); // game name must be set
3009
+ ASSERT(medalsSaveName); // game name must be set
1910
3010
  localStorage[this.storageKey()] = this.unlocked = 1;
1911
3011
  medalsDisplayQueue.push(this);
1912
3012
 
1913
3013
  // save for newgrounds and OS13K
1914
3014
  newgrounds && newgrounds.unlockMedal(this.id);
1915
- localStorage['OS13kTrophy,' + this.icon + ',' + medalsGameName + ',' + this.name] = this.description;
1916
- }
1917
-
1918
- storageKey()
1919
- {
1920
- return medalsGameName + '_medal_' + this.id;
3015
+ localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
1921
3016
  }
1922
3017
 
3018
+ /** Render a medal
3019
+ * @param {Number} [hidePercent=0] - How much to slide the medal off screen
3020
+ */
1923
3021
  render(hidePercent=0)
1924
3022
  {
1925
3023
  const context = overlayContext;
@@ -1940,25 +3038,36 @@ class Medal
1940
3038
 
1941
3039
  // draw the text
1942
3040
  context.textAlign = 'left';
1943
- context.font = '3em '+ defaultFont;
3041
+ context.font = '3em '+ fontDefault;
1944
3042
  context.fillText(this.name, x+medalDisplayIconSize+25, y+35);
1945
- context.font = '1.5em '+ defaultFont;
3043
+ context.font = '1.5em '+ fontDefault;
1946
3044
  context.restore(context.fillText(this.description, x+medalDisplayIconSize+25, y+70));
1947
3045
  }
1948
3046
 
3047
+ /** Render the icon for a medal
3048
+ * @param {Number} x - Screen space X position
3049
+ * @param {Number} y - Screen space Y position
3050
+ * @param {Number} [size=medalDisplayIconSize] - Screen space size
3051
+ */
1949
3052
  renderIcon(x, y, size=medalDisplayIconSize)
1950
3053
  {
1951
3054
  // draw the image or icon
1952
3055
  const context = overlayContext;
1953
3056
  context.textAlign = 'center';
1954
3057
  context.textBaseline = 'middle';
1955
- context.font = size*.6 + 'px '+ defaultFont;
3058
+ context.font = size*.6 + 'px '+ fontDefault;
1956
3059
  context.fillStyle = '#000';
1957
3060
  if (this.image)
1958
3061
  context.drawImage(this.image, x-size/2, y-size/2, size, size);
1959
3062
  else
1960
3063
  context.fillText(this.icon, x, y); // show icon if there is no image
1961
3064
  }
3065
+
3066
+ // Get local storage key used by the medal
3067
+ storageKey()
3068
+ {
3069
+ return medalsSaveName + '_medal_' + this.id;
3070
+ }
1962
3071
  }
1963
3072
 
1964
3073
  // engine automatically renders medals
@@ -1969,9 +3078,9 @@ function medalsRender()
1969
3078
 
1970
3079
  // update first medal in queue
1971
3080
  const medal = medalsDisplayQueue[0];
1972
- const time = realTime - medalsDisplayTimer;
3081
+ const time = timeReal - medalsDisplayTimer;
1973
3082
  if (!medalsDisplayTimer)
1974
- medalsDisplayTimer = realTime;
3083
+ medalsDisplayTimer = timeReal;
1975
3084
  else if (time > medalDisplayTime)
1976
3085
  medalsDisplayQueue.shift(medalsDisplayTimer = 0);
1977
3086
  else
@@ -1986,15 +3095,26 @@ function medalsRender()
1986
3095
  }
1987
3096
 
1988
3097
  ///////////////////////////////////////////////////////////////////////////////
1989
- // Newgrounds API wrapper
1990
3098
 
3099
+ /**
3100
+ * Newgrounds API wrapper object
3101
+ * @example
3102
+ * // create a newgrounds object, replace the app id and cipher with your own
3103
+ * const app_id = '53123:1ZuSTQ9l';
3104
+ * const cipher = 'enF0vGH@Mj/FRASKL23Q==';
3105
+ * newgrounds = new Newgrounds(app_id, cipher);
3106
+ */
1991
3107
  class Newgrounds
1992
3108
  {
3109
+ /** Create a newgrounds object
3110
+ * @param {Number} app_id - The newgrounds App ID
3111
+ * @param {String} [cipher] - The encryption Key (AES-128/Base64) */
1993
3112
  constructor(app_id, cipher)
1994
3113
  {
1995
3114
  ASSERT(!newgrounds && app_id);
1996
3115
  this.app_id = app_id;
1997
3116
  this.cipher = cipher;
3117
+ this.host = location ? location.hostname : '';
1998
3118
 
1999
3119
  // create an instance of CryptoJS for encrypted calls
2000
3120
  cipher && (this.cryptoJS = CryptoJS());
@@ -2016,11 +3136,16 @@ class Newgrounds
2016
3136
  if (medal)
2017
3137
  {
2018
3138
  // copy newgrounds medal data
2019
- medal.name = newgroundsMedal['name'];
3139
+ medal.image = new Image();
3140
+ medal.image.src = newgroundsMedal['icon'];
3141
+ medal.name = newgroundsMedal['name'];
2020
3142
  medal.description = newgroundsMedal['description'];
2021
- medal.unlocked = newgroundsMedal['unlocked'];
2022
- medal.image = new Image();
2023
- medal.image.src = newgroundsMedal['icon'];
3143
+ medal.unlocked = newgroundsMedal['unlocked'];
3144
+ medal.difficulty = newgroundsMedal['difficulty'];
3145
+ medal.value = newgroundsMedal['value'];
3146
+
3147
+ if (medal.value)
3148
+ medal.description = medal.description + ' (' + medal.value + ')';
2024
3149
  }
2025
3150
  }
2026
3151
 
@@ -2030,30 +3155,55 @@ class Newgrounds
2030
3155
  debugMedals && console.log(this.scoreboards);
2031
3156
  }
2032
3157
 
2033
- unlockMedal(id)
2034
- {
2035
- return this.call('Medal.unlock', {'id':id}, 1);
2036
- }
2037
-
2038
- postScore(id, value)
2039
- {
2040
- return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1);
2041
- }
2042
-
3158
+ /** Send message to unlock a medal by id
3159
+ * @param {Number} id - The medal id */
3160
+ unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
3161
+
3162
+ /** Send message to post score
3163
+ * @param {Number} id - The scoreboard id
3164
+ * @param {Number} value - The score value */
3165
+ postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
3166
+
3167
+ /** Send message to log a view */
3168
+ logView() { return this.call('App.logView', {'host':this.host}, 1); }
3169
+
3170
+ /** Get scores from a scoreboard
3171
+ * @param {Number} id - The scoreboard id
3172
+ * @param {String} [user=0] - A user's id or name
3173
+ * @param {Number} [social=0] - If true, only social scores will be loaded
3174
+ * @param {Number} [skip=0] - Number of scores to skip before start
3175
+ * @param {Number} [limit=10] - Number of scores to include in the list
3176
+ * @return {Object} - The response JSON object
3177
+ */
2043
3178
  getScores(id, user=0, social=0, skip=0, limit=10)
2044
- {
2045
- return this.call('ScoreBoard.getScores',
2046
- {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit});
2047
- }
2048
-
3179
+ { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
3180
+
3181
+ /** Send a message to call a component of the Newgrounds API
3182
+ * @param {String} component - Name of the component
3183
+ * @param {Object} [parameters=0] - Parameters to use for call
3184
+ * @param {Boolean} [async=0] - If true, wait for response before continuing (will cause stall)
3185
+ * @return {Object} - The response JSON object
3186
+ */
2049
3187
  call(component, parameters=0, async=0)
2050
3188
  {
3189
+ const call = {'component':component, 'parameters':parameters};
3190
+ if (this.cipher)
3191
+ {
3192
+ // encrypt using AES-128 Base64 with cryptoJS
3193
+ const cryptoJS = this.cryptoJS;
3194
+ const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
3195
+ const iv = cryptoJS['lib']['WordArray']['random'](16);
3196
+ const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
3197
+ call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
3198
+ call['parameters'] = 0;
3199
+ }
3200
+
2051
3201
  // build the input object
2052
3202
  const input =
2053
3203
  {
2054
3204
  'app_id': this.app_id,
2055
3205
  'session_id': this.session_id,
2056
- 'call': this.encryptCall({'component':component, 'parameters':parameters})
3206
+ 'call': call
2057
3207
  };
2058
3208
 
2059
3209
  // build post data
@@ -2068,21 +3218,6 @@ class Newgrounds
2068
3218
  debugMedals && console.log(xmlHttp.responseText);
2069
3219
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
2070
3220
  }
2071
-
2072
- encryptCall(call)
2073
- {
2074
- if (!this.cipher)
2075
- return call;
2076
-
2077
- // encrypt using AES-128 Base64 with cryptoJS
2078
- const cryptoJS = this.cryptoJS;
2079
- const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
2080
- const iv = cryptoJS['lib']['WordArray']['random'](16);
2081
- const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
2082
- call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
2083
- call['parameters'] = 0;
2084
- return call;
2085
- }
2086
3221
  }
2087
3222
 
2088
3223
  ///////////////////////////////////////////////////////////////////////////////
@@ -2090,21 +3225,40 @@ class Newgrounds
2090
3225
  // Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
2091
3226
 
2092
3227
  const CryptoJS=()=>eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
2093
- /*
2094
- LittleJS WebGL Interface
2095
- - All webgl used by the engine is wrapped up here
2096
- - Can be disabled with glEnable to revert to 2D canvas rendering
2097
- - Batches sprite rendering on GPU for incredibly fast performance
2098
- - Sprite transform math is done in the shader where possible
2099
- - For normal stuff you won't need to call any functions in this file
2100
- - For advanced stuff there are helper functions to create shaders, textures, etc
2101
- */
3228
+ /**
3229
+ * LittleJS WebGL Interface
3230
+ * <br> - All webgl used by the engine is wrapped up here
3231
+ * <br> - Can be disabled with glEnable to revert to 2D canvas rendering
3232
+ * <br> - Batches sprite rendering on GPU for incredibly fast performance
3233
+ * <br> - Sprite transform math is done in the shader where possible
3234
+ * <br> - For normal stuff you won't need to call any functions in this file
3235
+ * <br> - For advanced stuff there are helper functions to create shaders, textures, etc
3236
+ * @namespace WebGL
3237
+ */
2102
3238
 
2103
3239
  'use strict';
2104
3240
 
2105
- let glCanvas, glContext, glTileTexture, glActiveTexture, glShader,
2106
- glPositionData, glColorData, glBatchCount, glDirty, glAdditive;
3241
+ /** The WebGL canvas which appears above the main canvas and below the overlay canvas
3242
+ * @type {HTMLCanvasElement}
3243
+ * @memberof WebGL */
3244
+ let glCanvas;
3245
+
3246
+ /** 2d context for glCanvas
3247
+ * @type {WebGLRenderingContext}
3248
+ * @memberof WebGL */
3249
+ let glContext;
3250
+
3251
+ /** Main tile sheet texture automatically loaded by engine
3252
+ * @type {WebGLTexture}
3253
+ * @memberof WebGL */
3254
+ let glTileTexture;
2107
3255
 
3256
+ // WebGL internal variables not exposed to documentation
3257
+ let glActiveTexture, glShader, glPositionData, glColorData, glBatchCount, glBatchAdditive, glDirty, glAdditive;
3258
+
3259
+ ///////////////////////////////////////////////////////////////////////////////
3260
+
3261
+ // Init WebGL, called automatically by the engine
2108
3262
  function glInit()
2109
3263
  {
2110
3264
  if (!glEnable) return;
@@ -2116,7 +3270,7 @@ function glInit()
2116
3270
 
2117
3271
  if (glOverlay)
2118
3272
  {
2119
- // firefox is much faster without copying the gl buffer so we just overlay it with some tradeoffs
3273
+ // some browsers are much faster without copying the gl buffer so we just overlay it instead
2120
3274
  document.body.appendChild(glCanvas);
2121
3275
  glCanvas.style = mainCanvas.style.cssText;
2122
3276
  }
@@ -2167,20 +3321,21 @@ function glInit()
2167
3321
  initVertexAttribArray('b', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
2168
3322
  }
2169
3323
 
3324
+ /** Set the WebGl blend mode, normally you should call setBlendMode instead
3325
+ * @param {Boolean} [additive=0]
3326
+ * @memberof WebGL */
2170
3327
  function glSetBlendMode(additive)
2171
3328
  {
2172
3329
  if (!glEnable) return;
2173
3330
 
2174
- if (additive != glAdditive)
2175
- glFlush();
2176
-
2177
3331
  // setup blending
2178
3332
  glAdditive = additive;
2179
- const destBlend = additive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
2180
- glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
2181
- glContext.enable(gl_BLEND);
2182
3333
  }
2183
3334
 
3335
+ /** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
3336
+ * <br> - This may also flush the gl buffer resulting in more draw calls and worse performance
3337
+ * @param {WebGLTexture} [texture=glTileTexture]
3338
+ * @memberof WebGL */
2184
3339
  function glSetTexture(texture=glTileTexture)
2185
3340
  {
2186
3341
  if (!glEnable) return;
@@ -2191,6 +3346,11 @@ function glSetTexture(texture=glTileTexture)
2191
3346
  glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
2192
3347
  }
2193
3348
 
3349
+ /** Compile WebGL shader of the given type, will throw errors if in debug mode
3350
+ * @param {String} source
3351
+ * @param type
3352
+ * @return {WebGLShader}
3353
+ * @memberof WebGL */
2194
3354
  function glCompileShader(source, type)
2195
3355
  {
2196
3356
  if (!glEnable) return;
@@ -2206,6 +3366,11 @@ function glCompileShader(source, type)
2206
3366
  return shader;
2207
3367
  }
2208
3368
 
3369
+ /** Create WebGL program with given shaders
3370
+ * @param {WebGLShader} vsSource
3371
+ * @param {WebGLShader} fsSource
3372
+ * @return {WebGLProgram}
3373
+ * @memberof WebGL */
2209
3374
  function glCreateProgram(vsSource, fsSource)
2210
3375
  {
2211
3376
  if (!glEnable) return;
@@ -2222,6 +3387,12 @@ function glCreateProgram(vsSource, fsSource)
2222
3387
  return program;
2223
3388
  }
2224
3389
 
3390
+ /** Create WebGL buffer
3391
+ * @param bufferType
3392
+ * @param size
3393
+ * @param usage
3394
+ * @return {WebGLBuffer}
3395
+ * @memberof WebGL */
2225
3396
  function glCreateBuffer(bufferType, size, usage)
2226
3397
  {
2227
3398
  if (!glEnable) return;
@@ -2233,15 +3404,19 @@ function glCreateBuffer(bufferType, size, usage)
2233
3404
  return buffer;
2234
3405
  }
2235
3406
 
3407
+ /** Create WebGL texture from an image and set the texture settings
3408
+ * @param {Image} image
3409
+ * @return {WebGLTexture}
3410
+ * @memberof WebGL */
2236
3411
  function glCreateTexture(image)
2237
3412
  {
2238
- if (!glEnable) return;
3413
+ if (!glEnable || !image || !image.width) return;
2239
3414
 
2240
3415
  // build the texture
2241
3416
  const texture = glContext.createTexture();
2242
3417
  glContext.bindTexture(gl_TEXTURE_2D, texture);
2243
3418
  glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
2244
-
3419
+
2245
3420
  // use point filtering for pixelated rendering
2246
3421
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, pixelated ? gl_NEAREST : gl_LINEAR);
2247
3422
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, pixelated ? gl_NEAREST : gl_LINEAR);
@@ -2250,6 +3425,7 @@ function glCreateTexture(image)
2250
3425
  return texture;
2251
3426
  }
2252
3427
 
3428
+ // called automatically by engine before render
2253
3429
  function glPreRender(width, height)
2254
3430
  {
2255
3431
  if (!glEnable) return;
@@ -2277,24 +3453,33 @@ function glPreRender(width, height)
2277
3453
  );
2278
3454
  }
2279
3455
 
3456
+ /** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
3457
+ * @memberof WebGL */
2280
3458
  function glFlush()
2281
3459
  {
2282
3460
  if (!glEnable || !glBatchCount) return;
2283
3461
 
3462
+ const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
3463
+ glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
3464
+ glContext.enable(gl_BLEND);
3465
+
2284
3466
  // draw all the sprites in the batch and reset the buffer
2285
3467
  glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
2286
3468
  glPositionData.subarray(0, glBatchCount * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT));
2287
3469
  glContext.drawArrays(gl_TRIANGLES, 0, glBatchCount * gl_VERTICES_PER_QUAD);
2288
3470
  glBatchCount = 0;
3471
+ glBatchAdditive = glAdditive;
2289
3472
  }
2290
3473
 
3474
+ /** Draw any sprites still in the buffer, copy to main canvas and clear
3475
+ * @param {CanvasRenderingContext2D} context
3476
+ * @param {Boolean} [forceDraw=0]
3477
+ * @memberof WebGL */
2291
3478
  function glCopyToContext(context, forceDraw)
2292
3479
  {
2293
3480
  if (!glEnable || !glDirty) return;
2294
3481
 
2295
- // draw any sprites still in the buffer, copy to main canvas and clear
2296
3482
  glFlush();
2297
-
2298
3483
  if (!glOverlay || forceDraw)
2299
3484
  {
2300
3485
  // do not draw/clear in overlay mode because the canvas is visible
@@ -2303,12 +3488,25 @@ function glCopyToContext(context, forceDraw)
2303
3488
  }
2304
3489
  }
2305
3490
 
2306
- function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=0xffffffff, rgbaAdditive=0x00000000)
3491
+ /** Add a sprite to the gl draw list, used by all gl draw functions
3492
+ * @param x
3493
+ * @param y
3494
+ * @param sizeX
3495
+ * @param sizeY
3496
+ * @param [angle=0]
3497
+ * @param [uv0X=0]
3498
+ * @param [uv0Y=0]
3499
+ * @param [uv1X=1]
3500
+ * @param [uv1Y=1]
3501
+ * @param [rgba=0xffffffff]
3502
+ * @param [rgbaAdditive=0]
3503
+ * @memberof WebGL */
3504
+ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=0xffffffff, rgbaAdditive=0)
2307
3505
  {
2308
3506
  if (!glEnable) return;
2309
-
2310
- // flush if there is no room for more verts
2311
- if (glBatchCount == gl_MAX_BATCH)
3507
+
3508
+ // flush if there is no room for more verts or if different blend mode
3509
+ if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
2312
3510
  glFlush();
2313
3511
 
2314
3512
  // setup 2 triangles to form a quad
@@ -2317,69 +3515,45 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
2317
3515
 
2318
3516
  // vertex 0
2319
3517
  glPositionData[offset++] = angle;
2320
- glPositionData[offset++] = x;
2321
- glPositionData[offset++] = y;
2322
- glPositionData[offset++] = -sizeX;
2323
- glPositionData[offset++] = -sizeY;
2324
- glPositionData[offset++] = uv0X;
2325
- glPositionData[offset++] = uv1Y;
2326
- glColorData[offset++] = rgba;
2327
- glColorData[offset++] = rgbaAdditive;
3518
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3519
+ glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
3520
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3521
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2328
3522
 
2329
3523
  // vertex 1
2330
3524
  glPositionData[offset++] = angle;
2331
- glPositionData[offset++] = x;
2332
- glPositionData[offset++] = y;
2333
- glPositionData[offset++] = sizeX;
2334
- glPositionData[offset++] = sizeY;
2335
- glPositionData[offset++] = uv1X;
2336
- glPositionData[offset++] = uv0Y;
2337
- glColorData[offset++] = rgba;
2338
- glColorData[offset++] = rgbaAdditive;
3525
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3526
+ glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
3527
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3528
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2339
3529
 
2340
3530
  // vertex 2
2341
3531
  glPositionData[offset++] = angle;
2342
- glPositionData[offset++] = x;
2343
- glPositionData[offset++] = y;
2344
- glPositionData[offset++] = -sizeX;
2345
- glPositionData[offset++] = sizeY;
2346
- glPositionData[offset++] = uv0X;
2347
- glPositionData[offset++] = uv0Y;
2348
- glColorData[offset++] = rgba;
2349
- glColorData[offset++] = rgbaAdditive;
3532
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3533
+ glPositionData[offset++] = -sizeX; glPositionData[offset++] = sizeY;
3534
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
3535
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2350
3536
 
2351
3537
  // vertex 0
2352
3538
  glPositionData[offset++] = angle;
2353
- glPositionData[offset++] = x;
2354
- glPositionData[offset++] = y;
2355
- glPositionData[offset++] = -sizeX;
2356
- glPositionData[offset++] = -sizeY;
2357
- glPositionData[offset++] = uv0X;
2358
- glPositionData[offset++] = uv1Y;
2359
- glColorData[offset++] = rgba;
2360
- glColorData[offset++] = rgbaAdditive;
3539
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3540
+ glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
3541
+ glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3542
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2361
3543
 
2362
3544
  // vertex 3
2363
3545
  glPositionData[offset++] = angle;
2364
- glPositionData[offset++] = x;
2365
- glPositionData[offset++] = y;
2366
- glPositionData[offset++] = sizeX;
2367
- glPositionData[offset++] = -sizeY;
2368
- glPositionData[offset++] = uv1X;
2369
- glPositionData[offset++] = uv1Y;
2370
- glColorData[offset++] = rgba;
2371
- glColorData[offset++] = rgbaAdditive;
3546
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3547
+ glPositionData[offset++] = sizeX; glPositionData[offset++] = -sizeY;
3548
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
3549
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2372
3550
 
2373
3551
  // vertex 1
2374
3552
  glPositionData[offset++] = angle;
2375
- glPositionData[offset++] = x;
2376
- glPositionData[offset++] = y;
2377
- glPositionData[offset++] = sizeX;
2378
- glPositionData[offset++] = sizeY;
2379
- glPositionData[offset++] = uv1X;
2380
- glPositionData[offset++] = uv0Y;
2381
- glColorData[offset++] = rgba;
2382
- glColorData[offset++] = rgbaAdditive;
3553
+ glPositionData[offset++] = x; glPositionData[offset++] = y;
3554
+ glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
3555
+ glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3556
+ glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
2383
3557
  }
2384
3558
 
2385
3559
  ///////////////////////////////////////////////////////////////////////////////