littlejsengine 1.11.10 → 1.11.17

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 (50) hide show
  1. package/dist/littlejs.d.ts +1311 -81
  2. package/dist/littlejs.esm.js +3135 -299
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +291 -293
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +193 -288
  7. package/examples/box2d/game.js +7 -10
  8. package/examples/box2d/gameObjects.js +33 -33
  9. package/examples/box2d/index.html +6 -6
  10. package/examples/box2d/scenes.js +28 -28
  11. package/examples/breakout/game.js +1 -4
  12. package/examples/breakout/index.html +4 -4
  13. package/examples/breakoutTutorial/index.html +2 -2
  14. package/examples/htmlMenu/game.js +14 -7
  15. package/examples/htmlMenu/index.html +3 -12
  16. package/examples/module/index.html +1 -1
  17. package/examples/particles/index.html +1 -1
  18. package/examples/platformer/game.js +0 -3
  19. package/examples/platformer/index.html +8 -8
  20. package/examples/puzzle/game.js +0 -3
  21. package/examples/puzzle/index.html +2 -2
  22. package/examples/shorts/base.html +1 -1
  23. package/examples/starter/build.js +15 -5
  24. package/examples/starter/index.html +13 -13
  25. package/examples/stress/index.html +3 -2
  26. package/examples/uiSystem/game.js +1 -7
  27. package/examples/uiSystem/index.html +3 -3
  28. package/package.json +3 -3
  29. package/plugins/box2d.js +1552 -640
  30. package/plugins/{Box2D_v2.3.1_min.wasm.js → box2d.wasm.js} +1 -1
  31. package/plugins/newgrounds.js +13 -9
  32. package/plugins/pluginExport.js +51 -0
  33. package/plugins/postProcess.js +94 -93
  34. package/plugins/uiSystem.js +145 -161
  35. package/plugins/zzfxm.js +163 -0
  36. package/src/engine.js +15 -20
  37. package/src/engineAudio.js +74 -204
  38. package/src/engineBuild.js +21 -3
  39. package/src/engineDebug.js +104 -6
  40. package/src/engineDraw.js +27 -14
  41. package/src/engineExport.js +12 -6
  42. package/src/engineInput.js +10 -5
  43. package/src/engineMedals.js +20 -10
  44. package/src/engineObject.js +3 -1
  45. package/src/engineParticles.js +6 -1
  46. package/src/engineRelease.js +6 -1
  47. package/src/engineSettings.js +7 -14
  48. package/src/engineUtilities.js +5 -11
  49. package/src/engineWebGL.js +19 -7
  50. /package/plugins/{Box2D_v2.3.1_min.wasm.wasm → box2d.wasm.wasm} +0 -0
package/src/engineDraw.js CHANGED
@@ -160,6 +160,8 @@ class TextureInfo
160
160
  this.image = image;
161
161
  /** @property {Vector2} - size of the image */
162
162
  this.size = vec2(image.width, image.height);
163
+ /** @property {Vector2} - inverse of the size, cached for rendering */
164
+ this.sizeInverse = vec2(1/image.width, 1/image.height);
163
165
  /** @property {WebGLTexture} - webgl texture */
164
166
  this.glTexture = glEnable && glCreateTexture(image);
165
167
  }
@@ -205,19 +207,19 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
205
207
  * @param {Color} [color=(1,1,1,1)] - Color to modulate with
206
208
  * @param {number} [angle] - Angle to rotate by
207
209
  * @param {boolean} [mirror] - If true image is flipped along the Y axis
208
- * @param {Color} [additiveColor=(0,0,0,0)] - Additive color to be applied
210
+ * @param {Color} [additiveColor] - Additive color to be applied if any
209
211
  * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
210
212
  * @param {boolean} [screenSpace=false] - If true the pos and size are in screen space
211
213
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
212
214
  * @memberof Draw */
213
215
  function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
