littlejsengine 1.16.2 → 1.17.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/littlejs.d.ts +291 -246
- package/dist/littlejs.esm.js +1520 -1271
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1178 -920
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1170 -912
- package/examples/box2d/gameObjects.js +4 -4
- package/examples/breakout/game.js +5 -6
- package/examples/electron/index.html +2 -2
- package/examples/electron/tiles.png +0 -0
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +9 -8
- package/examples/logo.png +0 -0
- package/examples/module/build.bat +7 -0
- package/examples/module/build.js +121 -0
- package/examples/module/tiles.png +0 -0
- package/examples/platformer/game.js +1 -1
- package/examples/platformer/gameEffects.js +25 -29
- package/examples/platformer/gameLevel.js +27 -28
- package/examples/puzzle/game.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/box2dPool.js +2 -2
- package/examples/shorts/box2dTileLayer.js +48 -0
- package/examples/shorts/clock.js +3 -3
- package/examples/shorts/fontImage.js +4 -3
- package/examples/shorts/music.js +2 -2
- package/examples/shorts/musicPlayer.js +2 -2
- package/examples/shorts/parallax.js +1 -1
- package/examples/shorts/sequencer.js +1 -1
- package/examples/shorts/shapes.js +1 -1
- package/examples/shorts/texture.js +10 -6
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +2 -0
- package/examples/starter/index.html +2 -2
- package/examples/starter/tiles.png +0 -0
- package/examples/typescript/tiles.png +0 -0
- package/examples/uiSystem/game.js +1 -1
- package/examples/uiSystem/tiles.png +0 -0
- package/package.json +5 -5
- package/plugins/box2d.js +167 -63
- package/plugins/postProcess.js +47 -28
- package/plugins/uiSystem.js +18 -18
- package/plugins/zzfxm.js +2 -2
- package/reference.md +4 -3
- package/src/engine.js +182 -181
- package/src/engineAudio.js +47 -63
- package/src/engineDebug.js +8 -8
- package/src/engineDraw.js +184 -124
- package/src/engineExport.js +325 -334
- package/src/engineFont.png +0 -0
- package/src/engineInput.js +18 -24
- package/src/engineMath.js +24 -113
- package/src/engineObject.js +27 -26
- package/src/engineParticles.js +2 -2
- package/src/engineTileLayer.js +229 -192
- package/src/engineUtilities.js +100 -4
- package/src/engineWebGL.js +124 -73
package/dist/littlejs.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.5';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -88,8 +88,9 @@ function getPaused() { return paused; }
|
|
|
88
88
|
* @memberof Engine */
|
|
89
89
|
function setPaused(isPaused=true) { paused = isPaused; }
|
|
90
90
|
|
|
91
|
-
//
|
|
91
|
+
// Engine internal variables
|
|
92
92
|
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
93
|
+
let showEngineVersion = true;
|
|
93
94
|
|
|
94
95
|
///////////////////////////////////////////////////////////////////////////////
|
|
95
96
|
// plugin hooks
|
|
@@ -153,16 +154,17 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
153
154
|
* @example
|
|
154
155
|
* // Basic engine startup
|
|
155
156
|
* engineInit(
|
|
156
|
-
* ()
|
|
157
|
-
* ()
|
|
158
|
-
* ()
|
|
159
|
-
* ()
|
|
160
|
-
* ()
|
|
157
|
+
* ()=> { LOG('Game initialized!'); }, // gameInit
|
|
158
|
+
* ()=> { updateGameLogic(); }, // gameUpdate
|
|
159
|
+
* ()=> { updateUI(); }, // gameUpdatePost
|
|
160
|
+
* ()=> { drawBackground(); }, // gameRender
|
|
161
|
+
* ()=> { drawHUD(); }, // gameRenderPost
|
|
161
162
|
* ['tiles.png', 'tilesLevel.png'] // images to load
|
|
162
163
|
* );
|
|
163
164
|
* @memberof Engine */
|
|
164
165
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
165
166
|
{
|
|
167
|
+
showEngineVersion && console.log(`${engineName} Engine v${engineVersion}`);
|
|
166
168
|
ASSERT(!mainContext, 'engine already initialized');
|
|
167
169
|
ASSERT(isArray(imageSources), 'pass in images as array');
|
|
168
170
|
|
|
@@ -320,7 +322,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
320
322
|
|
|
321
323
|
// responsive aspect ratio with native resolution
|
|
322
324
|
const innerAspect = innerWidth / innerHeight;
|
|
323
|
-
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
325
|
+
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
324
326
|
if (canvasMaxAspect && innerAspect > canvasMaxAspect)
|
|
325
327
|
{
|
|
326
328
|
// full height
|
|
@@ -377,7 +379,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
377
379
|
debugInit();
|
|
378
380
|
|
|
379
381
|
// setup canvases
|
|
380
|
-
// transform way is still more reliable
|
|
382
|
+
// transform way is still more reliable than flexbox or grid
|
|
381
383
|
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
382
384
|
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
383
385
|
mainCanvas.style.cssText = styleCanvas;
|
|
@@ -422,18 +424,20 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
422
424
|
}));
|
|
423
425
|
}
|
|
424
426
|
|
|
427
|
+
// load engine font image
|
|
428
|
+
promises.push(fontImageInit());
|
|
429
|
+
|
|
425
430
|
if (showSplashScreen)
|
|
426
431
|
{
|
|
427
432
|
// draw splash screen
|
|
428
433
|
promises.push(new Promise(resolve =>
|
|
429
434
|
{
|
|
430
435
|
let t = 0;
|
|
431
|
-
console.log(`${engineName} Engine v${engineVersion}`);
|
|
432
436
|
updateSplash();
|
|
433
437
|
function updateSplash()
|
|
434
438
|
{
|
|
435
439
|
inputClear();
|
|
436
|
-
|
|
440
|
+
drawEngineLogo(t+=.01);
|
|
437
441
|
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
438
442
|
}
|
|
439
443
|
}));
|
|
@@ -449,172 +453,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
449
453
|
await gameInit();
|
|
450
454
|
engineUpdate();
|
|
451
455
|
}
|
|
452
|
-
|
|
453
|
-
///////////////////////////////////////////////////////////////////////////
|
|
454
|
-
// LittleJS Splash Screen
|
|
455
|
-
function drawEngineSplashScreen(t)
|
|
456
|
-
{
|
|
457
|
-
const x = mainContext;
|
|
458
|
-
const w = mainCanvas.width = innerWidth;
|
|
459
|
-
const h = mainCanvas.height = innerHeight;
|
|
460
|
-
|
|
461
|
-
{
|
|
462
|
-
// background
|
|
463
|
-
const p3 = percent(t, 1, .8);
|
|
464
|
-
const p4 = percent(t, 0, .5);
|
|
465
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
466
|
-
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
467
|
-
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
468
|
-
x.save();
|
|
469
|
-
x.fillStyle = g;
|
|
470
|
-
x.fillRect(0,0,w,h);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// draw LittleJS logo...
|
|
474
|
-
const rect = (X, Y, W, H, C)=>
|
|
475
|
-
{
|
|
476
|
-
x.beginPath();
|
|
477
|
-
x.rect(X,Y,W,C?H*p:H);
|
|
478
|
-
x.fillStyle = C;
|
|
479
|
-
C ? x.fill() : x.stroke();
|
|
480
|
-
};
|
|
481
|
-
const line = (X, Y, Z, W)=>
|
|
482
|
-
{
|
|
483
|
-
x.beginPath();
|
|
484
|
-
x.lineTo(X,Y);
|
|
485
|
-
x.lineTo(Z,W);
|
|
486
|
-
x.stroke();
|
|
487
|
-
};
|
|
488
|
-
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
489
|
-
{
|
|
490
|
-
const D = (A+B)/2, E = p*(B-A)/2;
|
|
491
|
-
x.beginPath();
|
|
492
|
-
F && x.lineTo(X,Y);
|
|
493
|
-
x.arc(X,Y,R,D-E,D+E);
|
|
494
|
-
x.fillStyle = C;
|
|
495
|
-
C ? x.fill() : x.stroke();
|
|
496
|
-
};
|
|
497
|
-
const color = (c=0, l=0) =>
|
|
498
|
-
hsl([.98,.3,.57,.14][c%4],.8,[0,.3,.5,.8,.9][l]).toString();
|
|
499
|
-
const alpha = wave(1,1,t);
|
|
500
|
-
const p = percent(alpha, .1, .5);
|
|
501
|
-
|
|
502
|
-
// setup
|
|
503
|
-
x.translate(w/2,h/2);
|
|
504
|
-
const size = min(6, min(w,h)/99); // fit to screen
|
|
505
|
-
x.scale(size,size);
|
|
506
|
-
x.translate(-40,-35);
|
|
507
|
-
x.lineJoin = x.lineCap = 'round';
|
|
508
|
-
x.lineWidth = .1 + p*1.9;
|
|
509
|
-
|
|
510
|
-
// drawing effect
|
|
511
|
-
const p2 = percent(alpha,.1,1);
|
|
512
|
-
x.setLineDash([99*p2,99]);
|
|
513
|
-
|
|
514
|
-
// cab top
|
|
515
|
-
rect(7,16,18,-8,color(2,2));
|
|
516
|
-
rect(7,8,18,4,color(2,3));
|
|
517
|
-
rect(25,8,8,8,color(2,1));
|
|
518
|
-
rect(25,8,-18,8);
|
|
519
|
-
rect(25,8,8,8);
|
|
520
|
-
|
|
521
|
-
// cab
|
|
522
|
-
rect(25,16,7,23,color());
|
|
523
|
-
rect(11,39,14,-23,color(1,1));
|
|
524
|
-
rect(11,16,14,18,color(1,2));
|
|
525
|
-
rect(11,16,14,8,color(1,3));
|
|
526
|
-
rect(25,16,-14,24);
|
|
527
|
-
|
|
528
|
-
// cab window
|
|
529
|
-
rect(15,29,6,-9,color(2,2));
|
|
530
|
-
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
531
|
-
rect(21,21,-6,9);
|
|
532
|
-
|
|
533
|
-
// little stack
|
|
534
|
-
rect(37,14,9,6,color(3,2));
|
|
535
|
-
rect(37,14,4.5,6,color(3,3));
|
|
536
|
-
rect(37,14,9,6);
|
|
537
|
-
|
|
538
|
-
// big stack
|
|
539
|
-
rect(50,20,10,-8,color(0,1));
|
|
540
|
-
rect(50,20,6.5,-8,color(0,2));
|
|
541
|
-
rect(50,20,3.5,-8,color(0,3));
|
|
542
|
-
rect(50,20,10,-8);
|
|
543
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
544
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
545
|
-
circle(55,2,11.4,.5,PI-.5);
|
|
546
|
-
rect(45,7,20,-7,color(0,2));
|
|
547
|
-
rect(45,-1,20,4,color(0,3));
|
|
548
|
-
rect(45,-1,20,8);
|
|
549
|
-
|
|
550
|
-
// engine
|
|
551
|
-
for (let i=5; i--;)
|
|
552
|
-
{
|
|
553
|
-
// stagger radius to fix slight seam
|
|
554
|
-
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
555
|
-
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
556
|
-
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// engine outline
|
|
560
|
-
circle(36,30,10,PI/2,PI*3/2);
|
|
561
|
-
circle(48,30,10,PI/2,PI*3/2);
|
|
562
|
-
circle(60,30,10);
|
|
563
|
-
line(36,20,60,20);
|
|
564
|
-
|
|
565
|
-
// engine front light
|
|
566
|
-
circle(60,30,4,PI,3*PI,color(3,2));
|
|
567
|
-
circle(60,30,4,PI,2*PI,color(3,3));
|
|
568
|
-
circle(60,30,4,PI,3*PI);
|
|
569
|
-
|
|
570
|
-
// front brush
|
|
571
|
-
for (let i=6; i--;)
|
|
572
|
-
{
|
|
573
|
-
x.beginPath();
|
|
574
|
-
x.lineTo(53,54);
|
|
575
|
-
x.lineTo(53,40);
|
|
576
|
-
x.lineTo(53+(1+i*2.9)*p,40);
|
|
577
|
-
x.lineTo(53+(4+i*3.5)*p,54);
|
|
578
|
-
x.fillStyle = color(0,i%2+2);
|
|
579
|
-
x.fill();
|
|
580
|
-
i%2 && x.stroke();
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// wheels
|
|
584
|
-
rect(6,40,5,5);
|
|
585
|
-
rect(6,40,5,5,color());
|
|
586
|
-
rect(15,54,38,-14,color());
|
|
587
|
-
for (let i=3; i--;)
|
|
588
|
-
for (let j=2; j--;)
|
|
589
|
-
{
|
|
590
|
-
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
591
|
-
x.stroke();
|
|
592
|
-
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
593
|
-
x.stroke();
|
|
594
|
-
}
|
|
595
|
-
line(6,40,68,40); // center
|
|
596
|
-
line(77,54,4,54); // bottom
|
|
597
|
-
|
|
598
|
-
// draw engine name
|
|
599
|
-
const s = engineName;
|
|
600
|
-
x.font = '900 16px arial';
|
|
601
|
-
x.textAlign = 'center';
|
|
602
|
-
x.textBaseline = 'top';
|
|
603
|
-
x.lineWidth = .1+p*3.9;
|
|
604
|
-
let w2 = 0;
|
|
605
|
-
for (let i=0; i<s.length; ++i)
|
|
606
|
-
w2 += x.measureText(s[i]).width;
|
|
607
|
-
for (let j=2; j--;)
|
|
608
|
-
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
609
|
-
{
|
|
610
|
-
x.fillStyle = color(i,2);
|
|
611
|
-
const w = x.measureText(s[i]).width;
|
|
612
|
-
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
613
|
-
X += w;
|
|
614
|
-
}
|
|
615
|
-
x.restore();
|
|
616
|
-
}
|
|
617
|
-
///////////////////////////////////////////////////////////////////////////
|
|
618
456
|
}
|
|
619
457
|
|
|
620
458
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -628,8 +466,7 @@ function engineObjectsUpdate()
|
|
|
628
466
|
// recursive object update
|
|
629
467
|
function updateObject(o)
|
|
630
468
|
{
|
|
631
|
-
if (o.destroyed)
|
|
632
|
-
return;
|
|
469
|
+
if (o.destroyed) return;
|
|
633
470
|
|
|
634
471
|
o.update();
|
|
635
472
|
for (const child of o.children)
|
|
@@ -637,8 +474,7 @@ function engineObjectsUpdate()
|
|
|
637
474
|
}
|
|
638
475
|
for (const o of engineObjects)
|
|
639
476
|
{
|
|
640
|
-
if (o.parent)
|
|
641
|
-
continue;
|
|
477
|
+
if (o.parent || o.destroyed) continue;
|
|
642
478
|
|
|
643
479
|
// update top level objects
|
|
644
480
|
o.update();
|
|
@@ -727,6 +563,171 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
727
563
|
|
|
728
564
|
debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
|
|
729
565
|
return hitObjects;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
569
|
+
function drawEngineLogo(t)
|
|
570
|
+
{
|
|
571
|
+
// LittleJS Logo and Splash Screen
|
|
572
|
+
const x = mainContext;
|
|
573
|
+
const w = mainCanvas.width = innerWidth;
|
|
574
|
+
const h = mainCanvas.height = innerHeight;
|
|
575
|
+
|
|
576
|
+
{
|
|
577
|
+
// background
|
|
578
|
+
const p3 = percent(t, 1, .8);
|
|
579
|
+
const p4 = percent(t, 0, .5);
|
|
580
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
581
|
+
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
582
|
+
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
583
|
+
x.save();
|
|
584
|
+
x.fillStyle = g;
|
|
585
|
+
x.fillRect(0,0,w,h);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// draw LittleJS logo...
|
|
589
|
+
const rect = (X, Y, W, H, C)=>
|
|
590
|
+
{
|
|
591
|
+
x.beginPath();
|
|
592
|
+
x.rect(X,Y,W,C?H*p:H);
|
|
593
|
+
x.fillStyle = C;
|
|
594
|
+
C ? x.fill() : x.stroke();
|
|
595
|
+
};
|
|
596
|
+
const line = (X, Y, Z, W)=>
|
|
597
|
+
{
|
|
598
|
+
x.beginPath();
|
|
599
|
+
x.lineTo(X,Y);
|
|
600
|
+
x.lineTo(Z,W);
|
|
601
|
+
x.stroke();
|
|
602
|
+
};
|
|
603
|
+
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
604
|
+
{
|
|
605
|
+
const D = (A+B)/2, E = p*(B-A)/2;
|
|
606
|
+
x.beginPath();
|
|
607
|
+
F && x.lineTo(X,Y);
|
|
608
|
+
x.arc(X,Y,R,D-E,D+E);
|
|
609
|
+
x.fillStyle = C;
|
|
610
|
+
C ? x.fill() : x.stroke();
|
|
611
|
+
};
|
|
612
|
+
const color = (c=0, l=0)=>
|
|
613
|
+
hsl([.98,.3,.57,.14][c%4],.9,[0,.3,.5,.8,.9][l]).toString();
|
|
614
|
+
const alpha = wave(1,1,t);
|
|
615
|
+
const p = percent(alpha, .1, .5);
|
|
616
|
+
|
|
617
|
+
// setup
|
|
618
|
+
x.translate(w/2,h/2);
|
|
619
|
+
const size = min(6, min(w,h)/99); // fit to screen
|
|
620
|
+
x.scale(size,size);
|
|
621
|
+
x.translate(-40,-35);
|
|
622
|
+
x.lineJoin = x.lineCap = 'round';
|
|
623
|
+
x.lineWidth = .1 + p*1.9;
|
|
624
|
+
|
|
625
|
+
// drawing effect
|
|
626
|
+
const p2 = percent(alpha,.1,1);
|
|
627
|
+
x.setLineDash([99*p2,99]);
|
|
628
|
+
|
|
629
|
+
// cab top
|
|
630
|
+
rect(7,16,18,-8,color(2,2));
|
|
631
|
+
rect(7,8,18,4,color(2,3));
|
|
632
|
+
rect(25,8,8,8,color(2,1));
|
|
633
|
+
rect(25,8,-18,8);
|
|
634
|
+
rect(25,8,8,8);
|
|
635
|
+
|
|
636
|
+
// cab
|
|
637
|
+
rect(25,16,7,24,color());
|
|
638
|
+
rect(11,39,14,-23,color(1,1));
|
|
639
|
+
rect(11,16,14,18,color(1,2));
|
|
640
|
+
rect(11,16,14,8,color(1,3));
|
|
641
|
+
rect(25,16,-14,24);
|
|
642
|
+
|
|
643
|
+
// cab window
|
|
644
|
+
rect(15,29,6,-9,color(2,2));
|
|
645
|
+
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
646
|
+
rect(21,21,-6,9);
|
|
647
|
+
|
|
648
|
+
// little stack
|
|
649
|
+
rect(37,14,9,6,color(3,2));
|
|
650
|
+
rect(37,14,4.5,6,color(3,3));
|
|
651
|
+
rect(37,14,9,6);
|
|
652
|
+
|
|
653
|
+
// big stack
|
|
654
|
+
rect(50,20,10,-8,color(0,1));
|
|
655
|
+
rect(50,20,6.5,-8,color(0,2));
|
|
656
|
+
rect(50,20,3.5,-8,color(0,3));
|
|
657
|
+
rect(50,20,10,-8);
|
|
658
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
659
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
660
|
+
circle(55,2,11.4,.5,PI-.5);
|
|
661
|
+
rect(45,7,20,-7,color(0,2));
|
|
662
|
+
rect(45,-1,20,4,color(0,3));
|
|
663
|
+
rect(45,-1,20,8);
|
|
664
|
+
|
|
665
|
+
// engine
|
|
666
|
+
for (let i=5; i--;)
|
|
667
|
+
{
|
|
668
|
+
// stagger radius to fix slight seam
|
|
669
|
+
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
670
|
+
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
671
|
+
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// engine outline
|
|
675
|
+
circle(36,30,10,PI/2,PI*3/2);
|
|
676
|
+
circle(48,30,10,PI/2,PI*3/2);
|
|
677
|
+
circle(60,30,10);
|
|
678
|
+
line(36,20,60,20);
|
|
679
|
+
|
|
680
|
+
// engine front light
|
|
681
|
+
circle(60,30,4,PI,3*PI,color(3,2));
|
|
682
|
+
circle(60,30,4,PI,2*PI,color(3,3));
|
|
683
|
+
circle(60,30,4,PI,3*PI);
|
|
684
|
+
|
|
685
|
+
// front brush
|
|
686
|
+
for (let i=6; i--;)
|
|
687
|
+
{
|
|
688
|
+
x.beginPath();
|
|
689
|
+
x.lineTo(53,54);
|
|
690
|
+
x.lineTo(53,40);
|
|
691
|
+
x.lineTo(53+(1+i*2.9)*p,40);
|
|
692
|
+
x.lineTo(53+(4+i*3.5)*p,54);
|
|
693
|
+
x.fillStyle = color(0,i%2+2);
|
|
694
|
+
x.fill();
|
|
695
|
+
i%2 && x.stroke();
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// wheels
|
|
699
|
+
rect(6,40,5,5);
|
|
700
|
+
rect(6,40,5,5,color());
|
|
701
|
+
rect(15,54,38,-14,color());
|
|
702
|
+
for (let i=3; i--;)
|
|
703
|
+
for (let j=2; j--;)
|
|
704
|
+
{
|
|
705
|
+
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
706
|
+
x.stroke();
|
|
707
|
+
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
708
|
+
x.stroke();
|
|
709
|
+
}
|
|
710
|
+
line(6,40,68,40); // center
|
|
711
|
+
line(77,54,4,54); // bottom
|
|
712
|
+
|
|
713
|
+
// draw engine name
|
|
714
|
+
const s = engineName;
|
|
715
|
+
x.font = '900 16px arial';
|
|
716
|
+
x.textAlign = 'center';
|
|
717
|
+
x.textBaseline = 'top';
|
|
718
|
+
x.lineWidth = .1+p*3.9;
|
|
719
|
+
let w2 = 0;
|
|
720
|
+
for (let i=0; i<s.length; ++i)
|
|
721
|
+
w2 += x.measureText(s[i]).width;
|
|
722
|
+
for (let j=2; j--;)
|
|
723
|
+
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
724
|
+
{
|
|
725
|
+
x.fillStyle = color(i,2);
|
|
726
|
+
const w = x.measureText(s[i]).width;
|
|
727
|
+
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
728
|
+
X += w;
|
|
729
|
+
}
|
|
730
|
+
x.restore();
|
|
730
731
|
}
|
|
731
732
|
/**
|
|
732
733
|
* LittleJS - Release Mode
|
|
@@ -767,12 +768,11 @@ function debugVideoCaptureStop (){}
|
|
|
767
768
|
function debugVideoCaptureUpdate(){}
|
|
768
769
|
function debugProtectConstant(o){ return o; }
|
|
769
770
|
/**
|
|
770
|
-
* LittleJS
|
|
771
|
+
* LittleJS Math Classes and Functions
|
|
771
772
|
* - 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
773
|
* - RandomGenerator - seeded random number generator
|
|
774
|
+
* - Vector2 - fast, simple, easy 2D vector class
|
|
775
|
+
* - Color - holds a rgba color with math functions
|
|
776
776
|
* @namespace Math
|
|
777
777
|
*/
|
|
778
778
|
|
|
@@ -783,25 +783,25 @@ function debugProtectConstant(o){ return o; }
|
|
|
783
783
|
const PI = Math.PI;
|
|
784
784
|
|
|
785
785
|
/** Returns absolute value of value passed in
|
|
786
|
-
* @param {number}
|
|
786
|
+
* @param {number} x
|
|
787
787
|
* @return {number}
|
|
788
788
|
* @memberof Math */
|
|
789
789
|
const abs = Math.abs;
|
|
790
790
|
|
|
791
791
|
/** Returns floored value of value passed in
|
|
792
|
-
* @param {number}
|
|
792
|
+
* @param {number} x
|
|
793
793
|
* @return {number}
|
|
794
794
|
* @memberof Math */
|
|
795
795
|
const floor = Math.floor;
|
|
796
796
|
|
|
797
797
|
/** Returns ceiled value of value passed in
|
|
798
|
-
* @param {number}
|
|
798
|
+
* @param {number} x
|
|
799
799
|
* @return {number}
|
|
800
800
|
* @memberof Math */
|
|
801
801
|
const ceil = Math.ceil;
|
|
802
802
|
|
|
803
803
|
/** Returns rounded value passed in
|
|
804
|
-
* @param {number}
|
|
804
|
+
* @param {number} x
|
|
805
805
|
* @return {number}
|
|
806
806
|
* @memberof Math */
|
|
807
807
|
const round = Math.round;
|
|
@@ -819,7 +819,7 @@ const min = Math.min;
|
|
|
819
819
|
const max = Math.max;
|
|
820
820
|
|
|
821
821
|
/** Returns the sign of value passed in
|
|
822
|
-
* @param {number}
|
|
822
|
+
* @param {number} x
|
|
823
823
|
* @return {number}
|
|
824
824
|
* @memberof Math */
|
|
825
825
|
const sign = Math.sign;
|
|
@@ -831,25 +831,25 @@ const sign = Math.sign;
|
|
|
831
831
|
const hypot = Math.hypot;
|
|
832
832
|
|
|
833
833
|
/** Returns log2 of value passed in
|
|
834
|
-
* @param {number}
|
|
834
|
+
* @param {number} x
|
|
835
835
|
* @return {number}
|
|
836
836
|
* @memberof Math */
|
|
837
837
|
const log2 = Math.log2;
|
|
838
838
|
|
|
839
839
|
/** Returns sin of value passed in
|
|
840
|
-
* @param {number}
|
|
840
|
+
* @param {number} x
|
|
841
841
|
* @return {number}
|
|
842
842
|
* @memberof Math */
|
|
843
843
|
const sin = Math.sin;
|
|
844
844
|
|
|
845
845
|
/** Returns cos of value passed in
|
|
846
|
-
* @param {number}
|
|
846
|
+
* @param {number} x
|
|
847
847
|
* @return {number}
|
|
848
848
|
* @memberof Math */
|
|
849
849
|
const cos = Math.cos;
|
|
850
850
|
|
|
851
851
|
/** Returns tan of value passed in
|
|
852
|
-
* @param {number}
|
|
852
|
+
* @param {number} x
|
|
853
853
|
* @return {number}
|
|
854
854
|
* @memberof Math */
|
|
855
855
|
const tan = Math.tan;
|
|
@@ -960,11 +960,11 @@ function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
|
960
960
|
|
|
961
961
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
962
962
|
* 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}
|
|
963
|
+
* @param {Vector2} posA - Center of box A
|
|
964
|
+
* @param {Vector2} sizeA - Size of box A
|
|
965
|
+
* @param {Vector2} posB - Center of box B
|
|
966
|
+
* @param {Vector2} [sizeB=vec2()] - Size of box B, uses a point if undefined
|
|
967
|
+
* @return {boolean} - True if overlapping
|
|
968
968
|
* @memberof Math */
|
|
969
969
|
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
970
970
|
{
|
|
@@ -1038,7 +1038,7 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
|
1038
1038
|
* @param {any} s
|
|
1039
1039
|
* @return {boolean}
|
|
1040
1040
|
* @memberof Math */
|
|
1041
|
-
function isString(s) { return s
|
|
1041
|
+
function isString(s) { return s != null && typeof s?.toString() === 'string'; }
|
|
1042
1042
|
|
|
1043
1043
|
/**
|
|
1044
1044
|
* Check if object is an array
|
|
@@ -1182,8 +1182,8 @@ function randInCircle(radius=1, minRadius=0)
|
|
|
1182
1182
|
{ return radius > 0 ? randVec2(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
|
|
1183
1183
|
|
|
1184
1184
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1185
|
-
* @param {Color} [colorA=
|
|
1186
|
-
* @param {Color} [colorB=
|
|
1185
|
+
* @param {Color} [colorA=WHITE]
|
|
1186
|
+
* @param {Color} [colorB=BLACK]
|
|
1187
1187
|
* @param {boolean} [linear]
|
|
1188
1188
|
* @return {Color}
|
|
1189
1189
|
* @memberof Random */
|
|
@@ -1262,8 +1262,8 @@ class RandomGenerator
|
|
|
1262
1262
|
{ return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
|
|
1263
1263
|
|
|
1264
1264
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1265
|
-
* @param {Color} [colorA=
|
|
1266
|
-
* @param {Color} [colorB=
|
|
1265
|
+
* @param {Color} [colorA=WHITE]
|
|
1266
|
+
* @param {Color} [colorB=BLACK]
|
|
1267
1267
|
* @param {boolean} [linear]
|
|
1268
1268
|
* @return {Color} */
|
|
1269
1269
|
randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
@@ -1307,7 +1307,7 @@ class RandomGenerator
|
|
|
1307
1307
|
* a = vec2(5); // set a to (5, 5)
|
|
1308
1308
|
* b = vec2(); // set b to (0, 0)
|
|
1309
1309
|
* @memberof Math */
|
|
1310
|
-
function vec2(x=0, y) { return new Vector2(x, y
|
|
1310
|
+
function vec2(x=0, y) { return new Vector2(x, y ?? x); }
|
|
1311
1311
|
|
|
1312
1312
|
/**
|
|
1313
1313
|
* Check if object is a valid Vector2
|
|
@@ -1515,10 +1515,6 @@ class Vector2
|
|
|
1515
1515
|
* @return {number} */
|
|
1516
1516
|
area() { return abs(this.x * this.y); }
|
|
1517
1517
|
|
|
1518
|
-
/** Returns true if this vector is (0,0)
|
|
1519
|
-
* @return {boolean} */
|
|
1520
|
-
isZero() { return !this.x && !this.y; }
|
|
1521
|
-
|
|
1522
1518
|
/** Returns a new vector that is p percent between this and the vector passed in
|
|
1523
1519
|
* @param {Vector2} v - other vector
|
|
1524
1520
|
* @param {number} percent
|
|
@@ -1880,7 +1876,97 @@ const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
|
1880
1876
|
/** Color - Magenta #ff00ff
|
|
1881
1877
|
* @type {Color}
|
|
1882
1878
|
* @memberof Math */
|
|
1883
|
-
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
1879
|
+
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
1880
|
+
/**
|
|
1881
|
+
* LittleJS Utility Classes and Functions
|
|
1882
|
+
* - General purpose utilities
|
|
1883
|
+
* - Timer - tracks time automatically
|
|
1884
|
+
* @namespace Utilities
|
|
1885
|
+
*/
|
|
1886
|
+
|
|
1887
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
1888
|
+
* @param {number} t - time in seconds
|
|
1889
|
+
* @return {string}
|
|
1890
|
+
* @memberof Utilities */
|
|
1891
|
+
function formatTime(t)
|
|
1892
|
+
{
|
|
1893
|
+
const sign = t < 0 ? '-' : '';
|
|
1894
|
+
t = abs(t)|0;
|
|
1895
|
+
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
1899
|
+
* @param {string} url - URL of JSON file
|
|
1900
|
+
* @return {Promise<object>}
|
|
1901
|
+
* @memberof Utilities */
|
|
1902
|
+
async function fetchJSON(url)
|
|
1903
|
+
{
|
|
1904
|
+
const response = await fetch(url);
|
|
1905
|
+
if (!response.ok)
|
|
1906
|
+
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
1907
|
+
return response.json();
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1911
|
+
|
|
1912
|
+
/** Save a text file to disk
|
|
1913
|
+
* @param {string} text
|
|
1914
|
+
* @param {string} [filename]
|
|
1915
|
+
* @param {string} [type]
|
|
1916
|
+
* @memberof Utilities */
|
|
1917
|
+
function saveText(text, filename='text', type='text/plain')
|
|
1918
|
+
{ saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
1919
|
+
|
|
1920
|
+
/** Save a canvas to disk
|
|
1921
|
+
* @param {HTMLCanvasElement|OffscreenCanvas} canvas
|
|
1922
|
+
* @param {string} [filename]
|
|
1923
|
+
* @param {string} [type]
|
|
1924
|
+
* @memberof Utilities */
|
|
1925
|
+
function saveCanvas(canvas, filename='screenshot', type='image/png')
|
|
1926
|
+
{
|
|
1927
|
+
if (canvas instanceof OffscreenCanvas)
|
|
1928
|
+
{
|
|
1929
|
+
// copy to temporary canvas and save
|
|
1930
|
+
const saveCanvas = document.createElement('canvas');
|
|
1931
|
+
saveCanvas.width = canvas.width;
|
|
1932
|
+
saveCanvas.height = canvas.height;
|
|
1933
|
+
saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
|
|
1934
|
+
saveDataURL(saveCanvas.toDataURL(type), filename);
|
|
1935
|
+
}
|
|
1936
|
+
else
|
|
1937
|
+
saveDataURL(canvas.toDataURL(type), filename);
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
/** Save a data url to disk
|
|
1941
|
+
* @param {string} url
|
|
1942
|
+
* @param {string} [filename]
|
|
1943
|
+
* @param {number} [revokeTime] - how long before revoking the url
|
|
1944
|
+
* @memberof Utilities */
|
|
1945
|
+
function saveDataURL(url, filename='download', revokeTime)
|
|
1946
|
+
{
|
|
1947
|
+
ASSERT(isString(url), 'saveDataURL requires url string');
|
|
1948
|
+
ASSERT(isString(filename), 'saveDataURL requires filename string');
|
|
1949
|
+
|
|
1950
|
+
// create link for saving screenshots
|
|
1951
|
+
const link = document.createElement('a');
|
|
1952
|
+
link.download = filename;
|
|
1953
|
+
link.href = url;
|
|
1954
|
+
link.click();
|
|
1955
|
+
if (revokeTime !== undefined)
|
|
1956
|
+
setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
/** Share content using the native share dialog if available
|
|
1960
|
+
* @param {string} title - title of the share
|
|
1961
|
+
* @param {string} url - url to share
|
|
1962
|
+
* @param {Function} [callback] - Called when share is complete
|
|
1963
|
+
* @memberof Utilities */
|
|
1964
|
+
function shareURL(title, url, callback)
|
|
1965
|
+
{
|
|
1966
|
+
ASSERT(isString(title), 'shareURL requires title string');
|
|
1967
|
+
ASSERT(isString(url), 'shareURL requires url string');
|
|
1968
|
+
navigator.share?.({title, url}).then(()=>callback?.());
|
|
1969
|
+
}
|
|
1884
1970
|
|
|
1885
1971
|
///////////////////////////////////////////////////////////////////////////////
|
|
1886
1972
|
|
|
@@ -1965,84 +2051,6 @@ class Timer
|
|
|
1965
2051
|
* @return {number} */
|
|
1966
2052
|
valueOf() { return this.get(); }
|
|
1967
2053
|
}
|
|
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
2054
|
/**
|
|
2047
2055
|
* LittleJS Engine Settings
|
|
2048
2056
|
* - All settings for the engine are here
|
|
@@ -2669,10 +2677,10 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
2669
2677
|
class EngineObject
|
|
2670
2678
|
{
|
|
2671
2679
|
/** 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]
|
|
2680
|
+
* @param {Vector2} [pos=vec2()] - World space position of the object
|
|
2681
|
+
* @param {Vector2} [size=vec2(1)] - World space size of the object
|
|
2682
|
+
* @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
|
|
2683
|
+
* @param {number} [angle] - Angle the object is rotated by
|
|
2676
2684
|
* @param {Color} [color=WHITE] - Color to apply to tile when rendered
|
|
2677
2685
|
* @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
|
|
2678
2686
|
*/
|
|
@@ -2800,15 +2808,16 @@ class EngineObject
|
|
|
2800
2808
|
// physics sanity checks
|
|
2801
2809
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
2802
2810
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
2803
|
-
|
|
2804
|
-
|
|
2811
|
+
|
|
2812
|
+
// don't do collision for static objects or if solver disabled
|
|
2813
|
+
if (!enablePhysicsSolver || !this.mass) return;
|
|
2805
2814
|
|
|
2806
2815
|
const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
|
|
2807
2816
|
if (this.groundObject)
|
|
2808
2817
|
{
|
|
2809
2818
|
// apply friction in local space of ground object
|
|
2810
2819
|
const friction = max(this.friction, this.groundObject.friction);
|
|
2811
|
-
const groundSpeed = this.groundObject.velocity
|
|
2820
|
+
const groundSpeed = this.groundObject.velocity.x;
|
|
2812
2821
|
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
|
|
2813
2822
|
this.groundObject = undefined;
|
|
2814
2823
|
}
|
|
@@ -2819,19 +2828,19 @@ class EngineObject
|
|
|
2819
2828
|
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
2820
2829
|
for (const o of engineObjectsCollide)
|
|
2821
2830
|
{
|
|
2831
|
+
// skip destroyed, child objects, or self collision
|
|
2832
|
+
if (o.destroyed || o.parent || o === this) continue;
|
|
2833
|
+
|
|
2822
2834
|
// non solid objects don't collide with each other
|
|
2823
|
-
if (
|
|
2824
|
-
continue;
|
|
2835
|
+
if (!this.isSolid && !o.isSolid) continue;
|
|
2825
2836
|
|
|
2826
2837
|
// check collision
|
|
2827
|
-
if (!this.isOverlappingObject(o))
|
|
2828
|
-
continue;
|
|
2838
|
+
if (!this.isOverlappingObject(o)) continue;
|
|
2829
2839
|
|
|
2830
2840
|
// notify objects of collision and check if should be resolved
|
|
2831
2841
|
const collide1 = this.collideWithObject(o);
|
|
2832
2842
|
const collide2 = o.collideWithObject(this);
|
|
2833
|
-
if (!collide1 || !collide2)
|
|
2834
|
-
continue;
|
|
2843
|
+
if (!collide1 || !collide2) continue;
|
|
2835
2844
|
|
|
2836
2845
|
if (isOverlapping(oldPos, this.size, o.pos, o.size))
|
|
2837
2846
|
{
|
|
@@ -2912,7 +2921,7 @@ class EngineObject
|
|
|
2912
2921
|
if (this.collideTiles)
|
|
2913
2922
|
{
|
|
2914
2923
|
// check collision against tiles
|
|
2915
|
-
const hitLayer = tileCollisionTest(this.pos, this.size, this)
|
|
2924
|
+
const hitLayer = tileCollisionTest(this.pos, this.size, this);
|
|
2916
2925
|
if (hitLayer)
|
|
2917
2926
|
{
|
|
2918
2927
|
// if already was stuck in collision, don't do anything
|
|
@@ -2933,7 +2942,7 @@ class EngineObject
|
|
|
2933
2942
|
const delta = y - this.pos.y;
|
|
2934
2943
|
if (delta < maxMoveUp)
|
|
2935
2944
|
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
2936
|
-
{
|
|
2945
|
+
{
|
|
2937
2946
|
this.pos.y = y;
|
|
2938
2947
|
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
2939
2948
|
return;
|
|
@@ -2993,10 +3002,10 @@ class EngineObject
|
|
|
2993
3002
|
|
|
2994
3003
|
// disconnect from parent and destroy children
|
|
2995
3004
|
this.destroyed = 1;
|
|
2996
|
-
this.parent
|
|
3005
|
+
this.parent?.removeChild(this);
|
|
2997
3006
|
for (const child of this.children)
|
|
2998
3007
|
{
|
|
2999
|
-
child.parent =
|
|
3008
|
+
child.parent = undefined;
|
|
3000
3009
|
child.destroy();
|
|
3001
3010
|
}
|
|
3002
3011
|
}
|
|
@@ -3017,15 +3026,15 @@ class EngineObject
|
|
|
3017
3026
|
* @param {Vector2} vec - world space vector */
|
|
3018
3027
|
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
3019
3028
|
|
|
3020
|
-
/** Called to check if a tile collision should be resolved
|
|
3029
|
+
/** 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
3030
|
* @param {number} tileData - the value of the tile at the position
|
|
3022
|
-
* @param {Vector2} pos
|
|
3023
|
-
* @return {boolean}
|
|
3031
|
+
* @param {Vector2} pos - tile where the collision occurred
|
|
3032
|
+
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity */
|
|
3024
3033
|
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
3025
3034
|
|
|
3026
|
-
/** Called to check if
|
|
3035
|
+
/** 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
3036
|
* @param {EngineObject} object - the object to test against
|
|
3028
|
-
* @return {boolean}
|
|
3037
|
+
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity
|
|
3029
3038
|
*/
|
|
3030
3039
|
collideWithObject(object) { return true; }
|
|
3031
3040
|
|
|
@@ -3066,9 +3075,9 @@ class EngineObject
|
|
|
3066
3075
|
* @return {number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
3067
3076
|
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
3068
3077
|
|
|
3069
|
-
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3078
|
+
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3070
3079
|
* @param {EngineObject} child
|
|
3071
|
-
* @param {Vector2} [localPos=(
|
|
3080
|
+
* @param {Vector2} [localPos=vec2()]
|
|
3072
3081
|
* @param {number} [localAngle]
|
|
3073
3082
|
* @return {EngineObject} The child object added */
|
|
3074
3083
|
addChild(child, localPos=vec2(), localAngle=0)
|
|
@@ -3092,7 +3101,7 @@ class EngineObject
|
|
|
3092
3101
|
const index = this.children.indexOf(child);
|
|
3093
3102
|
ASSERT(index >= 0, 'child not found in children array');
|
|
3094
3103
|
index >= 0 && this.children.splice(index, 1);
|
|
3095
|
-
child.parent =
|
|
3104
|
+
child.parent = undefined;
|
|
3096
3105
|
}
|
|
3097
3106
|
|
|
3098
3107
|
/** Check if overlapping another engine object
|
|
@@ -3104,7 +3113,7 @@ class EngineObject
|
|
|
3104
3113
|
|
|
3105
3114
|
/** Check if overlapping a point or aligned bounding box
|
|
3106
3115
|
* @param {Vector2} pos - Center of box
|
|
3107
|
-
* @param {Vector2} [size=(
|
|
3116
|
+
* @param {Vector2} [size=vec2()] - Size of box, uses a point if undefined
|
|
3108
3117
|
* @return {boolean} */
|
|
3109
3118
|
isOverlapping(pos, size=vec2())
|
|
3110
3119
|
{ return isOverlapping(this.pos, this.size, pos, size); }
|
|
@@ -3235,10 +3244,11 @@ let drawCount;
|
|
|
3235
3244
|
* Create a tile info object using a grid based system
|
|
3236
3245
|
* - This can take vecs or floats for easier use and conversion
|
|
3237
3246
|
* - If an index is passed in, the tile size and index will determine the position
|
|
3238
|
-
* @param {Vector2|number} [
|
|
3247
|
+
* @param {Vector2|number} [index=0] - Index of the tile in 1d or 2d form
|
|
3239
3248
|
* @param {Vector2|number} [size] - Size of tile in pixels
|
|
3240
|
-
* @param {number} [
|
|
3249
|
+
* @param {TextureInfo|number} [texture] - Texture index or info to use
|
|
3241
3250
|
* @param {number} [padding] - How many pixels padding around tiles
|
|
3251
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3242
3252
|
* @return {TileInfo}
|
|
3243
3253
|
* @example
|
|
3244
3254
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -3246,36 +3256,43 @@ let drawCount;
|
|
|
3246
3256
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
3247
3257
|
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
3248
3258
|
* @memberof Draw */
|
|
3249
|
-
function tile(
|
|
3259
|
+
function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3250
3260
|
{
|
|
3251
|
-
|
|
3252
|
-
|
|
3261
|
+
ASSERT(isVector2(index) || typeof index === 'number', 'index must be a vec2 or number');
|
|
3262
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
3263
|
+
ASSERT(isNumber(texture) || texture instanceof TextureInfo, 'texture must be a number or TextureInfo');
|
|
3264
|
+
ASSERT(isNumber(padding), 'padding must be a number');
|
|
3265
|
+
|
|
3266
|
+
if (headlessMode) return new TileInfo;
|
|
3253
3267
|
|
|
3254
|
-
// if size is a number, make it a vector
|
|
3255
3268
|
if (typeof size === 'number')
|
|
3256
3269
|
{
|
|
3270
|
+
// if size is a number, make it a vector
|
|
3257
3271
|
ASSERT(size > 0);
|
|
3258
3272
|
size = new Vector2(size, size);
|
|
3259
3273
|
}
|
|
3260
3274
|
|
|
3261
3275
|
// create tile info object
|
|
3262
|
-
const
|
|
3276
|
+
const textureInfo = typeof texture === 'number' ?
|
|
3277
|
+
textureInfos[texture] : texture;
|
|
3263
3278
|
|
|
3264
3279
|
// get the position of the tile
|
|
3265
|
-
const textureInfo = textureInfos[textureIndex];
|
|
3266
|
-
ASSERT(!!textureInfo, 'Texture not loaded');
|
|
3267
3280
|
const sizePaddedX = size.x + padding*2;
|
|
3268
3281
|
const sizePaddedY = size.y + padding*2;
|
|
3269
|
-
|
|
3282
|
+
let x, y;
|
|
3283
|
+
if (typeof index === 'number')
|
|
3270
3284
|
{
|
|
3271
3285
|
const cols = textureInfo.size.x / sizePaddedX |0;
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
|
|
3286
|
+
x = index % cols;
|
|
3287
|
+
y = index / cols |0;
|
|
3275
3288
|
}
|
|
3276
3289
|
else
|
|
3277
|
-
|
|
3278
|
-
|
|
3290
|
+
{
|
|
3291
|
+
x = index.x;
|
|
3292
|
+
y = index.y;
|
|
3293
|
+
}
|
|
3294
|
+
const pos = new Vector2(x*sizePaddedX + padding, y*sizePaddedY + padding);
|
|
3295
|
+
return new TileInfo(pos, size, textureInfo, padding, bleed);
|
|
3279
3296
|
}
|
|
3280
3297
|
|
|
3281
3298
|
/**
|
|
@@ -3285,24 +3302,22 @@ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=til
|
|
|
3285
3302
|
class TileInfo
|
|
3286
3303
|
{
|
|
3287
3304
|
/** Create a tile info object
|
|
3288
|
-
* @param {Vector2} [pos=(
|
|
3305
|
+
* @param {Vector2} [pos=vec2()] - Top left corner of tile in pixels
|
|
3289
3306
|
* @param {Vector2} [size] - Size of tile in pixels
|
|
3290
|
-
* @param {
|
|
3291
|
-
* @param {number}
|
|
3292
|
-
* @param {number}
|
|
3307
|
+
* @param {TextureInfo} [textureInfo] - Texture info to use
|
|
3308
|
+
* @param {number} [padding] - How many pixels padding around tiles
|
|
3309
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3293
3310
|
*/
|
|
3294
|
-
constructor(pos=vec2(), size=tileDefaultSize,
|
|
3311
|
+
constructor(pos=vec2(), size=tileDefaultSize, textureInfo=textureInfos[0], padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3295
3312
|
{
|
|
3296
3313
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
3297
3314
|
this.pos = pos.copy();
|
|
3298
3315
|
/** @property {Vector2} - Size of tile in pixels */
|
|
3299
3316
|
this.size = size.copy();
|
|
3300
|
-
/** @property {number} - Texture index to use */
|
|
3301
|
-
this.textureIndex = textureIndex;
|
|
3302
3317
|
/** @property {number} - How many pixels padding around tiles */
|
|
3303
3318
|
this.padding = padding;
|
|
3304
3319
|
/** @property {TextureInfo} - The texture info for this tile */
|
|
3305
|
-
this.textureInfo =
|
|
3320
|
+
this.textureInfo = textureInfo;
|
|
3306
3321
|
/** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
|
|
3307
3322
|
this.bleed = bleed;
|
|
3308
3323
|
}
|
|
@@ -3312,7 +3327,7 @@ class TileInfo
|
|
|
3312
3327
|
* @return {TileInfo}
|
|
3313
3328
|
*/
|
|
3314
3329
|
offset(offset)
|
|
3315
|
-
{ return new TileInfo(this.pos.add(offset), this.size, this.
|
|
3330
|
+
{ return new TileInfo(this.pos.add(offset), this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3316
3331
|
|
|
3317
3332
|
/** Returns a copy of this tile offset by a number of animation frames
|
|
3318
3333
|
* @param {number} frame - Offset to apply in animation frames
|
|
@@ -3321,23 +3336,33 @@ class TileInfo
|
|
|
3321
3336
|
frame(frame)
|
|
3322
3337
|
{
|
|
3323
3338
|
ASSERT(typeof frame === 'number');
|
|
3324
|
-
|
|
3339
|
+
const w = this.size.x + this.padding*2;
|
|
3340
|
+
const x = frame*w;
|
|
3341
|
+
ASSERT(x < this.textureInfo.size.x, 'frame extends beyond texture width!');
|
|
3342
|
+
return this.offset(new Vector2(x));
|
|
3325
3343
|
}
|
|
3326
3344
|
|
|
3327
3345
|
/**
|
|
3328
3346
|
* Set this tile to use a full image in a texture info
|
|
3329
|
-
* @param {TextureInfo} textureInfo
|
|
3347
|
+
* @param {TextureInfo} [textureInfo]
|
|
3330
3348
|
* @return {TileInfo}
|
|
3331
3349
|
*/
|
|
3332
|
-
setFullImage(textureInfo)
|
|
3350
|
+
setFullImage(textureInfo=this.textureInfo)
|
|
3333
3351
|
{
|
|
3352
|
+
this.textureInfo = textureInfo;
|
|
3334
3353
|
this.pos = new Vector2;
|
|
3335
3354
|
this.size = textureInfo.size.copy();
|
|
3336
|
-
this.textureInfo = textureInfo;
|
|
3337
|
-
// do not use padding or bleed
|
|
3338
3355
|
this.bleed = this.padding = 0;
|
|
3339
3356
|
return this;
|
|
3340
3357
|
}
|
|
3358
|
+
|
|
3359
|
+
/**
|
|
3360
|
+
* Returns a tile info for an index using this tile as refrence
|
|
3361
|
+
* @param {Vector2|number} [index=0]
|
|
3362
|
+
* @return {TileInfo}
|
|
3363
|
+
*/
|
|
3364
|
+
tile(index)
|
|
3365
|
+
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3341
3366
|
}
|
|
3342
3367
|
|
|
3343
3368
|
/**
|
|
@@ -3378,19 +3403,19 @@ class TextureInfo
|
|
|
3378
3403
|
///////////////////////////////////////////////////////////////////////////////
|
|
3379
3404
|
// Drawing functions
|
|
3380
3405
|
|
|
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]
|
|
3406
|
+
/** Draw textured tile centered in world space
|
|
3407
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
3408
|
+
* @param {Vector2} [size=vec2(1)] - Size of the tile in world space
|
|
3409
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
3410
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
3411
|
+
* @param {number} [angle] - Angle to rotate by
|
|
3412
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
3413
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
3389
3414
|
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
|
|
3390
3415
|
* @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
|
|
3391
3416
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
3392
3417
|
* @memberof Draw */
|
|
3393
|
-
function drawTile(pos, size=
|
|
3418
|
+
function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
3394
3419
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
3395
3420
|
{
|
|
3396
3421
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3400,7 +3425,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3400
3425
|
ASSERT(!additiveColor || isColor(additiveColor), 'additiveColor must be a color');
|
|
3401
3426
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3402
3427
|
|
|
3403
|
-
const textureInfo = tileInfo
|
|
3428
|
+
const textureInfo = tileInfo?.textureInfo;
|
|
3404
3429
|
const bleed = tileInfo?.bleed ?? 0;
|
|
3405
3430
|
if (useWebGL && glEnable)
|
|
3406
3431
|
{
|
|
@@ -3465,8 +3490,8 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3465
3490
|
|
|
3466
3491
|
/** Draw colored rect centered on pos
|
|
3467
3492
|
* @param {Vector2} pos
|
|
3468
|
-
* @param {Vector2} [size=(1
|
|
3469
|
-
* @param {Color} [color=
|
|
3493
|
+
* @param {Vector2} [size=vec2(1)]
|
|
3494
|
+
* @param {Color} [color=WHITE]
|
|
3470
3495
|
* @param {number} [angle]
|
|
3471
3496
|
* @param {boolean} [useWebGL=glEnable]
|
|
3472
3497
|
* @param {boolean} [screenSpace]
|
|
@@ -3479,9 +3504,9 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
3479
3504
|
|
|
3480
3505
|
/** Draw a rect centered on pos with a gradient from top to bottom
|
|
3481
3506
|
* @param {Vector2} pos
|
|
3482
|
-
* @param {Vector2} [size=(1
|
|
3483
|
-
* @param {Color} [colorTop=
|
|
3484
|
-
* @param {Color} [colorBottom=
|
|
3507
|
+
* @param {Vector2} [size=vec2(1)]
|
|
3508
|
+
* @param {Color} [colorTop=WHITE]
|
|
3509
|
+
* @param {Color} [colorBottom=BLACK]
|
|
3485
3510
|
* @param {number} [angle]
|
|
3486
3511
|
* @param {boolean} [useWebGL=glEnable]
|
|
3487
3512
|
* @param {boolean} [screenSpace]
|
|
@@ -3543,9 +3568,9 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3543
3568
|
/** Draw connected lines between a series of points
|
|
3544
3569
|
* @param {Array<Vector2>} points
|
|
3545
3570
|
* @param {number} [width]
|
|
3546
|
-
* @param {Color} [color=
|
|
3571
|
+
* @param {Color} [color=WHITE]
|
|
3547
3572
|
* @param {boolean} [wrap] - Should the last point connect to the first?
|
|
3548
|
-
* @param {Vector2} [pos=(
|
|
3573
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
3549
3574
|
* @param {number} [angle] - Angle to rotate by
|
|
3550
3575
|
* @param {boolean} [useWebGL=glEnable]
|
|
3551
3576
|
* @param {boolean} [screenSpace]
|
|
@@ -3580,13 +3605,9 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3580
3605
|
for (let i=0; i<points.length; ++i)
|
|
3581
3606
|
{
|
|
3582
3607
|
const point = points[i];
|
|
3583
|
-
|
|
3584
|
-
context.lineTo(point.x, point.y);
|
|
3585
|
-
else
|
|
3586
|
-
context.moveTo(point.x, point.y);
|
|
3608
|
+
context.lineTo(point.x, point.y);
|
|
3587
3609
|
}
|
|
3588
|
-
|
|
3589
|
-
context.closePath();
|
|
3610
|
+
wrap && context.closePath();
|
|
3590
3611
|
context.stroke();
|
|
3591
3612
|
}, screenSpace, context);
|
|
3592
3613
|
}
|
|
@@ -3596,8 +3617,8 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3596
3617
|
* @param {Vector2} posA
|
|
3597
3618
|
* @param {Vector2} posB
|
|
3598
3619
|
* @param {number} [width]
|
|
3599
|
-
* @param {Color} [color=
|
|
3600
|
-
* @param {Vector2} [pos=(
|
|
3620
|
+
* @param {Color} [color=WHITE]
|
|
3621
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
3601
3622
|
* @param {number} [angle] - Angle to rotate by
|
|
3602
3623
|
* @param {boolean} [useWebGL=glEnable]
|
|
3603
3624
|
* @param {boolean} [screenSpace]
|
|
@@ -3616,12 +3637,12 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
3616
3637
|
|
|
3617
3638
|
/** Draw colored regular polygon using passed in number of sides
|
|
3618
3639
|
* @param {Vector2} pos
|
|
3619
|
-
* @param {Vector2} [size=(1
|
|
3640
|
+
* @param {Vector2} [size=vec2(1)]
|
|
3620
3641
|
* @param {number} [sides]
|
|
3621
|
-
* @param {Color} [color=
|
|
3642
|
+
* @param {Color} [color=WHITE]
|
|
3622
3643
|
* @param {number} [angle]
|
|
3623
3644
|
* @param {number} [lineWidth]
|
|
3624
|
-
* @param {Color} [lineColor=
|
|
3645
|
+
* @param {Color} [lineColor=BLACK]
|
|
3625
3646
|
* @param {boolean} [useWebGL=glEnable]
|
|
3626
3647
|
* @param {boolean} [screenSpace]
|
|
3627
3648
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -3644,10 +3665,10 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
3644
3665
|
|
|
3645
3666
|
/** Draw colored polygon using passed in points
|
|
3646
3667
|
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
3647
|
-
* @param {Color} [color=
|
|
3668
|
+
* @param {Color} [color=WHITE]
|
|
3648
3669
|
* @param {number} [lineWidth]
|
|
3649
|
-
* @param {Color} [lineColor=
|
|
3650
|
-
* @param {Vector2} [pos=(
|
|
3670
|
+
* @param {Color} [lineColor=BLACK]
|
|
3671
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
3651
3672
|
* @param {number} [angle] - Angle to rotate by
|
|
3652
3673
|
* @param {boolean} [useWebGL=glEnable]
|
|
3653
3674
|
* @param {boolean} [screenSpace]
|
|
@@ -3694,11 +3715,11 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
3694
3715
|
|
|
3695
3716
|
/** Draw colored ellipse using passed in point
|
|
3696
3717
|
* @param {Vector2} pos
|
|
3697
|
-
* @param {Vector2} [size=(1
|
|
3698
|
-
* @param {Color} [color=
|
|
3718
|
+
* @param {Vector2} [size=vec2(1)] - Width and height diameter
|
|
3719
|
+
* @param {Color} [color=WHITE]
|
|
3699
3720
|
* @param {number} [angle]
|
|
3700
3721
|
* @param {number} [lineWidth]
|
|
3701
|
-
* @param {Color} [lineColor=
|
|
3722
|
+
* @param {Color} [lineColor=BLACK]
|
|
3702
3723
|
* @param {boolean} [useWebGL=glEnable]
|
|
3703
3724
|
* @param {boolean} [screenSpace]
|
|
3704
3725
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -3710,9 +3731,12 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
3710
3731
|
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
3711
3732
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
3712
3733
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
3713
|
-
ASSERT(lineWidth >= 0
|
|
3734
|
+
ASSERT(lineWidth >= 0, 'lineWidth must be a positive value or 0');
|
|
3714
3735
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3715
3736
|
|
|
3737
|
+
// clamp line width to prevent artifacts
|
|
3738
|
+
lineWidth = clamp(lineWidth, 0, Math.min(size.x, size.y));
|
|
3739
|
+
|
|
3716
3740
|
if (useWebGL && glEnable)
|
|
3717
3741
|
{
|
|
3718
3742
|
// draw as a regular polygon
|
|
@@ -3740,9 +3764,9 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
3740
3764
|
/** Draw colored circle using passed in point
|
|
3741
3765
|
* @param {Vector2} pos
|
|
3742
3766
|
* @param {number} [size=1] - Diameter
|
|
3743
|
-
* @param {Color} [color=
|
|
3767
|
+
* @param {Color} [color=WHITE]
|
|
3744
3768
|
* @param {number} [lineWidth=0]
|
|
3745
|
-
* @param {Color} [lineColor=
|
|
3769
|
+
* @param {Color} [lineColor=BLACK]
|
|
3746
3770
|
* @param {boolean} [useWebGL=glEnable]
|
|
3747
3771
|
* @param {boolean} [screenSpace]
|
|
3748
3772
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -3797,9 +3821,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
3797
3821
|
* @param {string|number} text
|
|
3798
3822
|
* @param {Vector2} pos
|
|
3799
3823
|
* @param {number} [size]
|
|
3800
|
-
* @param {Color} [color=
|
|
3824
|
+
* @param {Color} [color=WHITE]
|
|
3801
3825
|
* @param {number} [lineWidth]
|
|
3802
|
-
* @param {Color} [lineColor=
|
|
3826
|
+
* @param {Color} [lineColor=BLACK]
|
|
3803
3827
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
3804
3828
|
* @param {string} [font=fontDefault]
|
|
3805
3829
|
* @param {string} [fontStyle]
|
|
@@ -3823,10 +3847,10 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
3823
3847
|
* Automatically splits new lines into rows
|
|
3824
3848
|
* @param {string|number} text
|
|
3825
3849
|
* @param {Vector2} pos
|
|
3826
|
-
* @param {number}
|
|
3827
|
-
* @param {Color} [color=
|
|
3850
|
+
* @param {number} size
|
|
3851
|
+
* @param {Color} [color=WHITE]
|
|
3828
3852
|
* @param {number} [lineWidth]
|
|
3829
|
-
* @param {Color} [lineColor=
|
|
3853
|
+
* @param {Color} [lineColor=BLACK]
|
|
3830
3854
|
* @param {CanvasTextAlign} [textAlign]
|
|
3831
3855
|
* @param {string} [font=fontDefault]
|
|
3832
3856
|
* @param {string} [fontStyle]
|
|
@@ -3834,7 +3858,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
3834
3858
|
* @param {number} [angle]
|
|
3835
3859
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3836
3860
|
* @memberof Draw */
|
|
3837
|
-
function drawTextScreen(text, pos, size
|
|
3861
|
+
function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
|
|
3838
3862
|
{
|
|
3839
3863
|
ASSERT(isString(text), 'text must be a string');
|
|
3840
3864
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3958,7 +3982,7 @@ function worldToScreenDelta(worldDelta)
|
|
|
3958
3982
|
|
|
3959
3983
|
/** Convert screen space transform to world space
|
|
3960
3984
|
* @param {Vector2} screenPos
|
|
3961
|
-
* @param {Vector2} screenSize
|
|
3985
|
+
* @param {Vector2} screenSize
|
|
3962
3986
|
* @param {number} [screenAngle]
|
|
3963
3987
|
* @return {[Vector2, Vector2, number]} - [pos, size, angle]
|
|
3964
3988
|
* @memberof Draw */
|
|
@@ -4019,10 +4043,9 @@ function isOnScreen(pos, size=0)
|
|
|
4019
4043
|
* @param {boolean} [additive]
|
|
4020
4044
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4021
4045
|
* @memberof Draw */
|
|
4022
|
-
function setBlendMode(additive=false, context)
|
|
4046
|
+
function setBlendMode(additive=false, context=drawContext)
|
|
4023
4047
|
{
|
|
4024
4048
|
glAdditive = additive;
|
|
4025
|
-
context ||= drawContext;
|
|
4026
4049
|
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
4027
4050
|
}
|
|
4028
4051
|
|
|
@@ -4037,7 +4060,6 @@ function combineCanvases()
|
|
|
4037
4060
|
workContext.fillRect(0,0,w,h); // remove background alpha
|
|
4038
4061
|
glCopyToContext(workContext);
|
|
4039
4062
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
4040
|
-
mainCanvas.width |= 0;
|
|
4041
4063
|
mainContext.drawImage(workCanvas, 0, 0);
|
|
4042
4064
|
}
|
|
4043
4065
|
|
|
@@ -4142,17 +4164,22 @@ function setCursor(cursorStyle = 'auto')
|
|
|
4142
4164
|
|
|
4143
4165
|
///////////////////////////////////////////////////////////////////////////////
|
|
4144
4166
|
|
|
4167
|
+
/** Engine font image, 8x8 font provided by the engine
|
|
4168
|
+
* @type {FontImage}
|
|
4169
|
+
* @memberof Draw */
|
|
4145
4170
|
let engineFontImage;
|
|
4146
4171
|
|
|
4147
4172
|
/**
|
|
4148
|
-
* Font Image Object - Draw text
|
|
4173
|
+
* Font Image Object - Draw text by using tiles in an image
|
|
4149
4174
|
* - 96 characters (from space to tilde) are stored in an image
|
|
4150
|
-
* -
|
|
4151
|
-
* -
|
|
4175
|
+
* - A 8x8 default engine font is supplied for general use
|
|
4176
|
+
* - This system is WebGL enabled for fast text rendering
|
|
4177
|
+
* - Fonts can also be colored and scaled along each axis
|
|
4178
|
+
*
|
|
4152
4179
|
* @memberof Draw
|
|
4153
4180
|
* @example
|
|
4154
4181
|
* // use built in font
|
|
4155
|
-
* const font =
|
|
4182
|
+
* const font = engineFontImage;
|
|
4156
4183
|
*
|
|
4157
4184
|
* // draw text
|
|
4158
4185
|
* font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
|
|
@@ -4160,70 +4187,112 @@ let engineFontImage;
|
|
|
4160
4187
|
class FontImage
|
|
4161
4188
|
{
|
|
4162
4189
|
/** 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
|
|
4190
|
+
* @param {TileInfo} tileInfo - Tile info of first characeter in font
|
|
4166
4191
|
*/
|
|
4167
|
-
constructor(
|
|
4192
|
+
constructor(tileInfo)
|
|
4168
4193
|
{
|
|
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;
|
|
4194
|
+
ASSERT(!!tileInfo, 'tileInfo is required for FontImage');
|
|
4195
|
+
|
|
4196
|
+
/** @property {TileInfo} - Tile info for the font */
|
|
4197
|
+
this.tileInfo = tileInfo.frame(0);
|
|
4179
4198
|
}
|
|
4180
4199
|
|
|
4181
4200
|
/** Draw text in world space using the image font
|
|
4182
|
-
* @param {string|number}
|
|
4201
|
+
* @param {string|number} text
|
|
4183
4202
|
* @param {Vector2} pos
|
|
4184
|
-
* @param {number}
|
|
4185
|
-
* @param {boolean} [center]
|
|
4186
|
-
* @param {
|
|
4203
|
+
* @param {Vector2|number} [size]
|
|
4204
|
+
* @param {boolean} [center=true]
|
|
4205
|
+
* @param {Color} [color=WHITE]
|
|
4206
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
4207
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4187
4208
|
*/
|
|
4188
|
-
drawText(text, pos,
|
|
4209
|
+
drawText(text, pos, size=1, center, color, useWebGL, context)
|
|
4189
4210
|
{
|
|
4190
|
-
|
|
4211
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4212
|
+
|
|
4213
|
+
if (typeof size === 'number')
|
|
4214
|
+
{
|
|
4215
|
+
// if size is a number, make it a vector
|
|
4216
|
+
ASSERT(size > 0);
|
|
4217
|
+
size *= cameraScale;
|
|
4218
|
+
size = new Vector2(size, size);
|
|
4219
|
+
}
|
|
4220
|
+
else
|
|
4221
|
+
size = size.scale(cameraScale);
|
|
4222
|
+
this.drawTextScreen(text, worldToScreen(pos), size, center, color, useWebGL, context);
|
|
4191
4223
|
}
|
|
4192
4224
|
|
|
4193
4225
|
/** Draw text in screen space using the image font
|
|
4194
|
-
* @param {string|number}
|
|
4226
|
+
* @param {string|number} text
|
|
4195
4227
|
* @param {Vector2} pos
|
|
4196
|
-
* @param {number}
|
|
4228
|
+
* @param {Vector2|number} size
|
|
4197
4229
|
* @param {boolean} [center]
|
|
4198
|
-
* @param {
|
|
4230
|
+
* @param {Color} [color=WHITE]
|
|
4231
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
4232
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4199
4233
|
*/
|
|
4200
|
-
drawTextScreen(text, pos,
|
|
4234
|
+
drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
|
|
4201
4235
|
{
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4236
|
+
ASSERT(isString(text), 'text must be a string');
|
|
4237
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
4238
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4239
|
+
ASSERT(isColor(color), 'color must be a color');
|
|
4240
|
+
|
|
4241
|
+
// if size is a number, make it a vector
|
|
4242
|
+
size = typeof size === 'number' ? new Vector2(size, size) : size;
|
|
4243
|
+
|
|
4244
|
+
// precache objects for drawing
|
|
4245
|
+
const drawPos = new Vector2;
|
|
4246
|
+
const tileInfo = this.tileInfo;
|
|
4247
|
+
const padding = tileInfo.padding;
|
|
4248
|
+
const sizePaddedX = tileInfo.size.x + padding*2;
|
|
4249
|
+
const sizePaddedY = tileInfo.size.y + padding*2;
|
|
4250
|
+
const cols = tileInfo.textureInfo.size.x / sizePaddedX |0;
|
|
4251
|
+
|
|
4252
|
+
// draw each line of text
|
|
4253
|
+
(text+'').split('\n').forEach((line, j)=>
|
|
4207
4254
|
{
|
|
4208
|
-
const centerOffset = center ? line.length * size.x
|
|
4209
|
-
for (let
|
|
4255
|
+
const centerOffset = center ? (line.length-1) * size.x / 2 : 0;
|
|
4256
|
+
for (let i=line.length; i--;)
|
|
4210
4257
|
{
|
|
4211
|
-
//
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
charCode
|
|
4215
|
-
|
|
4216
|
-
// get the
|
|
4217
|
-
const
|
|
4218
|
-
const
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4258
|
+
// get the character index
|
|
4259
|
+
const charCode = line.charCodeAt(i);
|
|
4260
|
+
const index = charCode < 32 || charCode > 127 ?
|
|
4261
|
+
95 : charCode - 32; // handle out of range characters
|
|
4262
|
+
|
|
4263
|
+
// get the position of the tile
|
|
4264
|
+
const x = index % cols;
|
|
4265
|
+
const y = index / cols |0;
|
|
4266
|
+
tileInfo.pos.x = x*sizePaddedX + padding;
|
|
4267
|
+
tileInfo.pos.y = y*sizePaddedY + padding;
|
|
4268
|
+
|
|
4269
|
+
// draw the tile
|
|
4270
|
+
drawPos.x = pos.x + i * size.x - centerOffset |0;
|
|
4271
|
+
drawPos.y = pos.y + j * size.y |0;
|
|
4272
|
+
drawTile(drawPos, size, tileInfo, color, 0, false, undefined, useWebGL, true, context);
|
|
4223
4273
|
}
|
|
4224
4274
|
});
|
|
4225
|
-
context.restore();
|
|
4226
4275
|
}
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
// load engine font, called automatically on startup
|
|
4279
|
+
function fontImageInit()
|
|
4280
|
+
{
|
|
4281
|
+
return new Promise(resolve =>
|
|
4282
|
+
{
|
|
4283
|
+
// create the engine font
|
|
4284
|
+
const image = new Image;
|
|
4285
|
+
image.onerror = image.onload = ()=>
|
|
4286
|
+
{
|
|
4287
|
+
const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
|
|
4288
|
+
const textureInfo = new TextureInfo(image);
|
|
4289
|
+
const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
|
|
4290
|
+
engineFontImage = new FontImage(tileInfo);
|
|
4291
|
+
resolve();
|
|
4292
|
+
}
|
|
4293
|
+
image.crossOrigin = 'anonymous';
|
|
4294
|
+
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==';
|
|
4295
|
+
});
|
|
4227
4296
|
}
|
|
4228
4297
|
/**
|
|
4229
4298
|
* LittleJS Input System
|
|
@@ -4280,6 +4349,10 @@ let inputPreventDefault = true;
|
|
|
4280
4349
|
* @memberof Input */
|
|
4281
4350
|
let gamepadPrimary = 0;
|
|
4282
4351
|
|
|
4352
|
+
/** True if a touch device has been detected
|
|
4353
|
+
* @memberof Input */
|
|
4354
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
4355
|
+
|
|
4283
4356
|
/** Prevents input continuing to the default browser handling
|
|
4284
4357
|
* This is useful to disable for html menus so the browser can handle input normally
|
|
4285
4358
|
* @param {boolean} preventDefault
|
|
@@ -4478,10 +4551,6 @@ function gamepadStickCount(gamepad=gamepadPrimary)
|
|
|
4478
4551
|
return gamepadStickData[gamepad]?.length ?? 0;
|
|
4479
4552
|
}
|
|
4480
4553
|
|
|
4481
|
-
/** True if a touch device has been detected
|
|
4482
|
-
* @memberof Input */
|
|
4483
|
-
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
4484
|
-
|
|
4485
4554
|
///////////////////////////////////////////////////////////////////////////////
|
|
4486
4555
|
|
|
4487
4556
|
/** Pulse the vibration hardware if it exists
|
|
@@ -4490,7 +4559,7 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
|
4490
4559
|
function vibrate(pattern=100)
|
|
4491
4560
|
{
|
|
4492
4561
|
ASSERT(isNumber(pattern) || isArray(pattern), 'pattern must be a number or array');
|
|
4493
|
-
vibrateEnable && !headlessMode && navigator
|
|
4562
|
+
vibrateEnable && !headlessMode && navigator?.vibrate?.(pattern);
|
|
4494
4563
|
}
|
|
4495
4564
|
|
|
4496
4565
|
/** Cancel any ongoing vibration
|
|
@@ -4608,8 +4677,8 @@ function inputInit()
|
|
|
4608
4677
|
}
|
|
4609
4678
|
function onMouseUp(e)
|
|
4610
4679
|
{
|
|
4611
|
-
if (isTouchDevice && touchInputEnable)
|
|
4612
|
-
|
|
4680
|
+
if (isTouchDevice && touchInputEnable) return;
|
|
4681
|
+
|
|
4613
4682
|
inputData[0][e.button] = (inputData[0][e.button]&2) | 4;
|
|
4614
4683
|
}
|
|
4615
4684
|
function onMouseMove(e)
|
|
@@ -4633,9 +4702,9 @@ function inputInit()
|
|
|
4633
4702
|
function touchInputInit()
|
|
4634
4703
|
{
|
|
4635
4704
|
// add non passive touch event listeners
|
|
4636
|
-
document.addEventListener('touchstart', (e)
|
|
4637
|
-
document.addEventListener('touchmove', (e)
|
|
4638
|
-
document.addEventListener('touchend', (e)
|
|
4705
|
+
document.addEventListener('touchstart', (e)=> handleTouch(e), { passive: false });
|
|
4706
|
+
document.addEventListener('touchmove', (e)=> handleTouch(e), { passive: false });
|
|
4707
|
+
document.addEventListener('touchend', (e)=> handleTouch(e), { passive: false });
|
|
4639
4708
|
|
|
4640
4709
|
// handle all touch events the same way
|
|
4641
4710
|
let wasTouching;
|
|
@@ -4795,8 +4864,7 @@ function inputUpdate()
|
|
|
4795
4864
|
// update touch gamepad if enabled
|
|
4796
4865
|
if (touchGamepadEnable && isTouchDevice)
|
|
4797
4866
|
{
|
|
4798
|
-
if (!touchGamepadTimer.isSet())
|
|
4799
|
-
return;
|
|
4867
|
+
if (!touchGamepadTimer.isSet()) return;
|
|
4800
4868
|
|
|
4801
4869
|
// read virtual analog stick
|
|
4802
4870
|
gamepadPrimary = 0; // touch gamepad uses index 0
|
|
@@ -4834,17 +4902,15 @@ function inputUpdate()
|
|
|
4834
4902
|
}
|
|
4835
4903
|
|
|
4836
4904
|
// return if gamepads are disabled or not supported
|
|
4837
|
-
if (!gamepadsEnable || !navigator || !navigator.getGamepads)
|
|
4838
|
-
return;
|
|
4905
|
+
if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
|
|
4839
4906
|
|
|
4840
4907
|
// only poll gamepads when focused or in debug mode
|
|
4841
|
-
if (!debug && !document.hasFocus())
|
|
4842
|
-
return;
|
|
4908
|
+
if (!debug && !document.hasFocus()) return;
|
|
4843
4909
|
|
|
4844
4910
|
// poll gamepads
|
|
4845
4911
|
const maxGamepads = 8;
|
|
4846
4912
|
const gamepads = navigator.getGamepads();
|
|
4847
|
-
const gamepadCount = min(maxGamepads, gamepads.length)
|
|
4913
|
+
const gamepadCount = min(maxGamepads, gamepads.length);
|
|
4848
4914
|
for (let i=0; i<gamepadCount; ++i)
|
|
4849
4915
|
{
|
|
4850
4916
|
// get or create gamepad data
|
|
@@ -4876,8 +4942,7 @@ function inputUpdate()
|
|
|
4876
4942
|
data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
4877
4943
|
|
|
4878
4944
|
// check for any input on this gamepad, analog must be full press
|
|
4879
|
-
if (button.pressed)
|
|
4880
|
-
if (!button.value || button.value > .9)
|
|
4945
|
+
if (button.pressed && (!button.value || button.value > .9))
|
|
4881
4946
|
hadInput = true;
|
|
4882
4947
|
}
|
|
4883
4948
|
|
|
@@ -4906,7 +4971,7 @@ function inputUpdate()
|
|
|
4906
4971
|
}
|
|
4907
4972
|
|
|
4908
4973
|
// copy dpad to left analog stick when pressed
|
|
4909
|
-
if (gamepadDirectionEmulateStick &&
|
|
4974
|
+
if (gamepadDirectionEmulateStick && (dpad.x || dpad.y))
|
|
4910
4975
|
sticks[0] = dpad.clampLength();
|
|
4911
4976
|
}
|
|
4912
4977
|
|
|
@@ -4935,13 +5000,11 @@ function inputRender()
|
|
|
4935
5000
|
function touchGamepadRender()
|
|
4936
5001
|
{
|
|
4937
5002
|
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
4938
|
-
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
4939
|
-
return;
|
|
5003
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet()) return;
|
|
4940
5004
|
|
|
4941
5005
|
// fade off when not touching or paused
|
|
4942
5006
|
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
4943
|
-
if (!alpha || paused)
|
|
4944
|
-
return;
|
|
5007
|
+
if (!alpha || paused) return;
|
|
4945
5008
|
|
|
4946
5009
|
// setup the canvas
|
|
4947
5010
|
const context = mainContext;
|
|
@@ -5052,29 +5115,46 @@ function audioInit()
|
|
|
5052
5115
|
///////////////////////////////////////////////////////////////////////////////
|
|
5053
5116
|
|
|
5054
5117
|
/**
|
|
5055
|
-
* Sound Object - Stores a sound for later
|
|
5118
|
+
* Sound Object - Stores a sound for later
|
|
5119
|
+
* - this can be used to load and play wave, mp3, and ogg files
|
|
5120
|
+
* - it can also create sounds using the ZzFX sound generator
|
|
5121
|
+
* - can attenuate and apply stereo panning to sounds
|
|
5122
|
+
* - sound instance control with pause/resume capability
|
|
5056
5123
|
*
|
|
5057
5124
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
5058
5125
|
* @memberof Audio
|
|
5059
5126
|
* @example
|
|
5060
|
-
* //
|
|
5127
|
+
* // load an audio asset file
|
|
5128
|
+
* const sound_example = new Sound('sound.mp3');
|
|
5129
|
+
*
|
|
5130
|
+
* // create a zzfx sound
|
|
5061
5131
|
* const sound_example = new Sound([.5,.5]);
|
|
5062
5132
|
*
|
|
5063
|
-
* // play
|
|
5133
|
+
* // play a sound
|
|
5064
5134
|
* sound_example.play();
|
|
5065
5135
|
*/
|
|
5066
5136
|
class Sound
|
|
5067
5137
|
{
|
|
5068
|
-
/**
|
|
5069
|
-
*
|
|
5138
|
+
/**
|
|
5139
|
+
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
5140
|
+
* @param {Sound} sound
|
|
5141
|
+
* @memberof Audio
|
|
5142
|
+
*/
|
|
5143
|
+
|
|
5144
|
+
/** Create a sound object and cache the audio for later use
|
|
5145
|
+
* @param {string|Array} [asset] - Filename of audio file or zzfx array
|
|
5146
|
+
* @param {number} [randomness] - How much to randomize frequency each time sound plays, for zzfx sounds the zzfx default is used if undefined
|
|
5070
5147
|
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
5071
5148
|
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
5149
|
+
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
5072
5150
|
*/
|
|
5073
|
-
constructor(
|
|
5151
|
+
constructor(asset, randomness, range=soundDefaultRange, taper=soundDefaultTaper, onloadCallback)
|
|
5074
5152
|
{
|
|
5075
5153
|
if (!soundEnable || headlessMode) return;
|
|
5076
5154
|
|
|
5077
|
-
ASSERT(!
|
|
5155
|
+
ASSERT(!asset || isArray(asset) || isString(asset), 'asset must be a file name or zzfx array');
|
|
5156
|
+
ASSERT(randomness === undefined || isNumber(randomness), 'randomness must be a number');
|
|
5157
|
+
ASSERT(randomness === undefined || randomness >= 0 && randomness <=1, 'randomness must be between 0 and 1');
|
|
5078
5158
|
ASSERT(isNumber(range), 'range must be a number');
|
|
5079
5159
|
ASSERT(isNumber(taper), 'taper must be a number');
|
|
5080
5160
|
|
|
@@ -5083,24 +5163,35 @@ class Sound
|
|
|
5083
5163
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
5084
5164
|
this.taper = taper;
|
|
5085
5165
|
/** @property {number} - How much to randomize frequency each time sound plays */
|
|
5086
|
-
this.randomness = 0;
|
|
5166
|
+
this.randomness = randomness ?? 0;
|
|
5087
5167
|
/** @property {number} - Sample rate for this sound */
|
|
5088
5168
|
this.sampleRate = audioDefaultSampleRate;
|
|
5089
5169
|
/** @property {number} - Percentage of this sound currently loaded */
|
|
5090
5170
|
this.loadedPercent = 0;
|
|
5171
|
+
/** @property {SoundLoadCallback} - function to call when sound is loaded */
|
|
5172
|
+
this.onloadCallback = onloadCallback;
|
|
5091
5173
|
|
|
5092
|
-
|
|
5093
|
-
if (zzfxSound)
|
|
5174
|
+
if (Array.isArray(asset))
|
|
5094
5175
|
{
|
|
5176
|
+
// generate zzfx sound
|
|
5177
|
+
const zzfxSound = asset;
|
|
5178
|
+
|
|
5095
5179
|
// remove randomness so it can be applied on playback
|
|
5096
|
-
const
|
|
5097
|
-
|
|
5098
|
-
|
|
5180
|
+
const defaultRandomness = randomness ?? .05;
|
|
5181
|
+
const randomnessIndex = 1;
|
|
5182
|
+
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
5099
5183
|
zzfxSound[randomnessIndex] = 0;
|
|
5100
5184
|
|
|
5101
5185
|
// generate the zzfx samples
|
|
5102
5186
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
5103
5187
|
this.loadedPercent = 1;
|
|
5188
|
+
onloadCallback?.(this);
|
|
5189
|
+
}
|
|
5190
|
+
else if (typeof asset === 'string')
|
|
5191
|
+
{
|
|
5192
|
+
// load the audio file
|
|
5193
|
+
const filename = asset;
|
|
5194
|
+
this.loadSound(filename);
|
|
5104
5195
|
}
|
|
5105
5196
|
}
|
|
5106
5197
|
|
|
@@ -5152,7 +5243,7 @@ class Sound
|
|
|
5152
5243
|
* @param {number} [volume] - Volume to play the music at
|
|
5153
5244
|
* @param {boolean} [loop] - Should the music loop?
|
|
5154
5245
|
* @param {boolean} [paused] - Should the music start paused
|
|
5155
|
-
* @return {SoundInstance} - The
|
|
5246
|
+
* @return {SoundInstance} - The sound instance
|
|
5156
5247
|
*/
|
|
5157
5248
|
playMusic(volume=1, loop=true, paused=false)
|
|
5158
5249
|
{ return this.play(undefined, volume, 1, 0, loop, paused); }
|
|
@@ -5162,7 +5253,7 @@ class Sound
|
|
|
5162
5253
|
* @param {number} [semitoneOffset=0] - How many semitones to offset pitch
|
|
5163
5254
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
5164
5255
|
* @param {number} [volume=1] - How much to scale volume by
|
|
5165
|
-
* @return {SoundInstance} - The
|
|
5256
|
+
* @return {SoundInstance} - The sound instance
|
|
5166
5257
|
*/
|
|
5167
5258
|
playNote(semitoneOffset=0, pos, volume)
|
|
5168
5259
|
{
|
|
@@ -5175,57 +5266,14 @@ class Sound
|
|
|
5175
5266
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
5176
5267
|
*/
|
|
5177
5268
|
getDuration()
|
|
5178
|
-
{ return this.sampleChannels
|
|
5269
|
+
{ return this.sampleChannels?.[0]?.length / this.sampleRate || 0; }
|
|
5179
5270
|
|
|
5180
5271
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
5181
5272
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
5182
5273
|
*/
|
|
5183
5274
|
isLoaded() { return this.loadedPercent === 1; }
|
|
5184
|
-
}
|
|
5185
|
-
|
|
5186
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5187
|
-
|
|
5188
|
-
/**
|
|
5189
|
-
* Sound Wave Object - Loads and stores an audio file for later use
|
|
5190
|
-
* - this can be used to load and play wave, mp3, and ogg files
|
|
5191
|
-
* @extends Sound
|
|
5192
|
-
* @memberof Audio
|
|
5193
|
-
* @example
|
|
5194
|
-
* // load an audio asset file
|
|
5195
|
-
* const sound_example = new SoundWave('sound.mp3');
|
|
5196
|
-
*
|
|
5197
|
-
* // play the sound
|
|
5198
|
-
* sound_example.play();
|
|
5199
|
-
*/
|
|
5200
|
-
class SoundWave extends Sound
|
|
5201
|
-
{
|
|
5202
|
-
/**
|
|
5203
|
-
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
5204
|
-
* @param {SoundWave} sound
|
|
5205
|
-
* @memberof Audio
|
|
5206
|
-
*/
|
|
5207
5275
|
|
|
5208
|
-
/**
|
|
5209
|
-
* @param {string} filename - Filename of audio file to load
|
|
5210
|
-
* @param {number} [randomness] - How much to randomize frequency each time sound plays
|
|
5211
|
-
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
5212
|
-
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
5213
|
-
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
5214
|
-
*/
|
|
5215
|
-
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
5216
|
-
{
|
|
5217
|
-
super(undefined, range, taper);
|
|
5218
|
-
if (!soundEnable || headlessMode) return;
|
|
5219
|
-
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
5220
|
-
ASSERT(isNumber(randomness), 'randomness must be a number');
|
|
5221
|
-
|
|
5222
|
-
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
5223
|
-
this.onloadCallback = onloadCallback;
|
|
5224
|
-
this.randomness = randomness;
|
|
5225
|
-
filename && this.loadSound(filename);
|
|
5226
|
-
}
|
|
5227
|
-
|
|
5228
|
-
/** Loads a sound from a URL and decodes it into sample data. Must be used with await!
|
|
5276
|
+
/** Loads a sound from a URL and decodes it into sample data.
|
|
5229
5277
|
* @param {string} filename
|
|
5230
5278
|
* @return {Promise<void>} */
|
|
5231
5279
|
async loadSound(filename)
|
|
@@ -5267,8 +5315,7 @@ class SoundWave extends Sound
|
|
|
5267
5315
|
this.sampleRate = audioBuffer.sampleRate;
|
|
5268
5316
|
this.sampleChannels = sampleChannels;
|
|
5269
5317
|
this.loadedPercent = 1;
|
|
5270
|
-
|
|
5271
|
-
this.onloadCallback(this);
|
|
5318
|
+
this.onloadCallback?.(this);
|
|
5272
5319
|
}
|
|
5273
5320
|
}
|
|
5274
5321
|
|
|
@@ -5471,7 +5518,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
5471
5518
|
|
|
5472
5519
|
/** Stop all queued speech
|
|
5473
5520
|
* @memberof Audio */
|
|
5474
|
-
function speakStop() {speechSynthesis
|
|
5521
|
+
function speakStop() {speechSynthesis?.cancel();}
|
|
5475
5522
|
|
|
5476
5523
|
/** Get frequency of a note on a musical scale
|
|
5477
5524
|
* @param {number} semitoneOffset - How many semitones away from the root note
|
|
@@ -5730,7 +5777,7 @@ function tileCollisionGetData(pos)
|
|
|
5730
5777
|
|
|
5731
5778
|
/** Check if a tile layer collides with another object
|
|
5732
5779
|
* @param {Vector2} pos
|
|
5733
|
-
* @param {Vector2} [size=(
|
|
5780
|
+
* @param {Vector2} [size=vec2()]
|
|
5734
5781
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
5735
5782
|
* @param {boolean} [solidOnly] - Only check solid layers if true
|
|
5736
5783
|
* @return {TileCollisionLayer}
|
|
@@ -5848,20 +5895,20 @@ function tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLa
|
|
|
5848
5895
|
class TileLayerData
|
|
5849
5896
|
{
|
|
5850
5897
|
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
5851
|
-
* @param {number} [tile]
|
|
5898
|
+
* @param {number} [tile] - The tile to use, untextured if undefined
|
|
5852
5899
|
* @param {number} [direction] - Integer direction of tile, in 90 degree increments
|
|
5853
|
-
* @param {boolean} [mirror]
|
|
5854
|
-
* @param {Color} [color]
|
|
5900
|
+
* @param {boolean} [mirror] - If the tile should be mirrored along the x axis
|
|
5901
|
+
* @param {Color} [color] - Color of the tile */
|
|
5855
5902
|
constructor(tile, direction=0, mirror=false, color=new Color)
|
|
5856
5903
|
{
|
|
5857
|
-
/** @property {number}
|
|
5858
|
-
this.tile
|
|
5859
|
-
/** @property {number}
|
|
5904
|
+
/** @property {number} - The tile to use, untextured if undefined */
|
|
5905
|
+
this.tile = tile;
|
|
5906
|
+
/** @property {number} - Integer direction of tile, in 90 degree increments */
|
|
5860
5907
|
this.direction = direction;
|
|
5861
5908
|
/** @property {boolean} - If the tile should be mirrored along the x axis */
|
|
5862
|
-
this.mirror
|
|
5863
|
-
/** @property {Color}
|
|
5864
|
-
this.color
|
|
5909
|
+
this.mirror = mirror;
|
|
5910
|
+
/** @property {Color} - Color of the tile */
|
|
5911
|
+
this.color = color.copy();
|
|
5865
5912
|
}
|
|
5866
5913
|
|
|
5867
5914
|
/** Set this tile to clear, it will not be rendered */
|
|
@@ -5872,7 +5919,7 @@ class TileLayerData
|
|
|
5872
5919
|
/**
|
|
5873
5920
|
* Canvas Layer - cached off screen rendering system
|
|
5874
5921
|
* - Contains an offscreen canvas that can be rendered to
|
|
5875
|
-
* - WebGL rendering is optional, call
|
|
5922
|
+
* - WebGL rendering is optional, call updateWebGL to enable/update
|
|
5876
5923
|
* @extends EngineObject
|
|
5877
5924
|
* @memberof TileLayers
|
|
5878
5925
|
* @example
|
|
@@ -5881,29 +5928,27 @@ class TileLayerData
|
|
|
5881
5928
|
class CanvasLayer extends EngineObject
|
|
5882
5929
|
{
|
|
5883
5930
|
/** Create a canvas layer object
|
|
5884
|
-
* @param {Vector2} [
|
|
5931
|
+
* @param {Vector2} [pos] - World space position of the layer
|
|
5885
5932
|
* @param {Vector2} [size] - World space size of the layer
|
|
5886
5933
|
* @param {number} [angle] - Angle the layer is rotated by
|
|
5887
5934
|
* @param {number} [renderOrder] - Objects sorted by renderOrder
|
|
5888
5935
|
* @param {Vector2} [canvasSize] - Default size of canvas, can be changed later
|
|
5936
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
5889
5937
|
*/
|
|
5890
|
-
constructor(
|
|
5938
|
+
constructor(pos, size, angle=0, renderOrder=0, canvasSize=vec2(512), useWebGL=true)
|
|
5891
5939
|
{
|
|
5892
5940
|
ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
|
|
5893
|
-
super(
|
|
5941
|
+
super(pos, size, undefined, angle, WHITE, renderOrder);
|
|
5894
5942
|
|
|
5895
5943
|
/** @property {HTMLCanvasElement} - The canvas used by this layer */
|
|
5896
5944
|
this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
5897
5945
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
|
|
5898
5946
|
this.context = this.canvas?.getContext('2d');
|
|
5899
5947
|
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
5900
|
-
const useWebGL = false; // do not use webgl by default
|
|
5901
5948
|
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
5902
|
-
/** @property {boolean} - True if WebGL texture needs to be refreshed */
|
|
5903
|
-
this.refreshWebGL = false;
|
|
5904
5949
|
|
|
5905
5950
|
// disable physics by default
|
|
5906
|
-
this.mass =
|
|
5951
|
+
this.mass = 0;
|
|
5907
5952
|
}
|
|
5908
5953
|
|
|
5909
5954
|
/** Destroy this canvas layer */
|
|
@@ -5918,7 +5963,7 @@ class CanvasLayer extends EngineObject
|
|
|
5918
5963
|
// Render the layer, called automatically by the engine
|
|
5919
5964
|
render()
|
|
5920
5965
|
{
|
|
5921
|
-
this.draw(this.pos, this.size, this.
|
|
5966
|
+
this.draw(this.pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
5922
5967
|
}
|
|
5923
5968
|
|
|
5924
5969
|
/** Draw this canvas layer centered in world space, with color applied if using WebGL
|
|
@@ -5931,103 +5976,54 @@ class CanvasLayer extends EngineObject
|
|
|
5931
5976
|
* @param {boolean} [screenSpace] - If true the pos and size are in screen space
|
|
5932
5977
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
5933
5978
|
* @memberof Draw */
|
|
5934
|
-
draw(pos, size,
|
|
5979
|
+
draw(pos, size, color=WHITE, angle=0, mirror=false, additiveColor, screenSpace=false, context)
|
|
5935
5980
|
{
|
|
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
5981
|
// draw the canvas layer as a single tile that uses the whole texture
|
|
5945
5982
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
5983
|
+
const useWebGL = this.hasWebGL();
|
|
5946
5984
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
5947
5985
|
}
|
|
5948
5986
|
|
|
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)
|
|
5987
|
+
/** Draw a tile onto the layer canvas in world space
|
|
5956
5988
|
* @param {Vector2} pos
|
|
5957
|
-
* @param {Vector2} size
|
|
5958
|
-
* @param {
|
|
5959
|
-
* @param {
|
|
5960
|
-
* @param {
|
|
5961
|
-
|
|
5989
|
+
* @param {Vector2} [size=vec2(1)]
|
|
5990
|
+
* @param {TileInfo} [tileInfo]
|
|
5991
|
+
* @param {Color} [color=WHITE]
|
|
5992
|
+
* @param {number} [angle]
|
|
5993
|
+
* @param {boolean} [mirror] */
|
|
5994
|
+
drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle=0, mirror=false)
|
|
5962
5995
|
{
|
|
5963
|
-
if (!this.context) return;
|
|
5964
|
-
|
|
5965
|
-
const context = this.context;
|
|
5966
|
-
context.save();
|
|
5967
5996
|
pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
|
|
5968
5997
|
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
|
-
}
|
|
5998
|
+
pos.y = this.canvas.height - pos.y;
|
|
5975
5999
|
|
|
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
|
-
});
|
|
6000
|
+
// draw the tile onto the layer canvas
|
|
6001
|
+
const oldMainCanvasSize = mainCanvasSize;
|
|
6002
|
+
mainCanvasSize = vec2(this.canvas.width, this.canvas.height);
|
|
6003
|
+
const useWebGL = this.hasWebGL();
|
|
6004
|
+
useWebGL && glSetRenderTarget(this.textureInfo.glTexture);
|
|
6005
|
+
const drawContext = useWebGL ? undefined : this.context;
|
|
6006
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, undefined, useWebGL, true, drawContext);
|
|
6007
|
+
useWebGL && glSetRenderTarget();
|
|
6008
|
+
mainCanvasSize = oldMainCanvasSize;
|
|
6003
6009
|
}
|
|
6004
6010
|
|
|
6005
6011
|
/** Draw a rectangle onto the layer canvas in world space
|
|
6006
6012
|
* @param {Vector2} pos
|
|
6007
|
-
* @param {Vector2} [size=(1
|
|
6008
|
-
* @param {Color} [color=
|
|
6009
|
-
* @param {number} [angle
|
|
6013
|
+
* @param {Vector2} [size=vec2(1)]
|
|
6014
|
+
* @param {Color} [color=WHITE]
|
|
6015
|
+
* @param {number} [angle] */
|
|
6010
6016
|
drawRect(pos, size, color, angle)
|
|
6011
6017
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
6012
6018
|
|
|
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
|
-
}
|
|
6019
|
+
/** Create WebGL texture if necessary and copy layer canvas to it */
|
|
6020
|
+
updateWebGL()
|
|
6021
|
+
{ this.textureInfo.createWebGLTexture(); }
|
|
6025
6022
|
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
}
|
|
6023
|
+
/** Check if this layer is using WebGL
|
|
6024
|
+
* @return {boolean} */
|
|
6025
|
+
hasWebGL()
|
|
6026
|
+
{ return glEnable && this.textureInfo.hasWebGL(); }
|
|
6031
6027
|
}
|
|
6032
6028
|
|
|
6033
6029
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -6046,59 +6042,93 @@ class CanvasLayer extends EngineObject
|
|
|
6046
6042
|
class TileLayer extends CanvasLayer
|
|
6047
6043
|
{
|
|
6048
6044
|
/** Create a tile layer object
|
|
6049
|
-
* @param {Vector2}
|
|
6050
|
-
* @param {Vector2} size
|
|
6051
|
-
* @param {TileInfo} [tileInfo]
|
|
6045
|
+
* @param {Vector2} pos - World space position
|
|
6046
|
+
* @param {Vector2} size - World space size
|
|
6047
|
+
* @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
|
|
6052
6048
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6049
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
6053
6050
|
*/
|
|
6054
|
-
constructor(
|
|
6051
|
+
constructor(pos, size, tileInfo=tile(), renderOrder=0, useWebGL=true)
|
|
6055
6052
|
{
|
|
6056
6053
|
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
6057
|
-
super(
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
this.tileInfo =
|
|
6061
|
-
|
|
6062
|
-
// init tile data
|
|
6054
|
+
super(pos, size, 0, renderOrder, canvasSize, useWebGL);
|
|
6055
|
+
|
|
6056
|
+
/** @property {TileInfo} - Default tile info for layer */
|
|
6057
|
+
this.tileInfo = undefined;
|
|
6058
|
+
/** @property {Array<TileLayerData>} - Default tile info for layer */
|
|
6063
6059
|
this.data = [];
|
|
6064
|
-
|
|
6065
|
-
|
|
6060
|
+
/** @property {boolean} - Is this layer using a webgl texture? */
|
|
6061
|
+
this.isUsingWebGL = false;
|
|
6066
6062
|
|
|
6067
6063
|
if (headlessMode)
|
|
6068
6064
|
{
|
|
6069
|
-
// disable rendering
|
|
6070
|
-
this.
|
|
6071
|
-
this.
|
|
6072
|
-
this.redrawStart
|
|
6073
|
-
this.redrawEnd
|
|
6074
|
-
this.drawTileData
|
|
6075
|
-
this.
|
|
6076
|
-
this.
|
|
6065
|
+
// disable rendering in headless mode
|
|
6066
|
+
this.render = ()=> {};
|
|
6067
|
+
this.redraw = ()=> {};
|
|
6068
|
+
this.redrawStart = ()=> {};
|
|
6069
|
+
this.redrawEnd = ()=> {};
|
|
6070
|
+
this.drawTileData = ()=> {};
|
|
6071
|
+
this.redrawTileData = ()=> {};
|
|
6072
|
+
this.drawLayerTile = ()=> {};
|
|
6073
|
+
this.drawLayerRect = ()=> {};
|
|
6074
|
+
this.clearLayerRect = ()=> {};
|
|
6075
|
+
return;
|
|
6077
6076
|
}
|
|
6077
|
+
|
|
6078
|
+
if (tileInfo)
|
|
6079
|
+
{
|
|
6080
|
+
// set tile info
|
|
6081
|
+
this.tileInfo = tileInfo.frame(0);
|
|
6082
|
+
this.tileInfo.bleed = 0; // disable bleed for tile layers
|
|
6083
|
+
}
|
|
6084
|
+
|
|
6085
|
+
// init tile data
|
|
6086
|
+
for (let j = this.size.area(); j--;)
|
|
6087
|
+
this.data.push(new TileLayerData);
|
|
6078
6088
|
}
|
|
6079
6089
|
|
|
6080
6090
|
/** Set data at a given position in the array
|
|
6081
6091
|
* @param {Vector2} layerPos - Local position in array
|
|
6082
|
-
* @param {TileLayerData} data
|
|
6092
|
+
* @param {TileLayerData} data - Data to set
|
|
6083
6093
|
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
6084
6094
|
setData(layerPos, data, redraw=false)
|
|
6085
6095
|
{
|
|
6096
|
+
layerPos = layerPos.floor();
|
|
6086
6097
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6087
6098
|
ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6099
|
+
|
|
6100
|
+
if (!layerPos.arrayCheck(this.size)) return;
|
|
6101
|
+
this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
|
|
6102
|
+
|
|
6103
|
+
if (!redraw) return;
|
|
6104
|
+
const isRedraw = drawContext === this.context;
|
|
6105
|
+
isRedraw ? this.drawTileData(layerPos) : this.redrawTileData(layerPos);
|
|
6093
6106
|
}
|
|
6094
6107
|
|
|
6108
|
+
/** Clear data at a given position in the array
|
|
6109
|
+
* @param {Vector2} layerPos - Local position in array
|
|
6110
|
+
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
6111
|
+
clearData(layerPos, redraw=false)
|
|
6112
|
+
{ this.setData(layerPos, new TileLayerData, redraw) }
|
|
6113
|
+
|
|
6095
6114
|
/** Get data at a given position in the array
|
|
6096
6115
|
* @param {Vector2} layerPos - Local position in array
|
|
6097
6116
|
* @return {TileLayerData} */
|
|
6098
6117
|
getData(layerPos)
|
|
6099
6118
|
{
|
|
6100
6119
|
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];
|
|
6120
|
+
return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
|
|
6121
|
+
}
|
|
6122
|
+
|
|
6123
|
+
// Update the tile layer, refresh texture if needed
|
|
6124
|
+
update()
|
|
6125
|
+
{
|
|
6126
|
+
if (!glEnable && this.isUsingWebGL)
|
|
6127
|
+
{
|
|
6128
|
+
// redraw the layer if webgl was disabled or context lost
|
|
6129
|
+
this.isUsingWebGL = false;
|
|
6130
|
+
this.redraw();
|
|
6131
|
+
}
|
|
6102
6132
|
}
|
|
6103
6133
|
|
|
6104
6134
|
// Render the tile layer, called automatically by the engine
|
|
@@ -6106,40 +6136,35 @@ class TileLayer extends CanvasLayer
|
|
|
6106
6136
|
{
|
|
6107
6137
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
6108
6138
|
|
|
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
6139
|
const size = this.drawSize || this.size;
|
|
6119
6140
|
const pos = this.pos.add(size.scale(.5));
|
|
6120
|
-
|
|
6121
|
-
drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
|
|
6141
|
+
this.draw(pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
6122
6142
|
}
|
|
6123
6143
|
|
|
6144
|
+
/** Called after this layer is redrawn, does nothing by default */
|
|
6145
|
+
onRedraw() {}
|
|
6146
|
+
|
|
6124
6147
|
/** Draw all the tile data to an offscreen canvas
|
|
6125
|
-
* - This may be slow
|
|
6148
|
+
* - This may be slow if not using webgl but only needs to be done once */
|
|
6126
6149
|
redraw()
|
|
6127
6150
|
{
|
|
6128
6151
|
this.redrawStart(true);
|
|
6129
6152
|
for (let x = this.size.x; x--;)
|
|
6130
6153
|
for (let y = this.size.y; y--;)
|
|
6131
6154
|
this.drawTileData(vec2(x,y), false);
|
|
6155
|
+
this.isUsingWebGL && glFlush();
|
|
6156
|
+
this.onRedraw();
|
|
6132
6157
|
this.redrawEnd();
|
|
6133
|
-
this.useWebGL();
|
|
6134
6158
|
}
|
|
6135
6159
|
|
|
6136
6160
|
/** Call to start the redraw process
|
|
6137
|
-
* - This can be used to manually update
|
|
6161
|
+
* - This can be used to manually update parts of the level
|
|
6138
6162
|
* @param {boolean} [clear] - Should it clear the canvas before drawing */
|
|
6139
6163
|
redrawStart(clear=false)
|
|
6140
6164
|
{
|
|
6141
6165
|
if (!this.context) return;
|
|
6142
|
-
|
|
6166
|
+
ASSERT(drawContext !== this.context);
|
|
6167
|
+
|
|
6143
6168
|
// save current render settings
|
|
6144
6169
|
/** @type {[CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
|
|
6145
6170
|
this.savedRenderSettings = [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
|
|
@@ -6147,65 +6172,119 @@ class TileLayer extends CanvasLayer
|
|
|
6147
6172
|
// set the draw canvas and context to this layer
|
|
6148
6173
|
// use camera settings to match this layer's canvas
|
|
6149
6174
|
drawContext = this.context;
|
|
6150
|
-
|
|
6151
|
-
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
6152
|
-
cameraScale = tileSize.x;
|
|
6153
|
-
canvasClearColor = CLEAR_BLACK;
|
|
6175
|
+
const tileSize = this.tileInfo?.size ?? vec2(1);
|
|
6154
6176
|
mainCanvasSize = this.size.multiply(tileSize);
|
|
6155
|
-
|
|
6177
|
+
canvasClearColor = CLEAR_BLACK;
|
|
6178
|
+
cameraPos = this.size.multiply(tileSize).scale(.5);
|
|
6179
|
+
cameraScale = 1;
|
|
6180
|
+
|
|
6181
|
+
// set render target to this layer
|
|
6182
|
+
this.isUsingWebGL = this.hasWebGL();
|
|
6183
|
+
if (this.isUsingWebGL)
|
|
6184
|
+
glSetRenderTarget(this.textureInfo.glTexture, clear);
|
|
6185
|
+
else
|
|
6156
6186
|
{
|
|
6157
|
-
//
|
|
6158
|
-
this.
|
|
6159
|
-
|
|
6187
|
+
// disable smoothing for pixel art
|
|
6188
|
+
this.context.imageSmoothingEnabled = !tilesPixelated;
|
|
6189
|
+
if (clear)
|
|
6190
|
+
{
|
|
6191
|
+
// clear and set size
|
|
6192
|
+
this.canvas.width = mainCanvasSize.x;
|
|
6193
|
+
this.canvas.height = mainCanvasSize.y;
|
|
6194
|
+
}
|
|
6160
6195
|
}
|
|
6161
|
-
|
|
6162
|
-
// disable smoothing for pixel art
|
|
6163
|
-
drawContext.imageSmoothingEnabled = !tilesPixelated;
|
|
6164
|
-
|
|
6165
|
-
// setup gl rendering if enabled
|
|
6166
|
-
glPreRender();
|
|
6167
6196
|
}
|
|
6168
6197
|
|
|
6169
6198
|
/** Call to end the redraw process */
|
|
6170
6199
|
redrawEnd()
|
|
6171
6200
|
{
|
|
6172
6201
|
if (!this.context) return;
|
|
6202
|
+
ASSERT(drawContext === this.context);
|
|
6203
|
+
|
|
6204
|
+
// set stuff back to normal
|
|
6205
|
+
if (this.isUsingWebGL)
|
|
6206
|
+
glSetRenderTarget();
|
|
6207
|
+
[drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
|
|
6208
|
+
}
|
|
6209
|
+
|
|
6210
|
+
/** Draw the tile at a given position in the tile layer
|
|
6211
|
+
* This can be used to clear out tiles when they are destroyed
|
|
6212
|
+
* Tiles can also be redrawn if inside a redrawStart/End block
|
|
6213
|
+
* @param {Vector2} layerPos
|
|
6214
|
+
* @param {boolean} [clear] - should the old tile be cleared out
|
|
6215
|
+
*/
|
|
6216
|
+
drawTileData(layerPos, clear=true)
|
|
6217
|
+
{
|
|
6218
|
+
if (!this.context) return;
|
|
6219
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
6220
|
+
|
|
6221
|
+
// clear out where the tile was, can be skipped for fully opaque tiles
|
|
6222
|
+
const drawSize = this.tileInfo?.size ?? vec2(1);
|
|
6223
|
+
const drawPos = layerPos.multiply(drawSize);
|
|
6224
|
+
clear && this.clearLayerRect(drawPos, drawSize);
|
|
6225
|
+
|
|
6226
|
+
// draw the tile if it has layer data
|
|
6227
|
+
const d = this.getData(layerPos);
|
|
6228
|
+
if (!d.tile) return;
|
|
6229
|
+
|
|
6230
|
+
const tileInfo = this.tileInfo && this.tileInfo.tile(d.tile);
|
|
6231
|
+
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
6232
|
+
}
|
|
6233
|
+
|
|
6234
|
+
/** Draw the tile at a given position in the tile layer
|
|
6235
|
+
* This can be used to clear tiles when they are destroyed
|
|
6236
|
+
* For better performance use drawTileData inside a redrawStart/End block
|
|
6237
|
+
* @param {Vector2} layerPos
|
|
6238
|
+
* @param {boolean} [clear] - should the old tile be cleared
|
|
6239
|
+
*/
|
|
6240
|
+
redrawTileData(layerPos, clear=true)
|
|
6241
|
+
{
|
|
6242
|
+
if (!this.context) return;
|
|
6243
|
+
ASSERT(drawContext !== this.context, 'redrawStart() should not be active when calling redrawTileData(), instead use drawTileData()');
|
|
6173
6244
|
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6245
|
+
this.redrawStart();
|
|
6246
|
+
this.drawTileData(layerPos, clear);
|
|
6247
|
+
this.redrawEnd();
|
|
6248
|
+
}
|
|
6177
6249
|
|
|
6178
|
-
|
|
6179
|
-
|
|
6250
|
+
/** Draw textured tile in layer space
|
|
6251
|
+
* @param {Vector2} pos - Position in pixel coordinates
|
|
6252
|
+
* @param {Vector2} [size=vec2(1)] - Size of the tile
|
|
6253
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
6254
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
6255
|
+
* @param {number} [angle] - Angle to rotate by
|
|
6256
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
6257
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any */
|
|
6258
|
+
drawLayerTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
6259
|
+
angle=0, mirror, additiveColor)
|
|
6260
|
+
{
|
|
6261
|
+
const drawPos = pos.add(size.scale(.5));
|
|
6262
|
+
drawTile(drawPos, size, tileInfo, color, angle, mirror, additiveColor, this.isUsingWebGL);
|
|
6180
6263
|
}
|
|
6181
6264
|
|
|
6182
|
-
/**
|
|
6183
|
-
*
|
|
6184
|
-
*
|
|
6185
|
-
* @param {
|
|
6186
|
-
* @param {
|
|
6265
|
+
/** Clear a rectangle in layer space
|
|
6266
|
+
* @param {Vector2} pos
|
|
6267
|
+
* @param {Vector2} size
|
|
6268
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
6269
|
+
* @param {number} [angle] - Angle to rotate by
|
|
6187
6270
|
*/
|
|
6188
|
-
|
|
6271
|
+
drawLayerRect(pos, size, color, angle=0)
|
|
6272
|
+
{ this.drawLayerTile(pos, size, undefined, color, angle); }
|
|
6273
|
+
|
|
6274
|
+
/** Clear a rectangle in layer space
|
|
6275
|
+
* @param {Vector2} pos - position in pixel coordinates
|
|
6276
|
+
* @param {Vector2} size
|
|
6277
|
+
*/
|
|
6278
|
+
clearLayerRect(pos, size)
|
|
6189
6279
|
{
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
// clear out where the tile was, for full opaque tiles this can be skipped
|
|
6193
|
-
const s = this.tileInfo.size;
|
|
6194
|
-
if (clear)
|
|
6195
|
-
{
|
|
6196
|
-
const pos = layerPos.multiply(s);
|
|
6197
|
-
this.context.clearRect(pos.x, this.canvas.height-pos.y, s.x, -s.y);
|
|
6198
|
-
}
|
|
6280
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before clearing tiles');
|
|
6199
6281
|
|
|
6200
|
-
|
|
6201
|
-
const
|
|
6202
|
-
if (
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex, this.tileInfo.padding);
|
|
6207
|
-
drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
6208
|
-
}
|
|
6282
|
+
const x = pos.x, y = this.canvas.height - pos.y - size.y;
|
|
6283
|
+
const useWebGL = this.hasWebGL();
|
|
6284
|
+
if (useWebGL)
|
|
6285
|
+
glClearRect(x, y, size.x, size.y);
|
|
6286
|
+
else
|
|
6287
|
+
this.context.clearRect(x, y, size.x, size.y);
|
|
6209
6288
|
}
|
|
6210
6289
|
}
|
|
6211
6290
|
|
|
@@ -6214,21 +6293,21 @@ class TileLayer extends CanvasLayer
|
|
|
6214
6293
|
* Tile Collision Layer - a tile layer with collision
|
|
6215
6294
|
* - adds collision data and functions to TileLayer
|
|
6216
6295
|
* - there can be multiple tile collision layers
|
|
6217
|
-
* - tile collision layers should not overlap each other
|
|
6218
6296
|
* @extends TileLayer
|
|
6219
6297
|
* @memberof TileLayers
|
|
6220
6298
|
*/
|
|
6221
6299
|
class TileCollisionLayer extends TileLayer
|
|
6222
6300
|
{
|
|
6223
6301
|
/** Create a tile layer object
|
|
6224
|
-
* @param {Vector2}
|
|
6225
|
-
* @param {Vector2} size
|
|
6226
|
-
* @param {TileInfo} [tileInfo]
|
|
6302
|
+
* @param {Vector2} pos - World space position
|
|
6303
|
+
* @param {Vector2} size - World space size
|
|
6304
|
+
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
6227
6305
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6306
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
6228
6307
|
*/
|
|
6229
|
-
constructor(
|
|
6308
|
+
constructor(pos, size, tileInfo=tile(), renderOrder=0, useWebGL=true)
|
|
6230
6309
|
{
|
|
6231
|
-
super(
|
|
6310
|
+
super(pos, size.floor(), tileInfo, renderOrder, useWebGL);
|
|
6232
6311
|
|
|
6233
6312
|
/** @property {Array<number>} - The tile collision grid */
|
|
6234
6313
|
this.collisionData = [];
|
|
@@ -6264,29 +6343,34 @@ class TileCollisionLayer extends TileLayer
|
|
|
6264
6343
|
this.collisionData.fill(0);
|
|
6265
6344
|
}
|
|
6266
6345
|
|
|
6267
|
-
/** Set tile collision data for a given cell in the
|
|
6268
|
-
* @param {Vector2}
|
|
6346
|
+
/** Set tile collision data for a given cell in the layer
|
|
6347
|
+
* @param {Vector2} layerPos
|
|
6269
6348
|
* @param {number} [data] */
|
|
6270
|
-
setCollisionData(
|
|
6349
|
+
setCollisionData(layerPos, data=1)
|
|
6271
6350
|
{
|
|
6272
|
-
ASSERT(isVector2(
|
|
6273
|
-
const i = (
|
|
6274
|
-
|
|
6351
|
+
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6352
|
+
const i = (layerPos.y|0)*this.size.x + layerPos.x|0;
|
|
6353
|
+
layerPos.arrayCheck(this.size) && (this.collisionData[i] = data);
|
|
6275
6354
|
}
|
|
6276
6355
|
|
|
6277
|
-
/**
|
|
6278
|
-
* @param {Vector2}
|
|
6356
|
+
/** Clear tile collision data for a given cell in the layer
|
|
6357
|
+
* @param {Vector2} layerPos */
|
|
6358
|
+
clearCollisionData(layerPos)
|
|
6359
|
+
{ this.setCollisionData(layerPos, 0); }
|
|
6360
|
+
|
|
6361
|
+
/** Get tile collision data for a given cell in the layer
|
|
6362
|
+
* @param {Vector2} layerPos
|
|
6279
6363
|
* @return {number} */
|
|
6280
|
-
getCollisionData(
|
|
6364
|
+
getCollisionData(layerPos)
|
|
6281
6365
|
{
|
|
6282
|
-
ASSERT(isVector2(
|
|
6283
|
-
const i = (
|
|
6284
|
-
return
|
|
6366
|
+
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6367
|
+
const i = (layerPos.y|0)*this.size.x + layerPos.x|0;
|
|
6368
|
+
return layerPos.arrayCheck(this.size) ? this.collisionData[i] : 0;
|
|
6285
6369
|
}
|
|
6286
6370
|
|
|
6287
6371
|
/** Check if collision with another object should occur
|
|
6288
6372
|
* @param {Vector2} pos
|
|
6289
|
-
* @param {Vector2} [size=(
|
|
6373
|
+
* @param {Vector2} [size=vec2()]
|
|
6290
6374
|
* @param {EngineObject} [object]
|
|
6291
6375
|
* @return {boolean} */
|
|
6292
6376
|
collisionTest(pos, size=new Vector2, object)
|
|
@@ -6606,7 +6690,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6606
6690
|
particle.mirror = randBool();
|
|
6607
6691
|
|
|
6608
6692
|
// call particle create callback
|
|
6609
|
-
this.particleCreateCallback
|
|
6693
|
+
this.particleCreateCallback?.(particle);
|
|
6610
6694
|
|
|
6611
6695
|
// return the newly created particle
|
|
6612
6696
|
return particle;
|
|
@@ -6691,7 +6775,7 @@ class Particle extends EngineObject
|
|
|
6691
6775
|
const c = this.colorEnd;
|
|
6692
6776
|
this.color.set(c.r, c.g, c.b, c.a);
|
|
6693
6777
|
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
6694
|
-
this.destroyCallback
|
|
6778
|
+
this.destroyCallback?.(this);
|
|
6695
6779
|
this.destroyed = 1;
|
|
6696
6780
|
}
|
|
6697
6781
|
}
|
|
@@ -6969,7 +7053,7 @@ let glContext;
|
|
|
6969
7053
|
let glAntialias = true;
|
|
6970
7054
|
|
|
6971
7055
|
// WebGL internal variables not exposed to documentation
|
|
6972
|
-
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
|
|
7056
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glInstancedVAO, glPolyVAO, glFramebuffer, glRenderTarget, glCanBeEnabled = true;
|
|
6973
7057
|
|
|
6974
7058
|
// WebGL internal constants
|
|
6975
7059
|
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
@@ -7045,7 +7129,7 @@ function glInit(rootElement)
|
|
|
7045
7129
|
// setup instanced rendering shader program
|
|
7046
7130
|
glShader = glCreateProgram(
|
|
7047
7131
|
'#version 300 es\n' + // specify GLSL ES version
|
|
7048
|
-
'precision highp float;'+ // use highp for
|
|
7132
|
+
'precision highp float;'+ // use highp for accuracy
|
|
7049
7133
|
'uniform mat4 m;'+ // transform matrix
|
|
7050
7134
|
'in vec2 g;'+ // in: geometry
|
|
7051
7135
|
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
@@ -7060,7 +7144,7 @@ function glInit(rootElement)
|
|
|
7060
7144
|
'}' // end of shader
|
|
7061
7145
|
,
|
|
7062
7146
|
'#version 300 es\n' + // specify GLSL ES version
|
|
7063
|
-
'precision highp float;'+ // use highp for
|
|
7147
|
+
'precision highp float;'+ // use highp for accuracy
|
|
7064
7148
|
'uniform sampler2D s;'+ // texture
|
|
7065
7149
|
'in vec2 v;'+ // in: uv
|
|
7066
7150
|
'in vec4 d,e;'+ // in: color, additiveColor
|
|
@@ -7098,45 +7182,62 @@ function glInit(rootElement)
|
|
|
7098
7182
|
glColorData = new Uint32Array(glInstanceData);
|
|
7099
7183
|
glArrayBuffer = glContext.createBuffer();
|
|
7100
7184
|
glGeometryBuffer = glContext.createBuffer();
|
|
7185
|
+
glFramebuffer = glContext.createFramebuffer();
|
|
7186
|
+
glBatchCount = 0;
|
|
7101
7187
|
|
|
7102
7188
|
// create the geometry buffer, triangle strip square
|
|
7103
|
-
const geometry = new Float32Array([
|
|
7189
|
+
const geometry = new Float32Array([0,0,1,0,0,1,1,1]);
|
|
7104
7190
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7105
7191
|
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
7192
|
+
|
|
7193
|
+
let offset, shader, stride;
|
|
7194
|
+
const initVertexAttrib = (name, type, typeSize, size, divisor=0)=>
|
|
7195
|
+
{
|
|
7196
|
+
const location = glContext.getAttribLocation(shader, name);
|
|
7197
|
+
const normalize = typeSize === 1;
|
|
7198
|
+
const fixedStride = typeSize && stride;
|
|
7199
|
+
glContext.enableVertexAttribArray(location);
|
|
7200
|
+
glContext.vertexAttribPointer(location, size, type, normalize, fixedStride, offset);
|
|
7201
|
+
glContext.vertexAttribDivisor(location, divisor);
|
|
7202
|
+
offset += size*typeSize;
|
|
7203
|
+
}
|
|
7204
|
+
|
|
7205
|
+
// setup VAO for instanced rendering
|
|
7206
|
+
glInstancedVAO = glContext.createVertexArray();
|
|
7207
|
+
glContext.bindVertexArray(glInstancedVAO);
|
|
7208
|
+
|
|
7209
|
+
// configure instanced vertex attributes
|
|
7210
|
+
offset = 0, shader = glShader, stride = gl_INSTANCE_BYTE_STRIDE;
|
|
7211
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7212
|
+
initVertexAttrib('g', glContext.FLOAT, 0, 2); // geometry
|
|
7213
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7214
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7215
|
+
initVertexAttrib('p', glContext.FLOAT, 4, 4, 1); // position & size
|
|
7216
|
+
initVertexAttrib('u', glContext.FLOAT, 4, 4, 1); // texture coords
|
|
7217
|
+
initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4, 1); // color
|
|
7218
|
+
initVertexAttrib('a', glContext.UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
7219
|
+
initVertexAttrib('r', glContext.FLOAT, 4, 1, 1); // rotation
|
|
7220
|
+
|
|
7221
|
+
// setup VAO for poly rendering
|
|
7222
|
+
glPolyVAO = glContext.createVertexArray();
|
|
7223
|
+
glContext.bindVertexArray(glPolyVAO);
|
|
7224
|
+
|
|
7225
|
+
// configure poly vertex attributes
|
|
7226
|
+
offset = 0, shader = glPolyShader, stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
7227
|
+
initVertexAttrib('p', glContext.FLOAT, 4, 2); // position
|
|
7228
|
+
initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7106
7229
|
}
|
|
7107
7230
|
}
|
|
7108
7231
|
|
|
7109
|
-
function glSetInstancedMode()
|
|
7232
|
+
function glSetInstancedMode(force=false)
|
|
7110
7233
|
{
|
|
7111
|
-
if (!glPolyMode) return;
|
|
7234
|
+
if (!force && !glPolyMode) return;
|
|
7112
7235
|
|
|
7113
7236
|
// setup instanced mode
|
|
7114
7237
|
glFlush();
|
|
7115
7238
|
glPolyMode = false;
|
|
7116
7239
|
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
|
|
7240
|
+
glContext.bindVertexArray(glInstancedVAO);
|
|
7140
7241
|
}
|
|
7141
7242
|
|
|
7142
7243
|
function glSetPolyMode()
|
|
@@ -7147,36 +7248,30 @@ function glSetPolyMode()
|
|
|
7147
7248
|
glFlush();
|
|
7148
7249
|
glPolyMode = true;
|
|
7149
7250
|
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
|
|
7251
|
+
glContext.bindVertexArray(glPolyVAO);
|
|
7167
7252
|
}
|
|
7168
7253
|
|
|
7169
7254
|
// Setup WebGL render each frame, called automatically by engine
|
|
7170
7255
|
// Also used by tile layer rendering when redrawing tiles
|
|
7171
|
-
function glPreRender()
|
|
7256
|
+
function glPreRender(clear=true)
|
|
7172
7257
|
{
|
|
7173
7258
|
if (!glEnable || !glContext) return;
|
|
7174
7259
|
|
|
7175
|
-
|
|
7176
|
-
|
|
7260
|
+
ASSERT(!glBatchCount, 'glPreRender called with unflushed batch.');
|
|
7261
|
+
|
|
7262
|
+
if (!glRenderTarget)
|
|
7263
|
+
{
|
|
7264
|
+
// set to same size as main canvas
|
|
7265
|
+
glCanvas.width = mainCanvasSize.x;
|
|
7266
|
+
glCanvas.height = mainCanvasSize.y;
|
|
7267
|
+
}
|
|
7268
|
+
glContext.viewport(0, 0, mainCanvasSize.x, mainCanvasSize.y);
|
|
7269
|
+
clear && glClearCanvas();
|
|
7177
7270
|
|
|
7178
7271
|
// build the transform matrix
|
|
7179
7272
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
7273
|
+
if (glRenderTarget)
|
|
7274
|
+
s.y = -s.y; // invert y when using render target
|
|
7180
7275
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
7181
7276
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
7182
7277
|
const ca = cos(cameraAngle);
|
|
@@ -7188,7 +7283,7 @@ function glPreRender()
|
|
|
7188
7283
|
p.x, p.y, 0, 1];
|
|
7189
7284
|
|
|
7190
7285
|
// set the same transform matrix for both shaders
|
|
7191
|
-
const initUniform = (program, uniform, value)
|
|
7286
|
+
const initUniform = (program, uniform, value)=>
|
|
7192
7287
|
{
|
|
7193
7288
|
glContext.useProgram(program);
|
|
7194
7289
|
const location = glContext.getUniformLocation(program, uniform);
|
|
@@ -7205,12 +7300,14 @@ function glPreRender()
|
|
|
7205
7300
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7206
7301
|
}
|
|
7207
7302
|
|
|
7303
|
+
// rebind the array buffer
|
|
7304
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7305
|
+
|
|
7208
7306
|
// start with additive blending off
|
|
7209
7307
|
glAdditive = glBatchAdditive = false;
|
|
7210
7308
|
|
|
7211
|
-
// force it to set instanced mode
|
|
7212
|
-
|
|
7213
|
-
glSetInstancedMode();
|
|
7309
|
+
// force it to set instanced mode
|
|
7310
|
+
glSetInstancedMode(true);
|
|
7214
7311
|
}
|
|
7215
7312
|
|
|
7216
7313
|
/** Clear the canvas and setup the viewport
|
|
@@ -7219,13 +7316,9 @@ function glClearCanvas()
|
|
|
7219
7316
|
{
|
|
7220
7317
|
if (!glContext) return;
|
|
7221
7318
|
|
|
7222
|
-
// clear
|
|
7223
|
-
glCanvas.width = mainCanvasSize.x;
|
|
7224
|
-
glCanvas.height = mainCanvasSize.y;
|
|
7225
|
-
glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
|
|
7319
|
+
// clear using the canvasClearColor
|
|
7226
7320
|
const color = canvasClearColor;
|
|
7227
|
-
|
|
7228
|
-
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
7321
|
+
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
7229
7322
|
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
7230
7323
|
}
|
|
7231
7324
|
|
|
@@ -7302,7 +7395,7 @@ function glCreateTexture(image)
|
|
|
7302
7395
|
// build the texture
|
|
7303
7396
|
const texture = glContext.createTexture();
|
|
7304
7397
|
let mipMap = false;
|
|
7305
|
-
if (image
|
|
7398
|
+
if (image?.width)
|
|
7306
7399
|
{
|
|
7307
7400
|
glSetTextureData(texture, image);
|
|
7308
7401
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
@@ -7323,7 +7416,9 @@ function glCreateTexture(image)
|
|
|
7323
7416
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, minFilter);
|
|
7324
7417
|
if (mipMap)
|
|
7325
7418
|
glContext.generateMipmap(glContext.TEXTURE_2D);
|
|
7326
|
-
|
|
7419
|
+
|
|
7420
|
+
// rebind active texture
|
|
7421
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7327
7422
|
return texture;
|
|
7328
7423
|
}
|
|
7329
7424
|
|
|
@@ -7346,10 +7441,12 @@ function glSetTextureData(texture, image)
|
|
|
7346
7441
|
if (!glContext) return;
|
|
7347
7442
|
|
|
7348
7443
|
// build the texture
|
|
7349
|
-
ASSERT(
|
|
7444
|
+
ASSERT(image?.width > 0, 'Invalid image data.');
|
|
7350
7445
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
7351
7446
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
7352
|
-
|
|
7447
|
+
|
|
7448
|
+
// rebind active texture
|
|
7449
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7353
7450
|
}
|
|
7354
7451
|
|
|
7355
7452
|
/** Tells WebGL to create or update the glTexture and start tracking it
|
|
@@ -7568,6 +7665,48 @@ function glDrawColoredPoints(points, pointColors)
|
|
|
7568
7665
|
glBatchCount += vertCount;
|
|
7569
7666
|
}
|
|
7570
7667
|
|
|
7668
|
+
/** Set the WebGL render target to the given texture or back to the canvas
|
|
7669
|
+
* @param {WebGLTexture} [texture] - a texture or undefined to use normal glCanvas
|
|
7670
|
+
* @param {boolean} [clear] - should the render target be cleared
|
|
7671
|
+
* @memberof WebGL */
|
|
7672
|
+
function glSetRenderTarget(texture, clear=false)
|
|
7673
|
+
{
|
|
7674
|
+
if (texture)
|
|
7675
|
+
{
|
|
7676
|
+
glRenderTarget = texture;
|
|
7677
|
+
glContext.bindFramebuffer(glContext.FRAMEBUFFER, glFramebuffer);
|
|
7678
|
+
glContext.framebufferTexture2D(glContext.FRAMEBUFFER,
|
|
7679
|
+
glContext.COLOR_ATTACHMENT0, glContext.TEXTURE_2D, texture, 0);
|
|
7680
|
+
glPreRender(clear);
|
|
7681
|
+
}
|
|
7682
|
+
else
|
|
7683
|
+
{
|
|
7684
|
+
glFlush();
|
|
7685
|
+
glRenderTarget = undefined;
|
|
7686
|
+
glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
|
|
7687
|
+
}
|
|
7688
|
+
}
|
|
7689
|
+
|
|
7690
|
+
/** Clear out a rectangle area of the WebGL canvas or render target
|
|
7691
|
+
* @param {number} x
|
|
7692
|
+
* @param {number} y
|
|
7693
|
+
* @param {number} width
|
|
7694
|
+
* @param {number} height
|
|
7695
|
+
* @memberof WebGL */
|
|
7696
|
+
function glClearRect(x, y, width, height)
|
|
7697
|
+
{
|
|
7698
|
+
if (!glEnable) return;
|
|
7699
|
+
|
|
7700
|
+
// Enable scissor test to clear only the specified area
|
|
7701
|
+
glContext.enable(glContext.SCISSOR_TEST);
|
|
7702
|
+
glContext.scissor(x, y, width, height);
|
|
7703
|
+
glContext.clearColor(0, 0, 0, 0);
|
|
7704
|
+
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
7705
|
+
glContext.disable(glContext.SCISSOR_TEST);
|
|
7706
|
+
}
|
|
7707
|
+
|
|
7708
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7709
|
+
|
|
7571
7710
|
// WebGL internal function to convert polygon to outline triangle strip
|
|
7572
7711
|
function glMakeOutline(points, width, wrap=true)
|
|
7573
7712
|
{
|
|
@@ -7701,23 +7840,20 @@ function glPolyStrip(points)
|
|
|
7701
7840
|
const a = points[i0], b = points[i1], c = points[i2];
|
|
7702
7841
|
|
|
7703
7842
|
// check if convex
|
|
7704
|
-
if (cross(a, b, c) < e)
|
|
7705
|
-
continue;
|
|
7843
|
+
if (cross(a, b, c) < e) continue;
|
|
7706
7844
|
|
|
7707
7845
|
// check if any other point is inside
|
|
7708
7846
|
let hasInside = false;
|
|
7709
7847
|
for (let j = 0; j < indices.length; j++)
|
|
7710
7848
|
{
|
|
7711
7849
|
const k = indices[j];
|
|
7712
|
-
if (k === i0 || k === i1 || k === i2)
|
|
7713
|
-
|
|
7850
|
+
if (k === i0 || k === i1 || k === i2) continue;
|
|
7851
|
+
|
|
7714
7852
|
const p = points[k];
|
|
7715
7853
|
hasInside = pointInTriangle(p, a, b, c);
|
|
7716
|
-
if (hasInside)
|
|
7717
|
-
break;
|
|
7854
|
+
if (hasInside) break;
|
|
7718
7855
|
}
|
|
7719
|
-
if (hasInside)
|
|
7720
|
-
continue;
|
|
7856
|
+
if (hasInside) continue;
|
|
7721
7857
|
|
|
7722
7858
|
// found valid ear
|
|
7723
7859
|
triangles.push([i0, i1, i2]);
|
|
@@ -7742,8 +7878,7 @@ function glPolyStrip(points)
|
|
|
7742
7878
|
worstIndex = i;
|
|
7743
7879
|
}
|
|
7744
7880
|
}
|
|
7745
|
-
if (worstIndex < 0)
|
|
7746
|
-
break;
|
|
7881
|
+
if (worstIndex < 0) break;
|
|
7747
7882
|
|
|
7748
7883
|
const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
|
|
7749
7884
|
const i1 = indices[worstIndex];
|
|
@@ -7974,14 +8109,16 @@ class PostProcessPlugin
|
|
|
7974
8109
|
{
|
|
7975
8110
|
/** Create global post processing shader
|
|
7976
8111
|
* @param {string} shaderCode
|
|
7977
|
-
* @param {boolean} [includeMainCanvas]
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
8112
|
+
* @param {boolean} [includeMainCanvas] - combine mainCanvs onto glCanvas
|
|
8113
|
+
* @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
|
|
8114
|
+
* @example
|
|
8115
|
+
* // create the post process plugin object
|
|
8116
|
+
* new PostProcessPlugin(shaderCode);
|
|
8117
|
+
*/
|
|
8118
|
+
constructor(shaderCode, includeMainCanvas=false, feedbackTexture=false)
|
|
7983
8119
|
{
|
|
7984
8120
|
ASSERT(!postProcess, 'Post process already initialized');
|
|
8121
|
+
ASSERT(!(includeMainCanvas && feedbackTexture), 'Post process cannot both include main canvas and use feedback texture');
|
|
7985
8122
|
postProcess = this;
|
|
7986
8123
|
|
|
7987
8124
|
if (!shaderCode) // default shader pass through
|
|
@@ -7989,9 +8126,10 @@ class PostProcessPlugin
|
|
|
7989
8126
|
|
|
7990
8127
|
/** @property {WebGLProgram} - Shader for post processing */
|
|
7991
8128
|
this.shader = undefined;
|
|
7992
|
-
|
|
7993
8129
|
/** @property {WebGLTexture} - Texture for post processing */
|
|
7994
8130
|
this.texture = undefined;
|
|
8131
|
+
/** @property {WebGLVertexArrayObject} - Vertex array object */
|
|
8132
|
+
this.vao = undefined;
|
|
7995
8133
|
|
|
7996
8134
|
// setup the post processing plugin
|
|
7997
8135
|
initPostProcess();
|
|
@@ -8000,7 +8138,6 @@ class PostProcessPlugin
|
|
|
8000
8138
|
function initPostProcess()
|
|
8001
8139
|
{
|
|
8002
8140
|
if (headlessMode) return;
|
|
8003
|
-
|
|
8004
8141
|
if (!glEnable)
|
|
8005
8142
|
{
|
|
8006
8143
|
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
@@ -8011,14 +8148,14 @@ class PostProcessPlugin
|
|
|
8011
8148
|
postProcess.texture = glCreateTexture();
|
|
8012
8149
|
postProcess.shader = glCreateProgram(
|
|
8013
8150
|
'#version 300 es\n' + // specify GLSL ES version
|
|
8014
|
-
'precision highp float;'+ // use highp for
|
|
8151
|
+
'precision highp float;'+ // use highp for accuracy
|
|
8015
8152
|
'in vec2 p;'+ // position
|
|
8016
8153
|
'void main(){'+ // shader entry point
|
|
8017
8154
|
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
8018
8155
|
'}' // end of shader
|
|
8019
8156
|
,
|
|
8020
8157
|
'#version 300 es\n' + // specify GLSL ES version
|
|
8021
|
-
'precision highp float;'+ // use highp for
|
|
8158
|
+
'precision highp float;'+ // use highp for accuracy
|
|
8022
8159
|
'uniform sampler2D iChannel0;'+ // input texture
|
|
8023
8160
|
'uniform vec3 iResolution;'+ // size of output texture
|
|
8024
8161
|
'uniform float iTime;'+ // time
|
|
@@ -8029,6 +8166,17 @@ class PostProcessPlugin
|
|
|
8029
8166
|
'c.a=1.;'+ // always use full alpha
|
|
8030
8167
|
'}' // end of shader
|
|
8031
8168
|
);
|
|
8169
|
+
|
|
8170
|
+
// setup VAO for post processing
|
|
8171
|
+
postProcess.vao = glContext.createVertexArray();
|
|
8172
|
+
glContext.bindVertexArray(postProcess.vao);
|
|
8173
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
8174
|
+
|
|
8175
|
+
// configure vertex attributes
|
|
8176
|
+
const vertexByteStride = 8;
|
|
8177
|
+
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
8178
|
+
glContext.enableVertexAttribArray(pLocation);
|
|
8179
|
+
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
8032
8180
|
}
|
|
8033
8181
|
function postProcessContextLost()
|
|
8034
8182
|
{
|
|
@@ -8043,14 +8191,17 @@ class PostProcessPlugin
|
|
|
8043
8191
|
}
|
|
8044
8192
|
function postProcessRender()
|
|
8045
8193
|
{
|
|
8046
|
-
if (headlessMode) return;
|
|
8047
|
-
|
|
8048
|
-
if (!glEnable)
|
|
8049
|
-
return;
|
|
8194
|
+
if (headlessMode || !glEnable) return;
|
|
8050
8195
|
|
|
8051
8196
|
// clear out the buffer
|
|
8052
8197
|
glFlush();
|
|
8053
|
-
|
|
8198
|
+
|
|
8199
|
+
// setup shader program to draw a quad
|
|
8200
|
+
glContext.useProgram(postProcess.shader);
|
|
8201
|
+
glContext.bindVertexArray(postProcess.vao);
|
|
8202
|
+
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, true);
|
|
8203
|
+
glContext.disable(glContext.BLEND);
|
|
8204
|
+
|
|
8054
8205
|
// setup texture
|
|
8055
8206
|
glContext.activeTexture(glContext.TEXTURE0);
|
|
8056
8207
|
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
@@ -8061,29 +8212,32 @@ class PostProcessPlugin
|
|
|
8061
8212
|
workCanvas.height = mainCanvasSize.y;
|
|
8062
8213
|
glCopyToContext(workContext);
|
|
8063
8214
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
8215
|
+
mainCanvas.width |= 0
|
|
8064
8216
|
|
|
8065
8217
|
// copy work canvas to texture
|
|
8066
8218
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
|
|
8067
8219
|
}
|
|
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
|
-
|
|
8220
|
+
else if (!feedbackTexture)
|
|
8221
|
+
{
|
|
8222
|
+
// copy glCanvas to texture
|
|
8223
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
|
|
8224
|
+
}
|
|
8225
|
+
|
|
8081
8226
|
// set uniforms and draw
|
|
8082
8227
|
const uniformLocation = (name)=>glContext.getUniformLocation(postProcess.shader, name);
|
|
8083
8228
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
8084
8229
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
8085
8230
|
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
8086
8231
|
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
|
|
8232
|
+
|
|
8233
|
+
if (feedbackTexture)
|
|
8234
|
+
{
|
|
8235
|
+
// pass glCanvas back to overlay texture
|
|
8236
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
|
|
8237
|
+
}
|
|
8238
|
+
|
|
8239
|
+
// force it to set instanced mode
|
|
8240
|
+
glSetInstancedMode(true);
|
|
8087
8241
|
}
|
|
8088
8242
|
}
|
|
8089
8243
|
}
|
|
@@ -8142,7 +8296,7 @@ class ZzFXMusic extends Sound
|
|
|
8142
8296
|
/** Play the music that loops by default
|
|
8143
8297
|
* @param {number} [volume] - Volume to play the music at
|
|
8144
8298
|
* @param {boolean} [loop] - Should the music loop?
|
|
8145
|
-
* @return {
|
|
8299
|
+
* @return {SoundInstance} - The sound instance
|
|
8146
8300
|
*/
|
|
8147
8301
|
playMusic(volume=1, loop=true)
|
|
8148
8302
|
{ return super.play(undefined, volume, 1, 0, loop); }
|
|
@@ -8189,7 +8343,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
8189
8343
|
sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
|
|
8190
8344
|
|
|
8191
8345
|
// for each pattern in sequence
|
|
8192
|
-
sequence.forEach((patternIndex, sequenceIndex)
|
|
8346
|
+
sequence.forEach((patternIndex, sequenceIndex)=> {
|
|
8193
8347
|
// get pattern for current channel, use empty 1 note pattern if none found
|
|
8194
8348
|
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
8195
8349
|
|
|
@@ -8282,7 +8436,7 @@ let uiDebug = 0;
|
|
|
8282
8436
|
|
|
8283
8437
|
/** Enable UI system debug drawing
|
|
8284
8438
|
* 0=off, 1=normal, 2=show invisible
|
|
8285
|
-
* @param {number|boolean}
|
|
8439
|
+
* @param {number|boolean} debugMode
|
|
8286
8440
|
* @memberof UISystem */
|
|
8287
8441
|
function uiSetDebug(debugMode)
|
|
8288
8442
|
{ uiDebug = typeof debugMode === 'boolean' ? (debugMode ? 1 : 0) : debugMode; }
|
|
@@ -8764,7 +8918,7 @@ class UISystemPlugin
|
|
|
8764
8918
|
const up = 'ArrowUp', down = 'ArrowDown', left = 'ArrowLeft', right = 'ArrowRight';
|
|
8765
8919
|
if (both)
|
|
8766
8920
|
{
|
|
8767
|
-
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
8921
|
+
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
8768
8922
|
keyIsDown(down) || keyIsDown(right) ? 1 : 0;
|
|
8769
8923
|
}
|
|
8770
8924
|
const back = vertical ? up : left;
|
|
@@ -8795,7 +8949,7 @@ class UISystemPlugin
|
|
|
8795
8949
|
* @return {boolean} */
|
|
8796
8950
|
getNavigationWasPressed()
|
|
8797
8951
|
{
|
|
8798
|
-
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
8952
|
+
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
8799
8953
|
keyWasPressed('Space') || keyWasPressed('Enter');
|
|
8800
8954
|
}
|
|
8801
8955
|
|
|
@@ -8843,7 +8997,7 @@ class UISystemPlugin
|
|
|
8843
8997
|
buttonYes.textHeight = 40;
|
|
8844
8998
|
buttonYes.navigationIndex = 1;
|
|
8845
8999
|
buttonYes.hoverColor = hsl(0,1,.5);
|
|
8846
|
-
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
9000
|
+
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
8847
9001
|
confirmMenu.addChild(buttonYes);
|
|
8848
9002
|
|
|
8849
9003
|
// no button
|
|
@@ -8873,8 +9027,8 @@ class UISystemPlugin
|
|
|
8873
9027
|
class UIObject
|
|
8874
9028
|
{
|
|
8875
9029
|
/** Create a UIObject
|
|
8876
|
-
* @param {Vector2} [pos=(
|
|
8877
|
-
* @param {Vector2} [size=(1
|
|
9030
|
+
* @param {Vector2} [pos=vec2()]
|
|
9031
|
+
* @param {Vector2} [size=vec2(1)]
|
|
8878
9032
|
*/
|
|
8879
9033
|
constructor(pos=vec2(), size=vec2())
|
|
8880
9034
|
{
|
|
@@ -8889,7 +9043,7 @@ class UIObject
|
|
|
8889
9043
|
this.size = size.copy();
|
|
8890
9044
|
/** @property {Color} - Color of the object */
|
|
8891
9045
|
this.color = uiSystem.defaultColor.copy();
|
|
8892
|
-
/** @property {Color} - Color of the object when active, uses
|
|
9046
|
+
/** @property {Color} - Color of the object when active, uses hoverColor if undefined */
|
|
8893
9047
|
this.activeColor = undefined;
|
|
8894
9048
|
/** @property {string} - Text for this ui object */
|
|
8895
9049
|
this.text = undefined;
|
|
@@ -8987,10 +9141,10 @@ class UIObject
|
|
|
8987
9141
|
|
|
8988
9142
|
// disconnect from parent and destroy children
|
|
8989
9143
|
this.destroyed = 1;
|
|
8990
|
-
this.parent
|
|
9144
|
+
this.parent?.removeChild(this);
|
|
8991
9145
|
for (const child of this.children)
|
|
8992
9146
|
{
|
|
8993
|
-
child.parent =
|
|
9147
|
+
child.parent = undefined;
|
|
8994
9148
|
child.destroy();
|
|
8995
9149
|
}
|
|
8996
9150
|
}
|
|
@@ -9086,10 +9240,10 @@ class UIObject
|
|
|
9086
9240
|
this.interactive && this.isActiveObject() && !this.disabled ?
|
|
9087
9241
|
this.color : this.lineColor;
|
|
9088
9242
|
const color = isNavigationObject ? this.hoverColor :
|
|
9089
|
-
this.disabled ? this.disabledColor :
|
|
9090
|
-
this.interactive ?
|
|
9091
|
-
this.
|
|
9092
|
-
this.
|
|
9243
|
+
this.disabled ? this.disabledColor :
|
|
9244
|
+
this.interactive ?
|
|
9245
|
+
this.isActiveObject() ? this.activeColor || this.hoverColor :
|
|
9246
|
+
this.isHoverObject() ? this.hoverColor :
|
|
9093
9247
|
this.color : this.color;
|
|
9094
9248
|
const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
|
|
9095
9249
|
|
|
@@ -9101,7 +9255,7 @@ class UIObject
|
|
|
9101
9255
|
getTextSize()
|
|
9102
9256
|
{
|
|
9103
9257
|
return vec2(
|
|
9104
|
-
this.textWidth || this.textFitScale * this.size.x,
|
|
9258
|
+
this.textWidth || this.textFitScale * this.size.x,
|
|
9105
9259
|
this.textHeight || this.textFitScale * this.size.y);
|
|
9106
9260
|
}
|
|
9107
9261
|
|
|
@@ -9149,9 +9303,9 @@ class UIObject
|
|
|
9149
9303
|
renderDebug(visible=true)
|
|
9150
9304
|
{
|
|
9151
9305
|
// apply color based on state
|
|
9152
|
-
const color =
|
|
9306
|
+
const color =
|
|
9153
9307
|
!visible ? GREEN :
|
|
9154
|
-
this.isHoverObject() ? YELLOW :
|
|
9308
|
+
this.isHoverObject() ? YELLOW :
|
|
9155
9309
|
this.disabled ? PURPLE :
|
|
9156
9310
|
this.interactive ? RED : BLUE;
|
|
9157
9311
|
uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
|
|
@@ -9483,8 +9637,8 @@ class UIScrollbar extends UIObject
|
|
|
9483
9637
|
class UIVideo extends UIObject
|
|
9484
9638
|
{
|
|
9485
9639
|
/** Create a video player UI object
|
|
9486
|
-
* @param {Vector2}
|
|
9487
|
-
* @param {Vector2}
|
|
9640
|
+
* @param {Vector2} pos
|
|
9641
|
+
* @param {Vector2} size
|
|
9488
9642
|
* @param {string} src - Video file path or URL
|
|
9489
9643
|
* @param {boolean} [autoplay=false] - Start playing immediately?
|
|
9490
9644
|
* @param {boolean} [loop=false] - Loop the video?
|
|
@@ -9624,6 +9778,7 @@ class UIVideo extends UIObject
|
|
|
9624
9778
|
* - Contact begin and end callbacks
|
|
9625
9779
|
* - Wraps b2Vec2 type to/from Vector2
|
|
9626
9780
|
* - Raycasting and querying
|
|
9781
|
+
* - Box2dTileLayer for grid based collision
|
|
9627
9782
|
* - Every type of joint
|
|
9628
9783
|
* - Debug physics drawing
|
|
9629
9784
|
* @namespace Box2D
|
|
@@ -9673,21 +9828,24 @@ class Box2dObject extends EngineObject
|
|
|
9673
9828
|
bodyDef.set_type(bodyType);
|
|
9674
9829
|
bodyDef.set_position(box2d.vec2dTo(pos));
|
|
9675
9830
|
bodyDef.set_angle(-angle);
|
|
9831
|
+
|
|
9832
|
+
/** @property {Object} - The Box2d body */
|
|
9676
9833
|
this.body = box2d.world.CreateBody(bodyDef);
|
|
9677
|
-
|
|
9834
|
+
/** @property {Color} - Line color used for default box2d drawing */
|
|
9678
9835
|
this.lineColor = BLACK;
|
|
9679
|
-
box2d
|
|
9680
|
-
|
|
9681
|
-
// edge lists and loops for drawing
|
|
9836
|
+
/** @property {Array<Object>} - List of all edges for default box2d drawing */
|
|
9682
9837
|
this.edgeLists = [];
|
|
9838
|
+
/** @property {Array<Object>} - List of all edge loops for default box2d drawing */
|
|
9683
9839
|
this.edgeLoops = [];
|
|
9840
|
+
|
|
9841
|
+
this.body.object = this; // link body to this object
|
|
9842
|
+
box2d.objects.push(this); // keep track of all box2d objects
|
|
9684
9843
|
}
|
|
9685
9844
|
|
|
9686
9845
|
/** Destroy this object and its physics body */
|
|
9687
9846
|
destroy()
|
|
9688
9847
|
{
|
|
9689
|
-
if (this.destroyed)
|
|
9690
|
-
return;
|
|
9848
|
+
if (this.destroyed) return;
|
|
9691
9849
|
|
|
9692
9850
|
// destroy physics body, fixtures, and joints
|
|
9693
9851
|
ASSERT(this.body, 'Box2dObject has no body to destroy');
|
|
@@ -9718,11 +9876,12 @@ class Box2dObject extends EngineObject
|
|
|
9718
9876
|
}
|
|
9719
9877
|
|
|
9720
9878
|
/** Draws all this object's fixtures
|
|
9721
|
-
* @param {Color}
|
|
9722
|
-
* @param {Color}
|
|
9723
|
-
* @param {number}
|
|
9879
|
+
* @param {Color} [color]
|
|
9880
|
+
* @param {Color} [lineColor]
|
|
9881
|
+
* @param {number} [lineWidth]
|
|
9882
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
9724
9883
|
* @param {CanvasRenderingContext2D} [context] */
|
|
9725
|
-
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
9884
|
+
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
|
|
9726
9885
|
{
|
|
9727
9886
|
// draw non-edge fixtures
|
|
9728
9887
|
this.getFixtureList().forEach((fixture)=>
|
|
@@ -9730,7 +9889,7 @@ class Box2dObject extends EngineObject
|
|
|
9730
9889
|
const shape = box2d.castObjectType(fixture.GetShape());
|
|
9731
9890
|
if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
|
|
9732
9891
|
{
|
|
9733
|
-
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, context);
|
|
9892
|
+
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, useWebGL, context);
|
|
9734
9893
|
}
|
|
9735
9894
|
});
|
|
9736
9895
|
|
|
@@ -9956,6 +10115,14 @@ class Box2dObject extends EngineObject
|
|
|
9956
10115
|
return fixtures;
|
|
9957
10116
|
}
|
|
9958
10117
|
|
|
10118
|
+
/** Destroy a fixture from the body
|
|
10119
|
+
* @param {Object} [fixture] */
|
|
10120
|
+
destroyFixture(fixture) { this.body.DestroyFixture(fixture); }
|
|
10121
|
+
|
|
10122
|
+
/** Destroy all fixture from the body */
|
|
10123
|
+
destroyAllFixtures()
|
|
10124
|
+
{ this.getFixtureList().forEach(fixture=>this.destroyFixture(fixture)); }
|
|
10125
|
+
|
|
9959
10126
|
///////////////////////////////////////////////////////////////////////////////
|
|
9960
10127
|
// physics get functions
|
|
9961
10128
|
|
|
@@ -10188,6 +10355,136 @@ class Box2dObject extends EngineObject
|
|
|
10188
10355
|
}
|
|
10189
10356
|
}
|
|
10190
10357
|
|
|
10358
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10359
|
+
/**
|
|
10360
|
+
* Box2D Static Object - Box2d with a static physics body
|
|
10361
|
+
* @extends Box2dObject
|
|
10362
|
+
* @memberof Box2D
|
|
10363
|
+
*/
|
|
10364
|
+
class Box2dStaticObject extends Box2dObject
|
|
10365
|
+
{
|
|
10366
|
+
/** Create a LittleJS object with Box2d physics
|
|
10367
|
+
* @param {Vector2} [pos]
|
|
10368
|
+
* @param {Vector2} [size]
|
|
10369
|
+
* @param {TileInfo} [tileInfo]
|
|
10370
|
+
* @param {number} [angle]
|
|
10371
|
+
* @param {Color} [color]
|
|
10372
|
+
* @param {number} [renderOrder] */
|
|
10373
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
10374
|
+
{
|
|
10375
|
+
const bodyType = box2d.bodyTypeStatic;
|
|
10376
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
10377
|
+
}
|
|
10378
|
+
}
|
|
10379
|
+
|
|
10380
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10381
|
+
/**
|
|
10382
|
+
* Box2D Kiematic Object - Box2d with a kinematic physics body
|
|
10383
|
+
* @extends Box2dObject
|
|
10384
|
+
* @memberof Box2D
|
|
10385
|
+
*/
|
|
10386
|
+
class Box2dKiematicObject extends Box2dObject
|
|
10387
|
+
{
|
|
10388
|
+
/** Create a LittleJS object with Box2d physics
|
|
10389
|
+
* @param {Vector2} [pos]
|
|
10390
|
+
* @param {Vector2} [size]
|
|
10391
|
+
* @param {TileInfo} [tileInfo]
|
|
10392
|
+
* @param {number} [angle]
|
|
10393
|
+
* @param {Color} [color]
|
|
10394
|
+
* @param {number} [renderOrder] */
|
|
10395
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
10396
|
+
{
|
|
10397
|
+
const bodyType = box2d.bodyTypeKinematic;
|
|
10398
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
10399
|
+
}
|
|
10400
|
+
}
|
|
10401
|
+
|
|
10402
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10403
|
+
/**
|
|
10404
|
+
* Box2d Tile Layer
|
|
10405
|
+
* - adds Box2d support to tile layers
|
|
10406
|
+
* - creates static box2d fixtures for solid tiles
|
|
10407
|
+
* @extends Box2dObject
|
|
10408
|
+
* @memberof Box2D
|
|
10409
|
+
*/
|
|
10410
|
+
class Box2dTileLayer extends Box2dStaticObject
|
|
10411
|
+
{
|
|
10412
|
+
/** Create a Box2d tile layer object
|
|
10413
|
+
* @param {TileCollisionLayer} tileLayer - Tile layer for this object */
|
|
10414
|
+
constructor(tileLayer)
|
|
10415
|
+
{
|
|
10416
|
+
ASSERT(tileLayer instanceof TileCollisionLayer, 'tileLayer must be a TileCollisionLayer');
|
|
10417
|
+
super(tileLayer.pos, tileLayer.size);
|
|
10418
|
+
|
|
10419
|
+
/** @property {TileLayer} - The tile layer */
|
|
10420
|
+
this.tileLayer = tileLayer;
|
|
10421
|
+
this.addChild(tileLayer);
|
|
10422
|
+
}
|
|
10423
|
+
|
|
10424
|
+
render()
|
|
10425
|
+
{
|
|
10426
|
+
// do not render fixtures, tile layer handles rendering
|
|
10427
|
+
}
|
|
10428
|
+
|
|
10429
|
+
/** Create box2d collision fixtures for solid tiles
|
|
10430
|
+
* @param {number} [friction]
|
|
10431
|
+
* @param {number} [restitution] */
|
|
10432
|
+
buildCollision(friction=.2, restitution=0)
|
|
10433
|
+
{
|
|
10434
|
+
// destroy all fixtures and create new ones
|
|
10435
|
+
this.destroyAllFixtures();
|
|
10436
|
+
|
|
10437
|
+
// create box2d object for this layer
|
|
10438
|
+
const size = this.tileLayer.size;
|
|
10439
|
+
this.pos = this.tileLayer.pos.copy();
|
|
10440
|
+
this.size = size.copy();
|
|
10441
|
+
|
|
10442
|
+
// track which tiles have been processed
|
|
10443
|
+
const processed = [];
|
|
10444
|
+
const getIndex = (x, y)=> x + y * size.x;
|
|
10445
|
+
const isSolidUnprocessed = (x, y)=>
|
|
10446
|
+
!processed[getIndex(x, y)] &&
|
|
10447
|
+
this.tileLayer.getCollisionData(vec2(x, y)) > 0;
|
|
10448
|
+
|
|
10449
|
+
// combine tiles into larger boxes
|
|
10450
|
+
for (let x = 0; x < size.x; ++x)
|
|
10451
|
+
for (let y = 0; y < size.y; ++y)
|
|
10452
|
+
{
|
|
10453
|
+
if (!isSolidUnprocessed(x, y)) continue;
|
|
10454
|
+
|
|
10455
|
+
// find max width by scanning right
|
|
10456
|
+
let width = 1, height = 1, canExpand = true;
|
|
10457
|
+
while (isSolidUnprocessed(x + width, y))
|
|
10458
|
+
++width;
|
|
10459
|
+
|
|
10460
|
+
// find max height by scanning up, ensuring all rows have the same width
|
|
10461
|
+
while (canExpand)
|
|
10462
|
+
{
|
|
10463
|
+
for (let checkX = 0; checkX < width; ++checkX)
|
|
10464
|
+
{
|
|
10465
|
+
if (!isSolidUnprocessed(x + checkX, y + height))
|
|
10466
|
+
{
|
|
10467
|
+
canExpand = false;
|
|
10468
|
+
break;
|
|
10469
|
+
}
|
|
10470
|
+
}
|
|
10471
|
+
if (canExpand)
|
|
10472
|
+
++height;
|
|
10473
|
+
}
|
|
10474
|
+
|
|
10475
|
+
// mark all tiles in this rectangle as processed
|
|
10476
|
+
for (let rectX = width; rectX--;)
|
|
10477
|
+
for (let rectY = height; rectY--;)
|
|
10478
|
+
processed[getIndex(x + rectX, y + rectY)] = true;
|
|
10479
|
+
|
|
10480
|
+
// create a single fixture for the entire rectangle
|
|
10481
|
+
const shapeSize = vec2(width, height);
|
|
10482
|
+
const offset = vec2(x + width/2, y + height/2);
|
|
10483
|
+
this.addBox(shapeSize, offset, 0, 0, friction, restitution);
|
|
10484
|
+
}
|
|
10485
|
+
}
|
|
10486
|
+
}
|
|
10487
|
+
|
|
10191
10488
|
///////////////////////////////////////////////////////////////////////////////
|
|
10192
10489
|
/**
|
|
10193
10490
|
* Box2D Raycast Result
|
|
@@ -10229,6 +10526,7 @@ class Box2dJoint
|
|
|
10229
10526
|
* @param {Object} jointDef */
|
|
10230
10527
|
constructor(jointDef)
|
|
10231
10528
|
{
|
|
10529
|
+
/** @property {Object} - The Box2d joint */
|
|
10232
10530
|
this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
|
|
10233
10531
|
}
|
|
10234
10532
|
|
|
@@ -10575,7 +10873,7 @@ class Box2dGearJoint extends Box2dJoint
|
|
|
10575
10873
|
* @param {Box2dObject} objectB
|
|
10576
10874
|
* @param {Box2dJoint} joint1
|
|
10577
10875
|
* @param {Box2dJoint} joint2
|
|
10578
|
-
* @param {
|
|
10876
|
+
* @param {number} [ratio] */
|
|
10579
10877
|
constructor(objectA, objectB, joint1, joint2, ratio=1)
|
|
10580
10878
|
{
|
|
10581
10879
|
const jointDef = new box2d.instance.b2GearJointDef();
|
|
@@ -10717,50 +11015,6 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
10717
11015
|
getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
|
|
10718
11016
|
}
|
|
10719
11017
|
|
|
10720
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
10721
|
-
/**
|
|
10722
|
-
* Box2D Static Object - Box2d with a static physics body
|
|
10723
|
-
* @extends Box2dObject
|
|
10724
|
-
* @memberof Box2D
|
|
10725
|
-
*/
|
|
10726
|
-
class Box2dStaticObject extends Box2dObject
|
|
10727
|
-
{
|
|
10728
|
-
/** Create a LittleJS object with Box2d physics
|
|
10729
|
-
* @param {Vector2} [pos]
|
|
10730
|
-
* @param {Vector2} [size]
|
|
10731
|
-
* @param {TileInfo} [tileInfo]
|
|
10732
|
-
* @param {number} [angle]
|
|
10733
|
-
* @param {Color} [color]
|
|
10734
|
-
* @param {number} [renderOrder] */
|
|
10735
|
-
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
10736
|
-
{
|
|
10737
|
-
const bodyType = box2d.bodyTypeStatic;
|
|
10738
|
-
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
10739
|
-
}
|
|
10740
|
-
}
|
|
10741
|
-
|
|
10742
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
10743
|
-
/**
|
|
10744
|
-
* Box2D Kiematic Object - Box2d with a kinematic physics body
|
|
10745
|
-
* @extends Box2dObject
|
|
10746
|
-
* @memberof Box2D
|
|
10747
|
-
*/
|
|
10748
|
-
class Box2dKiematicObject extends Box2dObject
|
|
10749
|
-
{
|
|
10750
|
-
/** Create a LittleJS object with Box2d physics
|
|
10751
|
-
* @param {Vector2} [pos]
|
|
10752
|
-
* @param {Vector2} [size]
|
|
10753
|
-
* @param {TileInfo} [tileInfo]
|
|
10754
|
-
* @param {number} [angle]
|
|
10755
|
-
* @param {Color} [color]
|
|
10756
|
-
* @param {number} [renderOrder] */
|
|
10757
|
-
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
10758
|
-
{
|
|
10759
|
-
const bodyType = box2d.bodyTypeKinematic;
|
|
10760
|
-
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
10761
|
-
}
|
|
10762
|
-
}
|
|
10763
|
-
|
|
10764
11018
|
///////////////////////////////////////////////////////////////////////////////
|
|
10765
11019
|
/**
|
|
10766
11020
|
* Box2D Wheel Joint
|
|
@@ -11121,10 +11375,13 @@ class Box2dPlugin
|
|
|
11121
11375
|
{
|
|
11122
11376
|
ASSERT(!box2d, 'Box2D already initialized');
|
|
11123
11377
|
box2d = this;
|
|
11378
|
+
|
|
11379
|
+
/** @property {Object} - The Box2d instance */
|
|
11124
11380
|
this.instance = instance;
|
|
11381
|
+
/** @property {Object} - The Box2d world */
|
|
11125
11382
|
this.world = new box2d.instance.b2World();
|
|
11383
|
+
/** @property {Array<Box2dObject>} - List of all Box2d objects */
|
|
11126
11384
|
this.objects = [];
|
|
11127
|
-
|
|
11128
11385
|
/** @property {number} - Velocity iterations per update*/
|
|
11129
11386
|
this.velocityIterations = 8;
|
|
11130
11387
|
/** @property {number} - Position iterations per update*/
|
|
@@ -11323,8 +11580,9 @@ class Box2dPlugin
|
|
|
11323
11580
|
* @param {Color} [color]
|
|
11324
11581
|
* @param {Color} [lineColor]
|
|
11325
11582
|
* @param {number} [lineWidth]
|
|
11583
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
11326
11584
|
* @param {CanvasRenderingContext2D} [context] */
|
|
11327
|
-
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
11585
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebgl, context)
|
|
11328
11586
|
{
|
|
11329
11587
|
const shape = box2d.castObjectType(fixture.GetShape());
|
|
11330
11588
|
switch (shape.GetType())
|
|
@@ -11334,20 +11592,20 @@ class Box2dPlugin
|
|
|
11334
11592
|
let points = [];
|
|
11335
11593
|
for (let i=shape.GetVertexCount(); i--;)
|
|
11336
11594
|
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
11337
|
-
drawPoly(points, color, lineWidth, lineColor, pos, angle);
|
|
11595
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebgl, false, context);
|
|
11338
11596
|
break;
|
|
11339
11597
|
}
|
|
11340
11598
|
case box2d.instance.b2Shape.e_circle:
|
|
11341
11599
|
{
|
|
11342
11600
|
const radius = shape.get_m_radius();
|
|
11343
|
-
drawCircle(pos, radius*2, color, lineWidth, lineColor);
|
|
11601
|
+
drawCircle(pos, radius*2, color, lineWidth, lineColor, useWebgl, false, context);
|
|
11344
11602
|
break;
|
|
11345
11603
|
}
|
|
11346
11604
|
case box2d.instance.b2Shape.e_edge:
|
|
11347
11605
|
{
|
|
11348
11606
|
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
11349
11607
|
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
11350
|
-
drawLine(v1, v2, lineWidth, lineColor, pos, angle);
|
|
11608
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebgl, false, context);
|
|
11351
11609
|
break;
|
|
11352
11610
|
}
|
|
11353
11611
|
}
|
|
@@ -11365,7 +11623,7 @@ class Box2dPlugin
|
|
|
11365
11623
|
}
|
|
11366
11624
|
|
|
11367
11625
|
/** converts a box2d vec2 pointer to a Vector2
|
|
11368
|
-
* @param {Object}
|
|
11626
|
+
* @param {Object} vp */
|
|
11369
11627
|
vec2FromPointer(vp)
|
|
11370
11628
|
{
|
|
11371
11629
|
const v = box2d.instance.wrapPointer(vp, box2d.instance.b2Vec2);
|
|
@@ -11477,7 +11735,7 @@ async function box2dInit()
|
|
|
11477
11735
|
const box2dColorPointer = (c)=>
|
|
11478
11736
|
box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
|
|
11479
11737
|
const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
|
|
11480
|
-
const getPointsList = (vertices, vertexCount)
|
|
11738
|
+
const getPointsList = (vertices, vertexCount)=>
|
|
11481
11739
|
{
|
|
11482
11740
|
const points = [];
|
|
11483
11741
|
for (let i=vertexCount; i--;)
|