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
@@ -768,7 +766,7 @@ const debugPointSize = .5;
768
766
  * @type {boolean}
769
767
  * @default
770
768
  * @memberof Debug */
771
- let showWatermark = true;
769
+ let debugWatermark = true;
772
770
 
773
771
  /** Key code used to toggle debug mode, Esc by default
774
772
  * @type {string}
@@ -1054,12 +1052,19 @@ function debugUpdate()
1054
1052
  if (!debug)
1055
1053
  return;
1056
1054
 
1055
+ if (debugVideoCaptureIsActive())
1056
+ {
1057
+ // control to stop video capture
1058
+ if (keyWasPressed('Digit6') || keyWasPressed(debugKey))
1059
+ debugVideoCaptureStop();
1060
+ }
1061
+
1057
1062
  if (keyWasPressed(debugKey)) // Esc
1058
1063
  debugOverlay = !debugOverlay;
1059
1064
  if (debugOverlay)
1060
1065
  {
1061
1066
  if (keyWasPressed('Digit0'))
1062
- showWatermark = !showWatermark;
1067
+ debugWatermark = !debugWatermark;
1063
1068
  if (keyWasPressed('Digit1'))
1064
1069
  debugPhysics = !debugPhysics, debugParticles = false;
1065
1070
  if (keyWasPressed('Digit2'))
@@ -1070,8 +1075,8 @@ function debugUpdate()
1070
1075
  debugRaycast = !debugRaycast;
1071
1076
  if (keyWasPressed('Digit5'))
1072
1077
  debugScreenshot();
1073
- if (keyWasPressed('Digit6'))
1074
- debugVideoCaptureIsActive() ? debugVideoCaptureStop() : debugVideoCaptureStart();
1078
+ if (keyWasPressed('Digit6') && !debugVideoCaptureIsActive())
1079
+ debugVideoCaptureStart();
1075
1080
  }
1076
1081
  }
1077
1082
 
@@ -1087,13 +1092,11 @@ function debugRender()
1087
1092
  {
1088
1093
  // combine canvases, remove alpha and save
1089
1094
  combineCanvases();
1090
- const w = mainCanvas.width, h = mainCanvas.height;
1091
- overlayContext.fillRect(0,0,w,h);
1092
- overlayContext.drawImage(mainCanvas, 0, 0);
1093
- debugSaveCanvas(overlayCanvas);
1095
+ debugSaveCanvas(mainCanvas);
1094
1096
  debugTakeScreenshot = 0;
1095
1097
  }
1096
1098
 
1099
+ const debugContext = mainContext;
1097
1100
  if (debugGamepads && gamepadsEnable)
1098
1101
  {
1099
1102
  // draw gamepads
@@ -1141,9 +1144,6 @@ function debugRender()
1141
1144
  let debugObject;
1142
1145
  if (debugOverlay)
1143
1146
  {
1144
- const saveContext = mainContext;
1145
- mainContext = overlayContext;
1146
-
1147
1147
  // draw red rectangle around screen
1148
1148
  const cameraSize = getCameraSize();
1149
1149
  debugRect(cameraPos, cameraSize.subtract(vec2(.1)), '#f008');
@@ -1175,15 +1175,14 @@ function debugRender()
1175
1175
  // show floored tile pick for tile collision
1176
1176
  drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5));
1177
1177
  }
1178
- mainContext = saveContext;
1179
1178
  }
1180
1179
 
1181
1180
  {
1182
1181
  // draw debug primitives
1183
- overlayContext.lineWidth = 2;
1182
+ debugContext.lineWidth = 2;
1184
1183
  debugPrimitives.forEach(p=>
1185
1184
  {
1186
- overlayContext.save();
1185
+ debugContext.save();
1187
1186
 
1188
1187
  // create canvas transform from world space to screen space
1189
1188
  // without scaling because we want consistent pixel sizes
@@ -1194,55 +1193,56 @@ function debugRender()
1194
1193
  scale = cameraScale;
1195
1194
  angle -= cameraAngle;
1196
1195
  }
1197
- overlayContext.translate(pos.x|0, pos.y|0);
1198
- overlayContext.rotate(angle);
1199
- overlayContext.scale(1, p.text ? 1 : -1);
1200
- overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
1196
+ debugContext.translate(pos.x|0, pos.y|0);
1197
+ debugContext.rotate(angle);
1198
+ debugContext.scale(1, p.text ? 1 : -1);
1199
+ debugContext.fillStyle = p.color;
1200
+ debugContext.strokeStyle = p.color;
1201
1201
  if (p.text !== undefined)
1202
1202
  {
1203
- overlayContext.font = p.size*scale + 'px '+ p.font;
1204
- overlayContext.textAlign = 'center';
1205
- overlayContext.textBaseline = 'middle';
1206
- overlayContext.fillText(p.text, 0, 0);
1203
+ debugContext.font = p.size*scale + 'px '+ p.font;
1204
+ debugContext.textAlign = 'center';
1205
+ debugContext.textBaseline = 'middle';
1206
+ debugContext.fillText(p.text, 0, 0);
1207
1207
  }
1208
1208
  else if (p.points !== undefined)
1209
1209
  {
1210
1210
  // poly
1211
- overlayContext.beginPath();
1211
+ debugContext.beginPath();
1212
1212
  for (const point of p.points)
1213
1213
  {
1214
1214
  const p2 = point.scale(scale).floor();
1215
- overlayContext.lineTo(p2.x, p2.y);
1215
+ debugContext.lineTo(p2.x, p2.y);
1216
1216
  }
1217
- overlayContext.closePath();
1218
- p.fill && overlayContext.fill();
1219
- overlayContext.stroke();
1217
+ debugContext.closePath();
1218
+ p.fill && debugContext.fill();
1219
+ debugContext.stroke();
1220
1220
  }
1221
1221
  else if (p.size === 0 || (p.size.x === 0 && p.size.y === 0))
1222
1222
  {
1223
1223
  // point
1224
1224
  const pointSize = debugPointSize * scale;
1225
- overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
1226
- overlayContext.fillRect(-1, -pointSize/2, 3, pointSize);
1225
+ debugContext.fillRect(-pointSize/2, -1, pointSize, 3);
1226
+ debugContext.fillRect(-1, -pointSize/2, 3, pointSize);
1227
1227
  }
1228
1228
  else if (p.size.x !== undefined)
1229
1229
  {
1230
1230
  // rect
1231
1231
  const s = p.size.scale(scale).floor();
1232
1232
  const w = s.x, h = s.y;
1233
- p.fill && overlayContext.fillRect(-w/2|0, -h/2|0, w, h);
1234
- overlayContext.strokeRect(-w/2|0, -h/2|0, w, h);
1233
+ p.fill && debugContext.fillRect(-w/2|0, -h/2|0, w, h);
1234
+ debugContext.strokeRect(-w/2|0, -h/2|0, w, h);
1235
1235
  }
1236
1236
  else
1237
1237
  {
1238
1238
  // circle
1239
- overlayContext.beginPath();
1240
- overlayContext.arc(0, 0, p.size*scale/2, 0, 9);
1241
- p.fill && overlayContext.fill();
1242
- overlayContext.stroke();
1239
+ debugContext.beginPath();
1240
+ debugContext.arc(0, 0, p.size*scale/2, 0, 9);
1241
+ p.fill && debugContext.fill();
1242
+ debugContext.stroke();
1243
1243
  }
1244
1244
 
1245
- overlayContext.restore();
1245
+ debugContext.restore();
1246
1246
  });
1247
1247
 
1248
1248
  // remove expired primitives
@@ -1251,55 +1251,53 @@ function debugRender()
1251
1251
 
1252
1252
  if (debugObject)
1253
1253
  {
1254
- const saveContext = mainContext;
1255
- mainContext = overlayContext;
1256
1254
  const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
1257
1255
  raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
1258
1256
  drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5));
1259
1257
 
1260
- const debugText = 'mouse pos = ' + mousePos +
1261
- '\nmouse collision = ' + tileCollisionGetData(mousePos) +
1262
- '\n\n--- object info ---\n' +
1263
- debugObject.toString();
1258
+ let debugText = 'mouse pos = ' + mousePos;
1259
+ if (tileCollisionLayers.length)
1260
+ debugText += '\nmouse collision = ' + tileCollisionGetData(mousePos);
1261
+ debugText += '\n\n--- object info ---\n';
1262
+ debugText += debugObject.toString();
1264
1263
  drawTextScreen(debugText, mousePosScreen, 24, rgb(), .05, undefined, 'center', 'monospace');
1265
- mainContext = saveContext;
1266
1264
  }
1267
1265
 
1268
1266
  {
1269
1267
  // draw debug overlay
1270
1268
  const fontSize = 20;
1271
1269
  const lineHeight = fontSize * 1.2 | 0;
1272
- overlayContext.save();
1273
- overlayContext.fillStyle = '#fff';
1274
- overlayContext.textAlign = 'left';
1275
- overlayContext.textBaseline = 'top';
1276
- overlayContext.font = fontSize + 'px monospace';
1277
- overlayContext.shadowColor = '#000';
1278
- overlayContext.shadowBlur = 9;
1270
+ debugContext.save();
1271
+ debugContext.fillStyle = '#fff';
1272
+ debugContext.textAlign = 'left';
1273
+ debugContext.textBaseline = 'top';
1274
+ debugContext.font = fontSize + 'px monospace';
1275
+ debugContext.shadowColor = '#000';
1276
+ debugContext.shadowBlur = 9;
1279
1277
 
1280
1278
  let x = 9, y = 0, h = lineHeight;
1281
1279
  if (debugOverlay)
1282
1280
  {
1283
- overlayContext.fillText(`${engineName} v${engineVersion}`, x, y += h/2 );
1284
- overlayContext.fillText('Time: ' + formatTime(time), x, y += h);
1285
- overlayContext.fillText('FPS: ' + averageFPS.toFixed(1) + (glEnable?' WebGL':' Canvas2D'),
1281
+ debugContext.fillText(`${engineName} v${engineVersion}`, x, y += h/2 );
1282
+ debugContext.fillText('Time: ' + formatTime(time), x, y += h);
1283
+ debugContext.fillText('FPS: ' + averageFPS.toFixed(1) + (glEnable?' WebGL':' Canvas2D'),
1286
1284
  x, y += h);
1287
- overlayContext.fillText('Objects: ' + engineObjects.length, x, y += h);
1288
- overlayContext.fillText('Draw Count: ' + drawCount, x, y += h);
1289
- overlayContext.fillText('---------', x, y += h);
1290
- overlayContext.fillStyle = '#f00';
1291
- overlayContext.fillText('ESC: Debug Overlay', x, y += h);
1292
- overlayContext.fillStyle = debugPhysics ? '#f00' : '#fff';
1293
- overlayContext.fillText('1: Debug Physics', x, y += h);
1294
- overlayContext.fillStyle = debugParticles ? '#f00' : '#fff';
1295
- overlayContext.fillText('2: Debug Particles', x, y += h);
1296
- overlayContext.fillStyle = debugGamepads ? '#f00' : '#fff';
1297
- overlayContext.fillText('3: Debug Gamepads', x, y += h);
1298
- overlayContext.fillStyle = debugRaycast ? '#f00' : '#fff';
1299
- overlayContext.fillText('4: Debug Raycasts', x, y += h);
1300
- overlayContext.fillStyle = '#fff';
1301
- overlayContext.fillText('5: Save Screenshot', x, y += h);
1302
- overlayContext.fillText('6: Toggle Video Capture', x, y += h);
1285
+ debugContext.fillText('Objects: ' + engineObjects.length, x, y += h);
1286
+ debugContext.fillText('Draw Count: ' + drawCount, x, y += h);
1287
+ debugContext.fillText('---------', x, y += h);
1288
+ debugContext.fillStyle = '#f00';
1289
+ debugContext.fillText('ESC: Debug Overlay', x, y += h);
1290
+ debugContext.fillStyle = debugPhysics ? '#f00' : '#fff';
1291
+ debugContext.fillText('1: Debug Physics', x, y += h);
1292
+ debugContext.fillStyle = debugParticles ? '#f00' : '#fff';
1293
+ debugContext.fillText('2: Debug Particles', x, y += h);
1294
+ debugContext.fillStyle = debugGamepads ? '#f00' : '#fff';
1295
+ debugContext.fillText('3: Debug Gamepads', x, y += h);
1296
+ debugContext.fillStyle = debugRaycast ? '#f00' : '#fff';
1297
+ debugContext.fillText('4: Debug Raycasts', x, y += h);
1298
+ debugContext.fillStyle = '#fff';
1299
+ debugContext.fillText('5: Save Screenshot', x, y += h);
1300
+ debugContext.fillText('6: Toggle Video Capture', x, y += h);
1303
1301
 
1304
1302
  let keysPressed = '';
1305
1303
  let mousePressed = '';
@@ -1312,8 +1310,8 @@ function debugRender()
1312
1310
  else if (keyIsDown(i, 0))
1313
1311
  keysPressed += i + ' ' ;
1314
1312
  }
1315
- mousePressed && overlayContext.fillText('Mouse: ' + mousePressed, x, y += h);
1316
- keysPressed && overlayContext.fillText('Keys: ' + keysPressed, x, y += h);
1313
+ mousePressed && debugContext.fillText('Mouse: ' + mousePressed, x, y += h);
1314
+ keysPressed && debugContext.fillText('Keys: ' + keysPressed, x, y += h);
1317
1315
 
1318
1316
  // show gamepad buttons
1319
1317
  for (let i = 1; i < inputData.length; i++)
@@ -1325,18 +1323,18 @@ function debugRender()
1325
1323
  if (keyIsDown(j, i))
1326
1324
  buttonsPressed += j + ' ' ;
1327
1325
  }
1328
- buttonsPressed && overlayContext.fillText(`Gamepad ${i-1}: ` + buttonsPressed, x, y += h);
1326
+ buttonsPressed && debugContext.fillText(`Gamepad ${i-1}: ` + buttonsPressed, x, y += h);
1329
1327
  }
1330
1328
  }
1331
1329
  else
1332
1330
  {
1333
- overlayContext.fillText(debugPhysics ? 'Debug Physics' : '', x, y += h);
1334
- overlayContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
1335
- overlayContext.fillText(debugRaycast ? 'Debug Raycasts' : '', x, y += h);
1336
- overlayContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
1331
+ debugContext.fillText(debugPhysics ? 'Debug Physics' : '', x, y += h);
1332
+ debugContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
1333
+ debugContext.fillText(debugRaycast ? 'Debug Raycasts' : '', x, y += h);
1334
+ debugContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
1337
1335
  }
1338
1336
 
1339
- overlayContext.restore();
1337
+ debugContext.restore();
1340
1338
  }
1341
1339
  }
1342
1340
 
@@ -1387,7 +1385,7 @@ function debugVideoCaptureStart()
1387
1385
  // start recording
1388
1386
  LOG('Video capture started.');
1389
1387
  debugVideoCapture.start();
1390
- debugVideoCaptureTimer = new Timer(0);
1388
+ debugVideoCaptureTimer = new Timer(0, true);
1391
1389
 
1392
1390
  if (!debugVideoCaptureIcon)
1393
1391
  {
@@ -1585,11 +1583,7 @@ function percent(value, valueA, valueB)
1585
1583
  * @return {number}
1586
1584
  * @memberof Utilities */