214
- angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
216
+ angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
215
217
  {
216
218
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
217
219
  ASSERT(typeof tileInfo !== 'number' || !tileInfo,
218
220
  'this is an old style calls, to fix replace it with tile(tileIndex, tileSize)');
219
221
  ASSERT(isVector2(pos) && isVector2(size));
220
- ASSERT(isColor(color) && isColor(additiveColor));
222
+ ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)));
221
223
 
222
224
  const textureInfo = tileInfo && tileInfo.getTextureInfo();
223
225
  if (useWebGL)
@@ -228,21 +230,30 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
228
230
  pos = screenToWorld(pos);
229
231
  size = size.scale(1/cameraScale);
230
232
  }
231
-
232
233
  if (textureInfo)
233
234
  {
234
235
  // calculate uvs and render
235
- const sizeInverse = vec2(1).divide(textureInfo.size);
236
+ const sizeInverse = textureInfo.sizeInverse;
236
237
  const x = tileInfo.pos.x * sizeInverse.x;
237
238
  const y = tileInfo.pos.y * sizeInverse.y;
238
239
  const w = tileInfo.size.x * sizeInverse.x;
239
240
  const h = tileInfo.size.y * sizeInverse.y;
240
- const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
241
241
  glSetTexture(textureInfo.glTexture);
242
- glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
243
- x + tileImageFixBleed.x, y + tileImageFixBleed.y,
244
- x - tileImageFixBleed.x + w, y - tileImageFixBleed.y + h,
245
- color.rgbaInt(), additiveColor.rgbaInt());
242
+ if (tileFixBleedScale)
243
+ {
244
+ const tileImageFixBleedX = sizeInverse.x*tileFixBleedScale;
245
+ const tileImageFixBleedY = sizeInverse.y*tileFixBleedScale;
246
+ glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
247
+ x + tileImageFixBleedX, y + tileImageFixBleedY,
248
+ x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
249
+ color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
250
+ }
251
+ else
252
+ {
253
+ glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
254
+ x, y, x + w, y + h,
255
+ color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
256
+ }
246
257
  }
247
258
  else
248
259
  {
@@ -454,16 +465,15 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
454
465
  function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, maxWidth=undefined, context=overlayContext)
