littlejsengine 1.15.9 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/littlejs.d.ts +66 -83
  2. package/dist/littlejs.esm.js +318 -330
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +306 -319
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +229 -242
  7. package/examples/box2d/game.js +1 -1
  8. package/examples/breakout/game.js +3 -3
  9. package/examples/electron/game.js +1 -2
  10. package/examples/electron/index.html +2 -2
  11. package/examples/htmlMenu/game.js +0 -1
  12. package/examples/index.html +1 -1
  13. package/examples/module/game.js +1 -2
  14. package/examples/particles/index.html +1 -1
  15. package/examples/platformer/game.js +4 -4
  16. package/examples/puzzle/game.js +2 -2
  17. package/examples/shorts/base.html +3 -3
  18. package/examples/shorts/box2dPool.js +2 -2
  19. package/examples/shorts/empty.js +1 -1
  20. package/examples/shorts/{systemFont.js → fontImage.js} +3 -3
  21. package/examples/shorts/fps.js +1 -1
  22. package/examples/shorts/helloWorld.js +2 -2
  23. package/examples/shorts/nineSlice.js +2 -2
  24. package/examples/shorts/slidingPuzzle.js +1 -1
  25. package/examples/starter/game.js +1 -2
  26. package/examples/starter/index.html +2 -2
  27. package/examples/stress/index.html +1 -1
  28. package/examples/typescript/game.js +1 -2
  29. package/examples/typescript/game.ts +1 -2
  30. package/package.json +1 -1
  31. package/plugins/box2d.js +8 -8
  32. package/plugins/drawUtilities.js +6 -6
  33. package/plugins/postProcess.js +13 -20
  34. package/plugins/uiSystem.js +15 -3
  35. package/reference.md +4 -6
  36. package/src/engine.js +35 -37
  37. package/src/engineAudio.js +3 -3
  38. package/src/engineDebug.js +78 -78
  39. package/src/engineDraw.js +107 -109
  40. package/src/engineExport.js +12 -11
  41. package/src/engineInput.js +1 -1
  42. package/src/engineMedals.js +3 -3
  43. package/src/engineObject.js +4 -2
  44. package/src/engineRelease.js +1 -1
  45. package/src/engineSettings.js +20 -30
  46. package/src/engineTileLayer.js +5 -4
  47. package/src/engineUtilities.js +2 -10
  48. package/src/engineWebGL.js +6 -5
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
33
33
  * @type {string}
34
34
  * @default
35
35
  * @memberof Engine */
36
- const engineVersion = '1.15.9';
36
+ const engineVersion = '1.16.1';
37
37
 
38
38
  /** Frames per second to update
39
39
  * @type {number}
@@ -180,8 +180,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
180
180
  mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
181
181
 
182
182
  // disable smoothing for pixel art
183
- overlayContext.imageSmoothingEnabled =
184
- mainContext.imageSmoothingEnabled = !tilesPixelated;
183
+ mainContext.imageSmoothingEnabled = !tilesPixelated;
185
184
 
186
185
  // setup gl rendering if enabled
187
186
  glPreRender();
@@ -193,7 +192,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
193
192
  // update time keeping
194
193
  let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
195
194
  frameTimeLastMS = frameTimeMS;
196
- if (debug || showWatermark)
195
+ if (debug || debugWatermark)
197
196
  averageFPS = lerp(averageFPS, 1e3/(frameTimeDeltaMS||1), .05);
198
197
  const debugSpeedUp = debug && keyIsDown('Equal'); // +
199
198
  const debugSpeedDown = debug && keyIsDown('Minus'); // -
@@ -221,6 +220,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
221
220
  debugUpdate();
222
221
  gameUpdatePost();
223
222
  inputUpdatePost();
223
+ if (debugVideoCaptureIsActive())
224
+ renderFrame();
224
225
  }
225
226
  else
226
227
  {
@@ -284,19 +285,19 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
284
285
  glFlush();
285
286
  debugVideoCaptureUpdate();
286
287
 
287
- if (showWatermark && !debugVideoCaptureIsActive())
288
+ if (debugWatermark && !debugVideoCaptureIsActive())
288
289
  {
289
290
  // update fps display
290
- overlayContext.textAlign = 'right';
291
- overlayContext.textBaseline = 'top';
292
- overlayContext.font = '1em monospace';
293
- overlayContext.fillStyle = '#000';
291
+ mainContext.textAlign = 'right';
292
+ mainContext.textBaseline = 'top';
293
+ mainContext.font = '1em monospace';
294
+ mainContext.fillStyle = '#000';
294
295
  const text = engineName + ' ' + 'v' + engineVersion + ' / '
295
296
  + drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
296
297
  + (glEnable ? ' GL' : ' 2D') ;
297
- overlayContext.fillText(text, mainCanvas.width-3, 3);
298
- overlayContext.fillStyle = '#fff';
299
- overlayContext.fillText(text, mainCanvas.width-2, 2);
298
+ mainContext.fillText(text, mainCanvas.width-3, 3);
299
+ mainContext.fillStyle = '#fff';
300
+ mainContext.fillText(text, mainCanvas.width-2, 2);
300
301
  }
301
302
  drawCount = 0;
302
303
  }
@@ -316,8 +317,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
316
317
  const fixedAspect = canvasFixedSize.x / canvasFixedSize.y;
317
318
  const w = innerAspect < fixedAspect ? '100%' : '';
318
319
  const h = innerAspect < fixedAspect ? '' : '100%';
319
- overlayCanvas.style.width = mainCanvas.style.width = w;
320
- overlayCanvas.style.height = mainCanvas.style.height = h;
320
+ mainCanvas.style.width = w;
321
+ mainCanvas.style.height = h;
321
322
  if (glCanvas)
322
323
  {
323
324
  glCanvas.style.width = w;
@@ -332,6 +333,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
332
333
 
333
334
  // responsive aspect ratio with native resolution
334
335
  const innerAspect = innerWidth / innerHeight;
336
+ ASSERT(canvasMinAspect <= canvasMaxAspect);
335
337
  if (canvasMaxAspect && innerAspect > canvasMaxAspect)
336
338
  {
337
339
  // full height
@@ -346,12 +348,12 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
346
348
  }
347
349
  }
348
350
 
349
- // clear main and overlay canvas and set size
350
- overlayCanvas.width = mainCanvas.width = mainCanvasSize.x;
351
- overlayCanvas.height = mainCanvas.height = mainCanvasSize.y;
351
+ // clear main canvas and set size
352
+ mainCanvas.width = mainCanvasSize.x;
353
+ mainCanvas.height = mainCanvasSize.y;
352
354
 
353
355
  // apply the clear color to main canvas
354
- if (canvasClearColor.a > 0)
356
+ if (canvasClearColor.a > 0 && !glEnable)
355
357
  {
356
358
  mainContext.fillStyle = canvasClearColor.toString();
357
359
  mainContext.fillRect(0, 0, mainCanvasSize.x, mainCanvasSize.y);
@@ -359,9 +361,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
359
361
  }
360
362
 
361
363
  // set default line join and cap
362
- const lineJoin = 'round', lineCap = 'round';
363
- mainContext.lineJoin = overlayContext.lineJoin = lineJoin;
364
- mainContext.lineCap = overlayContext.lineCap = lineCap;
364
+ mainContext.lineJoin = 'round';
365
+ mainContext.lineCap = 'round';
365
366
  }
366
367
 
367
368
  // wait for gameInit to load
@@ -373,6 +374,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
373
374
  if (headlessMode)
374
375
  return startEngine();
375
376
 
377
+ // setup webgl
378
+ glInit(rootElement);
379
+
376
380
  // setup html
377
381
  const styleRoot =
378
382
  'margin:0;' + // fill the window
@@ -383,36 +387,30 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
383
387
  'touch-action:none;' + // prevent mobile pinch to resize
384
388
  '-webkit-touch-callout:none'; // compatibility for ios
385
389
  rootElement.style.cssText = styleRoot;
386
- drawCanvas = mainCanvas = document.createElement('canvas');
387
- rootElement.appendChild(mainCanvas);
390
+ drawCanvas = mainCanvas = rootElement.appendChild(document.createElement('canvas'));
388
391
  drawContext = mainContext = mainCanvas.getContext('2d');
389
392
 
390
393
  // init stuff and start engine
391
394
  inputInit();
392
395
  audioInit();
393
396
  debugInit();
394
- glInit();
395
-
396
- // create overlay canvas for hud to appear above gl canvas
397
- overlayCanvas = document.createElement('canvas')
398
- rootElement.appendChild(overlayCanvas);
399
- overlayContext = overlayCanvas.getContext('2d');
400
397
 
401
398
  // setup canvases
402
399
  // transform way is still more reliable then flexbox or grid
403
400
  const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
404
401
  'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
405
- mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
402
+ mainCanvas.style.cssText = styleCanvas;
406
403
  if (glCanvas)
407
404
  glCanvas.style.cssText = styleCanvas;
408
405
  setCanvasPixelated(canvasPixelated);
409
- setOverlayCanvasPixelated(overlayCanvasPixelated);
410
406
  updateCanvas();
411
407
  glPreRender();
412
408
 
413
- // create offscreen canvas for image processing
414
- workCanvas = new OffscreenCanvas(256, 256);
415
- workContext = workCanvas.getContext('2d', { willReadFrequently: true });
409
+ // create offscreen canvases for image processing
410
+ workCanvas = new OffscreenCanvas(64, 64);
411
+ workContext = workCanvas.getContext('2d');
412
+ workReadCanvas = new OffscreenCanvas(64, 64);
413
+ workReadContext = workReadCanvas.getContext('2d', { willReadFrequently: true });
416
414
 
417
415
  // create promises for loading images
418
416
  const promises = imageSources.map((src, textureIndex)=>
@@ -468,9 +466,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
468
466
  // LittleJS Splash Screen
469
467
  function drawEngineSplashScreen(t)
470
468
  {
471
- const x = overlayContext;
472
- const w = overlayCanvas.width = innerWidth;
473
- const h = overlayCanvas.height = innerHeight;
469
+ const x = mainContext;
470
+ const w = mainCanvas.width = innerWidth;
471
+ const h = mainCanvas.height = innerHeight;
474
472
 
475
473
  {
476
474
  // background
@@ -748,7 +746,7 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
748
746
  * - Debug functionality is disabled to reduce size and increase performance
749
747
  */