1587
1585
  function lerp(valueA, valueB, percent)
1588
- {
1589
- if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
1590
- console.warn('lerp() parameter order changed! use lerp(start, end, p)');
1591
- return valueA + clamp(percent) * (valueB-valueA);
1592
- }
1586
+ { return valueA + clamp(percent) * (valueB-valueA); }
1593
1587
 
1594
1588
  /** Gets percent between percentA and percentB and linearly interpolates between lerpA and lerpB
1595
1589
  * A shortcut for lerp(lerpA, lerpB, percent(value, percentA, percentB))
@@ -1620,11 +1614,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
1620
1614
  * @return {number}
1621
1615
  * @memberof Utilities */
1622
1616
  function lerpWrap(valueA, valueB, percent, wrapSize=1)
1623
- {
1624
- if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
1625
- console.warn('lerpWrap() parameter order changed! use lerpWrap(start, end, p)');
1626
- return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize);
1627
- }
1617
+ { return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
1628
1618
 
1629
1619
  /** Returns signed wrapped distance between the two angles passed in
1630
1620
  * @param {number} angleA
@@ -2764,15 +2754,7 @@ let canvasFixedSize = vec2();
2764
2754
  * @type {boolean}
2765
2755
  * @default
2766
2756
  * @memberof Settings */
2767
- let canvasPixelated = true;
2768
-
2769
- /** Use nearest canvas scaling for more pixelated look
2770
- * - If enabled sets css image-rendering:pixelated
2771
- * - This defaults to false because text looks better with smoothing
2772
- * @type {boolean}
2773
- * @default
2774
- * @memberof Settings */
2775
- let overlayCanvasPixelated = false;
2757
+ let canvasPixelated = false;
2776
2758
 
2777
2759
  /** Disables texture filtering for crisper pixel art
2778
2760
  * @type {boolean}
@@ -2820,13 +2802,19 @@ let glCircleSides = 32;
2820
2802
  * @type {Vector2}
2821
2803
  * @default Vector2(16,16)
2822
2804
  * @memberof Settings */
2823
- let tileSizeDefault = vec2(16);
2805
+ let tileDefaultSize = vec2(16);
2824
2806
 
2825
- /** How many pixels smaller to draw tiles to prevent bleeding from neighbors
2807
+ /** Default padding pixels around tiles
2826
2808
  * @type {number}
2827
2809
  * @default
2828
2810
  * @memberof Settings */
2829
- let tileFixBleedScale = 0;
2811
+ let tileDefaultPadding = 0;
2812
+
2813
+ /** Default amount of pixels smaller to draw tiles to prevent neighbor bleeding
2814
+ * @type {number}
2815
+ * @default
2816
+ * @memberof Settings */
2817
+ let tileDefaultBleed = 0;
2830
2818
 
2831
2819
  ///////////////////////////////////////////////////////////////////////////////
2832
2820
  // Object settings
@@ -3064,7 +3052,6 @@ function setCanvasMaxAspect(aspect) { canvasMaxAspect = aspect; }
3064
3052
  function setCanvasFixedSize(size) { canvasFixedSize = size.copy(); }
3065
3053
 
3066
3054
  /** Use nearest scaling algorithm for canvas for more pixelated look
3067
- * - If enabled sets css image-rendering:pixelated
3068
3055
  * @param {boolean} pixelated
3069
3056
  * @memberof Settings */
3070
3057
  function setCanvasPixelated(pixelated)
@@ -3076,18 +3063,6 @@ function setCanvasPixelated(pixelated)
3076
3063
  glCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
3077
3064
  }