455
466
  {
456
467
  context.fillStyle = color.toString();
457
- context.lineWidth = lineWidth;
458
468
  context.strokeStyle = lineColor.toString();
469
+ context.lineWidth = lineWidth;
459
470
  context.textAlign = textAlign;
460
471
  context.font = size + 'px '+ font;
461
472
  context.textBaseline = 'middle';
462
473
  context.lineJoin = 'round';
463
474
 
464
- pos = pos.copy();
465
-
466
475
  const lines = (text+'').split('\n');
476
+ pos = pos.copy();
467
477
  pos.y -= (lines.length-1) * size/2; // center text vertically
468
478
  lines.forEach(line=>
469
479
  {
@@ -533,7 +543,10 @@ class FontImage
533
543
  {
534
544
  // load default font image
535
545
  if (!engineFontImage)
536
- (engineFontImage = new Image).src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
546
+ {
547
+ engineFontImage = new Image;
548
+ engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
549
+ }
537
550
 
538
551
  this.image = image || engineFontImage;
539
552
  this.tileSize = tileSize;
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * LittleJS Module Export
3
- * - Export engine as a module
4
3
  */
5
4
 
5
+ 'use strict';
6
+
6
7
  export
7
8
  {
8
9
  // Engine
@@ -43,6 +44,10 @@ export
43
44
  debugSaveCanvas,
44
45
  debugSaveText,
45
46
  debugSaveDataURL,
47
+ debugShowErrors,
48
+ debugVideoCaptureIsActive,
49
+ debugVideoCaptureStart,
50
+ debugVideoCaptureStop,
46
51
 
47
52
  // Settings
48
53
  cameraPos,
@@ -82,7 +87,6 @@ export
82
87
  medalDisplayTime,
83
88
  medalDisplaySlideTime,
84
89
  medalDisplaySize,
85
- medalDisplayIconSize,
86
90
 
87
91
  // Setters for globals
88
92
  setCameraPos,
@@ -123,7 +127,6 @@ export
123
127
  setMedalDisplayTime,
124
128
  setMedalDisplaySlideTime,
125
129
  setMedalDisplaySize,
126
- setMedalDisplayIconSize,
127
130
  setMedalsPreventUnlock,
128
131
  setShowWatermark,
129
132
  setDebugKey,
@@ -218,6 +221,7 @@ export
218
221
  glCopyToContext,
219
222
  glCreateProgram,
220
223
  glCreateTexture,
224
+ glSetTextureData,
221
225
  glDraw,
222
226
  glFlush,
223
227
  glSetTexture,
@@ -247,7 +251,8 @@ export
247
251
  mousePosScreen,
248
252
  mouseWheel,
249
253
  isUsingGamepad,
250
- preventDefaultInput,
254
+ inputPreventDefault,
255
+ setInputPreventDefault,
251
256
  gamepadIsDown,
252
257
  gamepadWasPressed,
253
258
  gamepadWasReleased,
@@ -260,14 +265,15 @@ export
260
265
  // Audio
261
266
  Sound,
262
267
  SoundWave,
263
- Music,
264
268
  playAudioFile,
265
269
  speak,
266
270
  speakStop,
267
271
  getNoteFrequency,
268
- audioContext,
269
272
  playSamples,
270
273
  zzfx,
274
+ zzfxG,
275
+ zzfxR,
276
+ audioContext,
271
277
 
272
278
  // Base Object
273
279
  EngineObject,
@@ -97,10 +97,16 @@ let mouseWheel = 0;
97
97
  * @memberof Input */
98
98
  let isUsingGamepad = false;
99
99
 
100
- /** Prevents input continuing to the default browser handling (false by default)
100
+ /** Prevents input continuing to the default browser handling (true by default)
101
101
  * @type {boolean}
102
102
  * @memberof Input */
103
- let preventDefaultInput = false;
103
+ let inputPreventDefault = true;
104
+
105
+ /** Prevents input continuing to the default browser handling
106
+ * This is useful to disable for html menus so the browser can handle input normally
107
+ * @param {boolean} preventDefault
108
+ * @memberof Input */
109
+ function setInputPreventDefault(preventDefault) { inputPreventDefault = preventDefault; }
104
110
 
105
111
  /** Returns true if gamepad button is down
106
112
  * @param {number} button
@@ -180,7 +186,6 @@ function inputInit()
180
186
  if (inputWASDEmulateDirection)
181
187
  inputData[0][remapKey(e.code)] = 3;
182
188
  }
183
- preventDefaultInput && e.preventDefault();
184
189
  }
185
190
 
186
191
  onkeyup = (e)=>
@@ -210,7 +215,7 @@ function inputInit()
210
215
  isUsingGamepad = false;
211
216
  inputData[0][e.button] = 3;
212
217
  mousePosScreen = mouseEventToScreen(e);
213
- e.button && e.preventDefault();
218
+ inputPreventDefault && e.button && e.preventDefault();
214
219
  }
215
220
  onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
216
221
  onmousemove = (e)=> mousePosScreen = mouseEventToScreen(e);
@@ -394,7 +399,7 @@ function touchInputInit()
394
399
  wasTouching = touching;
395
400
 
396
401
  // prevent default handling like copy and magnifier lens
397
- if (document.hasFocus()) // allow document to get focus
402
+ if (inputPreventDefault && document.hasFocus()) // allow document to get focus
398
403
  e.preventDefault();
399
404
 
400
405
  // must return true so the document will get focus
@@ -134,8 +134,9 @@ class Medal
134
134
  {
135
135
  const context = overlayContext;
136
136
  const width = min(medalDisplaySize.x, mainCanvas.width);
137
+ const height = medalDisplaySize.y;
137
138
  const x = overlayCanvas.width - width;
138
- const y = -medalDisplaySize.y*hidePercent;
139
+ const y = -height*hidePercent;
139
140
 
140
141
  // draw containing rect and clip to that region
141
142
  context.save();
@@ -143,25 +144,34 @@ class Medal
143
144
  context.fillStyle = new Color(.9,.9,.9).toString();
144
145
  context.strokeStyle = new Color(0,0,0).toString();
145
146
  context.lineWidth = 3;
146
- context.rect(x, y, width, medalDisplaySize.y);
147
+ context.rect(x, y, width, height);
147
148
  context.fill();
148
149
  context.stroke();
149
150
  context.clip();
150
151
 
151
- // draw the icon and text
152
- this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplaySize.y/2));
153
- const pos = vec2(x+medalDisplayIconSize+30, y+28);
154
- drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, undefined, 'left');
155
- pos.y += 32;
156
- drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, undefined, 'left');
152
+ // draw the icon
153
+ const gap = vec2(.1, .05).scale(height);
154
+ const medalDisplayIconSize = height - 2*gap.x;
155
+ this.renderIcon(vec2(x + gap.x + medalDisplayIconSize/2, y + height/2), medalDisplayIconSize);
156
+
157
+ // draw the name
158
+ const nameSize = height*.5;
159
+ const descriptionSize = height*.3;
160
+ const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
161
+ const textWidth = width - medalDisplayIconSize - 3*gap.x;
162
+ drawTextScreen(this.name, pos, nameSize, new Color(0,0,0), 0, undefined, 'left', undefined, textWidth);
163
+
164
+ // draw the description
165
+ pos.y = y + height - gap.y*2 - descriptionSize/2;
166
+ drawTextScreen(this.description, pos, descriptionSize, new Color(0,0,0), 0, undefined, 'left', undefined, textWidth);
157
167
  context.restore();
158
168
  }
159
169
 
160
170
  /** Render the icon for a medal
161
171
  * @param {Vector2} pos - Screen space position
162
- * @param {Number} [size=medalDisplayIconSize] - Screen space size
172
+ * @param {Number} size - Screen space size
163
173
  */
164
- renderIcon(pos, size=medalDisplayIconSize)
174
+ renderIcon(pos, size)
165
175
  {
166
176
  // draw the image or icon
167
177
  if (this.image)
@@ -173,7 +173,8 @@ class EngineObject
173
173
  if (this.groundObject)
174
174
  {
175
175
  // apply friction in local space of ground object
176
- const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
176
+ const groundSpeed = this.groundObject != this && this.groundObject.velocity ?
177
+ this.groundObject.velocity.x : 0;
177
178
  this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * this.friction;
178
179
  this.groundObject = undefined;
179
180
  //debugOverlay && debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
@@ -300,6 +301,7 @@ class EngineObject
300
301
  this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
301
302
 
302
303
  // set ground object to self for tile collision
304
+ // TODO: rework system so tile collision is its own object
303
305
  this.groundObject = this;
304
306
  }
305
307
  else
@@ -166,7 +166,12 @@ class ParticleEmitter extends EngineObject
166
166
  else
167
167
  this.destroy();
168
168
 
169
- debugParticles && debugRect(this.pos, vec2(this.emitSize), '#0f0', 0, this.angle);
169
+ if (debugParticles)
170
+ {
171
+ // show emitter bounds
172
+ const emitSize = typeof this.emitSize === 'number' ? vec2(this.emitSize) : this.emitSize;
173
+ debugRect(this.pos, emitSize, '#0f0', 0, this.angle);
174
+ }
170
175
  }
171
176
 
172
177
  /** Spawn one particle
@@ -32,4 +32,9 @@ function debugClear (){}
32
32
  function debugScreenshot (){}
33
33
  function debugSaveCanvas (){}
34
34
  function debugSaveText (){}
35
- function debugSaveDataURL(){}
35
+ function debugSaveDataURL(){}
36
+ function debugShowErrors(){}
37
+ function debugVideoCaptureIsActive(){ return false; }
38
+ function debugVideoCaptureStart (){}
39
+ function debugVideoCaptureStop (){}
40
+ function debugVideoCaptureUpdate(){}
@@ -64,6 +64,7 @@ let fontDefault = 'arial';
64
64
  let showSplashScreen = false;
65
65
 
66
66
  /** Disables all rendering, audio, and input for servers
67
+ * - Must be set before startup to take effect
67
68
  * @type {boolean}
68
69
  * @default
69
70
  * @memberof Settings */
@@ -73,12 +74,14 @@ let headlessMode = false;
73
74
  // WebGL settings
74
75
 
75
76
  /** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
77
+ * - Must be set before startup to take effect
76
78
  * @type {boolean}
77
79
  * @default
78
80
  * @memberof Settings */
79
81
  let glEnable = true;
80
82
 
81
83
  /** Fixes slow rendering in some browsers by not compositing the WebGL canvas
84
+ * - Must be set before startup to take effect
82
85
  * @type {boolean}
83
86
  * @default
84
87
  * @memberof Settings */
@@ -179,6 +182,7 @@ let inputWASDEmulateDirection = true;
179
182
 
180
183
  /** True if touch input is enabled for mobile devices
181
184
  * - Touch events will be routed to mouse events
185
+ * - Must be set before startup to take effect
182
186
  * @type {boolean}
183
187
  * @default
184
188
  * @memberof Settings */
@@ -186,7 +190,7 @@ let touchInputEnable = true;
186
190
 
187
191
  /** True if touch gamepad should appear on mobile devices
188
192
  * - Supports left analog stick, 4 face buttons and start button (button 9)
189
- * - Must be set by end of gameInit to be activated
193
+ * - Must be set before startup to take effect
190
194
  * @type {boolean}
191
195
  * @default
192
196
  * @memberof Settings */
@@ -264,12 +268,6 @@ let medalDisplaySlideTime = .5;
264
268
  * @memberof Settings */
265
269
  let medalDisplaySize = vec2(640, 80);
266
270
 
267
- /** Size of icon in medal display
268
- * @type {number}
269
- * @default
270
- * @memberof Settings */
271
- let medalDisplayIconSize = 50;
272
-
273
271
  /** Set to stop medals from being unlockable (like if cheats are enabled)
274
272
  * @type {boolean}
275
273
  * @default
@@ -445,8 +443,8 @@ function setSoundEnable(enable) { soundEnable = enable; }
445
443
  function setSoundVolume(volume)
446
444
  {
447
445
  soundVolume = volume;
448
- if (soundEnable && !headlessMode && audioGainNode)
449
- audioGainNode.gain.value = volume; // update gain immediately
446
+ if (soundEnable && !headlessMode && audioMasterGain)
447
+ audioMasterGain.gain.value = volume; // update gain immediately
450
448
  }
451
449
 
452
450
  /** Set default range where sound no longer plays
@@ -474,11 +472,6 @@ function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
474
472
  * @memberof Settings */
475
473
  function setMedalDisplaySize(size) { medalDisplaySize = size; }
476
474
 
477
- /** Set size of icon in medal display
478
- * @param {number} size
479
- * @memberof Settings */
480
- function setMedalDisplayIconSize(size) { medalDisplayIconSize = size; }
481
-
482
475
  /** Set to stop medals from being unlockable
483
476
  * @param {boolean} preventUnlock
484
477
  * @memberof Settings */
@@ -91,7 +91,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
91
91
  * @returns {number}
92
92
  * @memberof Utilities */
93
93
  function lerpWrap(percent, valueA, valueB, wrapSize=1)
94
- { return valueB + clamp(percent) * distanceWrap(valueA, valueB, wrapSize); }
94
+ { return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
95
95
 
96
96
  /** Returns signed wrapped distance between the two angles passed in
97
97
  * @param {number} angleA
@@ -289,23 +289,17 @@ class RandomGenerator
289
289
  ///////////////////////////////////////////////////////////////////////////////
290
290
 
291
291
  /**
292
- * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
293
- * @param {Vector2|number} [x]
294
- * @param {number} [y]
292
+ * Create a 2d vector, can take 1 or 2 scalar values
293
+ * @param {number} [x]
294
+ * @param {number} [y] - if y is undefined, x is used for both
295
295
  * @return {Vector2}
296
296
  * @example
297
297
  * let a = vec2(0, 1); // vector with coordinates (0, 1)
298
- * let b = vec2(a); // copy a into b
299
298
  * a = vec2(5); // set a to (5, 5)
300
299
  * b = vec2(); // set b to (0, 0)
301
300
  * @memberof Utilities
302
301
  */
303
- function vec2(x=0, y)
304
- {
305
- return typeof x == 'number' ?
306
- new Vector2(x, y == undefined? x : y) :
307
- new Vector2(x.x, x.y);
308
- }
302
+ function vec2(x=0, y) { return new Vector2(x, y == undefined? x : y); }
309
303
 
310
304
  /**
311
305
  * Check if object is a valid Vector2
@@ -106,12 +106,12 @@ function glPreRender()
106
106
 
107
107
  // set vertex attributes
108
108
  let offset = glAdditive = glBatchAdditive = 0;
109
- let initVertexAttribArray = (name, type, typeSize, size)=>
109
+ const initVertexAttribArray = (name, type, typeSize, size)=>
110
110
  {
111
111
  const location = glContext.getAttribLocation(glShader, name);
112
112
  const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
113
113
  const divisor = typeSize && 1; // only if not geometry
114
- const normalize = typeSize==1; // only if color
114
+ const normalize = typeSize == 1; // only if color
115
115
  glContext.enableVertexAttribArray(location);
116
116
  glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
117
117
  glContext.vertexAttribDivisor(location, divisor);
@@ -210,14 +210,14 @@ function glCreateTexture(image)
210
210
  const texture = glContext.createTexture();
211
211
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
212
212
  if (image && image.width)
213
- glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
213
+ glSetTextureData(texture, image);
214
214
  else
215
215
  {
216
216
  // create a white texture
217
217
  const whitePixel = new Uint8Array([255, 255, 255, 255]);
218
218
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
219
219
  }
220
-
220
+
221
221
  // use point filtering for pixelated rendering
222
222
  const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
223
223
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
@@ -225,6 +225,18 @@ function glCreateTexture(image)
225
225
  return texture;
226
226
  }
227
227
 
228
+ /** Set WebGL texture data from an image
229
+ * @param {WebGLTexture} texture
230
+ * @param {HTMLImageElement} image
231
+ * @memberof WebGL */
232
+ function glSetTextureData(texture, image)
233
+ {
234
+ // build the texture
235
+ ASSERT(!!image && image.width > 0, 'Invalid image data.');
236
+ glContext.bindTexture(glContext.TEXTURE_2D, texture);
237
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
238
+ }
239
+
228
240
  /** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
229
241
  * @memberof WebGL */
230
242
  function glFlush()
@@ -278,10 +290,10 @@ function glSetAntialias(antialias=true)
278
290
  * @param {Number} uv0Y
279
291
  * @param {Number} uv1X
280
292
  * @param {Number} uv1Y
281
- * @param {Number} rgba
282
- * @param {Number} [rgbaAdditive=0]
293
+ * @param {Number} [rgba=-1] - white is -1
294
+ * @param {Number} [rgbaAdditive=0] - black is 0
283
295
  * @memberof WebGL */
284
- function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
296
+ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=-1, rgbaAdditive=0)
285
297
  {
286
298
  ASSERT(typeof rgba == 'number' && typeof rgbaAdditive == 'number', 'invalid color');
287
299