750
748
 
751
- let showWatermark = 0;
749
+ let debugWatermark = 0;
752
750
  let debugKey = '';
753
751
  const debug = 0;
754
752
  const debugOverlay = 0;
@@ -908,11 +906,7 @@ function percent(value, valueA, valueB)
908
906
  * @return {number}
909
907
  * @memberof Utilities */
910
908
  function lerp(valueA, valueB, percent)
911
- {
912
- if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
913
- console.warn('lerp() parameter order changed! use lerp(start, end, p)');
914
- return valueA + clamp(percent) * (valueB-valueA);
915
- }
909
+ { return valueA + clamp(percent) * (valueB-valueA); }
916
910
 
917
911
  /** Gets percent between percentA and percentB and linearly interpolates between lerpA and lerpB
918
912
  * A shortcut for lerp(lerpA, lerpB, percent(value, percentA, percentB))
@@ -943,11 +937,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
943
937
  * @return {number}
944
938
  * @memberof Utilities */
945
939
  function lerpWrap(valueA, valueB, percent, wrapSize=1)
946
- {
947
- if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
948
- console.warn('lerpWrap() parameter order changed! use lerpWrap(start, end, p)');
949
- return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize);
950
- }
940
+ { return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
951
941
 
952
942
  /** Returns signed wrapped distance between the two angles passed in
953
943
  * @param {number} angleA
@@ -2087,15 +2077,7 @@ let canvasFixedSize = vec2();
2087
2077
  * @type {boolean}
2088
2078
  * @default
2089
2079
  * @memberof Settings */
2090
- let canvasPixelated = true;
2091
-
2092
- /** Use nearest canvas scaling for more pixelated look
2093
- * - If enabled sets css image-rendering:pixelated
2094
- * - This defaults to false because text looks better with smoothing
2095
- * @type {boolean}
2096
- * @default
2097
- * @memberof Settings */
2098
- let overlayCanvasPixelated = false;
2080
+ let canvasPixelated = false;
2099
2081
 
2100
2082
  /** Disables texture filtering for crisper pixel art
2101
2083
  * @type {boolean}
@@ -2143,13 +2125,19 @@ let glCircleSides = 32;
2143
2125
  * @type {Vector2}
2144
2126
  * @default Vector2(16,16)
2145
2127
  * @memberof Settings */
2146
- let tileSizeDefault = vec2(16);
2128
+ let tileDefaultSize = vec2(16);
2147
2129
 
2148
- /** How many pixels smaller to draw tiles to prevent bleeding from neighbors
2130
+ /** Default padding pixels around tiles
2149
2131
  * @type {number}
2150
2132
  * @default
2151
2133
  * @memberof Settings */
2152
- let tileFixBleedScale = 0;
2134
+ let tileDefaultPadding = 0;
2135
+
2136
+ /** Default amount of pixels smaller to draw tiles to prevent neighbor bleeding
2137
+ * @type {number}
2138
+ * @default
2139
+ * @memberof Settings */
2140
+ let tileDefaultBleed = 0;
2153
2141
 
2154
2142
  ///////////////////////////////////////////////////////////////////////////////
2155
2143
  // Object settings
@@ -2387,7 +2375,6 @@ function setCanvasMaxAspect(aspect) { canvasMaxAspect = aspect; }
2387
2375
  function setCanvasFixedSize(size) { canvasFixedSize = size.copy(); }
2388
2376
 
2389
2377
  /** Use nearest scaling algorithm for canvas for more pixelated look
2390
- * - If enabled sets css image-rendering:pixelated
2391
2378
  * @param {boolean} pixelated
2392
2379
  * @memberof Settings */
2393
2380
  function setCanvasPixelated(pixelated)
@@ -2399,18 +2386,6 @@ function setCanvasPixelated(pixelated)
2399
2386
  glCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
2400
2387
  }