3078
3065
 
3079
- /** Use nearest scaling algorithm for canvas for more pixelated look
3080
- * - If enabled sets css image-rendering:pixelated
3081
- * - This defaults to false because text looks better with smoothing
3082
- * @param {boolean} pixelated
3083
- * @memberof Settings */
3084
- function setOverlayCanvasPixelated(pixelated)
3085
- {
3086
- overlayCanvasPixelated = pixelated;
3087
- if (overlayCanvas)
3088
- overlayCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
3089
- }
3090
-
3091
3066
  /** Disables texture filtering for crisper pixel art
3092
3067
  * @param {boolean} pixelated
3093
3068
  * @memberof Settings */
@@ -3131,12 +3106,17 @@ function setGLCircleSides(sides) { glCircleSides = sides; }
3131
3106
  /** Set default size of tiles in pixels
3132
3107
  * @param {Vector2} size
3133
3108
  * @memberof Settings */
3134
- function setTileSizeDefault(size) { tileSizeDefault = size.copy(); }
3109
+ function setTileDefaultSize(size) { tileDefaultSize = size.copy(); }
3135
3110
 
3136
- /** Set to prevent tile bleeding from neighbors in pixels
3137
- * @param {number} scale
3111
+ /** Default padding pixels around tiles
3112
+ * @param {number} padding
3138
3113
  * @memberof Settings */
3139
- function setTileFixBleedScale(scale) { tileFixBleedScale = scale; }
3114
+ function setTileDefaultPadding(padding) { tileDefaultPadding = padding; }
3115
+
3116
+ /** Default amount of pixels smaller to draw tiles to prevent neighbor bleeding
3117
+ * @param {number} bleed
3118
+ * @memberof Settings */
3119
+ function setTileDefaultBleed(bleed) { tileDefaultBleed = bleed; }
3140
3120
 
3141
3121
  /** Set if collisions between objects are enabled
3142
3122
  * @param {boolean} enable
@@ -3287,7 +3267,7 @@ function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUn
3287
3267
  /** Set if watermark with FPS should be shown
3288
3268
  * @param {boolean} show
3289
3269
  * @memberof Debug */
3290
- function setShowWatermark(show) { showWatermark = show; }
3270
+ function setDebugWatermark(show) { debugWatermark = show; }
3291
3271
 
3292
3272
  /** Set key code used to toggle debug mode, Esc by default
3293
3273
  * @param {string} key
@@ -3723,10 +3703,11 @@ class EngineObject
3723
3703
  * @return {number} -1 if this.mirror is true, or 1 if not mirrored */
3724
3704
  getMirrorSign() { return this.mirror ? -1 : 1; }
3725
3705
 
3726
- /** Attaches a child to this with a given local transform
3706
+ /** Attaches a child to this with a local transform, returns child for chaining
3727
3707
  * @param {EngineObject} child
3728
3708
  * @param {Vector2} [localPos=(0,0)]
3729
- * @param {number} [localAngle] */
3709
+ * @param {number} [localAngle]
3710
+ * @return {EngineObject} The child object added */
3730
3711
  addChild(child, localPos=vec2(), localAngle=0)
3731
3712
  {
3732
3713
  ASSERT(!child.parent && !this.children.includes(child));
@@ -3736,6 +3717,7 @@ class EngineObject
3736
3717
  child.parent = this;
3737
3718
  child.localPos = localPos.copy();
3738
3719
  child.localAngle = localAngle;
3720
+ return child;
3739
3721
  }
3740
3722
 
3741
3723
  /** Removes a child from this one
@@ -3826,7 +3808,6 @@ class EngineObject
3826
3808
  * There are 3 canvas/contexts available to draw to...
3827
3809
  * mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
3828
3810
  * glCanvas - Used by the accelerated WebGL batch rendering system.
3829
- * overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
3830
3811
  *
3831
3812
  * The WebGL rendering system is very fast with some caveats...
3832
3813
  * - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
@@ -3846,16 +3827,6 @@ let mainCanvas;
3846
3827
  * @memberof Draw */
3847
3828
  let mainContext;
3848
3829
 
