littlejsengine 1.16.2 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/littlejs.d.ts +200 -166
- package/dist/littlejs.esm.js +1231 -1086
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +888 -735
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +884 -731
- package/examples/box2d/gameObjects.js +1 -1
- 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 +1 -1
- package/examples/logo.png +0 -0
- package/examples/module/tiles.png +0 -0
- package/examples/platformer/gameEffects.js +23 -22
- package/examples/platformer/gameLevel.js +24 -25
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/clock.js +3 -3
- package/examples/shorts/fontImage.js +4 -3
- 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 +1 -1
- package/plugins/postProcess.js +47 -28
- package/plugins/uiSystem.js +15 -15
- package/reference.md +1 -1
- package/src/engine.js +174 -174
- package/src/engineAudio.js +4 -6
- package/src/engineDebug.js +4 -4
- package/src/engineDraw.js +179 -122
- package/src/engineExport.js +326 -334
- package/src/engineFont.png +0 -0
- package/src/engineInput.js +14 -20
- package/src/engineMath.js +15 -104
- package/src/engineObject.js +26 -25
- package/src/engineParticles.js +2 -2
- package/src/engineTileLayer.js +196 -170
- package/src/engineUtilities.js +100 -4
- package/src/engineWebGL.js +123 -72
package/dist/littlejs.release.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.
|
|
36
|
+
const engineVersion = '1.17.1';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -320,7 +320,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
320
320
|
|
|
321
321
|
// responsive aspect ratio with native resolution
|
|
322
322
|
const innerAspect = innerWidth / innerHeight;
|
|
323
|
-
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
323
|
+
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
324
324
|
if (canvasMaxAspect && innerAspect > canvasMaxAspect)
|
|
325
325
|
{
|
|
326
326
|
// full height
|
|
@@ -377,7 +377,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
377
377
|
debugInit();
|
|
378
378
|
|
|
379
379
|
// setup canvases
|
|
380
|
-
// transform way is still more reliable
|
|
380
|
+
// transform way is still more reliable than flexbox or grid
|
|
381
381
|
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
382
382
|
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
383
383
|
mainCanvas.style.cssText = styleCanvas;
|
|
@@ -422,6 +422,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
422
422
|
}));
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
+
// load engine font image
|
|
426
|
+
promises.push(fontImageInit());
|
|
427
|
+
|
|
425
428
|
if (showSplashScreen)
|
|
426
429
|
{
|
|
427
430
|
// draw splash screen
|
|
@@ -433,7 +436,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
433
436
|
function updateSplash()
|
|
434
437
|
{
|
|
435
438
|
inputClear();
|
|
436
|
-
|
|
439
|
+
drawEngineLogo(t+=.01);
|
|
437
440
|
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
438
441
|
}
|
|
439
442
|
}));
|
|
@@ -449,172 +452,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
449
452
|
await gameInit();
|
|
450
453
|
engineUpdate();
|
|
451
454
|
}
|
|
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
455
|
}
|
|
619
456
|
|
|
620
457
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -628,8 +465,7 @@ function engineObjectsUpdate()
|
|
|
628
465
|
// recursive object update
|
|
629
466
|
function updateObject(o)
|
|
630
467
|
{
|
|
631
|
-
if (o.destroyed)
|
|
632
|
-
return;
|
|
468
|
+
if (o.destroyed) return;
|
|
633
469
|
|
|
634
470
|
o.update();
|
|
635
471
|
for (const child of o.children)
|
|
@@ -637,8 +473,7 @@ function engineObjectsUpdate()
|
|
|
637
473
|
}
|
|
638
474
|
for (const o of engineObjects)
|
|
639
475
|
{
|
|
640
|
-
if (o.parent)
|
|
641
|
-
continue;
|
|
476
|
+
if (o.parent || o.destroyed) continue;
|
|
642
477
|
|
|
643
478
|
// update top level objects
|
|
644
479
|
o.update();
|
|
@@ -727,6 +562,171 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
727
562
|
|
|
728
563
|
debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
|
|
729
564
|
return hitObjects;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
568
|
+
function drawEngineLogo(t)
|
|
569
|
+
{
|
|
570
|
+
// LittleJS Logo and Splash Screen
|
|
571
|
+
const x = mainContext;
|
|
572
|
+
const w = mainCanvas.width = innerWidth;
|
|
573
|
+
const h = mainCanvas.height = innerHeight;
|
|
574
|
+
|
|
575
|
+
{
|
|
576
|
+
// background
|
|
577
|
+
const p3 = percent(t, 1, .8);
|
|
578
|
+
const p4 = percent(t, 0, .5);
|
|
579
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
580
|
+
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
581
|
+
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
582
|
+
x.save();
|
|
583
|
+
x.fillStyle = g;
|
|
584
|
+
x.fillRect(0,0,w,h);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// draw LittleJS logo...
|
|
588
|
+
const rect = (X, Y, W, H, C)=>
|
|
589
|
+
{
|
|
590
|
+
x.beginPath();
|
|
591
|
+
x.rect(X,Y,W,C?H*p:H);
|
|
592
|
+
x.fillStyle = C;
|
|
593
|
+
C ? x.fill() : x.stroke();
|
|
594
|
+
};
|
|
595
|
+
const line = (X, Y, Z, W)=>
|
|
596
|
+
{
|
|
597
|
+
x.beginPath();
|
|
598
|
+
x.lineTo(X,Y);
|
|
599
|
+
x.lineTo(Z,W);
|
|
600
|
+
x.stroke();
|
|
601
|
+
};
|
|
602
|
+
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
603
|
+
{
|
|
604
|
+
const D = (A+B)/2, E = p*(B-A)/2;
|
|
605
|
+
x.beginPath();
|
|
606
|
+
F && x.lineTo(X,Y);
|
|
607
|
+
x.arc(X,Y,R,D-E,D+E);
|
|
608
|
+
x.fillStyle = C;
|
|
609
|
+
C ? x.fill() : x.stroke();
|
|
610
|
+
};
|
|
611
|
+
const color = (c=0, l=0) =>
|
|
612
|
+
hsl([.98,.3,.57,.14][c%4],.9,[0,.3,.5,.8,.9][l]).toString();
|
|
613
|
+
const alpha = wave(1,1,t);
|
|
614
|
+
const p = percent(alpha, .1, .5);
|
|
615
|
+
|
|
616
|
+
// setup
|
|
617
|
+
x.translate(w/2,h/2);
|
|
618
|
+
const size = min(6, min(w,h)/99); // fit to screen
|
|
619
|
+
x.scale(size,size);
|
|
620
|
+
x.translate(-40,-35);
|
|
621
|
+
x.lineJoin = x.lineCap = 'round';
|
|
622
|
+
x.lineWidth = .1 + p*1.9;
|
|
623
|
+
|
|
624
|
+
// drawing effect
|
|
625
|
+
const p2 = percent(alpha,.1,1);
|
|
626
|
+
x.setLineDash([99*p2,99]);
|
|
627
|
+
|
|
628
|
+
// cab top
|
|
629
|
+
rect(7,16,18,-8,color(2,2));
|
|
630
|
+
rect(7,8,18,4,color(2,3));
|
|
631
|
+
rect(25,8,8,8,color(2,1));
|
|
632
|
+
rect(25,8,-18,8);
|
|
633
|
+
rect(25,8,8,8);
|
|
634
|
+
|
|
635
|
+
// cab
|
|
636
|
+
rect(25,16,7,24,color());
|
|
637
|
+
rect(11,39,14,-23,color(1,1));
|
|
638
|
+
rect(11,16,14,18,color(1,2));
|
|
639
|
+
rect(11,16,14,8,color(1,3));
|
|
640
|
+
rect(25,16,-14,24);
|
|
641
|
+
|
|
642
|
+
// cab window
|
|
643
|
+
rect(15,29,6,-9,color(2,2));
|
|
644
|
+
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
645
|
+
rect(21,21,-6,9);
|
|
646
|
+
|
|
647
|
+
// little stack
|
|
648
|
+
rect(37,14,9,6,color(3,2));
|
|
649
|
+
rect(37,14,4.5,6,color(3,3));
|
|
650
|
+
rect(37,14,9,6);
|
|
651
|
+
|
|
652
|
+
// big stack
|
|
653
|
+
rect(50,20,10,-8,color(0,1));
|
|
654
|
+
rect(50,20,6.5,-8,color(0,2));
|
|
655
|
+
rect(50,20,3.5,-8,color(0,3));
|
|
656
|
+
rect(50,20,10,-8);
|
|
657
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
658
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
659
|
+
circle(55,2,11.4,.5,PI-.5);
|
|
660
|
+
rect(45,7,20,-7,color(0,2));
|
|
661
|
+
rect(45,-1,20,4,color(0,3));
|
|
662
|
+
rect(45,-1,20,8);
|
|
663
|
+
|
|
664
|
+
// engine
|
|
665
|
+
for (let i=5; i--;)
|
|
666
|
+
{
|
|
667
|
+
// stagger radius to fix slight seam
|
|
668
|
+
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
669
|
+
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
670
|
+
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// engine outline
|
|
674
|
+
circle(36,30,10,PI/2,PI*3/2);
|
|
675
|
+
circle(48,30,10,PI/2,PI*3/2);
|
|
676
|
+
circle(60,30,10);
|
|
677
|
+
line(36,20,60,20);
|
|
678
|
+
|
|
679
|
+
// engine front light
|
|
680
|
+
circle(60,30,4,PI,3*PI,color(3,2));
|
|
681
|
+
circle(60,30,4,PI,2*PI,color(3,3));
|
|
682
|
+
circle(60,30,4,PI,3*PI);
|
|
683
|
+
|
|
684
|
+
// front brush
|
|
685
|
+
for (let i=6; i--;)
|
|
686
|
+
{
|
|
687
|
+
x.beginPath();
|
|
688
|
+
x.lineTo(53,54);
|
|
689
|
+
x.lineTo(53,40);
|
|
690
|
+
x.lineTo(53+(1+i*2.9)*p,40);
|
|
691
|
+
x.lineTo(53+(4+i*3.5)*p,54);
|
|
692
|
+
x.fillStyle = color(0,i%2+2);
|
|
693
|
+
x.fill();
|
|
694
|
+
i%2 && x.stroke();
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// wheels
|
|
698
|
+
rect(6,40,5,5);
|
|
699
|
+
rect(6,40,5,5,color());
|
|
700
|
+
rect(15,54,38,-14,color());
|
|
701
|
+
for (let i=3; i--;)
|
|
702
|
+
for (let j=2; j--;)
|
|
703
|
+
{
|
|
704
|
+
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
705
|
+
x.stroke();
|
|
706
|
+
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
707
|
+
x.stroke();
|
|
708
|
+
}
|
|
709
|
+
line(6,40,68,40); // center
|
|
710
|
+
line(77,54,4,54); // bottom
|
|
711
|
+
|
|
712
|
+
// draw engine name
|
|
713
|
+
const s = engineName;
|
|
714
|
+
x.font = '900 16px arial';
|
|
715
|
+
x.textAlign = 'center';
|
|
716
|
+
x.textBaseline = 'top';
|
|
717
|
+
x.lineWidth = .1+p*3.9;
|
|
718
|
+
let w2 = 0;
|
|
719
|
+
for (let i=0; i<s.length; ++i)
|
|
720
|
+
w2 += x.measureText(s[i]).width;
|
|
721
|
+
for (let j=2; j--;)
|
|
722
|
+
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
723
|
+
{
|
|
724
|
+
x.fillStyle = color(i,2);
|
|
725
|
+
const w = x.measureText(s[i]).width;
|
|
726
|
+
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
727
|
+
X += w;
|
|
728
|
+
}
|
|
729
|
+
x.restore();
|
|
730
730
|
}
|
|
731
731
|
/**
|
|
732
732
|
* LittleJS - Release Mode
|
|
@@ -767,12 +767,11 @@ function debugVideoCaptureStop (){}
|
|
|
767
767
|
function debugVideoCaptureUpdate(){}
|
|
768
768
|
function debugProtectConstant(o){ return o; }
|
|
769
769
|
/**
|
|
770
|
-
* LittleJS
|
|
770
|
+
* LittleJS Math Classes and Functions
|
|
771
771
|
* - General purpose math library
|
|
772
|
-
* - Vector2 - fast, simple, easy 2D vector class
|
|
773
|
-
* - Color - holds a rgba color with some math functions
|
|
774
|
-
* - Timer - tracks time automatically
|
|
775
772
|
* - RandomGenerator - seeded random number generator
|
|
773
|
+
* - Vector2 - fast, simple, easy 2D vector class
|
|
774
|
+
* - Color - holds a rgba color with math functions
|
|
776
775
|
* @namespace Math
|
|
777
776
|
*/
|
|
778
777
|
|
|
@@ -960,11 +959,11 @@ function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
|
960
959
|
|
|
961
960
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
962
961
|
* this can be used for simple collision detection between objects
|
|
963
|
-
* @param {Vector2} posA
|
|
964
|
-
* @param {Vector2} sizeA
|
|
965
|
-
* @param {Vector2} posB
|
|
966
|
-
* @param {Vector2} [sizeB=(
|
|
967
|
-
* @return {boolean}
|
|
962
|
+
* @param {Vector2} posA - Center of box A
|
|
963
|
+
* @param {Vector2} sizeA - Size of box A
|
|
964
|
+
* @param {Vector2} posB - Center of box B
|
|
965
|
+
* @param {Vector2} [sizeB=vec2()] - Size of box B, uses a point if undefined
|
|
966
|
+
* @return {boolean} - True if overlapping
|
|
968
967
|
* @memberof Math */
|
|
969
968
|
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
970
969
|
{
|
|
@@ -1038,7 +1037,7 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
|
1038
1037
|
* @param {any} s
|
|
1039
1038
|
* @return {boolean}
|
|
1040
1039
|
* @memberof Math */
|
|
1041
|
-
function isString(s) { return s
|
|
1040
|
+
function isString(s) { return s != null && typeof s?.toString() === 'string'; }
|
|
1042
1041
|
|
|
1043
1042
|
/**
|
|
1044
1043
|
* Check if object is an array
|
|
@@ -1182,8 +1181,8 @@ function randInCircle(radius=1, minRadius=0)
|
|
|
1182
1181
|
{ return radius > 0 ? randVec2(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
|
|
1183
1182
|
|
|
1184
1183
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1185
|
-
* @param {Color} [colorA=
|
|
1186
|
-
* @param {Color} [colorB=
|
|
1184
|
+
* @param {Color} [colorA=WHITE]
|
|
1185
|
+
* @param {Color} [colorB=BLACK]
|
|
1187
1186
|
* @param {boolean} [linear]
|
|
1188
1187
|
* @return {Color}
|
|
1189
1188
|
* @memberof Random */
|
|
@@ -1262,8 +1261,8 @@ class RandomGenerator
|
|
|
1262
1261
|
{ return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
|
|
1263
1262
|
|
|
1264
1263
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1265
|
-
* @param {Color} [colorA=
|
|
1266
|
-
* @param {Color} [colorB=
|
|
1264
|
+
* @param {Color} [colorA=WHITE]
|
|
1265
|
+
* @param {Color} [colorB=BLACK]
|
|
1267
1266
|
* @param {boolean} [linear]
|
|
1268
1267
|
* @return {Color} */
|
|
1269
1268
|
randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
@@ -1307,7 +1306,7 @@ class RandomGenerator
|
|
|
1307
1306
|
* a = vec2(5); // set a to (5, 5)
|
|
1308
1307
|
* b = vec2(); // set b to (0, 0)
|
|
1309
1308
|
* @memberof Math */
|
|
1310
|
-
function vec2(x=0, y) { return new Vector2(x, y
|
|
1309
|
+
function vec2(x=0, y) { return new Vector2(x, y ?? x); }
|
|
1311
1310
|
|
|
1312
1311
|
/**
|
|
1313
1312
|
* Check if object is a valid Vector2
|
|
@@ -1515,10 +1514,6 @@ class Vector2
|
|
|
1515
1514
|
* @return {number} */
|
|
1516
1515
|
area() { return abs(this.x * this.y); }
|
|
1517
1516
|
|
|
1518
|
-
/** Returns true if this vector is (0,0)
|
|
1519
|
-
* @return {boolean} */
|
|
1520
|
-
isZero() { return !this.x && !this.y; }
|
|
1521
|
-
|
|
1522
1517
|
/** Returns a new vector that is p percent between this and the vector passed in
|
|
1523
1518
|
* @param {Vector2} v - other vector
|
|
1524
1519
|
* @param {number} percent
|
|
@@ -1880,7 +1875,97 @@ const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
|
1880
1875
|
/** Color - Magenta #ff00ff
|
|
1881
1876
|
* @type {Color}
|
|
1882
1877
|
* @memberof Math */
|
|
1883
|
-
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
1878
|
+
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
1879
|
+
/**
|
|
1880
|
+
* LittleJS Utility Classes and Functions
|
|
1881
|
+
* - General purpose utilities
|
|
1882
|
+
* - Timer - tracks time automatically
|
|
1883
|
+
* @namespace Utilities
|
|
1884
|
+
*/
|
|
1885
|
+
|
|
1886
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
1887
|
+
* @param {number} t - time in seconds
|
|
1888
|
+
* @return {string}
|
|
1889
|
+
* @memberof Utilities */
|
|
1890
|
+
function formatTime(t)
|
|
1891
|
+
{
|
|
1892
|
+
const sign = t < 0 ? '-' : '';
|
|
1893
|
+
t = abs(t)|0;
|
|
1894
|
+
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
1898
|
+
* @param {string} url - URL of JSON file
|
|
1899
|
+
* @return {Promise<object>}
|
|
1900
|
+
* @memberof Utilities */
|
|
1901
|
+
async function fetchJSON(url)
|
|
1902
|
+
{
|
|
1903
|
+
const response = await fetch(url);
|
|
1904
|
+
if (!response.ok)
|
|
1905
|
+
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
1906
|
+
return response.json();
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1910
|
+
|
|
1911
|
+
/** Save a text file to disk
|
|
1912
|
+
* @param {string} text
|
|
1913
|
+
* @param {string} [filename]
|
|
1914
|
+
* @param {string} [type]
|
|
1915
|
+
* @memberof Utilities */
|
|
1916
|
+
function saveText(text, filename='text', type='text/plain')
|
|
1917
|
+
{ saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
1918
|
+
|
|
1919
|
+
/** Save a canvas to disk
|
|
1920
|
+
* @param {HTMLCanvasElement|OffscreenCanvas} canvas
|
|
1921
|
+
* @param {string} [filename]
|
|
1922
|
+
* @param {string} [type]
|
|
1923
|
+
* @memberof Utilities */
|
|
1924
|
+
function saveCanvas(canvas, filename='screenshot', type='image/png')
|
|
1925
|
+
{
|
|
1926
|
+
if (canvas instanceof OffscreenCanvas)
|
|
1927
|
+
{
|
|
1928
|
+
// copy to temporary canvas and save
|
|
1929
|
+
const saveCanvas = document.createElement('canvas');
|
|
1930
|
+
saveCanvas.width = canvas.width;
|
|
1931
|
+
saveCanvas.height = canvas.height;
|
|
1932
|
+
saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
|
|
1933
|
+
saveDataURL(saveCanvas.toDataURL(type), filename);
|
|
1934
|
+
}
|
|
1935
|
+
else
|
|
1936
|
+
saveDataURL(canvas.toDataURL(type), filename);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
/** Save a data url to disk
|
|
1940
|
+
* @param {string} url
|
|
1941
|
+
* @param {string} [filename]
|
|
1942
|
+
* @param {number} [revokeTime] - how long before revoking the url
|
|
1943
|
+
* @memberof Utilities */
|
|
1944
|
+
function saveDataURL(url, filename='download', revokeTime)
|
|
1945
|
+
{
|
|
1946
|
+
ASSERT(isString(url), 'saveDataURL requires url string');
|
|
1947
|
+
ASSERT(isString(filename), 'saveDataURL requires filename string');
|
|
1948
|
+
|
|
1949
|
+
// create link for saving screenshots
|
|
1950
|
+
const link = document.createElement('a');
|
|
1951
|
+
link.download = filename;
|
|
1952
|
+
link.href = url;
|
|
1953
|
+
link.click();
|
|
1954
|
+
if (revokeTime !== undefined)
|
|
1955
|
+
setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
/** Share content using the native share dialog if available
|
|
1959
|
+
* @param {string} title - title of the share
|
|
1960
|
+
* @param {string} url - url to share
|
|
1961
|
+
* @param {Function} [callback] - Called when share is complete
|
|
1962
|
+
* @memberof Utilities */
|
|
1963
|
+
function shareURL(title, url, callback)
|
|
1964
|
+
{
|
|
1965
|
+
ASSERT(isString(title), 'shareURL requires title string');
|
|
1966
|
+
ASSERT(isString(url), 'shareURL requires url string');
|
|
1967
|
+
navigator.share?.({title, url}).then(()=>callback?.());
|
|
1968
|
+
}
|
|
1884
1969
|
|
|
1885
1970
|
///////////////////////////////////////////////////////////////////////////////
|
|
1886
1971
|
|
|
@@ -1965,84 +2050,6 @@ class Timer
|
|
|
1965
2050
|
* @return {number} */
|
|
1966
2051
|
valueOf() { return this.get(); }
|
|
1967
2052
|
}
|
|
1968
|
-
/**
|
|
1969
|
-
* LittleJS Utility Classes and Functions
|
|
1970
|
-
* - General purpose math library
|
|
1971
|
-
* - Vector2 - fast, simple, easy 2D vector class
|
|
1972
|
-
* - Color - holds a rgba color with some math functions
|
|
1973
|
-
* - Timer - tracks time automatically
|
|
1974
|
-
* - RandomGenerator - seeded random number generator
|
|
1975
|
-
* @namespace Utilities
|
|
1976
|
-
*/
|
|
1977
|
-
|
|
1978
|
-
/** Formats seconds to mm:ss style for display purposes
|
|
1979
|
-
* @param {number} t - time in seconds
|
|
1980
|
-
* @return {string}
|
|
1981
|
-
* @memberof Utilities */
|
|
1982
|
-
function formatTime(t)
|
|
1983
|
-
{
|
|
1984
|
-
const sign = t < 0 ? '-' : '';
|
|
1985
|
-
t = abs(t)|0;
|
|
1986
|
-
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
1990
|
-
* @param {string} url - URL of JSON file
|
|
1991
|
-
* @return {Promise<object>}
|
|
1992
|
-
* @memberof Utilities */
|
|
1993
|
-
async function fetchJSON(url)
|
|
1994
|
-
{
|
|
1995
|
-
const response = await fetch(url);
|
|
1996
|
-
if (!response.ok)
|
|
1997
|
-
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
1998
|
-
return response.json();
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2002
|
-
|
|
2003
|
-
/** Save a text file to disk
|
|
2004
|
-
* @param {string} text
|
|
2005
|
-
* @param {string} [filename]
|
|
2006
|
-
* @param {string} [type]
|
|
2007
|
-
* @memberof Utilities */
|
|
2008
|
-
function saveText(text, filename='text', type='text/plain')
|
|
2009
|
-
{ saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
2010
|
-
|
|
2011
|
-
/** Save a canvas to disk
|
|
2012
|
-
* @param {HTMLCanvasElement|OffscreenCanvas} canvas
|
|
2013
|
-
* @param {string} [filename]
|
|
2014
|
-
* @param {string} [type]
|
|
2015
|
-
* @memberof Utilities */
|
|
2016
|
-
function saveCanvas(canvas, filename='screenshot', type='image/png')
|
|
2017
|
-
{
|
|
2018
|
-
if (canvas instanceof OffscreenCanvas)
|
|
2019
|
-
{
|
|
2020
|
-
// copy to temporary canvas and save
|
|
2021
|
-
const saveCanvas = document.createElement('canvas');
|
|
2022
|
-
saveCanvas.width = canvas.width;
|
|
2023
|
-
saveCanvas.height = canvas.height;
|
|
2024
|
-
saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
|
|
2025
|
-
saveDataURL(saveCanvas.toDataURL(type), filename);
|
|
2026
|
-
}
|
|
2027
|
-
else
|
|
2028
|
-
saveDataURL(canvas.toDataURL(type), filename);
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
/** Save a data url to disk
|
|
2032
|
-
* @param {string} url
|
|
2033
|
-
* @param {string} [filename]
|
|
2034
|
-
* @param {number} [revokeTime] - how long before revoking the url
|
|
2035
|
-
* @memberof Utilities */
|
|
2036
|
-
function saveDataURL(url, filename='download', revokeTime)
|
|
2037
|
-
{
|
|
2038
|
-
// create link for saving screenshots
|
|
2039
|
-
const link = document.createElement('a');
|
|
2040
|
-
link.download = filename;
|
|
2041
|
-
link.href = url;
|
|
2042
|
-
link.click();
|
|
2043
|
-
if (revokeTime !== undefined)
|
|
2044
|
-
setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
|
|
2045
|
-
}
|
|
2046
2053
|
/**
|
|
2047
2054
|
* LittleJS Engine Settings
|
|
2048
2055
|
* - All settings for the engine are here
|
|
@@ -2669,10 +2676,10 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
2669
2676
|
class EngineObject
|
|
2670
2677
|
{
|
|
2671
2678
|
/** Create an engine object and adds it to the list of objects
|
|
2672
|
-
* @param {Vector2} [pos=(
|
|
2673
|
-
* @param {Vector2} [size=(1
|
|
2674
|
-
* @param {TileInfo} [tileInfo]
|
|
2675
|
-
* @param {number} [angle]
|
|
2679
|
+
* @param {Vector2} [pos=vec2()] - World space position of the object
|
|
2680
|
+
* @param {Vector2} [size=vec2(1)] - World space size of the object
|
|
2681
|
+
* @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
|
|
2682
|
+
* @param {number} [angle] - Angle the object is rotated by
|
|
2676
2683
|
* @param {Color} [color=WHITE] - Color to apply to tile when rendered
|
|
2677
2684
|
* @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
|
|
2678
2685
|
*/
|
|
@@ -2800,15 +2807,16 @@ class EngineObject
|
|
|
2800
2807
|
// physics sanity checks
|
|
2801
2808
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
2802
2809
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
2803
|
-
|
|
2804
|
-
|
|
2810
|
+
|
|
2811
|
+
// don't do collision for static objects or if solver disabled
|
|
2812
|
+
if (!enablePhysicsSolver || !this.mass) return;
|
|
2805
2813
|
|
|
2806
2814
|
const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
|
|
2807
2815
|
if (this.groundObject)
|
|
2808
2816
|
{
|
|
2809
2817
|
// apply friction in local space of ground object
|
|
2810
2818
|
const friction = max(this.friction, this.groundObject.friction);
|
|
2811
|
-
const groundSpeed = this.groundObject.velocity
|
|
2819
|
+
const groundSpeed = this.groundObject.velocity.x;
|
|
2812
2820
|
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
|
|
2813
2821
|
this.groundObject = undefined;
|
|
2814
2822
|
}
|
|
@@ -2819,19 +2827,19 @@ class EngineObject
|
|
|
2819
2827
|
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
2820
2828
|
for (const o of engineObjectsCollide)
|
|
2821
2829
|
{
|
|
2830
|
+
// skip destroyed, child objects, or self collision
|
|
2831
|
+
if (o.destroyed || o.parent || o === this) continue;
|
|
2832
|
+
|
|
2822
2833
|
// non solid objects don't collide with each other
|
|
2823
|
-
if (
|
|
2824
|
-
continue;
|
|
2834
|
+
if (!this.isSolid && !o.isSolid) continue;
|
|
2825
2835
|
|
|
2826
2836
|
// check collision
|
|
2827
|
-
if (!this.isOverlappingObject(o))
|
|
2828
|
-
continue;
|
|
2837
|
+
if (!this.isOverlappingObject(o)) continue;
|
|
2829
2838
|
|
|
2830
2839
|
// notify objects of collision and check if should be resolved
|
|
2831
2840
|
const collide1 = this.collideWithObject(o);
|
|
2832
2841
|
const collide2 = o.collideWithObject(this);
|
|
2833
|
-
if (!collide1 || !collide2)
|
|
2834
|
-
continue;
|
|
2842
|
+
if (!collide1 || !collide2) continue;
|
|
2835
2843
|
|
|
2836
2844
|
if (isOverlapping(oldPos, this.size, o.pos, o.size))
|
|
2837
2845
|
{
|
|
@@ -2933,7 +2941,7 @@ class EngineObject
|
|
|
2933
2941
|
const delta = y - this.pos.y;
|
|
2934
2942
|
if (delta < maxMoveUp)
|
|
2935
2943
|
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
2936
|
-
{
|
|
2944
|
+
{
|
|
2937
2945
|
this.pos.y = y;
|
|
2938
2946
|
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
2939
2947
|
return;
|
|
@@ -2993,10 +3001,10 @@ class EngineObject
|
|
|
2993
3001
|
|
|
2994
3002
|
// disconnect from parent and destroy children
|
|
2995
3003
|
this.destroyed = 1;
|
|
2996
|
-
this.parent
|
|
3004
|
+
this.parent?.removeChild(this);
|
|
2997
3005
|
for (const child of this.children)
|
|
2998
3006
|
{
|
|
2999
|
-
child.parent =
|
|
3007
|
+
child.parent = undefined;
|
|
3000
3008
|
child.destroy();
|
|
3001
3009
|
}
|
|
3002
3010
|
}
|
|
@@ -3017,15 +3025,15 @@ class EngineObject
|
|
|
3017
3025
|
* @param {Vector2} vec - world space vector */
|
|
3018
3026
|
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
3019
3027
|
|
|
3020
|
-
/** Called to check if a tile collision should be resolved
|
|
3028
|
+
/** 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.
|
|
3021
3029
|
* @param {number} tileData - the value of the tile at the position
|
|
3022
|
-
* @param {Vector2} pos
|
|
3023
|
-
* @return {boolean}
|
|
3030
|
+
* @param {Vector2} pos - tile where the collision occurred
|
|
3031
|
+
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity */
|
|
3024
3032
|
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
3025
3033
|
|
|
3026
|
-
/** Called to check if
|
|
3034
|
+
/** 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.
|
|
3027
3035
|
* @param {EngineObject} object - the object to test against
|
|
3028
|
-
* @return {boolean}
|
|
3036
|
+
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity
|
|
3029
3037
|
*/
|
|
3030
3038
|
collideWithObject(object) { return true; }
|
|
3031
3039
|
|
|
@@ -3066,9 +3074,9 @@ class EngineObject
|
|
|
3066
3074
|
* @return {number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
3067
3075
|
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
3068
3076
|
|
|
3069
|
-
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3077
|
+
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3070
3078
|
* @param {EngineObject} child
|
|
3071
|
-
* @param {Vector2} [localPos=(
|
|
3079
|
+
* @param {Vector2} [localPos=vec2()]
|
|
3072
3080
|
* @param {number} [localAngle]
|
|
3073
3081
|
* @return {EngineObject} The child object added */
|
|
3074
3082
|
addChild(child, localPos=vec2(), localAngle=0)
|
|
@@ -3092,7 +3100,7 @@ class EngineObject
|
|
|
3092
3100
|
const index = this.children.indexOf(child);
|
|
3093
3101
|
ASSERT(index >= 0, 'child not found in children array');
|
|
3094
3102
|
index >= 0 && this.children.splice(index, 1);
|
|
3095
|
-
child.parent =
|
|
3103
|
+
child.parent = undefined;
|
|
3096
3104
|
}
|
|
3097
3105
|
|
|
3098
3106
|
/** Check if overlapping another engine object
|
|
@@ -3104,7 +3112,7 @@ class EngineObject
|
|
|
3104
3112
|
|
|
3105
3113
|
/** Check if overlapping a point or aligned bounding box
|
|
3106
3114
|
* @param {Vector2} pos - Center of box
|
|
3107
|
-
* @param {Vector2} [size=(
|
|
3115
|
+
* @param {Vector2} [size=vec2()] - Size of box, uses a point if undefined
|
|
3108
3116
|
* @return {boolean} */
|
|
3109
3117
|
isOverlapping(pos, size=vec2())
|
|
3110
3118
|
{ return isOverlapping(this.pos, this.size, pos, size); }
|
|
@@ -3235,10 +3243,11 @@ let drawCount;
|
|
|
3235
3243
|
* Create a tile info object using a grid based system
|
|
3236
3244
|
* - This can take vecs or floats for easier use and conversion
|
|
3237
3245
|
* - If an index is passed in, the tile size and index will determine the position
|
|
3238
|
-
* @param {Vector2|number} [
|
|
3246
|
+
* @param {Vector2|number} [index=0] - Index of the tile in 1d or 2d form
|
|
3239
3247
|
* @param {Vector2|number} [size] - Size of tile in pixels
|
|
3240
|
-
* @param {number} [
|
|
3248
|
+
* @param {TextureInfo|number} [texture] - Texture index or info to use
|
|
3241
3249
|
* @param {number} [padding] - How many pixels padding around tiles
|
|
3250
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3242
3251
|
* @return {TileInfo}
|
|
3243
3252
|
* @example
|
|
3244
3253
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -3246,36 +3255,43 @@ let drawCount;
|
|
|
3246
3255
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
3247
3256
|
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
3248
3257
|
* @memberof Draw */
|
|
3249
|
-
function tile(
|
|
3258
|
+
function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3250
3259
|
{
|
|
3251
|
-
|
|
3252
|
-
|
|
3260
|
+
ASSERT(isVector2(index) || typeof index === 'number', 'index must be a vec2 or number');
|
|
3261
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
3262
|
+
ASSERT(isNumber(texture) || texture instanceof TextureInfo, 'texture must be a number or TextureInfo');
|
|
3263
|
+
ASSERT(isNumber(padding), 'padding must be a number');
|
|
3264
|
+
|
|
3265
|
+
if (headlessMode) return new TileInfo;
|
|
3253
3266
|
|
|
3254
|
-
// if size is a number, make it a vector
|
|
3255
3267
|
if (typeof size === 'number')
|
|
3256
3268
|
{
|
|
3269
|
+
// if size is a number, make it a vector
|
|
3257
3270
|
ASSERT(size > 0);
|
|
3258
3271
|
size = new Vector2(size, size);
|
|
3259
3272
|
}
|
|
3260
3273
|
|
|
3261
3274
|
// create tile info object
|
|
3262
|
-
const
|
|
3275
|
+
const textureInfo = typeof texture === 'number' ?
|
|
3276
|
+
textureInfos[texture] : texture;
|
|
3263
3277
|
|
|
3264
3278
|
// get the position of the tile
|
|
3265
|
-
const textureInfo = textureInfos[textureIndex];
|
|
3266
|
-
ASSERT(!!textureInfo, 'Texture not loaded');
|
|
3267
3279
|
const sizePaddedX = size.x + padding*2;
|
|
3268
3280
|
const sizePaddedY = size.y + padding*2;
|
|
3269
|
-
|
|
3281
|
+
let x, y;
|
|
3282
|
+
if (typeof index === 'number')
|
|
3270
3283
|
{
|
|
3271
3284
|
const cols = textureInfo.size.x / sizePaddedX |0;
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
|
|
3285
|
+
x = index % cols;
|
|
3286
|
+
y = index / cols |0;
|
|
3275
3287
|
}
|
|
3276
3288
|
else
|
|
3277
|
-
|
|
3278
|
-
|
|
3289
|
+
{
|
|
3290
|
+
x = index.x;
|
|
3291
|
+
y = index.y;
|
|
3292
|
+
}
|
|
3293
|
+
const pos = new Vector2(x*sizePaddedX + padding, y*sizePaddedY + padding);
|
|
3294
|
+
return new TileInfo(pos, size, textureInfo, padding, bleed);
|
|
3279
3295
|
}
|
|
3280
3296
|
|
|
3281
3297
|
/**
|
|
@@ -3285,24 +3301,22 @@ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=til
|
|
|
3285
3301
|
class TileInfo
|
|
3286
3302
|
{
|
|
3287
3303
|
/** Create a tile info object
|
|
3288
|
-
* @param {Vector2} [pos=(
|
|
3304
|
+
* @param {Vector2} [pos=vec2()] - Top left corner of tile in pixels
|
|
3289
3305
|
* @param {Vector2} [size] - Size of tile in pixels
|
|
3290
|
-
* @param {
|
|
3291
|
-
* @param {number}
|
|
3292
|
-
* @param {number}
|
|
3306
|
+
* @param {TextureInfo} [textureInfo] - Texture info to use
|
|
3307
|
+
* @param {number} [padding] - How many pixels padding around tiles
|
|
3308
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3293
3309
|
*/
|
|
3294
|
-
constructor(pos=vec2(), size=tileDefaultSize,
|
|
3310
|
+
constructor(pos=vec2(), size=tileDefaultSize, textureInfo=textureInfos[0], padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3295
3311
|
{
|
|
3296
3312
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
3297
3313
|
this.pos = pos.copy();
|
|
3298
3314
|
/** @property {Vector2} - Size of tile in pixels */
|
|
3299
3315
|
this.size = size.copy();
|
|
3300
|
-
/** @property {number} - Texture index to use */
|
|
3301
|
-
this.textureIndex = textureIndex;
|
|
3302
3316
|
/** @property {number} - How many pixels padding around tiles */
|
|
3303
3317
|
this.padding = padding;
|
|
3304
3318
|
/** @property {TextureInfo} - The texture info for this tile */
|
|
3305
|
-
this.textureInfo =
|
|
3319
|
+
this.textureInfo = textureInfo;
|
|
3306
3320
|
/** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
|
|
3307
3321
|
this.bleed = bleed;
|
|
3308
3322
|
}
|
|
@@ -3312,7 +3326,7 @@ class TileInfo
|
|
|
3312
3326
|
* @return {TileInfo}
|
|
3313
3327
|
*/
|
|
3314
3328
|
offset(offset)
|
|
3315
|
-
{ return new TileInfo(this.pos.add(offset), this.size, this.
|
|
3329
|
+
{ return new TileInfo(this.pos.add(offset), this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3316
3330
|
|
|
3317
3331
|
/** Returns a copy of this tile offset by a number of animation frames
|
|
3318
3332
|
* @param {number} frame - Offset to apply in animation frames
|
|
@@ -3321,23 +3335,33 @@ class TileInfo
|
|
|
3321
3335
|
frame(frame)
|
|
3322
3336
|
{
|
|
3323
3337
|
ASSERT(typeof frame === 'number');
|
|
3324
|
-
|
|
3338
|
+
const w = this.size.x + this.padding*2;
|
|
3339
|
+
const x = frame*w;
|
|
3340
|
+
ASSERT(x < this.textureInfo.size.x, 'frame extends beyond texture width!');
|
|
3341
|
+
return this.offset(new Vector2(x));
|
|
3325
3342
|
}
|
|
3326
3343
|
|
|
3327
3344
|
/**
|
|
3328
3345
|
* Set this tile to use a full image in a texture info
|
|
3329
|
-
* @param {TextureInfo} textureInfo
|
|
3346
|
+
* @param {TextureInfo} [textureInfo]
|
|
3330
3347
|
* @return {TileInfo}
|
|
3331
3348
|
*/
|
|
3332
|
-
setFullImage(textureInfo)
|
|
3349
|
+
setFullImage(textureInfo=this.textureInfo)
|
|
3333
3350
|
{
|
|
3351
|
+
this.textureInfo = textureInfo;
|
|
3334
3352
|
this.pos = new Vector2;
|
|
3335
3353
|
this.size = textureInfo.size.copy();
|
|
3336
|
-
this.textureInfo = textureInfo;
|
|
3337
|
-
// do not use padding or bleed
|
|
3338
3354
|
this.bleed = this.padding = 0;
|
|
3339
3355
|
return this;
|
|
3340
3356
|
}
|
|
3357
|
+
|
|
3358
|
+
/**
|
|
3359
|
+
* Returns a tile info for an index using this tile as refrence
|
|
3360
|
+
* @param {Vector2|number} [index=0]
|
|
3361
|
+
* @return {TileInfo}
|
|
3362
|
+
*/
|
|
3363
|
+
tile(index)
|
|
3364
|
+
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3341
3365
|
}
|
|
3342
3366
|
|
|
3343
3367
|
/**
|
|
@@ -3378,19 +3402,19 @@ class TextureInfo
|
|
|
3378
3402
|
///////////////////////////////////////////////////////////////////////////////
|
|
3379
3403
|
// Drawing functions
|
|
3380
3404
|
|
|
3381
|
-
/** Draw textured tile centered in world space
|
|
3382
|
-
* @param {Vector2} pos
|
|
3383
|
-
* @param {Vector2} [size=(1
|
|
3384
|
-
* @param {TileInfo} [tileInfo]
|
|
3385
|
-
* @param {Color} [color=
|
|
3386
|
-
* @param {number} [angle]
|
|
3387
|
-
* @param {boolean} [mirror]
|
|
3388
|
-
* @param {Color} [additiveColor]
|
|
3405
|
+
/** Draw textured tile centered in world space
|
|
3406
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
3407
|
+
* @param {Vector2} [size=vec2(1)] - Size of the tile in world space
|
|
3408
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
3409
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
3410
|
+
* @param {number} [angle] - Angle to rotate by
|
|
3411
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
3412
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
3389
3413
|
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
|
|
3390
3414
|
* @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
|
|
3391
3415
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
3392
3416
|
* @memberof Draw */
|
|
3393
|
-
function drawTile(pos, size=
|
|
3417
|
+
function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
3394
3418
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
3395
3419
|
{
|
|
3396
3420
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3465,8 +3489,8 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3465
3489
|
|
|
3466
3490
|
/** Draw colored rect centered on pos
|
|
3467
3491
|
* @param {Vector2} pos
|
|
3468
|
-
* @param {Vector2} [size=(1
|
|
3469
|
-
* @param {Color} [color=
|
|
3492
|
+
* @param {Vector2} [size=vec2(1)]
|
|
3493
|
+
* @param {Color} [color=WHITE]
|
|
3470
3494
|
* @param {number} [angle]
|
|
3471
3495
|
* @param {boolean} [useWebGL=glEnable]
|
|
3472
3496
|
* @param {boolean} [screenSpace]
|
|
@@ -3479,9 +3503,9 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
3479
3503
|
|
|
3480
3504
|
/** Draw a rect centered on pos with a gradient from top to bottom
|
|
3481
3505
|
* @param {Vector2} pos
|
|
3482
|
-
* @param {Vector2} [size=(1
|
|
3483
|
-
* @param {Color} [colorTop=
|
|
3484
|
-
* @param {Color} [colorBottom=
|
|
3506
|
+
* @param {Vector2} [size=vec2(1)]
|
|
3507
|
+
* @param {Color} [colorTop=WHITE]
|
|
3508
|
+
* @param {Color} [colorBottom=BLACK]
|
|
3485
3509
|
* @param {number} [angle]
|
|
3486
3510
|
* @param {boolean} [useWebGL=glEnable]
|
|
3487
3511
|
* @param {boolean} [screenSpace]
|
|
@@ -3543,9 +3567,9 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3543
3567
|
/** Draw connected lines between a series of points
|
|
3544
3568
|
* @param {Array<Vector2>} points
|
|
3545
3569
|
* @param {number} [width]
|
|
3546
|
-
* @param {Color} [color=
|
|
3570
|
+
* @param {Color} [color=WHITE]
|
|
3547
3571
|
* @param {boolean} [wrap] - Should the last point connect to the first?
|
|
3548
|
-
* @param {Vector2} [pos=(
|
|
3572
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
3549
3573
|
* @param {number} [angle] - Angle to rotate by
|
|
3550
3574
|
* @param {boolean} [useWebGL=glEnable]
|
|
3551
3575
|
* @param {boolean} [screenSpace]
|
|
@@ -3580,13 +3604,9 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3580
3604
|
for (let i=0; i<points.length; ++i)
|
|
3581
3605
|
{
|
|
3582
3606
|
const point = points[i];
|
|
3583
|
-
|
|
3584
|
-
context.lineTo(point.x, point.y);
|
|
3585
|
-
else
|
|
3586
|
-
context.moveTo(point.x, point.y);
|
|
3607
|
+
context.lineTo(point.x, point.y);
|
|
3587
3608
|
}
|
|
3588
|
-
|
|
3589
|
-
context.closePath();
|
|
3609
|
+
wrap && context.closePath();
|
|
3590
3610
|
context.stroke();
|
|
3591
3611
|
}, screenSpace, context);
|
|
3592
3612
|
}
|
|
@@ -3596,8 +3616,8 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3596
3616
|
* @param {Vector2} posA
|
|
3597
3617
|
* @param {Vector2} posB
|
|
3598
3618
|
* @param {number} [width]
|
|
3599
|
-
* @param {Color} [color=
|
|
3600
|
-
* @param {Vector2} [pos=(
|
|
3619
|
+
* @param {Color} [color=WHITE]
|
|
3620
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
3601
3621
|
* @param {number} [angle] - Angle to rotate by
|
|
3602
3622
|
* @param {boolean} [useWebGL=glEnable]
|
|
3603
3623
|
* @param {boolean} [screenSpace]
|
|
@@ -3616,12 +3636,12 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
3616
3636
|
|
|
3617
3637
|
/** Draw colored regular polygon using passed in number of sides
|
|
3618
3638
|
* @param {Vector2} pos
|
|
3619
|
-
* @param {Vector2} [size=(1
|
|
3639
|
+
* @param {Vector2} [size=vec2(1)]
|
|
3620
3640
|
* @param {number} [sides]
|
|
3621
|
-
* @param {Color} [color=
|
|
3641
|
+
* @param {Color} [color=WHITE]
|
|
3622
3642
|
* @param {number} [angle]
|
|
3623
3643
|
* @param {number} [lineWidth]
|
|
3624
|
-
* @param {Color} [lineColor=
|
|
3644
|
+
* @param {Color} [lineColor=BLACK]
|
|
3625
3645
|
* @param {boolean} [useWebGL=glEnable]
|
|
3626
3646
|
* @param {boolean} [screenSpace]
|
|
3627
3647
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -3644,10 +3664,10 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
3644
3664
|
|
|
3645
3665
|
/** Draw colored polygon using passed in points
|
|
3646
3666
|
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
3647
|
-
* @param {Color} [color=
|
|
3667
|
+
* @param {Color} [color=WHITE]
|
|
3648
3668
|
* @param {number} [lineWidth]
|
|
3649
|
-
* @param {Color} [lineColor=
|
|
3650
|
-
* @param {Vector2} [pos=(
|
|
3669
|
+
* @param {Color} [lineColor=BLACK]
|
|
3670
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
3651
3671
|
* @param {number} [angle] - Angle to rotate by
|
|
3652
3672
|
* @param {boolean} [useWebGL=glEnable]
|
|
3653
3673
|
* @param {boolean} [screenSpace]
|
|
@@ -3694,11 +3714,11 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
3694
3714
|
|
|
3695
3715
|
/** Draw colored ellipse using passed in point
|
|
3696
3716
|
* @param {Vector2} pos
|
|
3697
|
-
* @param {Vector2} [size=(1
|
|
3698
|
-
* @param {Color} [color=
|
|
3717
|
+
* @param {Vector2} [size=vec2(1)] - Width and height diameter
|
|
3718
|
+
* @param {Color} [color=WHITE]
|
|
3699
3719
|
* @param {number} [angle]
|
|
3700
3720
|
* @param {number} [lineWidth]
|
|
3701
|
-
* @param {Color} [lineColor=
|
|
3721
|
+
* @param {Color} [lineColor=BLACK]
|
|
3702
3722
|
* @param {boolean} [useWebGL=glEnable]
|
|
3703
3723
|
* @param {boolean} [screenSpace]
|
|
3704
3724
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -3740,9 +3760,9 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
3740
3760
|
/** Draw colored circle using passed in point
|
|
3741
3761
|
* @param {Vector2} pos
|
|
3742
3762
|
* @param {number} [size=1] - Diameter
|
|
3743
|
-
* @param {Color} [color=
|
|
3763
|
+
* @param {Color} [color=WHITE]
|
|
3744
3764
|
* @param {number} [lineWidth=0]
|
|
3745
|
-
* @param {Color} [lineColor=
|
|
3765
|
+
* @param {Color} [lineColor=BLACK]
|
|
3746
3766
|
* @param {boolean} [useWebGL=glEnable]
|
|
3747
3767
|
* @param {boolean} [screenSpace]
|
|
3748
3768
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -3797,9 +3817,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
3797
3817
|
* @param {string|number} text
|
|
3798
3818
|
* @param {Vector2} pos
|
|
3799
3819
|
* @param {number} [size]
|
|
3800
|
-
* @param {Color} [color=
|
|
3820
|
+
* @param {Color} [color=WHITE]
|
|
3801
3821
|
* @param {number} [lineWidth]
|
|
3802
|
-
* @param {Color} [lineColor=
|
|
3822
|
+
* @param {Color} [lineColor=BLACK]
|
|
3803
3823
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
3804
3824
|
* @param {string} [font=fontDefault]
|
|
3805
3825
|
* @param {string} [fontStyle]
|
|
@@ -3823,10 +3843,10 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
3823
3843
|
* Automatically splits new lines into rows
|
|
3824
3844
|
* @param {string|number} text
|
|
3825
3845
|
* @param {Vector2} pos
|
|
3826
|
-
* @param {number}
|
|
3827
|
-
* @param {Color} [color=
|
|
3846
|
+
* @param {number} size
|
|
3847
|
+
* @param {Color} [color=WHITE]
|
|
3828
3848
|
* @param {number} [lineWidth]
|
|
3829
|
-
* @param {Color} [lineColor=
|
|
3849
|
+
* @param {Color} [lineColor=BLACK]
|
|
3830
3850
|
* @param {CanvasTextAlign} [textAlign]
|
|
3831
3851
|
* @param {string} [font=fontDefault]
|
|
3832
3852
|
* @param {string} [fontStyle]
|
|
@@ -3834,7 +3854,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
3834
3854
|
* @param {number} [angle]
|
|
3835
3855
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3836
3856
|
* @memberof Draw */
|
|
3837
|
-
function drawTextScreen(text, pos, size
|
|
3857
|
+
function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
|
|
3838
3858
|
{
|
|
3839
3859
|
ASSERT(isString(text), 'text must be a string');
|
|
3840
3860
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3958,7 +3978,7 @@ function worldToScreenDelta(worldDelta)
|
|
|
3958
3978
|
|
|
3959
3979
|
/** Convert screen space transform to world space
|
|
3960
3980
|
* @param {Vector2} screenPos
|
|
3961
|
-
* @param {Vector2} screenSize
|
|
3981
|
+
* @param {Vector2} screenSize
|
|
3962
3982
|
* @param {number} [screenAngle]
|
|
3963
3983
|
* @return {[Vector2, Vector2, number]} - [pos, size, angle]
|
|
3964
3984
|
* @memberof Draw */
|
|
@@ -4019,10 +4039,9 @@ function isOnScreen(pos, size=0)
|
|
|
4019
4039
|
* @param {boolean} [additive]
|
|
4020
4040
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4021
4041
|
* @memberof Draw */
|
|
4022
|
-
function setBlendMode(additive=false, context)
|
|
4042
|
+
function setBlendMode(additive=false, context=drawContext)
|
|
4023
4043
|
{
|
|
4024
4044
|
glAdditive = additive;
|
|
4025
|
-
context ||= drawContext;
|
|
4026
4045
|
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
4027
4046
|
}
|
|
4028
4047
|
|
|
@@ -4037,7 +4056,6 @@ function combineCanvases()
|
|
|
4037
4056
|
workContext.fillRect(0,0,w,h); // remove background alpha
|
|
4038
4057
|
glCopyToContext(workContext);
|
|
4039
4058
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
4040
|
-
mainCanvas.width |= 0;
|
|
4041
4059
|
mainContext.drawImage(workCanvas, 0, 0);
|
|
4042
4060
|
}
|
|
4043
4061
|
|
|
@@ -4142,17 +4160,22 @@ function setCursor(cursorStyle = 'auto')
|
|
|
4142
4160
|
|
|
4143
4161
|
///////////////////////////////////////////////////////////////////////////////
|
|
4144
4162
|
|
|
4163
|
+
/** Engine font image, 8x8 font provided by the engine
|
|
4164
|
+
* @type {FontImage}
|
|
4165
|
+
* @memberof Draw */
|
|
4145
4166
|
let engineFontImage;
|
|
4146
4167
|
|
|
4147
4168
|
/**
|
|
4148
|
-
* Font Image Object - Draw text
|
|
4169
|
+
* Font Image Object - Draw text by using tiles in an image
|
|
4149
4170
|
* - 96 characters (from space to tilde) are stored in an image
|
|
4150
|
-
* -
|
|
4151
|
-
* -
|
|
4171
|
+
* - A 8x8 default engine font is supplied for general use
|
|
4172
|
+
* - This system is WebGL enabled for fast text rendering
|
|
4173
|
+
* - Fonts can also be colored and scaled along each axis
|
|
4174
|
+
*
|
|
4152
4175
|
* @memberof Draw
|
|
4153
4176
|
* @example
|
|
4154
4177
|
* // use built in font
|
|
4155
|
-
* const font =
|
|
4178
|
+
* const font = engineFontImage;
|
|
4156
4179
|
*
|
|
4157
4180
|
* // draw text
|
|
4158
4181
|
* font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
|
|
@@ -4160,70 +4183,112 @@ let engineFontImage;
|
|
|
4160
4183
|
class FontImage
|
|
4161
4184
|
{
|
|
4162
4185
|
/** Create an image font
|
|
4163
|
-
* @param {
|
|
4164
|
-
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
4165
|
-
* @param {Vector2} [paddingSize=(0,1)] - How much space between characters
|
|
4186
|
+
* @param {TileInfo} tileInfo - Tile info of first characeter in font
|
|
4166
4187
|
*/
|
|
4167
|
-
constructor(
|
|
4188
|
+
constructor(tileInfo)
|
|
4168
4189
|
{
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
{
|
|
4172
|
-
|
|
4173
|
-
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
4174
|
-
}
|
|
4175
|
-
|
|
4176
|
-
this.image = image || engineFontImage;
|
|
4177
|
-
this.tileSize = tileSize;
|
|
4178
|
-
this.paddingSize = paddingSize;
|
|
4190
|
+
ASSERT(!!tileInfo, 'tileInfo is required for FontImage');
|
|
4191
|
+
|
|
4192
|
+
/** @property {TileInfo} - Tile info for the font */
|
|
4193
|
+
this.tileInfo = tileInfo.frame(0);
|
|
4179
4194
|
}
|
|
4180
4195
|
|
|
4181
4196
|
/** Draw text in world space using the image font
|
|
4182
|
-
* @param {string|number}
|
|
4197
|
+
* @param {string|number} text
|
|
4183
4198
|
* @param {Vector2} pos
|
|
4184
|
-
* @param {number}
|
|
4185
|
-
* @param {boolean} [center]
|
|
4186
|
-
* @param {
|
|
4199
|
+
* @param {Vector2|number} [size]
|
|
4200
|
+
* @param {boolean} [center=true]
|
|
4201
|
+
* @param {Color} [color=WHITE]
|
|
4202
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
4203
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4187
4204
|
*/
|
|
4188
|
-
drawText(text, pos,
|
|
4205
|
+
drawText(text, pos, size=1, center, color, useWebGL, context)
|
|
4189
4206
|
{
|
|
4190
|
-
|
|
4207
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4208
|
+
|
|
4209
|
+
if (typeof size === 'number')
|
|
4210
|
+
{
|
|
4211
|
+
// if size is a number, make it a vector
|
|
4212
|
+
ASSERT(size > 0);
|
|
4213
|
+
size *= cameraScale;
|
|
4214
|
+
size = new Vector2(size, size);
|
|
4215
|
+
}
|
|
4216
|
+
else
|
|
4217
|
+
size = size.scale(cameraScale);
|
|
4218
|
+
this.drawTextScreen(text, worldToScreen(pos), size, center, color, useWebGL, context);
|
|
4191
4219
|
}
|
|
4192
4220
|
|
|
4193
4221
|
/** Draw text in screen space using the image font
|
|
4194
|
-
* @param {string|number}
|
|
4222
|
+
* @param {string|number} text
|
|
4195
4223
|
* @param {Vector2} pos
|
|
4196
|
-
* @param {number}
|
|
4224
|
+
* @param {Vector2|number} size
|
|
4197
4225
|
* @param {boolean} [center]
|
|
4198
|
-
* @param {
|
|
4226
|
+
* @param {Color} [color=WHITE]
|
|
4227
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
4228
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4199
4229
|
*/
|
|
4200
|
-
drawTextScreen(text, pos,
|
|
4230
|
+
drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
|
|
4201
4231
|
{
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4232
|
+
ASSERT(isString(text), 'text must be a string');
|
|
4233
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
4234
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4235
|
+
ASSERT(isColor(color), 'color must be a color');
|
|
4236
|
+
|
|
4237
|
+
// if size is a number, make it a vector
|
|
4238
|
+
size = typeof size === 'number' ? new Vector2(size, size) : size;
|
|
4239
|
+
|
|
4240
|
+
// precache objects for drawing
|
|
4241
|
+
const drawPos = new Vector2;
|
|
4242
|
+
const tileInfo = this.tileInfo;
|
|
4243
|
+
const padding = tileInfo.padding;
|
|
4244
|
+
const sizePaddedX = tileInfo.size.x + padding*2;
|
|
4245
|
+
const sizePaddedY = tileInfo.size.y + padding*2;
|
|
4246
|
+
const cols = tileInfo.textureInfo.size.x / sizePaddedX |0;
|
|
4247
|
+
|
|
4248
|
+
// draw each line of text
|
|
4249
|
+
(text+'').split('\n').forEach((line, j)=>
|
|
4207
4250
|
{
|
|
4208
|
-
const centerOffset = center ? line.length * size.x
|
|
4209
|
-
for (let
|
|
4251
|
+
const centerOffset = center ? (line.length-1) * size.x / 2 : 0;
|
|
4252
|
+
for (let i=line.length; i--;)
|
|
4210
4253
|
{
|
|
4211
|
-
//
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
charCode
|
|
4215
|
-
|
|
4216
|
-
// get the
|
|
4217
|
-
const
|
|
4218
|
-
const
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4254
|
+
// get the character index
|
|
4255
|
+
const charCode = line.charCodeAt(i);
|
|
4256
|
+
const index = charCode < 32 || charCode > 127 ?
|
|
4257
|
+
95 : charCode - 32; // handle out of range characters
|
|
4258
|
+
|
|
4259
|
+
// get the position of the tile
|
|
4260
|
+
const x = index % cols;
|
|
4261
|
+
const y = index / cols |0;
|
|
4262
|
+
tileInfo.pos.x = x*sizePaddedX + padding;
|
|
4263
|
+
tileInfo.pos.y = y*sizePaddedY + padding;
|
|
4264
|
+
|
|
4265
|
+
// draw the tile
|
|
4266
|
+
drawPos.x = pos.x + i * size.x - centerOffset |0;
|
|
4267
|
+
drawPos.y = pos.y + j * size.y |0;
|
|
4268
|
+
drawTile(drawPos, size, tileInfo, color, 0, false, undefined, useWebGL, true, context);
|
|
4223
4269
|
}
|
|
4224
4270
|
});
|
|
4225
|
-
context.restore();
|
|
4226
4271
|
}
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
// load engine font, called automatically on startup
|
|
4275
|
+
function fontImageInit()
|
|
4276
|
+
{
|
|
4277
|
+
return new Promise(resolve =>
|
|
4278
|
+
{
|
|
4279
|
+
// create the engine font
|
|
4280
|
+
const image = new Image;
|
|
4281
|
+
image.onerror = image.onload = ()=>
|
|
4282
|
+
{
|
|
4283
|
+
const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
|
|
4284
|
+
const textureInfo = new TextureInfo(image);
|
|
4285
|
+
const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
|
|
4286
|
+
engineFontImage = new FontImage(tileInfo);
|
|
4287
|
+
resolve();
|
|
4288
|
+
}
|
|
4289
|
+
image.crossOrigin = 'anonymous';
|
|
4290
|
+
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==';
|
|
4291
|
+
});
|
|
4227
4292
|
}
|
|
4228
4293
|
/**
|
|
4229
4294
|
* LittleJS Input System
|
|
@@ -4280,6 +4345,10 @@ let inputPreventDefault = true;
|
|
|
4280
4345
|
* @memberof Input */
|
|
4281
4346
|
let gamepadPrimary = 0;
|
|
4282
4347
|
|
|
4348
|
+
/** True if a touch device has been detected
|
|
4349
|
+
* @memberof Input */
|
|
4350
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
4351
|
+
|
|
4283
4352
|
/** Prevents input continuing to the default browser handling
|
|
4284
4353
|
* This is useful to disable for html menus so the browser can handle input normally
|
|
4285
4354
|
* @param {boolean} preventDefault
|
|
@@ -4478,10 +4547,6 @@ function gamepadStickCount(gamepad=gamepadPrimary)
|
|
|
4478
4547
|
return gamepadStickData[gamepad]?.length ?? 0;
|
|
4479
4548
|
}
|
|
4480
4549
|
|
|
4481
|
-
/** True if a touch device has been detected
|
|
4482
|
-
* @memberof Input */
|
|
4483
|
-
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
4484
|
-
|
|
4485
4550
|
///////////////////////////////////////////////////////////////////////////////
|
|
4486
4551
|
|
|
4487
4552
|
/** Pulse the vibration hardware if it exists
|
|
@@ -4490,7 +4555,7 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
|
4490
4555
|
function vibrate(pattern=100)
|
|
4491
4556
|
{
|
|
4492
4557
|
ASSERT(isNumber(pattern) || isArray(pattern), 'pattern must be a number or array');
|
|
4493
|
-
vibrateEnable && !headlessMode && navigator
|
|
4558
|
+
vibrateEnable && !headlessMode && navigator?.vibrate?.(pattern);
|
|
4494
4559
|
}
|
|
4495
4560
|
|
|
4496
4561
|
/** Cancel any ongoing vibration
|
|
@@ -4608,8 +4673,8 @@ function inputInit()
|
|
|
4608
4673
|
}
|
|
4609
4674
|
function onMouseUp(e)
|
|
4610
4675
|
{
|
|
4611
|
-
if (isTouchDevice && touchInputEnable)
|
|
4612
|
-
|
|
4676
|
+
if (isTouchDevice && touchInputEnable) return;
|
|
4677
|
+
|
|
4613
4678
|
inputData[0][e.button] = (inputData[0][e.button]&2) | 4;
|
|
4614
4679
|
}
|
|
4615
4680
|
function onMouseMove(e)
|
|
@@ -4795,8 +4860,7 @@ function inputUpdate()
|
|
|
4795
4860
|
// update touch gamepad if enabled
|
|
4796
4861
|
if (touchGamepadEnable && isTouchDevice)
|
|
4797
4862
|
{
|
|
4798
|
-
if (!touchGamepadTimer.isSet())
|
|
4799
|
-
return;
|
|
4863
|
+
if (!touchGamepadTimer.isSet()) return;
|
|
4800
4864
|
|
|
4801
4865
|
// read virtual analog stick
|
|
4802
4866
|
gamepadPrimary = 0; // touch gamepad uses index 0
|
|
@@ -4834,12 +4898,10 @@ function inputUpdate()
|
|
|
4834
4898
|
}
|
|
4835
4899
|
|
|
4836
4900
|
// return if gamepads are disabled or not supported
|
|
4837
|
-
if (!gamepadsEnable || !navigator || !navigator.getGamepads)
|
|
4838
|
-
return;
|
|
4901
|
+
if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
|
|
4839
4902
|
|
|
4840
4903
|
// only poll gamepads when focused or in debug mode
|
|
4841
|
-
if (!debug && !document.hasFocus())
|
|
4842
|
-
return;
|
|
4904
|
+
if (!debug && !document.hasFocus()) return;
|
|
4843
4905
|
|
|
4844
4906
|
// poll gamepads
|
|
4845
4907
|
const maxGamepads = 8;
|
|
@@ -4876,8 +4938,7 @@ function inputUpdate()
|
|
|
4876
4938
|
data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
4877
4939
|
|
|
4878
4940
|
// check for any input on this gamepad, analog must be full press
|
|
4879
|
-
if (button.pressed)
|
|
4880
|
-
if (!button.value || button.value > .9)
|
|
4941
|
+
if (button.pressed && (!button.value || button.value > .9))
|
|
4881
4942
|
hadInput = true;
|
|
4882
4943
|
}
|
|
4883
4944
|
|
|
@@ -4906,7 +4967,7 @@ function inputUpdate()
|
|
|
4906
4967
|
}
|
|
4907
4968
|
|
|
4908
4969
|
// copy dpad to left analog stick when pressed
|
|
4909
|
-
if (gamepadDirectionEmulateStick &&
|
|
4970
|
+
if (gamepadDirectionEmulateStick && (dpad.x || dpad.y))
|
|
4910
4971
|
sticks[0] = dpad.clampLength();
|
|
4911
4972
|
}
|
|
4912
4973
|
|
|
@@ -4935,13 +4996,11 @@ function inputRender()
|
|
|
4935
4996
|
function touchGamepadRender()
|
|
4936
4997
|
{
|
|
4937
4998
|
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
4938
|
-
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
4939
|
-
return;
|
|
4999
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet()) return;
|
|
4940
5000
|
|
|
4941
5001
|
// fade off when not touching or paused
|
|
4942
5002
|
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
4943
|
-
if (!alpha || paused)
|
|
4944
|
-
return;
|
|
5003
|
+
if (!alpha || paused) return;
|
|
4945
5004
|
|
|
4946
5005
|
// setup the canvas
|
|
4947
5006
|
const context = mainContext;
|
|
@@ -5094,8 +5153,7 @@ class Sound
|
|
|
5094
5153
|
{
|
|
5095
5154
|
// remove randomness so it can be applied on playback
|
|
5096
5155
|
const randomnessIndex = 1, defaultRandomness = .05;
|
|
5097
|
-
this.randomness = zzfxSound[randomnessIndex]
|
|
5098
|
-
zzfxSound[randomnessIndex] : defaultRandomness;
|
|
5156
|
+
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
5099
5157
|
zzfxSound[randomnessIndex] = 0;
|
|
5100
5158
|
|
|
5101
5159
|
// generate the zzfx samples
|
|
@@ -5175,7 +5233,7 @@ class Sound
|
|
|
5175
5233
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
5176
5234
|
*/
|
|
5177
5235
|
getDuration()
|
|
5178
|
-
{ return this.sampleChannels
|
|
5236
|
+
{ return this.sampleChannels?.[0].length / this.sampleRate || 0; }
|
|
5179
5237
|
|
|
5180
5238
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
5181
5239
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -5267,8 +5325,7 @@ class SoundWave extends Sound
|
|
|
5267
5325
|
this.sampleRate = audioBuffer.sampleRate;
|
|
5268
5326
|
this.sampleChannels = sampleChannels;
|
|
5269
5327
|
this.loadedPercent = 1;
|
|
5270
|
-
|
|
5271
|
-
this.onloadCallback(this);
|
|
5328
|
+
this.onloadCallback?.(this);
|
|
5272
5329
|
}
|
|
5273
5330
|
}
|
|
5274
5331
|
|
|
@@ -5471,7 +5528,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
5471
5528
|
|
|
5472
5529
|
/** Stop all queued speech
|
|
5473
5530
|
* @memberof Audio */
|
|
5474
|
-
function speakStop() {speechSynthesis
|
|
5531
|
+
function speakStop() {speechSynthesis?.cancel();}
|
|
5475
5532
|
|
|
5476
5533
|
/** Get frequency of a note on a musical scale
|
|
5477
5534
|
* @param {number} semitoneOffset - How many semitones away from the root note
|
|
@@ -5730,7 +5787,7 @@ function tileCollisionGetData(pos)
|
|
|
5730
5787
|
|
|
5731
5788
|
/** Check if a tile layer collides with another object
|
|
5732
5789
|
* @param {Vector2} pos
|
|
5733
|
-
* @param {Vector2} [size=(
|
|
5790
|
+
* @param {Vector2} [size=vec2()]
|
|
5734
5791
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
5735
5792
|
* @param {boolean} [solidOnly] - Only check solid layers if true
|
|
5736
5793
|
* @return {TileCollisionLayer}
|
|
@@ -5848,20 +5905,20 @@ function tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLa
|
|
|
5848
5905
|
class TileLayerData
|
|
5849
5906
|
{
|
|
5850
5907
|
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
5851
|
-
* @param {number} [tile]
|
|
5908
|
+
* @param {number} [tile] - The tile to use, untextured if undefined
|
|
5852
5909
|
* @param {number} [direction] - Integer direction of tile, in 90 degree increments
|
|
5853
|
-
* @param {boolean} [mirror]
|
|
5854
|
-
* @param {Color} [color]
|
|
5910
|
+
* @param {boolean} [mirror] - If the tile should be mirrored along the x axis
|
|
5911
|
+
* @param {Color} [color] - Color of the tile */
|
|
5855
5912
|
constructor(tile, direction=0, mirror=false, color=new Color)
|
|
5856
5913
|
{
|
|
5857
|
-
/** @property {number}
|
|
5858
|
-
this.tile
|
|
5859
|
-
/** @property {number}
|
|
5914
|
+
/** @property {number} - The tile to use, untextured if undefined */
|
|
5915
|
+
this.tile = tile;
|
|
5916
|
+
/** @property {number} - Integer direction of tile, in 90 degree increments */
|
|
5860
5917
|
this.direction = direction;
|
|
5861
5918
|
/** @property {boolean} - If the tile should be mirrored along the x axis */
|
|
5862
|
-
this.mirror
|
|
5863
|
-
/** @property {Color}
|
|
5864
|
-
this.color
|
|
5919
|
+
this.mirror = mirror;
|
|
5920
|
+
/** @property {Color} - Color of the tile */
|
|
5921
|
+
this.color = color.copy();
|
|
5865
5922
|
}
|
|
5866
5923
|
|
|
5867
5924
|
/** Set this tile to clear, it will not be rendered */
|
|
@@ -5872,7 +5929,7 @@ class TileLayerData
|
|
|
5872
5929
|
/**
|
|
5873
5930
|
* Canvas Layer - cached off screen rendering system
|
|
5874
5931
|
* - Contains an offscreen canvas that can be rendered to
|
|
5875
|
-
* - WebGL rendering is optional, call
|
|
5932
|
+
* - WebGL rendering is optional, call updateWebGL to enable/update
|
|
5876
5933
|
* @extends EngineObject
|
|
5877
5934
|
* @memberof TileLayers
|
|
5878
5935
|
* @example
|
|
@@ -5886,8 +5943,9 @@ class CanvasLayer extends EngineObject
|
|
|
5886
5943
|
* @param {number} [angle] - Angle the layer is rotated by
|
|
5887
5944
|
* @param {number} [renderOrder] - Objects sorted by renderOrder
|
|
5888
5945
|
* @param {Vector2} [canvasSize] - Default size of canvas, can be changed later
|
|
5946
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
5889
5947
|
*/
|
|
5890
|
-
constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512))
|
|
5948
|
+
constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512), useWebGL=glEnable)
|
|
5891
5949
|
{
|
|
5892
5950
|
ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
|
|
5893
5951
|
super(position, size, undefined, angle, WHITE, renderOrder);
|
|
@@ -5897,13 +5955,10 @@ class CanvasLayer extends EngineObject
|
|
|
5897
5955
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
|
|
5898
5956
|
this.context = this.canvas?.getContext('2d');
|
|
5899
5957
|
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
5900
|
-
const useWebGL = false; // do not use webgl by default
|
|
5901
5958
|
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
5902
|
-
/** @property {boolean} - True if WebGL texture needs to be refreshed */
|
|
5903
|
-
this.refreshWebGL = false;
|
|
5904
5959
|
|
|
5905
5960
|
// disable physics by default
|
|
5906
|
-
this.mass =
|
|
5961
|
+
this.mass = 0;
|
|
5907
5962
|
}
|
|
5908
5963
|
|
|
5909
5964
|
/** Destroy this canvas layer */
|
|
@@ -5918,7 +5973,7 @@ class CanvasLayer extends EngineObject
|
|
|
5918
5973
|
// Render the layer, called automatically by the engine
|
|
5919
5974
|
render()
|
|
5920
5975
|
{
|
|
5921
|
-
this.draw(this.pos, this.size, this.
|
|
5976
|
+
this.draw(this.pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
5922
5977
|
}
|
|
5923
5978
|
|
|
5924
5979
|
/** Draw this canvas layer centered in world space, with color applied if using WebGL
|
|
@@ -5931,103 +5986,54 @@ class CanvasLayer extends EngineObject
|
|
|
5931
5986
|
* @param {boolean} [screenSpace] - If true the pos and size are in screen space
|
|
5932
5987
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
5933
5988
|
* @memberof Draw */
|
|
5934
|
-
draw(pos, size,
|
|
5989
|
+
draw(pos, size, color=WHITE, angle=0, mirror=false, additiveColor, screenSpace=false, context)
|
|
5935
5990
|
{
|
|
5936
|
-
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
5937
|
-
if (useWebGL && this.refreshWebGL)
|
|
5938
|
-
{
|
|
5939
|
-
// update the WebGL texture
|
|
5940
|
-
this.textureInfo.createWebGLTexture();
|
|
5941
|
-
this.refreshWebGL = false;
|
|
5942
|
-
}
|
|
5943
|
-
|
|
5944
5991
|
// draw the canvas layer as a single tile that uses the whole texture
|
|
5945
5992
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
5993
|
+
const useWebGL = this.hasWebGL();
|
|
5946
5994
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
5947
5995
|
}
|
|
5948
5996
|
|
|
5949
|
-
/**
|
|
5950
|
-
* @callback Canvas2DDrawCallback - Function that draws to a canvas 2D context
|
|
5951
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
5952
|
-
* @memberof TileLayers
|
|
5953
|
-
*/
|
|
5954
|
-
|
|
5955
|
-
/** Draw onto the layer canvas in world space (bypass WebGL)
|
|
5997
|
+
/** Draw a tile onto the layer canvas in world space
|
|
5956
5998
|
* @param {Vector2} pos
|
|
5957
|
-
* @param {Vector2} size
|
|
5958
|
-
* @param {
|
|
5959
|
-
* @param {
|
|
5960
|
-
* @param {
|
|
5961
|
-
|
|
5999
|
+
* @param {Vector2} [size=vec2(1)]
|
|
6000
|
+
* @param {TileInfo} [tileInfo]
|
|
6001
|
+
* @param {Color} [color=WHITE]
|
|
6002
|
+
* @param {number} [angle]
|
|
6003
|
+
* @param {boolean} [mirror] */
|
|
6004
|
+
drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle=0, mirror=false)
|
|
5962
6005
|
{
|
|
5963
|
-
if (!this.context) return;
|
|
5964
|
-
|
|
5965
|
-
const context = this.context;
|
|
5966
|
-
context.save();
|
|
5967
6006
|
pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
|
|
5968
6007
|
size = size.multiply(this.tileInfo.size);
|
|
5969
|
-
|
|
5970
|
-
context.rotate(angle);
|
|
5971
|
-
context.scale(mirror ? -size.x : size.x, size.y);
|
|
5972
|
-
drawFunction(context);
|
|
5973
|
-
context.restore();
|
|
5974
|
-
}
|
|
6008
|
+
pos.y = this.canvas.height - pos.y;
|
|
5975
6009
|
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
this.drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
5986
|
-
{
|
|
5987
|
-
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
5988
|
-
if (textureInfo)
|
|
5989
|
-
{
|
|
5990
|
-
context.globalAlpha = color.a; // only alpha is supported
|
|
5991
|
-
context.drawImage(textureInfo.image,
|
|
5992
|
-
tileInfo.pos.x, tileInfo.pos.y,
|
|
5993
|
-
tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
|
|
5994
|
-
context.globalAlpha = 1;
|
|
5995
|
-
}
|
|
5996
|
-
else
|
|
5997
|
-
{
|
|
5998
|
-
// untextured
|
|
5999
|
-
context.fillStyle = color.toString();
|
|
6000
|
-
context.fillRect(-.5, -.5, 1, 1);
|
|
6001
|
-
}
|
|
6002
|
-
});
|
|
6010
|
+
// draw the tile onto the layer canvas
|
|
6011
|
+
const oldMainCanvasSize = mainCanvasSize;
|
|
6012
|
+
mainCanvasSize = vec2(this.canvas.width, this.canvas.height);
|
|
6013
|
+
const useWebGL = this.hasWebGL();
|
|
6014
|
+
useWebGL && glSetRenderTarget(this.textureInfo.glTexture);
|
|
6015
|
+
const drawContext = useWebGL ? undefined : this.context;
|
|
6016
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, undefined, useWebGL, true, drawContext);
|
|
6017
|
+
useWebGL && glSetRenderTarget();
|
|
6018
|
+
mainCanvasSize = oldMainCanvasSize;
|
|
6003
6019
|
}
|
|
6004
6020
|
|
|
6005
6021
|
/** Draw a rectangle onto the layer canvas in world space
|
|
6006
6022
|
* @param {Vector2} pos
|
|
6007
|
-
* @param {Vector2} [size=(1
|
|
6008
|
-
* @param {Color} [color=
|
|
6009
|
-
* @param {number} [angle
|
|
6023
|
+
* @param {Vector2} [size=vec2(1)]
|
|
6024
|
+
* @param {Color} [color=WHITE]
|
|
6025
|
+
* @param {number} [angle] */
|
|
6010
6026
|
drawRect(pos, size, color, angle)
|
|
6011
6027
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
6012
6028
|
|
|
6013
|
-
/** Create
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
*/
|
|
6017
|
-
useWebGL(enable=true, immediate=false)
|
|
6018
|
-
{
|
|
6019
|
-
if (!immediate && enable && this.textureInfo.hasWebGL())
|
|
6020
|
-
{
|
|
6021
|
-
// refresh the texture when needed
|
|
6022
|
-
this.refreshWebGL = true;
|
|
6023
|
-
return;
|
|
6024
|
-
}
|
|
6029
|
+
/** Create WebGL texture if necessary and copy layer canvas to it */
|
|
6030
|
+
updateWebGL()
|
|
6031
|
+
{ this.textureInfo.createWebGLTexture(); }
|
|
6025
6032
|
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
}
|
|
6033
|
+
/** Check if this layer is using WebGL
|
|
6034
|
+
* @return {boolean} */
|
|
6035
|
+
hasWebGL()
|
|
6036
|
+
{ return glEnable && this.textureInfo.hasWebGL(); }
|
|
6031
6037
|
}
|
|
6032
6038
|
|
|
6033
6039
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -6046,50 +6052,67 @@ class CanvasLayer extends EngineObject
|
|
|
6046
6052
|
class TileLayer extends CanvasLayer
|
|
6047
6053
|
{
|
|
6048
6054
|
/** Create a tile layer object
|
|
6049
|
-
* @param {Vector2} position
|
|
6050
|
-
* @param {Vector2} size
|
|
6051
|
-
* @param {TileInfo} [tileInfo]
|
|
6055
|
+
* @param {Vector2} position - World space position
|
|
6056
|
+
* @param {Vector2} size - World space size
|
|
6057
|
+
* @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
|
|
6052
6058
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6053
6059
|
*/
|
|
6054
6060
|
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
6055
6061
|
{
|
|
6056
6062
|
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
+
const useWebGL = true;
|
|
6064
|
+
super(position, size, 0, renderOrder, canvasSize, useWebGL);
|
|
6065
|
+
|
|
6066
|
+
/** @property {TileInfo} - Default tile info for layer */
|
|
6067
|
+
this.tileInfo = undefined;
|
|
6068
|
+
/** @property {Array<TileLayerData>} - Default tile info for layer */
|
|
6063
6069
|
this.data = [];
|
|
6064
|
-
|
|
6065
|
-
|
|
6070
|
+
/** @property {boolean} - Is this layer using a webgl texture? */
|
|
6071
|
+
this.isUsingWebGL = false;
|
|
6066
6072
|
|
|
6067
6073
|
if (headlessMode)
|
|
6068
6074
|
{
|
|
6069
|
-
// disable rendering
|
|
6070
|
-
this.
|
|
6071
|
-
this.
|
|
6072
|
-
this.redrawStart
|
|
6073
|
-
this.redrawEnd
|
|
6074
|
-
this.drawTileData
|
|
6075
|
-
this.
|
|
6076
|
-
this.
|
|
6075
|
+
// disable rendering in headless mode
|
|
6076
|
+
this.render = () => {};
|
|
6077
|
+
this.redraw = () => {};
|
|
6078
|
+
this.redrawStart = () => {};
|
|
6079
|
+
this.redrawEnd = () => {};
|
|
6080
|
+
this.drawTileData = () => {};
|
|
6081
|
+
this.redrawTileData = () => {};
|
|
6082
|
+
this.drawLayerTile = () => {};
|
|
6083
|
+
this.drawLayerRect = () => {};
|
|
6084
|
+
this.clearLayerRect = () => {};
|
|
6085
|
+
return;
|
|
6077
6086
|
}
|
|
6087
|
+
|
|
6088
|
+
if (tileInfo)
|
|
6089
|
+
{
|
|
6090
|
+
// set tile info
|
|
6091
|
+
this.tileInfo = tileInfo.frame(0);
|
|
6092
|
+
this.tileInfo.bleed = 0; // disable bleed for tile layers
|
|
6093
|
+
}
|
|
6094
|
+
|
|
6095
|
+
// init tile data
|
|
6096
|
+
for (let j = this.size.area(); j--;)
|
|
6097
|
+
this.data.push(new TileLayerData);
|
|
6078
6098
|
}
|
|
6079
6099
|
|
|
6080
6100
|
/** Set data at a given position in the array
|
|
6081
6101
|
* @param {Vector2} layerPos - Local position in array
|
|
6082
|
-
* @param {TileLayerData} data
|
|
6102
|
+
* @param {TileLayerData} data - Data to set
|
|
6083
6103
|
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
6084
6104
|
setData(layerPos, data, redraw=false)
|
|
6085
6105
|
{
|
|
6106
|
+
layerPos = layerPos.floor();
|
|
6086
6107
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6087
6108
|
ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6109
|
+
|
|
6110
|
+
if (!layerPos.arrayCheck(this.size)) return;
|
|
6111
|
+
this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
|
|
6112
|
+
|
|
6113
|
+
if (!redraw) return;
|
|
6114
|
+
const isRedraw = drawContext === this.context;
|
|
6115
|
+
isRedraw ? this.drawTileData(layerPos) : this.redrawTileData(layerPos);
|
|
6093
6116
|
}
|
|
6094
6117
|
|
|
6095
6118
|
/** Get data at a given position in the array
|
|
@@ -6098,7 +6121,18 @@ class TileLayer extends CanvasLayer
|
|
|
6098
6121
|
getData(layerPos)
|
|
6099
6122
|
{
|
|
6100
6123
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6101
|
-
return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
|
|
6124
|
+
return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
|
|
6125
|
+
}
|
|
6126
|
+
|
|
6127
|
+
// Update the tile layer, refresh texture if needed
|
|
6128
|
+
update()
|
|
6129
|
+
{
|
|
6130
|
+
if (!glEnable && this.isUsingWebGL)
|
|
6131
|
+
{
|
|
6132
|
+
// redraw the layer if webgl was disabled or context lost
|
|
6133
|
+
this.isUsingWebGL = false;
|
|
6134
|
+
this.redraw();
|
|
6135
|
+
}
|
|
6102
6136
|
}
|
|
6103
6137
|
|
|
6104
6138
|
// Render the tile layer, called automatically by the engine
|
|
@@ -6106,40 +6140,35 @@ class TileLayer extends CanvasLayer
|
|
|
6106
6140
|
{
|
|
6107
6141
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
6108
6142
|
|
|
6109
|
-
if (this.refreshWebGL)
|
|
6110
|
-
{
|
|
6111
|
-
// update the WebGL texture
|
|
6112
|
-
this.textureInfo.createWebGLTexture();
|
|
6113
|
-
this.refreshWebGL = false;
|
|
6114
|
-
}
|
|
6115
|
-
|
|
6116
|
-
// draw the tile layer as a single tile
|
|
6117
|
-
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
6118
6143
|
const size = this.drawSize || this.size;
|
|
6119
6144
|
const pos = this.pos.add(size.scale(.5));
|
|
6120
|
-
|
|
6121
|
-
drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
|
|
6145
|
+
this.draw(pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
6122
6146
|
}
|
|
6123
6147
|
|
|
6148
|
+
/** Called after this layer is redrawn, does nothing by default */
|
|
6149
|
+
onRedraw() {}
|
|
6150
|
+
|
|
6124
6151
|
/** Draw all the tile data to an offscreen canvas
|
|
6125
|
-
* - This may be slow
|
|
6152
|
+
* - This may be slow if not using webgl but only needs to be done once */
|
|
6126
6153
|
redraw()
|
|
6127
6154
|
{
|
|
6128
6155
|
this.redrawStart(true);
|
|
6129
6156
|
for (let x = this.size.x; x--;)
|
|
6130
6157
|
for (let y = this.size.y; y--;)
|
|
6131
6158
|
this.drawTileData(vec2(x,y), false);
|
|
6159
|
+
this.hasWebGL() && glFlush();
|
|
6160
|
+
this.onRedraw();
|
|
6132
6161
|
this.redrawEnd();
|
|
6133
|
-
this.useWebGL();
|
|
6134
6162
|
}
|
|
6135
6163
|
|
|
6136
6164
|
/** Call to start the redraw process
|
|
6137
|
-
* - This can be used to manually update
|
|
6165
|
+
* - This can be used to manually update parts of the level
|
|
6138
6166
|
* @param {boolean} [clear] - Should it clear the canvas before drawing */
|
|
6139
6167
|
redrawStart(clear=false)
|
|
6140
6168
|
{
|
|
6141
6169
|
if (!this.context) return;
|
|
6142
|
-
|
|
6170
|
+
ASSERT(drawContext !== this.context);
|
|
6171
|
+
|
|
6143
6172
|
// save current render settings
|
|
6144
6173
|
/** @type {[CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
|
|
6145
6174
|
this.savedRenderSettings = [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
|
|
@@ -6147,33 +6176,37 @@ class TileLayer extends CanvasLayer
|
|
|
6147
6176
|
// set the draw canvas and context to this layer
|
|
6148
6177
|
// use camera settings to match this layer's canvas
|
|
6149
6178
|
drawContext = this.context;
|
|
6150
|
-
|
|
6151
|
-
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
6152
|
-
cameraScale = tileSize.x;
|
|
6153
|
-
canvasClearColor = CLEAR_BLACK;
|
|
6179
|
+
const tileSize = this.tileInfo?.size ?? vec2(1);
|
|
6154
6180
|
mainCanvasSize = this.size.multiply(tileSize);
|
|
6155
|
-
|
|
6181
|
+
canvasClearColor = CLEAR_BLACK;
|
|
6182
|
+
cameraPos = this.size.multiply(tileSize).scale(.5);
|
|
6183
|
+
cameraScale = 1;
|
|
6184
|
+
|
|
6185
|
+
// set render target to this layer
|
|
6186
|
+
this.isUsingWebGL = this.hasWebGL();
|
|
6187
|
+
if (this.isUsingWebGL)
|
|
6188
|
+
glSetRenderTarget(this.textureInfo.glTexture, clear);
|
|
6189
|
+
else
|
|
6156
6190
|
{
|
|
6157
|
-
//
|
|
6158
|
-
this.
|
|
6159
|
-
|
|
6191
|
+
// disable smoothing for pixel art
|
|
6192
|
+
this.context.imageSmoothingEnabled = !tilesPixelated;
|
|
6193
|
+
if (clear)
|
|
6194
|
+
{
|
|
6195
|
+
// clear and set size
|
|
6196
|
+
this.canvas.width = mainCanvasSize.x;
|
|
6197
|
+
this.canvas.height = mainCanvasSize.y;
|
|
6198
|
+
}
|
|
6160
6199
|
}
|
|
6161
|
-
|
|
6162
|
-
// disable smoothing for pixel art
|
|
6163
|
-
drawContext.imageSmoothingEnabled = !tilesPixelated;
|
|
6164
|
-
|
|
6165
|
-
// setup gl rendering if enabled
|
|
6166
|
-
glPreRender();
|
|
6167
6200
|
}
|
|
6168
6201
|
|
|
6169
6202
|
/** Call to end the redraw process */
|
|
6170
6203
|
redrawEnd()
|
|
6171
6204
|
{
|
|
6172
6205
|
if (!this.context) return;
|
|
6206
|
+
ASSERT(drawContext === this.context);
|
|
6173
6207
|
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
//saveCanvas(this.canvas);
|
|
6208
|
+
if (glEnable && this.textureInfo.glTexture)
|
|
6209
|
+
glSetRenderTarget();
|
|
6177
6210
|
|
|
6178
6211
|
// set stuff back to normal
|
|
6179
6212
|
[drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
|
|
@@ -6188,24 +6221,75 @@ class TileLayer extends CanvasLayer
|
|
|
6188
6221
|
drawTileData(layerPos, clear=true)
|
|
6189
6222
|
{
|
|
6190
6223
|
if (!this.context) return;
|
|
6224
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
6191
6225
|
|
|
6192
|
-
// clear out where the tile was, for
|
|
6193
|
-
const
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
const pos = layerPos.multiply(s);
|
|
6197
|
-
this.context.clearRect(pos.x, this.canvas.height-pos.y, s.x, -s.y);
|
|
6198
|
-
}
|
|
6226
|
+
// clear out where the tile was, can be skipped for fully opaque tiles
|
|
6227
|
+
const drawSize = this.tileInfo?.size ?? vec2(1);
|
|
6228
|
+
const drawPos = layerPos.multiply(drawSize);
|
|
6229
|
+
clear && this.clearLayerRect(drawPos, drawSize);
|
|
6199
6230
|
|
|
6200
6231
|
// draw the tile if it has layer data
|
|
6201
6232
|
const d = this.getData(layerPos);
|
|
6202
|
-
if (d.tile
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6233
|
+
if (!d.tile) return;
|
|
6234
|
+
|
|
6235
|
+
const tileInfo = this.tileInfo && this.tileInfo.tile(d.tile);
|
|
6236
|
+
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
6237
|
+
}
|
|
6238
|
+
|
|
6239
|
+
/** Draw the tile at a given position in the tile grid
|
|
6240
|
+
* This can be used to clear tiles when they are destroyed
|
|
6241
|
+
* For better performance use drawTileData inside a redrawStart/End block
|
|
6242
|
+
* @param {Vector2} layerPos
|
|
6243
|
+
* @param {boolean} [clear] - should the old tile be cleared
|
|
6244
|
+
*/
|
|
6245
|
+
redrawTileData(layerPos, clear=true)
|
|
6246
|
+
{
|
|
6247
|
+
if (!this.context) return;
|
|
6248
|
+
ASSERT(drawContext !== this.context, 'redrawStart() should not be active when calling redrawTileData(), instead use drawTileData()');
|
|
6249
|
+
|
|
6250
|
+
this.redrawStart();
|
|
6251
|
+
this.drawTileData(layerPos, clear);
|
|
6252
|
+
this.redrawEnd();
|
|
6253
|
+
}
|
|
6254
|
+
|
|
6255
|
+
/** Draw textured tile in layer space
|
|
6256
|
+
* @param {Vector2} pos - Position in pixel coordinates
|
|
6257
|
+
* @param {Vector2} [size=vec2(1)] - Size of the tile
|
|
6258
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
6259
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
6260
|
+
* @param {number} [angle] - Angle to rotate by
|
|
6261
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
6262
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any */
|
|
6263
|
+
drawLayerTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
6264
|
+
angle=0, mirror, additiveColor)
|
|
6265
|
+
{
|
|
6266
|
+
const drawPos = pos.add(size.scale(.5));
|
|
6267
|
+
drawTile(drawPos, size, tileInfo, color, angle, mirror, additiveColor);
|
|
6268
|
+
}
|
|
6269
|
+
|
|
6270
|
+
/** Clear a rectangle in layer space
|
|
6271
|
+
* @param {Vector2} pos
|
|
6272
|
+
* @param {Vector2} size
|
|
6273
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
6274
|
+
* @param {number} [angle] - Angle to rotate by
|
|
6275
|
+
*/
|
|
6276
|
+
drawLayerRect(pos, size, color, angle=0)
|
|
6277
|
+
{ this.drawLayerTile(pos, size, undefined, color, angle); }
|
|
6278
|
+
|
|
6279
|
+
/** Clear a rectangle in layer space
|
|
6280
|
+
* @param {Vector2} pos - position in pixel coordinates
|
|
6281
|
+
* @param {Vector2} size
|
|
6282
|
+
*/
|
|
6283
|
+
clearLayerRect(pos, size)
|
|
6284
|
+
{
|
|
6285
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before clearing tiles');
|
|
6286
|
+
|
|
6287
|
+
const x = pos.x, y = this.canvas.height - pos.y - size.y;
|
|
6288
|
+
const useWebGL = this.hasWebGL();
|
|
6289
|
+
if (useWebGL)
|
|
6290
|
+
glClearRect(x, y, size.x, size.y);
|
|
6291
|
+
else
|
|
6292
|
+
this.context.clearRect(x, y, size.x, size.y);
|
|
6209
6293
|
}
|
|
6210
6294
|
}
|
|
6211
6295
|
|
|
@@ -6214,7 +6298,6 @@ class TileLayer extends CanvasLayer
|
|
|
6214
6298
|
* Tile Collision Layer - a tile layer with collision
|
|
6215
6299
|
* - adds collision data and functions to TileLayer
|
|
6216
6300
|
* - there can be multiple tile collision layers
|
|
6217
|
-
* - tile collision layers should not overlap each other
|
|
6218
6301
|
* @extends TileLayer
|
|
6219
6302
|
* @memberof TileLayers
|
|
6220
6303
|
*/
|
|
@@ -6286,7 +6369,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6286
6369
|
|
|
6287
6370
|
/** Check if collision with another object should occur
|
|
6288
6371
|
* @param {Vector2} pos
|
|
6289
|
-
* @param {Vector2} [size=(
|
|
6372
|
+
* @param {Vector2} [size=vec2()]
|
|
6290
6373
|
* @param {EngineObject} [object]
|
|
6291
6374
|
* @return {boolean} */
|
|
6292
6375
|
collisionTest(pos, size=new Vector2, object)
|
|
@@ -6606,7 +6689,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6606
6689
|
particle.mirror = randBool();
|
|
6607
6690
|
|
|
6608
6691
|
// call particle create callback
|
|
6609
|
-
this.particleCreateCallback
|
|
6692
|
+
this.particleCreateCallback?.(particle);
|
|
6610
6693
|
|
|
6611
6694
|
// return the newly created particle
|
|
6612
6695
|
return particle;
|
|
@@ -6691,7 +6774,7 @@ class Particle extends EngineObject
|
|
|
6691
6774
|
const c = this.colorEnd;
|
|
6692
6775
|
this.color.set(c.r, c.g, c.b, c.a);
|
|
6693
6776
|
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
6694
|
-
this.destroyCallback
|
|
6777
|
+
this.destroyCallback?.(this);
|
|
6695
6778
|
this.destroyed = 1;
|
|
6696
6779
|
}
|
|
6697
6780
|
}
|
|
@@ -6969,7 +7052,7 @@ let glContext;
|
|
|
6969
7052
|
let glAntialias = true;
|
|
6970
7053
|
|
|
6971
7054
|
// WebGL internal variables not exposed to documentation
|
|
6972
|
-
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
|
|
7055
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glInstancedVAO, glPolyVAO, glFramebuffer, glRenderTarget, glCanBeEnabled = true;
|
|
6973
7056
|
|
|
6974
7057
|
// WebGL internal constants
|
|
6975
7058
|
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
@@ -7045,7 +7128,7 @@ function glInit(rootElement)
|
|
|
7045
7128
|
// setup instanced rendering shader program
|
|
7046
7129
|
glShader = glCreateProgram(
|
|
7047
7130
|
'#version 300 es\n' + // specify GLSL ES version
|
|
7048
|
-
'precision highp float;'+ // use highp for
|
|
7131
|
+
'precision highp float;'+ // use highp for accuracy
|
|
7049
7132
|
'uniform mat4 m;'+ // transform matrix
|
|
7050
7133
|
'in vec2 g;'+ // in: geometry
|
|
7051
7134
|
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
@@ -7060,7 +7143,7 @@ function glInit(rootElement)
|
|
|
7060
7143
|
'}' // end of shader
|
|
7061
7144
|
,
|
|
7062
7145
|
'#version 300 es\n' + // specify GLSL ES version
|
|
7063
|
-
'precision highp float;'+ // use highp for
|
|
7146
|
+
'precision highp float;'+ // use highp for accuracy
|
|
7064
7147
|
'uniform sampler2D s;'+ // texture
|
|
7065
7148
|
'in vec2 v;'+ // in: uv
|
|
7066
7149
|
'in vec4 d,e;'+ // in: color, additiveColor
|
|
@@ -7098,45 +7181,62 @@ function glInit(rootElement)
|
|
|
7098
7181
|
glColorData = new Uint32Array(glInstanceData);
|
|
7099
7182
|
glArrayBuffer = glContext.createBuffer();
|
|
7100
7183
|
glGeometryBuffer = glContext.createBuffer();
|
|
7184
|
+
glFramebuffer = glContext.createFramebuffer();
|
|
7185
|
+
glBatchCount = 0;
|
|
7101
7186
|
|
|
7102
7187
|
// create the geometry buffer, triangle strip square
|
|
7103
|
-
const geometry = new Float32Array([
|
|
7188
|
+
const geometry = new Float32Array([0,0,1,0,0,1,1,1]);
|
|
7104
7189
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7105
7190
|
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
7191
|
+
|
|
7192
|
+
let offset, shader, stride;
|
|
7193
|
+
const initVertexAttrib = (name, type, typeSize, size, divisor=0)=>
|
|
7194
|
+
{
|
|
7195
|
+
const location = glContext.getAttribLocation(shader, name);
|
|
7196
|
+
const normalize = typeSize === 1;
|
|
7197
|
+
const fixedStride = typeSize && stride;
|
|
7198
|
+
glContext.enableVertexAttribArray(location);
|
|
7199
|
+
glContext.vertexAttribPointer(location, size, type, normalize, fixedStride, offset);
|
|
7200
|
+
glContext.vertexAttribDivisor(location, divisor);
|
|
7201
|
+
offset += size*typeSize;
|
|
7202
|
+
}
|
|
7203
|
+
|
|
7204
|
+
// setup VAO for instanced rendering
|
|
7205
|
+
glInstancedVAO = glContext.createVertexArray();
|
|
7206
|
+
glContext.bindVertexArray(glInstancedVAO);
|
|
7207
|
+
|
|
7208
|
+
// configure instanced vertex attributes
|
|
7209
|
+
offset = 0, shader = glShader, stride = gl_INSTANCE_BYTE_STRIDE;
|
|
7210
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7211
|
+
initVertexAttrib('g', glContext.FLOAT, 0, 2); // geometry
|
|
7212
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7213
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7214
|
+
initVertexAttrib('p', glContext.FLOAT, 4, 4, 1); // position & size
|
|
7215
|
+
initVertexAttrib('u', glContext.FLOAT, 4, 4, 1); // texture coords
|
|
7216
|
+
initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4, 1); // color
|
|
7217
|
+
initVertexAttrib('a', glContext.UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
7218
|
+
initVertexAttrib('r', glContext.FLOAT, 4, 1, 1); // rotation
|
|
7219
|
+
|
|
7220
|
+
// setup VAO for poly rendering
|
|
7221
|
+
glPolyVAO = glContext.createVertexArray();
|
|
7222
|
+
glContext.bindVertexArray(glPolyVAO);
|
|
7223
|
+
|
|
7224
|
+
// configure poly vertex attributes
|
|
7225
|
+
offset = 0, shader = glPolyShader, stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
7226
|
+
initVertexAttrib('p', glContext.FLOAT, 4, 2); // position
|
|
7227
|
+
initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7106
7228
|
}
|
|
7107
7229
|
}
|
|
7108
7230
|
|
|
7109
|
-
function glSetInstancedMode()
|
|
7231
|
+
function glSetInstancedMode(force=false)
|
|
7110
7232
|
{
|
|
7111
|
-
if (!glPolyMode) return;
|
|
7233
|
+
if (!force && !glPolyMode) return;
|
|
7112
7234
|
|
|
7113
7235
|
// setup instanced mode
|
|
7114
7236
|
glFlush();
|
|
7115
7237
|
glPolyMode = false;
|
|
7116
7238
|
glContext.useProgram(glShader);
|
|
7117
|
-
|
|
7118
|
-
// set vertex attributes
|
|
7119
|
-
let offset = 0;
|
|
7120
|
-
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
7121
|
-
{
|
|
7122
|
-
const location = glContext.getAttribLocation(glShader, name);
|
|
7123
|
-
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
7124
|
-
const divisor = typeSize && 1; // only if not geometry
|
|
7125
|
-
const normalize = typeSize === 1; // only if color
|
|
7126
|
-
glContext.enableVertexAttribArray(location);
|
|
7127
|
-
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
7128
|
-
glContext.vertexAttribDivisor(location, divisor);
|
|
7129
|
-
offset += size*typeSize;
|
|
7130
|
-
}
|
|
7131
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7132
|
-
initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
|
|
7133
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7134
|
-
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7135
|
-
initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
|
|
7136
|
-
initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
|
|
7137
|
-
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7138
|
-
initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
|
|
7139
|
-
initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
|
|
7239
|
+
glContext.bindVertexArray(glInstancedVAO);
|
|
7140
7240
|
}
|
|
7141
7241
|
|
|
7142
7242
|
function glSetPolyMode()
|
|
@@ -7147,36 +7247,30 @@ function glSetPolyMode()
|
|
|
7147
7247
|
glFlush();
|
|
7148
7248
|
glPolyMode = true;
|
|
7149
7249
|
glContext.useProgram(glPolyShader);
|
|
7150
|
-
|
|
7151
|
-
// set vertex attributes
|
|
7152
|
-
let offset = 0;
|
|
7153
|
-
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
7154
|
-
{
|
|
7155
|
-
const location = glContext.getAttribLocation(glPolyShader, name);
|
|
7156
|
-
const normalize = typeSize === 1; // only normalize if color
|
|
7157
|
-
const stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
7158
|
-
glContext.enableVertexAttribArray(location);
|
|
7159
|
-
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
7160
|
-
glContext.vertexAttribDivisor(location, 0);
|
|
7161
|
-
offset += size*typeSize;
|
|
7162
|
-
}
|
|
7163
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7164
|
-
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7165
|
-
initVertexAttribArray('p', glContext.FLOAT, 4, 2); // position
|
|
7166
|
-
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7250
|
+
glContext.bindVertexArray(glPolyVAO);
|
|
7167
7251
|
}
|
|
7168
7252
|
|
|
7169
7253
|
// Setup WebGL render each frame, called automatically by engine
|
|
7170
7254
|
// Also used by tile layer rendering when redrawing tiles
|
|
7171
|
-
function glPreRender()
|
|
7255
|
+
function glPreRender(clear=true)
|
|
7172
7256
|
{
|
|
7173
7257
|
if (!glEnable || !glContext) return;
|
|
7174
7258
|
|
|
7175
|
-
|
|
7176
|
-
|
|
7259
|
+
ASSERT(!glBatchCount, 'glPreRender called with unflushed batch.');
|
|
7260
|
+
|
|
7261
|
+
if (!glRenderTarget)
|
|
7262
|
+
{
|
|
7263
|
+
// set to same size as main canvas
|
|
7264
|
+
glCanvas.width = mainCanvasSize.x;
|
|
7265
|
+
glCanvas.height = mainCanvasSize.y;
|
|
7266
|
+
}
|
|
7267
|
+
glContext.viewport(0, 0, mainCanvasSize.x, mainCanvasSize.y);
|
|
7268
|
+
clear && glClearCanvas();
|
|
7177
7269
|
|
|
7178
7270
|
// build the transform matrix
|
|
7179
7271
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
7272
|
+
if (glRenderTarget)
|
|
7273
|
+
s.y = -s.y; // invert y when using render target
|
|
7180
7274
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
7181
7275
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
7182
7276
|
const ca = cos(cameraAngle);
|
|
@@ -7205,12 +7299,14 @@ function glPreRender()
|
|
|
7205
7299
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7206
7300
|
}
|
|
7207
7301
|
|
|
7302
|
+
// rebind the array buffer
|
|
7303
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7304
|
+
|
|
7208
7305
|
// start with additive blending off
|
|
7209
7306
|
glAdditive = glBatchAdditive = false;
|
|
7210
7307
|
|
|
7211
|
-
// force it to set instanced mode
|
|
7212
|
-
|
|
7213
|
-
glSetInstancedMode();
|
|
7308
|
+
// force it to set instanced mode
|
|
7309
|
+
glSetInstancedMode(true);
|
|
7214
7310
|
}
|
|
7215
7311
|
|
|
7216
7312
|
/** Clear the canvas and setup the viewport
|
|
@@ -7219,13 +7315,9 @@ function glClearCanvas()
|
|
|
7219
7315
|
{
|
|
7220
7316
|
if (!glContext) return;
|
|
7221
7317
|
|
|
7222
|
-
// clear
|
|
7223
|
-
glCanvas.width = mainCanvasSize.x;
|
|
7224
|
-
glCanvas.height = mainCanvasSize.y;
|
|
7225
|
-
glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
|
|
7318
|
+
// clear using the canvasClearColor
|
|
7226
7319
|
const color = canvasClearColor;
|
|
7227
|
-
|
|
7228
|
-
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
7320
|
+
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
7229
7321
|
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
7230
7322
|
}
|
|
7231
7323
|
|
|
@@ -7302,7 +7394,7 @@ function glCreateTexture(image)
|
|
|
7302
7394
|
// build the texture
|
|
7303
7395
|
const texture = glContext.createTexture();
|
|
7304
7396
|
let mipMap = false;
|
|
7305
|
-
if (image
|
|
7397
|
+
if (image?.width)
|
|
7306
7398
|
{
|
|
7307
7399
|
glSetTextureData(texture, image);
|
|
7308
7400
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
@@ -7323,7 +7415,9 @@ function glCreateTexture(image)
|
|
|
7323
7415
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, minFilter);
|
|
7324
7416
|
if (mipMap)
|
|
7325
7417
|
glContext.generateMipmap(glContext.TEXTURE_2D);
|
|
7326
|
-
|
|
7418
|
+
|
|
7419
|
+
// rebind active texture
|
|
7420
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7327
7421
|
return texture;
|
|
7328
7422
|
}
|
|
7329
7423
|
|
|
@@ -7346,10 +7440,12 @@ function glSetTextureData(texture, image)
|
|
|
7346
7440
|
if (!glContext) return;
|
|
7347
7441
|
|
|
7348
7442
|
// build the texture
|
|
7349
|
-
ASSERT(
|
|
7443
|
+
ASSERT(image?.width > 0, 'Invalid image data.');
|
|
7350
7444
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
7351
7445
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
7352
|
-
|
|
7446
|
+
|
|
7447
|
+
// rebind active texture
|
|
7448
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7353
7449
|
}
|
|
7354
7450
|
|
|
7355
7451
|
/** Tells WebGL to create or update the glTexture and start tracking it
|
|
@@ -7568,6 +7664,48 @@ function glDrawColoredPoints(points, pointColors)
|
|
|
7568
7664
|
glBatchCount += vertCount;
|
|
7569
7665
|
}
|
|
7570
7666
|
|
|
7667
|
+
/** Set the WebGL render target to the given texture or back to the canvas
|
|
7668
|
+
* @param {WebGLTexture} [texture] - a texture or undefined to use normal glCanvas
|
|
7669
|
+
* @param {boolean} [clear] - should the render target be cleared
|
|
7670
|
+
* @memberof WebGL */
|
|
7671
|
+
function glSetRenderTarget(texture, clear=false)
|
|
7672
|
+
{
|
|
7673
|
+
if (texture)
|
|
7674
|
+
{
|
|
7675
|
+
glRenderTarget = texture;
|
|
7676
|
+
glContext.bindFramebuffer(glContext.FRAMEBUFFER, glFramebuffer);
|
|
7677
|
+
glContext.framebufferTexture2D(glContext.FRAMEBUFFER,
|
|
7678
|
+
glContext.COLOR_ATTACHMENT0, glContext.TEXTURE_2D, texture, 0);
|
|
7679
|
+
glPreRender(clear);
|
|
7680
|
+
}
|
|
7681
|
+
else
|
|
7682
|
+
{
|
|
7683
|
+
glFlush();
|
|
7684
|
+
glRenderTarget = undefined;
|
|
7685
|
+
glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
|
|
7686
|
+
}
|
|
7687
|
+
}
|
|
7688
|
+
|
|
7689
|
+
/** Clear out a rectangle area of the WebGL canvas or render target
|
|
7690
|
+
* @param {number} x
|
|
7691
|
+
* @param {number} y
|
|
7692
|
+
* @param {number} width
|
|
7693
|
+
* @param {number} height
|
|
7694
|
+
* @memberof WebGL */
|
|
7695
|
+
function glClearRect(x, y, width, height)
|
|
7696
|
+
{
|
|
7697
|
+
if (!glEnable) return;
|
|
7698
|
+
|
|
7699
|
+
// Enable scissor test to clear only the specified area
|
|
7700
|
+
glContext.enable(glContext.SCISSOR_TEST);
|
|
7701
|
+
glContext.scissor(x, y, width, height);
|
|
7702
|
+
glContext.clearColor(0, 0, 0, 0);
|
|
7703
|
+
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
7704
|
+
glContext.disable(glContext.SCISSOR_TEST);
|
|
7705
|
+
}
|
|
7706
|
+
|
|
7707
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7708
|
+
|
|
7571
7709
|
// WebGL internal function to convert polygon to outline triangle strip
|
|
7572
7710
|
function glMakeOutline(points, width, wrap=true)
|
|
7573
7711
|
{
|
|
@@ -7701,23 +7839,20 @@ function glPolyStrip(points)
|
|
|
7701
7839
|
const a = points[i0], b = points[i1], c = points[i2];
|
|
7702
7840
|
|
|
7703
7841
|
// check if convex
|
|
7704
|
-
if (cross(a, b, c) < e)
|
|
7705
|
-
continue;
|
|
7842
|
+
if (cross(a, b, c) < e) continue;
|
|
7706
7843
|
|
|
7707
7844
|
// check if any other point is inside
|
|
7708
7845
|
let hasInside = false;
|
|
7709
7846
|
for (let j = 0; j < indices.length; j++)
|
|
7710
7847
|
{
|
|
7711
7848
|
const k = indices[j];
|
|
7712
|
-
if (k === i0 || k === i1 || k === i2)
|
|
7713
|
-
|
|
7849
|
+
if (k === i0 || k === i1 || k === i2) continue;
|
|
7850
|
+
|
|
7714
7851
|
const p = points[k];
|
|
7715
7852
|
hasInside = pointInTriangle(p, a, b, c);
|
|
7716
|
-
if (hasInside)
|
|
7717
|
-
break;
|
|
7853
|
+
if (hasInside) break;
|
|
7718
7854
|
}
|
|
7719
|
-
if (hasInside)
|
|
7720
|
-
continue;
|
|
7855
|
+
if (hasInside) continue;
|
|
7721
7856
|
|
|
7722
7857
|
// found valid ear
|
|
7723
7858
|
triangles.push([i0, i1, i2]);
|
|
@@ -7742,8 +7877,7 @@ function glPolyStrip(points)
|
|
|
7742
7877
|
worstIndex = i;
|
|
7743
7878
|
}
|
|
7744
7879
|
}
|
|
7745
|
-
if (worstIndex < 0)
|
|
7746
|
-
break;
|
|
7880
|
+
if (worstIndex < 0) break;
|
|
7747
7881
|
|
|
7748
7882
|
const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
|
|
7749
7883
|
const i1 = indices[worstIndex];
|
|
@@ -7974,14 +8108,16 @@ class PostProcessPlugin
|
|
|
7974
8108
|
{
|
|
7975
8109
|
/** Create global post processing shader
|
|
7976
8110
|
* @param {string} shaderCode
|
|
7977
|
-
* @param {boolean} [includeMainCanvas]
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
8111
|
+
* @param {boolean} [includeMainCanvas] - combine mainCanvs onto glCanvas
|
|
8112
|
+
* @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
|
|
8113
|
+
* @example
|
|
8114
|
+
* // create the post process plugin object
|
|
8115
|
+
* new PostProcessPlugin(shaderCode);
|
|
8116
|
+
*/
|
|
8117
|
+
constructor(shaderCode, includeMainCanvas=false, feedbackTexture=false)
|
|
7983
8118
|
{
|
|
7984
8119
|
ASSERT(!postProcess, 'Post process already initialized');
|
|
8120
|
+
ASSERT(!(includeMainCanvas && feedbackTexture), 'Post process cannot both include main canvas and use feedback texture');
|
|
7985
8121
|
postProcess = this;
|
|
7986
8122
|
|
|
7987
8123
|
if (!shaderCode) // default shader pass through
|
|
@@ -7989,9 +8125,10 @@ class PostProcessPlugin
|
|
|
7989
8125
|
|
|
7990
8126
|
/** @property {WebGLProgram} - Shader for post processing */
|
|
7991
8127
|
this.shader = undefined;
|
|
7992
|
-
|
|
7993
8128
|
/** @property {WebGLTexture} - Texture for post processing */
|
|
7994
8129
|
this.texture = undefined;
|
|
8130
|
+
/** @property {WebGLVertexArrayObject} - Vertex array object */
|
|
8131
|
+
this.vao = undefined;
|
|
7995
8132
|
|
|
7996
8133
|
// setup the post processing plugin
|
|
7997
8134
|
initPostProcess();
|
|
@@ -8000,7 +8137,6 @@ class PostProcessPlugin
|
|
|
8000
8137
|
function initPostProcess()
|
|
8001
8138
|
{
|
|
8002
8139
|
if (headlessMode) return;
|
|
8003
|
-
|
|
8004
8140
|
if (!glEnable)
|
|
8005
8141
|
{
|
|
8006
8142
|
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
@@ -8011,14 +8147,14 @@ class PostProcessPlugin
|
|
|
8011
8147
|
postProcess.texture = glCreateTexture();
|
|
8012
8148
|
postProcess.shader = glCreateProgram(
|
|
8013
8149
|
'#version 300 es\n' + // specify GLSL ES version
|
|
8014
|
-
'precision highp float;'+ // use highp for
|
|
8150
|
+
'precision highp float;'+ // use highp for accuracy
|
|
8015
8151
|
'in vec2 p;'+ // position
|
|
8016
8152
|
'void main(){'+ // shader entry point
|
|
8017
8153
|
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
8018
8154
|
'}' // end of shader
|
|
8019
8155
|
,
|
|
8020
8156
|
'#version 300 es\n' + // specify GLSL ES version
|
|
8021
|
-
'precision highp float;'+ // use highp for
|
|
8157
|
+
'precision highp float;'+ // use highp for accuracy
|
|
8022
8158
|
'uniform sampler2D iChannel0;'+ // input texture
|
|
8023
8159
|
'uniform vec3 iResolution;'+ // size of output texture
|
|
8024
8160
|
'uniform float iTime;'+ // time
|
|
@@ -8029,6 +8165,17 @@ class PostProcessPlugin
|
|
|
8029
8165
|
'c.a=1.;'+ // always use full alpha
|
|
8030
8166
|
'}' // end of shader
|
|
8031
8167
|
);
|
|
8168
|
+
|
|
8169
|
+
// setup VAO for post processing
|
|
8170
|
+
postProcess.vao = glContext.createVertexArray();
|
|
8171
|
+
glContext.bindVertexArray(postProcess.vao);
|
|
8172
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
8173
|
+
|
|
8174
|
+
// configure vertex attributes
|
|
8175
|
+
const vertexByteStride = 8;
|
|
8176
|
+
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
8177
|
+
glContext.enableVertexAttribArray(pLocation);
|
|
8178
|
+
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
8032
8179
|
}
|
|
8033
8180
|
function postProcessContextLost()
|
|
8034
8181
|
{
|
|
@@ -8043,14 +8190,17 @@ class PostProcessPlugin
|
|
|
8043
8190
|
}
|
|
8044
8191
|
function postProcessRender()
|
|
8045
8192
|
{
|
|
8046
|
-
if (headlessMode) return;
|
|
8047
|
-
|
|
8048
|
-
if (!glEnable)
|
|
8049
|
-
return;
|
|
8193
|
+
if (headlessMode || !glEnable) return;
|
|
8050
8194
|
|
|
8051
8195
|
// clear out the buffer
|
|
8052
8196
|
glFlush();
|
|
8053
|
-
|
|
8197
|
+
|
|
8198
|
+
// setup shader program to draw a quad
|
|
8199
|
+
glContext.useProgram(postProcess.shader);
|
|
8200
|
+
glContext.bindVertexArray(postProcess.vao);
|
|
8201
|
+
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, true);
|
|
8202
|
+
glContext.disable(glContext.BLEND);
|
|
8203
|
+
|
|
8054
8204
|
// setup texture
|
|
8055
8205
|
glContext.activeTexture(glContext.TEXTURE0);
|
|
8056
8206
|
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
@@ -8061,29 +8211,32 @@ class PostProcessPlugin
|
|
|
8061
8211
|
workCanvas.height = mainCanvasSize.y;
|
|
8062
8212
|
glCopyToContext(workContext);
|
|
8063
8213
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
8214
|
+
mainCanvas.width |= 0
|
|
8064
8215
|
|
|
8065
8216
|
// copy work canvas to texture
|
|
8066
8217
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
|
|
8067
8218
|
}
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
// set vertex position attribute
|
|
8076
|
-
const vertexByteStride = 8;
|
|
8077
|
-
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
8078
|
-
glContext.enableVertexAttribArray(pLocation);
|
|
8079
|
-
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
8080
|
-
|
|
8219
|
+
else if (!feedbackTexture)
|
|
8220
|
+
{
|
|
8221
|
+
// copy glCanvas to texture
|
|
8222
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
|
|
8223
|
+
}
|
|
8224
|
+
|
|
8081
8225
|
// set uniforms and draw
|
|
8082
8226
|
const uniformLocation = (name)=>glContext.getUniformLocation(postProcess.shader, name);
|
|
8083
8227
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
8084
8228
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
8085
8229
|
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
8086
8230
|
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
|
|
8231
|
+
|
|
8232
|
+
if (feedbackTexture)
|
|
8233
|
+
{
|
|
8234
|
+
// pass glCanvas back to overlay texture
|
|
8235
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
|
|
8236
|
+
}
|
|
8237
|
+
|
|
8238
|
+
// force it to set instanced mode
|
|
8239
|
+
glSetInstancedMode(true);
|
|
8087
8240
|
}
|
|
8088
8241
|
}
|
|
8089
8242
|
}
|
|
@@ -8764,7 +8917,7 @@ class UISystemPlugin
|
|
|
8764
8917
|
const up = 'ArrowUp', down = 'ArrowDown', left = 'ArrowLeft', right = 'ArrowRight';
|
|
8765
8918
|
if (both)
|
|
8766
8919
|
{
|
|
8767
|
-
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
8920
|
+
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
8768
8921
|
keyIsDown(down) || keyIsDown(right) ? 1 : 0;
|
|
8769
8922
|
}
|
|
8770
8923
|
const back = vertical ? up : left;
|
|
@@ -8795,7 +8948,7 @@ class UISystemPlugin
|
|
|
8795
8948
|
* @return {boolean} */
|
|
8796
8949
|
getNavigationWasPressed()
|
|
8797
8950
|
{
|
|
8798
|
-
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
8951
|
+
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
8799
8952
|
keyWasPressed('Space') || keyWasPressed('Enter');
|
|
8800
8953
|
}
|
|
8801
8954
|
|
|
@@ -8843,7 +8996,7 @@ class UISystemPlugin
|
|
|
8843
8996
|
buttonYes.textHeight = 40;
|
|
8844
8997
|
buttonYes.navigationIndex = 1;
|
|
8845
8998
|
buttonYes.hoverColor = hsl(0,1,.5);
|
|
8846
|
-
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
8999
|
+
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
8847
9000
|
confirmMenu.addChild(buttonYes);
|
|
8848
9001
|
|
|
8849
9002
|
// no button
|
|
@@ -8873,8 +9026,8 @@ class UISystemPlugin
|
|
|
8873
9026
|
class UIObject
|
|
8874
9027
|
{
|
|
8875
9028
|
/** Create a UIObject
|
|
8876
|
-
* @param {Vector2} [pos=(
|
|
8877
|
-
* @param {Vector2} [size=(1
|
|
9029
|
+
* @param {Vector2} [pos=vec2()]
|
|
9030
|
+
* @param {Vector2} [size=vec2(1)]
|
|
8878
9031
|
*/
|
|
8879
9032
|
constructor(pos=vec2(), size=vec2())
|
|
8880
9033
|
{
|
|
@@ -8889,7 +9042,7 @@ class UIObject
|
|
|
8889
9042
|
this.size = size.copy();
|
|
8890
9043
|
/** @property {Color} - Color of the object */
|
|
8891
9044
|
this.color = uiSystem.defaultColor.copy();
|
|
8892
|
-
/** @property {Color} - Color of the object when active, uses
|
|
9045
|
+
/** @property {Color} - Color of the object when active, uses hoverColor if undefined */
|
|
8893
9046
|
this.activeColor = undefined;
|
|
8894
9047
|
/** @property {string} - Text for this ui object */
|
|
8895
9048
|
this.text = undefined;
|
|
@@ -8987,10 +9140,10 @@ class UIObject
|
|
|
8987
9140
|
|
|
8988
9141
|
// disconnect from parent and destroy children
|
|
8989
9142
|
this.destroyed = 1;
|
|
8990
|
-
this.parent
|
|
9143
|
+
this.parent?.removeChild(this);
|
|
8991
9144
|
for (const child of this.children)
|
|
8992
9145
|
{
|
|
8993
|
-
child.parent =
|
|
9146
|
+
child.parent = undefined;
|
|
8994
9147
|
child.destroy();
|
|
8995
9148
|
}
|
|
8996
9149
|
}
|
|
@@ -9086,10 +9239,10 @@ class UIObject
|
|
|
9086
9239
|
this.interactive && this.isActiveObject() && !this.disabled ?
|
|
9087
9240
|
this.color : this.lineColor;
|
|
9088
9241
|
const color = isNavigationObject ? this.hoverColor :
|
|
9089
|
-
this.disabled ? this.disabledColor :
|
|
9090
|
-
this.interactive ?
|
|
9091
|
-
this.
|
|
9092
|
-
this.
|
|
9242
|
+
this.disabled ? this.disabledColor :
|
|
9243
|
+
this.interactive ?
|
|
9244
|
+
this.isActiveObject() ? this.activeColor || this.hoverColor :
|
|
9245
|
+
this.isHoverObject() ? this.hoverColor :
|
|
9093
9246
|
this.color : this.color;
|
|
9094
9247
|
const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
|
|
9095
9248
|
|
|
@@ -9101,7 +9254,7 @@ class UIObject
|
|
|
9101
9254
|
getTextSize()
|
|
9102
9255
|
{
|
|
9103
9256
|
return vec2(
|
|
9104
|
-
this.textWidth || this.textFitScale * this.size.x,
|
|
9257
|
+
this.textWidth || this.textFitScale * this.size.x,
|
|
9105
9258
|
this.textHeight || this.textFitScale * this.size.y);
|
|
9106
9259
|
}
|
|
9107
9260
|
|
|
@@ -9149,9 +9302,9 @@ class UIObject
|
|
|
9149
9302
|
renderDebug(visible=true)
|
|
9150
9303
|
{
|
|
9151
9304
|
// apply color based on state
|
|
9152
|
-
const color =
|
|
9305
|
+
const color =
|
|
9153
9306
|
!visible ? GREEN :
|
|
9154
|
-
this.isHoverObject() ? YELLOW :
|
|
9307
|
+
this.isHoverObject() ? YELLOW :
|
|
9155
9308
|
this.disabled ? PURPLE :
|
|
9156
9309
|
this.interactive ? RED : BLUE;
|
|
9157
9310
|
uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
|