2401
2388
 
2402
- /** Use nearest scaling algorithm for canvas for more pixelated look
2403
- * - If enabled sets css image-rendering:pixelated
2404
- * - This defaults to false because text looks better with smoothing
2405
- * @param {boolean} pixelated
2406
- * @memberof Settings */
2407
- function setOverlayCanvasPixelated(pixelated)
2408
- {
2409
- overlayCanvasPixelated = pixelated;
2410
- if (overlayCanvas)
2411
- overlayCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
2412
- }
2413
-
2414
2389
  /** Disables texture filtering for crisper pixel art
2415
2390
  * @param {boolean} pixelated
2416
2391
  * @memberof Settings */
@@ -2454,12 +2429,17 @@ function setGLCircleSides(sides) { glCircleSides = sides; }
2454
2429
  /** Set default size of tiles in pixels
2455
2430
  * @param {Vector2} size
2456
2431
  * @memberof Settings */
2457
- function setTileSizeDefault(size) { tileSizeDefault = size.copy(); }
2432
+ function setTileDefaultSize(size) { tileDefaultSize = size.copy(); }
2458
2433
 
2459
- /** Set to prevent tile bleeding from neighbors in pixels
2460
- * @param {number} scale
2434
+ /** Default padding pixels around tiles
2435
+ * @param {number} padding
2461
2436
  * @memberof Settings */
2462
- function setTileFixBleedScale(scale) { tileFixBleedScale = scale; }
2437
+ function setTileDefaultPadding(padding) { tileDefaultPadding = padding; }
2438
+
2439
+ /** Default amount of pixels smaller to draw tiles to prevent neighbor bleeding
2440
+ * @param {number} bleed
2441
+ * @memberof Settings */
2442
+ function setTileDefaultBleed(bleed) { tileDefaultBleed = bleed; }
2463
2443
 
2464
2444
  /** Set if collisions between objects are enabled
2465
2445
  * @param {boolean} enable
@@ -2610,7 +2590,7 @@ function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUn
2610
2590
  /** Set if watermark with FPS should be shown
2611
2591
  * @param {boolean} show
2612
2592
  * @memberof Debug */
2613
- function setShowWatermark(show) { showWatermark = show; }
2593
+ function setDebugWatermark(show) { debugWatermark = show; }
2614
2594
 
2615
2595
  /** Set key code used to toggle debug mode, Esc by default
2616
2596
  * @param {string} key
@@ -3046,10 +3026,11 @@ class EngineObject
3046
3026
  * @return {number} -1 if this.mirror is true, or 1 if not mirrored */
3047
3027
  getMirrorSign() { return this.mirror ? -1 : 1; }
3048
3028
 
3049
- /** Attaches a child to this with a given local transform
3029
+ /** Attaches a child to this with a local transform, returns child for chaining
3050
3030
  * @param {EngineObject} child
3051
3031
  * @param {Vector2} [localPos=(0,0)]
3052
- * @param {number} [localAngle] */
3032
+ * @param {number} [localAngle]
3033
+ * @return {EngineObject} The child object added */
3053
3034
  addChild(child, localPos=vec2(), localAngle=0)
3054
3035
  {
3055
3036
  ASSERT(!child.parent && !this.children.includes(child));
@@ -3059,6 +3040,7 @@ class EngineObject
3059
3040
  child.parent = this;
3060
3041
  child.localPos = localPos.copy();
3061
3042
  child.localAngle = localAngle;
3043
+ return child;
3062
3044
  }
3063
3045
 
3064
3046
  /** Removes a child from this one
@@ -3149,7 +3131,6 @@ class EngineObject
3149
3131
  * There are 3 canvas/contexts available to draw to...
3150
3132
  * mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
3151
3133
  * glCanvas - Used by the accelerated WebGL batch rendering system.
3152
- * overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
3153
3134
  *
3154
3135
  * The WebGL rendering system is very fast with some caveats...
3155
3136
  * - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
@@ -3169,16 +3150,6 @@ let mainCanvas;
3169
3150
  * @memberof Draw */
3170
3151
  let mainContext;
3171
3152
 
3172
- /** A canvas that appears on top of everything the same size as mainCanvas
3173
- * @type {HTMLCanvasElement}
3174
- * @memberof Draw */
3175
- let overlayCanvas;
3176
-
3177
- /** 2d context for overlayCanvas
3178
- * @type {CanvasRenderingContext2D}
3179
- * @memberof Draw */
3180
- let overlayContext;
3181
-
3182
3153
  /** The default canvas to use for drawing, usually mainCanvas
3183
3154
  * @type {HTMLCanvasElement|OffscreenCanvas}
3184
3155
  * @memberof Draw */
@@ -3199,6 +3170,16 @@ let workCanvas;
3199
3170
  * @memberof Draw */
3200
3171
  let workContext;
3201
3172
 
3173
+ /** Offscreen canvas with willReadFrequently that can be used for image processing
3174
+ * @type {OffscreenCanvas}
3175
+ * @memberof Draw */
3176
+ let workReadCanvas;
3177
+
3178
+ /** Offscreen canvas with willReadFrequently that can be used for image processing
3179
+ * @type {OffscreenCanvasRenderingContext2D}
3180
+ * @memberof Draw */
3181
+ let workReadContext;
3182
+
3202
3183
  /** The size of the main canvas (and other secondary canvases)
3203
3184
  * @type {Vector2}
3204
3185
  * @memberof Draw */
@@ -3221,7 +3202,7 @@ let drawCount;
3221
3202
  * - This can take vecs or floats for easier use and conversion
3222
3203
  * - If an index is passed in, the tile size and index will determine the position
3223
3204
  * @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
3224
- * @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
3205
+ * @param {Vector2|number} [size] - Size of tile in pixels
3225
3206
  * @param {number} [textureIndex] - Texture index to use
3226
3207
  * @param {number} [padding] - How many pixels padding around tiles
3227
3208
  * @return {TileInfo}
@@ -3231,7 +3212,7 @@ let drawCount;
3231
3212
  * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
3232
3213
  * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
3233
3214
  * @memberof Draw */
