littlejsengine 1.16.1 → 1.16.2

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.
package/dist/littlejs.js CHANGED
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
33
33
  * @type {string}
34
34
  * @default
35
35
  * @memberof Engine */
36
- const engineVersion = '1.16.1';
36
+ const engineVersion = '1.16.2';
37
37
 
38
38
  /** Frames per second to update
39
39
  * @type {number}
@@ -272,33 +272,20 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
272
272
  if (!wasUpdated)
273
273
  updateCanvas();
274
274
 
275
- // render sort then render while removing destroyed objects
275
+ // render the game and objects
276
276
  enginePreRender();
277
277
  gameRender();
278
278
  engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
279
279
  for (const o of engineObjects)
280
280
  o.destroyed || o.render();
281
+
282
+ // post rendering
281
283
  gameRenderPost();
282
284
  pluginList.forEach(plugin=>plugin.render?.());
283
285
  inputRender();
284
286
  debugRender();
285
287
  glFlush();
286
- debugVideoCaptureUpdate();
287
-
288
- if (debugWatermark && !debugVideoCaptureIsActive())
289
- {
290
- // update fps display
291
- mainContext.textAlign = 'right';
292
- mainContext.textBaseline = 'top';
293
- mainContext.font = '1em monospace';
294
- mainContext.fillStyle = '#000';
295
- const text = engineName + ' ' + 'v' + engineVersion + ' / '
296
- + drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
297
- + (glEnable ? ' GL' : ' 2D') ;
298
- mainContext.fillText(text, mainCanvas.width-3, 3);
299
- mainContext.fillStyle = '#fff';
300
- mainContext.fillText(text, mainCanvas.width-2, 2);
301
- }
288
+ debugRenderPost();
302
289
  drawCount = 0;
303
290
  }
304
291
  }
@@ -364,15 +351,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
364
351
  mainContext.lineJoin = 'round';
365
352
  mainContext.lineCap = 'round';
366
353
  }
367
-
368
- // wait for gameInit to load
369
- async function startEngine()
370
- {
371
- await gameInit();
372
- engineUpdate();
373
- }
374
- if (headlessMode)
375
- return startEngine();
354
+
355
+ // skip setup if headless
356
+ if (headlessMode) return startEngine();
376
357
 
377
358
  // setup webgl
378
359
  glInit(rootElement);
@@ -387,7 +368,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
387
368
  'touch-action:none;' + // prevent mobile pinch to resize
388
369
  '-webkit-touch-callout:none'; // compatibility for ios
389
370
  rootElement.style.cssText = styleRoot;
390
- drawCanvas = mainCanvas = rootElement.appendChild(document.createElement('canvas'));
371
+ mainCanvas = rootElement.appendChild(document.createElement('canvas'));
391
372
  drawContext = mainContext = mainCanvas.getContext('2d');
392
373
 
393
374
  // init stuff and start engine
@@ -462,6 +443,13 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
462
443
  await Promise.all(promises);
463
444
  return startEngine();
464
445
 
446
+ async function startEngine()
447
+ {
448
+ // wait for gameInit to load
449
+ await gameInit();
450
+ engineUpdate();
451
+ }
452
+
465
453
  ///////////////////////////////////////////////////////////////////////////
466
454
  // LittleJS Splash Screen
467
455
  function drawEngineSplashScreen(t)
@@ -781,7 +769,7 @@ let debugKey = 'Escape';
781
769
  let debugOverlay = false;
782
770
 
783
771
  // Engine internal variables not exposed to documentation
784
- let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugMedals = false, debugTakeScreenshot, downloadLink, debugCanvas;
772
+ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugMedals = false, debugTakeScreenshot;
785
773
 
786
774
  ///////////////////////////////////////////////////////////////////////////////
787
775
  // Debug helper functions
@@ -968,46 +956,6 @@ function debugClear() { debugPrimitives = []; }
968
956
  * @memberof Debug */
969
957
  function debugScreenshot() { debugTakeScreenshot = 1; }
970
958
 
