littlejsengine 1.15.9 → 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 +154 -158
- package/dist/littlejs.esm.js +573 -570
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +553 -553
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +392 -396
- package/examples/box2d/game.js +1 -1
- package/examples/box2d/gameObjects.js +5 -5
- package/examples/breakout/game.js +3 -3
- package/examples/electron/game.js +1 -2
- package/examples/electron/index.html +2 -2
- package/examples/htmlMenu/game.js +0 -1
- package/examples/index.html +1 -1
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/module/game.js +1 -2
- package/examples/particles/index.html +1 -1
- package/examples/platformer/game.js +4 -4
- package/examples/platformer/gameEffects.js +1 -1
- package/examples/puzzle/game.js +2 -2
- package/examples/shorts/base.html +4 -3
- package/examples/shorts/box2dPool.js +2 -2
- package/examples/shorts/empty.js +1 -1
- package/examples/shorts/{systemFont.js → fontImage.js} +3 -3
- package/examples/shorts/fps.js +1 -1
- package/examples/shorts/helloWorld.js +2 -2
- package/examples/shorts/nineSlice.js +2 -2
- package/examples/shorts/slidingPuzzle.js +1 -1
- package/examples/starter/game.js +1 -2
- package/examples/starter/index.html +3 -2
- package/examples/stress/index.html +1 -1
- package/examples/typescript/game.js +1 -2
- package/examples/typescript/game.ts +1 -2
- package/package.json +1 -1
- package/plugins/box2d.js +8 -8
- package/plugins/drawUtilities.js +6 -6
- package/plugins/postProcess.js +13 -20
- package/plugins/uiSystem.js +16 -4
- package/reference.md +9 -11
- package/src/engine.js +40 -54
- package/src/engineAudio.js +5 -7
- package/src/engineBuild.js +1 -0
- package/src/engineDebug.js +163 -161
- package/src/engineDraw.js +108 -130
- package/src/engineExport.js +20 -17
- package/src/engineInput.js +4 -7
- package/src/engineMath.js +1201 -0
- package/src/engineMedals.js +7 -7
- package/src/engineObject.js +5 -4
- package/src/engineRelease.js +2 -4
- package/src/engineSettings.js +22 -32
- package/src/engineTileLayer.js +9 -10
- package/src/engineUtilities.js +34 -1187
- package/src/engineWebGL.js +13 -15
package/dist/littlejs.release.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.
|
|
36
|
+
const engineVersion = '1.16.2';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -180,8 +180,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
180
180
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
181
181
|
|
|
182
182
|
// disable smoothing for pixel art
|
|
183
|
-
|
|
184
|
-
mainContext.imageSmoothingEnabled = !tilesPixelated;
|
|
183
|
+
mainContext.imageSmoothingEnabled = !tilesPixelated;
|
|
185
184
|
|
|
186
185
|
// setup gl rendering if enabled
|
|
187
186
|
glPreRender();
|
|
@@ -193,7 +192,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
193
192
|
// update time keeping
|
|
194
193
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
195
194
|
frameTimeLastMS = frameTimeMS;
|
|
196
|
-
if (debug ||
|
|
195
|
+
if (debug || debugWatermark)
|
|
197
196
|
averageFPS = lerp(averageFPS, 1e3/(frameTimeDeltaMS||1), .05);
|
|
198
197
|
const debugSpeedUp = debug && keyIsDown('Equal'); // +
|
|
199
198
|
const debugSpeedDown = debug && keyIsDown('Minus'); // -
|
|
@@ -221,6 +220,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
221
220
|
debugUpdate();
|
|
222
221
|
gameUpdatePost();
|
|
223
222
|
inputUpdatePost();
|
|
223
|
+
if (debugVideoCaptureIsActive())
|
|
224
|
+
renderFrame();
|
|
224
225
|
}
|
|
225
226
|
else
|
|
226
227
|
{
|
|
@@ -271,33 +272,20 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
271
272
|
if (!wasUpdated)
|
|
272
273
|
updateCanvas();
|
|
273
274
|
|
|
274
|
-
// render
|
|
275
|
+
// render the game and objects
|
|
275
276
|
enginePreRender();
|
|
276
277
|
gameRender();
|
|
277
278
|
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
278
279
|
for (const o of engineObjects)
|
|
279
280
|
o.destroyed || o.render();
|
|
281
|
+
|
|
282
|
+
// post rendering
|
|
280
283
|
gameRenderPost();
|
|
281
284
|
pluginList.forEach(plugin=>plugin.render?.());
|
|
282
285
|
inputRender();
|
|
283
286
|
debugRender();
|
|
284
287
|
glFlush();
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (showWatermark && !debugVideoCaptureIsActive())
|
|
288
|
-
{
|
|
289
|
-
// update fps display
|
|
290
|
-
overlayContext.textAlign = 'right';
|
|
291
|
-
overlayContext.textBaseline = 'top';
|
|
292
|
-
overlayContext.font = '1em monospace';
|
|
293
|
-
overlayContext.fillStyle = '#000';
|
|
294
|
-
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
295
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
296
|
-
+ (glEnable ? ' GL' : ' 2D') ;
|
|
297
|
-
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
298
|
-
overlayContext.fillStyle = '#fff';
|
|
299
|
-
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
300
|
-
}
|
|
288
|
+
debugRenderPost();
|
|
301
289
|
drawCount = 0;
|
|
302
290
|
}
|
|
303
291
|
}
|
|
@@ -316,8 +304,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
316
304
|
const fixedAspect = canvasFixedSize.x / canvasFixedSize.y;
|
|
317
305
|
const w = innerAspect < fixedAspect ? '100%' : '';
|
|
318
306
|
const h = innerAspect < fixedAspect ? '' : '100%';
|
|
319
|
-
|
|
320
|
-
|
|
307
|
+
mainCanvas.style.width = w;
|
|
308
|
+
mainCanvas.style.height = h;
|
|
321
309
|
if (glCanvas)
|
|
322
310
|
{
|
|
323
311
|
glCanvas.style.width = w;
|
|
@@ -332,6 +320,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
332
320
|
|
|
333
321
|
// responsive aspect ratio with native resolution
|
|
334
322
|
const innerAspect = innerWidth / innerHeight;
|
|
323
|
+
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
335
324
|
if (canvasMaxAspect && innerAspect > canvasMaxAspect)
|
|
336
325
|
{
|
|
337
326
|
// full height
|
|
@@ -346,12 +335,12 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
346
335
|
}
|
|
347
336
|
}
|
|
348
337
|
|
|
349
|
-
// clear main
|
|
350
|
-
|
|
351
|
-
|
|
338
|
+
// clear main canvas and set size
|
|
339
|
+
mainCanvas.width = mainCanvasSize.x;
|
|
340
|
+
mainCanvas.height = mainCanvasSize.y;
|
|
352
341
|
|
|
353
342
|
// apply the clear color to main canvas
|
|
354
|
-
if (canvasClearColor.a > 0)
|
|
343
|
+
if (canvasClearColor.a > 0 && !glEnable)
|
|
355
344
|
{
|
|
356
345
|
mainContext.fillStyle = canvasClearColor.toString();
|
|
357
346
|
mainContext.fillRect(0, 0, mainCanvasSize.x, mainCanvasSize.y);
|
|
@@ -359,19 +348,15 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
359
348
|
}
|
|
360
349
|
|
|
361
350
|
// set default line join and cap
|
|
362
|
-
|
|
363
|
-
mainContext.
|
|
364
|
-
mainContext.lineCap = overlayContext.lineCap = lineCap;
|
|
351
|
+
mainContext.lineJoin = 'round';
|
|
352
|
+
mainContext.lineCap = 'round';
|
|
365
353
|
}
|
|
354
|
+
|
|
355
|
+
// skip setup if headless
|
|
356
|
+
if (headlessMode) return startEngine();
|
|
366
357
|
|
|
367
|
-
//
|
|
368
|
-
|
|
369
|
-
{
|
|
370
|
-
await gameInit();
|
|
371
|
-
engineUpdate();
|
|
372
|
-
}
|
|
373
|
-
if (headlessMode)
|
|
374
|
-
return startEngine();
|
|
358
|
+
// setup webgl
|
|
359
|
+
glInit(rootElement);
|
|
375
360
|
|
|
376
361
|
// setup html
|
|
377
362
|
const styleRoot =
|
|
@@ -383,36 +368,30 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
383
368
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
384
369
|
'-webkit-touch-callout:none'; // compatibility for ios
|
|
385
370
|
rootElement.style.cssText = styleRoot;
|
|
386
|
-
|
|
387
|
-
rootElement.appendChild(mainCanvas);
|
|
371
|
+
mainCanvas = rootElement.appendChild(document.createElement('canvas'));
|
|
388
372
|
drawContext = mainContext = mainCanvas.getContext('2d');
|
|
389
373
|
|
|
390
374
|
// init stuff and start engine
|
|
391
375
|
inputInit();
|
|
392
376
|
audioInit();
|
|
393
377
|
debugInit();
|
|
394
|
-
glInit();
|
|
395
|
-
|
|
396
|
-
// create overlay canvas for hud to appear above gl canvas
|
|
397
|
-
overlayCanvas = document.createElement('canvas')
|
|
398
|
-
rootElement.appendChild(overlayCanvas);
|
|
399
|
-
overlayContext = overlayCanvas.getContext('2d');
|
|
400
378
|
|
|
401
379
|
// setup canvases
|
|
402
380
|
// transform way is still more reliable then flexbox or grid
|
|
403
381
|
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
404
382
|
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
405
|
-
mainCanvas.style.cssText =
|
|
383
|
+
mainCanvas.style.cssText = styleCanvas;
|
|
406
384
|
if (glCanvas)
|
|
407
385
|
glCanvas.style.cssText = styleCanvas;
|
|
408
386
|
setCanvasPixelated(canvasPixelated);
|
|
409
|
-
setOverlayCanvasPixelated(overlayCanvasPixelated);
|
|
410
387
|
updateCanvas();
|
|
411
388
|
glPreRender();
|
|
412
389
|
|
|
413
|
-
// create offscreen
|
|
414
|
-
workCanvas = new OffscreenCanvas(
|
|
415
|
-
workContext = workCanvas.getContext('2d'
|
|
390
|
+
// create offscreen canvases for image processing
|
|
391
|
+
workCanvas = new OffscreenCanvas(64, 64);
|
|
392
|
+
workContext = workCanvas.getContext('2d');
|
|
393
|
+
workReadCanvas = new OffscreenCanvas(64, 64);
|
|
394
|
+
workReadContext = workReadCanvas.getContext('2d', { willReadFrequently: true });
|
|
416
395
|
|
|
417
396
|
// create promises for loading images
|
|
418
397
|
const promises = imageSources.map((src, textureIndex)=>
|
|
@@ -464,13 +443,20 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
464
443
|
await Promise.all(promises);
|
|
465
444
|
return startEngine();
|
|
466
445
|
|
|
446
|
+
async function startEngine()
|
|
447
|
+
{
|
|
448
|
+
// wait for gameInit to load
|
|
449
|
+
await gameInit();
|
|
450
|
+
engineUpdate();
|
|
451
|
+
}
|
|
452
|
+
|
|
467
453
|
///////////////////////////////////////////////////////////////////////////
|
|
468
454
|
// LittleJS Splash Screen
|
|
469
455
|
function drawEngineSplashScreen(t)
|
|
470
456
|
{
|
|
471
|
-
const x =
|
|
472
|
-
const w =
|
|
473
|
-
const h =
|
|
457
|
+
const x = mainContext;
|
|
458
|
+
const w = mainCanvas.width = innerWidth;
|
|
459
|
+
const h = mainCanvas.height = innerHeight;
|
|
474
460
|
|
|
475
461
|
{
|
|
476
462
|
// background
|
|
@@ -748,7 +734,7 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
748
734
|
* - Debug functionality is disabled to reduce size and increase performance
|
|
749
735
|
*/
|
|
750
736
|
|
|
751
|
-
let
|
|
737
|
+
let debugWatermark = 0;
|
|
752
738
|
let debugKey = '';
|
|
753
739
|
const debug = 0;
|
|
754
740
|
const debugOverlay = 0;
|
|
@@ -764,6 +750,7 @@ function LOG (){}
|
|
|
764
750
|
function debugInit (){}
|
|
765
751
|
function debugUpdate (){}
|
|
766
752
|
function debugRender (){}
|
|
753
|
+
function debugRenderPost (){}
|
|
767
754
|
function debugRect (){}
|
|
768
755
|
function debugPoly (){}
|
|
769
756
|
function debugCircle (){}
|
|
@@ -773,9 +760,6 @@ function debugOverlap (){}
|
|
|
773
760
|
function debugText (){}
|
|
774
761
|
function debugClear (){}
|
|
775
762
|
function debugScreenshot (){}
|
|
776
|
-
function debugSaveCanvas (){}
|
|
777
|
-
function debugSaveText (){}
|
|
778
|
-
function debugSaveDataURL(){}
|
|
779
763
|
function debugShowErrors(){}
|
|
780
764
|
function debugVideoCaptureIsActive(){ return false; }
|
|
781
765
|
function debugVideoCaptureStart (){}
|
|
@@ -789,99 +773,99 @@ function debugProtectConstant(o){ return o; }
|
|
|
789
773
|
* - Color - holds a rgba color with some math functions
|
|
790
774
|
* - Timer - tracks time automatically
|
|
791
775
|
* - RandomGenerator - seeded random number generator
|
|
792
|
-
* @namespace
|
|
776
|
+
* @namespace Math
|
|
793
777
|
*/
|
|
794
778
|
|
|
795
779
|
/** The value of PI
|
|
796
780
|
* @type {number}
|
|
797
781
|
* @default Math.PI
|
|
798
|
-
* @memberof
|
|
782
|
+
* @memberof Math */
|
|
799
783
|
const PI = Math.PI;
|
|
800
784
|
|
|
801
785
|
/** Returns absolute value of value passed in
|
|
802
786
|
* @param {number} value
|
|
803
787
|
* @return {number}
|
|
804
|
-
* @memberof
|
|
788
|
+
* @memberof Math */
|
|
805
789
|
const abs = Math.abs;
|
|
806
790
|
|
|
807
791
|
/** Returns floored value of value passed in
|
|
808
792
|
* @param {number} value
|
|
809
793
|
* @return {number}
|
|
810
|
-
* @memberof
|
|
794
|
+
* @memberof Math */
|
|
811
795
|
const floor = Math.floor;
|
|
812
796
|
|
|
813
797
|
/** Returns ceiled value of value passed in
|
|
814
798
|
* @param {number} value
|
|
815
799
|
* @return {number}
|
|
816
|
-
* @memberof
|
|
800
|
+
* @memberof Math */
|
|
817
801
|
const ceil = Math.ceil;
|
|
818
802
|
|
|
819
803
|
/** Returns rounded value passed in
|
|
820
804
|
* @param {number} value
|
|
821
805
|
* @return {number}
|
|
822
|
-
* @memberof
|
|
806
|
+
* @memberof Math */
|
|
823
807
|
const round = Math.round;
|
|
824
808
|
|
|
825
809
|
/** Returns lowest value passed in
|
|
826
810
|
* @param {...number} values
|
|
827
811
|
* @return {number}
|
|
828
|
-
* @memberof
|
|
812
|
+
* @memberof Math */
|
|
829
813
|
const min = Math.min;
|
|
830
814
|
|
|
831
815
|
/** Returns highest value passed in
|
|
832
816
|
* @param {...number} values
|
|
833
817
|
* @return {number}
|
|
834
|
-
* @memberof
|
|
818
|
+
* @memberof Math */
|
|
835
819
|
const max = Math.max;
|
|
836
820
|
|
|
837
821
|
/** Returns the sign of value passed in
|
|
838
822
|
* @param {number} value
|
|
839
823
|
* @return {number}
|
|
840
|
-
* @memberof
|
|
824
|
+
* @memberof Math */
|
|
841
825
|
const sign = Math.sign;
|
|
842
826
|
|
|
843
827
|
/** Returns hypotenuse of values passed in
|
|
844
828
|
* @param {...number} values
|
|
845
829
|
* @return {number}
|
|
846
|
-
* @memberof
|
|
830
|
+
* @memberof Math */
|
|
847
831
|
const hypot = Math.hypot;
|
|
848
832
|
|
|
849
833
|
/** Returns log2 of value passed in
|
|
850
834
|
* @param {number} value
|
|
851
835
|
* @return {number}
|
|
852
|
-
* @memberof
|
|
836
|
+
* @memberof Math */
|
|
853
837
|
const log2 = Math.log2;
|
|
854
838
|
|
|
855
839
|
/** Returns sin of value passed in
|
|
856
840
|
* @param {number} value
|
|
857
841
|
* @return {number}
|
|
858
|
-
* @memberof
|
|
842
|
+
* @memberof Math */
|
|
859
843
|
const sin = Math.sin;
|
|
860
844
|
|
|
861
845
|
/** Returns cos of value passed in
|
|
862
846
|
* @param {number} value
|
|
863
847
|
* @return {number}
|
|
864
|
-
* @memberof
|
|
848
|
+
* @memberof Math */
|
|
865
849
|
const cos = Math.cos;
|
|
866
850
|
|
|
867
851
|
/** Returns tan of value passed in
|
|
868
852
|
* @param {number} value
|
|
869
853
|
* @return {number}
|
|
870
|
-
* @memberof
|
|
854
|
+
* @memberof Math */
|
|
871
855
|
const tan = Math.tan;
|
|
872
856
|
|
|
873
857
|
/** Returns atan2 of values passed in
|
|
874
858
|
* @param {number} y
|
|
875
859
|
* @param {number} x
|
|
876
860
|
* @return {number}
|
|
877
|
-
* @memberof
|
|
861
|
+
* @memberof Math */
|
|
878
862
|
const atan2 = Math.atan2;
|
|
879
863
|
|
|
880
864
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
881
865
|
* @param {number} dividend
|
|
882
866
|
* @param {number} [divisor]
|
|
883
867
|
* @return {number}
|
|
884
|
-
* @memberof
|
|
868
|
+
* @memberof Math */
|
|
885
869
|
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
886
870
|
|
|
887
871
|
/** Clamps the value between max and min
|
|
@@ -889,7 +873,7 @@ function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % di
|
|
|
889
873
|
* @param {number} [min]
|
|
890
874
|
* @param {number} [max]
|
|
891
875
|
* @return {number}
|
|
892
|
-
* @memberof
|
|
876
|
+
* @memberof Math */
|
|
893
877
|
function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
|
|
894
878
|
|
|
895
879
|
/** Returns what percentage the value is between valueA and valueB
|
|
@@ -897,7 +881,7 @@ function clamp(value, min=0, max=1) { return value < min ? min : value > max ? m
|
|
|
897
881
|
* @param {number} valueA
|
|
898
882
|
* @param {number} valueB
|
|
899
883
|
* @return {number}
|
|
900
|
-
* @memberof
|
|
884
|
+
* @memberof Math */
|
|
901
885
|
function percent(value, valueA, valueB)
|
|
902
886
|
{ return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
|
|
903
887
|
|
|
@@ -906,13 +890,9 @@ function percent(value, valueA, valueB)
|
|
|
906
890
|
* @param {number} valueB
|
|
907
891
|
* @param {number} percent
|
|
908
892
|
* @return {number}
|
|
909
|
-
* @memberof
|
|
893
|
+
* @memberof Math */
|
|
910
894
|
function lerp(valueA, valueB, percent)
|
|
911
|
-
{
|
|
912
|
-
if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
|
|
913
|
-
console.warn('lerp() parameter order changed! use lerp(start, end, p)');
|
|
914
|
-
return valueA + clamp(percent) * (valueB-valueA);
|
|
915
|
-
}
|
|
895
|
+
{ return valueA + clamp(percent) * (valueB-valueA); }
|
|
916
896
|
|
|
917
897
|
/** Gets percent between percentA and percentB and linearly interpolates between lerpA and lerpB
|
|
918
898
|
* A shortcut for lerp(lerpA, lerpB, percent(value, percentA, percentB))
|
|
@@ -922,7 +902,7 @@ function lerp(valueA, valueB, percent)
|
|
|
922
902
|
* @param {number} lerpA
|
|
923
903
|
* @param {number} lerpB
|
|
924
904
|
* @return {number}
|
|
925
|
-
* @memberof
|
|
905
|
+
* @memberof Math */
|
|
926
906
|
function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
927
907
|
{ return lerp(lerpA, lerpB, percent(value, percentA, percentB)); }
|
|
928
908
|
|
|
@@ -931,7 +911,7 @@ function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
|
931
911
|
* @param {number} valueB
|
|
932
912
|
* @param {number} [wrapSize]
|
|
933
913
|
* @return {number}
|
|
934
|
-
* @memberof
|
|
914
|
+
* @memberof Math */
|
|
935
915
|
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
936
916
|
{ const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
|
|
937
917
|
|
|
@@ -941,19 +921,15 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
941
921
|
* @param {number} percent
|
|
942
922
|
* @param {number} [wrapSize]
|
|
943
923
|
* @return {number}
|
|
944
|
-
* @memberof
|
|
924
|
+
* @memberof Math */
|
|
945
925
|
function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
946
|
-
{
|
|
947
|
-
if (valueA >= 0 && valueA <= 1 && ((valueB < 0 || valueB > 1) && (percent < 0 || percent > 1)))
|
|
948
|
-
console.warn('lerpWrap() parameter order changed! use lerpWrap(start, end, p)');
|
|
949
|
-
return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize);
|
|
950
|
-
}
|
|
926
|
+
{ return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
|
|
951
927
|
|
|
952
928
|
/** Returns signed wrapped distance between the two angles passed in
|
|
953
929
|
* @param {number} angleA
|
|
954
930
|
* @param {number} angleB
|
|
955
931
|
* @return {number}
|
|
956
|
-
* @memberof
|
|
932
|
+
* @memberof Math */
|
|
957
933
|
function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
|
|
958
934
|
|
|
959
935
|
/** Linearly interpolates between the angles passed in with wrapping
|
|
@@ -961,25 +937,25 @@ function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*P
|
|
|
961
937
|
* @param {number} angleB
|
|
962
938
|
* @param {number} percent
|
|
963
939
|
* @return {number}
|
|
964
|
-
* @memberof
|
|
940
|
+
* @memberof Math */
|
|
965
941
|
function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
|
|
966
942
|
|
|
967
943
|
/** Applies smoothstep function to the percentage value
|
|
968
944
|
* @param {number} percent
|
|
969
945
|
* @return {number}
|
|
970
|
-
* @memberof
|
|
946
|
+
* @memberof Math */
|
|
971
947
|
function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
972
948
|
|
|
973
949
|
/** Checks if the value passed in is a power of two
|
|
974
950
|
* @param {number} value
|
|
975
951
|
* @return {boolean}
|
|
976
|
-
* @memberof
|
|
952
|
+
* @memberof Math */
|
|
977
953
|
function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
978
954
|
|
|
979
955
|
/** Returns the nearest power of two not less than the value
|
|
980
956
|
* @param {number} value
|
|
981
957
|
* @return {number}
|
|
982
|
-
* @memberof
|
|
958
|
+
* @memberof Math */
|
|
983
959
|
function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
984
960
|
|
|
985
961
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
@@ -989,7 +965,7 @@ function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
|
989
965
|
* @param {Vector2} posB - Center of box B
|
|
990
966
|
* @param {Vector2} [sizeB=(0,0)] - Size of box B, uses a point if undefined
|
|
991
967
|
* @return {boolean} - True if overlapping
|
|
992
|
-
* @memberof
|
|
968
|
+
* @memberof Math */
|
|
993
969
|
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
994
970
|
{
|
|
995
971
|
const dx = (posA.x - posB.x)*2;
|
|
@@ -1005,7 +981,7 @@ function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
|
1005
981
|
* @param {Vector2} pos - Center of box
|
|
1006
982
|
* @param {Vector2} size - Size of box
|
|
1007
983
|
* @return {boolean} - True if intersecting
|
|
1008
|
-
* @memberof
|
|
984
|
+
* @memberof Math */
|
|
1009
985
|
function isIntersecting(start, end, pos, size)
|
|
1010
986
|
{
|
|
1011
987
|
// Liang-Barsky algorithm
|
|
@@ -1046,52 +1022,29 @@ function isIntersecting(start, end, pos, size)
|
|
|
1046
1022
|
* @param {number} [t=time] - Value to use for time of the wave
|
|
1047
1023
|
* @param {number} [offset] - Value to use for time offset of the wave
|
|
1048
1024
|
* @return {number} - Value waving between 0 and amplitude
|
|
1049
|
-
* @memberof
|
|
1025
|
+
* @memberof Math */
|
|
1050
1026
|
function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
1051
1027
|
{ return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
|
|
1052
1028
|
|
|
1053
|
-
/** Formats seconds to mm:ss style for display purposes
|
|
1054
|
-
* @param {number} t - time in seconds
|
|
1055
|
-
* @return {string}
|
|
1056
|
-
* @memberof Utilities */
|
|
1057
|
-
function formatTime(t)
|
|
1058
|
-
{
|
|
1059
|
-
const sign = t < 0 ? '-' : '';
|
|
1060
|
-
t = abs(t)|0;
|
|
1061
|
-
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
1065
|
-
* @param {string} url - URL of JSON file
|
|
1066
|
-
* @return {Promise<object>}
|
|
1067
|
-
* @memberof Utilities */
|
|
1068
|
-
async function fetchJSON(url)
|
|
1069
|
-
{
|
|
1070
|
-
const response = await fetch(url);
|
|
1071
|
-
if (!response.ok)
|
|
1072
|
-
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
1073
|
-
return response.json();
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
1029
|
/**
|
|
1077
1030
|
* Check if object is a valid number, not NaN or undefined, but it may be infinite
|
|
1078
1031
|
* @param {any} n
|
|
1079
1032
|
* @return {boolean}
|
|
1080
|
-
* @memberof
|
|
1033
|
+
* @memberof Math */
|
|
1081
1034
|
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
1082
1035
|
|
|
1083
1036
|
/**
|
|
1084
1037
|
* Check if object is a valid string or can be converted to one
|
|
1085
1038
|
* @param {any} s
|
|
1086
1039
|
* @return {boolean}
|
|
1087
|
-
* @memberof
|
|
1040
|
+
* @memberof Math */
|
|
1088
1041
|
function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
|
|
1089
1042
|
|
|
1090
1043
|
/**
|
|
1091
1044
|
* Check if object is an array
|
|
1092
1045
|
* @param {any} a
|
|
1093
1046
|
* @return {boolean}
|
|
1094
|
-
* @memberof
|
|
1047
|
+
* @memberof Math */
|
|
1095
1048
|
function isArray(a) { return Array.isArray(a); }
|
|
1096
1049
|
|
|
1097
1050
|
/**
|
|
@@ -1107,7 +1060,7 @@ function isArray(a) { return Array.isArray(a); }
|
|
|
1107
1060
|
* @param {LineTestFunction} testFunction - Check if colliding
|
|
1108
1061
|
* @param {Vector2} [normal] - Optional vector to store the normal
|
|
1109
1062
|
* @return {Vector2|undefined} - Position of the collision or undefined if none found
|
|
1110
|
-
* @memberof
|
|
1063
|
+
* @memberof Math */
|
|
1111
1064
|
function lineTest(posStart, posEnd, testFunction, normal)
|
|
1112
1065
|
{
|
|
1113
1066
|
ASSERT(isVector2(posStart), 'posStart must be a vec2');
|
|
@@ -1119,8 +1072,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
|
|
|
1119
1072
|
const dx = posEnd.x - posStart.x;
|
|
1120
1073
|
const dy = posEnd.y - posStart.y;
|
|
1121
1074
|
const totalLength = hypot(dx, dy);
|
|
1122
|
-
if (!totalLength)
|
|
1123
|
-
return;
|
|
1075
|
+
if (!totalLength) return;
|
|
1124
1076
|
|
|
1125
1077
|
// current integer cell we are in
|
|
1126
1078
|
const pos = posStart.floor();
|
|
@@ -1354,14 +1306,14 @@ class RandomGenerator
|
|
|
1354
1306
|
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
1355
1307
|
* a = vec2(5); // set a to (5, 5)
|
|
1356
1308
|
* b = vec2(); // set b to (0, 0)
|
|
1357
|
-
* @memberof
|
|
1309
|
+
* @memberof Math */
|
|
1358
1310
|
function vec2(x=0, y) { return new Vector2(x, y === undefined ? x : y); }
|
|
1359
1311
|
|
|
1360
1312
|
/**
|
|
1361
1313
|
* Check if object is a valid Vector2
|
|
1362
1314
|
* @param {any} v
|
|
1363
1315
|
* @return {boolean}
|
|
1364
|
-
* @memberof
|
|
1316
|
+
* @memberof Math */
|
|
1365
1317
|
function isVector2(v) { return v instanceof Vector2 && v.isValid(); }
|
|
1366
1318
|
|
|
1367
1319
|
// vector2 asserts
|
|
@@ -1611,7 +1563,7 @@ class Vector2
|
|
|
1611
1563
|
* @param {number} [b=1] - blue
|
|
1612
1564
|
* @param {number} [a=1] - alpha
|
|
1613
1565
|
* @return {Color}
|
|
1614
|
-
* @memberof
|
|
1566
|
+
* @memberof Math
|
|
1615
1567
|
*/
|
|
1616
1568
|
function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
1617
1569
|
|
|
@@ -1622,14 +1574,14 @@ function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
|
1622
1574
|
* @param {number} [l=1] - lightness
|
|
1623
1575
|
* @param {number} [a=1] - alpha
|
|
1624
1576
|
* @return {Color}
|
|
1625
|
-
* @memberof
|
|
1577
|
+
* @memberof Math */
|
|
1626
1578
|
function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
1627
1579
|
|
|
1628
1580
|
/**
|
|
1629
1581
|
* Check if object is a valid Color
|
|
1630
1582
|
* @param {any} c
|
|
1631
1583
|
* @return {boolean}
|
|
1632
|
-
* @memberof
|
|
1584
|
+
* @memberof Math */
|
|
1633
1585
|
function isColor(c) { return c instanceof Color && c.isValid(); }
|
|
1634
1586
|
|
|
1635
1587
|
// color asserts
|
|
@@ -1810,7 +1762,7 @@ class Color
|
|
|
1810
1762
|
toString(useAlpha = true)
|
|
1811
1763
|
{
|
|
1812
1764
|
if (debug && !this.isValid())
|
|
1813
|
-
return
|
|
1765
|
+
return '#000';
|
|
1814
1766
|
const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
|
|
1815
1767
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
1816
1768
|
}
|
|
@@ -1867,67 +1819,67 @@ class Color
|
|
|
1867
1819
|
|
|
1868
1820
|
/** Color - White #ffffff
|
|
1869
1821
|
* @type {Color}
|
|
1870
|
-
* @memberof
|
|
1822
|
+
* @memberof Math */
|
|
1871
1823
|
const WHITE = debugProtectConstant(rgb());
|
|
1872
1824
|
|
|
1873
1825
|
/** Color - Clear White #757474ff with 0 alpha
|
|
1874
1826
|
* @type {Color}
|
|
1875
|
-
* @memberof
|
|
1827
|
+
* @memberof Math */
|
|
1876
1828
|
const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
1877
1829
|
|
|
1878
1830
|
/** Color - Black #000000
|
|
1879
1831
|
* @type {Color}
|
|
1880
|
-
* @memberof
|
|
1832
|
+
* @memberof Math */
|
|
1881
1833
|
const BLACK = debugProtectConstant(rgb(0,0,0));
|
|
1882
1834
|
|
|
1883
1835
|
/** Color - Clear Black #000000 with 0 alpha
|
|
1884
1836
|
* @type {Color}
|
|
1885
|
-
* @memberof
|
|
1837
|
+
* @memberof Math */
|
|
1886
1838
|
const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
|
|
1887
1839
|
|
|
1888
1840
|
/** Color - Gray #808080
|
|
1889
1841
|
* @type {Color}
|
|
1890
|
-
* @memberof
|
|
1842
|
+
* @memberof Math */
|
|
1891
1843
|
const GRAY = debugProtectConstant(rgb(.5,.5,.5));
|
|
1892
1844
|
|
|
1893
1845
|
/** Color - Red #ff0000
|
|
1894
1846
|
* @type {Color}
|
|
1895
|
-
* @memberof
|
|
1847
|
+
* @memberof Math */
|
|
1896
1848
|
const RED = debugProtectConstant(rgb(1,0,0));
|
|
1897
1849
|
|
|
1898
1850
|
/** Color - Orange #ff8000
|
|
1899
1851
|
* @type {Color}
|
|
1900
|
-
* @memberof
|
|
1852
|
+
* @memberof Math */
|
|
1901
1853
|
const ORANGE = debugProtectConstant(rgb(1,.5,0));
|
|
1902
1854
|
|
|
1903
1855
|
/** Color - Yellow #ffff00
|
|
1904
1856
|
* @type {Color}
|
|
1905
|
-
* @memberof
|
|
1857
|
+
* @memberof Math */
|
|
1906
1858
|
const YELLOW = debugProtectConstant(rgb(1,1,0));
|
|
1907
1859
|
|
|
1908
1860
|
/** Color - Green #00ff00
|
|
1909
1861
|
* @type {Color}
|
|
1910
|
-
* @memberof
|
|
1862
|
+
* @memberof Math */
|
|
1911
1863
|
const GREEN = debugProtectConstant(rgb(0,1,0));
|
|
1912
1864
|
|
|
1913
1865
|
/** Color - Cyan #00ffff
|
|
1914
1866
|
* @type {Color}
|
|
1915
|
-
* @memberof
|
|
1867
|
+
* @memberof Math */
|
|
1916
1868
|
const CYAN = debugProtectConstant(rgb(0,1,1));
|
|
1917
1869
|
|
|
1918
1870
|
/** Color - Blue #0000ff
|
|
1919
1871
|
* @type {Color}
|
|
1920
|
-
* @memberof
|
|
1872
|
+
* @memberof Math */
|
|
1921
1873
|
const BLUE = debugProtectConstant(rgb(0,0,1));
|
|
1922
1874
|
|
|
1923
1875
|
/** Color - Purple #8000ff
|
|
1924
1876
|
* @type {Color}
|
|
1925
|
-
* @memberof
|
|
1877
|
+
* @memberof Math */
|
|
1926
1878
|
const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
1927
1879
|
|
|
1928
1880
|
/** Color - Magenta #ff00ff
|
|
1929
1881
|
* @type {Color}
|
|
1930
|
-
* @memberof
|
|
1882
|
+
* @memberof Math */
|
|
1931
1883
|
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
1932
1884
|
|
|
1933
1885
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -2013,6 +1965,84 @@ class Timer
|
|
|
2013
1965
|
* @return {number} */
|
|
2014
1966
|
valueOf() { return this.get(); }
|
|
2015
1967
|
}
|
|
1968
|
+
/**
|
|
1969
|
+
* LittleJS Utility Classes and Functions
|
|
1970
|
+
* - General purpose math library
|
|
1971
|
+
* - Vector2 - fast, simple, easy 2D vector class
|
|
1972
|
+
* - Color - holds a rgba color with some math functions
|
|
1973
|
+
* - Timer - tracks time automatically
|
|
1974
|
+
* - RandomGenerator - seeded random number generator
|
|
1975
|
+
* @namespace Utilities
|
|
1976
|
+
*/
|
|
1977
|
+
|
|
1978
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
1979
|
+
* @param {number} t - time in seconds
|
|
1980
|
+
* @return {string}
|
|
1981
|
+
* @memberof Utilities */
|
|
1982
|
+
function formatTime(t)
|
|
1983
|
+
{
|
|
1984
|
+
const sign = t < 0 ? '-' : '';
|
|
1985
|
+
t = abs(t)|0;
|
|
1986
|
+
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
1990
|
+
* @param {string} url - URL of JSON file
|
|
1991
|
+
* @return {Promise<object>}
|
|
1992
|
+
* @memberof Utilities */
|
|
1993
|
+
async function fetchJSON(url)
|
|
1994
|
+
{
|
|
1995
|
+
const response = await fetch(url);
|
|
1996
|
+
if (!response.ok)
|
|
1997
|
+
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
1998
|
+
return response.json();
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2002
|
+
|
|
2003
|
+
/** Save a text file to disk
|
|
2004
|
+
* @param {string} text
|
|
2005
|
+
* @param {string} [filename]
|
|
2006
|
+
* @param {string} [type]
|
|
2007
|
+
* @memberof Utilities */
|
|
2008
|
+
function saveText(text, filename='text', type='text/plain')
|
|
2009
|
+
{ saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
2010
|
+
|
|
2011
|
+
/** Save a canvas to disk
|
|
2012
|
+
* @param {HTMLCanvasElement|OffscreenCanvas} canvas
|
|
2013
|
+
* @param {string} [filename]
|
|
2014
|
+
* @param {string} [type]
|
|
2015
|
+
* @memberof Utilities */
|
|
2016
|
+
function saveCanvas(canvas, filename='screenshot', type='image/png')
|
|
2017
|
+
{
|
|
2018
|
+
if (canvas instanceof OffscreenCanvas)
|
|
2019
|
+
{
|
|
2020
|
+
// copy to temporary canvas and save
|
|
2021
|
+
const saveCanvas = document.createElement('canvas');
|
|
2022
|
+
saveCanvas.width = canvas.width;
|
|
2023
|
+
saveCanvas.height = canvas.height;
|
|
2024
|
+
saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
|
|
2025
|
+
saveDataURL(saveCanvas.toDataURL(type), filename);
|
|
2026
|
+
}
|
|
2027
|
+
else
|
|
2028
|
+
saveDataURL(canvas.toDataURL(type), filename);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
/** Save a data url to disk
|
|
2032
|
+
* @param {string} url
|
|
2033
|
+
* @param {string} [filename]
|
|
2034
|
+
* @param {number} [revokeTime] - how long before revoking the url
|
|
2035
|
+
* @memberof Utilities */
|
|
2036
|
+
function saveDataURL(url, filename='download', revokeTime)
|
|
2037
|
+
{
|
|
2038
|
+
// create link for saving screenshots
|
|
2039
|
+
const link = document.createElement('a');
|
|
2040
|
+
link.download = filename;
|
|
2041
|
+
link.href = url;
|
|
2042
|
+
link.click();
|
|
2043
|
+
if (revokeTime !== undefined)
|
|
2044
|
+
setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
|
|
2045
|
+
}
|
|
2016
2046
|
/**
|
|
2017
2047
|
* LittleJS Engine Settings
|
|
2018
2048
|
* - All settings for the engine are here
|
|
@@ -2050,7 +2080,7 @@ let cameraScale = 32;
|
|
|
2050
2080
|
* @memberof Settings */
|
|
2051
2081
|
let canvasColorTiles = true;
|
|
2052
2082
|
|
|
2053
|
-
/** Color to clear the canvas to before render
|
|
2083
|
+
/** Color to clear the canvas to before render, does not clear if alpha is 0
|
|
2054
2084
|
* @type {Color}
|
|
2055
2085
|
* @memberof Draw */
|
|
2056
2086
|
let canvasClearColor = CLEAR_BLACK;
|
|
@@ -2087,15 +2117,7 @@ let canvasFixedSize = vec2();
|
|
|
2087
2117
|
* @type {boolean}
|
|
2088
2118
|
* @default
|
|
2089
2119
|
* @memberof Settings */
|
|
2090
|
-
let canvasPixelated =
|
|
2091
|
-
|
|
2092
|
-
/** Use nearest canvas scaling for more pixelated look
|
|
2093
|
-
* - If enabled sets css image-rendering:pixelated
|
|
2094
|
-
* - This defaults to false because text looks better with smoothing
|
|
2095
|
-
* @type {boolean}
|
|
2096
|
-
* @default
|
|
2097
|
-
* @memberof Settings */
|
|
2098
|
-
let overlayCanvasPixelated = false;
|
|
2120
|
+
let canvasPixelated = false;
|
|
2099
2121
|
|
|
2100
2122
|
/** Disables texture filtering for crisper pixel art
|
|
2101
2123
|
* @type {boolean}
|
|
@@ -2143,13 +2165,19 @@ let glCircleSides = 32;
|
|
|
2143
2165
|
* @type {Vector2}
|
|
2144
2166
|
* @default Vector2(16,16)
|
|
2145
2167
|
* @memberof Settings */
|
|
2146
|
-
let
|
|
2168
|
+
let tileDefaultSize = vec2(16);
|
|
2169
|
+
|
|
2170
|
+
/** Default padding pixels around tiles
|
|
2171
|
+
* @type {number}
|
|
2172
|
+
* @default
|
|
2173
|
+
* @memberof Settings */
|
|
2174
|
+
let tileDefaultPadding = 0;
|
|
2147
2175
|
|
|
2148
|
-
/**
|
|
2176
|
+
/** Default amount of pixels smaller to draw tiles to prevent neighbor bleeding
|
|
2149
2177
|
* @type {number}
|
|
2150
2178
|
* @default
|
|
2151
2179
|
* @memberof Settings */
|
|
2152
|
-
let
|
|
2180
|
+
let tileDefaultBleed = 0;
|
|
2153
2181
|
|
|
2154
2182
|
///////////////////////////////////////////////////////////////////////////////
|
|
2155
2183
|
// Object settings
|
|
@@ -2361,7 +2389,7 @@ function setCameraScale(scale) { cameraScale = scale; }
|
|
|
2361
2389
|
* @memberof Settings */
|
|
2362
2390
|
function setCanvasColorTiles(colorTiles) { canvasColorTiles = colorTiles; }
|
|
2363
2391
|
|
|
2364
|
-
/** Set color to clear the canvas to before render
|
|
2392
|
+
/** Set color to clear the canvas to before render, does not clear if alpha is 0
|
|
2365
2393
|
* @param {Color} color
|
|
2366
2394
|
* @memberof Settings */
|
|
2367
2395
|
function setCanvasClearColor(color) { canvasClearColor = color.copy(); }
|
|
@@ -2387,7 +2415,6 @@ function setCanvasMaxAspect(aspect) { canvasMaxAspect = aspect; }
|
|
|
2387
2415
|
function setCanvasFixedSize(size) { canvasFixedSize = size.copy(); }
|
|
2388
2416
|
|
|
2389
2417
|
/** Use nearest scaling algorithm for canvas for more pixelated look
|
|
2390
|
-
* - If enabled sets css image-rendering:pixelated
|
|
2391
2418
|
* @param {boolean} pixelated
|
|
2392
2419
|
* @memberof Settings */
|
|
2393
2420
|
function setCanvasPixelated(pixelated)
|
|
@@ -2399,18 +2426,6 @@ function setCanvasPixelated(pixelated)
|
|
|
2399
2426
|
glCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
|
|
2400
2427
|
}
|
|
2401
2428
|
|
|
2402
|
-
/** Use nearest scaling algorithm for canvas for more pixelated look
|
|
2403
|
-
* - If enabled sets css image-rendering:pixelated
|
|
2404
|
-
* - This defaults to false because text looks better with smoothing
|
|
2405
|
-
* @param {boolean} pixelated
|
|
2406
|
-
* @memberof Settings */
|
|
2407
|
-
function setOverlayCanvasPixelated(pixelated)
|
|
2408
|
-
{
|
|
2409
|
-
overlayCanvasPixelated = pixelated;
|
|
2410
|
-
if (overlayCanvas)
|
|
2411
|
-
overlayCanvas.style.imageRendering = pixelated ? 'pixelated' : '';
|
|
2412
|
-
}
|
|
2413
|
-
|
|
2414
2429
|
/** Disables texture filtering for crisper pixel art
|
|
2415
2430
|
* @param {boolean} pixelated
|
|
2416
2431
|
* @memberof Settings */
|
|
@@ -2454,12 +2469,17 @@ function setGLCircleSides(sides) { glCircleSides = sides; }
|
|
|
2454
2469
|
/** Set default size of tiles in pixels
|
|
2455
2470
|
* @param {Vector2} size
|
|
2456
2471
|
* @memberof Settings */
|
|
2457
|
-
function
|
|
2472
|
+
function setTileDefaultSize(size) { tileDefaultSize = size.copy(); }
|
|
2458
2473
|
|
|
2459
|
-
/**
|
|
2460
|
-
* @param {number}
|
|
2474
|
+
/** Default padding pixels around tiles
|
|
2475
|
+
* @param {number} padding
|
|
2476
|
+
* @memberof Settings */
|
|
2477
|
+
function setTileDefaultPadding(padding) { tileDefaultPadding = padding; }
|
|
2478
|
+
|
|
2479
|
+
/** Default amount of pixels smaller to draw tiles to prevent neighbor bleeding
|
|
2480
|
+
* @param {number} bleed
|
|
2461
2481
|
* @memberof Settings */
|
|
2462
|
-
function
|
|
2482
|
+
function setTileDefaultBleed(bleed) { tileDefaultBleed = bleed; }
|
|
2463
2483
|
|
|
2464
2484
|
/** Set if collisions between objects are enabled
|
|
2465
2485
|
* @param {boolean} enable
|
|
@@ -2610,7 +2630,7 @@ function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUn
|
|
|
2610
2630
|
/** Set if watermark with FPS should be shown
|
|
2611
2631
|
* @param {boolean} show
|
|
2612
2632
|
* @memberof Debug */
|
|
2613
|
-
function
|
|
2633
|
+
function setDebugWatermark(show) { debugWatermark = show; }
|
|
2614
2634
|
|
|
2615
2635
|
/** Set key code used to toggle debug mode, Esc by default
|
|
2616
2636
|
* @param {string} key
|
|
@@ -3046,10 +3066,11 @@ class EngineObject
|
|
|
3046
3066
|
* @return {number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
3047
3067
|
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
3048
3068
|
|
|
3049
|
-
/** Attaches a child to this with a
|
|
3069
|
+
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3050
3070
|
* @param {EngineObject} child
|
|
3051
3071
|
* @param {Vector2} [localPos=(0,0)]
|
|
3052
|
-
* @param {number} [localAngle]
|
|
3072
|
+
* @param {number} [localAngle]
|
|
3073
|
+
* @return {EngineObject} The child object added */
|
|
3053
3074
|
addChild(child, localPos=vec2(), localAngle=0)
|
|
3054
3075
|
{
|
|
3055
3076
|
ASSERT(!child.parent && !this.children.includes(child));
|
|
@@ -3059,6 +3080,7 @@ class EngineObject
|
|
|
3059
3080
|
child.parent = this;
|
|
3060
3081
|
child.localPos = localPos.copy();
|
|
3061
3082
|
child.localAngle = localAngle;
|
|
3083
|
+
return child;
|
|
3062
3084
|
}
|
|
3063
3085
|
|
|
3064
3086
|
/** Removes a child from this one
|
|
@@ -3125,8 +3147,7 @@ class EngineObject
|
|
|
3125
3147
|
/** Render debug info for this object */
|
|
3126
3148
|
renderDebugInfo()
|
|
3127
3149
|
{
|
|
3128
|
-
if (!debug)
|
|
3129
|
-
return;
|
|
3150
|
+
if (!debug) return;
|
|
3130
3151
|
|
|
3131
3152
|
// show object info for debugging
|
|
3132
3153
|
const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
|
|
@@ -3149,7 +3170,6 @@ class EngineObject
|
|
|
3149
3170
|
* There are 3 canvas/contexts available to draw to...
|
|
3150
3171
|
* mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
|
|
3151
3172
|
* glCanvas - Used by the accelerated WebGL batch rendering system.
|
|
3152
|
-
* overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
|
|
3153
3173
|
*
|
|
3154
3174
|
* The WebGL rendering system is very fast with some caveats...
|
|
3155
3175
|
* - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
|
|
@@ -3169,21 +3189,6 @@ let mainCanvas;
|
|
|
3169
3189
|
* @memberof Draw */
|
|
3170
3190
|
let mainContext;
|
|
3171
3191
|
|
|
3172
|
-
/** A canvas that appears on top of everything the same size as mainCanvas
|
|
3173
|
-
* @type {HTMLCanvasElement}
|
|
3174
|
-
* @memberof Draw */
|
|
3175
|
-
let overlayCanvas;
|
|
3176
|
-
|
|
3177
|
-
/** 2d context for overlayCanvas
|
|
3178
|
-
* @type {CanvasRenderingContext2D}
|
|
3179
|
-
* @memberof Draw */
|
|
3180
|
-
let overlayContext;
|
|
3181
|
-
|
|
3182
|
-
/** The default canvas to use for drawing, usually mainCanvas
|
|
3183
|
-
* @type {HTMLCanvasElement|OffscreenCanvas}
|
|
3184
|
-
* @memberof Draw */
|
|
3185
|
-
let drawCanvas;
|
|
3186
|
-
|
|
3187
3192
|
/** The default 2d context to use for drawing, usually mainContext
|
|
3188
3193
|
* @type {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}
|
|
3189
3194
|
* @memberof Draw */
|
|
@@ -3199,6 +3204,16 @@ let workCanvas;
|
|
|
3199
3204
|
* @memberof Draw */
|
|
3200
3205
|
let workContext;
|
|
3201
3206
|
|
|
3207
|
+
/** Offscreen canvas with willReadFrequently that can be used for image processing
|
|
3208
|
+
* @type {OffscreenCanvas}
|
|
3209
|
+
* @memberof Draw */
|
|
3210
|
+
let workReadCanvas;
|
|
3211
|
+
|
|
3212
|
+
/** Offscreen canvas with willReadFrequently that can be used for image processing
|
|
3213
|
+
* @type {OffscreenCanvasRenderingContext2D}
|
|
3214
|
+
* @memberof Draw */
|
|
3215
|
+
let workReadContext;
|
|
3216
|
+
|
|
3202
3217
|
/** The size of the main canvas (and other secondary canvases)
|
|
3203
3218
|
* @type {Vector2}
|
|
3204
3219
|
* @memberof Draw */
|
|
@@ -3221,7 +3236,7 @@ let drawCount;
|
|
|
3221
3236
|
* - This can take vecs or floats for easier use and conversion
|
|
3222
3237
|
* - If an index is passed in, the tile size and index will determine the position
|
|
3223
3238
|
* @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
|
|
3224
|
-
* @param {Vector2|number} [size
|
|
3239
|
+
* @param {Vector2|number} [size] - Size of tile in pixels
|
|
3225
3240
|
* @param {number} [textureIndex] - Texture index to use
|
|
3226
3241
|
* @param {number} [padding] - How many pixels padding around tiles
|
|
3227
3242
|
* @return {TileInfo}
|
|
@@ -3231,7 +3246,7 @@ let drawCount;
|
|
|
3231
3246
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
3232
3247
|
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
3233
3248
|
* @memberof Draw */
|
|
3234
|
-
function tile(pos=new Vector2, size=
|
|
3249
|
+
function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding)
|
|
3235
3250
|
{
|
|
3236
3251
|
if (headlessMode)
|
|
3237
3252
|
return new TileInfo;
|
|
@@ -3270,13 +3285,13 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
|
|
|
3270
3285
|
class TileInfo
|
|
3271
3286
|
{
|
|
3272
3287
|
/** Create a tile info object
|
|
3273
|
-
* @param {Vector2} [pos=(0,0)]
|
|
3274
|
-
* @param {Vector2} [size
|
|
3275
|
-
* @param {number} [textureIndex]
|
|
3276
|
-
* @param {number} [padding]
|
|
3277
|
-
* @param {number} [
|
|
3288
|
+
* @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
|
|
3289
|
+
* @param {Vector2} [size] - Size of tile in pixels
|
|
3290
|
+
* @param {number} [textureIndex] - Texture index to use
|
|
3291
|
+
* @param {number} [padding] - How many pixels padding around tiles
|
|
3292
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3278
3293
|
*/
|
|
3279
|
-
constructor(pos=vec2(), size=
|
|
3294
|
+
constructor(pos=vec2(), size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3280
3295
|
{
|
|
3281
3296
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
3282
3297
|
this.pos = pos.copy();
|
|
@@ -3289,7 +3304,7 @@ class TileInfo
|
|
|
3289
3304
|
/** @property {TextureInfo} - The texture info for this tile */
|
|
3290
3305
|
this.textureInfo = textureInfos[this.textureIndex];
|
|
3291
3306
|
/** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
|
|
3292
|
-
this.
|
|
3307
|
+
this.bleed = bleed;
|
|
3293
3308
|
}
|
|
3294
3309
|
|
|
3295
3310
|
/** Returns a copy of this tile offset by a vector
|
|
@@ -3297,7 +3312,7 @@ class TileInfo
|
|
|
3297
3312
|
* @return {TileInfo}
|
|
3298
3313
|
*/
|
|
3299
3314
|
offset(offset)
|
|
3300
|
-
{ return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.
|
|
3315
|
+
{ return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleed); }
|
|
3301
3316
|
|
|
3302
3317
|
/** Returns a copy of this tile offset by a number of animation frames
|
|
3303
3318
|
* @param {number} frame - Offset to apply in animation frames
|
|
@@ -3320,7 +3335,7 @@ class TileInfo
|
|
|
3320
3335
|
this.size = textureInfo.size.copy();
|
|
3321
3336
|
this.textureInfo = textureInfo;
|
|
3322
3337
|
// do not use padding or bleed
|
|
3323
|
-
this.
|
|
3338
|
+
this.bleed = this.padding = 0;
|
|
3324
3339
|
return this;
|
|
3325
3340
|
}
|
|
3326
3341
|
}
|
|
@@ -3386,7 +3401,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3386
3401
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3387
3402
|
|
|
3388
3403
|
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
3389
|
-
const
|
|
3404
|
+
const bleed = tileInfo?.bleed ?? 0;
|
|
3390
3405
|
if (useWebGL && glEnable)
|
|
3391
3406
|
{
|
|
3392
3407
|
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
@@ -3401,13 +3416,13 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3401
3416
|
const w = tileInfo.size.x * sizeInverse.x;
|
|
3402
3417
|
const h = tileInfo.size.y * sizeInverse.y;
|
|
3403
3418
|
glSetTexture(textureInfo.glTexture);
|
|
3404
|
-
if (
|
|
3419
|
+
if (bleed)
|
|
3405
3420
|
{
|
|
3406
|
-
const
|
|
3407
|
-
const
|
|
3421
|
+
const bleedX = sizeInverse.x*bleed;
|
|
3422
|
+
const bleedY = sizeInverse.y*bleed;
|
|
3408
3423
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
3409
|
-
x +
|
|
3410
|
-
x -
|
|
3424
|
+
x + bleedX, y + bleedY,
|
|
3425
|
+
x - bleedX + w, y - bleedY + h,
|
|
3411
3426
|
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
3412
3427
|
}
|
|
3413
3428
|
else
|
|
@@ -3435,7 +3450,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3435
3450
|
// calculate uvs and render
|
|
3436
3451
|
const x = tileInfo.pos.x, y = tileInfo.pos.y;
|
|
3437
3452
|
const w = tileInfo.size.x, h = tileInfo.size.y;
|
|
3438
|
-
drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor,
|
|
3453
|
+
drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor, bleed);
|
|
3439
3454
|
}
|
|
3440
3455
|
else
|
|
3441
3456
|
{
|
|
@@ -3761,7 +3776,11 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
3761
3776
|
ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
|
|
3762
3777
|
|
|
3763
3778
|
if (!screenSpace)
|
|
3764
|
-
|
|
3779
|
+
{
|
|
3780
|
+
pos = worldToScreen(pos);
|
|
3781
|
+
size = size.scale(cameraScale);
|
|
3782
|
+
angle -= cameraAngle;
|
|
3783
|
+
}
|
|
3765
3784
|
context.save();
|
|
3766
3785
|
context.translate(pos.x+.5, pos.y+.5);
|
|
3767
3786
|
context.rotate(angle);
|
|
@@ -3800,26 +3819,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
3800
3819
|
drawTextScreen(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
|
|
3801
3820
|
}
|
|
3802
3821
|
|
|
3803
|
-
/** Draw text
|
|
3804
|
-
* Automatically splits new lines into rows
|
|
3805
|
-
* @param {string|number} text
|
|
3806
|
-
* @param {Vector2} pos
|
|
3807
|
-
* @param {number} [size]
|
|
3808
|
-
* @param {Color} [color=(1,1,1,1)]
|
|
3809
|
-
* @param {number} [lineWidth]
|
|
3810
|
-
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3811
|
-
* @param {CanvasTextAlign} [textAlign='center']
|
|
3812
|
-
* @param {string} [font=fontDefault]
|
|
3813
|
-
* @param {string} [fontStyle]
|
|
3814
|
-
* @param {number} [maxWidth]
|
|
3815
|
-
* @param {number} [angle]
|
|
3816
|
-
* @memberof Draw */
|
|
3817
|
-
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0)
|
|
3818
|
-
{
|
|
3819
|
-
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, overlayContext);
|
|
3820
|
-
}
|
|
3821
|
-
|
|
3822
|
-
/** Draw text on overlay canvas in screen space
|
|
3822
|
+
/** Draw text in screen space
|
|
3823
3823
|
* Automatically splits new lines into rows
|
|
3824
3824
|
* @param {string|number} text
|
|
3825
3825
|
* @param {Vector2} pos
|
|
@@ -3832,9 +3832,9 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
3832
3832
|
* @param {string} [fontStyle]
|
|
3833
3833
|
* @param {number} [maxWidth]
|
|
3834
3834
|
* @param {number} [angle]
|
|
3835
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=
|
|
3835
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3836
3836
|
* @memberof Draw */
|
|
3837
|
-
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=
|
|
3837
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
|
|
3838
3838
|
{
|
|
3839
3839
|
ASSERT(isString(text), 'text must be a string');
|
|
3840
3840
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3878,16 +3878,16 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
3878
3878
|
* @memberof Draw */
|
|
3879
3879
|
function screenToWorld(screenPos)
|
|
3880
3880
|
{
|
|
3881
|
+
ASSERT(isVector2(screenPos), 'screenPos must be a vec2');
|
|
3882
|
+
|
|
3881
3883
|
let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
3882
3884
|
let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
3883
3885
|
if (cameraAngle)
|
|
3884
3886
|
{
|
|
3885
3887
|
// apply camera rotation
|
|
3886
3888
|
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
3887
|
-
const
|
|
3888
|
-
|
|
3889
|
-
x = rotatedX;
|
|
3890
|
-
y = rotatedY;
|
|
3889
|
+
const xr = x * c - y * s, yr = x * s + y * c;
|
|
3890
|
+
x = xr; y = yr;
|
|
3891
3891
|
}
|
|
3892
3892
|
return new Vector2(x + cameraPos.x, y + cameraPos.y);
|
|
3893
3893
|
}
|
|
@@ -3898,16 +3898,16 @@ function screenToWorld(screenPos)
|
|
|
3898
3898
|
* @memberof Draw */
|
|
3899
3899
|
function worldToScreen(worldPos)
|
|
3900
3900
|
{
|
|
3901
|
+
ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
|
|
3902
|
+
|
|
3901
3903
|
let x = worldPos.x - cameraPos.x;
|
|
3902
3904
|
let y = worldPos.y - cameraPos.y;
|
|
3903
3905
|
if (cameraAngle)
|
|
3904
3906
|
{
|
|
3905
3907
|
// apply inverse camera rotation
|
|
3906
3908
|
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
3907
|
-
const
|
|
3908
|
-
|
|
3909
|
-
x = rotatedX;
|
|
3910
|
-
y = rotatedY;
|
|
3909
|
+
const xr = x * c - y * s, yr = x * s + y * c;
|
|
3910
|
+
x = xr; y = yr;
|
|
3911
3911
|
}
|
|
3912
3912
|
return new Vector2
|
|
3913
3913
|
(
|
|
@@ -3922,16 +3922,16 @@ function worldToScreen(worldPos)
|
|
|
3922
3922
|
* @memberof Draw */
|
|
3923
3923
|
function screenToWorldDelta(screenDelta)
|
|
3924
3924
|
{
|
|
3925
|
+
ASSERT(isVector2(screenDelta), 'screenDelta must be a vec2');
|
|
3926
|
+
|
|
3925
3927
|
let x = screenDelta.x / cameraScale;
|
|
3926
3928
|
let y = screenDelta.y / -cameraScale;
|
|
3927
3929
|
if (cameraAngle)
|
|
3928
3930
|
{
|
|
3929
3931
|
// apply camera rotation
|
|
3930
3932
|
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
3931
|
-
const
|
|
3932
|
-
|
|
3933
|
-
x = rotatedX;
|
|
3934
|
-
y = rotatedY;
|
|
3933
|
+
const xr = x * c - y * s, yr = x * s + y * c;
|
|
3934
|
+
x = xr; y = yr;
|
|
3935
3935
|
}
|
|
3936
3936
|
return new Vector2(x, y);
|
|
3937
3937
|
}
|
|
@@ -3942,16 +3942,16 @@ function screenToWorldDelta(screenDelta)
|
|
|
3942
3942
|
* @memberof Draw */
|
|
3943
3943
|
function worldToScreenDelta(worldDelta)
|
|
3944
3944
|
{
|
|
3945
|
+
ASSERT(isVector2(worldDelta), 'worldDelta must be a vec2');
|
|
3946
|
+
|
|
3945
3947
|
let x = worldDelta.x;
|
|
3946
3948
|
let y = worldDelta.y;
|
|
3947
3949
|
if (cameraAngle)
|
|
3948
3950
|
{
|
|
3949
3951
|
// apply inverse camera rotation
|
|
3950
3952
|
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
3951
|
-
const
|
|
3952
|
-
|
|
3953
|
-
x = rotatedX;
|
|
3954
|
-
y = rotatedY;
|
|
3953
|
+
const xr = x * c - y * s, yr = x * s + y * c;
|
|
3954
|
+
x = xr; y = yr;
|
|
3955
3955
|
}
|
|
3956
3956
|
return new Vector2(x * cameraScale, y * -cameraScale);
|
|
3957
3957
|
}
|
|
@@ -3964,6 +3964,10 @@ function worldToScreenDelta(worldDelta)
|
|
|
3964
3964
|
* @memberof Draw */
|
|
3965
3965
|
function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
|
|
3966
3966
|
{
|
|
3967
|
+
ASSERT(isVector2(screenPos), 'screenPos must be a vec2');
|
|
3968
|
+
ASSERT(isVector2(screenSize), 'screenSize must be a vec2');
|
|
3969
|
+
ASSERT(isNumber(screenAngle), 'screenAngle must be a number');
|
|
3970
|
+
|
|
3967
3971
|
return [
|
|
3968
3972
|
screenToWorld(screenPos),
|
|
3969
3973
|
screenSize.scale(1/cameraScale),
|
|
@@ -3971,28 +3975,13 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
|
|
|
3971
3975
|
];
|
|
3972
3976
|
}
|
|
3973
3977
|
|
|
3974
|
-
/** Convert world space transform to screen space
|
|
3975
|
-
* @param {Vector2} worldPos
|
|
3976
|
-
* @param {Vector2} worldSize
|
|
3977
|
-
* @param {number} [worldAngle]
|
|
3978
|
-
* @return {[Vector2, Vector2, number]} - [pos, size, angle]
|
|
3979
|
-
* @memberof Draw */
|
|
3980
|
-
function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
|
|
3981
|
-
{
|
|
3982
|
-
return [
|
|
3983
|
-
worldToScreen(worldPos),
|
|
3984
|
-
worldSize.scale(cameraScale),
|
|
3985
|
-
worldAngle - cameraAngle
|
|
3986
|
-
];
|
|
3987
|
-
}
|
|
3988
|
-
|
|
3989
3978
|
/** Get the size of the camera window in world space
|
|
3990
3979
|
* @return {Vector2}
|
|
3991
3980
|
* @memberof Draw */
|
|
3992
3981
|
function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
3993
3982
|
|
|
3994
|
-
/** Check if a point or circle is on screen
|
|
3995
|
-
* If size is a Vector2, uses the
|
|
3983
|
+
/** Check if a box, point, or circle is on screen with a circle test
|
|
3984
|
+
* If size is a Vector2, uses the length as diameter
|
|
3996
3985
|
* This can be used to cull offscreen objects from render or update
|
|
3997
3986
|
* @param {Vector2} pos - world space position
|
|
3998
3987
|
* @param {Vector2|number} size - world space size or diameter
|
|
@@ -4000,12 +3989,30 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
|
4000
3989
|
* @memberof Draw */
|
|
4001
3990
|
function isOnScreen(pos, size=0)
|
|
4002
3991
|
{
|
|
4003
|
-
pos
|
|
3992
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3993
|
+
ASSERT(isVector2(size) || isNumber(size), 'size must be a vec2 or number');
|
|
3994
|
+
|
|
3995
|
+
// optimized circle on screen test
|
|
3996
|
+
// pos = worldToScreen(pos);
|
|
3997
|
+
let x = pos.x - cameraPos.x;
|
|
3998
|
+
let y = pos.y - cameraPos.y;
|
|
3999
|
+
if (cameraAngle)
|
|
4000
|
+
{
|
|
4001
|
+
// apply inverse camera rotation
|
|
4002
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
4003
|
+
const xr = x * c - y * s, yr = x * s + y * c;
|
|
4004
|
+
x = xr; y = yr;
|
|
4005
|
+
}
|
|
4006
|
+
x *= cameraScale*2; y *= -cameraScale*2;
|
|
4007
|
+
|
|
4004
4008
|
if (size instanceof Vector2)
|
|
4005
|
-
size =
|
|
4006
|
-
size *= cameraScale
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
+
size = size.length(); // use length of vector as diameter
|
|
4010
|
+
size *= cameraScale;
|
|
4011
|
+
|
|
4012
|
+
// check against screen bounds
|
|
4013
|
+
const w = mainCanvasSize.x, h = mainCanvasSize.y;
|
|
4014
|
+
return x + size > -w && x - size < w &&
|
|
4015
|
+
y + size > -h && y - size < h;
|
|
4009
4016
|
}
|
|
4010
4017
|
|
|
4011
4018
|
/** Enable normal or additive blend mode
|
|
@@ -4019,18 +4026,19 @@ function setBlendMode(additive=false, context)
|
|
|
4019
4026
|
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
4020
4027
|
}
|
|
4021
4028
|
|
|
4022
|
-
/** Combines
|
|
4023
|
-
* This is necessary for things like
|
|
4029
|
+
/** Combines LittleJS canvases onto the main canvas
|
|
4030
|
+
* This is necessary for things like screenshots and video
|
|
4024
4031
|
* @memberof Draw */
|
|
4025
4032
|
function combineCanvases()
|
|
4026
4033
|
{
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
+
const w = mainCanvasSize.x, h = mainCanvasSize.y;
|
|
4035
|
+
workCanvas.width = w;
|
|
4036
|
+
workCanvas.height = h;
|
|
4037
|
+
workContext.fillRect(0,0,w,h); // remove background alpha
|
|
4038
|
+
glCopyToContext(workContext);
|
|
4039
|
+
workContext.drawImage(mainCanvas, 0, 0);
|
|
4040
|
+
mainCanvas.width |= 0;
|
|
4041
|
+
mainContext.drawImage(workCanvas, 0, 0);
|
|
4034
4042
|
}
|
|
4035
4043
|
|
|
4036
4044
|
/** Helper function to draw an image with color and additive color applied
|
|
@@ -4047,18 +4055,18 @@ function combineCanvases()
|
|
|
4047
4055
|
* @param {number} dHeight
|
|
4048
4056
|
* @param {Color} color
|
|
4049
4057
|
* @param {Color} [additiveColor]
|
|
4050
|
-
* @param {number} [
|
|
4058
|
+
* @param {number} [bleed] - How many pixels to shrink the source, used to fix bleeding
|
|
4051
4059
|
* @memberof Draw */
|
|
4052
|
-
function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor,
|
|
4060
|
+
function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleed=0)
|
|
4053
4061
|
{
|
|
4054
4062
|
function isWhite(c) { return c.r >= 1 && c.g >= 1 && c.b >= 1; }
|
|
4055
4063
|
function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
|
|
4056
|
-
const sx2 =
|
|
4057
|
-
const sy2 =
|
|
4064
|
+
const sx2 = bleed;
|
|
4065
|
+
const sy2 = bleed;
|
|
4058
4066
|
sWidth = max(1,sWidth|0);
|
|
4059
4067
|
sHeight = max(1,sHeight|0);
|
|
4060
|
-
const sWidth2 = sWidth - 2*
|
|
4061
|
-
const sHeight2 = sHeight - 2*
|
|
4068
|
+
const sWidth2 = sWidth - 2*bleed;
|
|
4069
|
+
const sHeight2 = sHeight - 2*bleed;
|
|
4062
4070
|
if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
|
|
4063
4071
|
{
|
|
4064
4072
|
// white texture with no additive alpha, no need to tint
|
|
@@ -4069,12 +4077,12 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
4069
4077
|
else
|
|
4070
4078
|
{
|
|
4071
4079
|
// copy to offscreen canvas
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4080
|
+
workReadCanvas.width = sWidth;
|
|
4081
|
+
workReadCanvas.height = sHeight;
|
|
4082
|
+
workReadContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
4075
4083
|
|
|
4076
4084
|
// tint image using offscreen work context
|
|
4077
|
-
const imageData =
|
|
4085
|
+
const imageData = workReadContext.getImageData(0, 0, sWidth, sHeight);
|
|
4078
4086
|
const data = imageData.data;
|
|
4079
4087
|
if (additiveColor && !isBlack(additiveColor))
|
|
4080
4088
|
{
|
|
@@ -4083,8 +4091,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
4083
4091
|
const colorAdd = [additiveColor.r * 255, additiveColor.g * 255, additiveColor.b * 255, additiveColor.a * 255];
|
|
4084
4092
|
for (let i = 0; i < data.length; ++i)
|
|
4085
4093
|
data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
|
|
4086
|
-
|
|
4087
|
-
context.drawImage(
|
|
4094
|
+
workReadContext.putImageData(imageData, 0, 0);
|
|
4095
|
+
context.drawImage(workReadCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
4088
4096
|
}
|
|
4089
4097
|
else
|
|
4090
4098
|
{
|
|
@@ -4095,9 +4103,9 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
4095
4103
|
data[i+1] *= color.g;
|
|
4096
4104
|
data[i+2] *= color.b;
|
|
4097
4105
|
}
|
|
4098
|
-
|
|
4106
|
+
workReadContext.putImageData(imageData, 0, 0);
|
|
4099
4107
|
context.globalAlpha = color.a;
|
|
4100
|
-
context.drawImage(
|
|
4108
|
+
context.drawImage(workReadCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
4101
4109
|
context.globalAlpha = 1;
|
|
4102
4110
|
}
|
|
4103
4111
|
}
|
|
@@ -4159,7 +4167,7 @@ class FontImage
|
|
|
4159
4167
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
|
|
4160
4168
|
{
|
|
4161
4169
|
// load default font image
|
|
4162
|
-
if (!engineFontImage)
|
|
4170
|
+
if (!image && !engineFontImage)
|
|
4163
4171
|
{
|
|
4164
4172
|
engineFontImage = new Image;
|
|
4165
4173
|
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
@@ -4182,23 +4190,14 @@ class FontImage
|
|
|
4182
4190
|
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
|
|
4183
4191
|
}
|
|
4184
4192
|
|
|
4185
|
-
/** Draw text
|
|
4186
|
-
* @param {string|number} text
|
|
4187
|
-
* @param {Vector2} pos
|
|
4188
|
-
* @param {number} [scale]
|
|
4189
|
-
* @param {boolean} [center]
|
|
4190
|
-
*/
|
|
4191
|
-
drawTextOverlay(text, pos, scale=4, center)
|
|
4192
|
-
{ this.drawText(text, pos, scale, center, overlayContext); }
|
|
4193
|
-
|
|
4194
|
-
/** Draw text on overlay canvas in screen space using the image font
|
|
4193
|
+
/** Draw text in screen space using the image font
|
|
4195
4194
|
* @param {string|number} text
|
|
4196
4195
|
* @param {Vector2} pos
|
|
4197
4196
|
* @param {number} [scale]
|
|
4198
4197
|
* @param {boolean} [center]
|
|
4199
4198
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
4200
4199
|
*/
|
|
4201
|
-
drawTextScreen(text, pos, scale=4, center=true, context=
|
|
4200
|
+
drawTextScreen(text, pos, scale=4, center=true, context=drawContext)
|
|
4202
4201
|
{
|
|
4203
4202
|
context.save();
|
|
4204
4203
|
const size = this.tileSize;
|
|
@@ -4591,8 +4590,7 @@ function inputInit()
|
|
|
4591
4590
|
}
|
|
4592
4591
|
function onMouseDown(e)
|
|
4593
4592
|
{
|
|
4594
|
-
if (isTouchDevice && touchInputEnable)
|
|
4595
|
-
return;
|
|
4593
|
+
if (isTouchDevice && touchInputEnable) return;
|
|
4596
4594
|
|
|
4597
4595
|
// fix stalled audio requiring user interaction
|
|
4598
4596
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
@@ -4643,8 +4641,7 @@ function inputInit()
|
|
|
4643
4641
|
let wasTouching;
|
|
4644
4642
|
function handleTouch(e)
|
|
4645
4643
|
{
|
|
4646
|
-
if (!touchInputEnable)
|
|
4647
|
-
return;
|
|
4644
|
+
if (!touchInputEnable) return;
|
|
4648
4645
|
|
|
4649
4646
|
// route touch to gamepad
|
|
4650
4647
|
if (touchGamepadEnable)
|
|
@@ -4708,8 +4705,7 @@ function inputInit()
|
|
|
4708
4705
|
}
|
|
4709
4706
|
|
|
4710
4707
|
// don't process touch gamepad if paused
|
|
4711
|
-
if (paused)
|
|
4712
|
-
return;
|
|
4708
|
+
if (paused) return;
|
|
4713
4709
|
|
|
4714
4710
|
// get center of left and right sides
|
|
4715
4711
|
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
@@ -4948,7 +4944,7 @@ function inputRender()
|
|
|
4948
4944
|
return;
|
|
4949
4945
|
|
|
4950
4946
|
// setup the canvas
|
|
4951
|
-
const context =
|
|
4947
|
+
const context = mainContext;
|
|
4952
4948
|
context.save();
|
|
4953
4949
|
context.globalAlpha = alpha*touchGamepadAlpha;
|
|
4954
4950
|
context.strokeStyle = '#fff';
|
|
@@ -5190,12 +5186,12 @@ class Sound
|
|
|
5190
5186
|
///////////////////////////////////////////////////////////////////////////////
|
|
5191
5187
|
|
|
5192
5188
|
/**
|
|
5193
|
-
* Sound Wave Object -
|
|
5194
|
-
* - this can be used to play wave, mp3, and ogg files
|
|
5189
|
+
* Sound Wave Object - Loads and stores an audio file for later use
|
|
5190
|
+
* - this can be used to load and play wave, mp3, and ogg files
|
|
5195
5191
|
* @extends Sound
|
|
5196
5192
|
* @memberof Audio
|
|
5197
5193
|
* @example
|
|
5198
|
-
* //
|
|
5194
|
+
* // load an audio asset file
|
|
5199
5195
|
* const sound_example = new SoundWave('sound.mp3');
|
|
5200
5196
|
*
|
|
5201
5197
|
* // play the sound
|
|
@@ -5395,8 +5391,7 @@ class SoundInstance
|
|
|
5395
5391
|
/** Pause this sound instance */
|
|
5396
5392
|
pause()
|
|
5397
5393
|
{
|
|
5398
|
-
if (this.isPaused())
|
|
5399
|
-
return;
|
|
5394
|
+
if (this.isPaused()) return;
|
|
5400
5395
|
|
|
5401
5396
|
// save current time and stop sound
|
|
5402
5397
|
this.pausedTime = this.getCurrentTime();
|
|
@@ -5408,8 +5403,7 @@ class SoundInstance
|
|
|
5408
5403
|
/** Unpauses this sound instance */
|
|
5409
5404
|
resume()
|
|
5410
5405
|
{
|
|
5411
|
-
if (!this.isPaused())
|
|
5412
|
-
return;
|
|
5406
|
+
if (!this.isPaused()) return;
|
|
5413
5407
|
|
|
5414
5408
|
// restart sound from paused time
|
|
5415
5409
|
this.start(this.pausedTime);
|
|
@@ -6147,26 +6141,26 @@ class TileLayer extends CanvasLayer
|
|
|
6147
6141
|
if (!this.context) return;
|
|
6148
6142
|
|
|
6149
6143
|
// save current render settings
|
|
6150
|
-
/** @type {[
|
|
6151
|
-
this.savedRenderSettings = [
|
|
6144
|
+
/** @type {[CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
|
|
6145
|
+
this.savedRenderSettings = [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
|
|
6152
6146
|
|
|
6153
6147
|
// set the draw canvas and context to this layer
|
|
6154
6148
|
// use camera settings to match this layer's canvas
|
|
6155
|
-
drawCanvas = this.canvas;
|
|
6156
6149
|
drawContext = this.context;
|
|
6157
6150
|
cameraPos = this.size.scale(.5);
|
|
6158
6151
|
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
6159
6152
|
cameraScale = tileSize.x;
|
|
6153
|
+
canvasClearColor = CLEAR_BLACK;
|
|
6160
6154
|
mainCanvasSize = this.size.multiply(tileSize);
|
|
6161
6155
|
if (clear)
|
|
6162
6156
|
{
|
|
6163
6157
|
// clear and set size
|
|
6164
|
-
|
|
6165
|
-
|
|
6158
|
+
this.canvas.width = mainCanvasSize.x;
|
|
6159
|
+
this.canvas.height = mainCanvasSize.y;
|
|
6166
6160
|
}
|
|
6167
6161
|
|
|
6168
6162
|
// disable smoothing for pixel art
|
|
6169
|
-
|
|
6163
|
+
drawContext.imageSmoothingEnabled = !tilesPixelated;
|
|
6170
6164
|
|
|
6171
6165
|
// setup gl rendering if enabled
|
|
6172
6166
|
glPreRender();
|
|
@@ -6179,10 +6173,10 @@ class TileLayer extends CanvasLayer
|
|
|
6179
6173
|
|
|
6180
6174
|
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
6181
6175
|
glCopyToContext(drawContext);
|
|
6182
|
-
//
|
|
6176
|
+
//saveCanvas(this.canvas);
|
|
6183
6177
|
|
|
6184
6178
|
// set stuff back to normal
|
|
6185
|
-
[
|
|
6179
|
+
[drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
|
|
6186
6180
|
}
|
|
6187
6181
|
|
|
6188
6182
|
/** Draw the tile at a given position in the tile grid
|
|
@@ -6250,8 +6244,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6250
6244
|
/** Destroy this tile layer */
|
|
6251
6245
|
destroy()
|
|
6252
6246
|
{
|
|
6253
|
-
if (this.destroyed)
|
|
6254
|
-
return;
|
|
6247
|
+
if (this.destroyed) return;
|
|
6255
6248
|
|
|
6256
6249
|
// remove from collision layers array and destroy
|
|
6257
6250
|
const index = tileCollisionLayers.indexOf(this);
|
|
@@ -6788,10 +6781,11 @@ function medalsInit(saveName)
|
|
|
6788
6781
|
|
|
6789
6782
|
// engine automatically renders medals
|
|
6790
6783
|
engineAddPlugin(undefined, medalsRender);
|
|
6784
|
+
|
|
6785
|
+
// plugin functions
|
|
6791
6786
|
function medalsRender()
|
|
6792
6787
|
{
|
|
6793
|
-
if (!medalsDisplayQueue.length)
|
|
6794
|
-
return;
|
|
6788
|
+
if (!medalsDisplayQueue.length) return;
|
|
6795
6789
|
|
|
6796
6790
|
// update first medal in queue
|
|
6797
6791
|
const medal = medalsDisplayQueue[0];
|
|
@@ -6881,8 +6875,7 @@ class Medal
|
|
|
6881
6875
|
/** Unlocks a medal if not already unlocked */
|
|
6882
6876
|
unlock()
|
|
6883
6877
|
{
|
|
6884
|
-
if (medalsPreventUnlock || this.unlocked)
|
|
6885
|
-
return;
|
|
6878
|
+
if (medalsPreventUnlock || this.unlocked) return;
|
|
6886
6879
|
|
|
6887
6880
|
// save the medal
|
|
6888
6881
|
ASSERT(medalsSaveName, 'save name must be set');
|
|
@@ -6895,10 +6888,10 @@ class Medal
|
|
|
6895
6888
|
*/
|
|
6896
6889
|
render(hidePercent=0)
|
|
6897
6890
|
{
|
|
6898
|
-
const context =
|
|
6891
|
+
const context = mainContext;
|
|
6899
6892
|
const width = min(medalDisplaySize.x, mainCanvas.width);
|
|
6900
6893
|
const height = medalDisplaySize.y;
|
|
6901
|
-
const x =
|
|
6894
|
+
const x = mainCanvas.width - width;
|
|
6902
6895
|
const y = -height*hidePercent;
|
|
6903
6896
|
const backgroundColor = hsl(0,0,.9);
|
|
6904
6897
|
|
|
@@ -6939,7 +6932,7 @@ class Medal
|
|
|
6939
6932
|
{
|
|
6940
6933
|
// draw the image or icon
|
|
6941
6934
|
if (this.image)
|
|
6942
|
-
|
|
6935
|
+
mainContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
|
|
6943
6936
|
else
|
|
6944
6937
|
drawTextScreen(this.icon, pos, size*.7, BLACK);
|
|
6945
6938
|
}
|
|
@@ -6960,7 +6953,7 @@ class Medal
|
|
|
6960
6953
|
* @namespace WebGL
|
|
6961
6954
|
*/
|
|
6962
6955
|
|
|
6963
|
-
/** The WebGL canvas which appears
|
|
6956
|
+
/** The WebGL canvas which appears below the main canvas
|
|
6964
6957
|
* @type {HTMLCanvasElement}
|
|
6965
6958
|
* @memberof WebGL */
|
|
6966
6959
|
let glCanvas;
|
|
@@ -6990,7 +6983,7 @@ const gl_MAX_POLY_VERTEXES = gl_ARRAY_BUFFER_SIZE / gl_POLY_VERTEX_BYTE_STRIDE |
|
|
|
6990
6983
|
///////////////////////////////////////////////////////////////////////////////
|
|
6991
6984
|
|
|
6992
6985
|
// Initialize WebGL, called automatically by the engine
|
|
6993
|
-
function glInit()
|
|
6986
|
+
function glInit(rootElement)
|
|
6994
6987
|
{
|
|
6995
6988
|
// keep set of texture infos so they can be restored if context is lost
|
|
6996
6989
|
glTextureInfos = new Set;
|
|
@@ -7014,8 +7007,7 @@ function glInit()
|
|
|
7014
7007
|
return;
|
|
7015
7008
|
}
|
|
7016
7009
|
|
|
7017
|
-
// attach the WebGL canvas
|
|
7018
|
-
const rootElement = mainCanvas.parentElement;
|
|
7010
|
+
// attach the WebGL canvas;
|
|
7019
7011
|
rootElement.appendChild(glCanvas);
|
|
7020
7012
|
|
|
7021
7013
|
// startup webgl
|
|
@@ -7116,8 +7108,7 @@ function glInit()
|
|
|
7116
7108
|
|
|
7117
7109
|
function glSetInstancedMode()
|
|
7118
7110
|
{
|
|
7119
|
-
if (!glPolyMode)
|
|
7120
|
-
return;
|
|
7111
|
+
if (!glPolyMode) return;
|
|
7121
7112
|
|
|
7122
7113
|
// setup instanced mode
|
|
7123
7114
|
glFlush();
|
|
@@ -7150,8 +7141,7 @@ function glSetInstancedMode()
|
|
|
7150
7141
|
|
|
7151
7142
|
function glSetPolyMode()
|
|
7152
7143
|
{
|
|
7153
|
-
if (glPolyMode)
|
|
7154
|
-
return;
|
|
7144
|
+
if (glPolyMode) return;
|
|
7155
7145
|
|
|
7156
7146
|
// setup poly mode
|
|
7157
7147
|
glFlush();
|
|
@@ -7230,9 +7220,12 @@ function glClearCanvas()
|
|
|
7230
7220
|
if (!glContext) return;
|
|
7231
7221
|
|
|
7232
7222
|
// clear and set to same size as main canvas
|
|
7233
|
-
glCanvas.width =
|
|
7234
|
-
glCanvas.height =
|
|
7223
|
+
glCanvas.width = mainCanvasSize.x;
|
|
7224
|
+
glCanvas.height = mainCanvasSize.y;
|
|
7235
7225
|
glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
|
|
7226
|
+
const color = canvasClearColor;
|
|
7227
|
+
if (color.a > 0)
|
|
7228
|
+
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
7236
7229
|
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
7237
7230
|
}
|
|
7238
7231
|
|
|
@@ -7244,8 +7237,7 @@ function glClearCanvas()
|
|
|
7244
7237
|
function glSetTexture(texture, wrap=false)
|
|
7245
7238
|
{
|
|
7246
7239
|
// must flush cache with the old texture to set a new one
|
|
7247
|
-
if (!glContext || texture === glActiveTexture)
|
|
7248
|
-
return;
|
|
7240
|
+
if (!glContext || texture === glActiveTexture) return;
|
|
7249
7241
|
|
|
7250
7242
|
glFlush();
|
|
7251
7243
|
glActiveTexture = texture;
|
|
@@ -7341,6 +7333,7 @@ function glCreateTexture(image)
|
|
|
7341
7333
|
function glDeleteTexture(texture)
|
|
7342
7334
|
{
|
|
7343
7335
|
if (!glContext) return;
|
|
7336
|
+
|
|
7344
7337
|
glContext.deleteTexture(texture);
|
|
7345
7338
|
}
|
|
7346
7339
|
|
|
@@ -7425,8 +7418,7 @@ function glFlush()
|
|
|
7425
7418
|
* @memberof WebGL */
|
|
7426
7419
|
function glCopyToContext(context)
|
|
7427
7420
|
{
|
|
7428
|
-
if (!glEnable || !glContext)
|
|
7429
|
-
return;
|
|
7421
|
+
if (!glEnable || !glContext) return;
|
|
7430
7422
|
|
|
7431
7423
|
glFlush();
|
|
7432
7424
|
context.drawImage(glCanvas, 0, 0);
|
|
@@ -7462,7 +7454,6 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
|
|
|
7462
7454
|
glFlush();
|
|
7463
7455
|
glSetInstancedMode();
|
|
7464
7456
|
|
|
7465
|
-
glPolyMode = false;
|
|
7466
7457
|
let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
|
|
7467
7458
|
glPositionData[offset++] = x;
|
|
7468
7459
|
glPositionData[offset++] = y;
|
|
@@ -7983,13 +7974,12 @@ class PostProcessPlugin
|
|
|
7983
7974
|
{
|
|
7984
7975
|
/** Create global post processing shader
|
|
7985
7976
|
* @param {string} shaderCode
|
|
7986
|
-
* @param {boolean} [includeOverlay]
|
|
7987
7977
|
* @param {boolean} [includeMainCanvas]
|
|
7988
7978
|
* @example
|
|
7989
7979
|
* // create the post process plugin object
|
|
7990
7980
|
* new PostProcessPlugin(shaderCode);
|
|
7991
7981
|
*/
|
|
7992
|
-
constructor(shaderCode,
|
|
7982
|
+
constructor(shaderCode, includeMainCanvas=true)
|
|
7993
7983
|
{
|
|
7994
7984
|
ASSERT(!postProcess, 'Post process already initialized');
|
|
7995
7985
|
postProcess = this;
|
|
@@ -8061,17 +8051,19 @@ class PostProcessPlugin
|
|
|
8061
8051
|
// clear out the buffer
|
|
8062
8052
|
glFlush();
|
|
8063
8053
|
|
|
8064
|
-
|
|
8054
|
+
// setup texture
|
|
8055
|
+
glContext.activeTexture(glContext.TEXTURE0);
|
|
8056
|
+
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
8057
|
+
if (includeMainCanvas)
|
|
8065
8058
|
{
|
|
8066
|
-
// copy
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
}
|
|
8059
|
+
// copy main canvas to work canvas
|
|
8060
|
+
workCanvas.width = mainCanvasSize.x;
|
|
8061
|
+
workCanvas.height = mainCanvasSize.y;
|
|
8062
|
+
glCopyToContext(workContext);
|
|
8063
|
+
workContext.drawImage(mainCanvas, 0, 0);
|
|
8064
|
+
|
|
8065
|
+
// copy work canvas to texture
|
|
8066
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
|
|
8075
8067
|
}
|
|
8076
8068
|
|
|
8077
8069
|
// setup shader program to draw a quad
|
|
@@ -8080,14 +8072,6 @@ class PostProcessPlugin
|
|
|
8080
8072
|
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, 1);
|
|
8081
8073
|
glContext.disable(glContext.BLEND);
|
|
8082
8074
|
|
|
8083
|
-
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
8084
|
-
glContext.activeTexture(glContext.TEXTURE0);
|
|
8085
|
-
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
8086
|
-
if (includeMainCanvas || includeOverlay)
|
|
8087
|
-
{
|
|
8088
|
-
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, mainCanvas);
|
|
8089
|
-
}
|
|
8090
|
-
|
|
8091
8075
|
// set vertex position attribute
|
|
8092
8076
|
const vertexByteStride = 8;
|
|
8093
8077
|
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
@@ -8316,12 +8300,14 @@ class UISystemPlugin
|
|
|
8316
8300
|
* // create the ui plugin object
|
|
8317
8301
|
* new UISystemPlugin;
|
|
8318
8302
|
*/
|
|
8319
|
-
constructor(context=
|
|
8303
|
+
constructor(context=mainContext)
|
|
8320
8304
|
{
|
|
8321
8305
|
ASSERT(!uiSystem, 'UI system already initialized');
|
|
8322
8306
|
uiSystem = this;
|
|
8323
8307
|
|
|
8324
8308
|
// default settings
|
|
8309
|
+
/** @property {boolean} - Activate when mouse is pressed down instead of clicked */
|
|
8310
|
+
this.activateOnPress = false;
|
|
8325
8311
|
/** @property {Color} - Default fill color for UI elements */
|
|
8326
8312
|
this.defaultColor = WHITE;
|
|
8327
8313
|
/** @property {Color} - Default outline color for UI elements */
|
|
@@ -8973,13 +8959,15 @@ class UIObject
|
|
|
8973
8959
|
uiSystem.uiObjects.push(this);
|
|
8974
8960
|
}
|
|
8975
8961
|
|
|
8976
|
-
/** Add a child UIObject to this object
|
|
8977
|
-
* @param {UIObject} child
|
|
8962
|
+
/** Add a child UIObject to this object, returns child for chaining
|
|
8963
|
+
* @param {UIObject} child
|
|
8964
|
+
* @return {UIObject} The child object added */
|
|
8978
8965
|
addChild(child)
|
|
8979
8966
|
{
|
|
8980
8967
|
ASSERT(!child.parent && !this.children.includes(child));
|
|
8981
8968
|
this.children.push(child);
|
|
8982
8969
|
child.parent = this;
|
|
8970
|
+
return child;
|
|
8983
8971
|
}
|
|
8984
8972
|
|
|
8985
8973
|
/** Remove a child UIObject from this object
|
|
@@ -9026,7 +9014,7 @@ class UIObject
|
|
|
9026
9014
|
this.onUpdate();
|
|
9027
9015
|
|
|
9028
9016
|
// unset active if disabled
|
|
9029
|
-
if (this.disabled && this
|
|
9017
|
+
if (this.disabled && this === uiSystem.activeObject)
|
|
9030
9018
|
uiSystem.activeObject = undefined;
|
|
9031
9019
|
|
|
9032
9020
|
const wasHover = uiSystem.lastHoverObject === this;
|
|
@@ -9052,8 +9040,16 @@ class UIObject
|
|
|
9052
9040
|
if (uiSystem.activeObject && !isActive)
|
|
9053
9041
|
uiSystem.activeObject.onRelease();
|
|
9054
9042
|
uiSystem.activeObject = this;
|
|
9043
|
+
|
|
9044
|
+
if (uiSystem.activateOnPress)
|
|
9045
|
+
{
|
|
9046
|
+
this.onClick();
|
|
9047
|
+
if (!this.soundPress && this.soundClick)
|
|
9048
|
+
this.soundClick.play();
|
|
9049
|
+
}
|
|
9055
9050
|
}
|
|
9056
9051
|
}
|
|
9052
|
+
if (!uiSystem.activateOnPress)
|
|
9057
9053
|
if (!mouseDown && this.isActiveObject() && this.interactive)
|
|
9058
9054
|
{
|
|
9059
9055
|
this.onClick();
|
|
@@ -11493,33 +11489,33 @@ async function box2dInit()
|
|
|
11493
11489
|
color = getDebugColor(color);
|
|
11494
11490
|
point1 = box2d.vec2FromPointer(point1);
|
|
11495
11491
|
point2 = box2d.vec2FromPointer(point2);
|
|
11496
|
-
drawLine(point1, point2, debugLineWidth, color, vec2(), 0, false
|
|
11492
|
+
drawLine(point1, point2, debugLineWidth, color, vec2(), 0, false);
|
|
11497
11493
|
};
|
|
11498
11494
|
debugDraw.DrawPolygon = function(vertices, vertexCount, color)
|
|
11499
11495
|
{
|
|
11500
11496
|
color = getDebugColor(color);
|
|
11501
11497
|
const points = getPointsList(vertices, vertexCount);
|
|
11502
|
-
drawPoly(points, CLEAR_WHITE, debugLineWidth, color, vec2(), 0, false
|
|
11498
|
+
drawPoly(points, CLEAR_WHITE, debugLineWidth, color, vec2(), 0, false);
|
|
11503
11499
|
};
|
|
11504
11500
|
debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
|
|
11505
11501
|
{
|
|
11506
11502
|
color = getDebugColor(color);
|
|
11507
11503
|
const points = getPointsList(vertices, vertexCount);
|
|
11508
|
-
drawPoly(points, color, 0, color, vec2(), 0, false
|
|
11504
|
+
drawPoly(points, color, 0, color, vec2(), 0, false);
|
|
11509
11505
|
};
|
|
11510
11506
|
debugDraw.DrawCircle = function(center, radius, color)
|
|
11511
11507
|
{
|
|
11512
11508
|
color = getDebugColor(color);
|
|
11513
11509
|
center = box2d.vec2FromPointer(center);
|
|
11514
|
-
drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false
|
|
11510
|
+
drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false);
|
|
11515
11511
|
};
|
|
11516
11512
|
debugDraw.DrawSolidCircle = function(center, radius, axis, color)
|
|
11517
11513
|
{
|
|
11518
11514
|
color = getDebugColor(color);
|
|
11519
11515
|
center = box2d.vec2FromPointer(center);
|
|
11520
11516
|
axis = box2d.vec2FromPointer(axis).scale(radius);
|
|
11521
|
-
drawCircle(center, radius*2, color, debugLineWidth, color, false
|
|
11522
|
-
drawLine(vec2(), axis, debugLineWidth, color, center, 0, false
|
|
11517
|
+
drawCircle(center, radius*2, color, debugLineWidth, color, false);
|
|
11518
|
+
drawLine(vec2(), axis, debugLineWidth, color, center, 0, false);
|
|
11523
11519
|
};
|
|
11524
11520
|
debugDraw.DrawTransform = function(transform)
|
|
11525
11521
|
{
|
|
@@ -11528,8 +11524,8 @@ async function box2dInit()
|
|
|
11528
11524
|
const angle = -transform.get_q().GetAngle();
|
|
11529
11525
|
const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
|
|
11530
11526
|
const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
|
|
11531
|
-
drawLine(vec2(), p1, debugLineWidth, c1, pos, angle, false
|
|
11532
|
-
drawLine(vec2(), p2, debugLineWidth, c2, pos, angle, false
|
|
11527
|
+
drawLine(vec2(), p1, debugLineWidth, c1, pos, angle, false);
|
|
11528
|
+
drawLine(vec2(), p2, debugLineWidth, c2, pos, angle, false);
|
|
11533
11529
|
}
|
|
11534
11530
|
|
|
11535
11531
|
debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
|
|
@@ -11549,8 +11545,8 @@ async function box2dInit()
|
|
|
11549
11545
|
|
|
11550
11546
|
///////////////////////////////////////////////////////////////////////////////
|
|
11551
11547
|
|
|
11552
|
-
/** Draw a scalable nine-slice UI element to the
|
|
11553
|
-
* This function can not apply color because it draws using the
|
|
11548
|
+
/** Draw a scalable nine-slice UI element to the main canvas in screen space
|
|
11549
|
+
* This function can not apply color because it draws using the 2d context
|
|
11554
11550
|
* @param {Vector2} pos - Screen space position
|
|
11555
11551
|
* @param {Vector2} size - Screen space size
|
|
11556
11552
|
* @param {TileInfo} startTile - Starting tile for the nine-slice pattern
|
|
@@ -11560,7 +11556,7 @@ async function box2dInit()
|
|
|
11560
11556
|
* @memberof DrawUtilities */
|
|
11561
11557
|
function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2, angle=0)
|
|
11562
11558
|
{
|
|
11563
|
-
drawNineSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true
|
|
11559
|
+
drawNineSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true);
|
|
11564
11560
|
}
|
|
11565
11561
|
|
|
11566
11562
|
/** Draw a scalable nine-slice UI element in world space
|
|
@@ -11609,8 +11605,8 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
|
|
|
11609
11605
|
}
|
|
11610
11606
|
}
|
|
11611
11607
|
|
|
11612
|
-
/** Draw a scalable three-slice UI element to the
|
|
11613
|
-
* This function can not apply color because it draws using the
|
|
11608
|
+
/** Draw a scalable three-slice UI element to the main canvas in screen space
|
|
11609
|
+
* This function can not apply color because it draws using the 2d context
|
|
11614
11610
|
* @param {Vector2} pos - Screen space position
|
|
11615
11611
|
* @param {Vector2} size - Screen space size
|
|
11616
11612
|
* @param {TileInfo} startTile - Starting tile for the three-slice pattern
|
|
@@ -11620,7 +11616,7 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
|
|
|
11620
11616
|
* @memberof DrawUtilities */
|
|
11621
11617
|
function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2, angle=0)
|
|
11622
11618
|
{
|
|
11623
|
-
drawThreeSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true
|
|
11619
|
+
drawThreeSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true);
|
|
11624
11620
|
}
|
|
11625
11621
|
|
|
11626
11622
|
/** Draw a scalable three-slice UI element in world space
|