littlejsengine 1.16.2 → 1.17.5
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 +291 -246
- package/dist/littlejs.esm.js +1520 -1271
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1178 -920
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1170 -912
- package/examples/box2d/gameObjects.js +4 -4
- package/examples/breakout/game.js +5 -6
- package/examples/electron/index.html +2 -2
- package/examples/electron/tiles.png +0 -0
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +9 -8
- package/examples/logo.png +0 -0
- package/examples/module/build.bat +7 -0
- package/examples/module/build.js +121 -0
- package/examples/module/tiles.png +0 -0
- package/examples/platformer/game.js +1 -1
- package/examples/platformer/gameEffects.js +25 -29
- package/examples/platformer/gameLevel.js +27 -28
- package/examples/puzzle/game.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/box2dPool.js +2 -2
- package/examples/shorts/box2dTileLayer.js +48 -0
- package/examples/shorts/clock.js +3 -3
- package/examples/shorts/fontImage.js +4 -3
- package/examples/shorts/music.js +2 -2
- package/examples/shorts/musicPlayer.js +2 -2
- package/examples/shorts/parallax.js +1 -1
- package/examples/shorts/sequencer.js +1 -1
- package/examples/shorts/shapes.js +1 -1
- package/examples/shorts/texture.js +10 -6
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +2 -0
- package/examples/starter/index.html +2 -2
- package/examples/starter/tiles.png +0 -0
- package/examples/typescript/tiles.png +0 -0
- package/examples/uiSystem/game.js +1 -1
- package/examples/uiSystem/tiles.png +0 -0
- package/package.json +5 -5
- package/plugins/box2d.js +167 -63
- package/plugins/postProcess.js +47 -28
- package/plugins/uiSystem.js +18 -18
- package/plugins/zzfxm.js +2 -2
- package/reference.md +4 -3
- package/src/engine.js +182 -181
- package/src/engineAudio.js +47 -63
- package/src/engineDebug.js +8 -8
- package/src/engineDraw.js +184 -124
- package/src/engineExport.js +325 -334
- package/src/engineFont.png +0 -0
- package/src/engineInput.js +18 -24
- package/src/engineMath.js +24 -113
- package/src/engineObject.js +27 -26
- package/src/engineParticles.js +2 -2
- package/src/engineTileLayer.js +229 -192
- package/src/engineUtilities.js +100 -4
- package/src/engineWebGL.js +124 -73
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.
|
|
36
|
+
const engineVersion = '1.17.5';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -88,8 +88,9 @@ function getPaused() { return paused; }
|
|
|
88
88
|
* @memberof Engine */
|
|
89
89
|
function setPaused(isPaused=true) { paused = isPaused; }
|
|
90
90
|
|
|
91
|
-
//
|
|
91
|
+
// Engine internal variables
|
|
92
92
|
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
93
|
+
let showEngineVersion = true;
|
|
93
94
|
|
|
94
95
|
///////////////////////////////////////////////////////////////////////////////
|
|
95
96
|
// plugin hooks
|
|
@@ -153,16 +154,17 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
153
154
|
* @example
|
|
154
155
|
* // Basic engine startup
|
|
155
156
|
* engineInit(
|
|
156
|
-
* ()
|
|
157
|
-
* ()
|
|
158
|
-
* ()
|
|
159
|
-
* ()
|
|
160
|
-
* ()
|
|
157
|
+
* ()=> { LOG('Game initialized!'); }, // gameInit
|
|
158
|
+
* ()=> { updateGameLogic(); }, // gameUpdate
|
|
159
|
+
* ()=> { updateUI(); }, // gameUpdatePost
|
|
160
|
+
* ()=> { drawBackground(); }, // gameRender
|
|
161
|
+
* ()=> { drawHUD(); }, // gameRenderPost
|
|
161
162
|
* ['tiles.png', 'tilesLevel.png'] // images to load
|
|
162
163
|
* );
|
|
163
164
|
* @memberof Engine */
|
|
164
165
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
165
166
|
{
|
|
167
|
+
showEngineVersion && console.log(`${engineName} Engine v${engineVersion}`);
|
|
166
168
|
ASSERT(!mainContext, 'engine already initialized');
|
|
167
169
|
ASSERT(isArray(imageSources), 'pass in images as array');
|
|
168
170
|
|
|
@@ -320,7 +322,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
320
322
|
|
|
321
323
|
// responsive aspect ratio with native resolution
|
|
322
324
|
const innerAspect = innerWidth / innerHeight;
|
|
323
|
-
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
325
|
+
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
324
326
|
if (canvasMaxAspect && innerAspect > canvasMaxAspect)
|
|
325
327
|
{
|
|
326
328
|
// full height
|
|
@@ -377,7 +379,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
377
379
|
debugInit();
|
|
378
380
|
|
|
379
381
|
// setup canvases
|
|
380
|
-
// transform way is still more reliable
|
|
382
|
+
// transform way is still more reliable than flexbox or grid
|
|
381
383
|
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
382
384
|
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
383
385
|
mainCanvas.style.cssText = styleCanvas;
|
|
@@ -422,18 +424,20 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
422
424
|
}));
|
|
423
425
|
}
|
|
424
426
|
|
|
427
|
+
// load engine font image
|
|
428
|
+
promises.push(fontImageInit());
|
|
429
|
+
|
|
425
430
|
if (showSplashScreen)
|
|
426
431
|
{
|
|
427
432
|
// draw splash screen
|
|
428
433
|
promises.push(new Promise(resolve =>
|
|
429
434
|
{
|
|
430
435
|
let t = 0;
|
|
431
|
-
console.log(`${engineName} Engine v${engineVersion}`);
|
|
432
436
|
updateSplash();
|
|
433
437
|
function updateSplash()
|
|
434
438
|
{
|
|
435
439
|
inputClear();
|
|
436
|
-
|
|
440
|
+
drawEngineLogo(t+=.01);
|
|
437
441
|
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
438
442
|
}
|
|
439
443
|
}));
|
|
@@ -449,172 +453,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
449
453
|
await gameInit();
|
|
450
454
|
engineUpdate();
|
|
451
455
|
}
|
|
452
|
-
|
|
453
|
-
///////////////////////////////////////////////////////////////////////////
|
|
454
|
-
// LittleJS Splash Screen
|
|
455
|
-
function drawEngineSplashScreen(t)
|
|
456
|
-
{
|
|
457
|
-
const x = mainContext;
|
|
458
|
-
const w = mainCanvas.width = innerWidth;
|
|
459
|
-
const h = mainCanvas.height = innerHeight;
|
|
460
|
-
|
|
461
|
-
{
|
|
462
|
-
// background
|
|
463
|
-
const p3 = percent(t, 1, .8);
|
|
464
|
-
const p4 = percent(t, 0, .5);
|
|
465
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
466
|
-
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
467
|
-
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
468
|
-
x.save();
|
|
469
|
-
x.fillStyle = g;
|
|
470
|
-
x.fillRect(0,0,w,h);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// draw LittleJS logo...
|
|
474
|
-
const rect = (X, Y, W, H, C)=>
|
|
475
|
-
{
|
|
476
|
-
x.beginPath();
|
|
477
|
-
x.rect(X,Y,W,C?H*p:H);
|
|
478
|
-
x.fillStyle = C;
|
|
479
|
-
C ? x.fill() : x.stroke();
|
|
480
|
-
};
|
|
481
|
-
const line = (X, Y, Z, W)=>
|
|
482
|
-
{
|
|
483
|
-
x.beginPath();
|
|
484
|
-
x.lineTo(X,Y);
|
|
485
|
-
x.lineTo(Z,W);
|
|
486
|
-
x.stroke();
|
|
487
|
-
};
|
|
488
|
-
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
489
|
-
{
|
|
490
|
-
const D = (A+B)/2, E = p*(B-A)/2;
|
|
491
|
-
x.beginPath();
|
|
492
|
-
F && x.lineTo(X,Y);
|
|
493
|
-
x.arc(X,Y,R,D-E,D+E);
|
|
494
|
-
x.fillStyle = C;
|
|
495
|
-
C ? x.fill() : x.stroke();
|
|
496
|
-
};
|
|
497
|
-
const color = (c=0, l=0) =>
|
|
498
|
-
hsl([.98,.3,.57,.14][c%4],.8,[0,.3,.5,.8,.9][l]).toString();
|
|
499
|
-
const alpha = wave(1,1,t);
|
|
500
|
-
const p = percent(alpha, .1, .5);
|
|
501
|
-
|
|
502
|
-
// setup
|
|
503
|
-
x.translate(w/2,h/2);
|
|
504
|
-
const size = min(6, min(w,h)/99); // fit to screen
|
|
505
|
-
x.scale(size,size);
|
|
506
|
-
x.translate(-40,-35);
|
|
507
|
-
x.lineJoin = x.lineCap = 'round';
|
|
508
|
-
x.lineWidth = .1 + p*1.9;
|
|
509
|
-
|
|
510
|
-
// drawing effect
|
|
511
|
-
const p2 = percent(alpha,.1,1);
|
|
512
|
-
x.setLineDash([99*p2,99]);
|
|
513
|
-
|
|
514
|
-
// cab top
|
|
515
|
-
rect(7,16,18,-8,color(2,2));
|
|
516
|
-
rect(7,8,18,4,color(2,3));
|
|
517
|
-
rect(25,8,8,8,color(2,1));
|
|
518
|
-
rect(25,8,-18,8);
|
|
519
|
-
rect(25,8,8,8);
|
|
520
|
-
|
|
521
|
-
// cab
|
|
522
|
-
rect(25,16,7,23,color());
|
|
523
|
-
rect(11,39,14,-23,color(1,1));
|
|
524
|
-
rect(11,16,14,18,color(1,2));
|
|
525
|
-
rect(11,16,14,8,color(1,3));
|
|
526
|
-
rect(25,16,-14,24);
|
|
527
|
-
|
|
528
|
-
// cab window
|
|
529
|
-
rect(15,29,6,-9,color(2,2));
|
|
530
|
-
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
531
|
-
rect(21,21,-6,9);
|
|
532
|
-
|
|
533
|
-
// little stack
|
|
534
|
-
rect(37,14,9,6,color(3,2));
|
|
535
|
-
rect(37,14,4.5,6,color(3,3));
|
|
536
|
-
rect(37,14,9,6);
|
|
537
|
-
|
|
538
|
-
// big stack
|
|
539
|
-
rect(50,20,10,-8,color(0,1));
|
|
540
|
-
rect(50,20,6.5,-8,color(0,2));
|
|
541
|
-
rect(50,20,3.5,-8,color(0,3));
|
|
542
|
-
rect(50,20,10,-8);
|
|
543
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
544
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
545
|
-
circle(55,2,11.4,.5,PI-.5);
|
|
546
|
-
rect(45,7,20,-7,color(0,2));
|
|
547
|
-
rect(45,-1,20,4,color(0,3));
|
|
548
|
-
rect(45,-1,20,8);
|
|
549
|
-
|
|
550
|
-
// engine
|
|
551
|
-
for (let i=5; i--;)
|
|
552
|
-
{
|
|
553
|
-
// stagger radius to fix slight seam
|
|
554
|
-
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
555
|
-
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
556
|
-
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// engine outline
|
|
560
|
-
circle(36,30,10,PI/2,PI*3/2);
|
|
561
|
-
circle(48,30,10,PI/2,PI*3/2);
|
|
562
|
-
circle(60,30,10);
|
|
563
|
-
line(36,20,60,20);
|
|
564
|
-
|
|
565
|
-
// engine front light
|
|
566
|
-
circle(60,30,4,PI,3*PI,color(3,2));
|
|
567
|
-
circle(60,30,4,PI,2*PI,color(3,3));
|
|
568
|
-
circle(60,30,4,PI,3*PI);
|
|
569
|
-
|
|
570
|
-
// front brush
|
|
571
|
-
for (let i=6; i--;)
|
|
572
|
-
{
|
|
573
|
-
x.beginPath();
|
|
574
|
-
x.lineTo(53,54);
|
|
575
|
-
x.lineTo(53,40);
|
|
576
|
-
x.lineTo(53+(1+i*2.9)*p,40);
|
|
577
|
-
x.lineTo(53+(4+i*3.5)*p,54);
|
|
578
|
-
x.fillStyle = color(0,i%2+2);
|
|
579
|
-
x.fill();
|
|
580
|
-
i%2 && x.stroke();
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// wheels
|
|
584
|
-
rect(6,40,5,5);
|
|
585
|
-
rect(6,40,5,5,color());
|
|
586
|
-
rect(15,54,38,-14,color());
|
|
587
|
-
for (let i=3; i--;)
|
|
588
|
-
for (let j=2; j--;)
|
|
589
|
-
{
|
|
590
|
-
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
591
|
-
x.stroke();
|
|
592
|
-
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
593
|
-
x.stroke();
|
|
594
|
-
}
|
|
595
|
-
line(6,40,68,40); // center
|
|
596
|
-
line(77,54,4,54); // bottom
|
|
597
|
-
|
|
598
|
-
// draw engine name
|
|
599
|
-
const s = engineName;
|
|
600
|
-
x.font = '900 16px arial';
|
|
601
|
-
x.textAlign = 'center';
|
|
602
|
-
x.textBaseline = 'top';
|
|
603
|
-
x.lineWidth = .1+p*3.9;
|
|
604
|
-
let w2 = 0;
|
|
605
|
-
for (let i=0; i<s.length; ++i)
|
|
606
|
-
w2 += x.measureText(s[i]).width;
|
|
607
|
-
for (let j=2; j--;)
|
|
608
|
-
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
609
|
-
{
|
|
610
|
-
x.fillStyle = color(i,2);
|
|
611
|
-
const w = x.measureText(s[i]).width;
|
|
612
|
-
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
613
|
-
X += w;
|
|
614
|
-
}
|
|
615
|
-
x.restore();
|
|
616
|
-
}
|
|
617
|
-
///////////////////////////////////////////////////////////////////////////
|
|
618
456
|
}
|
|
619
457
|
|
|
620
458
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -628,8 +466,7 @@ function engineObjectsUpdate()
|
|
|
628
466
|
// recursive object update
|
|
629
467
|
function updateObject(o)
|
|
630
468
|
{
|
|
631
|
-
if (o.destroyed)
|
|
632
|
-
return;
|
|
469
|
+
if (o.destroyed) return;
|
|
633
470
|
|
|
634
471
|
o.update();
|
|
635
472
|
for (const child of o.children)
|
|
@@ -637,8 +474,7 @@ function engineObjectsUpdate()
|
|
|
637
474
|
}
|
|
638
475
|
for (const o of engineObjects)
|
|
639
476
|
{
|
|
640
|
-
if (o.parent)
|
|
641
|
-
continue;
|
|
477
|
+
if (o.parent || o.destroyed) continue;
|
|
642
478
|
|
|
643
479
|
// update top level objects
|
|
644
480
|
o.update();
|
|
@@ -727,6 +563,171 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
727
563
|
|
|
728
564
|
debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
|
|
729
565
|
return hitObjects;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
569
|
+
function drawEngineLogo(t)
|
|
570
|
+
{
|
|
571
|
+
// LittleJS Logo and Splash Screen
|
|
572
|
+
const x = mainContext;
|
|
573
|
+
const w = mainCanvas.width = innerWidth;
|
|
574
|
+
const h = mainCanvas.height = innerHeight;
|
|
575
|
+
|
|
576
|
+
{
|
|
577
|
+
// background
|
|
578
|
+
const p3 = percent(t, 1, .8);
|
|
579
|
+
const p4 = percent(t, 0, .5);
|
|
580
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
581
|
+
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
582
|
+
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
583
|
+
x.save();
|
|
584
|
+
x.fillStyle = g;
|
|
585
|
+
x.fillRect(0,0,w,h);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// draw LittleJS logo...
|
|
589
|
+
const rect = (X, Y, W, H, C)=>
|
|
590
|
+
{
|
|
591
|
+
x.beginPath();
|
|
592
|
+
x.rect(X,Y,W,C?H*p:H);
|
|
593
|
+
x.fillStyle = C;
|
|
594
|
+
C ? x.fill() : x.stroke();
|
|
595
|
+
};
|
|
596
|
+
const line = (X, Y, Z, W)=>
|
|
597
|
+
{
|
|
598
|
+
x.beginPath();
|
|
599
|
+
x.lineTo(X,Y);
|
|
600
|
+
x.lineTo(Z,W);
|
|
601
|
+
x.stroke();
|
|
602
|
+
};
|
|
603
|
+
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
604
|
+
{
|
|
605
|
+
const D = (A+B)/2, E = p*(B-A)/2;
|
|
606
|
+
x.beginPath();
|
|
607
|
+
F && x.lineTo(X,Y);
|
|
608
|
+
x.arc(X,Y,R,D-E,D+E);
|
|
609
|
+
x.fillStyle = C;
|
|
610
|
+
C ? x.fill() : x.stroke();
|
|
611
|
+
};
|
|
612
|
+
const color = (c=0, l=0)=>
|
|
613
|
+
hsl([.98,.3,.57,.14][c%4],.9,[0,.3,.5,.8,.9][l]).toString();
|
|
614
|
+
const alpha = wave(1,1,t);
|
|
615
|
+
const p = percent(alpha, .1, .5);
|
|
616
|
+
|
|
617
|
+
// setup
|
|
618
|
+
x.translate(w/2,h/2);
|
|
619
|
+
const size = min(6, min(w,h)/99); // fit to screen
|
|
620
|
+
x.scale(size,size);
|
|
621
|
+
x.translate(-40,-35);
|
|
622
|
+
x.lineJoin = x.lineCap = 'round';
|
|
623
|
+
x.lineWidth = .1 + p*1.9;
|
|
624
|
+
|
|
625
|
+
// drawing effect
|
|
626
|
+
const p2 = percent(alpha,.1,1);
|
|
627
|
+
x.setLineDash([99*p2,99]);
|
|
628
|
+
|
|
629
|
+
// cab top
|
|
630
|
+
rect(7,16,18,-8,color(2,2));
|
|
631
|
+
rect(7,8,18,4,color(2,3));
|
|
632
|
+
rect(25,8,8,8,color(2,1));
|
|
633
|
+
rect(25,8,-18,8);
|
|
634
|
+
rect(25,8,8,8);
|
|
635
|
+
|
|
636
|
+
// cab
|
|
637
|
+
rect(25,16,7,24,color());
|
|
638
|
+
rect(11,39,14,-23,color(1,1));
|
|
639
|
+
rect(11,16,14,18,color(1,2));
|
|
640
|
+
rect(11,16,14,8,color(1,3));
|
|
641
|
+
rect(25,16,-14,24);
|
|
642
|
+
|
|
643
|
+
// cab window
|
|
644
|
+
rect(15,29,6,-9,color(2,2));
|
|
645
|
+
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
646
|
+
rect(21,21,-6,9);
|
|
647
|
+
|
|
648
|
+
// little stack
|
|
649
|
+
rect(37,14,9,6,color(3,2));
|
|
650
|
+
rect(37,14,4.5,6,color(3,3));
|
|
651
|
+
rect(37,14,9,6);
|
|
652
|
+
|
|
653
|
+
// big stack
|
|
654
|
+
rect(50,20,10,-8,color(0,1));
|
|
655
|
+
rect(50,20,6.5,-8,color(0,2));
|
|
656
|
+
rect(50,20,3.5,-8,color(0,3));
|
|
657
|
+
rect(50,20,10,-8);
|
|
658
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
659
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
660
|
+
circle(55,2,11.4,.5,PI-.5);
|
|
661
|
+
rect(45,7,20,-7,color(0,2));
|
|
662
|
+
rect(45,-1,20,4,color(0,3));
|
|
663
|
+
rect(45,-1,20,8);
|
|
664
|
+
|
|
665
|
+
// engine
|
|
666
|
+
for (let i=5; i--;)
|
|
667
|
+
{
|
|
668
|
+
// stagger radius to fix slight seam
|
|
669
|
+
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
670
|
+
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
671
|
+
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// engine outline
|
|
675
|
+
circle(36,30,10,PI/2,PI*3/2);
|
|
676
|
+
circle(48,30,10,PI/2,PI*3/2);
|
|
677
|
+
circle(60,30,10);
|
|
678
|
+
line(36,20,60,20);
|
|
679
|
+
|
|
680
|
+
// engine front light
|
|
681
|
+
circle(60,30,4,PI,3*PI,color(3,2));
|
|
682
|
+
circle(60,30,4,PI,2*PI,color(3,3));
|
|
683
|
+
circle(60,30,4,PI,3*PI);
|
|
684
|
+
|
|
685
|
+
// front brush
|
|
686
|
+
for (let i=6; i--;)
|
|
687
|
+
{
|
|
688
|
+
x.beginPath();
|
|
689
|
+
x.lineTo(53,54);
|
|
690
|
+
x.lineTo(53,40);
|
|
691
|
+
x.lineTo(53+(1+i*2.9)*p,40);
|
|
692
|
+
x.lineTo(53+(4+i*3.5)*p,54);
|
|
693
|
+
x.fillStyle = color(0,i%2+2);
|
|
694
|
+
x.fill();
|
|
695
|
+
i%2 && x.stroke();
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// wheels
|
|
699
|
+
rect(6,40,5,5);
|
|
700
|
+
rect(6,40,5,5,color());
|
|
701
|
+
rect(15,54,38,-14,color());
|
|
702
|
+
for (let i=3; i--;)
|
|
703
|
+
for (let j=2; j--;)
|
|
704
|
+
{
|
|
705
|
+
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
706
|
+
x.stroke();
|
|
707
|
+
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
708
|
+
x.stroke();
|
|
709
|
+
}
|
|
710
|
+
line(6,40,68,40); // center
|
|
711
|
+
line(77,54,4,54); // bottom
|
|
712
|
+
|
|
713
|
+
// draw engine name
|
|
714
|
+
const s = engineName;
|
|
715
|
+
x.font = '900 16px arial';
|
|
716
|
+
x.textAlign = 'center';
|
|
717
|
+
x.textBaseline = 'top';
|
|
718
|
+
x.lineWidth = .1+p*3.9;
|
|
719
|
+
let w2 = 0;
|
|
720
|
+
for (let i=0; i<s.length; ++i)
|
|
721
|
+
w2 += x.measureText(s[i]).width;
|
|
722
|
+
for (let j=2; j--;)
|
|
723
|
+
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
724
|
+
{
|
|
725
|
+
x.fillStyle = color(i,2);
|
|
726
|
+
const w = x.measureText(s[i]).width;
|
|
727
|
+
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
728
|
+
X += w;
|
|
729
|
+
}
|
|
730
|
+
x.restore();
|
|
730
731
|
}
|
|
731
732
|
/**
|
|
732
733
|
* LittleJS Debug System
|
|
@@ -777,7 +778,7 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
|
|
|
777
778
|
/** Asserts if the expression is false, does nothing in release builds
|
|
778
779
|
* Halts execution if the assert fails and throws an error
|
|
779
780
|
* @param {boolean} assert
|
|
780
|
-
* @param {...Object}
|
|
781
|
+
* @param {...Object} output - error message output
|
|
781
782
|
* @memberof Debug */
|
|
782
783
|
function ASSERT(assert, ...output)
|
|
783
784
|
{
|
|
@@ -787,13 +788,13 @@ function ASSERT(assert, ...output)
|
|
|
787
788
|
}
|
|
788
789
|
|
|
789
790
|
/** Log to console if debug is enabled, does nothing in release builds
|
|
790
|
-
* @param {...Object}
|
|
791
|
+
* @param {...Object} output - message output
|
|
791
792
|
* @memberof Debug */
|
|
792
793
|
function LOG(...output) { console.log(...output); }
|
|
793
794
|
|
|
794
795
|
/** Draw a debug rectangle in world space
|
|
795
796
|
* @param {Vector2} pos
|
|
796
|
-
* @param {Vector2} [size=
|
|
797
|
+
* @param {Vector2} [size=vec2(0)]
|
|
797
798
|
* @param {Color|string} [color]
|
|
798
799
|
* @param {number} [time]
|
|
799
800
|
* @param {number} [angle]
|
|
@@ -1117,7 +1118,7 @@ function debugRender()
|
|
|
1117
1118
|
if (tileCollisionTest(mousePos))
|
|
1118
1119
|
{
|
|
1119
1120
|
// show floored tile pick for tile collision
|
|
1120
|
-
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5));
|
|
1121
|
+
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5), 0, false);
|
|
1121
1122
|
}
|
|
1122
1123
|
}
|
|
1123
1124
|
|
|
@@ -1196,8 +1197,8 @@ function debugRender()
|
|
|
1196
1197
|
if (debugObject)
|
|
1197
1198
|
{
|
|
1198
1199
|
const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
|
|
1199
|
-
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
|
|
1200
|
-
drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5));
|
|
1200
|
+
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3), 0, false);
|
|
1201
|
+
drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5), undefined, undefined, false);
|
|
1201
1202
|
|
|
1202
1203
|
let debugText = 'mouse pos = ' + mousePos;
|
|
1203
1204
|
if (tileCollisionLayers.length)
|
|
@@ -1434,8 +1435,8 @@ function debugProtectConstant(obj)
|
|
|
1434
1435
|
props.forEach(prop =>
|
|
1435
1436
|
{
|
|
1436
1437
|
Object.defineProperty(obj, prop, {
|
|
1437
|
-
get: ()
|
|
1438
|
-
set: (value)
|
|
1438
|
+
get: ()=> values[prop],
|
|
1439
|
+
set: (value)=>
|
|
1439
1440
|
{
|
|
1440
1441
|
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
1441
1442
|
},
|
|
@@ -1448,12 +1449,11 @@ function debugProtectConstant(obj)
|
|
|
1448
1449
|
return Object.freeze(obj);
|
|
1449
1450
|
}
|
|
1450
1451
|
/**
|
|
1451
|
-
* LittleJS
|
|
1452
|
+
* LittleJS Math Classes and Functions
|
|
1452
1453
|
* - General purpose math library
|
|
1453
|
-
* - Vector2 - fast, simple, easy 2D vector class
|
|
1454
|
-
* - Color - holds a rgba color with some math functions
|
|
1455
|
-
* - Timer - tracks time automatically
|
|
1456
1454
|
* - RandomGenerator - seeded random number generator
|
|
1455
|
+
* - Vector2 - fast, simple, easy 2D vector class
|
|
1456
|
+
* - Color - holds a rgba color with math functions
|
|
1457
1457
|
* @namespace Math
|
|
1458
1458
|
*/
|
|
1459
1459
|
|
|
@@ -1464,25 +1464,25 @@ function debugProtectConstant(obj)
|
|
|
1464
1464
|
const PI = Math.PI;
|
|
1465
1465
|
|
|
1466
1466
|
/** Returns absolute value of value passed in
|
|
1467
|
-
* @param {number}
|
|
1467
|
+
* @param {number} x
|
|
1468
1468
|
* @return {number}
|
|
1469
1469
|
* @memberof Math */
|
|
1470
1470
|
const abs = Math.abs;
|
|
1471
1471
|
|
|
1472
1472
|
/** Returns floored value of value passed in
|
|
1473
|
-
* @param {number}
|
|
1473
|
+
* @param {number} x
|
|
1474
1474
|
* @return {number}
|
|
1475
1475
|
* @memberof Math */
|
|
1476
1476
|
const floor = Math.floor;
|
|
1477
1477
|
|
|
1478
1478
|
/** Returns ceiled value of value passed in
|
|
1479
|
-
* @param {number}
|
|
1479
|
+
* @param {number} x
|
|
1480
1480
|
* @return {number}
|
|
1481
1481
|
* @memberof Math */
|
|
1482
1482
|
const ceil = Math.ceil;
|
|
1483
1483
|
|
|
1484
1484
|
/** Returns rounded value passed in
|
|
1485
|
-
* @param {number}
|
|
1485
|
+
* @param {number} x
|
|
1486
1486
|
* @return {number}
|
|
1487
1487
|
* @memberof Math */
|
|
1488
1488
|
const round = Math.round;
|
|
@@ -1500,7 +1500,7 @@ const min = Math.min;
|
|
|
1500
1500
|
const max = Math.max;
|
|
1501
1501
|
|
|
1502
1502
|
/** Returns the sign of value passed in
|
|
1503
|
-
* @param {number}
|
|
1503
|
+
* @param {number} x
|
|
1504
1504
|
* @return {number}
|
|
1505
1505
|
* @memberof Math */
|
|
1506
1506
|
const sign = Math.sign;
|
|
@@ -1512,25 +1512,25 @@ const sign = Math.sign;
|
|
|
1512
1512
|
const hypot = Math.hypot;
|
|
1513
1513
|
|
|
1514
1514
|
/** Returns log2 of value passed in
|
|
1515
|
-
* @param {number}
|
|
1515
|
+
* @param {number} x
|
|
1516
1516
|
* @return {number}
|
|
1517
1517
|
* @memberof Math */
|
|
1518
1518
|
const log2 = Math.log2;
|
|
1519
1519
|
|
|
1520
1520
|
/** Returns sin of value passed in
|
|
1521
|
-
* @param {number}
|
|
1521
|
+
* @param {number} x
|
|
1522
1522
|
* @return {number}
|
|
1523
1523
|
* @memberof Math */
|
|
1524
1524
|
const sin = Math.sin;
|
|
1525
1525
|
|
|
1526
1526
|
/** Returns cos of value passed in
|
|
1527
|
-
* @param {number}
|
|
1527
|
+
* @param {number} x
|
|
1528
1528
|
* @return {number}
|
|
1529
1529
|
* @memberof Math */
|
|
1530
1530
|
const cos = Math.cos;
|
|
1531
1531
|
|
|
1532
1532
|
/** Returns tan of value passed in
|
|
1533
|
-
* @param {number}
|
|
1533
|
+
* @param {number} x
|
|
1534
1534
|
* @return {number}
|
|
1535
1535
|
* @memberof Math */
|
|
1536
1536
|
const tan = Math.tan;
|
|
@@ -1641,11 +1641,11 @@ function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
|
1641
1641
|
|
|
1642
1642
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
1643
1643
|
* this can be used for simple collision detection between objects
|
|
1644
|
-
* @param {Vector2} posA
|
|
1645
|
-
* @param {Vector2} sizeA
|
|
1646
|
-
* @param {Vector2} posB
|
|
1647
|
-
* @param {Vector2} [sizeB=(
|
|
1648
|
-
* @return {boolean}
|
|
1644
|
+
* @param {Vector2} posA - Center of box A
|
|
1645
|
+
* @param {Vector2} sizeA - Size of box A
|
|
1646
|
+
* @param {Vector2} posB - Center of box B
|
|
1647
|
+
* @param {Vector2} [sizeB=vec2()] - Size of box B, uses a point if undefined
|
|
1648
|
+
* @return {boolean} - True if overlapping
|
|
1649
1649
|
* @memberof Math */
|
|
1650
1650
|
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
1651
1651
|
{
|
|
@@ -1719,7 +1719,7 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
|
1719
1719
|
* @param {any} s
|
|
1720
1720
|
* @return {boolean}
|
|
1721
1721
|
* @memberof Math */
|
|
1722
|
-
function isString(s) { return s
|
|
1722
|
+
function isString(s) { return s != null && typeof s?.toString() === 'string'; }
|
|
1723
1723
|
|
|
1724
1724
|
/**
|
|
1725
1725
|
* Check if object is an array
|
|
@@ -1863,8 +1863,8 @@ function randInCircle(radius=1, minRadius=0)
|
|
|
1863
1863
|
{ return radius > 0 ? randVec2(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
|
|
1864
1864
|
|
|
1865
1865
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1866
|
-
* @param {Color} [colorA=
|
|
1867
|
-
* @param {Color} [colorB=
|
|
1866
|
+
* @param {Color} [colorA=WHITE]
|
|
1867
|
+
* @param {Color} [colorB=BLACK]
|
|
1868
1868
|
* @param {boolean} [linear]
|
|
1869
1869
|
* @return {Color}
|
|
1870
1870
|
* @memberof Random */
|
|
@@ -1943,8 +1943,8 @@ class RandomGenerator
|
|
|
1943
1943
|
{ return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
|
|
1944
1944
|
|
|
1945
1945
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1946
|
-
* @param {Color} [colorA=
|
|
1947
|
-
* @param {Color} [colorB=
|
|
1946
|
+
* @param {Color} [colorA=WHITE]
|
|
1947
|
+
* @param {Color} [colorB=BLACK]
|
|
1948
1948
|
* @param {boolean} [linear]
|
|
1949
1949
|
* @return {Color} */
|
|
1950
1950
|
randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
@@ -1988,7 +1988,7 @@ class RandomGenerator
|
|
|
1988
1988
|
* a = vec2(5); // set a to (5, 5)
|
|
1989
1989
|
* b = vec2(); // set b to (0, 0)
|
|
1990
1990
|
* @memberof Math */
|
|
1991
|
-
function vec2(x=0, y) { return new Vector2(x, y
|
|
1991
|
+
function vec2(x=0, y) { return new Vector2(x, y ?? x); }
|
|
1992
1992
|
|
|
1993
1993
|
/**
|
|
1994
1994
|
* Check if object is a valid Vector2
|
|
@@ -2196,10 +2196,6 @@ class Vector2
|
|
|
2196
2196
|
* @return {number} */
|
|
2197
2197
|
area() { return abs(this.x * this.y); }
|
|
2198
2198
|
|
|
2199
|
-
/** Returns true if this vector is (0,0)
|
|
2200
|
-
* @return {boolean} */
|
|
2201
|
-
isZero() { return !this.x && !this.y; }
|
|
2202
|
-
|
|
2203
2199
|
/** Returns a new vector that is p percent between this and the vector passed in
|
|
2204
2200
|
* @param {Vector2} v - other vector
|
|
2205
2201
|
* @param {number} percent
|
|
@@ -2561,7 +2557,97 @@ const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
|
2561
2557
|
/** Color - Magenta #ff00ff
|
|
2562
2558
|
* @type {Color}
|
|
2563
2559
|
* @memberof Math */
|
|
2564
|
-
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
2560
|
+
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
2561
|
+
/**
|
|
2562
|
+
* LittleJS Utility Classes and Functions
|
|
2563
|
+
* - General purpose utilities
|
|
2564
|
+
* - Timer - tracks time automatically
|
|
2565
|
+
* @namespace Utilities
|
|
2566
|
+
*/
|
|
2567
|
+
|
|
2568
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
2569
|
+
* @param {number} t - time in seconds
|
|
2570
|
+
* @return {string}
|
|
2571
|
+
* @memberof Utilities */
|
|
2572
|
+
function formatTime(t)
|
|
2573
|
+
{
|
|
2574
|
+
const sign = t < 0 ? '-' : '';
|
|
2575
|
+
t = abs(t)|0;
|
|
2576
|
+
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
2580
|
+
* @param {string} url - URL of JSON file
|
|
2581
|
+
* @return {Promise<object>}
|
|
2582
|
+
* @memberof Utilities */
|
|
2583
|
+
async function fetchJSON(url)
|
|
2584
|
+
{
|
|
2585
|
+
const response = await fetch(url);
|
|
2586
|
+
if (!response.ok)
|
|
2587
|
+
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
2588
|
+
return response.json();
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2592
|
+
|
|
2593
|
+
/** Save a text file to disk
|
|
2594
|
+
* @param {string} text
|
|
2595
|
+
* @param {string} [filename]
|
|
2596
|
+
* @param {string} [type]
|
|
2597
|
+
* @memberof Utilities */
|
|
2598
|
+
function saveText(text, filename='text', type='text/plain')
|
|
2599
|
+
{ saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
2600
|
+
|
|
2601
|
+
/** Save a canvas to disk
|
|
2602
|
+
* @param {HTMLCanvasElement|OffscreenCanvas} canvas
|
|
2603
|
+
* @param {string} [filename]
|
|
2604
|
+
* @param {string} [type]
|
|
2605
|
+
* @memberof Utilities */
|
|
2606
|
+
function saveCanvas(canvas, filename='screenshot', type='image/png')
|
|
2607
|
+
{
|
|
2608
|
+
if (canvas instanceof OffscreenCanvas)
|
|
2609
|
+
{
|
|
2610
|
+
// copy to temporary canvas and save
|
|
2611
|
+
const saveCanvas = document.createElement('canvas');
|
|
2612
|
+
saveCanvas.width = canvas.width;
|
|
2613
|
+
saveCanvas.height = canvas.height;
|
|
2614
|
+
saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
|
|
2615
|
+
saveDataURL(saveCanvas.toDataURL(type), filename);
|
|
2616
|
+
}
|
|
2617
|
+
else
|
|
2618
|
+
saveDataURL(canvas.toDataURL(type), filename);
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
/** Save a data url to disk
|
|
2622
|
+
* @param {string} url
|
|
2623
|
+
* @param {string} [filename]
|
|
2624
|
+
* @param {number} [revokeTime] - how long before revoking the url
|
|
2625
|
+
* @memberof Utilities */
|
|
2626
|
+
function saveDataURL(url, filename='download', revokeTime)
|
|
2627
|
+
{
|
|
2628
|
+
ASSERT(isString(url), 'saveDataURL requires url string');
|
|
2629
|
+
ASSERT(isString(filename), 'saveDataURL requires filename string');
|
|
2630
|
+
|
|
2631
|
+
// create link for saving screenshots
|
|
2632
|
+
const link = document.createElement('a');
|
|
2633
|
+
link.download = filename;
|
|
2634
|
+
link.href = url;
|
|
2635
|
+
link.click();
|
|
2636
|
+
if (revokeTime !== undefined)
|
|
2637
|
+
setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
/** Share content using the native share dialog if available
|
|
2641
|
+
* @param {string} title - title of the share
|
|
2642
|
+
* @param {string} url - url to share
|
|
2643
|
+
* @param {Function} [callback] - Called when share is complete
|
|
2644
|
+
* @memberof Utilities */
|
|
2645
|
+
function shareURL(title, url, callback)
|
|
2646
|
+
{
|
|
2647
|
+
ASSERT(isString(title), 'shareURL requires title string');
|
|
2648
|
+
ASSERT(isString(url), 'shareURL requires url string');
|
|
2649
|
+
navigator.share?.({title, url}).then(()=>callback?.());
|
|
2650
|
+
}
|
|
2565
2651
|
|
|
2566
2652
|
///////////////////////////////////////////////////////////////////////////////
|
|
2567
2653
|
|
|
@@ -2646,84 +2732,6 @@ class Timer
|
|
|
2646
2732
|
* @return {number} */
|
|
2647
2733
|
valueOf() { return this.get(); }
|
|
2648
2734
|
}
|
|
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
|
-
}
|
|
2727
2735
|
/**
|
|
2728
2736
|
* LittleJS Engine Settings
|
|
2729
2737
|
* - All settings for the engine are here
|
|
@@ -3350,10 +3358,10 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
3350
3358
|
class EngineObject
|
|
3351
3359
|
{
|
|
3352
3360
|
/** Create an engine object and adds it to the list of objects
|
|
3353
|
-
* @param {Vector2} [pos=(
|
|
3354
|
-
* @param {Vector2} [size=(1
|
|
3355
|
-
* @param {TileInfo} [tileInfo]
|
|
3356
|
-
* @param {number} [angle]
|
|
3361
|
+
* @param {Vector2} [pos=vec2()] - World space position of the object
|
|
3362
|
+
* @param {Vector2} [size=vec2(1)] - World space size of the object
|
|
3363
|
+
* @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
|
|
3364
|
+
* @param {number} [angle] - Angle the object is rotated by
|
|
3357
3365
|
* @param {Color} [color=WHITE] - Color to apply to tile when rendered
|
|
3358
3366
|
* @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
|
|
3359
3367
|
*/
|
|
@@ -3481,15 +3489,16 @@ class EngineObject
|
|
|
3481
3489
|
// physics sanity checks
|
|
3482
3490
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
3483
3491
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
3484
|
-
|
|
3485
|
-
|
|
3492
|
+
|
|
3493
|
+
// don't do collision for static objects or if solver disabled
|
|
3494
|
+
if (!enablePhysicsSolver || !this.mass) return;
|
|
3486
3495
|
|
|
3487
3496
|
const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
|
|
3488
3497
|
if (this.groundObject)
|
|
3489
3498
|
{
|
|
3490
3499
|
// apply friction in local space of ground object
|
|
3491
3500
|
const friction = max(this.friction, this.groundObject.friction);
|
|
3492
|
-
const groundSpeed = this.groundObject.velocity
|
|
3501
|
+
const groundSpeed = this.groundObject.velocity.x;
|
|
3493
3502
|
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
|
|
3494
3503
|
this.groundObject = undefined;
|
|
3495
3504
|
}
|
|
@@ -3500,19 +3509,19 @@ class EngineObject
|
|
|
3500
3509
|
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
3501
3510
|
for (const o of engineObjectsCollide)
|
|
3502
3511
|
{
|
|
3512
|
+
// skip destroyed, child objects, or self collision
|
|
3513
|
+
if (o.destroyed || o.parent || o === this) continue;
|
|
3514
|
+
|
|
3503
3515
|
// non solid objects don't collide with each other
|
|
3504
|
-
if (
|
|
3505
|
-
continue;
|
|
3516
|
+
if (!this.isSolid && !o.isSolid) continue;
|
|
3506
3517
|
|
|
3507
3518
|
// check collision
|
|
3508
|
-
if (!this.isOverlappingObject(o))
|
|
3509
|
-
continue;
|
|
3519
|
+
if (!this.isOverlappingObject(o)) continue;
|
|
3510
3520
|
|
|
3511
3521
|
// notify objects of collision and check if should be resolved
|
|
3512
3522
|
const collide1 = this.collideWithObject(o);
|
|
3513
3523
|
const collide2 = o.collideWithObject(this);
|
|
3514
|
-
if (!collide1 || !collide2)
|
|
3515
|
-
continue;
|
|
3524
|
+
if (!collide1 || !collide2) continue;
|
|
3516
3525
|
|
|
3517
3526
|
if (isOverlapping(oldPos, this.size, o.pos, o.size))
|
|
3518
3527
|
{
|
|
@@ -3593,7 +3602,7 @@ class EngineObject
|
|
|
3593
3602
|
if (this.collideTiles)
|
|
3594
3603
|
{
|
|
3595
3604
|
// check collision against tiles
|
|
3596
|
-
const hitLayer = tileCollisionTest(this.pos, this.size, this)
|
|
3605
|
+
const hitLayer = tileCollisionTest(this.pos, this.size, this);
|
|
3597
3606
|
if (hitLayer)
|
|
3598
3607
|
{
|
|
3599
3608
|
// if already was stuck in collision, don't do anything
|
|
@@ -3614,7 +3623,7 @@ class EngineObject
|
|
|
3614
3623
|
const delta = y - this.pos.y;
|
|
3615
3624
|
if (delta < maxMoveUp)
|
|
3616
3625
|
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
3617
|
-
{
|
|
3626
|
+
{
|
|
3618
3627
|
this.pos.y = y;
|
|
3619
3628
|
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
3620
3629
|
return;
|
|
@@ -3674,10 +3683,10 @@ class EngineObject
|
|
|
3674
3683
|
|
|
3675
3684
|
// disconnect from parent and destroy children
|
|
3676
3685
|
this.destroyed = 1;
|
|
3677
|
-
this.parent
|
|
3686
|
+
this.parent?.removeChild(this);
|
|
3678
3687
|
for (const child of this.children)
|
|
3679
3688
|
{
|
|
3680
|
-
child.parent =
|
|
3689
|
+
child.parent = undefined;
|
|
3681
3690
|
child.destroy();
|
|
3682
3691
|
}
|
|
3683
3692
|
}
|
|
@@ -3698,15 +3707,15 @@ class EngineObject
|
|
|
3698
3707
|
* @param {Vector2} vec - world space vector */
|
|
3699
3708
|
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
3700
3709
|
|
|
3701
|
-
/** Called to check if a tile collision should be resolved
|
|
3710
|
+
/** Called to check if a tile collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
|
|
3702
3711
|
* @param {number} tileData - the value of the tile at the position
|
|
3703
|
-
* @param {Vector2} pos
|
|
3704
|
-
* @return {boolean}
|
|
3712
|
+
* @param {Vector2} pos - tile where the collision occurred
|
|
3713
|
+
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity */
|
|
3705
3714
|
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
3706
3715
|
|
|
3707
|
-
/** Called to check if
|
|
3716
|
+
/** Called by the engine to check if an object collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
|
|
3708
3717
|
* @param {EngineObject} object - the object to test against
|
|
3709
|
-
* @return {boolean}
|
|
3718
|
+
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity
|
|
3710
3719
|
*/
|
|
3711
3720
|
collideWithObject(object) { return true; }
|
|
3712
3721
|
|
|
@@ -3747,9 +3756,9 @@ class EngineObject
|
|
|
3747
3756
|
* @return {number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
3748
3757
|
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
3749
3758
|
|
|
3750
|
-
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3759
|
+
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3751
3760
|
* @param {EngineObject} child
|
|
3752
|
-
* @param {Vector2} [localPos=(
|
|
3761
|
+
* @param {Vector2} [localPos=vec2()]
|
|
3753
3762
|
* @param {number} [localAngle]
|
|
3754
3763
|
* @return {EngineObject} The child object added */
|
|
3755
3764
|
addChild(child, localPos=vec2(), localAngle=0)
|
|
@@ -3773,7 +3782,7 @@ class EngineObject
|
|
|
3773
3782
|
const index = this.children.indexOf(child);
|
|
3774
3783
|
ASSERT(index >= 0, 'child not found in children array');
|
|
3775
3784
|
index >= 0 && this.children.splice(index, 1);
|
|
3776
|
-
child.parent =
|
|
3785
|
+
child.parent = undefined;
|
|
3777
3786
|
}
|
|
3778
3787
|
|
|
3779
3788
|
/** Check if overlapping another engine object
|
|
@@ -3785,7 +3794,7 @@ class EngineObject
|
|
|
3785
3794
|
|
|
3786
3795
|
/** Check if overlapping a point or aligned bounding box
|
|
3787
3796
|
* @param {Vector2} pos - Center of box
|
|
3788
|
-
* @param {Vector2} [size=(
|
|
3797
|
+
* @param {Vector2} [size=vec2()] - Size of box, uses a point if undefined
|
|
3789
3798
|
* @return {boolean} */
|
|
3790
3799
|
isOverlapping(pos, size=vec2())
|
|
3791
3800
|
{ return isOverlapping(this.pos, this.size, pos, size); }
|
|
@@ -3916,10 +3925,11 @@ let drawCount;
|
|
|
3916
3925
|
* Create a tile info object using a grid based system
|
|
3917
3926
|
* - This can take vecs or floats for easier use and conversion
|
|
3918
3927
|
* - If an index is passed in, the tile size and index will determine the position
|
|
3919
|
-
* @param {Vector2|number} [
|
|
3928
|
+
* @param {Vector2|number} [index=0] - Index of the tile in 1d or 2d form
|
|
3920
3929
|
* @param {Vector2|number} [size] - Size of tile in pixels
|
|
3921
|
-
* @param {number} [
|
|
3930
|
+
* @param {TextureInfo|number} [texture] - Texture index or info to use
|
|
3922
3931
|
* @param {number} [padding] - How many pixels padding around tiles
|
|
3932
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3923
3933
|
* @return {TileInfo}
|
|
3924
3934
|
* @example
|
|
3925
3935
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -3927,36 +3937,43 @@ let drawCount;
|
|
|
3927
3937
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
3928
3938
|
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
3929
3939
|
* @memberof Draw */
|
|
3930
|
-
function tile(
|
|
3940
|
+
function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3931
3941
|
{
|
|
3932
|
-
|
|
3933
|
-
|
|
3942
|
+
ASSERT(isVector2(index) || typeof index === 'number', 'index must be a vec2 or number');
|
|
3943
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
3944
|
+
ASSERT(isNumber(texture) || texture instanceof TextureInfo, 'texture must be a number or TextureInfo');
|
|
3945
|
+
ASSERT(isNumber(padding), 'padding must be a number');
|
|
3946
|
+
|
|
3947
|
+
if (headlessMode) return new TileInfo;
|
|
3934
3948
|
|
|
3935
|
-
// if size is a number, make it a vector
|
|
3936
3949
|
if (typeof size === 'number')
|
|
3937
3950
|
{
|
|
3951
|
+
// if size is a number, make it a vector
|
|
3938
3952
|
ASSERT(size > 0);
|
|
3939
3953
|
size = new Vector2(size, size);
|
|
3940
3954
|
}
|
|
3941
3955
|
|
|
3942
3956
|
// create tile info object
|
|
3943
|
-
const
|
|
3957
|
+
const textureInfo = typeof texture === 'number' ?
|
|
3958
|
+
textureInfos[texture] : texture;
|
|
3944
3959
|
|
|
3945
3960
|
// get the position of the tile
|
|
3946
|
-
const textureInfo = textureInfos[textureIndex];
|
|
3947
|
-
ASSERT(!!textureInfo, 'Texture not loaded');
|
|
3948
3961
|
const sizePaddedX = size.x + padding*2;
|
|
3949
3962
|
const sizePaddedY = size.y + padding*2;
|
|
3950
|
-
|
|
3963
|
+
let x, y;
|
|
3964
|
+
if (typeof index === 'number')
|
|
3951
3965
|
{
|
|
3952
3966
|
const cols = textureInfo.size.x / sizePaddedX |0;
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
|
|
3967
|
+
x = index % cols;
|
|
3968
|
+
y = index / cols |0;
|
|
3956
3969
|
}
|
|
3957
3970
|
else
|
|
3958
|
-
|
|
3959
|
-
|
|
3971
|
+
{
|
|
3972
|
+
x = index.x;
|
|
3973
|
+
y = index.y;
|
|
3974
|
+
}
|
|
3975
|
+
const pos = new Vector2(x*sizePaddedX + padding, y*sizePaddedY + padding);
|
|
3976
|
+
return new TileInfo(pos, size, textureInfo, padding, bleed);
|
|
3960
3977
|
}
|
|
3961
3978
|
|
|
3962
3979
|
/**
|
|
@@ -3966,24 +3983,22 @@ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=til
|
|
|
3966
3983
|
class TileInfo
|
|
3967
3984
|
{
|
|
3968
3985
|
/** Create a tile info object
|
|
3969
|
-
* @param {Vector2} [pos=(
|
|
3986
|
+
* @param {Vector2} [pos=vec2()] - Top left corner of tile in pixels
|
|
3970
3987
|
* @param {Vector2} [size] - Size of tile in pixels
|
|
3971
|
-
* @param {
|
|
3972
|
-
* @param {number}
|
|
3973
|
-
* @param {number}
|
|
3988
|
+
* @param {TextureInfo} [textureInfo] - Texture info to use
|
|
3989
|
+
* @param {number} [padding] - How many pixels padding around tiles
|
|
3990
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3974
3991
|
*/
|
|
3975
|
-
constructor(pos=vec2(), size=tileDefaultSize,
|
|
3992
|
+
constructor(pos=vec2(), size=tileDefaultSize, textureInfo=textureInfos[0], padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3976
3993
|
{
|
|
3977
3994
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
3978
3995
|
this.pos = pos.copy();
|
|
3979
3996
|
/** @property {Vector2} - Size of tile in pixels */
|
|
3980
3997
|
this.size = size.copy();
|
|
3981
|
-
/** @property {number} - Texture index to use */
|
|
3982
|
-
this.textureIndex = textureIndex;
|
|
3983
3998
|
/** @property {number} - How many pixels padding around tiles */
|
|
3984
3999
|
this.padding = padding;
|
|
3985
4000
|
/** @property {TextureInfo} - The texture info for this tile */
|
|
3986
|
-
this.textureInfo =
|
|
4001
|
+
this.textureInfo = textureInfo;
|
|
3987
4002
|
/** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
|
|
3988
4003
|
this.bleed = bleed;
|
|
3989
4004
|
}
|
|
@@ -3993,7 +4008,7 @@ class TileInfo
|
|
|
3993
4008
|
* @return {TileInfo}
|
|
3994
4009
|
*/
|
|
3995
4010
|
offset(offset)
|
|
3996
|
-
{ return new TileInfo(this.pos.add(offset), this.size, this.
|
|
4011
|
+
{ return new TileInfo(this.pos.add(offset), this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3997
4012
|
|
|
3998
4013
|
/** Returns a copy of this tile offset by a number of animation frames
|
|
3999
4014
|
* @param {number} frame - Offset to apply in animation frames
|
|
@@ -4002,23 +4017,33 @@ class TileInfo
|
|
|
4002
4017
|
frame(frame)
|
|
4003
4018
|
{
|
|
4004
4019
|
ASSERT(typeof frame === 'number');
|
|
4005
|
-
|
|
4020
|
+
const w = this.size.x + this.padding*2;
|
|
4021
|
+
const x = frame*w;
|
|
4022
|
+
ASSERT(x < this.textureInfo.size.x, 'frame extends beyond texture width!');
|
|
4023
|
+
return this.offset(new Vector2(x));
|
|
4006
4024
|
}
|
|
4007
4025
|
|
|
4008
4026
|
/**
|
|
4009
4027
|
* Set this tile to use a full image in a texture info
|
|
4010
|
-
* @param {TextureInfo} textureInfo
|
|
4028
|
+
* @param {TextureInfo} [textureInfo]
|
|
4011
4029
|
* @return {TileInfo}
|
|
4012
4030
|
*/
|
|
4013
|
-
setFullImage(textureInfo)
|
|
4031
|
+
setFullImage(textureInfo=this.textureInfo)
|
|
4014
4032
|
{
|
|
4033
|
+
this.textureInfo = textureInfo;
|
|
4015
4034
|
this.pos = new Vector2;
|
|
4016
4035
|
this.size = textureInfo.size.copy();
|
|
4017
|
-
this.textureInfo = textureInfo;
|
|
4018
|
-
// do not use padding or bleed
|
|
4019
4036
|
this.bleed = this.padding = 0;
|
|
4020
4037
|
return this;
|
|
4021
4038
|
}
|
|
4039
|
+
|
|
4040
|
+
/**
|
|
4041
|
+
* Returns a tile info for an index using this tile as refrence
|
|
4042
|
+
* @param {Vector2|number} [index=0]
|
|
4043
|
+
* @return {TileInfo}
|
|
4044
|
+
*/
|
|
4045
|
+
tile(index)
|
|
4046
|
+
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
4022
4047
|
}
|
|
4023
4048
|
|
|
4024
4049
|
/**
|
|
@@ -4059,19 +4084,19 @@ class TextureInfo
|
|
|
4059
4084
|
///////////////////////////////////////////////////////////////////////////////
|
|
4060
4085
|
// Drawing functions
|
|
4061
4086
|
|
|
4062
|
-
/** Draw textured tile centered in world space
|
|
4063
|
-
* @param {Vector2} pos
|
|
4064
|
-
* @param {Vector2} [size=(1
|
|
4065
|
-
* @param {TileInfo} [tileInfo]
|
|
4066
|
-
* @param {Color} [color=
|
|
4067
|
-
* @param {number} [angle]
|
|
4068
|
-
* @param {boolean} [mirror]
|
|
4069
|
-
* @param {Color} [additiveColor]
|
|
4087
|
+
/** Draw textured tile centered in world space
|
|
4088
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
4089
|
+
* @param {Vector2} [size=vec2(1)] - Size of the tile in world space
|
|
4090
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
4091
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
4092
|
+
* @param {number} [angle] - Angle to rotate by
|
|
4093
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
4094
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
4070
4095
|
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
|
|
4071
4096
|
* @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
|
|
4072
4097
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
4073
4098
|
* @memberof Draw */
|
|
4074
|
-
function drawTile(pos, size=
|
|
4099
|
+
function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
4075
4100
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
4076
4101
|
{
|
|
4077
4102
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -4081,7 +4106,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
4081
4106
|
ASSERT(!additiveColor || isColor(additiveColor), 'additiveColor must be a color');
|
|
4082
4107
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
4083
4108
|
|
|
4084
|
-
const textureInfo = tileInfo
|
|
4109
|
+
const textureInfo = tileInfo?.textureInfo;
|
|
4085
4110
|
const bleed = tileInfo?.bleed ?? 0;
|
|
4086
4111
|
if (useWebGL && glEnable)
|
|
4087
4112
|
{
|
|
@@ -4146,8 +4171,8 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
4146
4171
|
|
|
4147
4172
|
/** Draw colored rect centered on pos
|
|
4148
4173
|
* @param {Vector2} pos
|
|
4149
|
-
* @param {Vector2} [size=(1
|
|
4150
|
-
* @param {Color} [color=
|
|
4174
|
+
* @param {Vector2} [size=vec2(1)]
|
|
4175
|
+
* @param {Color} [color=WHITE]
|
|
4151
4176
|
* @param {number} [angle]
|
|
4152
4177
|
* @param {boolean} [useWebGL=glEnable]
|
|
4153
4178
|
* @param {boolean} [screenSpace]
|
|
@@ -4160,9 +4185,9 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
4160
4185
|
|
|
4161
4186
|
/** Draw a rect centered on pos with a gradient from top to bottom
|
|
4162
4187
|
* @param {Vector2} pos
|
|
4163
|
-
* @param {Vector2} [size=(1
|
|
4164
|
-
* @param {Color} [colorTop=
|
|
4165
|
-
* @param {Color} [colorBottom=
|
|
4188
|
+
* @param {Vector2} [size=vec2(1)]
|
|
4189
|
+
* @param {Color} [colorTop=WHITE]
|
|
4190
|
+
* @param {Color} [colorBottom=BLACK]
|
|
4166
4191
|
* @param {number} [angle]
|
|
4167
4192
|
* @param {boolean} [useWebGL=glEnable]
|
|
4168
4193
|
* @param {boolean} [screenSpace]
|
|
@@ -4224,9 +4249,9 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
4224
4249
|
/** Draw connected lines between a series of points
|
|
4225
4250
|
* @param {Array<Vector2>} points
|
|
4226
4251
|
* @param {number} [width]
|
|
4227
|
-
* @param {Color} [color=
|
|
4252
|
+
* @param {Color} [color=WHITE]
|
|
4228
4253
|
* @param {boolean} [wrap] - Should the last point connect to the first?
|
|
4229
|
-
* @param {Vector2} [pos=(
|
|
4254
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
4230
4255
|
* @param {number} [angle] - Angle to rotate by
|
|
4231
4256
|
* @param {boolean} [useWebGL=glEnable]
|
|
4232
4257
|
* @param {boolean} [screenSpace]
|
|
@@ -4261,13 +4286,9 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
4261
4286
|
for (let i=0; i<points.length; ++i)
|
|
4262
4287
|
{
|
|
4263
4288
|
const point = points[i];
|
|
4264
|
-
|
|
4265
|
-
context.lineTo(point.x, point.y);
|
|
4266
|
-
else
|
|
4267
|
-
context.moveTo(point.x, point.y);
|
|
4289
|
+
context.lineTo(point.x, point.y);
|
|
4268
4290
|
}
|
|
4269
|
-
|
|
4270
|
-
context.closePath();
|
|
4291
|
+
wrap && context.closePath();
|
|
4271
4292
|
context.stroke();
|
|
4272
4293
|
}, screenSpace, context);
|
|
4273
4294
|
}
|
|
@@ -4277,8 +4298,8 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
4277
4298
|
* @param {Vector2} posA
|
|
4278
4299
|
* @param {Vector2} posB
|
|
4279
4300
|
* @param {number} [width]
|
|
4280
|
-
* @param {Color} [color=
|
|
4281
|
-
* @param {Vector2} [pos=(
|
|
4301
|
+
* @param {Color} [color=WHITE]
|
|
4302
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
4282
4303
|
* @param {number} [angle] - Angle to rotate by
|
|
4283
4304
|
* @param {boolean} [useWebGL=glEnable]
|
|
4284
4305
|
* @param {boolean} [screenSpace]
|
|
@@ -4297,12 +4318,12 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
4297
4318
|
|
|
4298
4319
|
/** Draw colored regular polygon using passed in number of sides
|
|
4299
4320
|
* @param {Vector2} pos
|
|
4300
|
-
* @param {Vector2} [size=(1
|
|
4321
|
+
* @param {Vector2} [size=vec2(1)]
|
|
4301
4322
|
* @param {number} [sides]
|
|
4302
|
-
* @param {Color} [color=
|
|
4323
|
+
* @param {Color} [color=WHITE]
|
|
4303
4324
|
* @param {number} [angle]
|
|
4304
4325
|
* @param {number} [lineWidth]
|
|
4305
|
-
* @param {Color} [lineColor=
|
|
4326
|
+
* @param {Color} [lineColor=BLACK]
|
|
4306
4327
|
* @param {boolean} [useWebGL=glEnable]
|
|
4307
4328
|
* @param {boolean} [screenSpace]
|
|
4308
4329
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -4325,10 +4346,10 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
4325
4346
|
|
|
4326
4347
|
/** Draw colored polygon using passed in points
|
|
4327
4348
|
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
4328
|
-
* @param {Color} [color=
|
|
4349
|
+
* @param {Color} [color=WHITE]
|
|
4329
4350
|
* @param {number} [lineWidth]
|
|
4330
|
-
* @param {Color} [lineColor=
|
|
4331
|
-
* @param {Vector2} [pos=(
|
|
4351
|
+
* @param {Color} [lineColor=BLACK]
|
|
4352
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
4332
4353
|
* @param {number} [angle] - Angle to rotate by
|
|
4333
4354
|
* @param {boolean} [useWebGL=glEnable]
|
|
4334
4355
|
* @param {boolean} [screenSpace]
|
|
@@ -4375,11 +4396,11 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
4375
4396
|
|
|
4376
4397
|
/** Draw colored ellipse using passed in point
|
|
4377
4398
|
* @param {Vector2} pos
|
|
4378
|
-
* @param {Vector2} [size=(1
|
|
4379
|
-
* @param {Color} [color=
|
|
4399
|
+
* @param {Vector2} [size=vec2(1)] - Width and height diameter
|
|
4400
|
+
* @param {Color} [color=WHITE]
|
|
4380
4401
|
* @param {number} [angle]
|
|
4381
4402
|
* @param {number} [lineWidth]
|
|
4382
|
-
* @param {Color} [lineColor=
|
|
4403
|
+
* @param {Color} [lineColor=BLACK]
|
|
4383
4404
|
* @param {boolean} [useWebGL=glEnable]
|
|
4384
4405
|
* @param {boolean} [screenSpace]
|
|
4385
4406
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -4391,9 +4412,12 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
4391
4412
|
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
4392
4413
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
4393
4414
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
4394
|
-
ASSERT(lineWidth >= 0
|
|
4415
|
+
ASSERT(lineWidth >= 0, 'lineWidth must be a positive value or 0');
|
|
4395
4416
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
4396
4417
|
|
|
4418
|
+
// clamp line width to prevent artifacts
|
|
4419
|
+
lineWidth = clamp(lineWidth, 0, Math.min(size.x, size.y));
|
|
4420
|
+
|
|
4397
4421
|
if (useWebGL && glEnable)
|
|
4398
4422
|
{
|
|
4399
4423
|
// draw as a regular polygon
|
|
@@ -4421,9 +4445,9 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
4421
4445
|
/** Draw colored circle using passed in point
|
|
4422
4446
|
* @param {Vector2} pos
|
|
4423
4447
|
* @param {number} [size=1] - Diameter
|
|
4424
|
-
* @param {Color} [color=
|
|
4448
|
+
* @param {Color} [color=WHITE]
|
|
4425
4449
|
* @param {number} [lineWidth=0]
|
|
4426
|
-
* @param {Color} [lineColor=
|
|
4450
|
+
* @param {Color} [lineColor=BLACK]
|
|
4427
4451
|
* @param {boolean} [useWebGL=glEnable]
|
|
4428
4452
|
* @param {boolean} [screenSpace]
|
|
4429
4453
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -4478,9 +4502,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
4478
4502
|
* @param {string|number} text
|
|
4479
4503
|
* @param {Vector2} pos
|
|
4480
4504
|
* @param {number} [size]
|
|
4481
|
-
* @param {Color} [color=
|
|
4505
|
+
* @param {Color} [color=WHITE]
|
|
4482
4506
|
* @param {number} [lineWidth]
|
|
4483
|
-
* @param {Color} [lineColor=
|
|
4507
|
+
* @param {Color} [lineColor=BLACK]
|
|
4484
4508
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
4485
4509
|
* @param {string} [font=fontDefault]
|
|
4486
4510
|
* @param {string} [fontStyle]
|
|
@@ -4504,10 +4528,10 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
4504
4528
|
* Automatically splits new lines into rows
|
|
4505
4529
|
* @param {string|number} text
|
|
4506
4530
|
* @param {Vector2} pos
|
|
4507
|
-
* @param {number}
|
|
4508
|
-
* @param {Color} [color=
|
|
4531
|
+
* @param {number} size
|
|
4532
|
+
* @param {Color} [color=WHITE]
|
|
4509
4533
|
* @param {number} [lineWidth]
|
|
4510
|
-
* @param {Color} [lineColor=
|
|
4534
|
+
* @param {Color} [lineColor=BLACK]
|
|
4511
4535
|
* @param {CanvasTextAlign} [textAlign]
|
|
4512
4536
|
* @param {string} [font=fontDefault]
|
|
4513
4537
|
* @param {string} [fontStyle]
|
|
@@ -4515,7 +4539,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
4515
4539
|
* @param {number} [angle]
|
|
4516
4540
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
4517
4541
|
* @memberof Draw */
|
|
4518
|
-
function drawTextScreen(text, pos, size
|
|
4542
|
+
function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
|
|
4519
4543
|
{
|
|
4520
4544
|
ASSERT(isString(text), 'text must be a string');
|
|
4521
4545
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -4639,7 +4663,7 @@ function worldToScreenDelta(worldDelta)
|
|
|
4639
4663
|
|
|
4640
4664
|
/** Convert screen space transform to world space
|
|
4641
4665
|
* @param {Vector2} screenPos
|
|
4642
|
-
* @param {Vector2} screenSize
|
|
4666
|
+
* @param {Vector2} screenSize
|
|
4643
4667
|
* @param {number} [screenAngle]
|
|
4644
4668
|
* @return {[Vector2, Vector2, number]} - [pos, size, angle]
|
|
4645
4669
|
* @memberof Draw */
|
|
@@ -4700,10 +4724,9 @@ function isOnScreen(pos, size=0)
|
|
|
4700
4724
|
* @param {boolean} [additive]
|
|
4701
4725
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4702
4726
|
* @memberof Draw */
|
|
4703
|
-
function setBlendMode(additive=false, context)
|
|
4727
|
+
function setBlendMode(additive=false, context=drawContext)
|
|
4704
4728
|
{
|
|
4705
4729
|
glAdditive = additive;
|
|
4706
|
-
context ||= drawContext;
|
|
4707
4730
|
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
4708
4731
|
}
|
|
4709
4732
|
|
|
@@ -4718,7 +4741,6 @@ function combineCanvases()
|
|
|
4718
4741
|
workContext.fillRect(0,0,w,h); // remove background alpha
|
|
4719
4742
|
glCopyToContext(workContext);
|
|
4720
4743
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
4721
|
-
mainCanvas.width |= 0;
|
|
4722
4744
|
mainContext.drawImage(workCanvas, 0, 0);
|
|
4723
4745
|
}
|
|
4724
4746
|
|
|
@@ -4823,17 +4845,22 @@ function setCursor(cursorStyle = 'auto')
|
|
|
4823
4845
|
|
|
4824
4846
|
///////////////////////////////////////////////////////////////////////////////
|
|
4825
4847
|
|
|
4848
|
+
/** Engine font image, 8x8 font provided by the engine
|
|
4849
|
+
* @type {FontImage}
|
|
4850
|
+
* @memberof Draw */
|
|
4826
4851
|
let engineFontImage;
|
|
4827
4852
|
|
|
4828
4853
|
/**
|
|
4829
|
-
* Font Image Object - Draw text
|
|
4854
|
+
* Font Image Object - Draw text by using tiles in an image
|
|
4830
4855
|
* - 96 characters (from space to tilde) are stored in an image
|
|
4831
|
-
* -
|
|
4832
|
-
* -
|
|
4856
|
+
* - A 8x8 default engine font is supplied for general use
|
|
4857
|
+
* - This system is WebGL enabled for fast text rendering
|
|
4858
|
+
* - Fonts can also be colored and scaled along each axis
|
|
4859
|
+
*
|
|
4833
4860
|
* @memberof Draw
|
|
4834
4861
|
* @example
|
|
4835
4862
|
* // use built in font
|
|
4836
|
-
* const font =
|
|
4863
|
+
* const font = engineFontImage;
|
|
4837
4864
|
*
|
|
4838
4865
|
* // draw text
|
|
4839
4866
|
* font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
|
|
@@ -4841,70 +4868,112 @@ let engineFontImage;
|
|
|
4841
4868
|
class FontImage
|
|
4842
4869
|
{
|
|
4843
4870
|
/** Create an image font
|
|
4844
|
-
* @param {
|
|
4845
|
-
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
4846
|
-
* @param {Vector2} [paddingSize=(0,1)] - How much space between characters
|
|
4871
|
+
* @param {TileInfo} tileInfo - Tile info of first characeter in font
|
|
4847
4872
|
*/
|
|
4848
|
-
constructor(
|
|
4873
|
+
constructor(tileInfo)
|
|
4849
4874
|
{
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
{
|
|
4853
|
-
|
|
4854
|
-
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
4855
|
-
}
|
|
4856
|
-
|
|
4857
|
-
this.image = image || engineFontImage;
|
|
4858
|
-
this.tileSize = tileSize;
|
|
4859
|
-
this.paddingSize = paddingSize;
|
|
4875
|
+
ASSERT(!!tileInfo, 'tileInfo is required for FontImage');
|
|
4876
|
+
|
|
4877
|
+
/** @property {TileInfo} - Tile info for the font */
|
|
4878
|
+
this.tileInfo = tileInfo.frame(0);
|
|
4860
4879
|
}
|
|
4861
4880
|
|
|
4862
4881
|
/** Draw text in world space using the image font
|
|
4863
|
-
* @param {string|number}
|
|
4882
|
+
* @param {string|number} text
|
|
4864
4883
|
* @param {Vector2} pos
|
|
4865
|
-
* @param {number}
|
|
4866
|
-
* @param {boolean} [center]
|
|
4867
|
-
* @param {
|
|
4884
|
+
* @param {Vector2|number} [size]
|
|
4885
|
+
* @param {boolean} [center=true]
|
|
4886
|
+
* @param {Color} [color=WHITE]
|
|
4887
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
4888
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4868
4889
|
*/
|
|
4869
|
-
drawText(text, pos,
|
|
4890
|
+
drawText(text, pos, size=1, center, color, useWebGL, context)
|
|
4870
4891
|
{
|
|
4871
|
-
|
|
4892
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4893
|
+
|
|
4894
|
+
if (typeof size === 'number')
|
|
4895
|
+
{
|
|
4896
|
+
// if size is a number, make it a vector
|
|
4897
|
+
ASSERT(size > 0);
|
|
4898
|
+
size *= cameraScale;
|
|
4899
|
+
size = new Vector2(size, size);
|
|
4900
|
+
}
|
|
4901
|
+
else
|
|
4902
|
+
size = size.scale(cameraScale);
|
|
4903
|
+
this.drawTextScreen(text, worldToScreen(pos), size, center, color, useWebGL, context);
|
|
4872
4904
|
}
|
|
4873
4905
|
|
|
4874
4906
|
/** Draw text in screen space using the image font
|
|
4875
|
-
* @param {string|number}
|
|
4907
|
+
* @param {string|number} text
|
|
4876
4908
|
* @param {Vector2} pos
|
|
4877
|
-
* @param {number}
|
|
4909
|
+
* @param {Vector2|number} size
|
|
4878
4910
|
* @param {boolean} [center]
|
|
4879
|
-
* @param {
|
|
4911
|
+
* @param {Color} [color=WHITE]
|
|
4912
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
4913
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4880
4914
|
*/
|
|
4881
|
-
drawTextScreen(text, pos,
|
|
4915
|
+
drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
|
|
4882
4916
|
{
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4917
|
+
ASSERT(isString(text), 'text must be a string');
|
|
4918
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
4919
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4920
|
+
ASSERT(isColor(color), 'color must be a color');
|
|
4921
|
+
|
|
4922
|
+
// if size is a number, make it a vector
|
|
4923
|
+
size = typeof size === 'number' ? new Vector2(size, size) : size;
|
|
4924
|
+
|
|
4925
|
+
// precache objects for drawing
|
|
4926
|
+
const drawPos = new Vector2;
|
|
4927
|
+
const tileInfo = this.tileInfo;
|
|
4928
|
+
const padding = tileInfo.padding;
|
|
4929
|
+
const sizePaddedX = tileInfo.size.x + padding*2;
|
|
4930
|
+
const sizePaddedY = tileInfo.size.y + padding*2;
|
|
4931
|
+
const cols = tileInfo.textureInfo.size.x / sizePaddedX |0;
|
|
4932
|
+
|
|
4933
|
+
// draw each line of text
|
|
4934
|
+
(text+'').split('\n').forEach((line, j)=>
|
|
4888
4935
|
{
|
|
4889
|
-
const centerOffset = center ? line.length * size.x
|
|
4890
|
-
for (let
|
|
4936
|
+
const centerOffset = center ? (line.length-1) * size.x / 2 : 0;
|
|
4937
|
+
for (let i=line.length; i--;)
|
|
4891
4938
|
{
|
|
4892
|
-
//
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
charCode
|
|
4896
|
-
|
|
4897
|
-
// get the
|
|
4898
|
-
const
|
|
4899
|
-
const
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4939
|
+
// get the character index
|
|
4940
|
+
const charCode = line.charCodeAt(i);
|
|
4941
|
+
const index = charCode < 32 || charCode > 127 ?
|
|
4942
|
+
95 : charCode - 32; // handle out of range characters
|
|
4943
|
+
|
|
4944
|
+
// get the position of the tile
|
|
4945
|
+
const x = index % cols;
|
|
4946
|
+
const y = index / cols |0;
|
|
4947
|
+
tileInfo.pos.x = x*sizePaddedX + padding;
|
|
4948
|
+
tileInfo.pos.y = y*sizePaddedY + padding;
|
|
4949
|
+
|
|
4950
|
+
// draw the tile
|
|
4951
|
+
drawPos.x = pos.x + i * size.x - centerOffset |0;
|
|
4952
|
+
drawPos.y = pos.y + j * size.y |0;
|
|
4953
|
+
drawTile(drawPos, size, tileInfo, color, 0, false, undefined, useWebGL, true, context);
|
|
4904
4954
|
}
|
|
4905
4955
|
});
|
|
4906
|
-
context.restore();
|
|
4907
4956
|
}
|
|
4957
|
+
}
|
|
4958
|
+
|
|
4959
|
+
// load engine font, called automatically on startup
|
|
4960
|
+
function fontImageInit()
|
|
4961
|
+
{
|
|
4962
|
+
return new Promise(resolve =>
|
|
4963
|
+
{
|
|
4964
|
+
// create the engine font
|
|
4965
|
+
const image = new Image;
|
|
4966
|
+
image.onerror = image.onload = ()=>
|
|
4967
|
+
{
|
|
4968
|
+
const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
|
|
4969
|
+
const textureInfo = new TextureInfo(image);
|
|
4970
|
+
const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
|
|
4971
|
+
engineFontImage = new FontImage(tileInfo);
|
|
4972
|
+
resolve();
|
|
4973
|
+
}
|
|
4974
|
+
image.crossOrigin = 'anonymous';
|
|
4975
|
+
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAeAQMAAABnrVXaAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAjpJREFUOMu9kzFu2zAUhn+CAROgqrk+B2l0BWYxMjlXeYaAtFtbdA1sGgHqRQfI0CNkSG5AwYB0BQ8d5Bsomwah6CPVeGg6tEPzAxLwyI+P78cP4u9lNO9OoMKnLMOobG5020/yaj/MrRcCGh1gBbyiLTPJEYaIiom5KM9Jq7KgynMGtb6L4GL4MF2H4LQKCXTvDVw2I4MsgZT7QLExdiutH+D08VOP3INXRrWX1/mmpbkNgAPYRVANb4xpcegYvhiNbIXauQICEjBuYLfMakaakWQeXxiZ0VDtuJCKs3ztMV59QtsHJNcRxDzfdL21ty3PrfIcXTN+E+GFAv6T5nbT9jd50/WFxb5ksdAv49qS6ouymG66ji08UMT6moykYLAo+V0j23GN4m829ZySAD5K7QsBfQTvOG8eE+gTeGYRAmnNAubN3hf5Zv9tJWDHp/VTuaSm7SN4fyINQqaNO3RMVxvpSPXnOChnRNvFcGY0gnwiPswYwTKVPE0zVtX3mTEIOoFzaqLrGuJaV+Uqumb71fVk/VoOH3cdLNQP/FHi8hV0CQNoqBZsUPlLPMsdCJro9QAaQQ0woDy9BJm0eTxCFnO9srcYlhNVlfR2EyTrph1uUtbUtAJifwRgrKuYdXVHeb0YI3QpawohQHkloI3J5FuVwI5ORxC9k2Tuz9Ir1IjgeIPGMHYkAZe2RuYkmWFmt3gGbTPOmBUWVTmRmHtGrfpzG/yuQNOKa6gBB/WA9khitPgl6/GP+gl2Af6tCbvaygAAAABJRU5ErkJggg==';
|
|
4976
|
+
});
|
|
4908
4977
|
}
|
|
4909
4978
|
/**
|
|
4910
4979
|
* LittleJS Input System
|
|
@@ -4961,6 +5030,10 @@ let inputPreventDefault = true;
|
|
|
4961
5030
|
* @memberof Input */
|
|
4962
5031
|
let gamepadPrimary = 0;
|
|
4963
5032
|
|
|
5033
|
+
/** True if a touch device has been detected
|
|
5034
|
+
* @memberof Input */
|
|
5035
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
5036
|
+
|
|
4964
5037
|
/** Prevents input continuing to the default browser handling
|
|
4965
5038
|
* This is useful to disable for html menus so the browser can handle input normally
|
|
4966
5039
|
* @param {boolean} preventDefault
|
|
@@ -5159,10 +5232,6 @@ function gamepadStickCount(gamepad=gamepadPrimary)
|
|
|
5159
5232
|
return gamepadStickData[gamepad]?.length ?? 0;
|
|
5160
5233
|
}
|
|
5161
5234
|
|
|
5162
|
-
/** True if a touch device has been detected
|
|
5163
|
-
* @memberof Input */
|
|
5164
|
-
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
5165
|
-
|
|
5166
5235
|
///////////////////////////////////////////////////////////////////////////////
|
|
5167
5236
|
|
|
5168
5237
|
/** Pulse the vibration hardware if it exists
|
|
@@ -5171,7 +5240,7 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
|
5171
5240
|
function vibrate(pattern=100)
|
|
5172
5241
|
{
|
|
5173
5242
|
ASSERT(isNumber(pattern) || isArray(pattern), 'pattern must be a number or array');
|
|
5174
|
-
vibrateEnable && !headlessMode && navigator
|
|
5243
|
+
vibrateEnable && !headlessMode && navigator?.vibrate?.(pattern);
|
|
5175
5244
|
}
|
|
5176
5245
|
|
|
5177
5246
|
/** Cancel any ongoing vibration
|
|
@@ -5289,8 +5358,8 @@ function inputInit()
|
|
|
5289
5358
|
}
|
|
5290
5359
|
function onMouseUp(e)
|
|
5291
5360
|
{
|
|
5292
|
-
if (isTouchDevice && touchInputEnable)
|
|
5293
|
-
|
|
5361
|
+
if (isTouchDevice && touchInputEnable) return;
|
|
5362
|
+
|
|
5294
5363
|
inputData[0][e.button] = (inputData[0][e.button]&2) | 4;
|
|
5295
5364
|
}
|
|
5296
5365
|
function onMouseMove(e)
|
|
@@ -5314,9 +5383,9 @@ function inputInit()
|
|
|
5314
5383
|
function touchInputInit()
|
|
5315
5384
|
{
|
|
5316
5385
|
// add non passive touch event listeners
|
|
5317
|
-
document.addEventListener('touchstart', (e)
|
|
5318
|
-
document.addEventListener('touchmove', (e)
|
|
5319
|
-
document.addEventListener('touchend', (e)
|
|
5386
|
+
document.addEventListener('touchstart', (e)=> handleTouch(e), { passive: false });
|
|
5387
|
+
document.addEventListener('touchmove', (e)=> handleTouch(e), { passive: false });
|
|
5388
|
+
document.addEventListener('touchend', (e)=> handleTouch(e), { passive: false });
|
|
5320
5389
|
|
|
5321
5390
|
// handle all touch events the same way
|
|
5322
5391
|
let wasTouching;
|
|
@@ -5476,8 +5545,7 @@ function inputUpdate()
|
|
|
5476
5545
|
// update touch gamepad if enabled
|
|
5477
5546
|
if (touchGamepadEnable && isTouchDevice)
|
|
5478
5547
|
{
|
|
5479
|
-
if (!touchGamepadTimer.isSet())
|
|
5480
|
-
return;
|
|
5548
|
+
if (!touchGamepadTimer.isSet()) return;
|
|
5481
5549
|
|
|
5482
5550
|
// read virtual analog stick
|
|
5483
5551
|
gamepadPrimary = 0; // touch gamepad uses index 0
|
|
@@ -5515,17 +5583,15 @@ function inputUpdate()
|
|
|
5515
5583
|
}
|
|
5516
5584
|
|
|
5517
5585
|
// return if gamepads are disabled or not supported
|
|
5518
|
-
if (!gamepadsEnable || !navigator || !navigator.getGamepads)
|
|
5519
|
-
return;
|
|
5586
|
+
if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
|
|
5520
5587
|
|
|
5521
5588
|
// only poll gamepads when focused or in debug mode
|
|
5522
|
-
if (!debug && !document.hasFocus())
|
|
5523
|
-
return;
|
|
5589
|
+
if (!debug && !document.hasFocus()) return;
|
|
5524
5590
|
|
|
5525
5591
|
// poll gamepads
|
|
5526
5592
|
const maxGamepads = 8;
|
|
5527
5593
|
const gamepads = navigator.getGamepads();
|
|
5528
|
-
const gamepadCount = min(maxGamepads, gamepads.length)
|
|
5594
|
+
const gamepadCount = min(maxGamepads, gamepads.length);
|
|
5529
5595
|
for (let i=0; i<gamepadCount; ++i)
|
|
5530
5596
|
{
|
|
5531
5597
|
// get or create gamepad data
|
|
@@ -5557,8 +5623,7 @@ function inputUpdate()
|
|
|
5557
5623
|
data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
5558
5624
|
|
|
5559
5625
|
// check for any input on this gamepad, analog must be full press
|
|
5560
|
-
if (button.pressed)
|
|
5561
|
-
if (!button.value || button.value > .9)
|
|
5626
|
+
if (button.pressed && (!button.value || button.value > .9))
|
|
5562
5627
|
hadInput = true;
|
|
5563
5628
|
}
|
|
5564
5629
|
|
|
@@ -5587,7 +5652,7 @@ function inputUpdate()
|
|
|
5587
5652
|
}
|
|
5588
5653
|
|
|
5589
5654
|
// copy dpad to left analog stick when pressed
|
|
5590
|
-
if (gamepadDirectionEmulateStick &&
|
|
5655
|
+
if (gamepadDirectionEmulateStick && (dpad.x || dpad.y))
|
|
5591
5656
|
sticks[0] = dpad.clampLength();
|
|
5592
5657
|
}
|
|
5593
5658
|
|
|
@@ -5616,13 +5681,11 @@ function inputRender()
|
|
|
5616
5681
|
function touchGamepadRender()
|
|
5617
5682
|
{
|
|
5618
5683
|
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
5619
|
-
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
5620
|
-
return;
|
|
5684
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet()) return;
|
|
5621
5685
|
|
|
5622
5686
|
// fade off when not touching or paused
|
|
5623
5687
|
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
5624
|
-
if (!alpha || paused)
|
|
5625
|
-
return;
|
|
5688
|
+
if (!alpha || paused) return;
|
|
5626
5689
|
|
|
5627
5690
|
// setup the canvas
|
|
5628
5691
|
const context = mainContext;
|
|
@@ -5733,29 +5796,46 @@ function audioInit()
|
|
|
5733
5796
|
///////////////////////////////////////////////////////////////////////////////
|
|
5734
5797
|
|
|
5735
5798
|
/**
|
|
5736
|
-
* Sound Object - Stores a sound for later
|
|
5799
|
+
* Sound Object - Stores a sound for later
|
|
5800
|
+
* - this can be used to load and play wave, mp3, and ogg files
|
|
5801
|
+
* - it can also create sounds using the ZzFX sound generator
|
|
5802
|
+
* - can attenuate and apply stereo panning to sounds
|
|
5803
|
+
* - sound instance control with pause/resume capability
|
|
5737
5804
|
*
|
|
5738
5805
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
5739
5806
|
* @memberof Audio
|
|
5740
5807
|
* @example
|
|
5741
|
-
* //
|
|
5808
|
+
* // load an audio asset file
|
|
5809
|
+
* const sound_example = new Sound('sound.mp3');
|
|
5810
|
+
*
|
|
5811
|
+
* // create a zzfx sound
|
|
5742
5812
|
* const sound_example = new Sound([.5,.5]);
|
|
5743
5813
|
*
|
|
5744
|
-
* // play
|
|
5814
|
+
* // play a sound
|
|
5745
5815
|
* sound_example.play();
|
|
5746
5816
|
*/
|
|
5747
5817
|
class Sound
|
|
5748
5818
|
{
|
|
5749
|
-
/**
|
|
5750
|
-
*
|
|
5819
|
+
/**
|
|
5820
|
+
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
5821
|
+
* @param {Sound} sound
|
|
5822
|
+
* @memberof Audio
|
|
5823
|
+
*/
|
|
5824
|
+
|
|
5825
|
+
/** Create a sound object and cache the audio for later use
|
|
5826
|
+
* @param {string|Array} [asset] - Filename of audio file or zzfx array
|
|
5827
|
+
* @param {number} [randomness] - How much to randomize frequency each time sound plays, for zzfx sounds the zzfx default is used if undefined
|
|
5751
5828
|
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
5752
5829
|
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
5830
|
+
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
5753
5831
|
*/
|
|
5754
|
-
constructor(
|
|
5832
|
+
constructor(asset, randomness, range=soundDefaultRange, taper=soundDefaultTaper, onloadCallback)
|
|
5755
5833
|
{
|
|
5756
5834
|
if (!soundEnable || headlessMode) return;
|
|
5757
5835
|
|
|
5758
|
-
ASSERT(!
|
|
5836
|
+
ASSERT(!asset || isArray(asset) || isString(asset), 'asset must be a file name or zzfx array');
|
|
5837
|
+
ASSERT(randomness === undefined || isNumber(randomness), 'randomness must be a number');
|
|
5838
|
+
ASSERT(randomness === undefined || randomness >= 0 && randomness <=1, 'randomness must be between 0 and 1');
|
|
5759
5839
|
ASSERT(isNumber(range), 'range must be a number');
|
|
5760
5840
|
ASSERT(isNumber(taper), 'taper must be a number');
|
|
5761
5841
|
|
|
@@ -5764,24 +5844,35 @@ class Sound
|
|
|
5764
5844
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
5765
5845
|
this.taper = taper;
|
|
5766
5846
|
/** @property {number} - How much to randomize frequency each time sound plays */
|
|
5767
|
-
this.randomness = 0;
|
|
5847
|
+
this.randomness = randomness ?? 0;
|
|
5768
5848
|
/** @property {number} - Sample rate for this sound */
|
|
5769
5849
|
this.sampleRate = audioDefaultSampleRate;
|
|
5770
5850
|
/** @property {number} - Percentage of this sound currently loaded */
|
|
5771
5851
|
this.loadedPercent = 0;
|
|
5852
|
+
/** @property {SoundLoadCallback} - function to call when sound is loaded */
|
|
5853
|
+
this.onloadCallback = onloadCallback;
|
|
5772
5854
|
|
|
5773
|
-
|
|
5774
|
-
if (zzfxSound)
|
|
5855
|
+
if (Array.isArray(asset))
|
|
5775
5856
|
{
|
|
5857
|
+
// generate zzfx sound
|
|
5858
|
+
const zzfxSound = asset;
|
|
5859
|
+
|
|
5776
5860
|
// remove randomness so it can be applied on playback
|
|
5777
|
-
const
|
|
5778
|
-
|
|
5779
|
-
|
|
5861
|
+
const defaultRandomness = randomness ?? .05;
|
|
5862
|
+
const randomnessIndex = 1;
|
|
5863
|
+
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
5780
5864
|
zzfxSound[randomnessIndex] = 0;
|
|
5781
5865
|
|
|
5782
5866
|
// generate the zzfx samples
|
|
5783
5867
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
5784
5868
|
this.loadedPercent = 1;
|
|
5869
|
+
onloadCallback?.(this);
|
|
5870
|
+
}
|
|
5871
|
+
else if (typeof asset === 'string')
|
|
5872
|
+
{
|
|
5873
|
+
// load the audio file
|
|
5874
|
+
const filename = asset;
|
|
5875
|
+
this.loadSound(filename);
|
|
5785
5876
|
}
|
|
5786
5877
|
}
|
|
5787
5878
|
|
|
@@ -5833,7 +5924,7 @@ class Sound
|
|
|
5833
5924
|
* @param {number} [volume] - Volume to play the music at
|
|
5834
5925
|
* @param {boolean} [loop] - Should the music loop?
|
|
5835
5926
|
* @param {boolean} [paused] - Should the music start paused
|
|
5836
|
-
* @return {SoundInstance} - The
|
|
5927
|
+
* @return {SoundInstance} - The sound instance
|
|
5837
5928
|
*/
|
|
5838
5929
|
playMusic(volume=1, loop=true, paused=false)
|
|
5839
5930
|
{ return this.play(undefined, volume, 1, 0, loop, paused); }
|
|
@@ -5843,7 +5934,7 @@ class Sound
|
|
|
5843
5934
|
* @param {number} [semitoneOffset=0] - How many semitones to offset pitch
|
|
5844
5935
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
5845
5936
|
* @param {number} [volume=1] - How much to scale volume by
|
|
5846
|
-
* @return {SoundInstance} - The
|
|
5937
|
+
* @return {SoundInstance} - The sound instance
|
|
5847
5938
|
*/
|
|
5848
5939
|
playNote(semitoneOffset=0, pos, volume)
|
|
5849
5940
|
{
|
|
@@ -5856,57 +5947,14 @@ class Sound
|
|
|
5856
5947
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
5857
5948
|
*/
|
|
5858
5949
|
getDuration()
|
|
5859
|
-
{ return this.sampleChannels
|
|
5950
|
+
{ return this.sampleChannels?.[0]?.length / this.sampleRate || 0; }
|
|
5860
5951
|
|
|
5861
5952
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
5862
5953
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
5863
5954
|
*/
|
|
5864
5955
|
isLoaded() { return this.loadedPercent === 1; }
|
|
5865
|
-
}
|
|
5866
|
-
|
|
5867
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5868
|
-
|
|
5869
|
-
/**
|
|
5870
|
-
* Sound Wave Object - Loads and stores an audio file for later use
|
|
5871
|
-
* - this can be used to load and play wave, mp3, and ogg files
|
|
5872
|
-
* @extends Sound
|
|
5873
|
-
* @memberof Audio
|
|
5874
|
-
* @example
|
|
5875
|
-
* // load an audio asset file
|
|
5876
|
-
* const sound_example = new SoundWave('sound.mp3');
|
|
5877
|
-
*
|
|
5878
|
-
* // play the sound
|
|
5879
|
-
* sound_example.play();
|
|
5880
|
-
*/
|
|
5881
|
-
class SoundWave extends Sound
|
|
5882
|
-
{
|
|
5883
|
-
/**
|
|
5884
|
-
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
5885
|
-
* @param {SoundWave} sound
|
|
5886
|
-
* @memberof Audio
|
|
5887
|
-
*/
|
|
5888
5956
|
|
|
5889
|
-
/**
|
|
5890
|
-
* @param {string} filename - Filename of audio file to load
|
|
5891
|
-
* @param {number} [randomness] - How much to randomize frequency each time sound plays
|
|
5892
|
-
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
5893
|
-
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
5894
|
-
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
5895
|
-
*/
|
|
5896
|
-
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
5897
|
-
{
|
|
5898
|
-
super(undefined, range, taper);
|
|
5899
|
-
if (!soundEnable || headlessMode) return;
|
|
5900
|
-
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
5901
|
-
ASSERT(isNumber(randomness), 'randomness must be a number');
|
|
5902
|
-
|
|
5903
|
-
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
5904
|
-
this.onloadCallback = onloadCallback;
|
|
5905
|
-
this.randomness = randomness;
|
|
5906
|
-
filename && this.loadSound(filename);
|
|
5907
|
-
}
|
|
5908
|
-
|
|
5909
|
-
/** Loads a sound from a URL and decodes it into sample data. Must be used with await!
|
|
5957
|
+
/** Loads a sound from a URL and decodes it into sample data.
|
|
5910
5958
|
* @param {string} filename
|
|
5911
5959
|
* @return {Promise<void>} */
|
|
5912
5960
|
async loadSound(filename)
|
|
@@ -5948,8 +5996,7 @@ class SoundWave extends Sound
|
|
|
5948
5996
|
this.sampleRate = audioBuffer.sampleRate;
|
|
5949
5997
|
this.sampleChannels = sampleChannels;
|
|
5950
5998
|
this.loadedPercent = 1;
|
|
5951
|
-
|
|
5952
|
-
this.onloadCallback(this);
|
|
5999
|
+
this.onloadCallback?.(this);
|
|
5953
6000
|
}
|
|
5954
6001
|
}
|
|
5955
6002
|
|
|
@@ -6152,7 +6199,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
6152
6199
|
|
|
6153
6200
|
/** Stop all queued speech
|
|
6154
6201
|
* @memberof Audio */
|
|
6155
|
-
function speakStop() {speechSynthesis
|
|
6202
|
+
function speakStop() {speechSynthesis?.cancel();}
|
|
6156
6203
|
|
|
6157
6204
|
/** Get frequency of a note on a musical scale
|
|
6158
6205
|
* @param {number} semitoneOffset - How many semitones away from the root note
|
|
@@ -6411,7 +6458,7 @@ function tileCollisionGetData(pos)
|
|
|
6411
6458
|
|
|
6412
6459
|
/** Check if a tile layer collides with another object
|
|
6413
6460
|
* @param {Vector2} pos
|
|
6414
|
-
* @param {Vector2} [size=(
|
|
6461
|
+
* @param {Vector2} [size=vec2()]
|
|
6415
6462
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
6416
6463
|
* @param {boolean} [solidOnly] - Only check solid layers if true
|
|
6417
6464
|
* @return {TileCollisionLayer}
|
|
@@ -6529,20 +6576,20 @@ function tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLa
|
|
|
6529
6576
|
class TileLayerData
|
|
6530
6577
|
{
|
|
6531
6578
|
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
6532
|
-
* @param {number} [tile]
|
|
6579
|
+
* @param {number} [tile] - The tile to use, untextured if undefined
|
|
6533
6580
|
* @param {number} [direction] - Integer direction of tile, in 90 degree increments
|
|
6534
|
-
* @param {boolean} [mirror]
|
|
6535
|
-
* @param {Color} [color]
|
|
6581
|
+
* @param {boolean} [mirror] - If the tile should be mirrored along the x axis
|
|
6582
|
+
* @param {Color} [color] - Color of the tile */
|
|
6536
6583
|
constructor(tile, direction=0, mirror=false, color=new Color)
|
|
6537
6584
|
{
|
|
6538
|
-
/** @property {number}
|
|
6539
|
-
this.tile
|
|
6540
|
-
/** @property {number}
|
|
6585
|
+
/** @property {number} - The tile to use, untextured if undefined */
|
|
6586
|
+
this.tile = tile;
|
|
6587
|
+
/** @property {number} - Integer direction of tile, in 90 degree increments */
|
|
6541
6588
|
this.direction = direction;
|
|
6542
6589
|
/** @property {boolean} - If the tile should be mirrored along the x axis */
|
|
6543
|
-
this.mirror
|
|
6544
|
-
/** @property {Color}
|
|
6545
|
-
this.color
|
|
6590
|
+
this.mirror = mirror;
|
|
6591
|
+
/** @property {Color} - Color of the tile */
|
|
6592
|
+
this.color = color.copy();
|
|
6546
6593
|
}
|
|
6547
6594
|
|
|
6548
6595
|
/** Set this tile to clear, it will not be rendered */
|
|
@@ -6553,7 +6600,7 @@ class TileLayerData
|
|
|
6553
6600
|
/**
|
|
6554
6601
|
* Canvas Layer - cached off screen rendering system
|
|
6555
6602
|
* - Contains an offscreen canvas that can be rendered to
|
|
6556
|
-
* - WebGL rendering is optional, call
|
|
6603
|
+
* - WebGL rendering is optional, call updateWebGL to enable/update
|
|
6557
6604
|
* @extends EngineObject
|
|
6558
6605
|
* @memberof TileLayers
|
|
6559
6606
|
* @example
|
|
@@ -6562,29 +6609,27 @@ class TileLayerData
|
|
|
6562
6609
|
class CanvasLayer extends EngineObject
|
|
6563
6610
|
{
|
|
6564
6611
|
/** Create a canvas layer object
|
|
6565
|
-
* @param {Vector2} [
|
|
6612
|
+
* @param {Vector2} [pos] - World space position of the layer
|
|
6566
6613
|
* @param {Vector2} [size] - World space size of the layer
|
|
6567
6614
|
* @param {number} [angle] - Angle the layer is rotated by
|
|
6568
6615
|
* @param {number} [renderOrder] - Objects sorted by renderOrder
|
|
6569
6616
|
* @param {Vector2} [canvasSize] - Default size of canvas, can be changed later
|
|
6617
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
6570
6618
|
*/
|
|
6571
|
-
constructor(
|
|
6619
|
+
constructor(pos, size, angle=0, renderOrder=0, canvasSize=vec2(512), useWebGL=true)
|
|
6572
6620
|
{
|
|
6573
6621
|
ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
|
|
6574
|
-
super(
|
|
6622
|
+
super(pos, size, undefined, angle, WHITE, renderOrder);
|
|
6575
6623
|
|
|
6576
6624
|
/** @property {HTMLCanvasElement} - The canvas used by this layer */
|
|
6577
6625
|
this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
6578
6626
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
|
|
6579
6627
|
this.context = this.canvas?.getContext('2d');
|
|
6580
6628
|
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
6581
|
-
const useWebGL = false; // do not use webgl by default
|
|
6582
6629
|
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
6583
|
-
/** @property {boolean} - True if WebGL texture needs to be refreshed */
|
|
6584
|
-
this.refreshWebGL = false;
|
|
6585
6630
|
|
|
6586
6631
|
// disable physics by default
|
|
6587
|
-
this.mass =
|
|
6632
|
+
this.mass = 0;
|
|
6588
6633
|
}
|
|
6589
6634
|
|
|
6590
6635
|
/** Destroy this canvas layer */
|
|
@@ -6599,7 +6644,7 @@ class CanvasLayer extends EngineObject
|
|
|
6599
6644
|
// Render the layer, called automatically by the engine
|
|
6600
6645
|
render()
|
|
6601
6646
|
{
|
|
6602
|
-
this.draw(this.pos, this.size, this.
|
|
6647
|
+
this.draw(this.pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
6603
6648
|
}
|
|
6604
6649
|
|
|
6605
6650
|
/** Draw this canvas layer centered in world space, with color applied if using WebGL
|
|
@@ -6612,103 +6657,54 @@ class CanvasLayer extends EngineObject
|
|
|
6612
6657
|
* @param {boolean} [screenSpace] - If true the pos and size are in screen space
|
|
6613
6658
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
6614
6659
|
* @memberof Draw */
|
|
6615
|
-
draw(pos, size,
|
|
6660
|
+
draw(pos, size, color=WHITE, angle=0, mirror=false, additiveColor, screenSpace=false, context)
|
|
6616
6661
|
{
|
|
6617
|
-
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
6618
|
-
if (useWebGL && this.refreshWebGL)
|
|
6619
|
-
{
|
|
6620
|
-
// update the WebGL texture
|
|
6621
|
-
this.textureInfo.createWebGLTexture();
|
|
6622
|
-
this.refreshWebGL = false;
|
|
6623
|
-
}
|
|
6624
|
-
|
|
6625
6662
|
// draw the canvas layer as a single tile that uses the whole texture
|
|
6626
6663
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
6664
|
+
const useWebGL = this.hasWebGL();
|
|
6627
6665
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
6628
6666
|
}
|
|
6629
6667
|
|
|
6630
|
-
/**
|
|
6631
|
-
* @callback Canvas2DDrawCallback - Function that draws to a canvas 2D context
|
|
6632
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
6633
|
-
* @memberof TileLayers
|
|
6634
|
-
*/
|
|
6635
|
-
|
|
6636
|
-
/** Draw onto the layer canvas in world space (bypass WebGL)
|
|
6668
|
+
/** Draw a tile onto the layer canvas in world space
|
|
6637
6669
|
* @param {Vector2} pos
|
|
6638
|
-
* @param {Vector2} size
|
|
6639
|
-
* @param {
|
|
6640
|
-
* @param {
|
|
6641
|
-
* @param {
|
|
6642
|
-
|
|
6670
|
+
* @param {Vector2} [size=vec2(1)]
|
|
6671
|
+
* @param {TileInfo} [tileInfo]
|
|
6672
|
+
* @param {Color} [color=WHITE]
|
|
6673
|
+
* @param {number} [angle]
|
|
6674
|
+
* @param {boolean} [mirror] */
|
|
6675
|
+
drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle=0, mirror=false)
|
|
6643
6676
|
{
|
|
6644
|
-
if (!this.context) return;
|
|
6645
|
-
|
|
6646
|
-
const context = this.context;
|
|
6647
|
-
context.save();
|
|
6648
6677
|
pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
|
|
6649
6678
|
size = size.multiply(this.tileInfo.size);
|
|
6650
|
-
|
|
6651
|
-
context.rotate(angle);
|
|
6652
|
-
context.scale(mirror ? -size.x : size.x, size.y);
|
|
6653
|
-
drawFunction(context);
|
|
6654
|
-
context.restore();
|
|
6655
|
-
}
|
|
6679
|
+
pos.y = this.canvas.height - pos.y;
|
|
6656
6680
|
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
this.drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
6667
|
-
{
|
|
6668
|
-
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
6669
|
-
if (textureInfo)
|
|
6670
|
-
{
|
|
6671
|
-
context.globalAlpha = color.a; // only alpha is supported
|
|
6672
|
-
context.drawImage(textureInfo.image,
|
|
6673
|
-
tileInfo.pos.x, tileInfo.pos.y,
|
|
6674
|
-
tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
|
|
6675
|
-
context.globalAlpha = 1;
|
|
6676
|
-
}
|
|
6677
|
-
else
|
|
6678
|
-
{
|
|
6679
|
-
// untextured
|
|
6680
|
-
context.fillStyle = color.toString();
|
|
6681
|
-
context.fillRect(-.5, -.5, 1, 1);
|
|
6682
|
-
}
|
|
6683
|
-
});
|
|
6681
|
+
// draw the tile onto the layer canvas
|
|
6682
|
+
const oldMainCanvasSize = mainCanvasSize;
|
|
6683
|
+
mainCanvasSize = vec2(this.canvas.width, this.canvas.height);
|
|
6684
|
+
const useWebGL = this.hasWebGL();
|
|
6685
|
+
useWebGL && glSetRenderTarget(this.textureInfo.glTexture);
|
|
6686
|
+
const drawContext = useWebGL ? undefined : this.context;
|
|
6687
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, undefined, useWebGL, true, drawContext);
|
|
6688
|
+
useWebGL && glSetRenderTarget();
|
|
6689
|
+
mainCanvasSize = oldMainCanvasSize;
|
|
6684
6690
|
}
|
|
6685
6691
|
|
|
6686
6692
|
/** Draw a rectangle onto the layer canvas in world space
|
|
6687
6693
|
* @param {Vector2} pos
|
|
6688
|
-
* @param {Vector2} [size=(1
|
|
6689
|
-
* @param {Color} [color=
|
|
6690
|
-
* @param {number} [angle
|
|
6694
|
+
* @param {Vector2} [size=vec2(1)]
|
|
6695
|
+
* @param {Color} [color=WHITE]
|
|
6696
|
+
* @param {number} [angle] */
|
|
6691
6697
|
drawRect(pos, size, color, angle)
|
|
6692
6698
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
6693
6699
|
|
|
6694
|
-
/** Create
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
*/
|
|
6698
|
-
useWebGL(enable=true, immediate=false)
|
|
6699
|
-
{
|
|
6700
|
-
if (!immediate && enable && this.textureInfo.hasWebGL())
|
|
6701
|
-
{
|
|
6702
|
-
// refresh the texture when needed
|
|
6703
|
-
this.refreshWebGL = true;
|
|
6704
|
-
return;
|
|
6705
|
-
}
|
|
6700
|
+
/** Create WebGL texture if necessary and copy layer canvas to it */
|
|
6701
|
+
updateWebGL()
|
|
6702
|
+
{ this.textureInfo.createWebGLTexture(); }
|
|
6706
6703
|
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
}
|
|
6704
|
+
/** Check if this layer is using WebGL
|
|
6705
|
+
* @return {boolean} */
|
|
6706
|
+
hasWebGL()
|
|
6707
|
+
{ return glEnable && this.textureInfo.hasWebGL(); }
|
|
6712
6708
|
}
|
|
6713
6709
|
|
|
6714
6710
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -6727,59 +6723,93 @@ class CanvasLayer extends EngineObject
|
|
|
6727
6723
|
class TileLayer extends CanvasLayer
|
|
6728
6724
|
{
|
|
6729
6725
|
/** Create a tile layer object
|
|
6730
|
-
* @param {Vector2}
|
|
6731
|
-
* @param {Vector2} size
|
|
6732
|
-
* @param {TileInfo} [tileInfo]
|
|
6726
|
+
* @param {Vector2} pos - World space position
|
|
6727
|
+
* @param {Vector2} size - World space size
|
|
6728
|
+
* @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
|
|
6733
6729
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6730
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
6734
6731
|
*/
|
|
6735
|
-
constructor(
|
|
6732
|
+
constructor(pos, size, tileInfo=tile(), renderOrder=0, useWebGL=true)
|
|
6736
6733
|
{
|
|
6737
6734
|
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
6738
|
-
super(
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
this.tileInfo =
|
|
6742
|
-
|
|
6743
|
-
// init tile data
|
|
6735
|
+
super(pos, size, 0, renderOrder, canvasSize, useWebGL);
|
|
6736
|
+
|
|
6737
|
+
/** @property {TileInfo} - Default tile info for layer */
|
|
6738
|
+
this.tileInfo = undefined;
|
|
6739
|
+
/** @property {Array<TileLayerData>} - Default tile info for layer */
|
|
6744
6740
|
this.data = [];
|
|
6745
|
-
|
|
6746
|
-
|
|
6741
|
+
/** @property {boolean} - Is this layer using a webgl texture? */
|
|
6742
|
+
this.isUsingWebGL = false;
|
|
6747
6743
|
|
|
6748
6744
|
if (headlessMode)
|
|
6749
6745
|
{
|
|
6750
|
-
// disable rendering
|
|
6751
|
-
this.
|
|
6752
|
-
this.
|
|
6753
|
-
this.redrawStart
|
|
6754
|
-
this.redrawEnd
|
|
6755
|
-
this.drawTileData
|
|
6756
|
-
this.
|
|
6757
|
-
this.
|
|
6746
|
+
// disable rendering in headless mode
|
|
6747
|
+
this.render = ()=> {};
|
|
6748
|
+
this.redraw = ()=> {};
|
|
6749
|
+
this.redrawStart = ()=> {};
|
|
6750
|
+
this.redrawEnd = ()=> {};
|
|
6751
|
+
this.drawTileData = ()=> {};
|
|
6752
|
+
this.redrawTileData = ()=> {};
|
|
6753
|
+
this.drawLayerTile = ()=> {};
|
|
6754
|
+
this.drawLayerRect = ()=> {};
|
|
6755
|
+
this.clearLayerRect = ()=> {};
|
|
6756
|
+
return;
|
|
6758
6757
|
}
|
|
6758
|
+
|
|
6759
|
+
if (tileInfo)
|
|
6760
|
+
{
|
|
6761
|
+
// set tile info
|
|
6762
|
+
this.tileInfo = tileInfo.frame(0);
|
|
6763
|
+
this.tileInfo.bleed = 0; // disable bleed for tile layers
|
|
6764
|
+
}
|
|
6765
|
+
|
|
6766
|
+
// init tile data
|
|
6767
|
+
for (let j = this.size.area(); j--;)
|
|
6768
|
+
this.data.push(new TileLayerData);
|
|
6759
6769
|
}
|
|
6760
6770
|
|
|
6761
6771
|
/** Set data at a given position in the array
|
|
6762
6772
|
* @param {Vector2} layerPos - Local position in array
|
|
6763
|
-
* @param {TileLayerData} data
|
|
6773
|
+
* @param {TileLayerData} data - Data to set
|
|
6764
6774
|
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
6765
6775
|
setData(layerPos, data, redraw=false)
|
|
6766
6776
|
{
|
|
6777
|
+
layerPos = layerPos.floor();
|
|
6767
6778
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6768
6779
|
ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6780
|
+
|
|
6781
|
+
if (!layerPos.arrayCheck(this.size)) return;
|
|
6782
|
+
this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
|
|
6783
|
+
|
|
6784
|
+
if (!redraw) return;
|
|
6785
|
+
const isRedraw = drawContext === this.context;
|
|
6786
|
+
isRedraw ? this.drawTileData(layerPos) : this.redrawTileData(layerPos);
|
|
6774
6787
|
}
|
|
6775
6788
|
|
|
6789
|
+
/** Clear data at a given position in the array
|
|
6790
|
+
* @param {Vector2} layerPos - Local position in array
|
|
6791
|
+
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
6792
|
+
clearData(layerPos, redraw=false)
|
|
6793
|
+
{ this.setData(layerPos, new TileLayerData, redraw) }
|
|
6794
|
+
|
|
6776
6795
|
/** Get data at a given position in the array
|
|
6777
6796
|
* @param {Vector2} layerPos - Local position in array
|
|
6778
6797
|
* @return {TileLayerData} */
|
|
6779
6798
|
getData(layerPos)
|
|
6780
6799
|
{
|
|
6781
6800
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6782
|
-
return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
|
|
6801
|
+
return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
|
|
6802
|
+
}
|
|
6803
|
+
|
|
6804
|
+
// Update the tile layer, refresh texture if needed
|
|
6805
|
+
update()
|
|
6806
|
+
{
|
|
6807
|
+
if (!glEnable && this.isUsingWebGL)
|
|
6808
|
+
{
|
|
6809
|
+
// redraw the layer if webgl was disabled or context lost
|
|
6810
|
+
this.isUsingWebGL = false;
|
|
6811
|
+
this.redraw();
|
|
6812
|
+
}
|
|
6783
6813
|
}
|
|
6784
6814
|
|
|
6785
6815
|
// Render the tile layer, called automatically by the engine
|
|
@@ -6787,40 +6817,35 @@ class TileLayer extends CanvasLayer
|
|
|
6787
6817
|
{
|
|
6788
6818
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
6789
6819
|
|
|
6790
|
-
if (this.refreshWebGL)
|
|
6791
|
-
{
|
|
6792
|
-
// update the WebGL texture
|
|
6793
|
-
this.textureInfo.createWebGLTexture();
|
|
6794
|
-
this.refreshWebGL = false;
|
|
6795
|
-
}
|
|
6796
|
-
|
|
6797
|
-
// draw the tile layer as a single tile
|
|
6798
|
-
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
6799
6820
|
const size = this.drawSize || this.size;
|
|
6800
6821
|
const pos = this.pos.add(size.scale(.5));
|
|
6801
|
-
|
|
6802
|
-
drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
|
|
6822
|
+
this.draw(pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
6803
6823
|
}
|
|
6804
6824
|
|
|
6825
|
+
/** Called after this layer is redrawn, does nothing by default */
|
|
6826
|
+
onRedraw() {}
|
|
6827
|
+
|
|
6805
6828
|
/** Draw all the tile data to an offscreen canvas
|
|
6806
|
-
* - This may be slow
|
|
6829
|
+
* - This may be slow if not using webgl but only needs to be done once */
|
|
6807
6830
|
redraw()
|
|
6808
6831
|
{
|
|
6809
6832
|
this.redrawStart(true);
|
|
6810
6833
|
for (let x = this.size.x; x--;)
|
|
6811
6834
|
for (let y = this.size.y; y--;)
|
|
6812
6835
|
this.drawTileData(vec2(x,y), false);
|
|
6836
|
+
this.isUsingWebGL && glFlush();
|
|
6837
|
+
this.onRedraw();
|
|
6813
6838
|
this.redrawEnd();
|
|
6814
|
-
this.useWebGL();
|
|
6815
6839
|
}
|
|
6816
6840
|
|
|
6817
6841
|
/** Call to start the redraw process
|
|
6818
|
-
* - This can be used to manually update
|
|
6842
|
+
* - This can be used to manually update parts of the level
|
|
6819
6843
|
* @param {boolean} [clear] - Should it clear the canvas before drawing */
|
|
6820
6844
|
redrawStart(clear=false)
|
|
6821
6845
|
{
|
|
6822
6846
|
if (!this.context) return;
|
|
6823
|
-
|
|
6847
|
+
ASSERT(drawContext !== this.context);
|
|
6848
|
+
|
|
6824
6849
|
// save current render settings
|
|
6825
6850
|
/** @type {[CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
|
|
6826
6851
|
this.savedRenderSettings = [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
|
|
@@ -6828,65 +6853,119 @@ class TileLayer extends CanvasLayer
|
|
|
6828
6853
|
// set the draw canvas and context to this layer
|
|
6829
6854
|
// use camera settings to match this layer's canvas
|
|
6830
6855
|
drawContext = this.context;
|
|
6831
|
-
|
|
6832
|
-
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
6833
|
-
cameraScale = tileSize.x;
|
|
6834
|
-
canvasClearColor = CLEAR_BLACK;
|
|
6856
|
+
const tileSize = this.tileInfo?.size ?? vec2(1);
|
|
6835
6857
|
mainCanvasSize = this.size.multiply(tileSize);
|
|
6836
|
-
|
|
6858
|
+
canvasClearColor = CLEAR_BLACK;
|
|
6859
|
+
cameraPos = this.size.multiply(tileSize).scale(.5);
|
|
6860
|
+
cameraScale = 1;
|
|
6861
|
+
|
|
6862
|
+
// set render target to this layer
|
|
6863
|
+
this.isUsingWebGL = this.hasWebGL();
|
|
6864
|
+
if (this.isUsingWebGL)
|
|
6865
|
+
glSetRenderTarget(this.textureInfo.glTexture, clear);
|
|
6866
|
+
else
|
|
6837
6867
|
{
|
|
6838
|
-
//
|
|
6839
|
-
this.
|
|
6840
|
-
|
|
6868
|
+
// disable smoothing for pixel art
|
|
6869
|
+
this.context.imageSmoothingEnabled = !tilesPixelated;
|
|
6870
|
+
if (clear)
|
|
6871
|
+
{
|
|
6872
|
+
// clear and set size
|
|
6873
|
+
this.canvas.width = mainCanvasSize.x;
|
|
6874
|
+
this.canvas.height = mainCanvasSize.y;
|
|
6875
|
+
}
|
|
6841
6876
|
}
|
|
6842
|
-
|
|
6843
|
-
// disable smoothing for pixel art
|
|
6844
|
-
drawContext.imageSmoothingEnabled = !tilesPixelated;
|
|
6845
|
-
|
|
6846
|
-
// setup gl rendering if enabled
|
|
6847
|
-
glPreRender();
|
|
6848
6877
|
}
|
|
6849
6878
|
|
|
6850
6879
|
/** Call to end the redraw process */
|
|
6851
6880
|
redrawEnd()
|
|
6852
6881
|
{
|
|
6853
6882
|
if (!this.context) return;
|
|
6883
|
+
ASSERT(drawContext === this.context);
|
|
6884
|
+
|
|
6885
|
+
// set stuff back to normal
|
|
6886
|
+
if (this.isUsingWebGL)
|
|
6887
|
+
glSetRenderTarget();
|
|
6888
|
+
[drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
|
|
6889
|
+
}
|
|
6890
|
+
|
|
6891
|
+
/** Draw the tile at a given position in the tile layer
|
|
6892
|
+
* This can be used to clear out tiles when they are destroyed
|
|
6893
|
+
* Tiles can also be redrawn if inside a redrawStart/End block
|
|
6894
|
+
* @param {Vector2} layerPos
|
|
6895
|
+
* @param {boolean} [clear] - should the old tile be cleared out
|
|
6896
|
+
*/
|
|
6897
|
+
drawTileData(layerPos, clear=true)
|
|
6898
|
+
{
|
|
6899
|
+
if (!this.context) return;
|
|
6900
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
6901
|
+
|
|
6902
|
+
// clear out where the tile was, can be skipped for fully opaque tiles
|
|
6903
|
+
const drawSize = this.tileInfo?.size ?? vec2(1);
|
|
6904
|
+
const drawPos = layerPos.multiply(drawSize);
|
|
6905
|
+
clear && this.clearLayerRect(drawPos, drawSize);
|
|
6906
|
+
|
|
6907
|
+
// draw the tile if it has layer data
|
|
6908
|
+
const d = this.getData(layerPos);
|
|
6909
|
+
if (!d.tile) return;
|
|
6910
|
+
|
|
6911
|
+
const tileInfo = this.tileInfo && this.tileInfo.tile(d.tile);
|
|
6912
|
+
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
6913
|
+
}
|
|
6914
|
+
|
|
6915
|
+
/** Draw the tile at a given position in the tile layer
|
|
6916
|
+
* This can be used to clear tiles when they are destroyed
|
|
6917
|
+
* For better performance use drawTileData inside a redrawStart/End block
|
|
6918
|
+
* @param {Vector2} layerPos
|
|
6919
|
+
* @param {boolean} [clear] - should the old tile be cleared
|
|
6920
|
+
*/
|
|
6921
|
+
redrawTileData(layerPos, clear=true)
|
|
6922
|
+
{
|
|
6923
|
+
if (!this.context) return;
|
|
6924
|
+
ASSERT(drawContext !== this.context, 'redrawStart() should not be active when calling redrawTileData(), instead use drawTileData()');
|
|
6854
6925
|
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6926
|
+
this.redrawStart();
|
|
6927
|
+
this.drawTileData(layerPos, clear);
|
|
6928
|
+
this.redrawEnd();
|
|
6929
|
+
}
|
|
6858
6930
|
|
|
6859
|
-
|
|
6860
|
-
|
|
6931
|
+
/** Draw textured tile in layer space
|
|
6932
|
+
* @param {Vector2} pos - Position in pixel coordinates
|
|
6933
|
+
* @param {Vector2} [size=vec2(1)] - Size of the tile
|
|
6934
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
6935
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
6936
|
+
* @param {number} [angle] - Angle to rotate by
|
|
6937
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
6938
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any */
|
|
6939
|
+
drawLayerTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
6940
|
+
angle=0, mirror, additiveColor)
|
|
6941
|
+
{
|
|
6942
|
+
const drawPos = pos.add(size.scale(.5));
|
|
6943
|
+
drawTile(drawPos, size, tileInfo, color, angle, mirror, additiveColor, this.isUsingWebGL);
|
|
6861
6944
|
}
|
|
6862
6945
|
|
|
6863
|
-
/**
|
|
6864
|
-
*
|
|
6865
|
-
*
|
|
6866
|
-
* @param {
|
|
6867
|
-
* @param {
|
|
6946
|
+
/** Clear a rectangle in layer space
|
|
6947
|
+
* @param {Vector2} pos
|
|
6948
|
+
* @param {Vector2} size
|
|
6949
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
6950
|
+
* @param {number} [angle] - Angle to rotate by
|
|
6868
6951
|
*/
|
|
6869
|
-
|
|
6952
|
+
drawLayerRect(pos, size, color, angle=0)
|
|
6953
|
+
{ this.drawLayerTile(pos, size, undefined, color, angle); }
|
|
6954
|
+
|
|
6955
|
+
/** Clear a rectangle in layer space
|
|
6956
|
+
* @param {Vector2} pos - position in pixel coordinates
|
|
6957
|
+
* @param {Vector2} size
|
|
6958
|
+
*/
|
|
6959
|
+
clearLayerRect(pos, size)
|
|
6870
6960
|
{
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
// clear out where the tile was, for full opaque tiles this can be skipped
|
|
6874
|
-
const s = this.tileInfo.size;
|
|
6875
|
-
if (clear)
|
|
6876
|
-
{
|
|
6877
|
-
const pos = layerPos.multiply(s);
|
|
6878
|
-
this.context.clearRect(pos.x, this.canvas.height-pos.y, s.x, -s.y);
|
|
6879
|
-
}
|
|
6961
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before clearing tiles');
|
|
6880
6962
|
|
|
6881
|
-
|
|
6882
|
-
const
|
|
6883
|
-
if (
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex, this.tileInfo.padding);
|
|
6888
|
-
drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
6889
|
-
}
|
|
6963
|
+
const x = pos.x, y = this.canvas.height - pos.y - size.y;
|
|
6964
|
+
const useWebGL = this.hasWebGL();
|
|
6965
|
+
if (useWebGL)
|
|
6966
|
+
glClearRect(x, y, size.x, size.y);
|
|
6967
|
+
else
|
|
6968
|
+
this.context.clearRect(x, y, size.x, size.y);
|
|
6890
6969
|
}
|
|
6891
6970
|
}
|
|
6892
6971
|
|
|
@@ -6895,21 +6974,21 @@ class TileLayer extends CanvasLayer
|
|
|
6895
6974
|
* Tile Collision Layer - a tile layer with collision
|
|
6896
6975
|
* - adds collision data and functions to TileLayer
|
|
6897
6976
|
* - there can be multiple tile collision layers
|
|
6898
|
-
* - tile collision layers should not overlap each other
|
|
6899
6977
|
* @extends TileLayer
|
|
6900
6978
|
* @memberof TileLayers
|
|
6901
6979
|
*/
|
|
6902
6980
|
class TileCollisionLayer extends TileLayer
|
|
6903
6981
|
{
|
|
6904
6982
|
/** Create a tile layer object
|
|
6905
|
-
* @param {Vector2}
|
|
6906
|
-
* @param {Vector2} size
|
|
6907
|
-
* @param {TileInfo} [tileInfo]
|
|
6983
|
+
* @param {Vector2} pos - World space position
|
|
6984
|
+
* @param {Vector2} size - World space size
|
|
6985
|
+
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
6908
6986
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6987
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
6909
6988
|
*/
|
|
6910
|
-
constructor(
|
|
6989
|
+
constructor(pos, size, tileInfo=tile(), renderOrder=0, useWebGL=true)
|
|
6911
6990
|
{
|
|
6912
|
-
super(
|
|
6991
|
+
super(pos, size.floor(), tileInfo, renderOrder, useWebGL);
|
|
6913
6992
|
|
|
6914
6993
|
/** @property {Array<number>} - The tile collision grid */
|
|
6915
6994
|
this.collisionData = [];
|
|
@@ -6945,29 +7024,34 @@ class TileCollisionLayer extends TileLayer
|
|
|
6945
7024
|
this.collisionData.fill(0);
|
|
6946
7025
|
}
|
|
6947
7026
|
|
|
6948
|
-
/** Set tile collision data for a given cell in the
|
|
6949
|
-
* @param {Vector2}
|
|
7027
|
+
/** Set tile collision data for a given cell in the layer
|
|
7028
|
+
* @param {Vector2} layerPos
|
|
6950
7029
|
* @param {number} [data] */
|
|
6951
|
-
setCollisionData(
|
|
7030
|
+
setCollisionData(layerPos, data=1)
|
|
6952
7031
|
{
|
|
6953
|
-
ASSERT(isVector2(
|
|
6954
|
-
const i = (
|
|
6955
|
-
|
|
7032
|
+
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
7033
|
+
const i = (layerPos.y|0)*this.size.x + layerPos.x|0;
|
|
7034
|
+
layerPos.arrayCheck(this.size) && (this.collisionData[i] = data);
|
|
6956
7035
|
}
|
|
6957
7036
|
|
|
6958
|
-
/**
|
|
6959
|
-
* @param {Vector2}
|
|
7037
|
+
/** Clear tile collision data for a given cell in the layer
|
|
7038
|
+
* @param {Vector2} layerPos */
|
|
7039
|
+
clearCollisionData(layerPos)
|
|
7040
|
+
{ this.setCollisionData(layerPos, 0); }
|
|
7041
|
+
|
|
7042
|
+
/** Get tile collision data for a given cell in the layer
|
|
7043
|
+
* @param {Vector2} layerPos
|
|
6960
7044
|
* @return {number} */
|
|
6961
|
-
getCollisionData(
|
|
7045
|
+
getCollisionData(layerPos)
|
|
6962
7046
|
{
|
|
6963
|
-
ASSERT(isVector2(
|
|
6964
|
-
const i = (
|
|
6965
|
-
return
|
|
7047
|
+
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
7048
|
+
const i = (layerPos.y|0)*this.size.x + layerPos.x|0;
|
|
7049
|
+
return layerPos.arrayCheck(this.size) ? this.collisionData[i] : 0;
|
|
6966
7050
|
}
|
|
6967
7051
|
|
|
6968
7052
|
/** Check if collision with another object should occur
|
|
6969
7053
|
* @param {Vector2} pos
|
|
6970
|
-
* @param {Vector2} [size=(
|
|
7054
|
+
* @param {Vector2} [size=vec2()]
|
|
6971
7055
|
* @param {EngineObject} [object]
|
|
6972
7056
|
* @return {boolean} */
|
|
6973
7057
|
collisionTest(pos, size=new Vector2, object)
|
|
@@ -7287,7 +7371,7 @@ class ParticleEmitter extends EngineObject
|
|
|
7287
7371
|
particle.mirror = randBool();
|
|
7288
7372
|
|
|
7289
7373
|
// call particle create callback
|
|
7290
|
-
this.particleCreateCallback
|
|
7374
|
+
this.particleCreateCallback?.(particle);
|
|
7291
7375
|
|
|
7292
7376
|
// return the newly created particle
|
|
7293
7377
|
return particle;
|
|
@@ -7372,7 +7456,7 @@ class Particle extends EngineObject
|
|
|
7372
7456
|
const c = this.colorEnd;
|
|
7373
7457
|
this.color.set(c.r, c.g, c.b, c.a);
|
|
7374
7458
|
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
7375
|
-
this.destroyCallback
|
|
7459
|
+
this.destroyCallback?.(this);
|
|
7376
7460
|
this.destroyed = 1;
|
|
7377
7461
|
}
|
|
7378
7462
|
}
|
|
@@ -7650,7 +7734,7 @@ let glContext;
|
|
|
7650
7734
|
let glAntialias = true;
|
|
7651
7735
|
|
|
7652
7736
|
// WebGL internal variables not exposed to documentation
|
|
7653
|
-
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
|
|
7737
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glInstancedVAO, glPolyVAO, glFramebuffer, glRenderTarget, glCanBeEnabled = true;
|
|
7654
7738
|
|
|
7655
7739
|
// WebGL internal constants
|
|
7656
7740
|
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
@@ -7726,7 +7810,7 @@ function glInit(rootElement)
|
|
|
7726
7810
|
// setup instanced rendering shader program
|
|
7727
7811
|
glShader = glCreateProgram(
|
|
7728
7812
|
'#version 300 es\n' + // specify GLSL ES version
|
|
7729
|
-
'precision highp float;'+ // use highp for
|
|
7813
|
+
'precision highp float;'+ // use highp for accuracy
|
|
7730
7814
|
'uniform mat4 m;'+ // transform matrix
|
|
7731
7815
|
'in vec2 g;'+ // in: geometry
|
|
7732
7816
|
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
@@ -7741,7 +7825,7 @@ function glInit(rootElement)
|
|
|
7741
7825
|
'}' // end of shader
|
|
7742
7826
|
,
|
|
7743
7827
|
'#version 300 es\n' + // specify GLSL ES version
|
|
7744
|
-
'precision highp float;'+ // use highp for
|
|
7828
|
+
'precision highp float;'+ // use highp for accuracy
|
|
7745
7829
|
'uniform sampler2D s;'+ // texture
|
|
7746
7830
|
'in vec2 v;'+ // in: uv
|
|
7747
7831
|
'in vec4 d,e;'+ // in: color, additiveColor
|
|
@@ -7779,45 +7863,62 @@ function glInit(rootElement)
|
|
|
7779
7863
|
glColorData = new Uint32Array(glInstanceData);
|
|
7780
7864
|
glArrayBuffer = glContext.createBuffer();
|
|
7781
7865
|
glGeometryBuffer = glContext.createBuffer();
|
|
7866
|
+
glFramebuffer = glContext.createFramebuffer();
|
|
7867
|
+
glBatchCount = 0;
|
|
7782
7868
|
|
|
7783
7869
|
// create the geometry buffer, triangle strip square
|
|
7784
|
-
const geometry = new Float32Array([
|
|
7870
|
+
const geometry = new Float32Array([0,0,1,0,0,1,1,1]);
|
|
7785
7871
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7786
7872
|
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
7873
|
+
|
|
7874
|
+
let offset, shader, stride;
|
|
7875
|
+
const initVertexAttrib = (name, type, typeSize, size, divisor=0)=>
|
|
7876
|
+
{
|
|
7877
|
+
const location = glContext.getAttribLocation(shader, name);
|
|
7878
|
+
const normalize = typeSize === 1;
|
|
7879
|
+
const fixedStride = typeSize && stride;
|
|
7880
|
+
glContext.enableVertexAttribArray(location);
|
|
7881
|
+
glContext.vertexAttribPointer(location, size, type, normalize, fixedStride, offset);
|
|
7882
|
+
glContext.vertexAttribDivisor(location, divisor);
|
|
7883
|
+
offset += size*typeSize;
|
|
7884
|
+
}
|
|
7885
|
+
|
|
7886
|
+
// setup VAO for instanced rendering
|
|
7887
|
+
glInstancedVAO = glContext.createVertexArray();
|
|
7888
|
+
glContext.bindVertexArray(glInstancedVAO);
|
|
7889
|
+
|
|
7890
|
+
// configure instanced vertex attributes
|
|
7891
|
+
offset = 0, shader = glShader, stride = gl_INSTANCE_BYTE_STRIDE;
|
|
7892
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7893
|
+
initVertexAttrib('g', glContext.FLOAT, 0, 2); // geometry
|
|
7894
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7895
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7896
|
+
initVertexAttrib('p', glContext.FLOAT, 4, 4, 1); // position & size
|
|
7897
|
+
initVertexAttrib('u', glContext.FLOAT, 4, 4, 1); // texture coords
|
|
7898
|
+
initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4, 1); // color
|
|
7899
|
+
initVertexAttrib('a', glContext.UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
7900
|
+
initVertexAttrib('r', glContext.FLOAT, 4, 1, 1); // rotation
|
|
7901
|
+
|
|
7902
|
+
// setup VAO for poly rendering
|
|
7903
|
+
glPolyVAO = glContext.createVertexArray();
|
|
7904
|
+
glContext.bindVertexArray(glPolyVAO);
|
|
7905
|
+
|
|
7906
|
+
// configure poly vertex attributes
|
|
7907
|
+
offset = 0, shader = glPolyShader, stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
7908
|
+
initVertexAttrib('p', glContext.FLOAT, 4, 2); // position
|
|
7909
|
+
initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7787
7910
|
}
|
|
7788
7911
|
}
|
|
7789
7912
|
|
|
7790
|
-
function glSetInstancedMode()
|
|
7913
|
+
function glSetInstancedMode(force=false)
|
|
7791
7914
|
{
|
|
7792
|
-
if (!glPolyMode) return;
|
|
7915
|
+
if (!force && !glPolyMode) return;
|
|
7793
7916
|
|
|
7794
7917
|
// setup instanced mode
|
|
7795
7918
|
glFlush();
|
|
7796
7919
|
glPolyMode = false;
|
|
7797
7920
|
glContext.useProgram(glShader);
|
|
7798
|
-
|
|
7799
|
-
// set vertex attributes
|
|
7800
|
-
let offset = 0;
|
|
7801
|
-
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
7802
|
-
{
|
|
7803
|
-
const location = glContext.getAttribLocation(glShader, name);
|
|
7804
|
-
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
7805
|
-
const divisor = typeSize && 1; // only if not geometry
|
|
7806
|
-
const normalize = typeSize === 1; // only if color
|
|
7807
|
-
glContext.enableVertexAttribArray(location);
|
|
7808
|
-
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
7809
|
-
glContext.vertexAttribDivisor(location, divisor);
|
|
7810
|
-
offset += size*typeSize;
|
|
7811
|
-
}
|
|
7812
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7813
|
-
initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
|
|
7814
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7815
|
-
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7816
|
-
initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
|
|
7817
|
-
initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
|
|
7818
|
-
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7819
|
-
initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
|
|
7820
|
-
initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
|
|
7921
|
+
glContext.bindVertexArray(glInstancedVAO);
|
|
7821
7922
|
}
|
|
7822
7923
|
|
|
7823
7924
|
function glSetPolyMode()
|
|
@@ -7828,36 +7929,30 @@ function glSetPolyMode()
|
|
|
7828
7929
|
glFlush();
|
|
7829
7930
|
glPolyMode = true;
|
|
7830
7931
|
glContext.useProgram(glPolyShader);
|
|
7831
|
-
|
|
7832
|
-
// set vertex attributes
|
|
7833
|
-
let offset = 0;
|
|
7834
|
-
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
7835
|
-
{
|
|
7836
|
-
const location = glContext.getAttribLocation(glPolyShader, name);
|
|
7837
|
-
const normalize = typeSize === 1; // only normalize if color
|
|
7838
|
-
const stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
7839
|
-
glContext.enableVertexAttribArray(location);
|
|
7840
|
-
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
7841
|
-
glContext.vertexAttribDivisor(location, 0);
|
|
7842
|
-
offset += size*typeSize;
|
|
7843
|
-
}
|
|
7844
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7845
|
-
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7846
|
-
initVertexAttribArray('p', glContext.FLOAT, 4, 2); // position
|
|
7847
|
-
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7932
|
+
glContext.bindVertexArray(glPolyVAO);
|
|
7848
7933
|
}
|
|
7849
7934
|
|
|
7850
7935
|
// Setup WebGL render each frame, called automatically by engine
|
|
7851
7936
|
// Also used by tile layer rendering when redrawing tiles
|
|
7852
|
-
function glPreRender()
|
|
7937
|
+
function glPreRender(clear=true)
|
|
7853
7938
|
{
|
|
7854
7939
|
if (!glEnable || !glContext) return;
|
|
7855
7940
|
|
|
7856
|
-
|
|
7857
|
-
|
|
7941
|
+
ASSERT(!glBatchCount, 'glPreRender called with unflushed batch.');
|
|
7942
|
+
|
|
7943
|
+
if (!glRenderTarget)
|
|
7944
|
+
{
|
|
7945
|
+
// set to same size as main canvas
|
|
7946
|
+
glCanvas.width = mainCanvasSize.x;
|
|
7947
|
+
glCanvas.height = mainCanvasSize.y;
|
|
7948
|
+
}
|
|
7949
|
+
glContext.viewport(0, 0, mainCanvasSize.x, mainCanvasSize.y);
|
|
7950
|
+
clear && glClearCanvas();
|
|
7858
7951
|
|
|
7859
7952
|
// build the transform matrix
|
|
7860
7953
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
7954
|
+
if (glRenderTarget)
|
|
7955
|
+
s.y = -s.y; // invert y when using render target
|
|
7861
7956
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
7862
7957
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
7863
7958
|
const ca = cos(cameraAngle);
|
|
@@ -7869,7 +7964,7 @@ function glPreRender()
|
|
|
7869
7964
|
p.x, p.y, 0, 1];
|
|
7870
7965
|
|
|
7871
7966
|
// set the same transform matrix for both shaders
|
|
7872
|
-
const initUniform = (program, uniform, value)
|
|
7967
|
+
const initUniform = (program, uniform, value)=>
|
|
7873
7968
|
{
|
|
7874
7969
|
glContext.useProgram(program);
|
|
7875
7970
|
const location = glContext.getUniformLocation(program, uniform);
|
|
@@ -7886,12 +7981,14 @@ function glPreRender()
|
|
|
7886
7981
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7887
7982
|
}
|
|
7888
7983
|
|
|
7984
|
+
// rebind the array buffer
|
|
7985
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7986
|
+
|
|
7889
7987
|
// start with additive blending off
|
|
7890
7988
|
glAdditive = glBatchAdditive = false;
|
|
7891
7989
|
|
|
7892
|
-
// force it to set instanced mode
|
|
7893
|
-
|
|
7894
|
-
glSetInstancedMode();
|
|
7990
|
+
// force it to set instanced mode
|
|
7991
|
+
glSetInstancedMode(true);
|
|
7895
7992
|
}
|
|
7896
7993
|
|
|
7897
7994
|
/** Clear the canvas and setup the viewport
|
|
@@ -7900,13 +7997,9 @@ function glClearCanvas()
|
|
|
7900
7997
|
{
|
|
7901
7998
|
if (!glContext) return;
|
|
7902
7999
|
|
|
7903
|
-
// clear
|
|
7904
|
-
glCanvas.width = mainCanvasSize.x;
|
|
7905
|
-
glCanvas.height = mainCanvasSize.y;
|
|
7906
|
-
glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
|
|
8000
|
+
// clear using the canvasClearColor
|
|
7907
8001
|
const color = canvasClearColor;
|
|
7908
|
-
|
|
7909
|
-
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
8002
|
+
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
7910
8003
|
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
7911
8004
|
}
|
|
7912
8005
|
|
|
@@ -7983,7 +8076,7 @@ function glCreateTexture(image)
|
|
|
7983
8076
|
// build the texture
|
|
7984
8077
|
const texture = glContext.createTexture();
|
|
7985
8078
|
let mipMap = false;
|
|
7986
|
-
if (image
|
|
8079
|
+
if (image?.width)
|
|
7987
8080
|
{
|
|
7988
8081
|
glSetTextureData(texture, image);
|
|
7989
8082
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
@@ -8004,7 +8097,9 @@ function glCreateTexture(image)
|
|
|
8004
8097
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, minFilter);
|
|
8005
8098
|
if (mipMap)
|
|
8006
8099
|
glContext.generateMipmap(glContext.TEXTURE_2D);
|
|
8007
|
-
|
|
8100
|
+
|
|
8101
|
+
// rebind active texture
|
|
8102
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
8008
8103
|
return texture;
|
|
8009
8104
|
}
|
|
8010
8105
|
|
|
@@ -8027,10 +8122,12 @@ function glSetTextureData(texture, image)
|
|
|
8027
8122
|
if (!glContext) return;
|
|
8028
8123
|
|
|
8029
8124
|
// build the texture
|
|
8030
|
-
ASSERT(
|
|
8125
|
+
ASSERT(image?.width > 0, 'Invalid image data.');
|
|
8031
8126
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
8032
8127
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
8033
|
-
|
|
8128
|
+
|
|
8129
|
+
// rebind active texture
|
|
8130
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
8034
8131
|
}
|
|
8035
8132
|
|
|
8036
8133
|
/** Tells WebGL to create or update the glTexture and start tracking it
|
|
@@ -8249,6 +8346,48 @@ function glDrawColoredPoints(points, pointColors)
|
|
|
8249
8346
|
glBatchCount += vertCount;
|
|
8250
8347
|
}
|
|
8251
8348
|
|
|
8349
|
+
/** Set the WebGL render target to the given texture or back to the canvas
|
|
8350
|
+
* @param {WebGLTexture} [texture] - a texture or undefined to use normal glCanvas
|
|
8351
|
+
* @param {boolean} [clear] - should the render target be cleared
|
|
8352
|
+
* @memberof WebGL */
|
|
8353
|
+
function glSetRenderTarget(texture, clear=false)
|
|
8354
|
+
{
|
|
8355
|
+
if (texture)
|
|
8356
|
+
{
|
|
8357
|
+
glRenderTarget = texture;
|
|
8358
|
+
glContext.bindFramebuffer(glContext.FRAMEBUFFER, glFramebuffer);
|
|
8359
|
+
glContext.framebufferTexture2D(glContext.FRAMEBUFFER,
|
|
8360
|
+
glContext.COLOR_ATTACHMENT0, glContext.TEXTURE_2D, texture, 0);
|
|
8361
|
+
glPreRender(clear);
|
|
8362
|
+
}
|
|
8363
|
+
else
|
|
8364
|
+
{
|
|
8365
|
+
glFlush();
|
|
8366
|
+
glRenderTarget = undefined;
|
|
8367
|
+
glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
|
|
8368
|
+
}
|
|
8369
|
+
}
|
|
8370
|
+
|
|
8371
|
+
/** Clear out a rectangle area of the WebGL canvas or render target
|
|
8372
|
+
* @param {number} x
|
|
8373
|
+
* @param {number} y
|
|
8374
|
+
* @param {number} width
|
|
8375
|
+
* @param {number} height
|
|
8376
|
+
* @memberof WebGL */
|
|
8377
|
+
function glClearRect(x, y, width, height)
|
|
8378
|
+
{
|
|
8379
|
+
if (!glEnable) return;
|
|
8380
|
+
|
|
8381
|
+
// Enable scissor test to clear only the specified area
|
|
8382
|
+
glContext.enable(glContext.SCISSOR_TEST);
|
|
8383
|
+
glContext.scissor(x, y, width, height);
|
|
8384
|
+
glContext.clearColor(0, 0, 0, 0);
|
|
8385
|
+
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
8386
|
+
glContext.disable(glContext.SCISSOR_TEST);
|
|
8387
|
+
}
|
|
8388
|
+
|
|
8389
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8390
|
+
|
|
8252
8391
|
// WebGL internal function to convert polygon to outline triangle strip
|
|
8253
8392
|
function glMakeOutline(points, width, wrap=true)
|
|
8254
8393
|
{
|
|
@@ -8382,23 +8521,20 @@ function glPolyStrip(points)
|
|
|
8382
8521
|
const a = points[i0], b = points[i1], c = points[i2];
|
|
8383
8522
|
|
|
8384
8523
|
// check if convex
|
|
8385
|
-
if (cross(a, b, c) < e)
|
|
8386
|
-
continue;
|
|
8524
|
+
if (cross(a, b, c) < e) continue;
|
|
8387
8525
|
|
|
8388
8526
|
// check if any other point is inside
|
|
8389
8527
|
let hasInside = false;
|
|
8390
8528
|
for (let j = 0; j < indices.length; j++)
|
|
8391
8529
|
{
|
|
8392
8530
|
const k = indices[j];
|
|
8393
|
-
if (k === i0 || k === i1 || k === i2)
|
|
8394
|
-
|
|
8531
|
+
if (k === i0 || k === i1 || k === i2) continue;
|
|
8532
|
+
|
|
8395
8533
|
const p = points[k];
|
|
8396
8534
|
hasInside = pointInTriangle(p, a, b, c);
|
|
8397
|
-
if (hasInside)
|
|
8398
|
-
break;
|
|
8535
|
+
if (hasInside) break;
|
|
8399
8536
|
}
|
|
8400
|
-
if (hasInside)
|
|
8401
|
-
continue;
|
|
8537
|
+
if (hasInside) continue;
|
|
8402
8538
|
|
|
8403
8539
|
// found valid ear
|
|
8404
8540
|
triangles.push([i0, i1, i2]);
|
|
@@ -8423,8 +8559,7 @@ function glPolyStrip(points)
|
|
|
8423
8559
|
worstIndex = i;
|
|
8424
8560
|
}
|
|
8425
8561
|
}
|
|
8426
|
-
if (worstIndex < 0)
|
|
8427
|
-
break;
|
|
8562
|
+
if (worstIndex < 0) break;
|
|
8428
8563
|
|
|
8429
8564
|
const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
|
|
8430
8565
|
const i1 = indices[worstIndex];
|
|
@@ -8655,14 +8790,16 @@ class PostProcessPlugin
|
|
|
8655
8790
|
{
|
|
8656
8791
|
/** Create global post processing shader
|
|
8657
8792
|
* @param {string} shaderCode
|
|
8658
|
-
* @param {boolean} [includeMainCanvas]
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8793
|
+
* @param {boolean} [includeMainCanvas] - combine mainCanvs onto glCanvas
|
|
8794
|
+
* @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
|
|
8795
|
+
* @example
|
|
8796
|
+
* // create the post process plugin object
|
|
8797
|
+
* new PostProcessPlugin(shaderCode);
|
|
8798
|
+
*/
|
|
8799
|
+
constructor(shaderCode, includeMainCanvas=false, feedbackTexture=false)
|
|
8664
8800
|
{
|
|
8665
8801
|
ASSERT(!postProcess, 'Post process already initialized');
|
|
8802
|
+
ASSERT(!(includeMainCanvas && feedbackTexture), 'Post process cannot both include main canvas and use feedback texture');
|
|
8666
8803
|
postProcess = this;
|
|
8667
8804
|
|
|
8668
8805
|
if (!shaderCode) // default shader pass through
|
|
@@ -8670,9 +8807,10 @@ class PostProcessPlugin
|
|
|
8670
8807
|
|
|
8671
8808
|
/** @property {WebGLProgram} - Shader for post processing */
|
|
8672
8809
|
this.shader = undefined;
|
|
8673
|
-
|
|
8674
8810
|
/** @property {WebGLTexture} - Texture for post processing */
|
|
8675
8811
|
this.texture = undefined;
|
|
8812
|
+
/** @property {WebGLVertexArrayObject} - Vertex array object */
|
|
8813
|
+
this.vao = undefined;
|
|
8676
8814
|
|
|
8677
8815
|
// setup the post processing plugin
|
|
8678
8816
|
initPostProcess();
|
|
@@ -8681,7 +8819,6 @@ class PostProcessPlugin
|
|
|
8681
8819
|
function initPostProcess()
|
|
8682
8820
|
{
|
|
8683
8821
|
if (headlessMode) return;
|
|
8684
|
-
|
|
8685
8822
|
if (!glEnable)
|
|
8686
8823
|
{
|
|
8687
8824
|
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
@@ -8692,14 +8829,14 @@ class PostProcessPlugin
|
|
|
8692
8829
|
postProcess.texture = glCreateTexture();
|
|
8693
8830
|
postProcess.shader = glCreateProgram(
|
|
8694
8831
|
'#version 300 es\n' + // specify GLSL ES version
|
|
8695
|
-
'precision highp float;'+ // use highp for
|
|
8832
|
+
'precision highp float;'+ // use highp for accuracy
|
|
8696
8833
|
'in vec2 p;'+ // position
|
|
8697
8834
|
'void main(){'+ // shader entry point
|
|
8698
8835
|
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
8699
8836
|
'}' // end of shader
|
|
8700
8837
|
,
|
|
8701
8838
|
'#version 300 es\n' + // specify GLSL ES version
|
|
8702
|
-
'precision highp float;'+ // use highp for
|
|
8839
|
+
'precision highp float;'+ // use highp for accuracy
|
|
8703
8840
|
'uniform sampler2D iChannel0;'+ // input texture
|
|
8704
8841
|
'uniform vec3 iResolution;'+ // size of output texture
|
|
8705
8842
|
'uniform float iTime;'+ // time
|
|
@@ -8710,6 +8847,17 @@ class PostProcessPlugin
|
|
|
8710
8847
|
'c.a=1.;'+ // always use full alpha
|
|
8711
8848
|
'}' // end of shader
|
|
8712
8849
|
);
|
|
8850
|
+
|
|
8851
|
+
// setup VAO for post processing
|
|
8852
|
+
postProcess.vao = glContext.createVertexArray();
|
|
8853
|
+
glContext.bindVertexArray(postProcess.vao);
|
|
8854
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
8855
|
+
|
|
8856
|
+
// configure vertex attributes
|
|
8857
|
+
const vertexByteStride = 8;
|
|
8858
|
+
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
8859
|
+
glContext.enableVertexAttribArray(pLocation);
|
|
8860
|
+
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
8713
8861
|
}
|
|
8714
8862
|
function postProcessContextLost()
|
|
8715
8863
|
{
|
|
@@ -8724,14 +8872,17 @@ class PostProcessPlugin
|
|
|
8724
8872
|
}
|
|
8725
8873
|
function postProcessRender()
|
|
8726
8874
|
{
|
|
8727
|
-
if (headlessMode) return;
|
|
8728
|
-
|
|
8729
|
-
if (!glEnable)
|
|
8730
|
-
return;
|
|
8875
|
+
if (headlessMode || !glEnable) return;
|
|
8731
8876
|
|
|
8732
8877
|
// clear out the buffer
|
|
8733
8878
|
glFlush();
|
|
8734
|
-
|
|
8879
|
+
|
|
8880
|
+
// setup shader program to draw a quad
|
|
8881
|
+
glContext.useProgram(postProcess.shader);
|
|
8882
|
+
glContext.bindVertexArray(postProcess.vao);
|
|
8883
|
+
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, true);
|
|
8884
|
+
glContext.disable(glContext.BLEND);
|
|
8885
|
+
|
|
8735
8886
|
// setup texture
|
|
8736
8887
|
glContext.activeTexture(glContext.TEXTURE0);
|
|
8737
8888
|
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
@@ -8742,29 +8893,32 @@ class PostProcessPlugin
|
|
|
8742
8893
|
workCanvas.height = mainCanvasSize.y;
|
|
8743
8894
|
glCopyToContext(workContext);
|
|
8744
8895
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
8896
|
+
mainCanvas.width |= 0
|
|
8745
8897
|
|
|
8746
8898
|
// copy work canvas to texture
|
|
8747
8899
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
|
|
8748
8900
|
}
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
// set vertex position attribute
|
|
8757
|
-
const vertexByteStride = 8;
|
|
8758
|
-
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
8759
|
-
glContext.enableVertexAttribArray(pLocation);
|
|
8760
|
-
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
8761
|
-
|
|
8901
|
+
else if (!feedbackTexture)
|
|
8902
|
+
{
|
|
8903
|
+
// copy glCanvas to texture
|
|
8904
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
|
|
8905
|
+
}
|
|
8906
|
+
|
|
8762
8907
|
// set uniforms and draw
|
|
8763
8908
|
const uniformLocation = (name)=>glContext.getUniformLocation(postProcess.shader, name);
|
|
8764
8909
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
8765
8910
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
8766
8911
|
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
8767
8912
|
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
|
|
8913
|
+
|
|
8914
|
+
if (feedbackTexture)
|
|
8915
|
+
{
|
|
8916
|
+
// pass glCanvas back to overlay texture
|
|
8917
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
|
|
8918
|
+
}
|
|
8919
|
+
|
|
8920
|
+
// force it to set instanced mode
|
|
8921
|
+
glSetInstancedMode(true);
|
|
8768
8922
|
}
|
|
8769
8923
|
}
|
|
8770
8924
|
}
|
|
@@ -8823,7 +8977,7 @@ class ZzFXMusic extends Sound
|
|
|
8823
8977
|
/** Play the music that loops by default
|
|
8824
8978
|
* @param {number} [volume] - Volume to play the music at
|
|
8825
8979
|
* @param {boolean} [loop] - Should the music loop?
|
|
8826
|
-
* @return {
|
|
8980
|
+
* @return {SoundInstance} - The sound instance
|
|
8827
8981
|
*/
|
|
8828
8982
|
playMusic(volume=1, loop=true)
|
|
8829
8983
|
{ return super.play(undefined, volume, 1, 0, loop); }
|
|
@@ -8870,7 +9024,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
8870
9024
|
sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
|
|
8871
9025
|
|
|
8872
9026
|
// for each pattern in sequence
|
|
8873
|
-
sequence.forEach((patternIndex, sequenceIndex)
|
|
9027
|
+
sequence.forEach((patternIndex, sequenceIndex)=> {
|
|
8874
9028
|
// get pattern for current channel, use empty 1 note pattern if none found
|
|
8875
9029
|
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
8876
9030
|
|
|
@@ -8963,7 +9117,7 @@ let uiDebug = 0;
|
|
|
8963
9117
|
|
|
8964
9118
|
/** Enable UI system debug drawing
|
|
8965
9119
|
* 0=off, 1=normal, 2=show invisible
|
|
8966
|
-
* @param {number|boolean}
|
|
9120
|
+
* @param {number|boolean} debugMode
|
|
8967
9121
|
* @memberof UISystem */
|
|
8968
9122
|
function uiSetDebug(debugMode)
|
|
8969
9123
|
{ uiDebug = typeof debugMode === 'boolean' ? (debugMode ? 1 : 0) : debugMode; }
|
|
@@ -9445,7 +9599,7 @@ class UISystemPlugin
|
|
|
9445
9599
|
const up = 'ArrowUp', down = 'ArrowDown', left = 'ArrowLeft', right = 'ArrowRight';
|
|
9446
9600
|
if (both)
|
|
9447
9601
|
{
|
|
9448
|
-
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
9602
|
+
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
9449
9603
|
keyIsDown(down) || keyIsDown(right) ? 1 : 0;
|
|
9450
9604
|
}
|
|
9451
9605
|
const back = vertical ? up : left;
|
|
@@ -9476,7 +9630,7 @@ class UISystemPlugin
|
|
|
9476
9630
|
* @return {boolean} */
|
|
9477
9631
|
getNavigationWasPressed()
|
|
9478
9632
|
{
|
|
9479
|
-
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
9633
|
+
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
9480
9634
|
keyWasPressed('Space') || keyWasPressed('Enter');
|
|
9481
9635
|
}
|
|
9482
9636
|
|
|
@@ -9524,7 +9678,7 @@ class UISystemPlugin
|
|
|
9524
9678
|
buttonYes.textHeight = 40;
|
|
9525
9679
|
buttonYes.navigationIndex = 1;
|
|
9526
9680
|
buttonYes.hoverColor = hsl(0,1,.5);
|
|
9527
|
-
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
9681
|
+
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
9528
9682
|
confirmMenu.addChild(buttonYes);
|
|
9529
9683
|
|
|
9530
9684
|
// no button
|
|
@@ -9554,8 +9708,8 @@ class UISystemPlugin
|
|
|
9554
9708
|
class UIObject
|
|
9555
9709
|
{
|
|
9556
9710
|
/** Create a UIObject
|
|
9557
|
-
* @param {Vector2} [pos=(
|
|
9558
|
-
* @param {Vector2} [size=(1
|
|
9711
|
+
* @param {Vector2} [pos=vec2()]
|
|
9712
|
+
* @param {Vector2} [size=vec2(1)]
|
|
9559
9713
|
*/
|
|
9560
9714
|
constructor(pos=vec2(), size=vec2())
|
|
9561
9715
|
{
|
|
@@ -9570,7 +9724,7 @@ class UIObject
|
|
|
9570
9724
|
this.size = size.copy();
|
|
9571
9725
|
/** @property {Color} - Color of the object */
|
|
9572
9726
|
this.color = uiSystem.defaultColor.copy();
|
|
9573
|
-
/** @property {Color} - Color of the object when active, uses
|
|
9727
|
+
/** @property {Color} - Color of the object when active, uses hoverColor if undefined */
|
|
9574
9728
|
this.activeColor = undefined;
|
|
9575
9729
|
/** @property {string} - Text for this ui object */
|
|
9576
9730
|
this.text = undefined;
|
|
@@ -9668,10 +9822,10 @@ class UIObject
|
|
|
9668
9822
|
|
|
9669
9823
|
// disconnect from parent and destroy children
|
|
9670
9824
|
this.destroyed = 1;
|
|
9671
|
-
this.parent
|
|
9825
|
+
this.parent?.removeChild(this);
|
|
9672
9826
|
for (const child of this.children)
|
|
9673
9827
|
{
|
|
9674
|
-
child.parent =
|
|
9828
|
+
child.parent = undefined;
|
|
9675
9829
|
child.destroy();
|
|
9676
9830
|
}
|
|
9677
9831
|
}
|
|
@@ -9767,10 +9921,10 @@ class UIObject
|
|
|
9767
9921
|
this.interactive && this.isActiveObject() && !this.disabled ?
|
|
9768
9922
|
this.color : this.lineColor;
|
|
9769
9923
|
const color = isNavigationObject ? this.hoverColor :
|
|
9770
|
-
this.disabled ? this.disabledColor :
|
|
9771
|
-
this.interactive ?
|
|
9772
|
-
this.
|
|
9773
|
-
this.
|
|
9924
|
+
this.disabled ? this.disabledColor :
|
|
9925
|
+
this.interactive ?
|
|
9926
|
+
this.isActiveObject() ? this.activeColor || this.hoverColor :
|
|
9927
|
+
this.isHoverObject() ? this.hoverColor :
|
|
9774
9928
|
this.color : this.color;
|
|
9775
9929
|
const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
|
|
9776
9930
|
|
|
@@ -9782,7 +9936,7 @@ class UIObject
|
|
|
9782
9936
|
getTextSize()
|
|
9783
9937
|
{
|
|
9784
9938
|
return vec2(
|
|
9785
|
-
this.textWidth || this.textFitScale * this.size.x,
|
|
9939
|
+
this.textWidth || this.textFitScale * this.size.x,
|
|
9786
9940
|
this.textHeight || this.textFitScale * this.size.y);
|
|
9787
9941
|
}
|
|
9788
9942
|
|
|
@@ -9830,9 +9984,9 @@ class UIObject
|
|
|
9830
9984
|
renderDebug(visible=true)
|
|
9831
9985
|
{
|
|
9832
9986
|
// apply color based on state
|
|
9833
|
-
const color =
|
|
9987
|
+
const color =
|
|
9834
9988
|
!visible ? GREEN :
|
|
9835
|
-
this.isHoverObject() ? YELLOW :
|
|
9989
|
+
this.isHoverObject() ? YELLOW :
|
|
9836
9990
|
this.disabled ? PURPLE :
|
|
9837
9991
|
this.interactive ? RED : BLUE;
|
|
9838
9992
|
uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
|
|
@@ -10164,8 +10318,8 @@ class UIScrollbar extends UIObject
|
|
|
10164
10318
|
class UIVideo extends UIObject
|
|
10165
10319
|
{
|
|
10166
10320
|
/** Create a video player UI object
|
|
10167
|
-
* @param {Vector2}
|
|
10168
|
-
* @param {Vector2}
|
|
10321
|
+
* @param {Vector2} pos
|
|
10322
|
+
* @param {Vector2} size
|
|
10169
10323
|
* @param {string} src - Video file path or URL
|
|
10170
10324
|
* @param {boolean} [autoplay=false] - Start playing immediately?
|
|
10171
10325
|
* @param {boolean} [loop=false] - Loop the video?
|
|
@@ -10305,6 +10459,7 @@ class UIVideo extends UIObject
|
|
|
10305
10459
|
* - Contact begin and end callbacks
|
|
10306
10460
|
* - Wraps b2Vec2 type to/from Vector2
|
|
10307
10461
|
* - Raycasting and querying
|
|
10462
|
+
* - Box2dTileLayer for grid based collision
|
|
10308
10463
|
* - Every type of joint
|
|
10309
10464
|
* - Debug physics drawing
|
|
10310
10465
|
* @namespace Box2D
|
|
@@ -10354,21 +10509,24 @@ class Box2dObject extends EngineObject
|
|
|
10354
10509
|
bodyDef.set_type(bodyType);
|
|
10355
10510
|
bodyDef.set_position(box2d.vec2dTo(pos));
|
|
10356
10511
|
bodyDef.set_angle(-angle);
|
|
10512
|
+
|
|
10513
|
+
/** @property {Object} - The Box2d body */
|
|
10357
10514
|
this.body = box2d.world.CreateBody(bodyDef);
|
|
10358
|
-
|
|
10515
|
+
/** @property {Color} - Line color used for default box2d drawing */
|
|
10359
10516
|
this.lineColor = BLACK;
|
|
10360
|
-
box2d
|
|
10361
|
-
|
|
10362
|
-
// edge lists and loops for drawing
|
|
10517
|
+
/** @property {Array<Object>} - List of all edges for default box2d drawing */
|
|
10363
10518
|
this.edgeLists = [];
|
|
10519
|
+
/** @property {Array<Object>} - List of all edge loops for default box2d drawing */
|
|
10364
10520
|
this.edgeLoops = [];
|
|
10521
|
+
|
|
10522
|
+
this.body.object = this; // link body to this object
|
|
10523
|
+
box2d.objects.push(this); // keep track of all box2d objects
|
|
10365
10524
|
}
|
|
10366
10525
|
|
|
10367
10526
|
/** Destroy this object and its physics body */
|
|
10368
10527
|
destroy()
|
|
10369
10528
|
{
|
|
10370
|
-
if (this.destroyed)
|
|
10371
|
-
return;
|
|
10529
|
+
if (this.destroyed) return;
|
|
10372
10530
|
|
|
10373
10531
|
// destroy physics body, fixtures, and joints
|
|
10374
10532
|
ASSERT(this.body, 'Box2dObject has no body to destroy');
|
|
@@ -10399,11 +10557,12 @@ class Box2dObject extends EngineObject
|
|
|
10399
10557
|
}
|
|
10400
10558
|
|
|
10401
10559
|
/** Draws all this object's fixtures
|
|
10402
|
-
* @param {Color}
|
|
10403
|
-
* @param {Color}
|
|
10404
|
-
* @param {number}
|
|
10560
|
+
* @param {Color} [color]
|
|
10561
|
+
* @param {Color} [lineColor]
|
|
10562
|
+
* @param {number} [lineWidth]
|
|
10563
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
10405
10564
|
* @param {CanvasRenderingContext2D} [context] */
|
|
10406
|
-
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
10565
|
+
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
|
|
10407
10566
|
{
|
|
10408
10567
|
// draw non-edge fixtures
|
|
10409
10568
|
this.getFixtureList().forEach((fixture)=>
|
|
@@ -10411,7 +10570,7 @@ class Box2dObject extends EngineObject
|
|
|
10411
10570
|
const shape = box2d.castObjectType(fixture.GetShape());
|
|
10412
10571
|
if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
|
|
10413
10572
|
{
|
|
10414
|
-
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, context);
|
|
10573
|
+
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, useWebGL, context);
|
|
10415
10574
|
}
|
|
10416
10575
|
});
|
|
10417
10576
|
|
|
@@ -10637,6 +10796,14 @@ class Box2dObject extends EngineObject
|
|
|
10637
10796
|
return fixtures;
|
|
10638
10797
|
}
|
|
10639
10798
|
|
|
10799
|
+
/** Destroy a fixture from the body
|
|
10800
|
+
* @param {Object} [fixture] */
|
|
10801
|
+
destroyFixture(fixture) { this.body.DestroyFixture(fixture); }
|
|
10802
|
+
|
|
10803
|
+
/** Destroy all fixture from the body */
|
|
10804
|
+
destroyAllFixtures()
|
|
10805
|
+
{ this.getFixtureList().forEach(fixture=>this.destroyFixture(fixture)); }
|
|
10806
|
+
|
|
10640
10807
|
///////////////////////////////////////////////////////////////////////////////
|
|
10641
10808
|
// physics get functions
|
|
10642
10809
|
|
|
@@ -10869,6 +11036,136 @@ class Box2dObject extends EngineObject
|
|
|
10869
11036
|
}
|
|
10870
11037
|
}
|
|
10871
11038
|
|
|
11039
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11040
|
+
/**
|
|
11041
|
+
* Box2D Static Object - Box2d with a static physics body
|
|
11042
|
+
* @extends Box2dObject
|
|
11043
|
+
* @memberof Box2D
|
|
11044
|
+
*/
|
|
11045
|
+
class Box2dStaticObject extends Box2dObject
|
|
11046
|
+
{
|
|
11047
|
+
/** Create a LittleJS object with Box2d physics
|
|
11048
|
+
* @param {Vector2} [pos]
|
|
11049
|
+
* @param {Vector2} [size]
|
|
11050
|
+
* @param {TileInfo} [tileInfo]
|
|
11051
|
+
* @param {number} [angle]
|
|
11052
|
+
* @param {Color} [color]
|
|
11053
|
+
* @param {number} [renderOrder] */
|
|
11054
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
11055
|
+
{
|
|
11056
|
+
const bodyType = box2d.bodyTypeStatic;
|
|
11057
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
11058
|
+
}
|
|
11059
|
+
}
|
|
11060
|
+
|
|
11061
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11062
|
+
/**
|
|
11063
|
+
* Box2D Kiematic Object - Box2d with a kinematic physics body
|
|
11064
|
+
* @extends Box2dObject
|
|
11065
|
+
* @memberof Box2D
|
|
11066
|
+
*/
|
|
11067
|
+
class Box2dKiematicObject extends Box2dObject
|
|
11068
|
+
{
|
|
11069
|
+
/** Create a LittleJS object with Box2d physics
|
|
11070
|
+
* @param {Vector2} [pos]
|
|
11071
|
+
* @param {Vector2} [size]
|
|
11072
|
+
* @param {TileInfo} [tileInfo]
|
|
11073
|
+
* @param {number} [angle]
|
|
11074
|
+
* @param {Color} [color]
|
|
11075
|
+
* @param {number} [renderOrder] */
|
|
11076
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
11077
|
+
{
|
|
11078
|
+
const bodyType = box2d.bodyTypeKinematic;
|
|
11079
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
11080
|
+
}
|
|
11081
|
+
}
|
|
11082
|
+
|
|
11083
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11084
|
+
/**
|
|
11085
|
+
* Box2d Tile Layer
|
|
11086
|
+
* - adds Box2d support to tile layers
|
|
11087
|
+
* - creates static box2d fixtures for solid tiles
|
|
11088
|
+
* @extends Box2dObject
|
|
11089
|
+
* @memberof Box2D
|
|
11090
|
+
*/
|
|
11091
|
+
class Box2dTileLayer extends Box2dStaticObject
|
|
11092
|
+
{
|
|
11093
|
+
/** Create a Box2d tile layer object
|
|
11094
|
+
* @param {TileCollisionLayer} tileLayer - Tile layer for this object */
|
|
11095
|
+
constructor(tileLayer)
|
|
11096
|
+
{
|
|
11097
|
+
ASSERT(tileLayer instanceof TileCollisionLayer, 'tileLayer must be a TileCollisionLayer');
|
|
11098
|
+
super(tileLayer.pos, tileLayer.size);
|
|
11099
|
+
|
|
11100
|
+
/** @property {TileLayer} - The tile layer */
|
|
11101
|
+
this.tileLayer = tileLayer;
|
|
11102
|
+
this.addChild(tileLayer);
|
|
11103
|
+
}
|
|
11104
|
+
|
|
11105
|
+
render()
|
|
11106
|
+
{
|
|
11107
|
+
// do not render fixtures, tile layer handles rendering
|
|
11108
|
+
}
|
|
11109
|
+
|
|
11110
|
+
/** Create box2d collision fixtures for solid tiles
|
|
11111
|
+
* @param {number} [friction]
|
|
11112
|
+
* @param {number} [restitution] */
|
|
11113
|
+
buildCollision(friction=.2, restitution=0)
|
|
11114
|
+
{
|
|
11115
|
+
// destroy all fixtures and create new ones
|
|
11116
|
+
this.destroyAllFixtures();
|
|
11117
|
+
|
|
11118
|
+
// create box2d object for this layer
|
|
11119
|
+
const size = this.tileLayer.size;
|
|
11120
|
+
this.pos = this.tileLayer.pos.copy();
|
|
11121
|
+
this.size = size.copy();
|
|
11122
|
+
|
|
11123
|
+
// track which tiles have been processed
|
|
11124
|
+
const processed = [];
|
|
11125
|
+
const getIndex = (x, y)=> x + y * size.x;
|
|
11126
|
+
const isSolidUnprocessed = (x, y)=>
|
|
11127
|
+
!processed[getIndex(x, y)] &&
|
|
11128
|
+
this.tileLayer.getCollisionData(vec2(x, y)) > 0;
|
|
11129
|
+
|
|
11130
|
+
// combine tiles into larger boxes
|
|
11131
|
+
for (let x = 0; x < size.x; ++x)
|
|
11132
|
+
for (let y = 0; y < size.y; ++y)
|
|
11133
|
+
{
|
|
11134
|
+
if (!isSolidUnprocessed(x, y)) continue;
|
|
11135
|
+
|
|
11136
|
+
// find max width by scanning right
|
|
11137
|
+
let width = 1, height = 1, canExpand = true;
|
|
11138
|
+
while (isSolidUnprocessed(x + width, y))
|
|
11139
|
+
++width;
|
|
11140
|
+
|
|
11141
|
+
// find max height by scanning up, ensuring all rows have the same width
|
|
11142
|
+
while (canExpand)
|
|
11143
|
+
{
|
|
11144
|
+
for (let checkX = 0; checkX < width; ++checkX)
|
|
11145
|
+
{
|
|
11146
|
+
if (!isSolidUnprocessed(x + checkX, y + height))
|
|
11147
|
+
{
|
|
11148
|
+
canExpand = false;
|
|
11149
|
+
break;
|
|
11150
|
+
}
|
|
11151
|
+
}
|
|
11152
|
+
if (canExpand)
|
|
11153
|
+
++height;
|
|
11154
|
+
}
|
|
11155
|
+
|
|
11156
|
+
// mark all tiles in this rectangle as processed
|
|
11157
|
+
for (let rectX = width; rectX--;)
|
|
11158
|
+
for (let rectY = height; rectY--;)
|
|
11159
|
+
processed[getIndex(x + rectX, y + rectY)] = true;
|
|
11160
|
+
|
|
11161
|
+
// create a single fixture for the entire rectangle
|
|
11162
|
+
const shapeSize = vec2(width, height);
|
|
11163
|
+
const offset = vec2(x + width/2, y + height/2);
|
|
11164
|
+
this.addBox(shapeSize, offset, 0, 0, friction, restitution);
|
|
11165
|
+
}
|
|
11166
|
+
}
|
|
11167
|
+
}
|
|
11168
|
+
|
|
10872
11169
|
///////////////////////////////////////////////////////////////////////////////
|
|
10873
11170
|
/**
|
|
10874
11171
|
* Box2D Raycast Result
|
|
@@ -10910,6 +11207,7 @@ class Box2dJoint
|
|
|
10910
11207
|
* @param {Object} jointDef */
|
|
10911
11208
|
constructor(jointDef)
|
|
10912
11209
|
{
|
|
11210
|
+
/** @property {Object} - The Box2d joint */
|
|
10913
11211
|
this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
|
|
10914
11212
|
}
|
|
10915
11213
|
|
|
@@ -11256,7 +11554,7 @@ class Box2dGearJoint extends Box2dJoint
|
|
|
11256
11554
|
* @param {Box2dObject} objectB
|
|
11257
11555
|
* @param {Box2dJoint} joint1
|
|
11258
11556
|
* @param {Box2dJoint} joint2
|
|
11259
|
-
* @param {
|
|
11557
|
+
* @param {number} [ratio] */
|
|
11260
11558
|
constructor(objectA, objectB, joint1, joint2, ratio=1)
|
|
11261
11559
|
{
|
|
11262
11560
|
const jointDef = new box2d.instance.b2GearJointDef();
|
|
@@ -11398,50 +11696,6 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
11398
11696
|
getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
|
|
11399
11697
|
}
|
|
11400
11698
|
|
|
11401
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
11402
|
-
/**
|
|
11403
|
-
* Box2D Static Object - Box2d with a static physics body
|
|
11404
|
-
* @extends Box2dObject
|
|
11405
|
-
* @memberof Box2D
|
|
11406
|
-
*/
|
|
11407
|
-
class Box2dStaticObject extends Box2dObject
|
|
11408
|
-
{
|
|
11409
|
-
/** Create a LittleJS object with Box2d physics
|
|
11410
|
-
* @param {Vector2} [pos]
|
|
11411
|
-
* @param {Vector2} [size]
|
|
11412
|
-
* @param {TileInfo} [tileInfo]
|
|
11413
|
-
* @param {number} [angle]
|
|
11414
|
-
* @param {Color} [color]
|
|
11415
|
-
* @param {number} [renderOrder] */
|
|
11416
|
-
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
11417
|
-
{
|
|
11418
|
-
const bodyType = box2d.bodyTypeStatic;
|
|
11419
|
-
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
11420
|
-
}
|
|
11421
|
-
}
|
|
11422
|
-
|
|
11423
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
11424
|
-
/**
|
|
11425
|
-
* Box2D Kiematic Object - Box2d with a kinematic physics body
|
|
11426
|
-
* @extends Box2dObject
|
|
11427
|
-
* @memberof Box2D
|
|
11428
|
-
*/
|
|
11429
|
-
class Box2dKiematicObject extends Box2dObject
|
|
11430
|
-
{
|
|
11431
|
-
/** Create a LittleJS object with Box2d physics
|
|
11432
|
-
* @param {Vector2} [pos]
|
|
11433
|
-
* @param {Vector2} [size]
|
|
11434
|
-
* @param {TileInfo} [tileInfo]
|
|
11435
|
-
* @param {number} [angle]
|
|
11436
|
-
* @param {Color} [color]
|
|
11437
|
-
* @param {number} [renderOrder] */
|
|
11438
|
-
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
11439
|
-
{
|
|
11440
|
-
const bodyType = box2d.bodyTypeKinematic;
|
|
11441
|
-
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
11442
|
-
}
|
|
11443
|
-
}
|
|
11444
|
-
|
|
11445
11699
|
///////////////////////////////////////////////////////////////////////////////
|
|
11446
11700
|
/**
|
|
11447
11701
|
* Box2D Wheel Joint
|
|
@@ -11802,10 +12056,13 @@ class Box2dPlugin
|
|
|
11802
12056
|
{
|
|
11803
12057
|
ASSERT(!box2d, 'Box2D already initialized');
|
|
11804
12058
|
box2d = this;
|
|
12059
|
+
|
|
12060
|
+
/** @property {Object} - The Box2d instance */
|
|
11805
12061
|
this.instance = instance;
|
|
12062
|
+
/** @property {Object} - The Box2d world */
|
|
11806
12063
|
this.world = new box2d.instance.b2World();
|
|
12064
|
+
/** @property {Array<Box2dObject>} - List of all Box2d objects */
|
|
11807
12065
|
this.objects = [];
|
|
11808
|
-
|
|
11809
12066
|
/** @property {number} - Velocity iterations per update*/
|
|
11810
12067
|
this.velocityIterations = 8;
|
|
11811
12068
|
/** @property {number} - Position iterations per update*/
|
|
@@ -12004,8 +12261,9 @@ class Box2dPlugin
|
|
|
12004
12261
|
* @param {Color} [color]
|
|
12005
12262
|
* @param {Color} [lineColor]
|
|
12006
12263
|
* @param {number} [lineWidth]
|
|
12264
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
12007
12265
|
* @param {CanvasRenderingContext2D} [context] */
|
|
12008
|
-
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
12266
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebgl, context)
|
|
12009
12267
|
{
|
|
12010
12268
|
const shape = box2d.castObjectType(fixture.GetShape());
|
|
12011
12269
|
switch (shape.GetType())
|
|
@@ -12015,20 +12273,20 @@ class Box2dPlugin
|
|
|
12015
12273
|
let points = [];
|
|
12016
12274
|
for (let i=shape.GetVertexCount(); i--;)
|
|
12017
12275
|
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
12018
|
-
drawPoly(points, color, lineWidth, lineColor, pos, angle);
|
|
12276
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebgl, false, context);
|
|
12019
12277
|
break;
|
|
12020
12278
|
}
|
|
12021
12279
|
case box2d.instance.b2Shape.e_circle:
|
|
12022
12280
|
{
|
|
12023
12281
|
const radius = shape.get_m_radius();
|
|
12024
|
-
drawCircle(pos, radius*2, color, lineWidth, lineColor);
|
|
12282
|
+
drawCircle(pos, radius*2, color, lineWidth, lineColor, useWebgl, false, context);
|
|
12025
12283
|
break;
|
|
12026
12284
|
}
|
|
12027
12285
|
case box2d.instance.b2Shape.e_edge:
|
|
12028
12286
|
{
|
|
12029
12287
|
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
12030
12288
|
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
12031
|
-
drawLine(v1, v2, lineWidth, lineColor, pos, angle);
|
|
12289
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebgl, false, context);
|
|
12032
12290
|
break;
|
|
12033
12291
|
}
|
|
12034
12292
|
}
|
|
@@ -12046,7 +12304,7 @@ class Box2dPlugin
|
|
|
12046
12304
|
}
|
|
12047
12305
|
|
|
12048
12306
|
/** converts a box2d vec2 pointer to a Vector2
|
|
12049
|
-
* @param {Object}
|
|
12307
|
+
* @param {Object} vp */
|
|
12050
12308
|
vec2FromPointer(vp)
|
|
12051
12309
|
{
|
|
12052
12310
|
const v = box2d.instance.wrapPointer(vp, box2d.instance.b2Vec2);
|
|
@@ -12158,7 +12416,7 @@ async function box2dInit()
|
|
|
12158
12416
|
const box2dColorPointer = (c)=>
|
|
12159
12417
|
box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
|
|
12160
12418
|
const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
|
|
12161
|
-
const getPointsList = (vertices, vertexCount)
|
|
12419
|
+
const getPointsList = (vertices, vertexCount)=>
|
|
12162
12420
|
{
|
|
12163
12421
|
const points = [];
|
|
12164
12422
|
for (let i=vertexCount; i--;)
|
|
@@ -12354,357 +12612,348 @@ function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor
|
|
|
12354
12612
|
|
|
12355
12613
|
export
|
|
12356
12614
|
{
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12400
|
-
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
|
|
12588
|
-
|
|
12589
|
-
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
|
|
12618
|
-
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
12636
|
-
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
|
|
12661
|
-
|
|
12662
|
-
|
|
12663
|
-
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
|
|
12687
|
-
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
// Particles
|
|
12700
|
-
ParticleEmitter,
|
|
12701
|
-
Particle,
|
|
12702
|
-
|
|
12703
|
-
// Medals
|
|
12704
|
-
medals,
|
|
12705
|
-
medalsPreventUnlock,
|
|
12706
|
-
medalsInit,
|
|
12707
|
-
Medal,
|
|
12615
|
+
// Engine
|
|
12616
|
+
engineName,
|
|
12617
|
+
engineVersion,
|
|
12618
|
+
frameRate,
|
|
12619
|
+
timeDelta,
|
|
12620
|
+
engineObjects,
|
|
12621
|
+
frame,
|
|
12622
|
+
time,
|
|
12623
|
+
timeReal,
|
|
12624
|
+
paused,
|
|
12625
|
+
getPaused,
|
|
12626
|
+
setPaused,
|
|
12627
|
+
engineInit,
|
|
12628
|
+
engineObjectsUpdate,
|
|
12629
|
+
engineObjectsDestroy,
|
|
12630
|
+
engineObjectsCollect,
|
|
12631
|
+
engineObjectsCallback,
|
|
12632
|
+
engineObjectsRaycast,
|
|
12633
|
+
engineAddPlugin,
|
|
12634
|
+
|
|
12635
|
+
// Globals
|
|
12636
|
+
debug,
|
|
12637
|
+
debugOverlay,
|
|
12638
|
+
debugWatermark,
|
|
12639
|
+
|
|
12640
|
+
// Debug
|
|
12641
|
+
ASSERT,
|
|
12642
|
+
LOG,
|
|
12643
|
+
debugRect,
|
|
12644
|
+
debugPoly,
|
|
12645
|
+
debugCircle,
|
|
12646
|
+
debugPoint,
|
|
12647
|
+
debugLine,
|
|
12648
|
+
debugOverlap,
|
|
12649
|
+
debugText,
|
|
12650
|
+
debugClear,
|
|
12651
|
+
debugScreenshot,
|
|
12652
|
+
debugShowErrors,
|
|
12653
|
+
debugVideoCaptureStart,
|
|
12654
|
+
debugVideoCaptureStop,
|
|
12655
|
+
debugVideoCaptureIsActive,
|
|
12656
|
+
|
|
12657
|
+
// Settings
|
|
12658
|
+
cameraPos,
|
|
12659
|
+
cameraAngle,
|
|
12660
|
+
cameraScale,
|
|
12661
|
+
canvasColorTiles,
|
|
12662
|
+
canvasClearColor,
|
|
12663
|
+
canvasMaxSize,
|
|
12664
|
+
canvasMinAspect,
|
|
12665
|
+
canvasMaxAspect,
|
|
12666
|
+
canvasFixedSize,
|
|
12667
|
+
canvasPixelated,
|
|
12668
|
+
tilesPixelated,
|
|
12669
|
+
fontDefault,
|
|
12670
|
+
showSplashScreen,
|
|
12671
|
+
headlessMode,
|
|
12672
|
+
tileDefaultSize,
|
|
12673
|
+
tileDefaultPadding,
|
|
12674
|
+
tileDefaultBleed,
|
|
12675
|
+
enablePhysicsSolver,
|
|
12676
|
+
objectDefaultMass,
|
|
12677
|
+
objectDefaultDamping,
|
|
12678
|
+
objectDefaultAngleDamping,
|
|
12679
|
+
objectDefaultRestitution,
|
|
12680
|
+
objectDefaultFriction,
|
|
12681
|
+
objectMaxSpeed,
|
|
12682
|
+
gravity,
|
|
12683
|
+
particleEmitRateScale,
|
|
12684
|
+
glEnable,
|
|
12685
|
+
gamepadsEnable,
|
|
12686
|
+
gamepadDirectionEmulateStick,
|
|
12687
|
+
inputWASDEmulateDirection,
|
|
12688
|
+
touchGamepadEnable,
|
|
12689
|
+
touchGamepadCenterButton,
|
|
12690
|
+
touchGamepadAnalog,
|
|
12691
|
+
touchGamepadSize,
|
|
12692
|
+
touchGamepadAlpha,
|
|
12693
|
+
vibrateEnable,
|
|
12694
|
+
soundEnable,
|
|
12695
|
+
soundVolume,
|
|
12696
|
+
soundDefaultRange,
|
|
12697
|
+
soundDefaultTaper,
|
|
12698
|
+
medalDisplayTime,
|
|
12699
|
+
medalDisplaySlideTime,
|
|
12700
|
+
medalDisplaySize,
|
|
12701
|
+
|
|
12702
|
+
// Setters for globals
|
|
12703
|
+
setCameraPos,
|
|
12704
|
+
setCameraAngle,
|
|
12705
|
+
setCameraScale,
|
|
12706
|
+
setCanvasColorTiles,
|
|
12707
|
+
setCanvasClearColor,
|
|
12708
|
+
setCanvasMaxSize,
|
|
12709
|
+
setCanvasMinAspect,
|
|
12710
|
+
setCanvasMaxAspect,
|
|
12711
|
+
setCanvasFixedSize,
|
|
12712
|
+
setCanvasPixelated,
|
|
12713
|
+
setTilesPixelated,
|
|
12714
|
+
setFontDefault,
|
|
12715
|
+
setShowSplashScreen,
|
|
12716
|
+
setHeadlessMode,
|
|
12717
|
+
setGLEnable,
|
|
12718
|
+
setTileDefaultSize,
|
|
12719
|
+
setTileDefaultPadding,
|
|
12720
|
+
setTileDefaultBleed,
|
|
12721
|
+
setEnablePhysicsSolver,
|
|
12722
|
+
setObjectDefaultMass,
|
|
12723
|
+
setObjectDefaultDamping,
|
|
12724
|
+
setObjectDefaultAngleDamping,
|
|
12725
|
+
setObjectDefaultRestitution,
|
|
12726
|
+
setObjectDefaultFriction,
|
|
12727
|
+
setObjectMaxSpeed,
|
|
12728
|
+
setGravity,
|
|
12729
|
+
setParticleEmitRateScale,
|
|
12730
|
+
setTouchInputEnable,
|
|
12731
|
+
setGamepadsEnable,
|
|
12732
|
+
setGamepadDirectionEmulateStick,
|
|
12733
|
+
setInputWASDEmulateDirection,
|
|
12734
|
+
setTouchGamepadEnable,
|
|
12735
|
+
setTouchGamepadCenterButton,
|
|
12736
|
+
setTouchGamepadButtonCount,
|
|
12737
|
+
setTouchGamepadAnalog,
|
|
12738
|
+
setTouchGamepadSize,
|
|
12739
|
+
setTouchGamepadAlpha,
|
|
12740
|
+
setVibrateEnable,
|
|
12741
|
+
setSoundEnable,
|
|
12742
|
+
setSoundVolume,
|
|
12743
|
+
setSoundDefaultRange,
|
|
12744
|
+
setSoundDefaultTaper,
|
|
12745
|
+
setMedalDisplayTime,
|
|
12746
|
+
setMedalDisplaySlideTime,
|
|
12747
|
+
setMedalDisplaySize,
|
|
12748
|
+
setMedalsPreventUnlock,
|
|
12749
|
+
setDebugWatermark,
|
|
12750
|
+
setDebugKey,
|
|
12751
|
+
|
|
12752
|
+
// Math
|
|
12753
|
+
PI,
|
|
12754
|
+
abs,
|
|
12755
|
+
floor,
|
|
12756
|
+
ceil,
|
|
12757
|
+
round,
|
|
12758
|
+
min,
|
|
12759
|
+
max,
|
|
12760
|
+
sign,
|
|
12761
|
+
hypot,
|
|
12762
|
+
log2,
|
|
12763
|
+
sin,
|
|
12764
|
+
cos,
|
|
12765
|
+
tan,
|
|
12766
|
+
atan2,
|
|
12767
|
+
mod,
|
|
12768
|
+
clamp,
|
|
12769
|
+
percent,
|
|
12770
|
+
distanceWrap,
|
|
12771
|
+
lerpWrap,
|
|
12772
|
+
distanceAngle,
|
|
12773
|
+
lerpAngle,
|
|
12774
|
+
lerp,
|
|
12775
|
+
smoothStep,
|
|
12776
|
+
nearestPowerOfTwo,
|
|
12777
|
+
isOverlapping,
|
|
12778
|
+
isIntersecting,
|
|
12779
|
+
wave,
|
|
12780
|
+
|
|
12781
|
+
// Utilities
|
|
12782
|
+
formatTime,
|
|
12783
|
+
fetchJSON,
|
|
12784
|
+
saveText,
|
|
12785
|
+
saveCanvas,
|
|
12786
|
+
saveDataURL,
|
|
12787
|
+
shareURL,
|
|
12788
|
+
|
|
12789
|
+
// Random
|
|
12790
|
+
rand,
|
|
12791
|
+
randInt,
|
|
12792
|
+
randBool,
|
|
12793
|
+
randSign,
|
|
12794
|
+
randInCircle,
|
|
12795
|
+
randVec2,
|
|
12796
|
+
randColor,
|
|
12797
|
+
|
|
12798
|
+
// Utility Classes
|
|
12799
|
+
RandomGenerator,
|
|
12800
|
+
Vector2,
|
|
12801
|
+
Color,
|
|
12802
|
+
Timer,
|
|
12803
|
+
vec2,
|
|
12804
|
+
rgb,
|
|
12805
|
+
hsl,
|
|
12806
|
+
isColor,
|
|
12807
|
+
isVector2,
|
|
12808
|
+
isNumber,
|
|
12809
|
+
isString,
|
|
12810
|
+
isArray,
|
|
12811
|
+
|
|
12812
|
+
// Default Colors
|
|
12813
|
+
WHITE,
|
|
12814
|
+
CLEAR_WHITE,
|
|
12815
|
+
BLACK,
|
|
12816
|
+
CLEAR_BLACK,
|
|
12817
|
+
GRAY,
|
|
12818
|
+
RED,
|
|
12819
|
+
ORANGE,
|
|
12820
|
+
YELLOW,
|
|
12821
|
+
GREEN,
|
|
12822
|
+
CYAN,
|
|
12823
|
+
BLUE,
|
|
12824
|
+
PURPLE,
|
|
12825
|
+
MAGENTA,
|
|
12826
|
+
|
|
12827
|
+
// Draw
|
|
12828
|
+
tile,
|
|
12829
|
+
TileInfo,
|
|
12830
|
+
TextureInfo,
|
|
12831
|
+
mainCanvas,
|
|
12832
|
+
mainContext,
|
|
12833
|
+
drawContext,
|
|
12834
|
+
workCanvas,
|
|
12835
|
+
workContext,
|
|
12836
|
+
workReadCanvas,
|
|
12837
|
+
workReadContext,
|
|
12838
|
+
mainCanvasSize,
|
|
12839
|
+
textureInfos,
|
|
12840
|
+
drawCount,
|
|
12841
|
+
screenToWorld,
|
|
12842
|
+
worldToScreen,
|
|
12843
|
+
screenToWorldDelta,
|
|
12844
|
+
worldToScreenDelta,
|
|
12845
|
+
screenToWorldTransform,
|
|
12846
|
+
drawTile,
|
|
12847
|
+
drawRect,
|
|
12848
|
+
drawRectGradient,
|
|
12849
|
+
drawLineList,
|
|
12850
|
+
drawLine,
|
|
12851
|
+
drawPoly,
|
|
12852
|
+
drawEllipse,
|
|
12853
|
+
drawCircle,
|
|
12854
|
+
drawCanvas2D,
|
|
12855
|
+
drawText,
|
|
12856
|
+
drawTextScreen,
|
|
12857
|
+
setBlendMode,
|
|
12858
|
+
combineCanvases,
|
|
12859
|
+
engineFontImage,
|
|
12860
|
+
FontImage,
|
|
12861
|
+
isFullscreen,
|
|
12862
|
+
toggleFullscreen,
|
|
12863
|
+
setCursor,
|
|
12864
|
+
getCameraSize,
|
|
12865
|
+
|
|
12866
|
+
// WebGL
|
|
12867
|
+
glCanvas,
|
|
12868
|
+
glContext,
|
|
12869
|
+
glAntialias,
|
|
12870
|
+
glClearCanvas,
|
|
12871
|
+
glSetTexture,
|
|
12872
|
+
glCompileShader,
|
|
12873
|
+
glCreateProgram,
|
|
12874
|
+
glCreateTexture,
|
|
12875
|
+
glDeleteTexture,
|
|
12876
|
+
glSetTextureData,
|
|
12877
|
+
glFlush,
|
|
12878
|
+
glCopyToContext,
|
|
12879
|
+
glSetAntialias,
|
|
12880
|
+
glDraw,
|
|
12881
|
+
glDrawPointsTransform,
|
|
12882
|
+
glDrawOutlineTransform,
|
|
12883
|
+
glDrawPoints,
|
|
12884
|
+
glDrawColoredPoints,
|
|
12885
|
+
glSetRenderTarget,
|
|
12886
|
+
glClearRect,
|
|
12887
|
+
|
|
12888
|
+
// Input
|
|
12889
|
+
keyIsDown,
|
|
12890
|
+
keyWasPressed,
|
|
12891
|
+
keyWasReleased,
|
|
12892
|
+
keyDirection,
|
|
12893
|
+
inputClear,
|
|
12894
|
+
inputClearKey,
|
|
12895
|
+
mouseIsDown,
|
|
12896
|
+
mouseWasPressed,
|
|
12897
|
+
mouseWasReleased,
|
|
12898
|
+
mousePos,
|
|
12899
|
+
mousePosScreen,
|
|
12900
|
+
mouseDelta,
|
|
12901
|
+
mouseDeltaScreen,
|
|
12902
|
+
mouseWheel,
|
|
12903
|
+
mouseInWindow,
|
|
12904
|
+
isUsingGamepad,
|
|
12905
|
+
inputPreventDefault,
|
|
12906
|
+
gamepadPrimary,
|
|
12907
|
+
isTouchDevice,
|
|
12908
|
+
setInputPreventDefault,
|
|
12909
|
+
gamepadIsDown,
|
|
12910
|
+
gamepadWasPressed,
|
|
12911
|
+
gamepadWasReleased,
|
|
12912
|
+
gamepadStick,
|
|
12913
|
+
gamepadDpad,
|
|
12914
|
+
gamepadConnected,
|
|
12915
|
+
vibrate,
|
|
12916
|
+
vibrateStop,
|
|
12917
|
+
pointerLockRequest,
|
|
12918
|
+
pointerLockExit,
|
|
12919
|
+
pointerLockIsActive,
|
|
12920
|
+
|
|
12921
|
+
// Audio
|
|
12922
|
+
audioContext,
|
|
12923
|
+
audioMasterGain,
|
|
12924
|
+
audioDefaultSampleRate,
|
|
12925
|
+
Sound,
|
|
12926
|
+
SoundInstance,
|
|
12927
|
+
speak,
|
|
12928
|
+
speakStop,
|
|
12929
|
+
getNoteFrequency,
|
|
12930
|
+
playSamples,
|
|
12931
|
+
zzfx,
|
|
12932
|
+
zzfxG,
|
|
12933
|
+
|
|
12934
|
+
// Base Object
|
|
12935
|
+
EngineObject,
|
|
12936
|
+
|
|
12937
|
+
// Tiles
|
|
12938
|
+
tileCollisionLayers,
|
|
12939
|
+
tileCollisionGetData,
|
|
12940
|
+
tileCollisionTest,
|
|
12941
|
+
tileCollisionRaycast,
|
|
12942
|
+
tileLayersLoad,
|
|
12943
|
+
TileLayerData,
|
|
12944
|
+
CanvasLayer,
|
|
12945
|
+
TileLayer,
|
|
12946
|
+
TileCollisionLayer,
|
|
12947
|
+
|
|
12948
|
+
// Particles
|
|
12949
|
+
ParticleEmitter,
|
|
12950
|
+
Particle,
|
|
12951
|
+
|
|
12952
|
+
// Medals
|
|
12953
|
+
medals,
|
|
12954
|
+
medalsPreventUnlock,
|
|
12955
|
+
medalsInit,
|
|
12956
|
+
Medal,
|
|
12708
12957
|
}
|
|
12709
12958
|
/**
|
|
12710
12959
|
* LittleJS Module Plugins Export
|