971
- /** Save a canvas to disk
972
- * @param {HTMLCanvasElement|OffscreenCanvas} canvas
973
- * @param {string} [filename]
974
- * @param {string} [type]
975
- * @memberof Debug */
976
- function debugSaveCanvas(canvas, filename='screenshot', type='image/png')
977
- {
978
- if (canvas instanceof OffscreenCanvas)
979
- {
980
- // copy to temporary canvas and save
981
- if (!debugCanvas)
982
- debugCanvas = document.createElement('canvas');
983
- debugCanvas.width = canvas.width;
984
- debugCanvas.height = canvas.height;
985
- debugCanvas.getContext('2d').drawImage(canvas, 0, 0);
986
- debugSaveDataURL(debugCanvas.toDataURL(type), filename);
987
- }
988
- else
989
- debugSaveDataURL(canvas.toDataURL(type), filename);
990
- }
991
-
992
- /** Save a text file to disk
993
- * @param {string} text
994
- * @param {string} [filename]
995
- * @param {string} [type]
996
- * @memberof Debug */
997
- function debugSaveText(text, filename='text', type='text/plain')
998
- { debugSaveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
999
-
1000
- /** Save a data url to disk
1001
- * @param {string} dataURL
1002
- * @param {string} filename
1003
- * @memberof Debug */
1004
- function debugSaveDataURL(dataURL, filename)
1005
- {
1006
- downloadLink.download = filename;
1007
- downloadLink.href = dataURL;
1008
- downloadLink.click();
1009
- }
1010
-
1011
959
  /** Breaks on all asserts/errors, hides the canvas, and shows message in plain text
1012
960
  * This is a good function to call at the start of your game to catch all errors
1013
961
  * In release builds this function has no effect
@@ -1043,21 +991,11 @@ function debugShowErrors()
1043
991
 
1044
992
  function debugInit()
1045
993
  {
1046
- // create link for saving screenshots
1047
- downloadLink = document.createElement('a');
1048
994
  }
1049
995
 
1050
996
  function debugUpdate()
1051
997
  {
1052
- if (!debug)
1053
- return;
1054
-
1055
- if (debugVideoCaptureIsActive())
1056
- {
1057
- // control to stop video capture
1058
- if (keyWasPressed('Digit6') || keyWasPressed(debugKey))
1059
- debugVideoCaptureStop();
1060
- }
998
+ if (!debug) return;
1061
999
 
1062
1000
  if (keyWasPressed(debugKey)) // Esc
1063
1001
  debugOverlay = !debugOverlay;
@@ -1075,9 +1013,15 @@ function debugUpdate()
1075
1013
  debugRaycast = !debugRaycast;
1076
1014
  if (keyWasPressed('Digit5'))
1077
1015
  debugScreenshot();
1078
- if (keyWasPressed('Digit6') && !debugVideoCaptureIsActive())
1079
- debugVideoCaptureStart();
1080
1016
  }
1017
+ if (debugVideoCaptureIsActive())
1018
+ {
1019
+ // control to stop video capture
1020
+ if (!debugOverlay || keyWasPressed('Digit6'))
1021
+ debugVideoCaptureStop();
1022
+ }
1023
+ else if (debugOverlay && keyWasPressed('Digit6'))
1024
+ debugVideoCaptureStart();
1081
1025
  }
1082
1026
 
1083
1027
  function debugRender()
@@ -1092,7 +1036,7 @@ function debugRender()
1092
1036
  {
1093
1037
  // combine canvases, remove alpha and save
1094
1038
  combineCanvases();
1095
- debugSaveCanvas(mainCanvas);
1039
+ saveCanvas(mainCanvas);
1096
1040
  debugTakeScreenshot = 0;
1097
1041
  }
1098
1042
 
@@ -1338,11 +1282,34 @@ function debugRender()
1338
1282
  }
1339
1283
  }
1340
1284
 
1285
+ function debugRenderPost()
1286
+ {
1287
+ if (debugVideoCaptureIsActive())
1288
+ {
1289
+ debugVideoCaptureUpdate();
1290
+ return;
1291
+ }
1292
+
1293
+ if (!debugWatermark) return;
1294
+
1295
+ // update fps display
1296
+ mainContext.textAlign = 'right';
1297
+ mainContext.textBaseline = 'top';
1298
+ mainContext.font = '1em monospace';
1299
+ mainContext.fillStyle = '#000';
1300
+ const text = engineName + ' v' + engineVersion + ' / '
1301
+ + drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
1302
+ + (glEnable ? ' GL' : ' 2D') ;
1303
+ mainContext.fillText(text, mainCanvas.width-3, 3);
1304
+ mainContext.fillStyle = '#fff';
1305
+ mainContext.fillText(text, mainCanvas.width-2, 2);
1306
+ }
1307
+
1341
1308
  ///////////////////////////////////////////////////////////////////////////////
1342
1309
  // video capture - records video and audio at 60 fps using MediaRecorder API
1343
1310
 
1344
1311
  // internal variables used to capture video
1345
- let debugVideoCapture, debugVideoCaptureTrack, debugVideoCaptureIcon, debugVideoCaptureTimer;
1312
+ let debugVideoCapture, debugVideoCaptureIcon;
1346
1313
 
1347
1314
  /** Check if video capture is active
1348
1315
  * @memberof Debug */
@@ -1352,80 +1319,103 @@ function debugVideoCaptureIsActive() { return !!debugVideoCapture; }
1352
1319
  * @memberof Debug */
1353
1320
  function debugVideoCaptureStart()
1354
1321
  {
1355
- if (debugVideoCaptureIsActive())
1356
- return; // already recording
1322
+ ASSERT(!debugVideoCaptureIsActive(), 'Already capturing video!');
1323
+
1324
+ if (!debugVideoCaptureIcon)
1325
+ {
1326
+ // create recording icon to show it is capturing video
1327
+ debugVideoCaptureIcon = document.createElement('div');
1328
+ debugVideoCaptureIcon.style.position = 'absolute';
1329
+ debugVideoCaptureIcon.style.padding = '9px';
1330
+ debugVideoCaptureIcon.style.color = '#f00';
1331
+ debugVideoCaptureIcon.style.font = '50px monospace';
1332
+ document.body.appendChild(debugVideoCaptureIcon);
1333
+ }
1334
+ // show recording icon
1335
+ debugVideoCaptureIcon.textContent = '';
1336
+ debugVideoCaptureIcon.style.display = '';
1357
1337
 
1358
- // captureStream passing in 0 to only capture when requestFrame() is called
1338
+ // setup captureStream to capture manually by passing 0
1359
1339
  const stream = mainCanvas.captureStream(0);
1340
+ const videoTrack = stream.getVideoTracks()[0];
1341
+ const captureTimer = new Timer(0, true);
1360
1342
  const chunks = [];
1361
- debugVideoCaptureTrack = stream.getVideoTracks()[0];
1362
- if (debugVideoCaptureTrack.applyConstraints)
1363
- debugVideoCaptureTrack.applyConstraints({frameRate:60}); // force 60 fps
1364
- debugVideoCapture = new MediaRecorder(stream, {mimeType:'video/webm;codecs=vp8'});
1365
- debugVideoCapture.ondataavailable = (e)=> chunks.push(e.data);
1366
- debugVideoCapture.onstop = ()=>
1343
+ videoTrack.applyConstraints({frameRate:frameRate});
1344
+
1345
+ // set up the media recorder
1346
+ const mediaRecorder = new MediaRecorder(stream,
1347
+ {mimeType:'video/webm;codecs=vp8'});
1348
+ mediaRecorder.ondataavailable = (e)=> chunks.push(e.data);
1349
+ mediaRecorder.onstop = ()=>
1367
1350
  {
1368
1351
  const blob = new Blob(chunks, {type: 'video/webm'});
1369
1352
  const url = URL.createObjectURL(blob);
1370
- downloadLink.download = 'capture.webm';
1371
- downloadLink.href = url;
1372
- downloadLink.click();
1373
- URL.revokeObjectURL(url);
1353
+ saveDataURL(url, 'capture.webm', 1e3);
1374
1354
  };
1375
1355
 
1376
- if (audioMasterGain)
1356
+ let audioStreamDestination, silentAudioSource;
1357
+ if (soundEnable)
1377
1358
  {
1359
+ // create silent audio source
1360
+ // fixes issue where video can not start recording without audio
1361
+ silentAudioSource = new ConstantSourceNode(audioContext, { offset: 0 });
1362
+ silentAudioSource.connect(audioMasterGain);
1363
+ silentAudioSource.start();
1364
+
1378
1365
  // connect to audio master gain node
1379
- const audioStreamDestination = audioContext.createMediaStreamDestination();
1366
+ audioStreamDestination = audioContext.createMediaStreamDestination();
1380
1367
  audioMasterGain.connect(audioStreamDestination);
1381
1368
  for (const track of audioStreamDestination.stream.getAudioTracks())
1382
1369
  stream.addTrack(track); // add audio tracks to capture stream
1383
1370
  }
1384
1371
 
1385
1372
  // start recording
1373
+ try { mediaRecorder.start(); }
1374
+ catch(e)
1375
+ {
1376
+ LOG('Video capture not supported in this browser!');
1377
+ silentAudioSource?.stop();
1378
+ return;
1379
+ }
1380
+
1386
1381
  LOG('Video capture started.');
1387
- debugVideoCapture.start();
1388
- debugVideoCaptureTimer = new Timer(0, true);
1389
1382
 
1390
- if (!debugVideoCaptureIcon)
1383
+ // save debug video info
1384
+ debugVideoCapture =
1391
1385
  {
1392
- // create recording icon to show it is capturing video
1393
- debugVideoCaptureIcon = document.createElement('div');
1394
- debugVideoCaptureIcon.style.position = 'absolute';
1395
- debugVideoCaptureIcon.style.padding = '9px';
1396
- debugVideoCaptureIcon.style.color = '#f00';
1397
- debugVideoCaptureIcon.style.font = '50px monospace';
1398
- document.body.appendChild(debugVideoCaptureIcon);
1399
- }
1400
- // show recording icon
1401
- debugVideoCaptureIcon.textContent = '';
1402
- debugVideoCaptureIcon.style.display = '';
1386
+ mediaRecorder,
1387
+ captureTimer,
1388
+ videoTrack,
1389
+ silentAudioSource,
1390
+ audioStreamDestination
1391
+ };
1403
1392
  }