3234
- function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
3215
+ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding)
3235
3216
  {
3236
3217
  if (headlessMode)
3237
3218
  return new TileInfo;
@@ -3270,13 +3251,13 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
3270
3251
  class TileInfo
3271
3252
  {
3272
3253
  /** Create a tile info object
3273
- * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
3274
- * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
3275
- * @param {number} [textureIndex] - Texture index to use
3276
- * @param {number} [padding] - How many pixels padding around tiles
3277
- * @param {number} [bleedScale] - How many pixels smaller to draw tiles
3254
+ * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
3255
+ * @param {Vector2} [size] - Size of tile in pixels
3256
+ * @param {number} [textureIndex] - Texture index to use
3257
+ * @param {number} [padding] - How many pixels padding around tiles
3258
+ * @param {number} [bleed] - How many pixels smaller to draw tiles
3278
3259
  */
3279
- constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0, bleedScale=tileFixBleedScale)
3260
+ constructor(pos=vec2(), size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
3280
3261
  {
3281
3262
  /** @property {Vector2} - Top left corner of tile in pixels */
3282
3263
  this.pos = pos.copy();
@@ -3289,7 +3270,7 @@ class TileInfo
3289
3270
  /** @property {TextureInfo} - The texture info for this tile */
3290
3271
  this.textureInfo = textureInfos[this.textureIndex];
3291
3272
  /** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
3292
- this.bleedScale = bleedScale;
3273
+ this.bleed = bleed;
3293
3274
  }
3294
3275
 
3295
3276
  /** Returns a copy of this tile offset by a vector
@@ -3297,7 +3278,7 @@ class TileInfo
3297
3278
  * @return {TileInfo}
3298
3279
  */
3299
3280
  offset(offset)
3300
- { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleedScale); }
3281
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleed); }
3301
3282
 
3302
3283
  /** Returns a copy of this tile offset by a number of animation frames
3303
3284
  * @param {number} frame - Offset to apply in animation frames
@@ -3320,7 +3301,7 @@ class TileInfo
3320
3301
  this.size = textureInfo.size.copy();
3321
3302
  this.textureInfo = textureInfo;
3322
3303
  // do not use padding or bleed
3323
- this.bleedScale = this.padding = 0;
3304
+ this.bleed = this.padding = 0;
3324
3305
  return this;
3325
3306
  }
3326
3307
  }
@@ -3386,7 +3367,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
3386
3367
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
3387
3368
 
3388
3369
  const textureInfo = tileInfo && tileInfo.textureInfo;
3389
- const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
3370
+ const bleed = tileInfo?.bleed ?? 0;
3390
3371
  if (useWebGL && glEnable)
3391
3372
  {
3392
3373
  ASSERT(!!glContext, 'WebGL is not enabled!');
@@ -3401,13 +3382,13 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
3401
3382
  const w = tileInfo.size.x * sizeInverse.x;
3402
3383
  const h = tileInfo.size.y * sizeInverse.y;
3403
3384
  glSetTexture(textureInfo.glTexture);
3404
- if (bleedScale)
3385
+ if (bleed)
3405
3386
  {
3406
- const tileImageFixBleedX = sizeInverse.x*bleedScale;
3407
- const tileImageFixBleedY = sizeInverse.y*bleedScale;
3387
+ const bleedX = sizeInverse.x*bleed;
3388
+ const bleedY = sizeInverse.y*bleed;
3408
3389
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
3409
- x + tileImageFixBleedX, y + tileImageFixBleedY,
3410
- x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
3390
+ x + bleedX, y + bleedY,
3391
+ x - bleedX + w, y - bleedY + h,
3411
3392
  color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
3412
3393
  }
3413
3394
  else
@@ -3435,7 +3416,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
3435
3416
  // calculate uvs and render
3436
3417
  const x = tileInfo.pos.x, y = tileInfo.pos.y;
3437
3418
  const w = tileInfo.size.x, h = tileInfo.size.y;
3438
- drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor, bleedScale);
3419
+ drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor, bleed);
3439
3420
  }
3440
3421
  else
3441
3422
  {
@@ -3800,26 +3781,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
3800
3781
  drawTextScreen(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
3801
3782
  }
3802
3783
 
3803
- /** Draw text on overlay canvas in world space
3804
- * Automatically splits new lines into rows
3805
- * @param {string|number} text
3806
- * @param {Vector2} pos
3807
- * @param {number} [size]
3808
- * @param {Color} [color=(1,1,1,1)]
3809
- * @param {number} [lineWidth]
3810
- * @param {Color} [lineColor=(0,0,0,1)]
3811
- * @param {CanvasTextAlign} [textAlign='center']
3812
- * @param {string} [font=fontDefault]
3813
- * @param {string} [fontStyle]
3814
- * @param {number} [maxWidth]
3815
- * @param {number} [angle]
3816
- * @memberof Draw */
3817
- function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0)
3818
- {
3819
- drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, overlayContext);
3820
- }
3821
-
3822
- /** Draw text on overlay canvas in screen space
3784
+ /** Draw text in screen space
3823
3785
  * Automatically splits new lines into rows
3824
3786
  * @param {string|number} text
3825
3787
  * @param {Vector2} pos
@@ -3832,9 +3794,9 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
3832
3794
  * @param {string} [fontStyle]
3833
3795
  * @param {number} [maxWidth]
3834
3796
  * @param {number} [angle]
3835
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
3797
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
3836
3798
  * @memberof Draw */
