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.d.ts +91 -78
- package/dist/littlejs.esm.js +287 -272
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +279 -266
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +184 -175
- package/examples/box2d/gameObjects.js +5 -5
- package/examples/electron/index.html +2 -2
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/platformer/gameEffects.js +1 -1
- package/examples/shorts/base.html +2 -1
- package/examples/starter/index.html +3 -2
- package/package.json +1 -1
- package/plugins/uiSystem.js +1 -1
- package/reference.md +5 -5
- package/src/engine.js +16 -28
- package/src/engineAudio.js +2 -4
- package/src/engineBuild.js +1 -0
- package/src/engineDebug.js +96 -94
- package/src/engineDraw.js +8 -28
- package/src/engineExport.js +8 -6
- package/src/engineInput.js +3 -6
- package/src/engineMath.js +1201 -0
- package/src/engineMedals.js +4 -4
- package/src/engineObject.js +1 -2
- package/src/engineRelease.js +1 -3
- package/src/engineSettings.js +2 -2
- package/src/engineTileLayer.js +7 -9
- package/src/engineUtilities.js +34 -1179
- package/src/engineWebGL.js +7 -10
package/dist/littlejs.esm.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.
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
369
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1356
|
-
return; // already recording
|
|
1322
|
+
ASSERT(!debugVideoCaptureIsActive(), 'Already capturing video!');
|
|
1357
1323
|
|
|
1358
|
-
|
|
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 = '';
|
|
1337
|
+
|
|
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
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
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
|
-
|
|
1371
|
-
downloadLink.href = url;
|
|
1372
|
-
downloadLink.click();
|
|
1373
|
-
URL.revokeObjectURL(url);
|
|
1353
|
+
saveDataURL(url, 'capture.webm', 1e3);
|
|
1374
1354
|
};
|
|
1375
1355
|
|
|
1376
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1383
|
+
// save debug video info
|
|
1384
|
+
debugVideoCapture =
|
|
1391
1385
|
{
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
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
|
-
|
|
1410
|
-
return; // not recording
|
|
1398
|
+
ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
|
|
1411
1399
|
|
|
1412
1400
|
// stop recording
|
|
1413
|
-
LOG(`Video capture ended. ${
|
|
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
|
-
|
|
1423
|
-
return; // not recording
|
|
1412
|
+
ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
|
|
1424
1413
|
|
|
1425
1414
|
// save the video frame
|
|
1426
1415
|
combineCanvases();
|
|
1427
|
-
|
|
1428
|
-
debugVideoCaptureIcon.textContent = '● REC '
|
|
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
|
|
1457
|
+
* @namespace Math
|
|
1468
1458
|
*/
|
|
1469
1459
|
|
|
1470
1460
|
/** The value of PI
|
|
1471
1461
|
* @type {number}
|
|
1472
1462
|
* @default Math.PI
|
|
1473
|
-
* @memberof
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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=
|
|
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 {[
|
|
6808
|
-
this.savedRenderSettings = [
|
|
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
|
-
|
|
6823
|
-
|
|
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
|
-
//
|
|
6857
|
+
//saveCanvas(this.canvas);
|
|
6841
6858
|
|
|
6842
6859
|
// set stuff back to normal
|
|
6843
|
-
[
|
|
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 =
|
|
7891
|
-
glCanvas.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
|
|
9698
|
+
if (this.disabled && this === uiSystem.activeObject)
|
|
9686
9699
|
uiSystem.activeObject = undefined;
|
|
9687
9700
|
|
|
9688
9701
|
const wasHover = uiSystem.lastHoverObject === this;
|
|
@@ -12378,13 +12391,10 @@ export
|
|
|
12378
12391
|
debugText,
|
|
12379
12392
|
debugClear,
|
|
12380
12393
|
debugScreenshot,
|
|
12381
|
-
debugSaveCanvas,
|
|
12382
|
-
debugSaveText,
|
|
12383
|
-
debugSaveDataURL,
|
|
12384
12394
|
debugShowErrors,
|
|
12385
|
-
debugVideoCaptureIsActive,
|
|
12386
12395
|
debugVideoCaptureStart,
|
|
12387
12396
|
debugVideoCaptureStop,
|
|
12397
|
+
debugVideoCaptureIsActive,
|
|
12388
12398
|
|
|
12389
12399
|
// Settings
|
|
12390
12400
|
cameraPos,
|
|
@@ -12481,7 +12491,7 @@ export
|
|
|
12481
12491
|
setDebugWatermark,
|
|
12482
12492
|
setDebugKey,
|
|
12483
12493
|
|
|
12484
|
-
//
|
|
12494
|
+
// Math
|
|
12485
12495
|
PI,
|
|
12486
12496
|
abs,
|
|
12487
12497
|
floor,
|
|
@@ -12509,8 +12519,13 @@ export
|
|
|
12509
12519
|
isOverlapping,
|
|
12510
12520
|
isIntersecting,
|
|
12511
12521
|
wave,
|
|
12522
|
+
|
|
12523
|
+
// Utilities
|
|
12512
12524
|
formatTime,
|
|
12513
12525
|
fetchJSON,
|
|
12526
|
+
saveText,
|
|
12527
|
+
saveCanvas,
|
|
12528
|
+
saveDataURL,
|
|
12514
12529
|
|
|
12515
12530
|
// Random
|
|
12516
12531
|
rand,
|
|
@@ -12556,7 +12571,6 @@ export
|
|
|
12556
12571
|
TextureInfo,
|
|
12557
12572
|
mainCanvas,
|
|
12558
12573
|
mainContext,
|
|
12559
|
-
drawCanvas,
|
|
12560
12574
|
drawContext,
|
|
12561
12575
|
workCanvas,
|
|
12562
12576
|
workContext,
|
|
@@ -12569,6 +12583,7 @@ export
|
|
|
12569
12583
|
worldToScreen,
|
|
12570
12584
|
screenToWorldDelta,
|
|
12571
12585
|
worldToScreenDelta,
|
|
12586
|
+
screenToWorldTransform,
|
|
12572
12587
|
drawTile,
|
|
12573
12588
|
drawRect,
|
|
12574
12589
|
drawRectGradient,
|