1404
1393
 
1405
1394
  /** Stop capturing video and save to disk
1406
1395
  * @memberof Debug */
1407
1396
  function debugVideoCaptureStop()
1408
1397
  {
1409
- if (!debugVideoCaptureIsActive())
1410
- return; // not recording
1398
+ ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
1411
1399
 
1412
1400
  // stop recording
1413
- LOG(`Video capture ended. ${debugVideoCaptureTimer.get().toFixed(2)} seconds recorded.`);
1414
- debugVideoCapture.stop();
1415
- debugVideoCapture = 0;
1401
+ LOG(`Video capture ended. ${debugVideoCapture.captureTimer.get().toFixed(2)} seconds recorded.`);
1416
1402
  debugVideoCaptureIcon.style.display = 'none';
1403
+ debugVideoCapture.silentAudioSource?.stop();
1404
+ debugVideoCapture.mediaRecorder?.stop();
1405
+ debugVideoCapture.videoTrack?.stop();
1406
+ debugVideoCapture = undefined;
1417
1407
  }
1418
1408
 
1419
1409
  // update video capture, called automatically by engine
1420
1410
  function debugVideoCaptureUpdate()
1421
1411
  {
1422
- if (!debugVideoCaptureIsActive())
1423
- return; // not recording
1412
+ ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
1424
1413
 
1425
1414
  // save the video frame
1426
1415
  combineCanvases();
1427
- debugVideoCaptureTrack.requestFrame();
1428
- debugVideoCaptureIcon.textContent = '● REC ' + formatTime(debugVideoCaptureTimer);
1416
+ debugVideoCapture.videoTrack.requestFrame();
1417
+ debugVideoCaptureIcon.textContent = '● REC '
1418
+ + formatTime(debugVideoCapture.captureTimer);
1429
1419
  }
1430
1420
 
1431
1421
  ///////////////////////////////////////////////////////////////////////////////
@@ -1464,99 +1454,99 @@ function debugProtectConstant(obj)
1464
1454
  * - Color - holds a rgba color with some math functions
1465
1455
  * - Timer - tracks time automatically
1466
1456
  * - RandomGenerator - seeded random number generator
1467
- * @namespace Utilities
1457
+ * @namespace Math
1468
1458
  */
1469
1459
 
1470
1460
  /** The value of PI
1471
1461
  * @type {number}
1472
1462
  * @default Math.PI
1473
- * @memberof Utilities */
1463
+ * @memberof Math */
1474
1464
  const PI = Math.PI;
1475
1465
 
1476
1466
  /** Returns absolute value of value passed in
1477
1467
  * @param {number} value
1478
1468
  * @return {number}
1479
- * @memberof Utilities */
1469
+ * @memberof Math */
1480
1470
  const abs = Math.abs;
1481
1471
 
1482
1472
  /** Returns floored value of value passed in
1483
1473
  * @param {number} value
1484
1474
  * @return {number}
1485
- * @memberof Utilities */
1475
+ * @memberof Math */
1486
1476
  const floor = Math.floor;
1487
1477
 
1488
1478
  /** Returns ceiled value of value passed in
1489
1479
  * @param {number} value
1490
1480
  * @return {number}
1491
- * @memberof Utilities */
1481
+ * @memberof Math */
1492
1482
  const ceil = Math.ceil;
1493
1483
 
1494
1484
  /** Returns rounded value passed in
1495
1485
  * @param {number} value
1496
1486
  * @return {number}
1497
- * @memberof Utilities */
1487
+ * @memberof Math */
1498
1488
  const round = Math.round;
1499
1489
 
1500
1490
  /** Returns lowest value passed in
1501
1491
  * @param {...number} values
1502
1492
  * @return {number}
1503
- * @memberof Utilities */
1493
+ * @memberof Math */
1504
1494
  const min = Math.min;
1505
1495
 
1506
1496
  /** Returns highest value passed in
1507
1497
  * @param {...number} values
1508
1498
  * @return {number}
1509
- * @memberof Utilities */
1499
+ * @memberof Math */
1510
1500
  const max = Math.max;
1511
1501
 
1512
1502
  /** Returns the sign of value passed in
1513
1503
  * @param {number} value
1514
1504
  * @return {number}
1515
- * @memberof Utilities */
1505
+ * @memberof Math */
1516
1506
  const sign = Math.sign;