3849
- /** A canvas that appears on top of everything the same size as mainCanvas
3850
- * @type {HTMLCanvasElement}
3851
- * @memberof Draw */
3852
- let overlayCanvas;
3853
-
3854
- /** 2d context for overlayCanvas
3855
- * @type {CanvasRenderingContext2D}
3856
- * @memberof Draw */
3857
- let overlayContext;
3858
-
3859
3830
  /** The default canvas to use for drawing, usually mainCanvas
3860
3831
  * @type {HTMLCanvasElement|OffscreenCanvas}
3861
3832
  * @memberof Draw */
@@ -3876,6 +3847,16 @@ let workCanvas;
3876
3847
  * @memberof Draw */
3877
3848
  let workContext;
3878
3849
 
3850
+ /** Offscreen canvas with willReadFrequently that can be used for image processing
3851
+ * @type {OffscreenCanvas}
3852
+ * @memberof Draw */
3853
+ let workReadCanvas;
3854
+
3855
+ /** Offscreen canvas with willReadFrequently that can be used for image processing
3856
+ * @type {OffscreenCanvasRenderingContext2D}
3857
+ * @memberof Draw */
3858
+ let workReadContext;
3859
+
3879
3860
  /** The size of the main canvas (and other secondary canvases)
3880
3861
  * @type {Vector2}
3881
3862
  * @memberof Draw */
@@ -3898,7 +3879,7 @@ let drawCount;
3898
3879
  * - This can take vecs or floats for easier use and conversion
3899
3880
  * - If an index is passed in, the tile size and index will determine the position
3900
3881
  * @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
3901
- * @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
3882
+ * @param {Vector2|number} [size] - Size of tile in pixels
3902
3883
  * @param {number} [textureIndex] - Texture index to use
3903
3884
  * @param {number} [padding] - How many pixels padding around tiles
3904
3885
  * @return {TileInfo}
@@ -3908,7 +3889,7 @@ let drawCount;
3908
3889
  * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
3909
3890
  * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
3910
3891
  * @memberof Draw */
3911
- function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
3892
+ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding)
3912
3893
  {
3913
3894
  if (headlessMode)
3914
3895
  return new TileInfo;
@@ -3947,13 +3928,13 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
3947
3928
  class TileInfo
3948
3929
  {
3949
3930
  /** Create a tile info object
3950
- * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
3951
- * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
3952
- * @param {number} [textureIndex] - Texture index to use
3953
- * @param {number} [padding] - How many pixels padding around tiles
3954
- * @param {number} [bleedScale] - How many pixels smaller to draw tiles
3931
+ * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
3932
+ * @param {Vector2} [size] - Size of tile in pixels
3933
+ * @param {number} [textureIndex] - Texture index to use
3934
+ * @param {number} [padding] - How many pixels padding around tiles
3935
+ * @param {number} [bleed] - How many pixels smaller to draw tiles
3955
3936
  */
3956
- constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0, bleedScale=tileFixBleedScale)
3937
+ constructor(pos=vec2(), size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
3957
3938
  {
3958
3939
  /** @property {Vector2} - Top left corner of tile in pixels */
3959
3940
  this.pos = pos.copy();
@@ -3966,7 +3947,7 @@ class TileInfo
3966
3947
  /** @property {TextureInfo} - The texture info for this tile */
3967
3948
  this.textureInfo = textureInfos[this.textureIndex];
3968
3949
  /** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
3969
- this.bleedScale = bleedScale;
3950
+ this.bleed = bleed;
3970
3951
  }
3971
3952
 
3972
3953
  /** Returns a copy of this tile offset by a vector
@@ -3974,7 +3955,7 @@ class TileInfo
3974
3955
  * @return {TileInfo}
3975
3956
  */
3976
3957
  offset(offset)
3977
- { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleedScale); }
3958
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleed); }
3978
3959
 
3979
3960
  /** Returns a copy of this tile offset by a number of animation frames
3980
3961
  * @param {number} frame - Offset to apply in animation frames
@@ -3997,7 +3978,7 @@ class TileInfo
3997
3978
  this.size = textureInfo.size.copy();
3998
3979
  this.textureInfo = textureInfo;
3999
3980
  // do not use padding or bleed
4000
- this.bleedScale = this.padding = 0;
3981
+ this.bleed = this.padding = 0;
4001
3982
  return this;
4002
3983
  }
4003
3984
  }
@@ -4063,7 +4044,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
4063
4044
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
4064
4045
 
4065
4046
  const textureInfo = tileInfo && tileInfo.textureInfo;
4066
- const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
4047
+ const bleed = tileInfo?.bleed ?? 0;
4067
4048
  if (useWebGL && glEnable)
4068
4049
  {
4069
4050
  ASSERT(!!glContext, 'WebGL is not enabled!');
@@ -4078,13 +4059,13 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
4078
4059
  const w = tileInfo.size.x * sizeInverse.x;
4079
4060
  const h = tileInfo.size.y * sizeInverse.y;
4080
4061
  glSetTexture(textureInfo.glTexture);
4081
- if (bleedScale)
4062
+ if (bleed)
4082
4063
  {
4083
- const tileImageFixBleedX = sizeInverse.x*bleedScale;
4084
- const tileImageFixBleedY = sizeInverse.y*bleedScale;
4064
+ const bleedX = sizeInverse.x*bleed;
4065
+ const bleedY = sizeInverse.y*bleed;
4085
4066
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
4086
- x + tileImageFixBleedX, y + tileImageFixBleedY,
4087
- x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
4067
+ x + bleedX, y + bleedY,
4068
+ x - bleedX + w, y - bleedY + h,
4088
4069
  color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
4089
4070
  }
4090
4071
  else
@@ -4112,7 +4093,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
4112
4093
  // calculate uvs and render
4113
4094
  const x = tileInfo.pos.x, y = tileInfo.pos.y;
4114
4095
  const w = tileInfo.size.x, h = tileInfo.size.y;
4115
- drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor, bleedScale);
4096
+ drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor, bleed);
4116
4097
  }
4117
4098
  else
4118
4099
  {
@@ -4477,26 +4458,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
4477
4458
  drawTextScreen(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
4478
4459
  }
4479
4460
 
4480
- /** Draw text on overlay canvas in world space
4481
- * Automatically splits new lines into rows
4482
- * @param {string|number} text
4483
- * @param {Vector2} pos
4484
- * @param {number} [size]
4485
- * @param {Color} [color=(1,1,1,1)]
4486
- * @param {number} [lineWidth]
4487
- * @param {Color} [lineColor=(0,0,0,1)]
4488
- * @param {CanvasTextAlign} [textAlign='center']
4489
- * @param {string} [font=fontDefault]
4490
- * @param {string} [fontStyle]
4491
- * @param {number} [maxWidth]
4492
- * @param {number} [angle]
4493
- * @memberof Draw */
4494
- function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0)
4495
- {
4496
- drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, overlayContext);
4497
- }
4498
-
4499
- /** Draw text on overlay canvas in screen space
4461
+ /** Draw text in screen space
4500
4462
  * Automatically splits new lines into rows
4501
4463
  * @param {string|number} text
4502
4464
  * @param {Vector2} pos
@@ -4509,9 +4471,9 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
4509
4471
  * @param {string} [fontStyle]
4510
4472
  * @param {number} [maxWidth]
4511
4473
  * @param {number} [angle]
4512
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
4474
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
4513
4475
  * @memberof Draw */