3837
- function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=overlayContext)
3799
+ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=mainContext)
3838
3800
  {
3839
3801
  ASSERT(isString(text), 'text must be a string');
3840
3802
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -3878,16 +3840,16 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
3878
3840
  * @memberof Draw */
3879
3841
  function screenToWorld(screenPos)
3880
3842
  {
3843
+ ASSERT(isVector2(screenPos), 'screenPos must be a vec2');
3844
+
3881
3845
  let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
3882
3846
  let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
3883
3847
  if (cameraAngle)
3884
3848
  {
3885
3849
  // apply camera rotation
3886
3850
  const c = cos(-cameraAngle), s = sin(-cameraAngle);
3887
- const rotatedX = x * c - y * s;
3888
- const rotatedY = x * s + y * c;
3889
- x = rotatedX;
3890
- y = rotatedY;
3851
+ const xr = x * c - y * s, yr = x * s + y * c;
3852
+ x = xr; y = yr;
3891
3853
  }
3892
3854
  return new Vector2(x + cameraPos.x, y + cameraPos.y);
3893
3855
  }
@@ -3898,16 +3860,16 @@ function screenToWorld(screenPos)
3898
3860
  * @memberof Draw */
3899
3861
  function worldToScreen(worldPos)
3900
3862
  {
3863
+ ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
3864
+
3901
3865
  let x = worldPos.x - cameraPos.x;
3902
3866
  let y = worldPos.y - cameraPos.y;
3903
3867
  if (cameraAngle)
3904
3868
  {
3905
3869
  // apply inverse camera rotation
3906
3870
  const c = cos(cameraAngle), s = sin(cameraAngle);
3907
- const rotatedX = x * c - y * s;
3908
- const rotatedY = x * s + y * c;
3909
- x = rotatedX;
3910
- y = rotatedY;
3871
+ const xr = x * c - y * s, yr = x * s + y * c;
3872
+ x = xr; y = yr;
3911
3873
  }
3912
3874
  return new Vector2
3913
3875
  (
@@ -3922,16 +3884,16 @@ function worldToScreen(worldPos)
3922
3884
  * @memberof Draw */
3923
3885
  function screenToWorldDelta(screenDelta)
3924
3886
  {
3887
+ ASSERT(isVector2(screenDelta), 'screenDelta must be a vec2');
3888
+
3925
3889
  let x = screenDelta.x / cameraScale;
3926
3890
  let y = screenDelta.y / -cameraScale;
3927
3891
  if (cameraAngle)
3928
3892
  {
3929
3893
  // apply camera rotation
3930
3894
  const c = cos(-cameraAngle), s = sin(-cameraAngle);
3931
- const rotatedX = x * c - y * s;
3932
- const rotatedY = x * s + y * c;
3933
- x = rotatedX;
3934
- y = rotatedY;
3895
+ const xr = x * c - y * s, yr = x * s + y * c;
3896
+ x = xr; y = yr;
3935
3897
  }
3936
3898
  return new Vector2(x, y);
3937
3899
  }
@@ -3942,16 +3904,16 @@ function screenToWorldDelta(screenDelta)
3942
3904
  * @memberof Draw */
3943
3905
  function worldToScreenDelta(worldDelta)
3944
3906
  {
3907
+ ASSERT(isVector2(worldDelta), 'worldDelta must be a vec2');
3908
+
3945
3909
  let x = worldDelta.x;
3946
3910
  let y = worldDelta.y;
3947
3911
  if (cameraAngle)
3948
3912
  {
3949
3913
  // apply inverse camera rotation
3950
3914
  const c = cos(cameraAngle), s = sin(cameraAngle);
3951
- const rotatedX = x * c - y * s;
3952
- const rotatedY = x * s + y * c;
3953
- x = rotatedX;
3954
- y = rotatedY;
3915
+ const xr = x * c - y * s, yr = x * s + y * c;
3916
+ x = xr; y = yr;
3955
3917
  }
3956
3918
  return new Vector2(x * cameraScale, y * -cameraScale);
3957
3919
  }
@@ -3964,6 +3926,10 @@ function worldToScreenDelta(worldDelta)
3964
3926
  * @memberof Draw */
3965
3927
  function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
3966
3928
  {
3929
+ ASSERT(isVector2(screenPos), 'screenPos must be a vec2');
3930
+ ASSERT(isVector2(screenSize), 'screenSize must be a vec2');
3931
+ ASSERT(isNumber(screenAngle), 'screenAngle must be a number');
3932
+
3967
3933
  return [
3968
3934
  screenToWorld(screenPos),
3969
3935
  screenSize.scale(1/cameraScale),
@@ -3979,6 +3945,10 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
3979
3945
  * @memberof Draw */
3980
3946
  function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
3981
3947
  {
3948
+ ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
3949
+ ASSERT(isVector2(worldSize), 'worldSize must be a vec2');
3950
+ ASSERT(isNumber(worldAngle), 'worldAngle must be a number');
3951
+
3982
3952
  return [
3983
3953
  worldToScreen(worldPos),
3984
3954
  worldSize.scale(cameraScale),
@@ -3991,8 +3961,8 @@ function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
3991
3961
  * @memberof Draw */
3992
3962
  function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
3993
3963
 
3994
- /** Check if a point or circle is on screen
3995
- * If size is a Vector2, uses the largest dimension as diameter
3964
+ /** Check if a box, point, or circle is on screen with a circle test
3965
+ * If size is a Vector2, uses the length as diameter
3996
3966
  * This can be used to cull offscreen objects from render or update
3997
3967
  * @param {Vector2} pos - world space position
3998
3968
  * @param {Vector2|number} size - world space size or diameter
@@ -4000,12 +3970,30 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
4000
3970
  * @memberof Draw */
4001
3971
  function isOnScreen(pos, size=0)
4002
3972
  {
4003
- pos = worldToScreen(pos);
3973
+ ASSERT(isVector2(pos), 'pos must be a vec2');
3974
+ ASSERT(isVector2(size) || isNumber(size), 'size must be a vec2 or number');
3975
+
3976
+ // optimized circle on screen test
3977
+ // pos = worldToScreen(pos);
3978
+ let x = pos.x - cameraPos.x;
3979
+ let y = pos.y - cameraPos.y;
3980
+ if (cameraAngle)
3981
+ {
3982
+ // apply inverse camera rotation
3983
+ const c = cos(cameraAngle), s = sin(cameraAngle);
3984
+ const xr = x * c - y * s, yr = x * s + y * c;
3985
+ x = xr; y = yr;
3986
+ }
3987
+ x *= cameraScale*2; y *= -cameraScale*2;
3988
+
4004
3989
  if (size instanceof Vector2)
4005
- size = max(size.x, size.y); // use largest dimension
4006
- size *= cameraScale/2;
4007
- return pos.x + size > 0 && pos.x - size < mainCanvasSize.x &&
4008
- pos.y + size > 0 && pos.y - size < mainCanvasSize.y;
3990
+ size = size.length(); // use length of vector as diameter
3991
+ size *= cameraScale;
3992
+
3993
+ // check against screen bounds
3994
+ const w = mainCanvasSize.x, h = mainCanvasSize.y;
3995
+ return x + size > -w && x - size < w &&
3996
+ y + size > -h && y - size < h;
4009
3997
  }
4010
3998
 
4011
3999
  /** Enable normal or additive blend mode
@@ -4019,18 +4007,19 @@ function setBlendMode(additive=false, context)
4019
4007
  context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
4020
4008
  }
4021
4009
 
4022
- /** Combines all LittleJS canvases onto the main canvas and clears them
4023
- * This is necessary for things like saving a screenshot
4010
+ /** Combines LittleJS canvases onto the main canvas
4011
+ * This is necessary for things like screenshots and video
4024
4012
  * @memberof Draw */
4025
4013
  function combineCanvases()
4026
4014
  {
4027
- // combine canvases
4028
- glCopyToContext(mainContext);
4029
- mainContext.drawImage(overlayCanvas, 0, 0);
4030
-
4031
- // clear canvases
4032
- glClearCanvas();
4033
- overlayCanvas.width |= 0;
4015
+ const w = mainCanvasSize.x, h = mainCanvasSize.y;
4016
+ workCanvas.width = w;
4017
+ workCanvas.height = h;
4018
+ workContext.fillRect(0,0,w,h); // remove background alpha
4019
+ glCopyToContext(workContext);
4020
+ workContext.drawImage(mainCanvas, 0, 0);
4021
+ mainCanvas.width |= 0;
4022
+ mainContext.drawImage(workCanvas, 0, 0);
4034
4023
  }
4035
4024
 
4036
4025
  /** Helper function to draw an image with color and additive color applied
@@ -4047,18 +4036,18 @@ function combineCanvases()
4047
4036
  * @param {number} dHeight
4048
4037
  * @param {Color} color
4049
4038
  * @param {Color} [additiveColor]
4050
- * @param {number} [bleedScale] - How much to shrink the source, used to fix bleeding
4039
+ * @param {number} [bleed] - How many pixels to shrink the source, used to fix bleeding
4051
4040
  * @memberof Draw */
4052
- function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleedScale=0)
4041
+ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleed=0)
4053
4042
  {
4054
4043
  function isWhite(c) { return c.r >= 1 && c.g >= 1 && c.b >= 1; }
4055
4044
  function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
4056
- const sx2 = bleedScale;
4057
- const sy2 = bleedScale;
4045
+ const sx2 = bleed;
4046
+ const sy2 = bleed;
4058
4047
  sWidth = max(1,sWidth|0);
4059
4048
  sHeight = max(1,sHeight|0);
4060
- const sWidth2 = sWidth - 2*bleedScale;
4061
- const sHeight2 = sHeight - 2*bleedScale;
4049
+ const sWidth2 = sWidth - 2*bleed;
4050
+ const sHeight2 = sHeight - 2*bleed;
4062
4051
  if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
4063
4052
  {
4064
4053
  // white texture with no additive alpha, no need to tint
@@ -4069,12 +4058,12 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
4069
4058
  else
4070
4059
  {
4071
4060
  // copy to offscreen canvas
4072
- workCanvas.width = sWidth;
4073
- workCanvas.height = sHeight;
4074
- workContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
4061
+ workReadCanvas.width = sWidth;
4062
+ workReadCanvas.height = sHeight;
4063
+ workReadContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
4075
4064
 
4076
4065
  // tint image using offscreen work context
4077
- const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
4066
+ const imageData = workReadContext.getImageData(0, 0, sWidth, sHeight);
4078
4067
  const data = imageData.data;
4079
4068
  if (additiveColor && !isBlack(additiveColor))
4080
4069
  {
@@ -4083,8 +4072,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
4083
4072
  const colorAdd = [additiveColor.r * 255, additiveColor.g * 255, additiveColor.b * 255, additiveColor.a * 255];
4084
4073
  for (let i = 0; i < data.length; ++i)
4085
4074
  data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
4086
- workContext.putImageData(imageData, 0, 0);
4087
- context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
4075
+ workReadContext.putImageData(imageData, 0, 0);
4076
+ context.drawImage(workReadCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
4088
4077
  }
4089
4078
  else
4090
4079
  {
@@ -4095,9 +4084,9 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
4095
4084
  data[i+1] *= color.g;
4096
4085
  data[i+2] *= color.b;
4097
4086
  }
4098
- workContext.putImageData(imageData, 0, 0);
4087
+ workReadContext.putImageData(imageData, 0, 0);
4099
4088
  context.globalAlpha = color.a;
4100
- context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
4089
+ context.drawImage(workReadCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
4101
4090
  context.globalAlpha = 1;
4102
4091
  }
4103
4092
  }
@@ -4159,7 +4148,7 @@ class FontImage
4159
4148
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
4160
4149
  {
4161
4150
  // load default font image
4162
- if (!engineFontImage)
4151
+ if (!image && !engineFontImage)
4163
4152
  {
4164
4153
  engineFontImage = new Image;
4165
4154
  engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
@@ -4182,23 +4171,14 @@ class FontImage
4182
4171
  this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
4183
4172
  }
4184
4173
 
4185
- /** Draw text on overlay canvas in world space using the image font
4186
- * @param {string|number} text
4187
- * @param {Vector2} pos
4188
- * @param {number} [scale]
4189
- * @param {boolean} [center]
4190
- */
4191
- drawTextOverlay(text, pos, scale=4, center)
4192
- { this.drawText(text, pos, scale, center, overlayContext); }
4193
-
4194
- /** Draw text on overlay canvas in screen space using the image font
4174
+ /** Draw text in screen space using the image font
4195
4175
  * @param {string|number} text
4196
4176
  * @param {Vector2} pos
4197
4177
  * @param {number} [scale]
4198
4178
  * @param {boolean} [center]
4199
4179
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
4200
4180
  */
4201
- drawTextScreen(text, pos, scale=4, center=true, context=overlayContext)
4181
+ drawTextScreen(text, pos, scale=4, center=true, context=mainContext)
4202
4182
  {
4203
4183
  context.save();
4204
4184
  const size = this.tileSize;
@@ -4948,7 +4928,7 @@ function inputRender()
4948
4928
  return;
4949
4929
 
4950
4930
  // setup the canvas
4951
- const context = overlayContext;
4931
+ const context = mainContext;
4952
4932
  context.save();
4953
4933
  context.globalAlpha = alpha*touchGamepadAlpha;
4954
4934
  context.strokeStyle = '#fff';
@@ -5190,12 +5170,12 @@ class Sound
5190
5170
  ///////////////////////////////////////////////////////////////////////////////
5191
5171
 
5192
5172
  /**
5193
- * Sound Wave Object - Stores a wave sound for later use and can be played positionally
5194
- * - this can be used to play wave, mp3, and ogg files
5173
+ * Sound Wave Object - Loads and stores an audio file for later use
5174
+ * - this can be used to load and play wave, mp3, and ogg files
5195
5175
  * @extends Sound
5196
5176
  * @memberof Audio
5197
5177
  * @example
5198
- * // create a sound
5178
+ * // load an audio asset file
5199
5179
  * const sound_example = new SoundWave('sound.mp3');
5200
5180
  *
5201
5181
  * // play the sound
@@ -6147,8 +6127,8 @@ class TileLayer extends CanvasLayer
6147
6127
  if (!this.context) return;
6148
6128
 
6149
6129
  // save current render settings
6150
- /** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number]} */
6151
- this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale];
6130
+ /** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
6131
+ this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
6152
6132
 
6153
6133
  // set the draw canvas and context to this layer
6154
6134
  // use camera settings to match this layer's canvas
@@ -6157,6 +6137,7 @@ class TileLayer extends CanvasLayer
6157
6137
  cameraPos = this.size.scale(.5);
6158
6138
  const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
6159
6139
  cameraScale = tileSize.x;
6140
+ canvasClearColor = CLEAR_BLACK;
6160
6141
  mainCanvasSize = this.size.multiply(tileSize);
6161
6142
  if (clear)
6162
6143
  {
@@ -6166,7 +6147,7 @@ class TileLayer extends CanvasLayer
6166
6147
  }
6167
6148
 
6168
6149
  // disable smoothing for pixel art
6169
- this.context.imageSmoothingEnabled = !tilesPixelated;
6150
+ drawContext.imageSmoothingEnabled = !tilesPixelated;
6170
6151
 
6171
6152
  // setup gl rendering if enabled
6172
6153
  glPreRender();
@@ -6182,7 +6163,7 @@ class TileLayer extends CanvasLayer
6182
6163
  //debugSaveCanvas(this.canvas);
6183
6164
 
6184
6165
  // set stuff back to normal
6185
- [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
6166
+ [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
6186
6167
  }
6187
6168
 
6188
6169
  /** Draw the tile at a given position in the tile grid
@@ -6895,10 +6876,10 @@ class Medal
6895
6876
  */
6896
6877
  render(hidePercent=0)
6897
6878
  {
6898
- const context = overlayContext;
6879
+ const context = mainContext;
6899
6880
  const width = min(medalDisplaySize.x, mainCanvas.width);
6900
6881
  const height = medalDisplaySize.y;
6901
- const x = overlayCanvas.width - width;
6882
+ const x = mainCanvas.width - width;
6902
6883
  const y = -height*hidePercent;
6903
6884
  const backgroundColor = hsl(0,0,.9);
6904
6885
 
@@ -6939,7 +6920,7 @@ class Medal
6939
6920
  {
6940
6921
  // draw the image or icon
6941
6922
  if (this.image)
6942
- overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
6923
+ mainContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
6943
6924
  else
6944
6925
  drawTextScreen(this.icon, pos, size*.7, BLACK);
6945
6926
  }
@@ -6960,7 +6941,7 @@ class Medal
6960
6941
  * @namespace WebGL
6961
6942
  */
6962
6943
 
6963
- /** The WebGL canvas which appears above the main canvas and below the overlay canvas
6944
+ /** The WebGL canvas which appears below the main canvas
6964
6945
  * @type {HTMLCanvasElement}
6965
6946
  * @memberof WebGL */
6966
6947
  let glCanvas;
@@ -6990,7 +6971,7 @@ const gl_MAX_POLY_VERTEXES = gl_ARRAY_BUFFER_SIZE / gl_POLY_VERTEX_BYTE_STRIDE |
6990
6971
  ///////////////////////////////////////////////////////////////////////////////
6991
6972
 
6992
6973
  // Initialize WebGL, called automatically by the engine
6993
- function glInit()
6974
+ function glInit(rootElement)
6994
6975
  {
6995
6976
  // keep set of texture infos so they can be restored if context is lost
6996
6977
  glTextureInfos = new Set;
@@ -7014,8 +6995,7 @@ function glInit()
7014
6995
  return;
7015
6996
  }
7016
6997
 
7017
- // attach the WebGL canvas
7018
- const rootElement = mainCanvas.parentElement;
6998
+ // attach the WebGL canvas;
7019
6999
  rootElement.appendChild(glCanvas);
7020
7000
 
7021
7001
  // startup webgl
@@ -7233,6 +7213,9 @@ function glClearCanvas()
7233
7213
  glCanvas.width = drawCanvas.width;
7234
7214
  glCanvas.height = drawCanvas.height;
7235
7215
  glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
7216
+ const color = canvasClearColor;
7217
+ if (color.a > 0)
7218
+ glContext.clearColor(color.r, color.g, color.b, color.a);
7236
7219
  glContext.clear(glContext.COLOR_BUFFER_BIT);
7237
7220
  }
7238
7221
 
@@ -7462,7 +7445,6 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
7462
7445
  glFlush();
7463
7446
  glSetInstancedMode();
7464
7447
 
7465
- glPolyMode = false;
7466
7448
  let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
7467
7449
  glPositionData[offset++] = x;
7468
7450
  glPositionData[offset++] = y;
@@ -7983,13 +7965,12 @@ class PostProcessPlugin
7983
7965
  {
7984
7966
  /** Create global post processing shader
7985
7967
  * @param {string} shaderCode
7986
- * @param {boolean} [includeOverlay]
7987
7968
  * @param {boolean} [includeMainCanvas]
7988
7969
  * @example
7989
7970
  * // create the post process plugin object
7990
7971
  * new PostProcessPlugin(shaderCode);
7991
7972
  */
7992
- constructor(shaderCode, includeOverlay=false, includeMainCanvas=true)
7973
+ constructor(shaderCode, includeMainCanvas=true)
7993
7974
  {
7994
7975
  ASSERT(!postProcess, 'Post process already initialized');
7995
7976
  postProcess = this;
@@ -8061,17 +8042,19 @@ class PostProcessPlugin
8061
8042
  // clear out the buffer
8062
8043
  glFlush();
8063
8044
 
8064
- if (includeMainCanvas || includeOverlay)
8045
+ // setup texture
8046
+ glContext.activeTexture(glContext.TEXTURE0);
8047
+ glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
8048
+ if (includeMainCanvas)
8065
8049
  {
8066
- // copy WebGL to the main canvas
8067
- mainContext.drawImage(glCanvas, 0, 0);
8068
-
8069
- if (includeOverlay)
8070
- {
8071
- // copy overlay canvas so it will be included in post processing
8072
- mainContext.drawImage(overlayCanvas, 0, 0);
8073
- overlayCanvas.width |= 0; // clear overlay canvas
8074
- }
8050
+ // copy main canvas to work canvas
8051
+ workCanvas.width = mainCanvasSize.x;
8052
+ workCanvas.height = mainCanvasSize.y;
8053
+ glCopyToContext(workContext);
8054
+ workContext.drawImage(mainCanvas, 0, 0);
8055
+
8056
+ // copy work canvas to texture
8057
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
8075
8058
  }
8076
8059
 
8077
8060
  // setup shader program to draw a quad
@@ -8080,14 +8063,6 @@ class PostProcessPlugin
8080
8063
  glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, 1);
8081
8064
  glContext.disable(glContext.BLEND);
8082
8065
 
8083
- // set textures, pass in the 2d canvas and gl canvas in separate texture channels
8084
- glContext.activeTexture(glContext.TEXTURE0);
8085
- glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
8086
- if (includeMainCanvas || includeOverlay)
8087
- {
8088
- glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, mainCanvas);
8089
- }
8090
-
8091
8066
  // set vertex position attribute
8092
8067
  const vertexByteStride = 8;
8093
8068
  const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
@@ -8316,12 +8291,14 @@ class UISystemPlugin
8316
8291
  * // create the ui plugin object
8317
8292
  * new UISystemPlugin;
8318
8293
  */
8319
- constructor(context=overlayContext)
8294
+ constructor(context=mainContext)
8320
8295
  {
8321
8296
  ASSERT(!uiSystem, 'UI system already initialized');
8322
8297
  uiSystem = this;
8323
8298
 
8324
8299
  // default settings
8300
+ /** @property {boolean} - Activate when mouse is pressed down instead of clicked */
8301
+ this.activateOnPress = false;
8325
8302
  /** @property {Color} - Default fill color for UI elements */
8326
8303
  this.defaultColor = WHITE;
8327
8304
  /** @property {Color} - Default outline color for UI elements */
@@ -8973,13 +8950,15 @@ class UIObject
8973
8950
  uiSystem.uiObjects.push(this);
8974
8951
  }
8975
8952
 
8976
- /** Add a child UIObject to this object
8977
- * @param {UIObject} child */
8953
+ /** Add a child UIObject to this object, returns child for chaining
8954
+ * @param {UIObject} child
8955
+ * @return {UIObject} The child object added */
8978
8956
  addChild(child)
8979
8957
  {
8980
8958
  ASSERT(!child.parent && !this.children.includes(child));
8981
8959
  this.children.push(child);
8982
8960
  child.parent = this;
8961
+ return child;
8983
8962
  }
8984
8963
 
8985
8964
  /** Remove a child UIObject from this object
@@ -9052,8 +9031,16 @@ class UIObject
9052
9031
  if (uiSystem.activeObject && !isActive)
9053
9032
  uiSystem.activeObject.onRelease();
9054
9033
  uiSystem.activeObject = this;
9034
+
9035
+ if (uiSystem.activateOnPress)
9036
+ {
9037
+ this.onClick();
9038
+ if (!this.soundPress && this.soundClick)
9039
+ this.soundClick.play();
9040
+ }
9055
9041
  }
9056
9042
  }
9043
+ if (!uiSystem.activateOnPress)
9057
9044
  if (!mouseDown && this.isActiveObject() && this.interactive)
9058
9045
  {
9059
9046
  this.onClick();
@@ -11493,33 +11480,33 @@ async function box2dInit()
11493
11480
  color = getDebugColor(color);
11494
11481
  point1 = box2d.vec2FromPointer(point1);
11495
11482
  point2 = box2d.vec2FromPointer(point2);
11496
- drawLine(point1, point2, debugLineWidth, color, vec2(), 0, false, false, overlayContext);
11483
+ drawLine(point1, point2, debugLineWidth, color, vec2(), 0, false);
11497
11484
  };
11498
11485
  debugDraw.DrawPolygon = function(vertices, vertexCount, color)
11499
11486
  {
11500
11487
  color = getDebugColor(color);
11501
11488
  const points = getPointsList(vertices, vertexCount);
11502
- drawPoly(points, CLEAR_WHITE, debugLineWidth, color, vec2(), 0, false, false, overlayContext);
11489
+ drawPoly(points, CLEAR_WHITE, debugLineWidth, color, vec2(), 0, false);
11503
11490
  };
11504
11491
  debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
11505
11492
  {
11506
11493
  color = getDebugColor(color);
11507
11494
  const points = getPointsList(vertices, vertexCount);
11508
- drawPoly(points, color, 0, color, vec2(), 0, false, false, overlayContext);
11495
+ drawPoly(points, color, 0, color, vec2(), 0, false);
11509
11496
  };
11510
11497
  debugDraw.DrawCircle = function(center, radius, color)
11511
11498
  {
11512
11499
  color = getDebugColor(color);
11513
11500
  center = box2d.vec2FromPointer(center);
11514
- drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false, false, overlayContext);
11501
+ drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false);
11515
11502
  };
11516
11503
  debugDraw.DrawSolidCircle = function(center, radius, axis, color)
11517
11504
  {
11518
11505
  color = getDebugColor(color);
11519
11506
  center = box2d.vec2FromPointer(center);
11520
11507
  axis = box2d.vec2FromPointer(axis).scale(radius);
11521
- drawCircle(center, radius*2, color, debugLineWidth, color, false, false, overlayContext);
11522
- drawLine(vec2(), axis, debugLineWidth, color, center, 0, false, false, overlayContext);
11508
+ drawCircle(center, radius*2, color, debugLineWidth, color, false);
11509
+ drawLine(vec2(), axis, debugLineWidth, color, center, 0, false);
11523
11510
  };
11524
11511
  debugDraw.DrawTransform = function(transform)
11525
11512
  {
@@ -11528,8 +11515,8 @@ async function box2dInit()
11528
11515
  const angle = -transform.get_q().GetAngle();
11529
11516
  const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
11530
11517
  const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
11531
- drawLine(vec2(), p1, debugLineWidth, c1, pos, angle, false, false, overlayContext);
11532
- drawLine(vec2(), p2, debugLineWidth, c2, pos, angle, false, false, overlayContext);
11518
+ drawLine(vec2(), p1, debugLineWidth, c1, pos, angle, false);
11519
+ drawLine(vec2(), p2, debugLineWidth, c2, pos, angle, false);
11533
11520
  }
11534
11521
 
11535
11522
  debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
@@ -11549,8 +11536,8 @@ async function box2dInit()
11549
11536
 
11550
11537
  ///////////////////////////////////////////////////////////////////////////////
11551
11538
 
11552
- /** Draw a scalable nine-slice UI element to the overlay canvas in screen space
11553
- * This function can not apply color because it draws using the overlay 2d context
11539
+ /** Draw a scalable nine-slice UI element to the main canvas in screen space
11540
+ * This function can not apply color because it draws using the 2d context
11554
11541
  * @param {Vector2} pos - Screen space position
11555
11542
  * @param {Vector2} size - Screen space size
11556
11543
  * @param {TileInfo} startTile - Starting tile for the nine-slice pattern
@@ -11560,7 +11547,7 @@ async function box2dInit()
11560
11547
  * @memberof DrawUtilities */
11561
11548
  function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2, angle=0)
11562
11549
  {
11563
- drawNineSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true, overlayContext);
11550
+ drawNineSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true);
11564
11551
  }
11565
11552
 
11566
11553
  /** Draw a scalable nine-slice UI element in world space
@@ -11609,8 +11596,8 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
11609
11596
  }
11610
11597
  }
11611
11598
 
11612
- /** Draw a scalable three-slice UI element to the overlay canvas in screen space
11613
- * This function can not apply color because it draws using the overlay 2d context
11599
+ /** Draw a scalable three-slice UI element to the main canvas in screen space
11600
+ * This function can not apply color because it draws using the 2d context
11614
11601
  * @param {Vector2} pos - Screen space position
11615
11602
  * @param {Vector2} size - Screen space size
11616
11603
  * @param {TileInfo} startTile - Starting tile for the three-slice pattern
@@ -11620,7 +11607,7 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
11620
11607
  * @memberof DrawUtilities */
11621
11608
  function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2, angle=0)
11622
11609
  {
11623
- drawThreeSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true, overlayContext);
11610
+ drawThreeSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true);
11624
11611
  }
11625
11612
 
11626
11613
  /** Draw a scalable three-slice UI element in world space