1517
1507
 
1518
1508
  /** Returns hypotenuse of values passed in
1519
1509
  * @param {...number} values
1520
1510
  * @return {number}
1521
- * @memberof Utilities */
1511
+ * @memberof Math */
1522
1512
  const hypot = Math.hypot;
1523
1513
 
1524
1514
  /** Returns log2 of value passed in
1525
1515
  * @param {number} value
1526
1516
  * @return {number}
1527
- * @memberof Utilities */
1517
+ * @memberof Math */
1528
1518
  const log2 = Math.log2;
1529
1519
 
1530
1520
  /** Returns sin of value passed in
1531
1521
  * @param {number} value
1532
1522
  * @return {number}
1533
- * @memberof Utilities */
1523
+ * @memberof Math */
1534
1524
  const sin = Math.sin;
1535
1525
 
1536
1526
  /** Returns cos of value passed in
1537
1527
  * @param {number} value
1538
1528
  * @return {number}
1539
- * @memberof Utilities */
1529
+ * @memberof Math */
1540
1530
  const cos = Math.cos;
1541
1531
 
1542
1532
  /** Returns tan of value passed in
1543
1533
  * @param {number} value
1544
1534
  * @return {number}
1545
- * @memberof Utilities */
1535
+ * @memberof Math */
1546
1536
  const tan = Math.tan;
1547
1537
 
1548
1538
  /** Returns atan2 of values passed in
1549
1539
  * @param {number} y
1550
1540
  * @param {number} x
1551
1541
  * @return {number}
1552
- * @memberof Utilities */
1542
+ * @memberof Math */
1553
1543
  const atan2 = Math.atan2;
1554
1544
 
1555
1545
  /** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
1556
1546
  * @param {number} dividend
1557
1547
  * @param {number} [divisor]
1558
1548
  * @return {number}
1559
- * @memberof Utilities */
1549
+ * @memberof Math */
1560
1550
  function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
1561
1551
 
1562
1552
  /** Clamps the value between max and min
@@ -1564,7 +1554,7 @@ function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % di
1564
1554
  * @param {number} [min]
1565
1555
  * @param {number} [max]
1566
1556
  * @return {number}
1567
- * @memberof Utilities */
1557
+ * @memberof Math */
1568
1558
  function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
1569
1559
 
1570
1560
  /** Returns what percentage the value is between valueA and valueB
@@ -1572,7 +1562,7 @@ function clamp(value, min=0, max=1) { return value < min ? min : value > max ? m
1572
1562
  * @param {number} valueA
1573
1563
  * @param {number} valueB
1574
1564
  * @return {number}
1575
- * @memberof Utilities */
1565
+ * @memberof Math */
1576
1566
  function percent(value, valueA, valueB)