4514
- function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=overlayContext)
4476
+ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=mainContext)
4515
4477
  {
4516
4478
  ASSERT(isString(text), 'text must be a string');
4517
4479
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -4555,16 +4517,16 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
4555
4517
  * @memberof Draw */
4556
4518
  function screenToWorld(screenPos)
4557
4519
  {
4520
+ ASSERT(isVector2(screenPos), 'screenPos must be a vec2');
4521
+
4558
4522
  let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
4559
4523
  let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
4560
4524
  if (cameraAngle)
4561
4525
  {
4562
4526
  // apply camera rotation
4563
4527
  const c = cos(-cameraAngle), s = sin(-cameraAngle);
4564
- const rotatedX = x * c - y * s;
4565
- const rotatedY = x * s + y * c;
4566
- x = rotatedX;
4567
- y = rotatedY;
4528
+ const xr = x * c - y * s, yr = x * s + y * c;
4529
+ x = xr; y = yr;
4568
4530
  }
4569
4531
  return new Vector2(x + cameraPos.x, y + cameraPos.y);
4570
4532
  }
@@ -4575,16 +4537,16 @@ function screenToWorld(screenPos)
4575
4537
  * @memberof Draw */
4576
4538
  function worldToScreen(worldPos)
4577
4539
  {
4540
+ ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
4541
+
4578
4542
  let x = worldPos.x - cameraPos.x;
4579
4543
  let y = worldPos.y - cameraPos.y;
4580
4544
  if (cameraAngle)
4581
4545
  {
4582
4546
  // apply inverse camera rotation
4583
4547
  const c = cos(cameraAngle), s = sin(cameraAngle);
4584
- const rotatedX = x * c - y * s;
4585
- const rotatedY = x * s + y * c;
4586
- x = rotatedX;
4587
- y = rotatedY;
4548
+ const xr = x * c - y * s, yr = x * s + y * c;
4549
+ x = xr; y = yr;
4588
4550
  }
4589
4551
  return new Vector2
4590
4552
  (
@@ -4599,16 +4561,16 @@ function worldToScreen(worldPos)
4599
4561
  * @memberof Draw */
4600
4562
  function screenToWorldDelta(screenDelta)
4601
4563
  {
4564
+ ASSERT(isVector2(screenDelta), 'screenDelta must be a vec2');
4565
+
4602
4566
  let x = screenDelta.x / cameraScale;
4603
4567
  let y = screenDelta.y / -cameraScale;
4604
4568
  if (cameraAngle)
4605
4569
  {
4606
4570
  // apply camera rotation
4607
4571
  const c = cos(-cameraAngle), s = sin(-cameraAngle);
4608
- const rotatedX = x * c - y * s;
4609
- const rotatedY = x * s + y * c;
4610
- x = rotatedX;
4611
- y = rotatedY;
4572
+ const xr = x * c - y * s, yr = x * s + y * c;
4573
+ x = xr; y = yr;
4612
4574
  }
4613
4575
  return new Vector2(x, y);
4614
4576
  }
@@ -4619,16 +4581,16 @@ function screenToWorldDelta(screenDelta)
4619
4581
  * @memberof Draw */
4620
4582
  function worldToScreenDelta(worldDelta)
4621
4583
  {
4584
+ ASSERT(isVector2(worldDelta), 'worldDelta must be a vec2');
4585
+
4622
4586
  let x = worldDelta.x;
4623
4587
  let y = worldDelta.y;
4624
4588
  if (cameraAngle)
4625
4589
  {
4626
4590
  // apply inverse camera rotation
4627
4591
  const c = cos(cameraAngle), s = sin(cameraAngle);
4628
- const rotatedX = x * c - y * s;
4629
- const rotatedY = x * s + y * c;
4630
- x = rotatedX;
4631
- y = rotatedY;
4592
+ const xr = x * c - y * s, yr = x * s + y * c;
4593
+ x = xr; y = yr;
4632
4594
  }
4633
4595
  return new Vector2(x * cameraScale, y * -cameraScale);
4634
4596
  }
@@ -4641,6 +4603,10 @@ function worldToScreenDelta(worldDelta)
4641
4603
  * @memberof Draw */
4642
4604
  function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
4643
4605
  {
4606
+ ASSERT(isVector2(screenPos), 'screenPos must be a vec2');
4607
+ ASSERT(isVector2(screenSize), 'screenSize must be a vec2');
4608
+ ASSERT(isNumber(screenAngle), 'screenAngle must be a number');
4609
+
4644
4610
  return [
4645
4611
  screenToWorld(screenPos),
4646
4612
  screenSize.scale(1/cameraScale),
@@ -4656,6 +4622,10 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
4656
4622
  * @memberof Draw */
4657
4623
  function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
4658
4624
  {
4625
+ ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
4626
+ ASSERT(isVector2(worldSize), 'worldSize must be a vec2');
4627
+ ASSERT(isNumber(worldAngle), 'worldAngle must be a number');
4628
+
4659
4629
  return [
4660
4630
  worldToScreen(worldPos),
4661
4631
  worldSize.scale(cameraScale),
@@ -4668,8 +4638,8 @@ function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
4668
4638
  * @memberof Draw */
4669
4639
  function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
4670
4640
 
4671
- /** Check if a point or circle is on screen
4672
- * If size is a Vector2, uses the largest dimension as diameter
4641
+ /** Check if a box, point, or circle is on screen with a circle test
4642
+ * If size is a Vector2, uses the length as diameter
4673
4643
  * This can be used to cull offscreen objects from render or update
4674
4644
  * @param {Vector2} pos - world space position
4675
4645
  * @param {Vector2|number} size - world space size or diameter
@@ -4677,12 +4647,30 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
4677
4647
  * @memberof Draw */
4678
4648
  function isOnScreen(pos, size=0)
4679
4649
  {
4680
- pos = worldToScreen(pos);
4650
+ ASSERT(isVector2(pos), 'pos must be a vec2');
4651
+ ASSERT(isVector2(size) || isNumber(size), 'size must be a vec2 or number');
4652
+
4653
+ // optimized circle on screen test
4654
+ // pos = worldToScreen(pos);
4655
+ let x = pos.x - cameraPos.x;
4656
+ let y = pos.y - cameraPos.y;
4657
+ if (cameraAngle)
4658
+ {
4659
+ // apply inverse camera rotation
4660
+ const c = cos(cameraAngle), s = sin(cameraAngle);
4661
+ const xr = x * c - y * s, yr = x * s + y * c;
4662
+ x = xr; y = yr;
4663
+ }
4664
+ x *= cameraScale*2; y *= -cameraScale*2;
4665
+
4681
4666
  if (size instanceof Vector2)
4682
- size = max(size.x, size.y); // use largest dimension
4683
- size *= cameraScale/2;
4684
- return pos.x + size > 0 && pos.x - size < mainCanvasSize.x &&
4685
- pos.y + size > 0 && pos.y - size < mainCanvasSize.y;
4667
+ size = size.length(); // use length of vector as diameter
4668
+ size *= cameraScale;
4669
+
4670
+ // check against screen bounds
4671
+ const w = mainCanvasSize.x, h = mainCanvasSize.y;
4672
+ return x + size > -w && x - size < w &&
4673
+ y + size > -h && y - size < h;
4686
4674
  }
4687
4675
 
4688
4676
  /** Enable normal or additive blend mode
@@ -4696,18 +4684,19 @@ function setBlendMode(additive=false, context)
4696
4684
  context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
4697
4685
  }
4698
4686
 
4699
- /** Combines all LittleJS canvases onto the main canvas and clears them
4700
- * This is necessary for things like saving a screenshot
4687
+ /** Combines LittleJS canvases onto the main canvas
4688
+ * This is necessary for things like screenshots and video
4701
4689
  * @memberof Draw */
4702
4690
  function combineCanvases()
4703
4691
  {
4704
- // combine canvases
4705
- glCopyToContext(mainContext);
4706
- mainContext.drawImage(overlayCanvas, 0, 0);
4707
-
4708
- // clear canvases
4709
- glClearCanvas();
4710
- overlayCanvas.width |= 0;
4692
+ const w = mainCanvasSize.x, h = mainCanvasSize.y;
4693
+ workCanvas.width = w;
4694
+ workCanvas.height = h;
4695
+ workContext.fillRect(0,0,w,h); // remove background alpha
4696
+ glCopyToContext(workContext);
4697
+ workContext.drawImage(mainCanvas, 0, 0);
4698
+ mainCanvas.width |= 0;
4699
+ mainContext.drawImage(workCanvas, 0, 0);
4711
4700
  }
4712
4701
 
4713
4702
  /** Helper function to draw an image with color and additive color applied
@@ -4724,18 +4713,18 @@ function combineCanvases()
4724
4713
  * @param {number} dHeight
4725
4714
  * @param {Color} color
4726
4715
  * @param {Color} [additiveColor]
4727
- * @param {number} [bleedScale] - How much to shrink the source, used to fix bleeding
4716
+ * @param {number} [bleed] - How many pixels to shrink the source, used to fix bleeding
4728
4717
  * @memberof Draw */
4729
- function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleedScale=0)
4718
+ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleed=0)
4730
4719
  {
4731
4720
  function isWhite(c) { return c.r >= 1 && c.g >= 1 && c.b >= 1; }
4732
4721
  function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
4733
- const sx2 = bleedScale;
4734
- const sy2 = bleedScale;
4722
+ const sx2 = bleed;
4723
+ const sy2 = bleed;
4735
4724
  sWidth = max(1,sWidth|0);
4736
4725
  sHeight = max(1,sHeight|0);
4737
- const sWidth2 = sWidth - 2*bleedScale;
4738
- const sHeight2 = sHeight - 2*bleedScale;
4726
+ const sWidth2 = sWidth - 2*bleed;
4727
+ const sHeight2 = sHeight - 2*bleed;
4739
4728
  if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
4740
4729
  {
4741
4730
  // white texture with no additive alpha, no need to tint
@@ -4746,12 +4735,12 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
4746
4735
  else
4747
4736
  {
4748
4737
  // copy to offscreen canvas
4749
- workCanvas.width = sWidth;
4750
- workCanvas.height = sHeight;
4751
- workContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
4738
+ workReadCanvas.width = sWidth;
4739
+ workReadCanvas.height = sHeight;
4740
+ workReadContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
4752
4741
 
4753
4742
  // tint image using offscreen work context
4754
- const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
4743
+ const imageData = workReadContext.getImageData(0, 0, sWidth, sHeight);
4755
4744
  const data = imageData.data;
4756
4745
  if (additiveColor && !isBlack(additiveColor))
4757
4746
  {
@@ -4760,8 +4749,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
4760
4749
  const colorAdd = [additiveColor.r * 255, additiveColor.g * 255, additiveColor.b * 255, additiveColor.a * 255];
4761
4750
  for (let i = 0; i < data.length; ++i)
4762
4751
  data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
4763
- workContext.putImageData(imageData, 0, 0);
4764
- context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
4752
+ workReadContext.putImageData(imageData, 0, 0);
4753
+ context.drawImage(workReadCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
4765
4754
  }
4766
4755
  else
4767
4756
  {
@@ -4772,9 +4761,9 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
4772
4761
  data[i+1] *= color.g;
4773
4762
  data[i+2] *= color.b;
4774
4763
  }
4775
- workContext.putImageData(imageData, 0, 0);
4764
+ workReadContext.putImageData(imageData, 0, 0);
4776
4765
  context.globalAlpha = color.a;
4777
- context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
4766
+ context.drawImage(workReadCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
4778
4767
  context.globalAlpha = 1;
4779
4768
  }
4780
4769
  }
@@ -4836,7 +4825,7 @@ class FontImage
4836
4825
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
4837
4826
  {
4838
4827
  // load default font image
4839
- if (!engineFontImage)
4828
+ if (!image && !engineFontImage)
4840
4829
  {
4841
4830
  engineFontImage = new Image;
4842
4831
  engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
@@ -4859,23 +4848,14 @@ class FontImage
4859
4848
  this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
4860
4849
  }
4861
4850
 
4862
- /** Draw text on overlay canvas in world space using the image font
4863
- * @param {string|number} text
4864
- * @param {Vector2} pos
4865
- * @param {number} [scale]
4866
- * @param {boolean} [center]
4867
- */
4868
- drawTextOverlay(text, pos, scale=4, center)
4869
- { this.drawText(text, pos, scale, center, overlayContext); }
4870
-
4871
- /** Draw text on overlay canvas in screen space using the image font
4851
+ /** Draw text in screen space using the image font
4872
4852
  * @param {string|number} text
4873
4853
  * @param {Vector2} pos
4874
4854
  * @param {number} [scale]
4875
4855
  * @param {boolean} [center]
4876
4856
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
4877
4857
  */
4878
- drawTextScreen(text, pos, scale=4, center=true, context=overlayContext)
4858
+ drawTextScreen(text, pos, scale=4, center=true, context=mainContext)
4879
4859
  {
4880
4860
  context.save();
4881
4861
  const size = this.tileSize;
@@ -5625,7 +5605,7 @@ function inputRender()
5625
5605
  return;
5626
5606
 
5627
5607
  // setup the canvas
5628
- const context = overlayContext;
5608
+ const context = mainContext;
5629
5609
  context.save();
5630
5610
  context.globalAlpha = alpha*touchGamepadAlpha;
5631
5611
  context.strokeStyle = '#fff';
@@ -5867,12 +5847,12 @@ class Sound
5867
5847
  ///////////////////////////////////////////////////////////////////////////////
5868
5848
 
5869
5849
  /**
5870
- * Sound Wave Object - Stores a wave sound for later use and can be played positionally
5871
- * - this can be used to play wave, mp3, and ogg files
5850
+ * Sound Wave Object - Loads and stores an audio file for later use
5851
+ * - this can be used to load and play wave, mp3, and ogg files
5872
5852
  * @extends Sound
5873
5853
  * @memberof Audio
5874
5854
  * @example
5875
- * // create a sound
5855
+ * // load an audio asset file
5876
5856
  * const sound_example = new SoundWave('sound.mp3');
5877
5857
  *
5878
5858
  * // play the sound
@@ -6824,8 +6804,8 @@ class TileLayer extends CanvasLayer
6824
6804
  if (!this.context) return;
6825
6805
 
6826
6806
  // save current render settings
6827
- /** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number]} */
6828
- this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale];
6807
+ /** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
6808
+ this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
6829
6809
 
6830
6810
  // set the draw canvas and context to this layer
6831
6811
  // use camera settings to match this layer's canvas
@@ -6834,6 +6814,7 @@ class TileLayer extends CanvasLayer
6834
6814
  cameraPos = this.size.scale(.5);
6835
6815
  const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
6836
6816
  cameraScale = tileSize.x;
6817
+ canvasClearColor = CLEAR_BLACK;
6837
6818
  mainCanvasSize = this.size.multiply(tileSize);
6838
6819
  if (clear)
6839
6820
  {
@@ -6843,7 +6824,7 @@ class TileLayer extends CanvasLayer
6843
6824
  }
6844
6825
 
6845
6826
  // disable smoothing for pixel art
6846
- this.context.imageSmoothingEnabled = !tilesPixelated;
6827
+ drawContext.imageSmoothingEnabled = !tilesPixelated;
6847
6828
 
6848
6829
  // setup gl rendering if enabled
6849
6830
  glPreRender();
@@ -6859,7 +6840,7 @@ class TileLayer extends CanvasLayer
6859
6840
  //debugSaveCanvas(this.canvas);
6860
6841
 
6861
6842
  // set stuff back to normal
6862
- [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
6843
+ [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
6863
6844
  }
6864
6845
 
6865
6846
  /** Draw the tile at a given position in the tile grid
@@ -7572,10 +7553,10 @@ class Medal
7572
7553
  */
7573
7554
  render(hidePercent=0)
7574
7555
  {
7575
- const context = overlayContext;
7556
+ const context = mainContext;
7576
7557
  const width = min(medalDisplaySize.x, mainCanvas.width);
7577
7558
  const height = medalDisplaySize.y;
7578
- const x = overlayCanvas.width - width;
7559
+ const x = mainCanvas.width - width;
7579
7560
  const y = -height*hidePercent;
7580
7561
  const backgroundColor = hsl(0,0,.9);
7581
7562
 
@@ -7616,7 +7597,7 @@ class Medal
7616
7597
  {
7617
7598
  // draw the image or icon
7618
7599
  if (this.image)
7619
- overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
7600
+ mainContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
7620
7601
  else
7621
7602
  drawTextScreen(this.icon, pos, size*.7, BLACK);
7622
7603
  }
@@ -7637,7 +7618,7 @@ class Medal
7637
7618
  * @namespace WebGL
7638
7619
  */
7639
7620
 
7640
- /** The WebGL canvas which appears above the main canvas and below the overlay canvas
7621
+ /** The WebGL canvas which appears below the main canvas
7641
7622
  * @type {HTMLCanvasElement}
7642
7623
  * @memberof WebGL */
7643
7624
  let glCanvas;
@@ -7667,7 +7648,7 @@ const gl_MAX_POLY_VERTEXES = gl_ARRAY_BUFFER_SIZE / gl_POLY_VERTEX_BYTE_STRIDE |
7667
7648
  ///////////////////////////////////////////////////////////////////////////////
7668
7649
 
7669
7650
  // Initialize WebGL, called automatically by the engine
7670
- function glInit()
7651
+ function glInit(rootElement)
7671
7652
  {
7672
7653
  // keep set of texture infos so they can be restored if context is lost
7673
7654
  glTextureInfos = new Set;
@@ -7691,8 +7672,7 @@ function glInit()
7691
7672
  return;
7692
7673
  }
7693
7674
 
7694
- // attach the WebGL canvas
7695
- const rootElement = mainCanvas.parentElement;
7675
+ // attach the WebGL canvas;
7696
7676
  rootElement.appendChild(glCanvas);
7697
7677
 
7698
7678
  // startup webgl
@@ -7910,6 +7890,9 @@ function glClearCanvas()
7910
7890
  glCanvas.width = drawCanvas.width;
7911
7891
  glCanvas.height = drawCanvas.height;
7912
7892
  glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
7893
+ const color = canvasClearColor;
7894
+ if (color.a > 0)
7895
+ glContext.clearColor(color.r, color.g, color.b, color.a);
7913
7896
  glContext.clear(glContext.COLOR_BUFFER_BIT);
7914
7897
  }
7915
7898
 
@@ -8139,7 +8122,6 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
8139
8122
  glFlush();
8140
8123
  glSetInstancedMode();
8141
8124
 
8142
- glPolyMode = false;
8143
8125
  let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
8144
8126
  glPositionData[offset++] = x;
8145
8127
  glPositionData[offset++] = y;
@@ -8660,13 +8642,12 @@ class PostProcessPlugin
8660
8642
  {
8661
8643
  /** Create global post processing shader
8662
8644
  * @param {string} shaderCode
8663
- * @param {boolean} [includeOverlay]
8664
8645
  * @param {boolean} [includeMainCanvas]
8665
8646
  * @example
8666
8647
  * // create the post process plugin object
8667
8648
  * new PostProcessPlugin(shaderCode);
8668
8649
  */
8669
- constructor(shaderCode, includeOverlay=false, includeMainCanvas=true)
8650
+ constructor(shaderCode, includeMainCanvas=true)
8670
8651
  {
8671
8652
  ASSERT(!postProcess, 'Post process already initialized');
8672
8653
  postProcess = this;
@@ -8738,17 +8719,19 @@ class PostProcessPlugin
8738
8719
  // clear out the buffer
8739
8720
  glFlush();
8740
8721
 
8741
- if (includeMainCanvas || includeOverlay)
8722
+ // setup texture
8723
+ glContext.activeTexture(glContext.TEXTURE0);
8724
+ glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
8725
+ if (includeMainCanvas)
8742
8726
  {
8743
- // copy WebGL to the main canvas
8744
- mainContext.drawImage(glCanvas, 0, 0);
8745
-
8746
- if (includeOverlay)
8747
- {
8748
- // copy overlay canvas so it will be included in post processing
8749
- mainContext.drawImage(overlayCanvas, 0, 0);
8750
- overlayCanvas.width |= 0; // clear overlay canvas
8751
- }
8727
+ // copy main canvas to work canvas
8728
+ workCanvas.width = mainCanvasSize.x;
8729
+ workCanvas.height = mainCanvasSize.y;
8730
+ glCopyToContext(workContext);
8731
+ workContext.drawImage(mainCanvas, 0, 0);
8732
+
8733
+ // copy work canvas to texture
8734
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
8752
8735
  }
8753
8736
 
8754
8737
  // setup shader program to draw a quad
@@ -8757,14 +8740,6 @@ class PostProcessPlugin
8757
8740
  glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, 1);
8758
8741
  glContext.disable(glContext.BLEND);
8759
8742
 
8760
- // set textures, pass in the 2d canvas and gl canvas in separate texture channels
8761
- glContext.activeTexture(glContext.TEXTURE0);
8762
- glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
8763
- if (includeMainCanvas || includeOverlay)
8764
- {
8765
- glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, mainCanvas);
8766
- }
8767
-
8768
8743
  // set vertex position attribute
8769
8744
  const vertexByteStride = 8;
8770
8745
  const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
@@ -8993,12 +8968,14 @@ class UISystemPlugin
8993
8968
  * // create the ui plugin object
8994
8969
  * new UISystemPlugin;
8995
8970
  */
8996
- constructor(context=overlayContext)
8971
+ constructor(context=mainContext)
8997
8972
  {
8998
8973
  ASSERT(!uiSystem, 'UI system already initialized');
8999
8974
  uiSystem = this;
9000
8975
 
9001
8976
  // default settings
8977
+ /** @property {boolean} - Activate when mouse is pressed down instead of clicked */
8978
+ this.activateOnPress = false;
9002
8979
  /** @property {Color} - Default fill color for UI elements */
9003
8980
  this.defaultColor = WHITE;
9004
8981
  /** @property {Color} - Default outline color for UI elements */
@@ -9650,13 +9627,15 @@ class UIObject
9650
9627
  uiSystem.uiObjects.push(this);
9651
9628
  }
9652
9629
 
9653
- /** Add a child UIObject to this object
9654
- * @param {UIObject} child */
9630
+ /** Add a child UIObject to this object, returns child for chaining
9631
+ * @param {UIObject} child
9632
+ * @return {UIObject} The child object added */
9655
9633
  addChild(child)
9656
9634
  {
9657
9635
  ASSERT(!child.parent && !this.children.includes(child));
9658
9636
  this.children.push(child);
9659
9637
  child.parent = this;
9638
+ return child;
9660
9639
  }
9661
9640
 
9662
9641
  /** Remove a child UIObject from this object
@@ -9729,8 +9708,16 @@ class UIObject
9729
9708
  if (uiSystem.activeObject && !isActive)
9730
9709
  uiSystem.activeObject.onRelease();
9731
9710
  uiSystem.activeObject = this;
9711
+
9712
+ if (uiSystem.activateOnPress)
9713
+ {
9714
+ this.onClick();
9715
+ if (!this.soundPress && this.soundClick)
9716
+ this.soundClick.play();
9717
+ }
9732
9718
  }
9733
9719
  }
9720
+ if (!uiSystem.activateOnPress)
9734
9721
  if (!mouseDown && this.isActiveObject() && this.interactive)
9735
9722
  {
9736
9723
  this.onClick();
@@ -12170,33 +12157,33 @@ async function box2dInit()
12170
12157
  color = getDebugColor(color);
12171
12158
  point1 = box2d.vec2FromPointer(point1);
12172
12159
  point2 = box2d.vec2FromPointer(point2);
12173
- drawLine(point1, point2, debugLineWidth, color, vec2(), 0, false, false, overlayContext);
12160
+ drawLine(point1, point2, debugLineWidth, color, vec2(), 0, false);
12174
12161
  };
12175
12162
  debugDraw.DrawPolygon = function(vertices, vertexCount, color)
12176
12163
  {
12177
12164
  color = getDebugColor(color);
12178
12165
  const points = getPointsList(vertices, vertexCount);
12179
- drawPoly(points, CLEAR_WHITE, debugLineWidth, color, vec2(), 0, false, false, overlayContext);
12166
+ drawPoly(points, CLEAR_WHITE, debugLineWidth, color, vec2(), 0, false);
12180
12167
  };
12181
12168
  debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
12182
12169
  {
12183
12170
  color = getDebugColor(color);
12184
12171
  const points = getPointsList(vertices, vertexCount);
12185
- drawPoly(points, color, 0, color, vec2(), 0, false, false, overlayContext);
12172
+ drawPoly(points, color, 0, color, vec2(), 0, false);
12186
12173
  };
12187
12174
  debugDraw.DrawCircle = function(center, radius, color)
12188
12175
  {
12189
12176
  color = getDebugColor(color);
12190
12177
  center = box2d.vec2FromPointer(center);
12191
- drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false, false, overlayContext);
12178
+ drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false);
12192
12179
  };
12193
12180
  debugDraw.DrawSolidCircle = function(center, radius, axis, color)
12194
12181
  {
12195
12182
  color = getDebugColor(color);
12196
12183
  center = box2d.vec2FromPointer(center);
12197
12184
  axis = box2d.vec2FromPointer(axis).scale(radius);
12198
- drawCircle(center, radius*2, color, debugLineWidth, color, false, false, overlayContext);
12199
- drawLine(vec2(), axis, debugLineWidth, color, center, 0, false, false, overlayContext);
12185
+ drawCircle(center, radius*2, color, debugLineWidth, color, false);
12186
+ drawLine(vec2(), axis, debugLineWidth, color, center, 0, false);
12200
12187
  };
12201
12188
  debugDraw.DrawTransform = function(transform)
12202
12189
  {
@@ -12205,8 +12192,8 @@ async function box2dInit()
12205
12192
  const angle = -transform.get_q().GetAngle();
12206
12193
  const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
12207
12194
  const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
12208
- drawLine(vec2(), p1, debugLineWidth, c1, pos, angle, false, false, overlayContext);
12209
- drawLine(vec2(), p2, debugLineWidth, c2, pos, angle, false, false, overlayContext);
12195
+ drawLine(vec2(), p1, debugLineWidth, c1, pos, angle, false);
12196
+ drawLine(vec2(), p2, debugLineWidth, c2, pos, angle, false);
12210
12197
  }
12211
12198
 
12212
12199
  debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
@@ -12226,8 +12213,8 @@ async function box2dInit()
12226
12213
 
12227
12214
  ///////////////////////////////////////////////////////////////////////////////
12228
12215
 
12229
- /** Draw a scalable nine-slice UI element to the overlay canvas in screen space
12230
- * This function can not apply color because it draws using the overlay 2d context
12216
+ /** Draw a scalable nine-slice UI element to the main canvas in screen space
12217
+ * This function can not apply color because it draws using the 2d context
12231
12218
  * @param {Vector2} pos - Screen space position
12232
12219
  * @param {Vector2} size - Screen space size
12233
12220
  * @param {TileInfo} startTile - Starting tile for the nine-slice pattern
@@ -12237,7 +12224,7 @@ async function box2dInit()
12237
12224
  * @memberof DrawUtilities */
12238
12225
  function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2, angle=0)
12239
12226
  {
12240
- drawNineSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true, overlayContext);
12227
+ drawNineSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true);
12241
12228
  }
12242
12229
 
12243
12230
  /** Draw a scalable nine-slice UI element in world space
@@ -12286,8 +12273,8 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
12286
12273
  }
12287
12274
  }
12288
12275
 
12289
- /** Draw a scalable three-slice UI element to the overlay canvas in screen space
12290
- * This function can not apply color because it draws using the overlay 2d context
12276
+ /** Draw a scalable three-slice UI element to the main canvas in screen space
12277
+ * This function can not apply color because it draws using the 2d context
12291
12278
  * @param {Vector2} pos - Screen space position
12292
12279
  * @param {Vector2} size - Screen space size
12293
12280
  * @param {TileInfo} startTile - Starting tile for the three-slice pattern
@@ -12297,7 +12284,7 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
12297
12284
  * @memberof DrawUtilities */
12298
12285
  function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2, angle=0)
12299
12286
  {
12300
- drawThreeSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true, overlayContext);
12287
+ drawThreeSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true);
12301
12288
  }
12302
12289
 
12303
12290
  /** Draw a scalable three-slice UI element in world space
@@ -12377,7 +12364,7 @@ export
12377
12364
  // Globals
12378
12365
  debug,
12379
12366
  debugOverlay,
12380
- showWatermark,
12367
+ debugWatermark,
12381
12368
 
12382
12369
  // Debug
12383
12370
  ASSERT,
@@ -12410,13 +12397,13 @@ export
12410
12397
  canvasMaxAspect,
12411
12398
  canvasFixedSize,
12412
12399
  canvasPixelated,
12413
- overlayCanvasPixelated,
12414
12400
  tilesPixelated,
12415
12401
  fontDefault,
12416
12402
  showSplashScreen,
12417
12403
  headlessMode,
12418
- tileSizeDefault,
12419
- tileFixBleedScale,
12404
+ tileDefaultSize,
12405
+ tileDefaultPadding,
12406
+ tileDefaultBleed,
12420
12407
  enablePhysicsSolver,
12421
12408
  objectDefaultMass,
12422
12409
  objectDefaultDamping,
@@ -12455,14 +12442,14 @@ export
12455
12442
  setCanvasMaxAspect,
12456
12443
  setCanvasFixedSize,
12457
12444
  setCanvasPixelated,
12458
- setOverlayCanvasPixelated,
12459
12445
  setTilesPixelated,
12460
12446
  setFontDefault,
12461
12447
  setShowSplashScreen,
12462
12448
  setHeadlessMode,
12463
12449
  setGLEnable,
12464
- setTileSizeDefault,
12465
- setTileFixBleedScale,
12450
+ setTileDefaultSize,
12451
+ setTileDefaultPadding,
12452
+ setTileDefaultBleed,
12466
12453
  setEnablePhysicsSolver,
12467
12454
  setObjectDefaultMass,
12468
12455
  setObjectDefaultDamping,
@@ -12491,7 +12478,7 @@ export
12491
12478
  setMedalDisplaySlideTime,
12492
12479
  setMedalDisplaySize,
12493
12480
  setMedalsPreventUnlock,
12494
- setShowWatermark,
12481
+ setDebugWatermark,
12495
12482
  setDebugKey,
12496
12483
 
12497
12484
  // Utilities
@@ -12571,8 +12558,10 @@ export
12571
12558
  mainContext,
12572
12559
  drawCanvas,
12573
12560
  drawContext,
12574
- overlayCanvas,
12575
- overlayContext,
12561
+ workCanvas,
12562
+ workContext,
12563
+ workReadCanvas,
12564
+ workReadContext,
12576
12565
  mainCanvasSize,
12577
12566
  textureInfos,
12578
12567
  drawCount,
@@ -12590,7 +12579,6 @@ export
12590
12579
  drawCircle,
12591
12580
  drawCanvas2D,
12592
12581
  drawText,
12593
- drawTextOverlay,
12594
12582
  drawTextScreen,
12595
12583
  setBlendMode,
12596
12584
  combineCanvases,