1577
1567
  { return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
1578
1568
 
@@ -1581,7 +1571,7 @@ function percent(value, valueA, valueB)
1581
1571
  * @param {number} valueB
1582
1572
  * @param {number} percent
1583
1573
  * @return {number}
1584
- * @memberof Utilities */
1574
+ * @memberof Math */
1585
1575
  function lerp(valueA, valueB, percent)
1586
1576
  { return valueA + clamp(percent) * (valueB-valueA); }
1587
1577
 
@@ -1593,7 +1583,7 @@ function lerp(valueA, valueB, percent)
1593
1583
  * @param {number} lerpA
1594
1584
  * @param {number} lerpB
1595
1585
  * @return {number}
1596
- * @memberof Utilities */
1586
+ * @memberof Math */
1597
1587
  function percentLerp(value, percentA, percentB, lerpA, lerpB)
1598
1588
  { return lerp(lerpA, lerpB, percent(value, percentA, percentB)); }
1599
1589
 
@@ -1602,7 +1592,7 @@ function percentLerp(value, percentA, percentB, lerpA, lerpB)
1602
1592
  * @param {number} valueB
1603
1593
  * @param {number} [wrapSize]
1604
1594
  * @return {number}
1605
- * @memberof Utilities */
1595
+ * @memberof Math */
1606
1596
  function distanceWrap(valueA, valueB, wrapSize=1)
1607
1597
  { const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
1608
1598
 
@@ -1612,7 +1602,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
1612
1602
  * @param {number} percent
1613
1603
  * @param {number} [wrapSize]
1614
1604
  * @return {number}
1615
- * @memberof Utilities */
1605
+ * @memberof Math */
1616
1606
  function lerpWrap(valueA, valueB, percent, wrapSize=1)
1617
1607
  { return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
1618
1608
 
@@ -1620,7 +1610,7 @@ function lerpWrap(valueA, valueB, percent, wrapSize=1)
1620
1610
  * @param {number} angleA
1621
1611
  * @param {number} angleB
1622
1612
  * @return {number}
1623
- * @memberof Utilities */
1613
+ * @memberof Math */
1624
1614
  function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
1625
1615
 
1626
1616
  /** Linearly interpolates between the angles passed in with wrapping
@@ -1628,25 +1618,25 @@ function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*P
1628
1618
  * @param {number} angleB
1629
1619
  * @param {number} percent
1630
1620
  * @return {number}
1631
- * @memberof Utilities */
1621
+ * @memberof Math */
1632
1622
  function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
1633
1623
 
1634
1624
  /** Applies smoothstep function to the percentage value
1635
1625
  * @param {number} percent
1636
1626
  * @return {number}
1637
- * @memberof Utilities */
1627
+ * @memberof Math */
1638
1628
  function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
1639
1629
 
1640
1630
  /** Checks if the value passed in is a power of two
1641
1631
  * @param {number} value
1642
1632
  * @return {boolean}
1643
- * @memberof Utilities */
1633
+ * @memberof Math */
1644
1634
  function isPowerOfTwo(value) { return !(value & (value - 1)); }
1645
1635
 
1646
1636
  /** Returns the nearest power of two not less than the value
1647
1637
  * @param {number} value
1648
1638
  * @return {number}
1649
- * @memberof Utilities */
1639
+ * @memberof Math */
1650
1640
  function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
1651
1641
 
1652
1642
  /** Returns true if two axis aligned bounding boxes are overlapping
@@ -1656,7 +1646,7 @@ function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
1656
1646
  * @param {Vector2} posB - Center of box B
1657
1647
  * @param {Vector2} [sizeB=(0,0)] - Size of box B, uses a point if undefined
1658
1648
  * @return {boolean} - True if overlapping
1659
- * @memberof Utilities */
1649
+ * @memberof Math */
1660
1650
  function isOverlapping(posA, sizeA, posB, sizeB=vec2())
1661
1651
  {
1662
1652
  const dx = (posA.x - posB.x)*2;
@@ -1672,7 +1662,7 @@ function isOverlapping(posA, sizeA, posB, sizeB=vec2())
1672
1662
  * @param {Vector2} pos - Center of box
1673
1663
  * @param {Vector2} size - Size of box
1674
1664
  * @return {boolean} - True if intersecting
1675
- * @memberof Utilities */
1665
+ * @memberof Math */
1676
1666
  function isIntersecting(start, end, pos, size)
1677
1667
  {
1678
1668
  // Liang-Barsky algorithm
@@ -1713,52 +1703,29 @@ function isIntersecting(start, end, pos, size)
1713
1703
  * @param {number} [t=time] - Value to use for time of the wave
1714
1704
  * @param {number} [offset] - Value to use for time offset of the wave
1715
1705
  * @return {number} - Value waving between 0 and amplitude
1716
- * @memberof Utilities */
1706
+ * @memberof Math */
1717
1707
  function wave(frequency=1, amplitude=1, t=time, offset=0)
1718
1708
  { return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
1719
1709
 
1720
- /** Formats seconds to mm:ss style for display purposes
1721
- * @param {number} t - time in seconds
1722
- * @return {string}
1723
- * @memberof Utilities */
1724
- function formatTime(t)
1725
- {
1726
- const sign = t < 0 ? '-' : '';
1727
- t = abs(t)|0;
1728
- return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
1729
- }
1730
-
1731
- /** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
1732
- * @param {string} url - URL of JSON file
1733
- * @return {Promise<object>}
1734
- * @memberof Utilities */
1735
- async function fetchJSON(url)
1736
- {
1737
- const response = await fetch(url);
1738
- if (!response.ok)
1739
- throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
1740
- return response.json();
1741
- }
1742
-
1743
1710
  /**
1744
1711
  * Check if object is a valid number, not NaN or undefined, but it may be infinite
1745
1712
  * @param {any} n
1746
1713
  * @return {boolean}
1747
- * @memberof Utilities */
1714
+ * @memberof Math */
1748
1715
  function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
1749
1716
 
1750
1717
  /**
1751
1718
  * Check if object is a valid string or can be converted to one
1752
1719
  * @param {any} s
1753
1720
  * @return {boolean}
1754
- * @memberof Utilities */
1721
+ * @memberof Math */
1755
1722
  function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
1756
1723
 
1757
1724
  /**
1758
1725
  * Check if object is an array
1759
1726
  * @param {any} a
1760
1727
  * @return {boolean}
1761
- * @memberof Utilities */
1728
+ * @memberof Math */
1762
1729
  function isArray(a) { return Array.isArray(a); }
1763
1730
 
1764
1731
  /**
@@ -1774,7 +1741,7 @@ function isArray(a) { return Array.isArray(a); }
1774
1741
  * @param {LineTestFunction} testFunction - Check if colliding
1775
1742
  * @param {Vector2} [normal] - Optional vector to store the normal
1776
1743
  * @return {Vector2|undefined} - Position of the collision or undefined if none found
1777
- * @memberof Utilities */
1744
+ * @memberof Math */
1778
1745
  function lineTest(posStart, posEnd, testFunction, normal)
1779
1746
  {
1780
1747
  ASSERT(isVector2(posStart), 'posStart must be a vec2');
@@ -1786,8 +1753,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
1786
1753
  const dx = posEnd.x - posStart.x;
1787
1754
  const dy = posEnd.y - posStart.y;
1788
1755
  const totalLength = hypot(dx, dy);
1789
- if (!totalLength)
1790
- return;
1756
+ if (!totalLength) return;
1791
1757
 
1792
1758
  // current integer cell we are in
1793
1759
  const pos = posStart.floor();
@@ -2021,14 +1987,14 @@ class RandomGenerator
2021
1987
  * let a = vec2(0, 1); // vector with coordinates (0, 1)
2022
1988
  * a = vec2(5); // set a to (5, 5)
2023
1989
  * b = vec2(); // set b to (0, 0)
2024
- * @memberof Utilities */
1990
+ * @memberof Math */
2025
1991
  function vec2(x=0, y) { return new Vector2(x, y === undefined ? x : y); }
2026
1992
 
2027
1993
  /**
2028
1994
  * Check if object is a valid Vector2
2029
1995
  * @param {any} v
2030
1996
  * @return {boolean}
2031
- * @memberof Utilities */
1997
+ * @memberof Math */
2032
1998
  function isVector2(v) { return v instanceof Vector2 && v.isValid(); }
2033
1999
 
2034
2000
  // vector2 asserts
@@ -2278,7 +2244,7 @@ class Vector2
2278
2244
  * @param {number} [b=1] - blue
2279
2245
  * @param {number} [a=1] - alpha
2280
2246
  * @return {Color}
2281
- * @memberof Utilities
2247
+ * @memberof Math
2282
2248
  */
2283
2249
  function rgb(r, g, b, a) { return new Color(r, g, b, a); }
2284
2250
 
@@ -2289,14 +2255,14 @@ function rgb(r, g, b, a) { return new Color(r, g, b, a); }
2289
2255
  * @param {number} [l=1] - lightness
2290
2256
  * @param {number} [a=1] - alpha
2291
2257
  * @return {Color}
2292
- * @memberof Utilities */
2258
+ * @memberof Math */
2293
2259
  function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
2294
2260
 
2295
2261
  /**
2296
2262
  * Check if object is a valid Color
2297
2263
  * @param {any} c
2298
2264
  * @return {boolean}
2299
- * @memberof Utilities */
2265
+ * @memberof Math */
2300
2266
  function isColor(c) { return c instanceof Color && c.isValid(); }
2301
2267
 
2302
2268
  // color asserts
@@ -2477,7 +2443,7 @@ class Color
2477
2443
  toString(useAlpha = true)
2478
2444
  {
2479
2445
  if (debug && !this.isValid())
2480
- return `#000`;
2446
+ return '#000';
2481
2447
  const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
2482
2448
  return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
2483
2449
  }
@@ -2534,67 +2500,67 @@ class Color
2534
2500
 
2535
2501
  /** Color - White #ffffff
2536
2502
  * @type {Color}
2537
- * @memberof Utilities */
2503
+ * @memberof Math */
2538
2504
  const WHITE = debugProtectConstant(rgb());
2539
2505
 
2540
2506
  /** Color - Clear White #757474ff with 0 alpha
2541
2507
  * @type {Color}
2542
- * @memberof Utilities */
2508
+ * @memberof Math */
2543
2509
  const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
2544
2510
 
2545
2511
  /** Color - Black #000000
2546
2512
  * @type {Color}
2547
- * @memberof Utilities */
2513
+ * @memberof Math */
2548
2514
  const BLACK = debugProtectConstant(rgb(0,0,0));
2549
2515
 
2550
2516
  /** Color - Clear Black #000000 with 0 alpha
2551
2517
  * @type {Color}
2552
- * @memberof Utilities */
2518
+ * @memberof Math */
2553
2519
  const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
2554
2520
 
2555
2521
  /** Color - Gray #808080
2556
2522
  * @type {Color}
2557
- * @memberof Utilities */
2523
+ * @memberof Math */
2558
2524
  const GRAY = debugProtectConstant(rgb(.5,.5,.5));
2559
2525
 
2560
2526
  /** Color - Red #ff0000
2561
2527
  * @type {Color}
2562
- * @memberof Utilities */
2528
+ * @memberof Math */
2563
2529
  const RED = debugProtectConstant(rgb(1,0,0));
2564
2530
 
2565
2531
  /** Color - Orange #ff8000
2566
2532
  * @type {Color}
2567
- * @memberof Utilities */
2533
+ * @memberof Math */
2568
2534
  const ORANGE = debugProtectConstant(rgb(1,.5,0));
2569
2535
 
2570
2536
  /** Color - Yellow #ffff00
2571
2537
  * @type {Color}
2572
- * @memberof Utilities */
2538
+ * @memberof Math */
2573
2539
  const YELLOW = debugProtectConstant(rgb(1,1,0));
2574
2540
 
2575
2541
  /** Color - Green #00ff00
2576
2542
  * @type {Color}
2577
- * @memberof Utilities */
2543
+ * @memberof Math */
2578
2544
  const GREEN = debugProtectConstant(rgb(0,1,0));
2579
2545
 
2580
2546
  /** Color - Cyan #00ffff
2581
2547
  * @type {Color}
2582
- * @memberof Utilities */
2548
+ * @memberof Math */
2583
2549
  const CYAN = debugProtectConstant(rgb(0,1,1));
2584
2550
 
2585
2551
  /** Color - Blue #0000ff
2586
2552
  * @type {Color}
2587
- * @memberof Utilities */
2553
+ * @memberof Math */
2588
2554
  const BLUE = debugProtectConstant(rgb(0,0,1));
2589
2555
 
2590
2556
  /** Color - Purple #8000ff
2591
2557
  * @type {Color}
2592
- * @memberof Utilities */
2558
+ * @memberof Math */
2593
2559
  const PURPLE = debugProtectConstant(rgb(.5,0,1));
2594
2560
 
2595
2561
  /** Color - Magenta #ff00ff
2596
2562
  * @type {Color}
2597
- * @memberof Utilities */
2563
+ * @memberof Math */
2598
2564
  const MAGENTA = debugProtectConstant(rgb(1,0,1));
2599
2565
 
2600
2566
  ///////////////////////////////////////////////////////////////////////////////
@@ -2680,6 +2646,84 @@ class Timer
2680
2646
  * @return {number} */
2681
2647
  valueOf() { return this.get(); }
2682
2648
  }
2649
+ /**
2650
+ * LittleJS Utility Classes and Functions
2651
+ * - General purpose math library
2652
+ * - Vector2 - fast, simple, easy 2D vector class
2653
+ * - Color - holds a rgba color with some math functions
2654
+ * - Timer - tracks time automatically
2655
+ * - RandomGenerator - seeded random number generator
2656
+ * @namespace Utilities
2657
+ */
2658
+
2659
+ /** Formats seconds to mm:ss style for display purposes
2660
+ * @param {number} t - time in seconds
2661
+ * @return {string}
2662
+ * @memberof Utilities */
2663
+ function formatTime(t)
2664
+ {
2665
+ const sign = t < 0 ? '-' : '';
2666
+ t = abs(t)|0;
2667
+ return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
2668
+ }
2669
+
2670
+ /** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
2671
+ * @param {string} url - URL of JSON file
2672
+ * @return {Promise<object>}
2673
+ * @memberof Utilities */
2674
+ async function fetchJSON(url)
2675
+ {
2676
+ const response = await fetch(url);
2677
+ if (!response.ok)
2678
+ throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
2679
+ return response.json();
2680
+ }
2681
+
2682
+ ///////////////////////////////////////////////////////////////////////////////
2683
+
2684
+ /** Save a text file to disk
2685
+ * @param {string} text
2686
+ * @param {string} [filename]
2687
+ * @param {string} [type]
2688
+ * @memberof Utilities */
2689
+ function saveText(text, filename='text', type='text/plain')
2690
+ { saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
2691
+
2692
+ /** Save a canvas to disk
2693
+ * @param {HTMLCanvasElement|OffscreenCanvas} canvas
2694
+ * @param {string} [filename]
2695
+ * @param {string} [type]
2696
+ * @memberof Utilities */
2697
+ function saveCanvas(canvas, filename='screenshot', type='image/png')
2698
+ {
2699
+ if (canvas instanceof OffscreenCanvas)
2700
+ {
2701
+ // copy to temporary canvas and save
2702
+ const saveCanvas = document.createElement('canvas');
2703
+ saveCanvas.width = canvas.width;
2704
+ saveCanvas.height = canvas.height;
2705
+ saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
2706
+ saveDataURL(saveCanvas.toDataURL(type), filename);
2707
+ }
2708
+ else
2709
+ saveDataURL(canvas.toDataURL(type), filename);
2710
+ }
2711
+
2712
+ /** Save a data url to disk
2713
+ * @param {string} url
2714
+ * @param {string} [filename]
2715
+ * @param {number} [revokeTime] - how long before revoking the url
2716
+ * @memberof Utilities */
2717
+ function saveDataURL(url, filename='download', revokeTime)
2718
+ {
2719
+ // create link for saving screenshots
2720
+ const link = document.createElement('a');
2721
+ link.download = filename;
2722
+ link.href = url;
2723
+ link.click();
2724
+ if (revokeTime !== undefined)
2725
+ setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
2726
+ }
2683
2727
  /**
2684
2728
  * LittleJS Engine Settings
2685
2729
  * - All settings for the engine are here
@@ -2717,7 +2761,7 @@ let cameraScale = 32;
2717
2761
  * @memberof Settings */
2718
2762
  let canvasColorTiles = true;
2719
2763
 
2720
- /** Color to clear the canvas to before render
2764
+ /** Color to clear the canvas to before render, does not clear if alpha is 0
2721
2765
  * @type {Color}
2722
2766
  * @memberof Draw */
2723
2767
  let canvasClearColor = CLEAR_BLACK;
@@ -3026,7 +3070,7 @@ function setCameraScale(scale) { cameraScale = scale; }
3026
3070
  * @memberof Settings */
3027
3071
  function setCanvasColorTiles(colorTiles) { canvasColorTiles = colorTiles; }
3028
3072
 
3029
- /** Set color to clear the canvas to before render
3073
+ /** Set color to clear the canvas to before render, does not clear if alpha is 0
3030
3074
  * @param {Color} color
3031
3075
  * @memberof Settings */
3032
3076
  function setCanvasClearColor(color) { canvasClearColor = color.copy(); }
@@ -3784,8 +3828,7 @@ class EngineObject
3784
3828
  /** Render debug info for this object */
3785
3829
  renderDebugInfo()
3786
3830
  {
3787
- if (!debug)
3788
- return;
3831
+ if (!debug) return;
3789
3832
 
3790
3833
  // show object info for debugging
3791
3834
  const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
@@ -3827,11 +3870,6 @@ let mainCanvas;
3827
3870
  * @memberof Draw */
3828
3871
  let mainContext;
3829
3872
 
3830
- /** The default canvas to use for drawing, usually mainCanvas
3831
- * @type {HTMLCanvasElement|OffscreenCanvas}
3832
- * @memberof Draw */
3833
- let drawCanvas;
3834
-
3835
3873
  /** The default 2d context to use for drawing, usually mainContext
3836
3874
  * @type {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}
3837
3875
  * @memberof Draw */
@@ -4419,7 +4457,11 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
4419
4457
  ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
4420
4458
 
4421
4459
  if (!screenSpace)
4422
- [pos, size, angle] = worldToScreenTransform(pos, size, angle);
4460
+ {
4461
+ pos = worldToScreen(pos);
4462
+ size = size.scale(cameraScale);
4463
+ angle -= cameraAngle;
4464
+ }
4423
4465
  context.save();
4424
4466
  context.translate(pos.x+.5, pos.y+.5);
4425
4467
  context.rotate(angle);
@@ -4471,9 +4513,9 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
4471
4513
  * @param {string} [fontStyle]
4472
4514
  * @param {number} [maxWidth]
4473
4515
  * @param {number} [angle]
4474
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
4516
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
4475
4517
  * @memberof Draw */
4476
- function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=mainContext)
4518
+ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
4477
4519
  {
4478
4520
  ASSERT(isString(text), 'text must be a string');
4479
4521
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -4614,25 +4656,6 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
4614
4656
  ];
4615
4657
  }
4616
4658
 
4617
- /** Convert world space transform to screen space
4618
- * @param {Vector2} worldPos
4619
- * @param {Vector2} worldSize
4620
- * @param {number} [worldAngle]
4621
- * @return {[Vector2, Vector2, number]} - [pos, size, angle]
4622
- * @memberof Draw */
4623
- function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
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
-
4629
- return [
4630
- worldToScreen(worldPos),
4631
- worldSize.scale(cameraScale),
4632
- worldAngle - cameraAngle
4633
- ];
4634
- }
4635
-
4636
4659
  /** Get the size of the camera window in world space
4637
4660
  * @return {Vector2}
4638
4661
  * @memberof Draw */
@@ -4855,7 +4878,7 @@ class FontImage
4855
4878
  * @param {boolean} [center]
4856
4879
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
4857
4880
  */
4858
- drawTextScreen(text, pos, scale=4, center=true, context=mainContext)
4881
+ drawTextScreen(text, pos, scale=4, center=true, context=drawContext)
4859
4882
  {
4860
4883
  context.save();
4861
4884
  const size = this.tileSize;
@@ -5248,8 +5271,7 @@ function inputInit()
5248
5271
  }
5249
5272
  function onMouseDown(e)
5250
5273
  {
5251
- if (isTouchDevice && touchInputEnable)
5252
- return;
5274
+ if (isTouchDevice && touchInputEnable) return;
5253
5275
 
5254
5276
  // fix stalled audio requiring user interaction
5255
5277
  if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
@@ -5300,8 +5322,7 @@ function inputInit()
5300
5322
  let wasTouching;
5301
5323
  function handleTouch(e)
5302
5324
  {
5303
- if (!touchInputEnable)
5304
- return;
5325
+ if (!touchInputEnable) return;
5305
5326
 
5306
5327
  // route touch to gamepad
5307
5328
  if (touchGamepadEnable)
@@ -5365,8 +5386,7 @@ function inputInit()
5365
5386
  }
5366
5387
 
5367
5388
  // don't process touch gamepad if paused
5368
- if (paused)
5369
- return;
5389
+ if (paused) return;
5370
5390
 
5371
5391
  // get center of left and right sides
5372
5392
  const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
@@ -6052,8 +6072,7 @@ class SoundInstance
6052
6072
  /** Pause this sound instance */
6053
6073
  pause()
6054
6074
  {
6055
- if (this.isPaused())
6056
- return;
6075
+ if (this.isPaused()) return;
6057
6076
 
6058
6077
  // save current time and stop sound
6059
6078
  this.pausedTime = this.getCurrentTime();
@@ -6065,8 +6084,7 @@ class SoundInstance
6065
6084
  /** Unpauses this sound instance */
6066
6085
  resume()
6067
6086
  {
6068
- if (!this.isPaused())
6069
- return;
6087
+ if (!this.isPaused()) return;
6070
6088
 
6071
6089
  // restart sound from paused time
6072
6090
  this.start(this.pausedTime);
@@ -6804,12 +6822,11 @@ class TileLayer extends CanvasLayer
6804
6822
  if (!this.context) return;
6805
6823
 
6806
6824
  // save current render settings
6807
- /** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
6808
- this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
6825
+ /** @type {[CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
6826
+ this.savedRenderSettings = [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
6809
6827
 
6810
6828
  // set the draw canvas and context to this layer
6811
6829
  // use camera settings to match this layer's canvas
6812
- drawCanvas = this.canvas;
6813
6830
  drawContext = this.context;
6814
6831
  cameraPos = this.size.scale(.5);
6815
6832
  const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
@@ -6819,8 +6836,8 @@ class TileLayer extends CanvasLayer
6819
6836
  if (clear)
6820
6837
  {
6821
6838
  // clear and set size
6822
- drawCanvas.width = mainCanvasSize.x;
6823
- drawCanvas.height = mainCanvasSize.y;
6839
+ this.canvas.width = mainCanvasSize.x;
6840
+ this.canvas.height = mainCanvasSize.y;
6824
6841
  }
6825
6842
 
6826
6843
  // disable smoothing for pixel art
@@ -6837,10 +6854,10 @@ class TileLayer extends CanvasLayer
6837
6854
 
6838
6855
  ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
6839
6856
  glCopyToContext(drawContext);
6840
- //debugSaveCanvas(this.canvas);
6857
+ //saveCanvas(this.canvas);
6841
6858
 
6842
6859
  // set stuff back to normal
6843
- [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
6860
+ [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
6844
6861
  }
6845
6862
 
6846
6863
  /** Draw the tile at a given position in the tile grid
@@ -6908,8 +6925,7 @@ class TileCollisionLayer extends TileLayer
6908
6925
  /** Destroy this tile layer */
6909
6926
  destroy()
6910
6927
  {
6911
- if (this.destroyed)
6912
- return;
6928
+ if (this.destroyed) return;
6913
6929
 
6914
6930
  // remove from collision layers array and destroy
6915
6931
  const index = tileCollisionLayers.indexOf(this);
@@ -7446,10 +7462,11 @@ function medalsInit(saveName)
7446
7462
 
7447
7463
  // engine automatically renders medals
7448
7464
  engineAddPlugin(undefined, medalsRender);
7465
+
7466
+ // plugin functions
7449
7467
  function medalsRender()
7450
7468
  {
7451
- if (!medalsDisplayQueue.length)
7452
- return;
7469
+ if (!medalsDisplayQueue.length) return;
7453
7470
 
7454
7471
  // update first medal in queue
7455
7472
  const medal = medalsDisplayQueue[0];
@@ -7539,8 +7556,7 @@ class Medal
7539
7556
  /** Unlocks a medal if not already unlocked */
7540
7557
  unlock()
7541
7558
  {
7542
- if (medalsPreventUnlock || this.unlocked)
7543
- return;
7559
+ if (medalsPreventUnlock || this.unlocked) return;
7544
7560
 
7545
7561
  // save the medal
7546
7562
  ASSERT(medalsSaveName, 'save name must be set');
@@ -7773,8 +7789,7 @@ function glInit(rootElement)
7773
7789
 
7774
7790
  function glSetInstancedMode()
7775
7791
  {
7776
- if (!glPolyMode)
7777
- return;
7792
+ if (!glPolyMode) return;
7778
7793
 
7779
7794
  // setup instanced mode
7780
7795
  glFlush();
@@ -7807,8 +7822,7 @@ function glSetInstancedMode()
7807
7822
 
7808
7823
  function glSetPolyMode()
7809
7824
  {
7810
- if (glPolyMode)
7811
- return;
7825
+ if (glPolyMode) return;
7812
7826
 
7813
7827
  // setup poly mode
7814
7828
  glFlush();
@@ -7887,8 +7901,8 @@ function glClearCanvas()
7887
7901
  if (!glContext) return;
7888
7902
 
7889
7903
  // clear and set to same size as main canvas
7890
- glCanvas.width = drawCanvas.width;
7891
- glCanvas.height = drawCanvas.height;
7904
+ glCanvas.width = mainCanvasSize.x;
7905
+ glCanvas.height = mainCanvasSize.y;
7892
7906
  glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
7893
7907
  const color = canvasClearColor;
7894
7908
  if (color.a > 0)
@@ -7904,8 +7918,7 @@ function glClearCanvas()
7904
7918
  function glSetTexture(texture, wrap=false)
7905
7919
  {
7906
7920
  // must flush cache with the old texture to set a new one
7907
- if (!glContext || texture === glActiveTexture)
7908
- return;
7921
+ if (!glContext || texture === glActiveTexture) return;
7909
7922
 
7910
7923
  glFlush();
7911
7924
  glActiveTexture = texture;
@@ -8001,6 +8014,7 @@ function glCreateTexture(image)
8001
8014
  function glDeleteTexture(texture)
8002
8015
  {
8003
8016
  if (!glContext) return;
8017
+
8004
8018
  glContext.deleteTexture(texture);
8005
8019
  }
8006
8020
 
@@ -8085,8 +8099,7 @@ function glFlush()
8085
8099
  * @memberof WebGL */
8086
8100
  function glCopyToContext(context)
8087
8101
  {
8088
- if (!glEnable || !glContext)
8089
- return;
8102
+ if (!glEnable || !glContext) return;
8090
8103
 
8091
8104
  glFlush();
8092
8105
  context.drawImage(glCanvas, 0, 0);
@@ -9682,7 +9695,7 @@ class UIObject
9682
9695
  this.onUpdate();
9683
9696
 
9684
9697
  // unset active if disabled
9685
- if (this.disabled && this == uiSystem.activeObject)
9698
+ if (this.disabled && this === uiSystem.activeObject)
9686
9699
  uiSystem.activeObject = undefined;
9687
9700
 
9688
9701
  const wasHover = uiSystem.lastHoverObject === this;