littlejsengine 1.16.1 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/littlejs.d.ts +287 -240
- package/dist/littlejs.esm.js +1419 -1259
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1076 -910
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +977 -815
- package/examples/box2d/gameObjects.js +6 -6
- package/examples/breakout/game.js +5 -6
- package/examples/electron/index.html +2 -2
- package/examples/electron/tiles.png +0 -0
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +1 -1
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/module/tiles.png +0 -0
- package/examples/platformer/gameEffects.js +24 -23
- package/examples/platformer/gameLevel.js +24 -25
- package/examples/shorts/base.html +2 -1
- package/examples/shorts/clock.js +3 -3
- package/examples/shorts/fontImage.js +4 -3
- package/examples/shorts/parallax.js +1 -1
- package/examples/shorts/sequencer.js +1 -1
- package/examples/shorts/shapes.js +1 -1
- package/examples/shorts/texture.js +10 -6
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +2 -0
- package/examples/starter/index.html +3 -2
- package/examples/starter/tiles.png +0 -0
- package/examples/typescript/tiles.png +0 -0
- package/examples/uiSystem/game.js +1 -1
- package/examples/uiSystem/tiles.png +0 -0
- package/package.json +1 -1
- package/plugins/postProcess.js +47 -28
- package/plugins/uiSystem.js +16 -16
- package/reference.md +6 -6
- package/src/engine.js +186 -198
- package/src/engineAudio.js +6 -10
- package/src/engineBuild.js +1 -0
- package/src/engineDebug.js +100 -98
- package/src/engineDraw.js +185 -148
- package/src/engineExport.js +327 -333
- package/src/engineFont.png +0 -0
- package/src/engineInput.js +17 -26
- package/src/engineMath.js +1112 -0
- package/src/engineMedals.js +4 -4
- package/src/engineObject.js +27 -27
- package/src/engineParticles.js +2 -2
- package/src/engineRelease.js +1 -3
- package/src/engineSettings.js +2 -2
- package/src/engineTileLayer.js +200 -176
- package/src/engineUtilities.js +48 -1097
- package/src/engineWebGL.js +127 -79
package/dist/littlejs.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.
|
|
36
|
+
const engineVersion = '1.17.1';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -272,33 +272,20 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
272
272
|
if (!wasUpdated)
|
|
273
273
|
updateCanvas();
|
|
274
274
|
|
|
275
|
-
// render
|
|
275
|
+
// render the game and objects
|
|
276
276
|
enginePreRender();
|
|
277
277
|
gameRender();
|
|
278
278
|
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
279
279
|
for (const o of engineObjects)
|
|
280
280
|
o.destroyed || o.render();
|
|
281
|
+
|
|
282
|
+
// post rendering
|
|
281
283
|
gameRenderPost();
|
|
282
284
|
pluginList.forEach(plugin=>plugin.render?.());
|
|
283
285
|
inputRender();
|
|
284
286
|
debugRender();
|
|
285
287
|
glFlush();
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (debugWatermark && !debugVideoCaptureIsActive())
|
|
289
|
-
{
|
|
290
|
-
// update fps display
|
|
291
|
-
mainContext.textAlign = 'right';
|
|
292
|
-
mainContext.textBaseline = 'top';
|
|
293
|
-
mainContext.font = '1em monospace';
|
|
294
|
-
mainContext.fillStyle = '#000';
|
|
295
|
-
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
296
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
297
|
-
+ (glEnable ? ' GL' : ' 2D') ;
|
|
298
|
-
mainContext.fillText(text, mainCanvas.width-3, 3);
|
|
299
|
-
mainContext.fillStyle = '#fff';
|
|
300
|
-
mainContext.fillText(text, mainCanvas.width-2, 2);
|
|
301
|
-
}
|
|
288
|
+
debugRenderPost();
|
|
302
289
|
drawCount = 0;
|
|
303
290
|
}
|
|
304
291
|
}
|
|
@@ -333,7 +320,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
333
320
|
|
|
334
321
|
// responsive aspect ratio with native resolution
|
|
335
322
|
const innerAspect = innerWidth / innerHeight;
|
|
336
|
-
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
323
|
+
ASSERT(canvasMinAspect <= canvasMaxAspect);
|
|
337
324
|
if (canvasMaxAspect && innerAspect > canvasMaxAspect)
|
|
338
325
|
{
|
|
339
326
|
// full height
|
|
@@ -364,15 +351,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
364
351
|
mainContext.lineJoin = 'round';
|
|
365
352
|
mainContext.lineCap = 'round';
|
|
366
353
|
}
|
|
367
|
-
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
{
|
|
371
|
-
await gameInit();
|
|
372
|
-
engineUpdate();
|
|
373
|
-
}
|
|
374
|
-
if (headlessMode)
|
|
375
|
-
return startEngine();
|
|
354
|
+
|
|
355
|
+
// skip setup if headless
|
|
356
|
+
if (headlessMode) return startEngine();
|
|
376
357
|
|
|
377
358
|
// setup webgl
|
|
378
359
|
glInit(rootElement);
|
|
@@ -387,7 +368,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
387
368
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
388
369
|
'-webkit-touch-callout:none'; // compatibility for ios
|
|
389
370
|
rootElement.style.cssText = styleRoot;
|
|
390
|
-
|
|
371
|
+
mainCanvas = rootElement.appendChild(document.createElement('canvas'));
|
|
391
372
|
drawContext = mainContext = mainCanvas.getContext('2d');
|
|
392
373
|
|
|
393
374
|
// init stuff and start engine
|
|
@@ -396,7 +377,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
396
377
|
debugInit();
|
|
397
378
|
|
|
398
379
|
// setup canvases
|
|
399
|
-
// transform way is still more reliable
|
|
380
|
+
// transform way is still more reliable than flexbox or grid
|
|
400
381
|
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
401
382
|
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
402
383
|
mainCanvas.style.cssText = styleCanvas;
|
|
@@ -441,6 +422,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
441
422
|
}));
|
|
442
423
|
}
|
|
443
424
|
|
|
425
|
+
// load engine font image
|
|
426
|
+
promises.push(fontImageInit());
|
|
427
|
+
|
|
444
428
|
if (showSplashScreen)
|
|
445
429
|
{
|
|
446
430
|
// draw splash screen
|
|
@@ -452,7 +436,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
452
436
|
function updateSplash()
|
|
453
437
|
{
|
|
454
438
|
inputClear();
|
|
455
|
-
|
|
439
|
+
drawEngineLogo(t+=.01);
|
|
456
440
|
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
457
441
|
}
|
|
458
442
|
}));
|
|
@@ -462,171 +446,12 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
462
446
|
await Promise.all(promises);
|
|
463
447
|
return startEngine();
|
|
464
448
|
|
|
465
|
-
|
|
466
|
-
// LittleJS Splash Screen
|
|
467
|
-
function drawEngineSplashScreen(t)
|
|
449
|
+
async function startEngine()
|
|
468
450
|
{
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
{
|
|
474
|
-
// background
|
|
475
|
-
const p3 = percent(t, 1, .8);
|
|
476
|
-
const p4 = percent(t, 0, .5);
|
|
477
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
478
|
-
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
479
|
-
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
480
|
-
x.save();
|
|
481
|
-
x.fillStyle = g;
|
|
482
|
-
x.fillRect(0,0,w,h);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// draw LittleJS logo...
|
|
486
|
-
const rect = (X, Y, W, H, C)=>
|
|
487
|
-
{
|
|
488
|
-
x.beginPath();
|
|
489
|
-
x.rect(X,Y,W,C?H*p:H);
|
|
490
|
-
x.fillStyle = C;
|
|
491
|
-
C ? x.fill() : x.stroke();
|
|
492
|
-
};
|
|
493
|
-
const line = (X, Y, Z, W)=>
|
|
494
|
-
{
|
|
495
|
-
x.beginPath();
|
|
496
|
-
x.lineTo(X,Y);
|
|
497
|
-
x.lineTo(Z,W);
|
|
498
|
-
x.stroke();
|
|
499
|
-
};
|
|
500
|
-
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
501
|
-
{
|
|
502
|
-
const D = (A+B)/2, E = p*(B-A)/2;
|
|
503
|
-
x.beginPath();
|
|
504
|
-
F && x.lineTo(X,Y);
|
|
505
|
-
x.arc(X,Y,R,D-E,D+E);
|
|
506
|
-
x.fillStyle = C;
|
|
507
|
-
C ? x.fill() : x.stroke();
|
|
508
|
-
};
|
|
509
|
-
const color = (c=0, l=0) =>
|
|
510
|
-
hsl([.98,.3,.57,.14][c%4],.8,[0,.3,.5,.8,.9][l]).toString();
|
|
511
|
-
const alpha = wave(1,1,t);
|
|
512
|
-
const p = percent(alpha, .1, .5);
|
|
513
|
-
|
|
514
|
-
// setup
|
|
515
|
-
x.translate(w/2,h/2);
|
|
516
|
-
const size = min(6, min(w,h)/99); // fit to screen
|
|
517
|
-
x.scale(size,size);
|
|
518
|
-
x.translate(-40,-35);
|
|
519
|
-
x.lineJoin = x.lineCap = 'round';
|
|
520
|
-
x.lineWidth = .1 + p*1.9;
|
|
521
|
-
|
|
522
|
-
// drawing effect
|
|
523
|
-
const p2 = percent(alpha,.1,1);
|
|
524
|
-
x.setLineDash([99*p2,99]);
|
|
525
|
-
|
|
526
|
-
// cab top
|
|
527
|
-
rect(7,16,18,-8,color(2,2));
|
|
528
|
-
rect(7,8,18,4,color(2,3));
|
|
529
|
-
rect(25,8,8,8,color(2,1));
|
|
530
|
-
rect(25,8,-18,8);
|
|
531
|
-
rect(25,8,8,8);
|
|
532
|
-
|
|
533
|
-
// cab
|
|
534
|
-
rect(25,16,7,23,color());
|
|
535
|
-
rect(11,39,14,-23,color(1,1));
|
|
536
|
-
rect(11,16,14,18,color(1,2));
|
|
537
|
-
rect(11,16,14,8,color(1,3));
|
|
538
|
-
rect(25,16,-14,24);
|
|
539
|
-
|
|
540
|
-
// cab window
|
|
541
|
-
rect(15,29,6,-9,color(2,2));
|
|
542
|
-
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
543
|
-
rect(21,21,-6,9);
|
|
544
|
-
|
|
545
|
-
// little stack
|
|
546
|
-
rect(37,14,9,6,color(3,2));
|
|
547
|
-
rect(37,14,4.5,6,color(3,3));
|
|
548
|
-
rect(37,14,9,6);
|
|
549
|
-
|
|
550
|
-
// big stack
|
|
551
|
-
rect(50,20,10,-8,color(0,1));
|
|
552
|
-
rect(50,20,6.5,-8,color(0,2));
|
|
553
|
-
rect(50,20,3.5,-8,color(0,3));
|
|
554
|
-
rect(50,20,10,-8);
|
|
555
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
556
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
557
|
-
circle(55,2,11.4,.5,PI-.5);
|
|
558
|
-
rect(45,7,20,-7,color(0,2));
|
|
559
|
-
rect(45,-1,20,4,color(0,3));
|
|
560
|
-
rect(45,-1,20,8);
|
|
561
|
-
|
|
562
|
-
// engine
|
|
563
|
-
for (let i=5; i--;)
|
|
564
|
-
{
|
|
565
|
-
// stagger radius to fix slight seam
|
|
566
|
-
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
567
|
-
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
568
|
-
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// engine outline
|
|
572
|
-
circle(36,30,10,PI/2,PI*3/2);
|
|
573
|
-
circle(48,30,10,PI/2,PI*3/2);
|
|
574
|
-
circle(60,30,10);
|
|
575
|
-
line(36,20,60,20);
|
|
576
|
-
|
|
577
|
-
// engine front light
|
|
578
|
-
circle(60,30,4,PI,3*PI,color(3,2));
|
|
579
|
-
circle(60,30,4,PI,2*PI,color(3,3));
|
|
580
|
-
circle(60,30,4,PI,3*PI);
|
|
581
|
-
|
|
582
|
-
// front brush
|
|
583
|
-
for (let i=6; i--;)
|
|
584
|
-
{
|
|
585
|
-
x.beginPath();
|
|
586
|
-
x.lineTo(53,54);
|
|
587
|
-
x.lineTo(53,40);
|
|
588
|
-
x.lineTo(53+(1+i*2.9)*p,40);
|
|
589
|
-
x.lineTo(53+(4+i*3.5)*p,54);
|
|
590
|
-
x.fillStyle = color(0,i%2+2);
|
|
591
|
-
x.fill();
|
|
592
|
-
i%2 && x.stroke();
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// wheels
|
|
596
|
-
rect(6,40,5,5);
|
|
597
|
-
rect(6,40,5,5,color());
|
|
598
|
-
rect(15,54,38,-14,color());
|
|
599
|
-
for (let i=3; i--;)
|
|
600
|
-
for (let j=2; j--;)
|
|
601
|
-
{
|
|
602
|
-
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
603
|
-
x.stroke();
|
|
604
|
-
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
605
|
-
x.stroke();
|
|
606
|
-
}
|
|
607
|
-
line(6,40,68,40); // center
|
|
608
|
-
line(77,54,4,54); // bottom
|
|
609
|
-
|
|
610
|
-
// draw engine name
|
|
611
|
-
const s = engineName;
|
|
612
|
-
x.font = '900 16px arial';
|
|
613
|
-
x.textAlign = 'center';
|
|
614
|
-
x.textBaseline = 'top';
|
|
615
|
-
x.lineWidth = .1+p*3.9;
|
|
616
|
-
let w2 = 0;
|
|
617
|
-
for (let i=0; i<s.length; ++i)
|
|
618
|
-
w2 += x.measureText(s[i]).width;
|
|
619
|
-
for (let j=2; j--;)
|
|
620
|
-
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
621
|
-
{
|
|
622
|
-
x.fillStyle = color(i,2);
|
|
623
|
-
const w = x.measureText(s[i]).width;
|
|
624
|
-
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
625
|
-
X += w;
|
|
626
|
-
}
|
|
627
|
-
x.restore();
|
|
451
|
+
// wait for gameInit to load
|
|
452
|
+
await gameInit();
|
|
453
|
+
engineUpdate();
|
|
628
454
|
}
|
|
629
|
-
///////////////////////////////////////////////////////////////////////////
|
|
630
455
|
}
|
|
631
456
|
|
|
632
457
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -640,8 +465,7 @@ function engineObjectsUpdate()
|
|
|
640
465
|
// recursive object update
|
|
641
466
|
function updateObject(o)
|
|
642
467
|
{
|
|
643
|
-
if (o.destroyed)
|
|
644
|
-
return;
|
|
468
|
+
if (o.destroyed) return;
|
|
645
469
|
|
|
646
470
|
o.update();
|
|
647
471
|
for (const child of o.children)
|
|
@@ -649,8 +473,7 @@ function engineObjectsUpdate()
|
|
|
649
473
|
}
|
|
650
474
|
for (const o of engineObjects)
|
|
651
475
|
{
|
|
652
|
-
if (o.parent)
|
|
653
|
-
continue;
|
|
476
|
+
if (o.parent || o.destroyed) continue;
|
|
654
477
|
|
|
655
478
|
// update top level objects
|
|
656
479
|
o.update();
|
|
@@ -739,6 +562,171 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
|
739
562
|
|
|
740
563
|
debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
|
|
741
564
|
return hitObjects;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
568
|
+
function drawEngineLogo(t)
|
|
569
|
+
{
|
|
570
|
+
// LittleJS Logo and Splash Screen
|
|
571
|
+
const x = mainContext;
|
|
572
|
+
const w = mainCanvas.width = innerWidth;
|
|
573
|
+
const h = mainCanvas.height = innerHeight;
|
|
574
|
+
|
|
575
|
+
{
|
|
576
|
+
// background
|
|
577
|
+
const p3 = percent(t, 1, .8);
|
|
578
|
+
const p4 = percent(t, 0, .5);
|
|
579
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
580
|
+
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
581
|
+
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
582
|
+
x.save();
|
|
583
|
+
x.fillStyle = g;
|
|
584
|
+
x.fillRect(0,0,w,h);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// draw LittleJS logo...
|
|
588
|
+
const rect = (X, Y, W, H, C)=>
|
|
589
|
+
{
|
|
590
|
+
x.beginPath();
|
|
591
|
+
x.rect(X,Y,W,C?H*p:H);
|
|
592
|
+
x.fillStyle = C;
|
|
593
|
+
C ? x.fill() : x.stroke();
|
|
594
|
+
};
|
|
595
|
+
const line = (X, Y, Z, W)=>
|
|
596
|
+
{
|
|
597
|
+
x.beginPath();
|
|
598
|
+
x.lineTo(X,Y);
|
|
599
|
+
x.lineTo(Z,W);
|
|
600
|
+
x.stroke();
|
|
601
|
+
};
|
|
602
|
+
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
603
|
+
{
|
|
604
|
+
const D = (A+B)/2, E = p*(B-A)/2;
|
|
605
|
+
x.beginPath();
|
|
606
|
+
F && x.lineTo(X,Y);
|
|
607
|
+
x.arc(X,Y,R,D-E,D+E);
|
|
608
|
+
x.fillStyle = C;
|
|
609
|
+
C ? x.fill() : x.stroke();
|
|
610
|
+
};
|
|
611
|
+
const color = (c=0, l=0) =>
|
|
612
|
+
hsl([.98,.3,.57,.14][c%4],.9,[0,.3,.5,.8,.9][l]).toString();
|
|
613
|
+
const alpha = wave(1,1,t);
|
|
614
|
+
const p = percent(alpha, .1, .5);
|
|
615
|
+
|
|
616
|
+
// setup
|
|
617
|
+
x.translate(w/2,h/2);
|
|
618
|
+
const size = min(6, min(w,h)/99); // fit to screen
|
|
619
|
+
x.scale(size,size);
|
|
620
|
+
x.translate(-40,-35);
|
|
621
|
+
x.lineJoin = x.lineCap = 'round';
|
|
622
|
+
x.lineWidth = .1 + p*1.9;
|
|
623
|
+
|
|
624
|
+
// drawing effect
|
|
625
|
+
const p2 = percent(alpha,.1,1);
|
|
626
|
+
x.setLineDash([99*p2,99]);
|
|
627
|
+
|
|
628
|
+
// cab top
|
|
629
|
+
rect(7,16,18,-8,color(2,2));
|
|
630
|
+
rect(7,8,18,4,color(2,3));
|
|
631
|
+
rect(25,8,8,8,color(2,1));
|
|
632
|
+
rect(25,8,-18,8);
|
|
633
|
+
rect(25,8,8,8);
|
|
634
|
+
|
|
635
|
+
// cab
|
|
636
|
+
rect(25,16,7,24,color());
|
|
637
|
+
rect(11,39,14,-23,color(1,1));
|
|
638
|
+
rect(11,16,14,18,color(1,2));
|
|
639
|
+
rect(11,16,14,8,color(1,3));
|
|
640
|
+
rect(25,16,-14,24);
|
|
641
|
+
|
|
642
|
+
// cab window
|
|
643
|
+
rect(15,29,6,-9,color(2,2));
|
|
644
|
+
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
645
|
+
rect(21,21,-6,9);
|
|
646
|
+
|
|
647
|
+
// little stack
|
|
648
|
+
rect(37,14,9,6,color(3,2));
|
|
649
|
+
rect(37,14,4.5,6,color(3,3));
|
|
650
|
+
rect(37,14,9,6);
|
|
651
|
+
|
|
652
|
+
// big stack
|
|
653
|
+
rect(50,20,10,-8,color(0,1));
|
|
654
|
+
rect(50,20,6.5,-8,color(0,2));
|
|
655
|
+
rect(50,20,3.5,-8,color(0,3));
|
|
656
|
+
rect(50,20,10,-8);
|
|
657
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
658
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
659
|
+
circle(55,2,11.4,.5,PI-.5);
|
|
660
|
+
rect(45,7,20,-7,color(0,2));
|
|
661
|
+
rect(45,-1,20,4,color(0,3));
|
|
662
|
+
rect(45,-1,20,8);
|
|
663
|
+
|
|
664
|
+
// engine
|
|
665
|
+
for (let i=5; i--;)
|
|
666
|
+
{
|
|
667
|
+
// stagger radius to fix slight seam
|
|
668
|
+
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
669
|
+
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
670
|
+
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// engine outline
|
|
674
|
+
circle(36,30,10,PI/2,PI*3/2);
|
|
675
|
+
circle(48,30,10,PI/2,PI*3/2);
|
|
676
|
+
circle(60,30,10);
|
|
677
|
+
line(36,20,60,20);
|
|
678
|
+
|
|
679
|
+
// engine front light
|
|
680
|
+
circle(60,30,4,PI,3*PI,color(3,2));
|
|
681
|
+
circle(60,30,4,PI,2*PI,color(3,3));
|
|
682
|
+
circle(60,30,4,PI,3*PI);
|
|
683
|
+
|
|
684
|
+
// front brush
|
|
685
|
+
for (let i=6; i--;)
|
|
686
|
+
{
|
|
687
|
+
x.beginPath();
|
|
688
|
+
x.lineTo(53,54);
|
|
689
|
+
x.lineTo(53,40);
|
|
690
|
+
x.lineTo(53+(1+i*2.9)*p,40);
|
|
691
|
+
x.lineTo(53+(4+i*3.5)*p,54);
|
|
692
|
+
x.fillStyle = color(0,i%2+2);
|
|
693
|
+
x.fill();
|
|
694
|
+
i%2 && x.stroke();
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// wheels
|
|
698
|
+
rect(6,40,5,5);
|
|
699
|
+
rect(6,40,5,5,color());
|
|
700
|
+
rect(15,54,38,-14,color());
|
|
701
|
+
for (let i=3; i--;)
|
|
702
|
+
for (let j=2; j--;)
|
|
703
|
+
{
|
|
704
|
+
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
705
|
+
x.stroke();
|
|
706
|
+
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
707
|
+
x.stroke();
|
|
708
|
+
}
|
|
709
|
+
line(6,40,68,40); // center
|
|
710
|
+
line(77,54,4,54); // bottom
|
|
711
|
+
|
|
712
|
+
// draw engine name
|
|
713
|
+
const s = engineName;
|
|
714
|
+
x.font = '900 16px arial';
|
|
715
|
+
x.textAlign = 'center';
|
|
716
|
+
x.textBaseline = 'top';
|
|
717
|
+
x.lineWidth = .1+p*3.9;
|
|
718
|
+
let w2 = 0;
|
|
719
|
+
for (let i=0; i<s.length; ++i)
|
|
720
|
+
w2 += x.measureText(s[i]).width;
|
|
721
|
+
for (let j=2; j--;)
|
|
722
|
+
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
723
|
+
{
|
|
724
|
+
x.fillStyle = color(i,2);
|
|
725
|
+
const w = x.measureText(s[i]).width;
|
|
726
|
+
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
727
|
+
X += w;
|
|
728
|
+
}
|
|
729
|
+
x.restore();
|
|
742
730
|
}
|
|
743
731
|
/**
|
|
744
732
|
* LittleJS Debug System
|
|
@@ -781,7 +769,7 @@ let debugKey = 'Escape';
|
|
|
781
769
|
let debugOverlay = false;
|
|
782
770
|
|
|
783
771
|
// Engine internal variables not exposed to documentation
|
|
784
|
-
let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugMedals = false, debugTakeScreenshot
|
|
772
|
+
let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugMedals = false, debugTakeScreenshot;
|
|
785
773
|
|
|
786
774
|
///////////////////////////////////////////////////////////////////////////////
|
|
787
775
|
// Debug helper functions
|
|
@@ -805,7 +793,7 @@ function LOG(...output) { console.log(...output); }
|
|
|
805
793
|
|
|
806
794
|
/** Draw a debug rectangle in world space
|
|
807
795
|
* @param {Vector2} pos
|
|
808
|
-
* @param {Vector2} [size=
|
|
796
|
+
* @param {Vector2} [size=vec2(0)]
|
|
809
797
|
* @param {Color|string} [color]
|
|
810
798
|
* @param {number} [time]
|
|
811
799
|
* @param {number} [angle]
|
|
@@ -968,46 +956,6 @@ function debugClear() { debugPrimitives = []; }
|
|
|
968
956
|
* @memberof Debug */
|
|
969
957
|
function debugScreenshot() { debugTakeScreenshot = 1; }
|
|
970
958
|
|
|
971
|
-
/** Save a canvas to disk
|
|
972
|
-
* @param {HTMLCanvasElement|OffscreenCanvas} canvas
|
|
973
|
-
* @param {string} [filename]
|
|
974
|
-
* @param {string} [type]
|
|
975
|
-
* @memberof Debug */
|
|
976
|
-
function debugSaveCanvas(canvas, filename='screenshot', type='image/png')
|
|
977
|
-
{
|
|
978
|
-
if (canvas instanceof OffscreenCanvas)
|
|
979
|
-
{
|
|
980
|
-
// copy to temporary canvas and save
|
|
981
|
-
if (!debugCanvas)
|
|
982
|
-
debugCanvas = document.createElement('canvas');
|
|
983
|
-
debugCanvas.width = canvas.width;
|
|
984
|
-
debugCanvas.height = canvas.height;
|
|
985
|
-
debugCanvas.getContext('2d').drawImage(canvas, 0, 0);
|
|
986
|
-
debugSaveDataURL(debugCanvas.toDataURL(type), filename);
|
|
987
|
-
}
|
|
988
|
-
else
|
|
989
|
-
debugSaveDataURL(canvas.toDataURL(type), filename);
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
/** Save a text file to disk
|
|
993
|
-
* @param {string} text
|
|
994
|
-
* @param {string} [filename]
|
|
995
|
-
* @param {string} [type]
|
|
996
|
-
* @memberof Debug */
|
|
997
|
-
function debugSaveText(text, filename='text', type='text/plain')
|
|
998
|
-
{ debugSaveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
999
|
-
|
|
1000
|
-
/** Save a data url to disk
|
|
1001
|
-
* @param {string} dataURL
|
|
1002
|
-
* @param {string} filename
|
|
1003
|
-
* @memberof Debug */
|
|
1004
|
-
function debugSaveDataURL(dataURL, filename)
|
|
1005
|
-
{
|
|
1006
|
-
downloadLink.download = filename;
|
|
1007
|
-
downloadLink.href = dataURL;
|
|
1008
|
-
downloadLink.click();
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
959
|
/** Breaks on all asserts/errors, hides the canvas, and shows message in plain text
|
|
1012
960
|
* This is a good function to call at the start of your game to catch all errors
|
|
1013
961
|
* In release builds this function has no effect
|
|
@@ -1043,21 +991,11 @@ function debugShowErrors()
|
|
|
1043
991
|
|
|
1044
992
|
function debugInit()
|
|
1045
993
|
{
|
|
1046
|
-
// create link for saving screenshots
|
|
1047
|
-
downloadLink = document.createElement('a');
|
|
1048
994
|
}
|
|
1049
995
|
|
|
1050
996
|
function debugUpdate()
|
|
1051
997
|
{
|
|
1052
|
-
if (!debug)
|
|
1053
|
-
return;
|
|
1054
|
-
|
|
1055
|
-
if (debugVideoCaptureIsActive())
|
|
1056
|
-
{
|
|
1057
|
-
// control to stop video capture
|
|
1058
|
-
if (keyWasPressed('Digit6') || keyWasPressed(debugKey))
|
|
1059
|
-
debugVideoCaptureStop();
|
|
1060
|
-
}
|
|
998
|
+
if (!debug) return;
|
|
1061
999
|
|
|
1062
1000
|
if (keyWasPressed(debugKey)) // Esc
|
|
1063
1001
|
debugOverlay = !debugOverlay;
|
|
@@ -1075,9 +1013,15 @@ function debugUpdate()
|
|
|
1075
1013
|
debugRaycast = !debugRaycast;
|
|
1076
1014
|
if (keyWasPressed('Digit5'))
|
|
1077
1015
|
debugScreenshot();
|
|
1078
|
-
if (keyWasPressed('Digit6') && !debugVideoCaptureIsActive())
|
|
1079
|
-
debugVideoCaptureStart();
|
|
1080
1016
|
}
|
|
1017
|
+
if (debugVideoCaptureIsActive())
|
|
1018
|
+
{
|
|
1019
|
+
// control to stop video capture
|
|
1020
|
+
if (!debugOverlay || keyWasPressed('Digit6'))
|
|
1021
|
+
debugVideoCaptureStop();
|
|
1022
|
+
}
|
|
1023
|
+
else if (debugOverlay && keyWasPressed('Digit6'))
|
|
1024
|
+
debugVideoCaptureStart();
|
|
1081
1025
|
}
|
|
1082
1026
|
|
|
1083
1027
|
function debugRender()
|
|
@@ -1092,7 +1036,7 @@ function debugRender()
|
|
|
1092
1036
|
{
|
|
1093
1037
|
// combine canvases, remove alpha and save
|
|
1094
1038
|
combineCanvases();
|
|
1095
|
-
|
|
1039
|
+
saveCanvas(mainCanvas);
|
|
1096
1040
|
debugTakeScreenshot = 0;
|
|
1097
1041
|
}
|
|
1098
1042
|
|
|
@@ -1173,7 +1117,7 @@ function debugRender()
|
|
|
1173
1117
|
if (tileCollisionTest(mousePos))
|
|
1174
1118
|
{
|
|
1175
1119
|
// show floored tile pick for tile collision
|
|
1176
|
-
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5));
|
|
1120
|
+
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5), 0, false);
|
|
1177
1121
|
}
|
|
1178
1122
|
}
|
|
1179
1123
|
|
|
@@ -1252,8 +1196,8 @@ function debugRender()
|
|
|
1252
1196
|
if (debugObject)
|
|
1253
1197
|
{
|
|
1254
1198
|
const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
|
|
1255
|
-
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
|
|
1256
|
-
drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5));
|
|
1199
|
+
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3), 0, false);
|
|
1200
|
+
drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5), undefined, undefined, false);
|
|
1257
1201
|
|
|
1258
1202
|
let debugText = 'mouse pos = ' + mousePos;
|
|
1259
1203
|
if (tileCollisionLayers.length)
|
|
@@ -1338,11 +1282,34 @@ function debugRender()
|
|
|
1338
1282
|
}
|
|
1339
1283
|
}
|
|
1340
1284
|
|
|
1285
|
+
function debugRenderPost()
|
|
1286
|
+
{
|
|
1287
|
+
if (debugVideoCaptureIsActive())
|
|
1288
|
+
{
|
|
1289
|
+
debugVideoCaptureUpdate();
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
if (!debugWatermark) return;
|
|
1294
|
+
|
|
1295
|
+
// update fps display
|
|
1296
|
+
mainContext.textAlign = 'right';
|
|
1297
|
+
mainContext.textBaseline = 'top';
|
|
1298
|
+
mainContext.font = '1em monospace';
|
|
1299
|
+
mainContext.fillStyle = '#000';
|
|
1300
|
+
const text = engineName + ' v' + engineVersion + ' / '
|
|
1301
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
1302
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
1303
|
+
mainContext.fillText(text, mainCanvas.width-3, 3);
|
|
1304
|
+
mainContext.fillStyle = '#fff';
|
|
1305
|
+
mainContext.fillText(text, mainCanvas.width-2, 2);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1341
1308
|
///////////////////////////////////////////////////////////////////////////////
|
|
1342
1309
|
// video capture - records video and audio at 60 fps using MediaRecorder API
|
|
1343
1310
|
|
|
1344
1311
|
// internal variables used to capture video
|
|
1345
|
-
let debugVideoCapture,
|
|
1312
|
+
let debugVideoCapture, debugVideoCaptureIcon;
|
|
1346
1313
|
|
|
1347
1314
|
/** Check if video capture is active
|
|
1348
1315
|
* @memberof Debug */
|
|
@@ -1352,80 +1319,103 @@ function debugVideoCaptureIsActive() { return !!debugVideoCapture; }
|
|
|
1352
1319
|
* @memberof Debug */
|
|
1353
1320
|
function debugVideoCaptureStart()
|
|
1354
1321
|
{
|
|
1355
|
-
|
|
1356
|
-
return; // already recording
|
|
1322
|
+
ASSERT(!debugVideoCaptureIsActive(), 'Already capturing video!');
|
|
1357
1323
|
|
|
1358
|
-
|
|
1324
|
+
if (!debugVideoCaptureIcon)
|
|
1325
|
+
{
|
|
1326
|
+
// create recording icon to show it is capturing video
|
|
1327
|
+
debugVideoCaptureIcon = document.createElement('div');
|
|
1328
|
+
debugVideoCaptureIcon.style.position = 'absolute';
|
|
1329
|
+
debugVideoCaptureIcon.style.padding = '9px';
|
|
1330
|
+
debugVideoCaptureIcon.style.color = '#f00';
|
|
1331
|
+
debugVideoCaptureIcon.style.font = '50px monospace';
|
|
1332
|
+
document.body.appendChild(debugVideoCaptureIcon);
|
|
1333
|
+
}
|
|
1334
|
+
// show recording icon
|
|
1335
|
+
debugVideoCaptureIcon.textContent = '';
|
|
1336
|
+
debugVideoCaptureIcon.style.display = '';
|
|
1337
|
+
|
|
1338
|
+
// setup captureStream to capture manually by passing 0
|
|
1359
1339
|
const stream = mainCanvas.captureStream(0);
|
|
1340
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
1341
|
+
const captureTimer = new Timer(0, true);
|
|
1360
1342
|
const chunks = [];
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1343
|
+
videoTrack.applyConstraints({frameRate:frameRate});
|
|
1344
|
+
|
|
1345
|
+
// set up the media recorder
|
|
1346
|
+
const mediaRecorder = new MediaRecorder(stream,
|
|
1347
|
+
{mimeType:'video/webm;codecs=vp8'});
|
|
1348
|
+
mediaRecorder.ondataavailable = (e)=> chunks.push(e.data);
|
|
1349
|
+
mediaRecorder.onstop = ()=>
|
|
1367
1350
|
{
|
|
1368
1351
|
const blob = new Blob(chunks, {type: 'video/webm'});
|
|
1369
1352
|
const url = URL.createObjectURL(blob);
|
|
1370
|
-
|
|
1371
|
-
downloadLink.href = url;
|
|
1372
|
-
downloadLink.click();
|
|
1373
|
-
URL.revokeObjectURL(url);
|
|
1353
|
+
saveDataURL(url, 'capture.webm', 1e3);
|
|
1374
1354
|
};
|
|
1375
1355
|
|
|
1376
|
-
|
|
1356
|
+
let audioStreamDestination, silentAudioSource;
|
|
1357
|
+
if (soundEnable)
|
|
1377
1358
|
{
|
|
1359
|
+
// create silent audio source
|
|
1360
|
+
// fixes issue where video can not start recording without audio
|
|
1361
|
+
silentAudioSource = new ConstantSourceNode(audioContext, { offset: 0 });
|
|
1362
|
+
silentAudioSource.connect(audioMasterGain);
|
|
1363
|
+
silentAudioSource.start();
|
|
1364
|
+
|
|
1378
1365
|
// connect to audio master gain node
|
|
1379
|
-
|
|
1366
|
+
audioStreamDestination = audioContext.createMediaStreamDestination();
|
|
1380
1367
|
audioMasterGain.connect(audioStreamDestination);
|
|
1381
1368
|
for (const track of audioStreamDestination.stream.getAudioTracks())
|
|
1382
1369
|
stream.addTrack(track); // add audio tracks to capture stream
|
|
1383
1370
|
}
|
|
1384
1371
|
|
|
1385
1372
|
// start recording
|
|
1373
|
+
try { mediaRecorder.start(); }
|
|
1374
|
+
catch(e)
|
|
1375
|
+
{
|
|
1376
|
+
LOG('Video capture not supported in this browser!');
|
|
1377
|
+
silentAudioSource?.stop();
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1386
1381
|
LOG('Video capture started.');
|
|
1387
|
-
debugVideoCapture.start();
|
|
1388
|
-
debugVideoCaptureTimer = new Timer(0, true);
|
|
1389
1382
|
|
|
1390
|
-
|
|
1383
|
+
// save debug video info
|
|
1384
|
+
debugVideoCapture =
|
|
1391
1385
|
{
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
document.body.appendChild(debugVideoCaptureIcon);
|
|
1399
|
-
}
|
|
1400
|
-
// show recording icon
|
|
1401
|
-
debugVideoCaptureIcon.textContent = '';
|
|
1402
|
-
debugVideoCaptureIcon.style.display = '';
|
|
1386
|
+
mediaRecorder,
|
|
1387
|
+
captureTimer,
|
|
1388
|
+
videoTrack,
|
|
1389
|
+
silentAudioSource,
|
|
1390
|
+
audioStreamDestination
|
|
1391
|
+
};
|
|
1403
1392
|
}
|
|
1404
1393
|
|
|
1405
1394
|
/** Stop capturing video and save to disk
|
|
1406
1395
|
* @memberof Debug */
|
|
1407
1396
|
function debugVideoCaptureStop()
|
|
1408
1397
|
{
|
|
1409
|
-
|
|
1410
|
-
return; // not recording
|
|
1398
|
+
ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
|
|
1411
1399
|
|
|
1412
1400
|
// stop recording
|
|
1413
|
-
LOG(`Video capture ended. ${
|
|
1414
|
-
debugVideoCapture.stop();
|
|
1415
|
-
debugVideoCapture = 0;
|
|
1401
|
+
LOG(`Video capture ended. ${debugVideoCapture.captureTimer.get().toFixed(2)} seconds recorded.`);
|
|
1416
1402
|
debugVideoCaptureIcon.style.display = 'none';
|
|
1403
|
+
debugVideoCapture.silentAudioSource?.stop();
|
|
1404
|
+
debugVideoCapture.mediaRecorder?.stop();
|
|
1405
|
+
debugVideoCapture.videoTrack?.stop();
|
|
1406
|
+
debugVideoCapture = undefined;
|
|
1417
1407
|
}
|
|
1418
1408
|
|
|
1419
1409
|
// update video capture, called automatically by engine
|
|
1420
1410
|
function debugVideoCaptureUpdate()
|
|
1421
1411
|
{
|
|
1422
|
-
|
|
1423
|
-
return; // not recording
|
|
1412
|
+
ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
|
|
1424
1413
|
|
|
1425
1414
|
// save the video frame
|
|
1426
1415
|
combineCanvases();
|
|
1427
|
-
|
|
1428
|
-
debugVideoCaptureIcon.textContent = '● REC '
|
|
1416
|
+
debugVideoCapture.videoTrack.requestFrame();
|
|
1417
|
+
debugVideoCaptureIcon.textContent = '● REC '
|
|
1418
|
+
+ formatTime(debugVideoCapture.captureTimer);
|
|
1429
1419
|
}
|
|
1430
1420
|
|
|
1431
1421
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1458,105 +1448,104 @@ function debugProtectConstant(obj)
|
|
|
1458
1448
|
return Object.freeze(obj);
|
|
1459
1449
|
}
|
|
1460
1450
|
/**
|
|
1461
|
-
* LittleJS
|
|
1451
|
+
* LittleJS Math Classes and Functions
|
|
1462
1452
|
* - General purpose math library
|
|
1463
|
-
* - Vector2 - fast, simple, easy 2D vector class
|
|
1464
|
-
* - Color - holds a rgba color with some math functions
|
|
1465
|
-
* - Timer - tracks time automatically
|
|
1466
1453
|
* - RandomGenerator - seeded random number generator
|
|
1467
|
-
*
|
|
1454
|
+
* - Vector2 - fast, simple, easy 2D vector class
|
|
1455
|
+
* - Color - holds a rgba color with math functions
|
|
1456
|
+
* @namespace Math
|
|
1468
1457
|
*/
|
|
1469
1458
|
|
|
1470
1459
|
/** The value of PI
|
|
1471
1460
|
* @type {number}
|
|
1472
1461
|
* @default Math.PI
|
|
1473
|
-
* @memberof
|
|
1462
|
+
* @memberof Math */
|
|
1474
1463
|
const PI = Math.PI;
|
|
1475
1464
|
|
|
1476
1465
|
/** Returns absolute value of value passed in
|
|
1477
1466
|
* @param {number} value
|
|
1478
1467
|
* @return {number}
|
|
1479
|
-
* @memberof
|
|
1468
|
+
* @memberof Math */
|
|
1480
1469
|
const abs = Math.abs;
|
|
1481
1470
|
|
|
1482
1471
|
/** Returns floored value of value passed in
|
|
1483
1472
|
* @param {number} value
|
|
1484
1473
|
* @return {number}
|
|
1485
|
-
* @memberof
|
|
1474
|
+
* @memberof Math */
|
|
1486
1475
|
const floor = Math.floor;
|
|
1487
1476
|
|
|
1488
1477
|
/** Returns ceiled value of value passed in
|
|
1489
1478
|
* @param {number} value
|
|
1490
1479
|
* @return {number}
|
|
1491
|
-
* @memberof
|
|
1480
|
+
* @memberof Math */
|
|
1492
1481
|
const ceil = Math.ceil;
|
|
1493
1482
|
|
|
1494
1483
|
/** Returns rounded value passed in
|
|
1495
1484
|
* @param {number} value
|
|
1496
1485
|
* @return {number}
|
|
1497
|
-
* @memberof
|
|
1486
|
+
* @memberof Math */
|
|
1498
1487
|
const round = Math.round;
|
|
1499
1488
|
|
|
1500
1489
|
/** Returns lowest value passed in
|
|
1501
1490
|
* @param {...number} values
|
|
1502
1491
|
* @return {number}
|
|
1503
|
-
* @memberof
|
|
1492
|
+
* @memberof Math */
|
|
1504
1493
|
const min = Math.min;
|
|
1505
1494
|
|
|
1506
1495
|
/** Returns highest value passed in
|
|
1507
1496
|
* @param {...number} values
|
|
1508
1497
|
* @return {number}
|
|
1509
|
-
* @memberof
|
|
1498
|
+
* @memberof Math */
|
|
1510
1499
|
const max = Math.max;
|
|
1511
1500
|
|
|
1512
1501
|
/** Returns the sign of value passed in
|
|
1513
1502
|
* @param {number} value
|
|
1514
1503
|
* @return {number}
|
|
1515
|
-
* @memberof
|
|
1504
|
+
* @memberof Math */
|
|
1516
1505
|
const sign = Math.sign;
|
|
1517
1506
|
|
|
1518
1507
|
/** Returns hypotenuse of values passed in
|
|
1519
1508
|
* @param {...number} values
|
|
1520
1509
|
* @return {number}
|
|
1521
|
-
* @memberof
|
|
1510
|
+
* @memberof Math */
|
|
1522
1511
|
const hypot = Math.hypot;
|
|
1523
1512
|
|
|
1524
1513
|
/** Returns log2 of value passed in
|
|
1525
1514
|
* @param {number} value
|
|
1526
1515
|
* @return {number}
|
|
1527
|
-
* @memberof
|
|
1516
|
+
* @memberof Math */
|
|
1528
1517
|
const log2 = Math.log2;
|
|
1529
1518
|
|
|
1530
1519
|
/** Returns sin of value passed in
|
|
1531
1520
|
* @param {number} value
|
|
1532
1521
|
* @return {number}
|
|
1533
|
-
* @memberof
|
|
1522
|
+
* @memberof Math */
|
|
1534
1523
|
const sin = Math.sin;
|
|
1535
1524
|
|
|
1536
1525
|
/** Returns cos of value passed in
|
|
1537
1526
|
* @param {number} value
|
|
1538
1527
|
* @return {number}
|
|
1539
|
-
* @memberof
|
|
1528
|
+
* @memberof Math */
|
|
1540
1529
|
const cos = Math.cos;
|
|
1541
1530
|
|
|
1542
1531
|
/** Returns tan of value passed in
|
|
1543
1532
|
* @param {number} value
|
|
1544
1533
|
* @return {number}
|
|
1545
|
-
* @memberof
|
|
1534
|
+
* @memberof Math */
|
|
1546
1535
|
const tan = Math.tan;
|
|
1547
1536
|
|
|
1548
1537
|
/** Returns atan2 of values passed in
|
|
1549
1538
|
* @param {number} y
|
|
1550
1539
|
* @param {number} x
|
|
1551
1540
|
* @return {number}
|
|
1552
|
-
* @memberof
|
|
1541
|
+
* @memberof Math */
|
|
1553
1542
|
const atan2 = Math.atan2;
|
|
1554
1543
|
|
|
1555
1544
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
1556
1545
|
* @param {number} dividend
|
|
1557
1546
|
* @param {number} [divisor]
|
|
1558
1547
|
* @return {number}
|
|
1559
|
-
* @memberof
|
|
1548
|
+
* @memberof Math */
|
|
1560
1549
|
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
1561
1550
|
|
|
1562
1551
|
/** Clamps the value between max and min
|
|
@@ -1564,7 +1553,7 @@ function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % di
|
|
|
1564
1553
|
* @param {number} [min]
|
|
1565
1554
|
* @param {number} [max]
|
|
1566
1555
|
* @return {number}
|
|
1567
|
-
* @memberof
|
|
1556
|
+
* @memberof Math */
|
|
1568
1557
|
function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
|
|
1569
1558
|
|
|
1570
1559
|
/** Returns what percentage the value is between valueA and valueB
|
|
@@ -1572,7 +1561,7 @@ function clamp(value, min=0, max=1) { return value < min ? min : value > max ? m
|
|
|
1572
1561
|
* @param {number} valueA
|
|
1573
1562
|
* @param {number} valueB
|
|
1574
1563
|
* @return {number}
|
|
1575
|
-
* @memberof
|
|
1564
|
+
* @memberof Math */
|
|
1576
1565
|
function percent(value, valueA, valueB)
|
|
1577
1566
|
{ return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
|
|
1578
1567
|
|
|
@@ -1581,7 +1570,7 @@ function percent(value, valueA, valueB)
|
|
|
1581
1570
|
* @param {number} valueB
|
|
1582
1571
|
* @param {number} percent
|
|
1583
1572
|
* @return {number}
|
|
1584
|
-
* @memberof
|
|
1573
|
+
* @memberof Math */
|
|
1585
1574
|
function lerp(valueA, valueB, percent)
|
|
1586
1575
|
{ return valueA + clamp(percent) * (valueB-valueA); }
|
|
1587
1576
|
|
|
@@ -1593,7 +1582,7 @@ function lerp(valueA, valueB, percent)
|
|
|
1593
1582
|
* @param {number} lerpA
|
|
1594
1583
|
* @param {number} lerpB
|
|
1595
1584
|
* @return {number}
|
|
1596
|
-
* @memberof
|
|
1585
|
+
* @memberof Math */
|
|
1597
1586
|
function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
1598
1587
|
{ return lerp(lerpA, lerpB, percent(value, percentA, percentB)); }
|
|
1599
1588
|
|
|
@@ -1602,7 +1591,7 @@ function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
|
1602
1591
|
* @param {number} valueB
|
|
1603
1592
|
* @param {number} [wrapSize]
|
|
1604
1593
|
* @return {number}
|
|
1605
|
-
* @memberof
|
|
1594
|
+
* @memberof Math */
|
|
1606
1595
|
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
1607
1596
|
{ const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
|
|
1608
1597
|
|
|
@@ -1612,7 +1601,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
1612
1601
|
* @param {number} percent
|
|
1613
1602
|
* @param {number} [wrapSize]
|
|
1614
1603
|
* @return {number}
|
|
1615
|
-
* @memberof
|
|
1604
|
+
* @memberof Math */
|
|
1616
1605
|
function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
1617
1606
|
{ return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
|
|
1618
1607
|
|
|
@@ -1620,7 +1609,7 @@ function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
|
1620
1609
|
* @param {number} angleA
|
|
1621
1610
|
* @param {number} angleB
|
|
1622
1611
|
* @return {number}
|
|
1623
|
-
* @memberof
|
|
1612
|
+
* @memberof Math */
|
|
1624
1613
|
function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
|
|
1625
1614
|
|
|
1626
1615
|
/** Linearly interpolates between the angles passed in with wrapping
|
|
@@ -1628,35 +1617,35 @@ function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*P
|
|
|
1628
1617
|
* @param {number} angleB
|
|
1629
1618
|
* @param {number} percent
|
|
1630
1619
|
* @return {number}
|
|
1631
|
-
* @memberof
|
|
1620
|
+
* @memberof Math */
|
|
1632
1621
|
function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
|
|
1633
1622
|
|
|
1634
1623
|
/** Applies smoothstep function to the percentage value
|
|
1635
1624
|
* @param {number} percent
|
|
1636
1625
|
* @return {number}
|
|
1637
|
-
* @memberof
|
|
1626
|
+
* @memberof Math */
|
|
1638
1627
|
function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
1639
1628
|
|
|
1640
1629
|
/** Checks if the value passed in is a power of two
|
|
1641
1630
|
* @param {number} value
|
|
1642
1631
|
* @return {boolean}
|
|
1643
|
-
* @memberof
|
|
1632
|
+
* @memberof Math */
|
|
1644
1633
|
function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
1645
1634
|
|
|
1646
1635
|
/** Returns the nearest power of two not less than the value
|
|
1647
1636
|
* @param {number} value
|
|
1648
1637
|
* @return {number}
|
|
1649
|
-
* @memberof
|
|
1638
|
+
* @memberof Math */
|
|
1650
1639
|
function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
1651
1640
|
|
|
1652
1641
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
1653
1642
|
* this can be used for simple collision detection between objects
|
|
1654
|
-
* @param {Vector2} posA
|
|
1655
|
-
* @param {Vector2} sizeA
|
|
1656
|
-
* @param {Vector2} posB
|
|
1657
|
-
* @param {Vector2} [sizeB=(
|
|
1658
|
-
* @return {boolean}
|
|
1659
|
-
* @memberof
|
|
1643
|
+
* @param {Vector2} posA - Center of box A
|
|
1644
|
+
* @param {Vector2} sizeA - Size of box A
|
|
1645
|
+
* @param {Vector2} posB - Center of box B
|
|
1646
|
+
* @param {Vector2} [sizeB=vec2()] - Size of box B, uses a point if undefined
|
|
1647
|
+
* @return {boolean} - True if overlapping
|
|
1648
|
+
* @memberof Math */
|
|
1660
1649
|
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
1661
1650
|
{
|
|
1662
1651
|
const dx = (posA.x - posB.x)*2;
|
|
@@ -1672,7 +1661,7 @@ function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
|
1672
1661
|
* @param {Vector2} pos - Center of box
|
|
1673
1662
|
* @param {Vector2} size - Size of box
|
|
1674
1663
|
* @return {boolean} - True if intersecting
|
|
1675
|
-
* @memberof
|
|
1664
|
+
* @memberof Math */
|
|
1676
1665
|
function isIntersecting(start, end, pos, size)
|
|
1677
1666
|
{
|
|
1678
1667
|
// Liang-Barsky algorithm
|
|
@@ -1713,52 +1702,29 @@ function isIntersecting(start, end, pos, size)
|
|
|
1713
1702
|
* @param {number} [t=time] - Value to use for time of the wave
|
|
1714
1703
|
* @param {number} [offset] - Value to use for time offset of the wave
|
|
1715
1704
|
* @return {number} - Value waving between 0 and amplitude
|
|
1716
|
-
* @memberof
|
|
1705
|
+
* @memberof Math */
|
|
1717
1706
|
function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
1718
1707
|
{ return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
|
|
1719
1708
|
|
|
1720
|
-
/** Formats seconds to mm:ss style for display purposes
|
|
1721
|
-
* @param {number} t - time in seconds
|
|
1722
|
-
* @return {string}
|
|
1723
|
-
* @memberof Utilities */
|
|
1724
|
-
function formatTime(t)
|
|
1725
|
-
{
|
|
1726
|
-
const sign = t < 0 ? '-' : '';
|
|
1727
|
-
t = abs(t)|0;
|
|
1728
|
-
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
|
-
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
1732
|
-
* @param {string} url - URL of JSON file
|
|
1733
|
-
* @return {Promise<object>}
|
|
1734
|
-
* @memberof Utilities */
|
|
1735
|
-
async function fetchJSON(url)
|
|
1736
|
-
{
|
|
1737
|
-
const response = await fetch(url);
|
|
1738
|
-
if (!response.ok)
|
|
1739
|
-
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
1740
|
-
return response.json();
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
1709
|
/**
|
|
1744
1710
|
* Check if object is a valid number, not NaN or undefined, but it may be infinite
|
|
1745
1711
|
* @param {any} n
|
|
1746
1712
|
* @return {boolean}
|
|
1747
|
-
* @memberof
|
|
1713
|
+
* @memberof Math */
|
|
1748
1714
|
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
1749
1715
|
|
|
1750
1716
|
/**
|
|
1751
1717
|
* Check if object is a valid string or can be converted to one
|
|
1752
1718
|
* @param {any} s
|
|
1753
1719
|
* @return {boolean}
|
|
1754
|
-
* @memberof
|
|
1755
|
-
function isString(s) { return s
|
|
1720
|
+
* @memberof Math */
|
|
1721
|
+
function isString(s) { return s != null && typeof s?.toString() === 'string'; }
|
|
1756
1722
|
|
|
1757
1723
|
/**
|
|
1758
1724
|
* Check if object is an array
|
|
1759
1725
|
* @param {any} a
|
|
1760
1726
|
* @return {boolean}
|
|
1761
|
-
* @memberof
|
|
1727
|
+
* @memberof Math */
|
|
1762
1728
|
function isArray(a) { return Array.isArray(a); }
|
|
1763
1729
|
|
|
1764
1730
|
/**
|
|
@@ -1774,7 +1740,7 @@ function isArray(a) { return Array.isArray(a); }
|
|
|
1774
1740
|
* @param {LineTestFunction} testFunction - Check if colliding
|
|
1775
1741
|
* @param {Vector2} [normal] - Optional vector to store the normal
|
|
1776
1742
|
* @return {Vector2|undefined} - Position of the collision or undefined if none found
|
|
1777
|
-
* @memberof
|
|
1743
|
+
* @memberof Math */
|
|
1778
1744
|
function lineTest(posStart, posEnd, testFunction, normal)
|
|
1779
1745
|
{
|
|
1780
1746
|
ASSERT(isVector2(posStart), 'posStart must be a vec2');
|
|
@@ -1786,8 +1752,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
|
|
|
1786
1752
|
const dx = posEnd.x - posStart.x;
|
|
1787
1753
|
const dy = posEnd.y - posStart.y;
|
|
1788
1754
|
const totalLength = hypot(dx, dy);
|
|
1789
|
-
if (!totalLength)
|
|
1790
|
-
return;
|
|
1755
|
+
if (!totalLength) return;
|
|
1791
1756
|
|
|
1792
1757
|
// current integer cell we are in
|
|
1793
1758
|
const pos = posStart.floor();
|
|
@@ -1897,8 +1862,8 @@ function randInCircle(radius=1, minRadius=0)
|
|
|
1897
1862
|
{ return radius > 0 ? randVec2(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
|
|
1898
1863
|
|
|
1899
1864
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1900
|
-
* @param {Color} [colorA=
|
|
1901
|
-
* @param {Color} [colorB=
|
|
1865
|
+
* @param {Color} [colorA=WHITE]
|
|
1866
|
+
* @param {Color} [colorB=BLACK]
|
|
1902
1867
|
* @param {boolean} [linear]
|
|
1903
1868
|
* @return {Color}
|
|
1904
1869
|
* @memberof Random */
|
|
@@ -1977,8 +1942,8 @@ class RandomGenerator
|
|
|
1977
1942
|
{ return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
|
|
1978
1943
|
|
|
1979
1944
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1980
|
-
* @param {Color} [colorA=
|
|
1981
|
-
* @param {Color} [colorB=
|
|
1945
|
+
* @param {Color} [colorA=WHITE]
|
|
1946
|
+
* @param {Color} [colorB=BLACK]
|
|
1982
1947
|
* @param {boolean} [linear]
|
|
1983
1948
|
* @return {Color} */
|
|
1984
1949
|
randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
@@ -2021,14 +1986,14 @@ class RandomGenerator
|
|
|
2021
1986
|
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
2022
1987
|
* a = vec2(5); // set a to (5, 5)
|
|
2023
1988
|
* b = vec2(); // set b to (0, 0)
|
|
2024
|
-
* @memberof
|
|
2025
|
-
function vec2(x=0, y) { return new Vector2(x, y
|
|
1989
|
+
* @memberof Math */
|
|
1990
|
+
function vec2(x=0, y) { return new Vector2(x, y ?? x); }
|
|
2026
1991
|
|
|
2027
1992
|
/**
|
|
2028
1993
|
* Check if object is a valid Vector2
|
|
2029
1994
|
* @param {any} v
|
|
2030
1995
|
* @return {boolean}
|
|
2031
|
-
* @memberof
|
|
1996
|
+
* @memberof Math */
|
|
2032
1997
|
function isVector2(v) { return v instanceof Vector2 && v.isValid(); }
|
|
2033
1998
|
|
|
2034
1999
|
// vector2 asserts
|
|
@@ -2230,10 +2195,6 @@ class Vector2
|
|
|
2230
2195
|
* @return {number} */
|
|
2231
2196
|
area() { return abs(this.x * this.y); }
|
|
2232
2197
|
|
|
2233
|
-
/** Returns true if this vector is (0,0)
|
|
2234
|
-
* @return {boolean} */
|
|
2235
|
-
isZero() { return !this.x && !this.y; }
|
|
2236
|
-
|
|
2237
2198
|
/** Returns a new vector that is p percent between this and the vector passed in
|
|
2238
2199
|
* @param {Vector2} v - other vector
|
|
2239
2200
|
* @param {number} percent
|
|
@@ -2278,7 +2239,7 @@ class Vector2
|
|
|
2278
2239
|
* @param {number} [b=1] - blue
|
|
2279
2240
|
* @param {number} [a=1] - alpha
|
|
2280
2241
|
* @return {Color}
|
|
2281
|
-
* @memberof
|
|
2242
|
+
* @memberof Math
|
|
2282
2243
|
*/
|
|
2283
2244
|
function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
2284
2245
|
|
|
@@ -2289,14 +2250,14 @@ function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
|
2289
2250
|
* @param {number} [l=1] - lightness
|
|
2290
2251
|
* @param {number} [a=1] - alpha
|
|
2291
2252
|
* @return {Color}
|
|
2292
|
-
* @memberof
|
|
2253
|
+
* @memberof Math */
|
|
2293
2254
|
function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
2294
2255
|
|
|
2295
2256
|
/**
|
|
2296
2257
|
* Check if object is a valid Color
|
|
2297
2258
|
* @param {any} c
|
|
2298
2259
|
* @return {boolean}
|
|
2299
|
-
* @memberof
|
|
2260
|
+
* @memberof Math */
|
|
2300
2261
|
function isColor(c) { return c instanceof Color && c.isValid(); }
|
|
2301
2262
|
|
|
2302
2263
|
// color asserts
|
|
@@ -2477,7 +2438,7 @@ class Color
|
|
|
2477
2438
|
toString(useAlpha = true)
|
|
2478
2439
|
{
|
|
2479
2440
|
if (debug && !this.isValid())
|
|
2480
|
-
return
|
|
2441
|
+
return '#000';
|
|
2481
2442
|
const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
|
|
2482
2443
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
2483
2444
|
}
|
|
@@ -2534,68 +2495,158 @@ class Color
|
|
|
2534
2495
|
|
|
2535
2496
|
/** Color - White #ffffff
|
|
2536
2497
|
* @type {Color}
|
|
2537
|
-
* @memberof
|
|
2498
|
+
* @memberof Math */
|
|
2538
2499
|
const WHITE = debugProtectConstant(rgb());
|
|
2539
2500
|
|
|
2540
2501
|
/** Color - Clear White #757474ff with 0 alpha
|
|
2541
2502
|
* @type {Color}
|
|
2542
|
-
* @memberof
|
|
2503
|
+
* @memberof Math */
|
|
2543
2504
|
const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
2544
2505
|
|
|
2545
2506
|
/** Color - Black #000000
|
|
2546
2507
|
* @type {Color}
|
|
2547
|
-
* @memberof
|
|
2508
|
+
* @memberof Math */
|
|
2548
2509
|
const BLACK = debugProtectConstant(rgb(0,0,0));
|
|
2549
2510
|
|
|
2550
2511
|
/** Color - Clear Black #000000 with 0 alpha
|
|
2551
2512
|
* @type {Color}
|
|
2552
|
-
* @memberof
|
|
2513
|
+
* @memberof Math */
|
|
2553
2514
|
const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
|
|
2554
2515
|
|
|
2555
2516
|
/** Color - Gray #808080
|
|
2556
2517
|
* @type {Color}
|
|
2557
|
-
* @memberof
|
|
2518
|
+
* @memberof Math */
|
|
2558
2519
|
const GRAY = debugProtectConstant(rgb(.5,.5,.5));
|
|
2559
2520
|
|
|
2560
2521
|
/** Color - Red #ff0000
|
|
2561
2522
|
* @type {Color}
|
|
2562
|
-
* @memberof
|
|
2523
|
+
* @memberof Math */
|
|
2563
2524
|
const RED = debugProtectConstant(rgb(1,0,0));
|
|
2564
2525
|
|
|
2565
2526
|
/** Color - Orange #ff8000
|
|
2566
2527
|
* @type {Color}
|
|
2567
|
-
* @memberof
|
|
2528
|
+
* @memberof Math */
|
|
2568
2529
|
const ORANGE = debugProtectConstant(rgb(1,.5,0));
|
|
2569
2530
|
|
|
2570
2531
|
/** Color - Yellow #ffff00
|
|
2571
2532
|
* @type {Color}
|
|
2572
|
-
* @memberof
|
|
2533
|
+
* @memberof Math */
|
|
2573
2534
|
const YELLOW = debugProtectConstant(rgb(1,1,0));
|
|
2574
2535
|
|
|
2575
2536
|
/** Color - Green #00ff00
|
|
2576
2537
|
* @type {Color}
|
|
2577
|
-
* @memberof
|
|
2538
|
+
* @memberof Math */
|
|
2578
2539
|
const GREEN = debugProtectConstant(rgb(0,1,0));
|
|
2579
2540
|
|
|
2580
2541
|
/** Color - Cyan #00ffff
|
|
2581
2542
|
* @type {Color}
|
|
2582
|
-
* @memberof
|
|
2543
|
+
* @memberof Math */
|
|
2583
2544
|
const CYAN = debugProtectConstant(rgb(0,1,1));
|
|
2584
2545
|
|
|
2585
2546
|
/** Color - Blue #0000ff
|
|
2586
2547
|
* @type {Color}
|
|
2587
|
-
* @memberof
|
|
2548
|
+
* @memberof Math */
|
|
2588
2549
|
const BLUE = debugProtectConstant(rgb(0,0,1));
|
|
2589
2550
|
|
|
2590
2551
|
/** Color - Purple #8000ff
|
|
2591
2552
|
* @type {Color}
|
|
2592
|
-
* @memberof
|
|
2553
|
+
* @memberof Math */
|
|
2593
2554
|
const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
2594
2555
|
|
|
2595
2556
|
/** Color - Magenta #ff00ff
|
|
2596
2557
|
* @type {Color}
|
|
2558
|
+
* @memberof Math */
|
|
2559
|
+
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
2560
|
+
/**
|
|
2561
|
+
* LittleJS Utility Classes and Functions
|
|
2562
|
+
* - General purpose utilities
|
|
2563
|
+
* - Timer - tracks time automatically
|
|
2564
|
+
* @namespace Utilities
|
|
2565
|
+
*/
|
|
2566
|
+
|
|
2567
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
2568
|
+
* @param {number} t - time in seconds
|
|
2569
|
+
* @return {string}
|
|
2570
|
+
* @memberof Utilities */
|
|
2571
|
+
function formatTime(t)
|
|
2572
|
+
{
|
|
2573
|
+
const sign = t < 0 ? '-' : '';
|
|
2574
|
+
t = abs(t)|0;
|
|
2575
|
+
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
2579
|
+
* @param {string} url - URL of JSON file
|
|
2580
|
+
* @return {Promise<object>}
|
|
2581
|
+
* @memberof Utilities */
|
|
2582
|
+
async function fetchJSON(url)
|
|
2583
|
+
{
|
|
2584
|
+
const response = await fetch(url);
|
|
2585
|
+
if (!response.ok)
|
|
2586
|
+
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
2587
|
+
return response.json();
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2591
|
+
|
|
2592
|
+
/** Save a text file to disk
|
|
2593
|
+
* @param {string} text
|
|
2594
|
+
* @param {string} [filename]
|
|
2595
|
+
* @param {string} [type]
|
|
2597
2596
|
* @memberof Utilities */
|
|
2598
|
-
|
|
2597
|
+
function saveText(text, filename='text', type='text/plain')
|
|
2598
|
+
{ saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
2599
|
+
|
|
2600
|
+
/** Save a canvas to disk
|
|
2601
|
+
* @param {HTMLCanvasElement|OffscreenCanvas} canvas
|
|
2602
|
+
* @param {string} [filename]
|
|
2603
|
+
* @param {string} [type]
|
|
2604
|
+
* @memberof Utilities */
|
|
2605
|
+
function saveCanvas(canvas, filename='screenshot', type='image/png')
|
|
2606
|
+
{
|
|
2607
|
+
if (canvas instanceof OffscreenCanvas)
|
|
2608
|
+
{
|
|
2609
|
+
// copy to temporary canvas and save
|
|
2610
|
+
const saveCanvas = document.createElement('canvas');
|
|
2611
|
+
saveCanvas.width = canvas.width;
|
|
2612
|
+
saveCanvas.height = canvas.height;
|
|
2613
|
+
saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
|
|
2614
|
+
saveDataURL(saveCanvas.toDataURL(type), filename);
|
|
2615
|
+
}
|
|
2616
|
+
else
|
|
2617
|
+
saveDataURL(canvas.toDataURL(type), filename);
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
/** Save a data url to disk
|
|
2621
|
+
* @param {string} url
|
|
2622
|
+
* @param {string} [filename]
|
|
2623
|
+
* @param {number} [revokeTime] - how long before revoking the url
|
|
2624
|
+
* @memberof Utilities */
|
|
2625
|
+
function saveDataURL(url, filename='download', revokeTime)
|
|
2626
|
+
{
|
|
2627
|
+
ASSERT(isString(url), 'saveDataURL requires url string');
|
|
2628
|
+
ASSERT(isString(filename), 'saveDataURL requires filename string');
|
|
2629
|
+
|
|
2630
|
+
// create link for saving screenshots
|
|
2631
|
+
const link = document.createElement('a');
|
|
2632
|
+
link.download = filename;
|
|
2633
|
+
link.href = url;
|
|
2634
|
+
link.click();
|
|
2635
|
+
if (revokeTime !== undefined)
|
|
2636
|
+
setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
/** Share content using the native share dialog if available
|
|
2640
|
+
* @param {string} title - title of the share
|
|
2641
|
+
* @param {string} url - url to share
|
|
2642
|
+
* @param {Function} [callback] - Called when share is complete
|
|
2643
|
+
* @memberof Utilities */
|
|
2644
|
+
function shareURL(title, url, callback)
|
|
2645
|
+
{
|
|
2646
|
+
ASSERT(isString(title), 'shareURL requires title string');
|
|
2647
|
+
ASSERT(isString(url), 'shareURL requires url string');
|
|
2648
|
+
navigator.share?.({title, url}).then(()=>callback?.());
|
|
2649
|
+
}
|
|
2599
2650
|
|
|
2600
2651
|
///////////////////////////////////////////////////////////////////////////////
|
|
2601
2652
|
|
|
@@ -2717,7 +2768,7 @@ let cameraScale = 32;
|
|
|
2717
2768
|
* @memberof Settings */
|
|
2718
2769
|
let canvasColorTiles = true;
|
|
2719
2770
|
|
|
2720
|
-
/** Color to clear the canvas to before render
|
|
2771
|
+
/** Color to clear the canvas to before render, does not clear if alpha is 0
|
|
2721
2772
|
* @type {Color}
|
|
2722
2773
|
* @memberof Draw */
|
|
2723
2774
|
let canvasClearColor = CLEAR_BLACK;
|
|
@@ -3026,7 +3077,7 @@ function setCameraScale(scale) { cameraScale = scale; }
|
|
|
3026
3077
|
* @memberof Settings */
|
|
3027
3078
|
function setCanvasColorTiles(colorTiles) { canvasColorTiles = colorTiles; }
|
|
3028
3079
|
|
|
3029
|
-
/** Set color to clear the canvas to before render
|
|
3080
|
+
/** Set color to clear the canvas to before render, does not clear if alpha is 0
|
|
3030
3081
|
* @param {Color} color
|
|
3031
3082
|
* @memberof Settings */
|
|
3032
3083
|
function setCanvasClearColor(color) { canvasClearColor = color.copy(); }
|
|
@@ -3306,10 +3357,10 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
3306
3357
|
class EngineObject
|
|
3307
3358
|
{
|
|
3308
3359
|
/** Create an engine object and adds it to the list of objects
|
|
3309
|
-
* @param {Vector2} [pos=(
|
|
3310
|
-
* @param {Vector2} [size=(1
|
|
3311
|
-
* @param {TileInfo} [tileInfo]
|
|
3312
|
-
* @param {number} [angle]
|
|
3360
|
+
* @param {Vector2} [pos=vec2()] - World space position of the object
|
|
3361
|
+
* @param {Vector2} [size=vec2(1)] - World space size of the object
|
|
3362
|
+
* @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
|
|
3363
|
+
* @param {number} [angle] - Angle the object is rotated by
|
|
3313
3364
|
* @param {Color} [color=WHITE] - Color to apply to tile when rendered
|
|
3314
3365
|
* @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
|
|
3315
3366
|
*/
|
|
@@ -3437,15 +3488,16 @@ class EngineObject
|
|
|
3437
3488
|
// physics sanity checks
|
|
3438
3489
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
3439
3490
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
3440
|
-
|
|
3441
|
-
|
|
3491
|
+
|
|
3492
|
+
// don't do collision for static objects or if solver disabled
|
|
3493
|
+
if (!enablePhysicsSolver || !this.mass) return;
|
|
3442
3494
|
|
|
3443
3495
|
const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
|
|
3444
3496
|
if (this.groundObject)
|
|
3445
3497
|
{
|
|
3446
3498
|
// apply friction in local space of ground object
|
|
3447
3499
|
const friction = max(this.friction, this.groundObject.friction);
|
|
3448
|
-
const groundSpeed = this.groundObject.velocity
|
|
3500
|
+
const groundSpeed = this.groundObject.velocity.x;
|
|
3449
3501
|
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
|
|
3450
3502
|
this.groundObject = undefined;
|
|
3451
3503
|
}
|
|
@@ -3456,19 +3508,19 @@ class EngineObject
|
|
|
3456
3508
|
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
3457
3509
|
for (const o of engineObjectsCollide)
|
|
3458
3510
|
{
|
|
3511
|
+
// skip destroyed, child objects, or self collision
|
|
3512
|
+
if (o.destroyed || o.parent || o === this) continue;
|
|
3513
|
+
|
|
3459
3514
|
// non solid objects don't collide with each other
|
|
3460
|
-
if (
|
|
3461
|
-
continue;
|
|
3515
|
+
if (!this.isSolid && !o.isSolid) continue;
|
|
3462
3516
|
|
|
3463
3517
|
// check collision
|
|
3464
|
-
if (!this.isOverlappingObject(o))
|
|
3465
|
-
continue;
|
|
3518
|
+
if (!this.isOverlappingObject(o)) continue;
|
|
3466
3519
|
|
|
3467
3520
|
// notify objects of collision and check if should be resolved
|
|
3468
3521
|
const collide1 = this.collideWithObject(o);
|
|
3469
3522
|
const collide2 = o.collideWithObject(this);
|
|
3470
|
-
if (!collide1 || !collide2)
|
|
3471
|
-
continue;
|
|
3523
|
+
if (!collide1 || !collide2) continue;
|
|
3472
3524
|
|
|
3473
3525
|
if (isOverlapping(oldPos, this.size, o.pos, o.size))
|
|
3474
3526
|
{
|
|
@@ -3570,7 +3622,7 @@ class EngineObject
|
|
|
3570
3622
|
const delta = y - this.pos.y;
|
|
3571
3623
|
if (delta < maxMoveUp)
|
|
3572
3624
|
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
3573
|
-
{
|
|
3625
|
+
{
|
|
3574
3626
|
this.pos.y = y;
|
|
3575
3627
|
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
3576
3628
|
return;
|
|
@@ -3630,10 +3682,10 @@ class EngineObject
|
|
|
3630
3682
|
|
|
3631
3683
|
// disconnect from parent and destroy children
|
|
3632
3684
|
this.destroyed = 1;
|
|
3633
|
-
this.parent
|
|
3685
|
+
this.parent?.removeChild(this);
|
|
3634
3686
|
for (const child of this.children)
|
|
3635
3687
|
{
|
|
3636
|
-
child.parent =
|
|
3688
|
+
child.parent = undefined;
|
|
3637
3689
|
child.destroy();
|
|
3638
3690
|
}
|
|
3639
3691
|
}
|
|
@@ -3654,15 +3706,15 @@ class EngineObject
|
|
|
3654
3706
|
* @param {Vector2} vec - world space vector */
|
|
3655
3707
|
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
3656
3708
|
|
|
3657
|
-
/** Called to check if a tile collision should be resolved
|
|
3709
|
+
/** 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.
|
|
3658
3710
|
* @param {number} tileData - the value of the tile at the position
|
|
3659
|
-
* @param {Vector2} pos
|
|
3660
|
-
* @return {boolean}
|
|
3711
|
+
* @param {Vector2} pos - tile where the collision occurred
|
|
3712
|
+
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity */
|
|
3661
3713
|
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
3662
3714
|
|
|
3663
|
-
/** Called to check if
|
|
3715
|
+
/** 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.
|
|
3664
3716
|
* @param {EngineObject} object - the object to test against
|
|
3665
|
-
* @return {boolean}
|
|
3717
|
+
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity
|
|
3666
3718
|
*/
|
|
3667
3719
|
collideWithObject(object) { return true; }
|
|
3668
3720
|
|
|
@@ -3703,9 +3755,9 @@ class EngineObject
|
|
|
3703
3755
|
* @return {number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
3704
3756
|
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
3705
3757
|
|
|
3706
|
-
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3758
|
+
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3707
3759
|
* @param {EngineObject} child
|
|
3708
|
-
* @param {Vector2} [localPos=(
|
|
3760
|
+
* @param {Vector2} [localPos=vec2()]
|
|
3709
3761
|
* @param {number} [localAngle]
|
|
3710
3762
|
* @return {EngineObject} The child object added */
|
|
3711
3763
|
addChild(child, localPos=vec2(), localAngle=0)
|
|
@@ -3729,7 +3781,7 @@ class EngineObject
|
|
|
3729
3781
|
const index = this.children.indexOf(child);
|
|
3730
3782
|
ASSERT(index >= 0, 'child not found in children array');
|
|
3731
3783
|
index >= 0 && this.children.splice(index, 1);
|
|
3732
|
-
child.parent =
|
|
3784
|
+
child.parent = undefined;
|
|
3733
3785
|
}
|
|
3734
3786
|
|
|
3735
3787
|
/** Check if overlapping another engine object
|
|
@@ -3741,7 +3793,7 @@ class EngineObject
|
|
|
3741
3793
|
|
|
3742
3794
|
/** Check if overlapping a point or aligned bounding box
|
|
3743
3795
|
* @param {Vector2} pos - Center of box
|
|
3744
|
-
* @param {Vector2} [size=(
|
|
3796
|
+
* @param {Vector2} [size=vec2()] - Size of box, uses a point if undefined
|
|
3745
3797
|
* @return {boolean} */
|
|
3746
3798
|
isOverlapping(pos, size=vec2())
|
|
3747
3799
|
{ return isOverlapping(this.pos, this.size, pos, size); }
|
|
@@ -3784,8 +3836,7 @@ class EngineObject
|
|
|
3784
3836
|
/** Render debug info for this object */
|
|
3785
3837
|
renderDebugInfo()
|
|
3786
3838
|
{
|
|
3787
|
-
if (!debug)
|
|
3788
|
-
return;
|
|
3839
|
+
if (!debug) return;
|
|
3789
3840
|
|
|
3790
3841
|
// show object info for debugging
|
|
3791
3842
|
const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
|
|
@@ -3827,11 +3878,6 @@ let mainCanvas;
|
|
|
3827
3878
|
* @memberof Draw */
|
|
3828
3879
|
let mainContext;
|
|
3829
3880
|
|
|
3830
|
-
/** The default canvas to use for drawing, usually mainCanvas
|
|
3831
|
-
* @type {HTMLCanvasElement|OffscreenCanvas}
|
|
3832
|
-
* @memberof Draw */
|
|
3833
|
-
let drawCanvas;
|
|
3834
|
-
|
|
3835
3881
|
/** The default 2d context to use for drawing, usually mainContext
|
|
3836
3882
|
* @type {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}
|
|
3837
3883
|
* @memberof Draw */
|
|
@@ -3878,10 +3924,11 @@ let drawCount;
|
|
|
3878
3924
|
* Create a tile info object using a grid based system
|
|
3879
3925
|
* - This can take vecs or floats for easier use and conversion
|
|
3880
3926
|
* - If an index is passed in, the tile size and index will determine the position
|
|
3881
|
-
* @param {Vector2|number} [
|
|
3927
|
+
* @param {Vector2|number} [index=0] - Index of the tile in 1d or 2d form
|
|
3882
3928
|
* @param {Vector2|number} [size] - Size of tile in pixels
|
|
3883
|
-
* @param {number} [
|
|
3929
|
+
* @param {TextureInfo|number} [texture] - Texture index or info to use
|
|
3884
3930
|
* @param {number} [padding] - How many pixels padding around tiles
|
|
3931
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3885
3932
|
* @return {TileInfo}
|
|
3886
3933
|
* @example
|
|
3887
3934
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -3889,36 +3936,43 @@ let drawCount;
|
|
|
3889
3936
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
3890
3937
|
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
3891
3938
|
* @memberof Draw */
|
|
3892
|
-
function tile(
|
|
3939
|
+
function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3893
3940
|
{
|
|
3894
|
-
|
|
3895
|
-
|
|
3941
|
+
ASSERT(isVector2(index) || typeof index === 'number', 'index must be a vec2 or number');
|
|
3942
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
3943
|
+
ASSERT(isNumber(texture) || texture instanceof TextureInfo, 'texture must be a number or TextureInfo');
|
|
3944
|
+
ASSERT(isNumber(padding), 'padding must be a number');
|
|
3945
|
+
|
|
3946
|
+
if (headlessMode) return new TileInfo;
|
|
3896
3947
|
|
|
3897
|
-
// if size is a number, make it a vector
|
|
3898
3948
|
if (typeof size === 'number')
|
|
3899
3949
|
{
|
|
3950
|
+
// if size is a number, make it a vector
|
|
3900
3951
|
ASSERT(size > 0);
|
|
3901
3952
|
size = new Vector2(size, size);
|
|
3902
3953
|
}
|
|
3903
3954
|
|
|
3904
3955
|
// create tile info object
|
|
3905
|
-
const
|
|
3956
|
+
const textureInfo = typeof texture === 'number' ?
|
|
3957
|
+
textureInfos[texture] : texture;
|
|
3906
3958
|
|
|
3907
3959
|
// get the position of the tile
|
|
3908
|
-
const textureInfo = textureInfos[textureIndex];
|
|
3909
|
-
ASSERT(!!textureInfo, 'Texture not loaded');
|
|
3910
3960
|
const sizePaddedX = size.x + padding*2;
|
|
3911
3961
|
const sizePaddedY = size.y + padding*2;
|
|
3912
|
-
|
|
3962
|
+
let x, y;
|
|
3963
|
+
if (typeof index === 'number')
|
|
3913
3964
|
{
|
|
3914
3965
|
const cols = textureInfo.size.x / sizePaddedX |0;
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
|
|
3966
|
+
x = index % cols;
|
|
3967
|
+
y = index / cols |0;
|
|
3918
3968
|
}
|
|
3919
3969
|
else
|
|
3920
|
-
|
|
3921
|
-
|
|
3970
|
+
{
|
|
3971
|
+
x = index.x;
|
|
3972
|
+
y = index.y;
|
|
3973
|
+
}
|
|
3974
|
+
const pos = new Vector2(x*sizePaddedX + padding, y*sizePaddedY + padding);
|
|
3975
|
+
return new TileInfo(pos, size, textureInfo, padding, bleed);
|
|
3922
3976
|
}
|
|
3923
3977
|
|
|
3924
3978
|
/**
|
|
@@ -3928,24 +3982,22 @@ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=til
|
|
|
3928
3982
|
class TileInfo
|
|
3929
3983
|
{
|
|
3930
3984
|
/** Create a tile info object
|
|
3931
|
-
* @param {Vector2} [pos=(
|
|
3985
|
+
* @param {Vector2} [pos=vec2()] - Top left corner of tile in pixels
|
|
3932
3986
|
* @param {Vector2} [size] - Size of tile in pixels
|
|
3933
|
-
* @param {
|
|
3934
|
-
* @param {number}
|
|
3935
|
-
* @param {number}
|
|
3987
|
+
* @param {TextureInfo} [textureInfo] - Texture info to use
|
|
3988
|
+
* @param {number} [padding] - How many pixels padding around tiles
|
|
3989
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3936
3990
|
*/
|
|
3937
|
-
constructor(pos=vec2(), size=tileDefaultSize,
|
|
3991
|
+
constructor(pos=vec2(), size=tileDefaultSize, textureInfo=textureInfos[0], padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3938
3992
|
{
|
|
3939
3993
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
3940
3994
|
this.pos = pos.copy();
|
|
3941
3995
|
/** @property {Vector2} - Size of tile in pixels */
|
|
3942
3996
|
this.size = size.copy();
|
|
3943
|
-
/** @property {number} - Texture index to use */
|
|
3944
|
-
this.textureIndex = textureIndex;
|
|
3945
3997
|
/** @property {number} - How many pixels padding around tiles */
|
|
3946
3998
|
this.padding = padding;
|
|
3947
3999
|
/** @property {TextureInfo} - The texture info for this tile */
|
|
3948
|
-
this.textureInfo =
|
|
4000
|
+
this.textureInfo = textureInfo;
|
|
3949
4001
|
/** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
|
|
3950
4002
|
this.bleed = bleed;
|
|
3951
4003
|
}
|
|
@@ -3955,7 +4007,7 @@ class TileInfo
|
|
|
3955
4007
|
* @return {TileInfo}
|
|
3956
4008
|
*/
|
|
3957
4009
|
offset(offset)
|
|
3958
|
-
{ return new TileInfo(this.pos.add(offset), this.size, this.
|
|
4010
|
+
{ return new TileInfo(this.pos.add(offset), this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3959
4011
|
|
|
3960
4012
|
/** Returns a copy of this tile offset by a number of animation frames
|
|
3961
4013
|
* @param {number} frame - Offset to apply in animation frames
|
|
@@ -3964,23 +4016,33 @@ class TileInfo
|
|
|
3964
4016
|
frame(frame)
|
|
3965
4017
|
{
|
|
3966
4018
|
ASSERT(typeof frame === 'number');
|
|
3967
|
-
|
|
4019
|
+
const w = this.size.x + this.padding*2;
|
|
4020
|
+
const x = frame*w;
|
|
4021
|
+
ASSERT(x < this.textureInfo.size.x, 'frame extends beyond texture width!');
|
|
4022
|
+
return this.offset(new Vector2(x));
|
|
3968
4023
|
}
|
|
3969
4024
|
|
|
3970
4025
|
/**
|
|
3971
4026
|
* Set this tile to use a full image in a texture info
|
|
3972
|
-
* @param {TextureInfo} textureInfo
|
|
4027
|
+
* @param {TextureInfo} [textureInfo]
|
|
3973
4028
|
* @return {TileInfo}
|
|
3974
4029
|
*/
|
|
3975
|
-
setFullImage(textureInfo)
|
|
4030
|
+
setFullImage(textureInfo=this.textureInfo)
|
|
3976
4031
|
{
|
|
4032
|
+
this.textureInfo = textureInfo;
|
|
3977
4033
|
this.pos = new Vector2;
|
|
3978
4034
|
this.size = textureInfo.size.copy();
|
|
3979
|
-
this.textureInfo = textureInfo;
|
|
3980
|
-
// do not use padding or bleed
|
|
3981
4035
|
this.bleed = this.padding = 0;
|
|
3982
4036
|
return this;
|
|
3983
4037
|
}
|
|
4038
|
+
|
|
4039
|
+
/**
|
|
4040
|
+
* Returns a tile info for an index using this tile as refrence
|
|
4041
|
+
* @param {Vector2|number} [index=0]
|
|
4042
|
+
* @return {TileInfo}
|
|
4043
|
+
*/
|
|
4044
|
+
tile(index)
|
|
4045
|
+
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3984
4046
|
}
|
|
3985
4047
|
|
|
3986
4048
|
/**
|
|
@@ -4021,19 +4083,19 @@ class TextureInfo
|
|
|
4021
4083
|
///////////////////////////////////////////////////////////////////////////////
|
|
4022
4084
|
// Drawing functions
|
|
4023
4085
|
|
|
4024
|
-
/** Draw textured tile centered in world space
|
|
4025
|
-
* @param {Vector2} pos
|
|
4026
|
-
* @param {Vector2} [size=(1
|
|
4027
|
-
* @param {TileInfo} [tileInfo]
|
|
4028
|
-
* @param {Color} [color=
|
|
4029
|
-
* @param {number} [angle]
|
|
4030
|
-
* @param {boolean} [mirror]
|
|
4031
|
-
* @param {Color} [additiveColor]
|
|
4086
|
+
/** Draw textured tile centered in world space
|
|
4087
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
4088
|
+
* @param {Vector2} [size=vec2(1)] - Size of the tile in world space
|
|
4089
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
4090
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
4091
|
+
* @param {number} [angle] - Angle to rotate by
|
|
4092
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
4093
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
4032
4094
|
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
|
|
4033
4095
|
* @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
|
|
4034
4096
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
4035
4097
|
* @memberof Draw */
|
|
4036
|
-
function drawTile(pos, size=
|
|
4098
|
+
function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
4037
4099
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
4038
4100
|
{
|
|
4039
4101
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -4108,8 +4170,8 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
4108
4170
|
|
|
4109
4171
|
/** Draw colored rect centered on pos
|
|
4110
4172
|
* @param {Vector2} pos
|
|
4111
|
-
* @param {Vector2} [size=(1
|
|
4112
|
-
* @param {Color} [color=
|
|
4173
|
+
* @param {Vector2} [size=vec2(1)]
|
|
4174
|
+
* @param {Color} [color=WHITE]
|
|
4113
4175
|
* @param {number} [angle]
|
|
4114
4176
|
* @param {boolean} [useWebGL=glEnable]
|
|
4115
4177
|
* @param {boolean} [screenSpace]
|
|
@@ -4122,9 +4184,9 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
4122
4184
|
|
|
4123
4185
|
/** Draw a rect centered on pos with a gradient from top to bottom
|
|
4124
4186
|
* @param {Vector2} pos
|
|
4125
|
-
* @param {Vector2} [size=(1
|
|
4126
|
-
* @param {Color} [colorTop=
|
|
4127
|
-
* @param {Color} [colorBottom=
|
|
4187
|
+
* @param {Vector2} [size=vec2(1)]
|
|
4188
|
+
* @param {Color} [colorTop=WHITE]
|
|
4189
|
+
* @param {Color} [colorBottom=BLACK]
|
|
4128
4190
|
* @param {number} [angle]
|
|
4129
4191
|
* @param {boolean} [useWebGL=glEnable]
|
|
4130
4192
|
* @param {boolean} [screenSpace]
|
|
@@ -4186,9 +4248,9 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
4186
4248
|
/** Draw connected lines between a series of points
|
|
4187
4249
|
* @param {Array<Vector2>} points
|
|
4188
4250
|
* @param {number} [width]
|
|
4189
|
-
* @param {Color} [color=
|
|
4251
|
+
* @param {Color} [color=WHITE]
|
|
4190
4252
|
* @param {boolean} [wrap] - Should the last point connect to the first?
|
|
4191
|
-
* @param {Vector2} [pos=(
|
|
4253
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
4192
4254
|
* @param {number} [angle] - Angle to rotate by
|
|
4193
4255
|
* @param {boolean} [useWebGL=glEnable]
|
|
4194
4256
|
* @param {boolean} [screenSpace]
|
|
@@ -4223,13 +4285,9 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
4223
4285
|
for (let i=0; i<points.length; ++i)
|
|
4224
4286
|
{
|
|
4225
4287
|
const point = points[i];
|
|
4226
|
-
|
|
4227
|
-
context.lineTo(point.x, point.y);
|
|
4228
|
-
else
|
|
4229
|
-
context.moveTo(point.x, point.y);
|
|
4288
|
+
context.lineTo(point.x, point.y);
|
|
4230
4289
|
}
|
|
4231
|
-
|
|
4232
|
-
context.closePath();
|
|
4290
|
+
wrap && context.closePath();
|
|
4233
4291
|
context.stroke();
|
|
4234
4292
|
}, screenSpace, context);
|
|
4235
4293
|
}
|
|
@@ -4239,8 +4297,8 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
4239
4297
|
* @param {Vector2} posA
|
|
4240
4298
|
* @param {Vector2} posB
|
|
4241
4299
|
* @param {number} [width]
|
|
4242
|
-
* @param {Color} [color=
|
|
4243
|
-
* @param {Vector2} [pos=(
|
|
4300
|
+
* @param {Color} [color=WHITE]
|
|
4301
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
4244
4302
|
* @param {number} [angle] - Angle to rotate by
|
|
4245
4303
|
* @param {boolean} [useWebGL=glEnable]
|
|
4246
4304
|
* @param {boolean} [screenSpace]
|
|
@@ -4259,12 +4317,12 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
4259
4317
|
|
|
4260
4318
|
/** Draw colored regular polygon using passed in number of sides
|
|
4261
4319
|
* @param {Vector2} pos
|
|
4262
|
-
* @param {Vector2} [size=(1
|
|
4320
|
+
* @param {Vector2} [size=vec2(1)]
|
|
4263
4321
|
* @param {number} [sides]
|
|
4264
|
-
* @param {Color} [color=
|
|
4322
|
+
* @param {Color} [color=WHITE]
|
|
4265
4323
|
* @param {number} [angle]
|
|
4266
4324
|
* @param {number} [lineWidth]
|
|
4267
|
-
* @param {Color} [lineColor=
|
|
4325
|
+
* @param {Color} [lineColor=BLACK]
|
|
4268
4326
|
* @param {boolean} [useWebGL=glEnable]
|
|
4269
4327
|
* @param {boolean} [screenSpace]
|
|
4270
4328
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -4287,10 +4345,10 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
4287
4345
|
|
|
4288
4346
|
/** Draw colored polygon using passed in points
|
|
4289
4347
|
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
4290
|
-
* @param {Color} [color=
|
|
4348
|
+
* @param {Color} [color=WHITE]
|
|
4291
4349
|
* @param {number} [lineWidth]
|
|
4292
|
-
* @param {Color} [lineColor=
|
|
4293
|
-
* @param {Vector2} [pos=(
|
|
4350
|
+
* @param {Color} [lineColor=BLACK]
|
|
4351
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
4294
4352
|
* @param {number} [angle] - Angle to rotate by
|
|
4295
4353
|
* @param {boolean} [useWebGL=glEnable]
|
|
4296
4354
|
* @param {boolean} [screenSpace]
|
|
@@ -4337,11 +4395,11 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
4337
4395
|
|
|
4338
4396
|
/** Draw colored ellipse using passed in point
|
|
4339
4397
|
* @param {Vector2} pos
|
|
4340
|
-
* @param {Vector2} [size=(1
|
|
4341
|
-
* @param {Color} [color=
|
|
4398
|
+
* @param {Vector2} [size=vec2(1)] - Width and height diameter
|
|
4399
|
+
* @param {Color} [color=WHITE]
|
|
4342
4400
|
* @param {number} [angle]
|
|
4343
4401
|
* @param {number} [lineWidth]
|
|
4344
|
-
* @param {Color} [lineColor=
|
|
4402
|
+
* @param {Color} [lineColor=BLACK]
|
|
4345
4403
|
* @param {boolean} [useWebGL=glEnable]
|
|
4346
4404
|
* @param {boolean} [screenSpace]
|
|
4347
4405
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -4383,9 +4441,9 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
4383
4441
|
/** Draw colored circle using passed in point
|
|
4384
4442
|
* @param {Vector2} pos
|
|
4385
4443
|
* @param {number} [size=1] - Diameter
|
|
4386
|
-
* @param {Color} [color=
|
|
4444
|
+
* @param {Color} [color=WHITE]
|
|
4387
4445
|
* @param {number} [lineWidth=0]
|
|
4388
|
-
* @param {Color} [lineColor=
|
|
4446
|
+
* @param {Color} [lineColor=BLACK]
|
|
4389
4447
|
* @param {boolean} [useWebGL=glEnable]
|
|
4390
4448
|
* @param {boolean} [screenSpace]
|
|
4391
4449
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -4419,7 +4477,11 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
4419
4477
|
ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
|
|
4420
4478
|
|
|
4421
4479
|
if (!screenSpace)
|
|
4422
|
-
|
|
4480
|
+
{
|
|
4481
|
+
pos = worldToScreen(pos);
|
|
4482
|
+
size = size.scale(cameraScale);
|
|
4483
|
+
angle -= cameraAngle;
|
|
4484
|
+
}
|
|
4423
4485
|
context.save();
|
|
4424
4486
|
context.translate(pos.x+.5, pos.y+.5);
|
|
4425
4487
|
context.rotate(angle);
|
|
@@ -4436,9 +4498,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
4436
4498
|
* @param {string|number} text
|
|
4437
4499
|
* @param {Vector2} pos
|
|
4438
4500
|
* @param {number} [size]
|
|
4439
|
-
* @param {Color} [color=
|
|
4501
|
+
* @param {Color} [color=WHITE]
|
|
4440
4502
|
* @param {number} [lineWidth]
|
|
4441
|
-
* @param {Color} [lineColor=
|
|
4503
|
+
* @param {Color} [lineColor=BLACK]
|
|
4442
4504
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
4443
4505
|
* @param {string} [font=fontDefault]
|
|
4444
4506
|
* @param {string} [fontStyle]
|
|
@@ -4462,18 +4524,18 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
4462
4524
|
* Automatically splits new lines into rows
|
|
4463
4525
|
* @param {string|number} text
|
|
4464
4526
|
* @param {Vector2} pos
|
|
4465
|
-
* @param {number}
|
|
4466
|
-
* @param {Color} [color=
|
|
4527
|
+
* @param {number} size
|
|
4528
|
+
* @param {Color} [color=WHITE]
|
|
4467
4529
|
* @param {number} [lineWidth]
|
|
4468
|
-
* @param {Color} [lineColor=
|
|
4530
|
+
* @param {Color} [lineColor=BLACK]
|
|
4469
4531
|
* @param {CanvasTextAlign} [textAlign]
|
|
4470
4532
|
* @param {string} [font=fontDefault]
|
|
4471
4533
|
* @param {string} [fontStyle]
|
|
4472
4534
|
* @param {number} [maxWidth]
|
|
4473
4535
|
* @param {number} [angle]
|
|
4474
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=
|
|
4536
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
4475
4537
|
* @memberof Draw */
|
|
4476
|
-
function drawTextScreen(text, pos, size
|
|
4538
|
+
function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
|
|
4477
4539
|
{
|
|
4478
4540
|
ASSERT(isString(text), 'text must be a string');
|
|
4479
4541
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -4597,7 +4659,7 @@ function worldToScreenDelta(worldDelta)
|
|
|
4597
4659
|
|
|
4598
4660
|
/** Convert screen space transform to world space
|
|
4599
4661
|
* @param {Vector2} screenPos
|
|
4600
|
-
* @param {Vector2} screenSize
|
|
4662
|
+
* @param {Vector2} screenSize
|
|
4601
4663
|
* @param {number} [screenAngle]
|
|
4602
4664
|
* @return {[Vector2, Vector2, number]} - [pos, size, angle]
|
|
4603
4665
|
* @memberof Draw */
|
|
@@ -4614,25 +4676,6 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
|
|
|
4614
4676
|
];
|
|
4615
4677
|
}
|
|
4616
4678
|
|
|
4617
|
-
/** Convert world space transform to screen space
|
|
4618
|
-
* @param {Vector2} worldPos
|
|
4619
|
-
* @param {Vector2} worldSize
|
|
4620
|
-
* @param {number} [worldAngle]
|
|
4621
|
-
* @return {[Vector2, Vector2, number]} - [pos, size, angle]
|
|
4622
|
-
* @memberof Draw */
|
|
4623
|
-
function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
|
|
4624
|
-
{
|
|
4625
|
-
ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
|
|
4626
|
-
ASSERT(isVector2(worldSize), 'worldSize must be a vec2');
|
|
4627
|
-
ASSERT(isNumber(worldAngle), 'worldAngle must be a number');
|
|
4628
|
-
|
|
4629
|
-
return [
|
|
4630
|
-
worldToScreen(worldPos),
|
|
4631
|
-
worldSize.scale(cameraScale),
|
|
4632
|
-
worldAngle - cameraAngle
|
|
4633
|
-
];
|
|
4634
|
-
}
|
|
4635
|
-
|
|
4636
4679
|
/** Get the size of the camera window in world space
|
|
4637
4680
|
* @return {Vector2}
|
|
4638
4681
|
* @memberof Draw */
|
|
@@ -4677,10 +4720,9 @@ function isOnScreen(pos, size=0)
|
|
|
4677
4720
|
* @param {boolean} [additive]
|
|
4678
4721
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4679
4722
|
* @memberof Draw */
|
|
4680
|
-
function setBlendMode(additive=false, context)
|
|
4723
|
+
function setBlendMode(additive=false, context=drawContext)
|
|
4681
4724
|
{
|
|
4682
4725
|
glAdditive = additive;
|
|
4683
|
-
context ||= drawContext;
|
|
4684
4726
|
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
4685
4727
|
}
|
|
4686
4728
|
|
|
@@ -4695,7 +4737,6 @@ function combineCanvases()
|
|
|
4695
4737
|
workContext.fillRect(0,0,w,h); // remove background alpha
|
|
4696
4738
|
glCopyToContext(workContext);
|
|
4697
4739
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
4698
|
-
mainCanvas.width |= 0;
|
|
4699
4740
|
mainContext.drawImage(workCanvas, 0, 0);
|
|
4700
4741
|
}
|
|
4701
4742
|
|
|
@@ -4800,17 +4841,22 @@ function setCursor(cursorStyle = 'auto')
|
|
|
4800
4841
|
|
|
4801
4842
|
///////////////////////////////////////////////////////////////////////////////
|
|
4802
4843
|
|
|
4844
|
+
/** Engine font image, 8x8 font provided by the engine
|
|
4845
|
+
* @type {FontImage}
|
|
4846
|
+
* @memberof Draw */
|
|
4803
4847
|
let engineFontImage;
|
|
4804
4848
|
|
|
4805
4849
|
/**
|
|
4806
|
-
* Font Image Object - Draw text
|
|
4850
|
+
* Font Image Object - Draw text by using tiles in an image
|
|
4807
4851
|
* - 96 characters (from space to tilde) are stored in an image
|
|
4808
|
-
* -
|
|
4809
|
-
* -
|
|
4852
|
+
* - A 8x8 default engine font is supplied for general use
|
|
4853
|
+
* - This system is WebGL enabled for fast text rendering
|
|
4854
|
+
* - Fonts can also be colored and scaled along each axis
|
|
4855
|
+
*
|
|
4810
4856
|
* @memberof Draw
|
|
4811
4857
|
* @example
|
|
4812
4858
|
* // use built in font
|
|
4813
|
-
* const font =
|
|
4859
|
+
* const font = engineFontImage;
|
|
4814
4860
|
*
|
|
4815
4861
|
* // draw text
|
|
4816
4862
|
* font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
|
|
@@ -4818,70 +4864,112 @@ let engineFontImage;
|
|
|
4818
4864
|
class FontImage
|
|
4819
4865
|
{
|
|
4820
4866
|
/** Create an image font
|
|
4821
|
-
* @param {
|
|
4822
|
-
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
4823
|
-
* @param {Vector2} [paddingSize=(0,1)] - How much space between characters
|
|
4867
|
+
* @param {TileInfo} tileInfo - Tile info of first characeter in font
|
|
4824
4868
|
*/
|
|
4825
|
-
constructor(
|
|
4869
|
+
constructor(tileInfo)
|
|
4826
4870
|
{
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
{
|
|
4830
|
-
|
|
4831
|
-
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
4832
|
-
}
|
|
4833
|
-
|
|
4834
|
-
this.image = image || engineFontImage;
|
|
4835
|
-
this.tileSize = tileSize;
|
|
4836
|
-
this.paddingSize = paddingSize;
|
|
4871
|
+
ASSERT(!!tileInfo, 'tileInfo is required for FontImage');
|
|
4872
|
+
|
|
4873
|
+
/** @property {TileInfo} - Tile info for the font */
|
|
4874
|
+
this.tileInfo = tileInfo.frame(0);
|
|
4837
4875
|
}
|
|
4838
4876
|
|
|
4839
4877
|
/** Draw text in world space using the image font
|
|
4840
|
-
* @param {string|number}
|
|
4878
|
+
* @param {string|number} text
|
|
4841
4879
|
* @param {Vector2} pos
|
|
4842
|
-
* @param {number}
|
|
4843
|
-
* @param {boolean} [center]
|
|
4844
|
-
* @param {
|
|
4880
|
+
* @param {Vector2|number} [size]
|
|
4881
|
+
* @param {boolean} [center=true]
|
|
4882
|
+
* @param {Color} [color=WHITE]
|
|
4883
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
4884
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4845
4885
|
*/
|
|
4846
|
-
drawText(text, pos,
|
|
4886
|
+
drawText(text, pos, size=1, center, color, useWebGL, context)
|
|
4847
4887
|
{
|
|
4848
|
-
|
|
4888
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4889
|
+
|
|
4890
|
+
if (typeof size === 'number')
|
|
4891
|
+
{
|
|
4892
|
+
// if size is a number, make it a vector
|
|
4893
|
+
ASSERT(size > 0);
|
|
4894
|
+
size *= cameraScale;
|
|
4895
|
+
size = new Vector2(size, size);
|
|
4896
|
+
}
|
|
4897
|
+
else
|
|
4898
|
+
size = size.scale(cameraScale);
|
|
4899
|
+
this.drawTextScreen(text, worldToScreen(pos), size, center, color, useWebGL, context);
|
|
4849
4900
|
}
|
|
4850
4901
|
|
|
4851
4902
|
/** Draw text in screen space using the image font
|
|
4852
|
-
* @param {string|number}
|
|
4903
|
+
* @param {string|number} text
|
|
4853
4904
|
* @param {Vector2} pos
|
|
4854
|
-
* @param {number}
|
|
4905
|
+
* @param {Vector2|number} size
|
|
4855
4906
|
* @param {boolean} [center]
|
|
4856
|
-
* @param {
|
|
4907
|
+
* @param {Color} [color=WHITE]
|
|
4908
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
4909
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4857
4910
|
*/
|
|
4858
|
-
drawTextScreen(text, pos,
|
|
4911
|
+
drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
|
|
4859
4912
|
{
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4913
|
+
ASSERT(isString(text), 'text must be a string');
|
|
4914
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
4915
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4916
|
+
ASSERT(isColor(color), 'color must be a color');
|
|
4917
|
+
|
|
4918
|
+
// if size is a number, make it a vector
|
|
4919
|
+
size = typeof size === 'number' ? new Vector2(size, size) : size;
|
|
4920
|
+
|
|
4921
|
+
// precache objects for drawing
|
|
4922
|
+
const drawPos = new Vector2;
|
|
4923
|
+
const tileInfo = this.tileInfo;
|
|
4924
|
+
const padding = tileInfo.padding;
|
|
4925
|
+
const sizePaddedX = tileInfo.size.x + padding*2;
|
|
4926
|
+
const sizePaddedY = tileInfo.size.y + padding*2;
|
|
4927
|
+
const cols = tileInfo.textureInfo.size.x / sizePaddedX |0;
|
|
4928
|
+
|
|
4929
|
+
// draw each line of text
|
|
4930
|
+
(text+'').split('\n').forEach((line, j)=>
|
|
4865
4931
|
{
|
|
4866
|
-
const centerOffset = center ? line.length * size.x
|
|
4867
|
-
for (let
|
|
4932
|
+
const centerOffset = center ? (line.length-1) * size.x / 2 : 0;
|
|
4933
|
+
for (let i=line.length; i--;)
|
|
4868
4934
|
{
|
|
4869
|
-
//
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
charCode
|
|
4873
|
-
|
|
4874
|
-
// get the
|
|
4875
|
-
const
|
|
4876
|
-
const
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4935
|
+
// get the character index
|
|
4936
|
+
const charCode = line.charCodeAt(i);
|
|
4937
|
+
const index = charCode < 32 || charCode > 127 ?
|
|
4938
|
+
95 : charCode - 32; // handle out of range characters
|
|
4939
|
+
|
|
4940
|
+
// get the position of the tile
|
|
4941
|
+
const x = index % cols;
|
|
4942
|
+
const y = index / cols |0;
|
|
4943
|
+
tileInfo.pos.x = x*sizePaddedX + padding;
|
|
4944
|
+
tileInfo.pos.y = y*sizePaddedY + padding;
|
|
4945
|
+
|
|
4946
|
+
// draw the tile
|
|
4947
|
+
drawPos.x = pos.x + i * size.x - centerOffset |0;
|
|
4948
|
+
drawPos.y = pos.y + j * size.y |0;
|
|
4949
|
+
drawTile(drawPos, size, tileInfo, color, 0, false, undefined, useWebGL, true, context);
|
|
4881
4950
|
}
|
|
4882
4951
|
});
|
|
4883
|
-
context.restore();
|
|
4884
4952
|
}
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4955
|
+
// load engine font, called automatically on startup
|
|
4956
|
+
function fontImageInit()
|
|
4957
|
+
{
|
|
4958
|
+
return new Promise(resolve =>
|
|
4959
|
+
{
|
|
4960
|
+
// create the engine font
|
|
4961
|
+
const image = new Image;
|
|
4962
|
+
image.onerror = image.onload = ()=>
|
|
4963
|
+
{
|
|
4964
|
+
const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
|
|
4965
|
+
const textureInfo = new TextureInfo(image);
|
|
4966
|
+
const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
|
|
4967
|
+
engineFontImage = new FontImage(tileInfo);
|
|
4968
|
+
resolve();
|
|
4969
|
+
}
|
|
4970
|
+
image.crossOrigin = 'anonymous';
|
|
4971
|
+
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==';
|
|
4972
|
+
});
|
|
4885
4973
|
}
|
|
4886
4974
|
/**
|
|
4887
4975
|
* LittleJS Input System
|
|
@@ -4938,6 +5026,10 @@ let inputPreventDefault = true;
|
|
|
4938
5026
|
* @memberof Input */
|
|
4939
5027
|
let gamepadPrimary = 0;
|
|
4940
5028
|
|
|
5029
|
+
/** True if a touch device has been detected
|
|
5030
|
+
* @memberof Input */
|
|
5031
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
5032
|
+
|
|
4941
5033
|
/** Prevents input continuing to the default browser handling
|
|
4942
5034
|
* This is useful to disable for html menus so the browser can handle input normally
|
|
4943
5035
|
* @param {boolean} preventDefault
|
|
@@ -5136,10 +5228,6 @@ function gamepadStickCount(gamepad=gamepadPrimary)
|
|
|
5136
5228
|
return gamepadStickData[gamepad]?.length ?? 0;
|
|
5137
5229
|
}
|
|
5138
5230
|
|
|
5139
|
-
/** True if a touch device has been detected
|
|
5140
|
-
* @memberof Input */
|
|
5141
|
-
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
5142
|
-
|
|
5143
5231
|
///////////////////////////////////////////////////////////////////////////////
|
|
5144
5232
|
|
|
5145
5233
|
/** Pulse the vibration hardware if it exists
|
|
@@ -5148,7 +5236,7 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
|
5148
5236
|
function vibrate(pattern=100)
|
|
5149
5237
|
{
|
|
5150
5238
|
ASSERT(isNumber(pattern) || isArray(pattern), 'pattern must be a number or array');
|
|
5151
|
-
vibrateEnable && !headlessMode && navigator
|
|
5239
|
+
vibrateEnable && !headlessMode && navigator?.vibrate?.(pattern);
|
|
5152
5240
|
}
|
|
5153
5241
|
|
|
5154
5242
|
/** Cancel any ongoing vibration
|
|
@@ -5248,8 +5336,7 @@ function inputInit()
|
|
|
5248
5336
|
}
|
|
5249
5337
|
function onMouseDown(e)
|
|
5250
5338
|
{
|
|
5251
|
-
if (isTouchDevice && touchInputEnable)
|
|
5252
|
-
return;
|
|
5339
|
+
if (isTouchDevice && touchInputEnable) return;
|
|
5253
5340
|
|
|
5254
5341
|
// fix stalled audio requiring user interaction
|
|
5255
5342
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
@@ -5267,8 +5354,8 @@ function inputInit()
|
|
|
5267
5354
|
}
|
|
5268
5355
|
function onMouseUp(e)
|
|
5269
5356
|
{
|
|
5270
|
-
if (isTouchDevice && touchInputEnable)
|
|
5271
|
-
|
|
5357
|
+
if (isTouchDevice && touchInputEnable) return;
|
|
5358
|
+
|
|
5272
5359
|
inputData[0][e.button] = (inputData[0][e.button]&2) | 4;
|
|
5273
5360
|
}
|
|
5274
5361
|
function onMouseMove(e)
|
|
@@ -5300,8 +5387,7 @@ function inputInit()
|
|
|
5300
5387
|
let wasTouching;
|
|
5301
5388
|
function handleTouch(e)
|
|
5302
5389
|
{
|
|
5303
|
-
if (!touchInputEnable)
|
|
5304
|
-
return;
|
|
5390
|
+
if (!touchInputEnable) return;
|
|
5305
5391
|
|
|
5306
5392
|
// route touch to gamepad
|
|
5307
5393
|
if (touchGamepadEnable)
|
|
@@ -5365,8 +5451,7 @@ function inputInit()
|
|
|
5365
5451
|
}
|
|
5366
5452
|
|
|
5367
5453
|
// don't process touch gamepad if paused
|
|
5368
|
-
if (paused)
|
|
5369
|
-
return;
|
|
5454
|
+
if (paused) return;
|
|
5370
5455
|
|
|
5371
5456
|
// get center of left and right sides
|
|
5372
5457
|
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
@@ -5456,8 +5541,7 @@ function inputUpdate()
|
|
|
5456
5541
|
// update touch gamepad if enabled
|
|
5457
5542
|
if (touchGamepadEnable && isTouchDevice)
|
|
5458
5543
|
{
|
|
5459
|
-
if (!touchGamepadTimer.isSet())
|
|
5460
|
-
return;
|
|
5544
|
+
if (!touchGamepadTimer.isSet()) return;
|
|
5461
5545
|
|
|
5462
5546
|
// read virtual analog stick
|
|
5463
5547
|
gamepadPrimary = 0; // touch gamepad uses index 0
|
|
@@ -5495,12 +5579,10 @@ function inputUpdate()
|
|
|
5495
5579
|
}
|
|
5496
5580
|
|
|
5497
5581
|
// return if gamepads are disabled or not supported
|
|
5498
|
-
if (!gamepadsEnable || !navigator || !navigator.getGamepads)
|
|
5499
|
-
return;
|
|
5582
|
+
if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
|
|
5500
5583
|
|
|
5501
5584
|
// only poll gamepads when focused or in debug mode
|
|
5502
|
-
if (!debug && !document.hasFocus())
|
|
5503
|
-
return;
|
|
5585
|
+
if (!debug && !document.hasFocus()) return;
|
|
5504
5586
|
|
|
5505
5587
|
// poll gamepads
|
|
5506
5588
|
const maxGamepads = 8;
|
|
@@ -5537,8 +5619,7 @@ function inputUpdate()
|
|
|
5537
5619
|
data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
5538
5620
|
|
|
5539
5621
|
// check for any input on this gamepad, analog must be full press
|
|
5540
|
-
if (button.pressed)
|
|
5541
|
-
if (!button.value || button.value > .9)
|
|
5622
|
+
if (button.pressed && (!button.value || button.value > .9))
|
|
5542
5623
|
hadInput = true;
|
|
5543
5624
|
}
|
|
5544
5625
|
|
|
@@ -5567,7 +5648,7 @@ function inputUpdate()
|
|
|
5567
5648
|
}
|
|
5568
5649
|
|
|
5569
5650
|
// copy dpad to left analog stick when pressed
|
|
5570
|
-
if (gamepadDirectionEmulateStick &&
|
|
5651
|
+
if (gamepadDirectionEmulateStick && (dpad.x || dpad.y))
|
|
5571
5652
|
sticks[0] = dpad.clampLength();
|
|
5572
5653
|
}
|
|
5573
5654
|
|
|
@@ -5596,13 +5677,11 @@ function inputRender()
|
|
|
5596
5677
|
function touchGamepadRender()
|
|
5597
5678
|
{
|
|
5598
5679
|
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
5599
|
-
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
5600
|
-
return;
|
|
5680
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet()) return;
|
|
5601
5681
|
|
|
5602
5682
|
// fade off when not touching or paused
|
|
5603
5683
|
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
5604
|
-
if (!alpha || paused)
|
|
5605
|
-
return;
|
|
5684
|
+
if (!alpha || paused) return;
|
|
5606
5685
|
|
|
5607
5686
|
// setup the canvas
|
|
5608
5687
|
const context = mainContext;
|
|
@@ -5755,8 +5834,7 @@ class Sound
|
|
|
5755
5834
|
{
|
|
5756
5835
|
// remove randomness so it can be applied on playback
|
|
5757
5836
|
const randomnessIndex = 1, defaultRandomness = .05;
|
|
5758
|
-
this.randomness = zzfxSound[randomnessIndex]
|
|
5759
|
-
zzfxSound[randomnessIndex] : defaultRandomness;
|
|
5837
|
+
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
5760
5838
|
zzfxSound[randomnessIndex] = 0;
|
|
5761
5839
|
|
|
5762
5840
|
// generate the zzfx samples
|
|
@@ -5836,7 +5914,7 @@ class Sound
|
|
|
5836
5914
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
5837
5915
|
*/
|
|
5838
5916
|
getDuration()
|
|
5839
|
-
{ return this.sampleChannels
|
|
5917
|
+
{ return this.sampleChannels?.[0].length / this.sampleRate || 0; }
|
|
5840
5918
|
|
|
5841
5919
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
5842
5920
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -5928,8 +6006,7 @@ class SoundWave extends Sound
|
|
|
5928
6006
|
this.sampleRate = audioBuffer.sampleRate;
|
|
5929
6007
|
this.sampleChannels = sampleChannels;
|
|
5930
6008
|
this.loadedPercent = 1;
|
|
5931
|
-
|
|
5932
|
-
this.onloadCallback(this);
|
|
6009
|
+
this.onloadCallback?.(this);
|
|
5933
6010
|
}
|
|
5934
6011
|
}
|
|
5935
6012
|
|
|
@@ -6052,8 +6129,7 @@ class SoundInstance
|
|
|
6052
6129
|
/** Pause this sound instance */
|
|
6053
6130
|
pause()
|
|
6054
6131
|
{
|
|
6055
|
-
if (this.isPaused())
|
|
6056
|
-
return;
|
|
6132
|
+
if (this.isPaused()) return;
|
|
6057
6133
|
|
|
6058
6134
|
// save current time and stop sound
|
|
6059
6135
|
this.pausedTime = this.getCurrentTime();
|
|
@@ -6065,8 +6141,7 @@ class SoundInstance
|
|
|
6065
6141
|
/** Unpauses this sound instance */
|
|
6066
6142
|
resume()
|
|
6067
6143
|
{
|
|
6068
|
-
if (!this.isPaused())
|
|
6069
|
-
return;
|
|
6144
|
+
if (!this.isPaused()) return;
|
|
6070
6145
|
|
|
6071
6146
|
// restart sound from paused time
|
|
6072
6147
|
this.start(this.pausedTime);
|
|
@@ -6134,7 +6209,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
6134
6209
|
|
|
6135
6210
|
/** Stop all queued speech
|
|
6136
6211
|
* @memberof Audio */
|
|
6137
|
-
function speakStop() {speechSynthesis
|
|
6212
|
+
function speakStop() {speechSynthesis?.cancel();}
|
|
6138
6213
|
|
|
6139
6214
|
/** Get frequency of a note on a musical scale
|
|
6140
6215
|
* @param {number} semitoneOffset - How many semitones away from the root note
|
|
@@ -6393,7 +6468,7 @@ function tileCollisionGetData(pos)
|
|
|
6393
6468
|
|
|
6394
6469
|
/** Check if a tile layer collides with another object
|
|
6395
6470
|
* @param {Vector2} pos
|
|
6396
|
-
* @param {Vector2} [size=(
|
|
6471
|
+
* @param {Vector2} [size=vec2()]
|
|
6397
6472
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
6398
6473
|
* @param {boolean} [solidOnly] - Only check solid layers if true
|
|
6399
6474
|
* @return {TileCollisionLayer}
|
|
@@ -6511,20 +6586,20 @@ function tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLa
|
|
|
6511
6586
|
class TileLayerData
|
|
6512
6587
|
{
|
|
6513
6588
|
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
6514
|
-
* @param {number} [tile]
|
|
6589
|
+
* @param {number} [tile] - The tile to use, untextured if undefined
|
|
6515
6590
|
* @param {number} [direction] - Integer direction of tile, in 90 degree increments
|
|
6516
|
-
* @param {boolean} [mirror]
|
|
6517
|
-
* @param {Color} [color]
|
|
6591
|
+
* @param {boolean} [mirror] - If the tile should be mirrored along the x axis
|
|
6592
|
+
* @param {Color} [color] - Color of the tile */
|
|
6518
6593
|
constructor(tile, direction=0, mirror=false, color=new Color)
|
|
6519
6594
|
{
|
|
6520
|
-
/** @property {number}
|
|
6521
|
-
this.tile
|
|
6522
|
-
/** @property {number}
|
|
6595
|
+
/** @property {number} - The tile to use, untextured if undefined */
|
|
6596
|
+
this.tile = tile;
|
|
6597
|
+
/** @property {number} - Integer direction of tile, in 90 degree increments */
|
|
6523
6598
|
this.direction = direction;
|
|
6524
6599
|
/** @property {boolean} - If the tile should be mirrored along the x axis */
|
|
6525
|
-
this.mirror
|
|
6526
|
-
/** @property {Color}
|
|
6527
|
-
this.color
|
|
6600
|
+
this.mirror = mirror;
|
|
6601
|
+
/** @property {Color} - Color of the tile */
|
|
6602
|
+
this.color = color.copy();
|
|
6528
6603
|
}
|
|
6529
6604
|
|
|
6530
6605
|
/** Set this tile to clear, it will not be rendered */
|
|
@@ -6535,7 +6610,7 @@ class TileLayerData
|
|
|
6535
6610
|
/**
|
|
6536
6611
|
* Canvas Layer - cached off screen rendering system
|
|
6537
6612
|
* - Contains an offscreen canvas that can be rendered to
|
|
6538
|
-
* - WebGL rendering is optional, call
|
|
6613
|
+
* - WebGL rendering is optional, call updateWebGL to enable/update
|
|
6539
6614
|
* @extends EngineObject
|
|
6540
6615
|
* @memberof TileLayers
|
|
6541
6616
|
* @example
|
|
@@ -6549,8 +6624,9 @@ class CanvasLayer extends EngineObject
|
|
|
6549
6624
|
* @param {number} [angle] - Angle the layer is rotated by
|
|
6550
6625
|
* @param {number} [renderOrder] - Objects sorted by renderOrder
|
|
6551
6626
|
* @param {Vector2} [canvasSize] - Default size of canvas, can be changed later
|
|
6627
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
6552
6628
|
*/
|
|
6553
|
-
constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512))
|
|
6629
|
+
constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512), useWebGL=glEnable)
|
|
6554
6630
|
{
|
|
6555
6631
|
ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
|
|
6556
6632
|
super(position, size, undefined, angle, WHITE, renderOrder);
|
|
@@ -6560,13 +6636,10 @@ class CanvasLayer extends EngineObject
|
|
|
6560
6636
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
|
|
6561
6637
|
this.context = this.canvas?.getContext('2d');
|
|
6562
6638
|
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
6563
|
-
const useWebGL = false; // do not use webgl by default
|
|
6564
6639
|
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
6565
|
-
/** @property {boolean} - True if WebGL texture needs to be refreshed */
|
|
6566
|
-
this.refreshWebGL = false;
|
|
6567
6640
|
|
|
6568
6641
|
// disable physics by default
|
|
6569
|
-
this.mass =
|
|
6642
|
+
this.mass = 0;
|
|
6570
6643
|
}
|
|
6571
6644
|
|
|
6572
6645
|
/** Destroy this canvas layer */
|
|
@@ -6581,7 +6654,7 @@ class CanvasLayer extends EngineObject
|
|
|
6581
6654
|
// Render the layer, called automatically by the engine
|
|
6582
6655
|
render()
|
|
6583
6656
|
{
|
|
6584
|
-
this.draw(this.pos, this.size, this.
|
|
6657
|
+
this.draw(this.pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
6585
6658
|
}
|
|
6586
6659
|
|
|
6587
6660
|
/** Draw this canvas layer centered in world space, with color applied if using WebGL
|
|
@@ -6594,103 +6667,54 @@ class CanvasLayer extends EngineObject
|
|
|
6594
6667
|
* @param {boolean} [screenSpace] - If true the pos and size are in screen space
|
|
6595
6668
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
6596
6669
|
* @memberof Draw */
|
|
6597
|
-
draw(pos, size,
|
|
6670
|
+
draw(pos, size, color=WHITE, angle=0, mirror=false, additiveColor, screenSpace=false, context)
|
|
6598
6671
|
{
|
|
6599
|
-
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
6600
|
-
if (useWebGL && this.refreshWebGL)
|
|
6601
|
-
{
|
|
6602
|
-
// update the WebGL texture
|
|
6603
|
-
this.textureInfo.createWebGLTexture();
|
|
6604
|
-
this.refreshWebGL = false;
|
|
6605
|
-
}
|
|
6606
|
-
|
|
6607
6672
|
// draw the canvas layer as a single tile that uses the whole texture
|
|
6608
6673
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
6674
|
+
const useWebGL = this.hasWebGL();
|
|
6609
6675
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
6610
6676
|
}
|
|
6611
6677
|
|
|
6612
|
-
/**
|
|
6613
|
-
* @callback Canvas2DDrawCallback - Function that draws to a canvas 2D context
|
|
6614
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
6615
|
-
* @memberof TileLayers
|
|
6616
|
-
*/
|
|
6617
|
-
|
|
6618
|
-
/** Draw onto the layer canvas in world space (bypass WebGL)
|
|
6678
|
+
/** Draw a tile onto the layer canvas in world space
|
|
6619
6679
|
* @param {Vector2} pos
|
|
6620
|
-
* @param {Vector2} size
|
|
6621
|
-
* @param {
|
|
6622
|
-
* @param {
|
|
6623
|
-
* @param {
|
|
6624
|
-
|
|
6680
|
+
* @param {Vector2} [size=vec2(1)]
|
|
6681
|
+
* @param {TileInfo} [tileInfo]
|
|
6682
|
+
* @param {Color} [color=WHITE]
|
|
6683
|
+
* @param {number} [angle]
|
|
6684
|
+
* @param {boolean} [mirror] */
|
|
6685
|
+
drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle=0, mirror=false)
|
|
6625
6686
|
{
|
|
6626
|
-
if (!this.context) return;
|
|
6627
|
-
|
|
6628
|
-
const context = this.context;
|
|
6629
|
-
context.save();
|
|
6630
6687
|
pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
|
|
6631
6688
|
size = size.multiply(this.tileInfo.size);
|
|
6632
|
-
|
|
6633
|
-
context.rotate(angle);
|
|
6634
|
-
context.scale(mirror ? -size.x : size.x, size.y);
|
|
6635
|
-
drawFunction(context);
|
|
6636
|
-
context.restore();
|
|
6637
|
-
}
|
|
6689
|
+
pos.y = this.canvas.height - pos.y;
|
|
6638
6690
|
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
this.drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
6649
|
-
{
|
|
6650
|
-
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
6651
|
-
if (textureInfo)
|
|
6652
|
-
{
|
|
6653
|
-
context.globalAlpha = color.a; // only alpha is supported
|
|
6654
|
-
context.drawImage(textureInfo.image,
|
|
6655
|
-
tileInfo.pos.x, tileInfo.pos.y,
|
|
6656
|
-
tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
|
|
6657
|
-
context.globalAlpha = 1;
|
|
6658
|
-
}
|
|
6659
|
-
else
|
|
6660
|
-
{
|
|
6661
|
-
// untextured
|
|
6662
|
-
context.fillStyle = color.toString();
|
|
6663
|
-
context.fillRect(-.5, -.5, 1, 1);
|
|
6664
|
-
}
|
|
6665
|
-
});
|
|
6691
|
+
// draw the tile onto the layer canvas
|
|
6692
|
+
const oldMainCanvasSize = mainCanvasSize;
|
|
6693
|
+
mainCanvasSize = vec2(this.canvas.width, this.canvas.height);
|
|
6694
|
+
const useWebGL = this.hasWebGL();
|
|
6695
|
+
useWebGL && glSetRenderTarget(this.textureInfo.glTexture);
|
|
6696
|
+
const drawContext = useWebGL ? undefined : this.context;
|
|
6697
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, undefined, useWebGL, true, drawContext);
|
|
6698
|
+
useWebGL && glSetRenderTarget();
|
|
6699
|
+
mainCanvasSize = oldMainCanvasSize;
|
|
6666
6700
|
}
|
|
6667
6701
|
|
|
6668
6702
|
/** Draw a rectangle onto the layer canvas in world space
|
|
6669
6703
|
* @param {Vector2} pos
|
|
6670
|
-
* @param {Vector2} [size=(1
|
|
6671
|
-
* @param {Color} [color=
|
|
6672
|
-
* @param {number} [angle
|
|
6704
|
+
* @param {Vector2} [size=vec2(1)]
|
|
6705
|
+
* @param {Color} [color=WHITE]
|
|
6706
|
+
* @param {number} [angle] */
|
|
6673
6707
|
drawRect(pos, size, color, angle)
|
|
6674
6708
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
6675
6709
|
|
|
6676
|
-
/** Create
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
*/
|
|
6680
|
-
useWebGL(enable=true, immediate=false)
|
|
6681
|
-
{
|
|
6682
|
-
if (!immediate && enable && this.textureInfo.hasWebGL())
|
|
6683
|
-
{
|
|
6684
|
-
// refresh the texture when needed
|
|
6685
|
-
this.refreshWebGL = true;
|
|
6686
|
-
return;
|
|
6687
|
-
}
|
|
6710
|
+
/** Create WebGL texture if necessary and copy layer canvas to it */
|
|
6711
|
+
updateWebGL()
|
|
6712
|
+
{ this.textureInfo.createWebGLTexture(); }
|
|
6688
6713
|
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
}
|
|
6714
|
+
/** Check if this layer is using WebGL
|
|
6715
|
+
* @return {boolean} */
|
|
6716
|
+
hasWebGL()
|
|
6717
|
+
{ return glEnable && this.textureInfo.hasWebGL(); }
|
|
6694
6718
|
}
|
|
6695
6719
|
|
|
6696
6720
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -6709,50 +6733,67 @@ class CanvasLayer extends EngineObject
|
|
|
6709
6733
|
class TileLayer extends CanvasLayer
|
|
6710
6734
|
{
|
|
6711
6735
|
/** Create a tile layer object
|
|
6712
|
-
* @param {Vector2} position
|
|
6713
|
-
* @param {Vector2} size
|
|
6714
|
-
* @param {TileInfo} [tileInfo]
|
|
6736
|
+
* @param {Vector2} position - World space position
|
|
6737
|
+
* @param {Vector2} size - World space size
|
|
6738
|
+
* @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
|
|
6715
6739
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6716
6740
|
*/
|
|
6717
6741
|
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
6718
6742
|
{
|
|
6719
6743
|
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6744
|
+
const useWebGL = true;
|
|
6745
|
+
super(position, size, 0, renderOrder, canvasSize, useWebGL);
|
|
6746
|
+
|
|
6747
|
+
/** @property {TileInfo} - Default tile info for layer */
|
|
6748
|
+
this.tileInfo = undefined;
|
|
6749
|
+
/** @property {Array<TileLayerData>} - Default tile info for layer */
|
|
6726
6750
|
this.data = [];
|
|
6727
|
-
|
|
6728
|
-
|
|
6751
|
+
/** @property {boolean} - Is this layer using a webgl texture? */
|
|
6752
|
+
this.isUsingWebGL = false;
|
|
6729
6753
|
|
|
6730
6754
|
if (headlessMode)
|
|
6731
6755
|
{
|
|
6732
|
-
// disable rendering
|
|
6733
|
-
this.
|
|
6734
|
-
this.
|
|
6735
|
-
this.redrawStart
|
|
6736
|
-
this.redrawEnd
|
|
6737
|
-
this.drawTileData
|
|
6738
|
-
this.
|
|
6739
|
-
this.
|
|
6756
|
+
// disable rendering in headless mode
|
|
6757
|
+
this.render = () => {};
|
|
6758
|
+
this.redraw = () => {};
|
|
6759
|
+
this.redrawStart = () => {};
|
|
6760
|
+
this.redrawEnd = () => {};
|
|
6761
|
+
this.drawTileData = () => {};
|
|
6762
|
+
this.redrawTileData = () => {};
|
|
6763
|
+
this.drawLayerTile = () => {};
|
|
6764
|
+
this.drawLayerRect = () => {};
|
|
6765
|
+
this.clearLayerRect = () => {};
|
|
6766
|
+
return;
|
|
6767
|
+
}
|
|
6768
|
+
|
|
6769
|
+
if (tileInfo)
|
|
6770
|
+
{
|
|
6771
|
+
// set tile info
|
|
6772
|
+
this.tileInfo = tileInfo.frame(0);
|
|
6773
|
+
this.tileInfo.bleed = 0; // disable bleed for tile layers
|
|
6740
6774
|
}
|
|
6775
|
+
|
|
6776
|
+
// init tile data
|
|
6777
|
+
for (let j = this.size.area(); j--;)
|
|
6778
|
+
this.data.push(new TileLayerData);
|
|
6741
6779
|
}
|
|
6742
6780
|
|
|
6743
6781
|
/** Set data at a given position in the array
|
|
6744
6782
|
* @param {Vector2} layerPos - Local position in array
|
|
6745
|
-
* @param {TileLayerData} data
|
|
6783
|
+
* @param {TileLayerData} data - Data to set
|
|
6746
6784
|
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
6747
6785
|
setData(layerPos, data, redraw=false)
|
|
6748
6786
|
{
|
|
6787
|
+
layerPos = layerPos.floor();
|
|
6749
6788
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6750
6789
|
ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6790
|
+
|
|
6791
|
+
if (!layerPos.arrayCheck(this.size)) return;
|
|
6792
|
+
this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
|
|
6793
|
+
|
|
6794
|
+
if (!redraw) return;
|
|
6795
|
+
const isRedraw = drawContext === this.context;
|
|
6796
|
+
isRedraw ? this.drawTileData(layerPos) : this.redrawTileData(layerPos);
|
|
6756
6797
|
}
|
|
6757
6798
|
|
|
6758
6799
|
/** Get data at a given position in the array
|
|
@@ -6761,7 +6802,18 @@ class TileLayer extends CanvasLayer
|
|
|
6761
6802
|
getData(layerPos)
|
|
6762
6803
|
{
|
|
6763
6804
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6764
|
-
return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
|
|
6805
|
+
return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
|
|
6806
|
+
}
|
|
6807
|
+
|
|
6808
|
+
// Update the tile layer, refresh texture if needed
|
|
6809
|
+
update()
|
|
6810
|
+
{
|
|
6811
|
+
if (!glEnable && this.isUsingWebGL)
|
|
6812
|
+
{
|
|
6813
|
+
// redraw the layer if webgl was disabled or context lost
|
|
6814
|
+
this.isUsingWebGL = false;
|
|
6815
|
+
this.redraw();
|
|
6816
|
+
}
|
|
6765
6817
|
}
|
|
6766
6818
|
|
|
6767
6819
|
// Render the tile layer, called automatically by the engine
|
|
@@ -6769,78 +6821,76 @@ class TileLayer extends CanvasLayer
|
|
|
6769
6821
|
{
|
|
6770
6822
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
6771
6823
|
|
|
6772
|
-
if (this.refreshWebGL)
|
|
6773
|
-
{
|
|
6774
|
-
// update the WebGL texture
|
|
6775
|
-
this.textureInfo.createWebGLTexture();
|
|
6776
|
-
this.refreshWebGL = false;
|
|
6777
|
-
}
|
|
6778
|
-
|
|
6779
|
-
// draw the tile layer as a single tile
|
|
6780
|
-
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
6781
6824
|
const size = this.drawSize || this.size;
|
|
6782
6825
|
const pos = this.pos.add(size.scale(.5));
|
|
6783
|
-
|
|
6784
|
-
drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
|
|
6826
|
+
this.draw(pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
6785
6827
|
}
|
|
6786
6828
|
|
|
6829
|
+
/** Called after this layer is redrawn, does nothing by default */
|
|
6830
|
+
onRedraw() {}
|
|
6831
|
+
|
|
6787
6832
|
/** Draw all the tile data to an offscreen canvas
|
|
6788
|
-
* - This may be slow
|
|
6833
|
+
* - This may be slow if not using webgl but only needs to be done once */
|
|
6789
6834
|
redraw()
|
|
6790
6835
|
{
|
|
6791
6836
|
this.redrawStart(true);
|
|
6792
6837
|
for (let x = this.size.x; x--;)
|
|
6793
6838
|
for (let y = this.size.y; y--;)
|
|
6794
6839
|
this.drawTileData(vec2(x,y), false);
|
|
6840
|
+
this.hasWebGL() && glFlush();
|
|
6841
|
+
this.onRedraw();
|
|
6795
6842
|
this.redrawEnd();
|
|
6796
|
-
this.useWebGL();
|
|
6797
6843
|
}
|
|
6798
6844
|
|
|
6799
6845
|
/** Call to start the redraw process
|
|
6800
|
-
* - This can be used to manually update
|
|
6846
|
+
* - This can be used to manually update parts of the level
|
|
6801
6847
|
* @param {boolean} [clear] - Should it clear the canvas before drawing */
|
|
6802
6848
|
redrawStart(clear=false)
|
|
6803
6849
|
{
|
|
6804
6850
|
if (!this.context) return;
|
|
6805
|
-
|
|
6851
|
+
ASSERT(drawContext !== this.context);
|
|
6852
|
+
|
|
6806
6853
|
// save current render settings
|
|
6807
|
-
/** @type {[
|
|
6808
|
-
this.savedRenderSettings = [
|
|
6854
|
+
/** @type {[CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
|
|
6855
|
+
this.savedRenderSettings = [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
|
|
6809
6856
|
|
|
6810
6857
|
// set the draw canvas and context to this layer
|
|
6811
6858
|
// use camera settings to match this layer's canvas
|
|
6812
|
-
drawCanvas = this.canvas;
|
|
6813
6859
|
drawContext = this.context;
|
|
6814
|
-
|
|
6815
|
-
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
6816
|
-
cameraScale = tileSize.x;
|
|
6817
|
-
canvasClearColor = CLEAR_BLACK;
|
|
6860
|
+
const tileSize = this.tileInfo?.size ?? vec2(1);
|
|
6818
6861
|
mainCanvasSize = this.size.multiply(tileSize);
|
|
6819
|
-
|
|
6862
|
+
canvasClearColor = CLEAR_BLACK;
|
|
6863
|
+
cameraPos = this.size.multiply(tileSize).scale(.5);
|
|
6864
|
+
cameraScale = 1;
|
|
6865
|
+
|
|
6866
|
+
// set render target to this layer
|
|
6867
|
+
this.isUsingWebGL = this.hasWebGL();
|
|
6868
|
+
if (this.isUsingWebGL)
|
|
6869
|
+
glSetRenderTarget(this.textureInfo.glTexture, clear);
|
|
6870
|
+
else
|
|
6820
6871
|
{
|
|
6821
|
-
//
|
|
6822
|
-
|
|
6823
|
-
|
|
6872
|
+
// disable smoothing for pixel art
|
|
6873
|
+
this.context.imageSmoothingEnabled = !tilesPixelated;
|
|
6874
|
+
if (clear)
|
|
6875
|
+
{
|
|
6876
|
+
// clear and set size
|
|
6877
|
+
this.canvas.width = mainCanvasSize.x;
|
|
6878
|
+
this.canvas.height = mainCanvasSize.y;
|
|
6879
|
+
}
|
|
6824
6880
|
}
|
|
6825
|
-
|
|
6826
|
-
// disable smoothing for pixel art
|
|
6827
|
-
drawContext.imageSmoothingEnabled = !tilesPixelated;
|
|
6828
|
-
|
|
6829
|
-
// setup gl rendering if enabled
|
|
6830
|
-
glPreRender();
|
|
6831
6881
|
}
|
|
6832
6882
|
|
|
6833
6883
|
/** Call to end the redraw process */
|
|
6834
6884
|
redrawEnd()
|
|
6835
6885
|
{
|
|
6836
6886
|
if (!this.context) return;
|
|
6887
|
+
ASSERT(drawContext === this.context);
|
|
6837
6888
|
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
//debugSaveCanvas(this.canvas);
|
|
6889
|
+
if (glEnable && this.textureInfo.glTexture)
|
|
6890
|
+
glSetRenderTarget();
|
|
6841
6891
|
|
|
6842
6892
|
// set stuff back to normal
|
|
6843
|
-
[
|
|
6893
|
+
[drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
|
|
6844
6894
|
}
|
|
6845
6895
|
|
|
6846
6896
|
/** Draw the tile at a given position in the tile grid
|
|
@@ -6852,24 +6902,75 @@ class TileLayer extends CanvasLayer
|
|
|
6852
6902
|
drawTileData(layerPos, clear=true)
|
|
6853
6903
|
{
|
|
6854
6904
|
if (!this.context) return;
|
|
6905
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
6855
6906
|
|
|
6856
|
-
// clear out where the tile was, for
|
|
6857
|
-
const
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
const pos = layerPos.multiply(s);
|
|
6861
|
-
this.context.clearRect(pos.x, this.canvas.height-pos.y, s.x, -s.y);
|
|
6862
|
-
}
|
|
6907
|
+
// clear out where the tile was, can be skipped for fully opaque tiles
|
|
6908
|
+
const drawSize = this.tileInfo?.size ?? vec2(1);
|
|
6909
|
+
const drawPos = layerPos.multiply(drawSize);
|
|
6910
|
+
clear && this.clearLayerRect(drawPos, drawSize);
|
|
6863
6911
|
|
|
6864
6912
|
// draw the tile if it has layer data
|
|
6865
6913
|
const d = this.getData(layerPos);
|
|
6866
|
-
if (d.tile
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6914
|
+
if (!d.tile) return;
|
|
6915
|
+
|
|
6916
|
+
const tileInfo = this.tileInfo && this.tileInfo.tile(d.tile);
|
|
6917
|
+
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
6918
|
+
}
|
|
6919
|
+
|
|
6920
|
+
/** Draw the tile at a given position in the tile grid
|
|
6921
|
+
* This can be used to clear tiles when they are destroyed
|
|
6922
|
+
* For better performance use drawTileData inside a redrawStart/End block
|
|
6923
|
+
* @param {Vector2} layerPos
|
|
6924
|
+
* @param {boolean} [clear] - should the old tile be cleared
|
|
6925
|
+
*/
|
|
6926
|
+
redrawTileData(layerPos, clear=true)
|
|
6927
|
+
{
|
|
6928
|
+
if (!this.context) return;
|
|
6929
|
+
ASSERT(drawContext !== this.context, 'redrawStart() should not be active when calling redrawTileData(), instead use drawTileData()');
|
|
6930
|
+
|
|
6931
|
+
this.redrawStart();
|
|
6932
|
+
this.drawTileData(layerPos, clear);
|
|
6933
|
+
this.redrawEnd();
|
|
6934
|
+
}
|
|
6935
|
+
|
|
6936
|
+
/** Draw textured tile in layer space
|
|
6937
|
+
* @param {Vector2} pos - Position in pixel coordinates
|
|
6938
|
+
* @param {Vector2} [size=vec2(1)] - Size of the tile
|
|
6939
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
6940
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
6941
|
+
* @param {number} [angle] - Angle to rotate by
|
|
6942
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
6943
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any */
|
|
6944
|
+
drawLayerTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
6945
|
+
angle=0, mirror, additiveColor)
|
|
6946
|
+
{
|
|
6947
|
+
const drawPos = pos.add(size.scale(.5));
|
|
6948
|
+
drawTile(drawPos, size, tileInfo, color, angle, mirror, additiveColor);
|
|
6949
|
+
}
|
|
6950
|
+
|
|
6951
|
+
/** Clear a rectangle in layer space
|
|
6952
|
+
* @param {Vector2} pos
|
|
6953
|
+
* @param {Vector2} size
|
|
6954
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
6955
|
+
* @param {number} [angle] - Angle to rotate by
|
|
6956
|
+
*/
|
|
6957
|
+
drawLayerRect(pos, size, color, angle=0)
|
|
6958
|
+
{ this.drawLayerTile(pos, size, undefined, color, angle); }
|
|
6959
|
+
|
|
6960
|
+
/** Clear a rectangle in layer space
|
|
6961
|
+
* @param {Vector2} pos - position in pixel coordinates
|
|
6962
|
+
* @param {Vector2} size
|
|
6963
|
+
*/
|
|
6964
|
+
clearLayerRect(pos, size)
|
|
6965
|
+
{
|
|
6966
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before clearing tiles');
|
|
6967
|
+
|
|
6968
|
+
const x = pos.x, y = this.canvas.height - pos.y - size.y;
|
|
6969
|
+
const useWebGL = this.hasWebGL();
|
|
6970
|
+
if (useWebGL)
|
|
6971
|
+
glClearRect(x, y, size.x, size.y);
|
|
6972
|
+
else
|
|
6973
|
+
this.context.clearRect(x, y, size.x, size.y);
|
|
6873
6974
|
}
|
|
6874
6975
|
}
|
|
6875
6976
|
|
|
@@ -6878,7 +6979,6 @@ class TileLayer extends CanvasLayer
|
|
|
6878
6979
|
* Tile Collision Layer - a tile layer with collision
|
|
6879
6980
|
* - adds collision data and functions to TileLayer
|
|
6880
6981
|
* - there can be multiple tile collision layers
|
|
6881
|
-
* - tile collision layers should not overlap each other
|
|
6882
6982
|
* @extends TileLayer
|
|
6883
6983
|
* @memberof TileLayers
|
|
6884
6984
|
*/
|
|
@@ -6908,8 +7008,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6908
7008
|
/** Destroy this tile layer */
|
|
6909
7009
|
destroy()
|
|
6910
7010
|
{
|
|
6911
|
-
if (this.destroyed)
|
|
6912
|
-
return;
|
|
7011
|
+
if (this.destroyed) return;
|
|
6913
7012
|
|
|
6914
7013
|
// remove from collision layers array and destroy
|
|
6915
7014
|
const index = tileCollisionLayers.indexOf(this);
|
|
@@ -6951,7 +7050,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6951
7050
|
|
|
6952
7051
|
/** Check if collision with another object should occur
|
|
6953
7052
|
* @param {Vector2} pos
|
|
6954
|
-
* @param {Vector2} [size=(
|
|
7053
|
+
* @param {Vector2} [size=vec2()]
|
|
6955
7054
|
* @param {EngineObject} [object]
|
|
6956
7055
|
* @return {boolean} */
|
|
6957
7056
|
collisionTest(pos, size=new Vector2, object)
|
|
@@ -7271,7 +7370,7 @@ class ParticleEmitter extends EngineObject
|
|
|
7271
7370
|
particle.mirror = randBool();
|
|
7272
7371
|
|
|
7273
7372
|
// call particle create callback
|
|
7274
|
-
this.particleCreateCallback
|
|
7373
|
+
this.particleCreateCallback?.(particle);
|
|
7275
7374
|
|
|
7276
7375
|
// return the newly created particle
|
|
7277
7376
|
return particle;
|
|
@@ -7356,7 +7455,7 @@ class Particle extends EngineObject
|
|
|
7356
7455
|
const c = this.colorEnd;
|
|
7357
7456
|
this.color.set(c.r, c.g, c.b, c.a);
|
|
7358
7457
|
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
7359
|
-
this.destroyCallback
|
|
7458
|
+
this.destroyCallback?.(this);
|
|
7360
7459
|
this.destroyed = 1;
|
|
7361
7460
|
}
|
|
7362
7461
|
}
|
|
@@ -7446,10 +7545,11 @@ function medalsInit(saveName)
|
|
|
7446
7545
|
|
|
7447
7546
|
// engine automatically renders medals
|
|
7448
7547
|
engineAddPlugin(undefined, medalsRender);
|
|
7548
|
+
|
|
7549
|
+
// plugin functions
|
|
7449
7550
|
function medalsRender()
|
|
7450
7551
|
{
|
|
7451
|
-
if (!medalsDisplayQueue.length)
|
|
7452
|
-
return;
|
|
7552
|
+
if (!medalsDisplayQueue.length) return;
|
|
7453
7553
|
|
|
7454
7554
|
// update first medal in queue
|
|
7455
7555
|
const medal = medalsDisplayQueue[0];
|
|
@@ -7539,8 +7639,7 @@ class Medal
|
|
|
7539
7639
|
/** Unlocks a medal if not already unlocked */
|
|
7540
7640
|
unlock()
|
|
7541
7641
|
{
|
|
7542
|
-
if (medalsPreventUnlock || this.unlocked)
|
|
7543
|
-
return;
|
|
7642
|
+
if (medalsPreventUnlock || this.unlocked) return;
|
|
7544
7643
|
|
|
7545
7644
|
// save the medal
|
|
7546
7645
|
ASSERT(medalsSaveName, 'save name must be set');
|
|
@@ -7634,7 +7733,7 @@ let glContext;
|
|
|
7634
7733
|
let glAntialias = true;
|
|
7635
7734
|
|
|
7636
7735
|
// WebGL internal variables not exposed to documentation
|
|
7637
|
-
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
|
|
7736
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glInstancedVAO, glPolyVAO, glFramebuffer, glRenderTarget, glCanBeEnabled = true;
|
|
7638
7737
|
|
|
7639
7738
|
// WebGL internal constants
|
|
7640
7739
|
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
@@ -7710,7 +7809,7 @@ function glInit(rootElement)
|
|
|
7710
7809
|
// setup instanced rendering shader program
|
|
7711
7810
|
glShader = glCreateProgram(
|
|
7712
7811
|
'#version 300 es\n' + // specify GLSL ES version
|
|
7713
|
-
'precision highp float;'+ // use highp for
|
|
7812
|
+
'precision highp float;'+ // use highp for accuracy
|
|
7714
7813
|
'uniform mat4 m;'+ // transform matrix
|
|
7715
7814
|
'in vec2 g;'+ // in: geometry
|
|
7716
7815
|
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
@@ -7725,7 +7824,7 @@ function glInit(rootElement)
|
|
|
7725
7824
|
'}' // end of shader
|
|
7726
7825
|
,
|
|
7727
7826
|
'#version 300 es\n' + // specify GLSL ES version
|
|
7728
|
-
'precision highp float;'+ // use highp for
|
|
7827
|
+
'precision highp float;'+ // use highp for accuracy
|
|
7729
7828
|
'uniform sampler2D s;'+ // texture
|
|
7730
7829
|
'in vec2 v;'+ // in: uv
|
|
7731
7830
|
'in vec4 d,e;'+ // in: color, additiveColor
|
|
@@ -7763,87 +7862,96 @@ function glInit(rootElement)
|
|
|
7763
7862
|
glColorData = new Uint32Array(glInstanceData);
|
|
7764
7863
|
glArrayBuffer = glContext.createBuffer();
|
|
7765
7864
|
glGeometryBuffer = glContext.createBuffer();
|
|
7865
|
+
glFramebuffer = glContext.createFramebuffer();
|
|
7866
|
+
glBatchCount = 0;
|
|
7766
7867
|
|
|
7767
7868
|
// create the geometry buffer, triangle strip square
|
|
7768
|
-
const geometry = new Float32Array([
|
|
7869
|
+
const geometry = new Float32Array([0,0,1,0,0,1,1,1]);
|
|
7769
7870
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7770
7871
|
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
7872
|
+
|
|
7873
|
+
let offset, shader, stride;
|
|
7874
|
+
const initVertexAttrib = (name, type, typeSize, size, divisor=0)=>
|
|
7875
|
+
{
|
|
7876
|
+
const location = glContext.getAttribLocation(shader, name);
|
|
7877
|
+
const normalize = typeSize === 1;
|
|
7878
|
+
const fixedStride = typeSize && stride;
|
|
7879
|
+
glContext.enableVertexAttribArray(location);
|
|
7880
|
+
glContext.vertexAttribPointer(location, size, type, normalize, fixedStride, offset);
|
|
7881
|
+
glContext.vertexAttribDivisor(location, divisor);
|
|
7882
|
+
offset += size*typeSize;
|
|
7883
|
+
}
|
|
7884
|
+
|
|
7885
|
+
// setup VAO for instanced rendering
|
|
7886
|
+
glInstancedVAO = glContext.createVertexArray();
|
|
7887
|
+
glContext.bindVertexArray(glInstancedVAO);
|
|
7888
|
+
|
|
7889
|
+
// configure instanced vertex attributes
|
|
7890
|
+
offset = 0, shader = glShader, stride = gl_INSTANCE_BYTE_STRIDE;
|
|
7891
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7892
|
+
initVertexAttrib('g', glContext.FLOAT, 0, 2); // geometry
|
|
7893
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7894
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7895
|
+
initVertexAttrib('p', glContext.FLOAT, 4, 4, 1); // position & size
|
|
7896
|
+
initVertexAttrib('u', glContext.FLOAT, 4, 4, 1); // texture coords
|
|
7897
|
+
initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4, 1); // color
|
|
7898
|
+
initVertexAttrib('a', glContext.UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
7899
|
+
initVertexAttrib('r', glContext.FLOAT, 4, 1, 1); // rotation
|
|
7900
|
+
|
|
7901
|
+
// setup VAO for poly rendering
|
|
7902
|
+
glPolyVAO = glContext.createVertexArray();
|
|
7903
|
+
glContext.bindVertexArray(glPolyVAO);
|
|
7904
|
+
|
|
7905
|
+
// configure poly vertex attributes
|
|
7906
|
+
offset = 0, shader = glPolyShader, stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
7907
|
+
initVertexAttrib('p', glContext.FLOAT, 4, 2); // position
|
|
7908
|
+
initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7771
7909
|
}
|
|
7772
7910
|
}
|
|
7773
7911
|
|
|
7774
|
-
function glSetInstancedMode()
|
|
7912
|
+
function glSetInstancedMode(force=false)
|
|
7775
7913
|
{
|
|
7776
|
-
if (!glPolyMode)
|
|
7777
|
-
return;
|
|
7914
|
+
if (!force && !glPolyMode) return;
|
|
7778
7915
|
|
|
7779
7916
|
// setup instanced mode
|
|
7780
7917
|
glFlush();
|
|
7781
7918
|
glPolyMode = false;
|
|
7782
7919
|
glContext.useProgram(glShader);
|
|
7783
|
-
|
|
7784
|
-
// set vertex attributes
|
|
7785
|
-
let offset = 0;
|
|
7786
|
-
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
7787
|
-
{
|
|
7788
|
-
const location = glContext.getAttribLocation(glShader, name);
|
|
7789
|
-
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
7790
|
-
const divisor = typeSize && 1; // only if not geometry
|
|
7791
|
-
const normalize = typeSize === 1; // only if color
|
|
7792
|
-
glContext.enableVertexAttribArray(location);
|
|
7793
|
-
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
7794
|
-
glContext.vertexAttribDivisor(location, divisor);
|
|
7795
|
-
offset += size*typeSize;
|
|
7796
|
-
}
|
|
7797
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7798
|
-
initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
|
|
7799
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7800
|
-
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7801
|
-
initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
|
|
7802
|
-
initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
|
|
7803
|
-
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7804
|
-
initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
|
|
7805
|
-
initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
|
|
7920
|
+
glContext.bindVertexArray(glInstancedVAO);
|
|
7806
7921
|
}
|
|
7807
7922
|
|
|
7808
7923
|
function glSetPolyMode()
|
|
7809
7924
|
{
|
|
7810
|
-
if (glPolyMode)
|
|
7811
|
-
return;
|
|
7925
|
+
if (glPolyMode) return;
|
|
7812
7926
|
|
|
7813
7927
|
// setup poly mode
|
|
7814
7928
|
glFlush();
|
|
7815
7929
|
glPolyMode = true;
|
|
7816
7930
|
glContext.useProgram(glPolyShader);
|
|
7817
|
-
|
|
7818
|
-
// set vertex attributes
|
|
7819
|
-
let offset = 0;
|
|
7820
|
-
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
7821
|
-
{
|
|
7822
|
-
const location = glContext.getAttribLocation(glPolyShader, name);
|
|
7823
|
-
const normalize = typeSize === 1; // only normalize if color
|
|
7824
|
-
const stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
7825
|
-
glContext.enableVertexAttribArray(location);
|
|
7826
|
-
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
7827
|
-
glContext.vertexAttribDivisor(location, 0);
|
|
7828
|
-
offset += size*typeSize;
|
|
7829
|
-
}
|
|
7830
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7831
|
-
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7832
|
-
initVertexAttribArray('p', glContext.FLOAT, 4, 2); // position
|
|
7833
|
-
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7931
|
+
glContext.bindVertexArray(glPolyVAO);
|
|
7834
7932
|
}
|
|
7835
7933
|
|
|
7836
7934
|
// Setup WebGL render each frame, called automatically by engine
|
|
7837
7935
|
// Also used by tile layer rendering when redrawing tiles
|
|
7838
|
-
function glPreRender()
|
|
7936
|
+
function glPreRender(clear=true)
|
|
7839
7937
|
{
|
|
7840
7938
|
if (!glEnable || !glContext) return;
|
|
7841
7939
|
|
|
7842
|
-
|
|
7843
|
-
|
|
7940
|
+
ASSERT(!glBatchCount, 'glPreRender called with unflushed batch.');
|
|
7941
|
+
|
|
7942
|
+
if (!glRenderTarget)
|
|
7943
|
+
{
|
|
7944
|
+
// set to same size as main canvas
|
|
7945
|
+
glCanvas.width = mainCanvasSize.x;
|
|
7946
|
+
glCanvas.height = mainCanvasSize.y;
|
|
7947
|
+
}
|
|
7948
|
+
glContext.viewport(0, 0, mainCanvasSize.x, mainCanvasSize.y);
|
|
7949
|
+
clear && glClearCanvas();
|
|
7844
7950
|
|
|
7845
7951
|
// build the transform matrix
|
|
7846
7952
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
7953
|
+
if (glRenderTarget)
|
|
7954
|
+
s.y = -s.y; // invert y when using render target
|
|
7847
7955
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
7848
7956
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
7849
7957
|
const ca = cos(cameraAngle);
|
|
@@ -7872,12 +7980,14 @@ function glPreRender()
|
|
|
7872
7980
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7873
7981
|
}
|
|
7874
7982
|
|
|
7983
|
+
// rebind the array buffer
|
|
7984
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7985
|
+
|
|
7875
7986
|
// start with additive blending off
|
|
7876
7987
|
glAdditive = glBatchAdditive = false;
|
|
7877
7988
|
|
|
7878
|
-
// force it to set instanced mode
|
|
7879
|
-
|
|
7880
|
-
glSetInstancedMode();
|
|
7989
|
+
// force it to set instanced mode
|
|
7990
|
+
glSetInstancedMode(true);
|
|
7881
7991
|
}
|
|
7882
7992
|
|
|
7883
7993
|
/** Clear the canvas and setup the viewport
|
|
@@ -7886,13 +7996,9 @@ function glClearCanvas()
|
|
|
7886
7996
|
{
|
|
7887
7997
|
if (!glContext) return;
|
|
7888
7998
|
|
|
7889
|
-
// clear
|
|
7890
|
-
glCanvas.width = drawCanvas.width;
|
|
7891
|
-
glCanvas.height = drawCanvas.height;
|
|
7892
|
-
glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
|
|
7999
|
+
// clear using the canvasClearColor
|
|
7893
8000
|
const color = canvasClearColor;
|
|
7894
|
-
|
|
7895
|
-
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
8001
|
+
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
7896
8002
|
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
7897
8003
|
}
|
|
7898
8004
|
|
|
@@ -7904,8 +8010,7 @@ function glClearCanvas()
|
|
|
7904
8010
|
function glSetTexture(texture, wrap=false)
|
|
7905
8011
|
{
|
|
7906
8012
|
// must flush cache with the old texture to set a new one
|
|
7907
|
-
if (!glContext || texture === glActiveTexture)
|
|
7908
|
-
return;
|
|
8013
|
+
if (!glContext || texture === glActiveTexture) return;
|
|
7909
8014
|
|
|
7910
8015
|
glFlush();
|
|
7911
8016
|
glActiveTexture = texture;
|
|
@@ -7970,7 +8075,7 @@ function glCreateTexture(image)
|
|
|
7970
8075
|
// build the texture
|
|
7971
8076
|
const texture = glContext.createTexture();
|
|
7972
8077
|
let mipMap = false;
|
|
7973
|
-
if (image
|
|
8078
|
+
if (image?.width)
|
|
7974
8079
|
{
|
|
7975
8080
|
glSetTextureData(texture, image);
|
|
7976
8081
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
@@ -7991,7 +8096,9 @@ function glCreateTexture(image)
|
|
|
7991
8096
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, minFilter);
|
|
7992
8097
|
if (mipMap)
|
|
7993
8098
|
glContext.generateMipmap(glContext.TEXTURE_2D);
|
|
7994
|
-
|
|
8099
|
+
|
|
8100
|
+
// rebind active texture
|
|
8101
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7995
8102
|
return texture;
|
|
7996
8103
|
}
|
|
7997
8104
|
|
|
@@ -8001,6 +8108,7 @@ function glCreateTexture(image)
|
|
|
8001
8108
|
function glDeleteTexture(texture)
|
|
8002
8109
|
{
|
|
8003
8110
|
if (!glContext) return;
|
|
8111
|
+
|
|
8004
8112
|
glContext.deleteTexture(texture);
|
|
8005
8113
|
}
|
|
8006
8114
|
|
|
@@ -8013,10 +8121,12 @@ function glSetTextureData(texture, image)
|
|
|
8013
8121
|
if (!glContext) return;
|
|
8014
8122
|
|
|
8015
8123
|
// build the texture
|
|
8016
|
-
ASSERT(
|
|
8124
|
+
ASSERT(image?.width > 0, 'Invalid image data.');
|
|
8017
8125
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
8018
8126
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
8019
|
-
|
|
8127
|
+
|
|
8128
|
+
// rebind active texture
|
|
8129
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
8020
8130
|
}
|
|
8021
8131
|
|
|
8022
8132
|
/** Tells WebGL to create or update the glTexture and start tracking it
|
|
@@ -8085,8 +8195,7 @@ function glFlush()
|
|
|
8085
8195
|
* @memberof WebGL */
|
|
8086
8196
|
function glCopyToContext(context)
|
|
8087
8197
|
{
|
|
8088
|
-
if (!glEnable || !glContext)
|
|
8089
|
-
return;
|
|
8198
|
+
if (!glEnable || !glContext) return;
|
|
8090
8199
|
|
|
8091
8200
|
glFlush();
|
|
8092
8201
|
context.drawImage(glCanvas, 0, 0);
|
|
@@ -8236,6 +8345,48 @@ function glDrawColoredPoints(points, pointColors)
|
|
|
8236
8345
|
glBatchCount += vertCount;
|
|
8237
8346
|
}
|
|
8238
8347
|
|
|
8348
|
+
/** Set the WebGL render target to the given texture or back to the canvas
|
|
8349
|
+
* @param {WebGLTexture} [texture] - a texture or undefined to use normal glCanvas
|
|
8350
|
+
* @param {boolean} [clear] - should the render target be cleared
|
|
8351
|
+
* @memberof WebGL */
|
|
8352
|
+
function glSetRenderTarget(texture, clear=false)
|
|
8353
|
+
{
|
|
8354
|
+
if (texture)
|
|
8355
|
+
{
|
|
8356
|
+
glRenderTarget = texture;
|
|
8357
|
+
glContext.bindFramebuffer(glContext.FRAMEBUFFER, glFramebuffer);
|
|
8358
|
+
glContext.framebufferTexture2D(glContext.FRAMEBUFFER,
|
|
8359
|
+
glContext.COLOR_ATTACHMENT0, glContext.TEXTURE_2D, texture, 0);
|
|
8360
|
+
glPreRender(clear);
|
|
8361
|
+
}
|
|
8362
|
+
else
|
|
8363
|
+
{
|
|
8364
|
+
glFlush();
|
|
8365
|
+
glRenderTarget = undefined;
|
|
8366
|
+
glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
|
|
8367
|
+
}
|
|
8368
|
+
}
|
|
8369
|
+
|
|
8370
|
+
/** Clear out a rectangle area of the WebGL canvas or render target
|
|
8371
|
+
* @param {number} x
|
|
8372
|
+
* @param {number} y
|
|
8373
|
+
* @param {number} width
|
|
8374
|
+
* @param {number} height
|
|
8375
|
+
* @memberof WebGL */
|
|
8376
|
+
function glClearRect(x, y, width, height)
|
|
8377
|
+
{
|
|
8378
|
+
if (!glEnable) return;
|
|
8379
|
+
|
|
8380
|
+
// Enable scissor test to clear only the specified area
|
|
8381
|
+
glContext.enable(glContext.SCISSOR_TEST);
|
|
8382
|
+
glContext.scissor(x, y, width, height);
|
|
8383
|
+
glContext.clearColor(0, 0, 0, 0);
|
|
8384
|
+
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
8385
|
+
glContext.disable(glContext.SCISSOR_TEST);
|
|
8386
|
+
}
|
|
8387
|
+
|
|
8388
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8389
|
+
|
|
8239
8390
|
// WebGL internal function to convert polygon to outline triangle strip
|
|
8240
8391
|
function glMakeOutline(points, width, wrap=true)
|
|
8241
8392
|
{
|
|
@@ -8369,23 +8520,20 @@ function glPolyStrip(points)
|
|
|
8369
8520
|
const a = points[i0], b = points[i1], c = points[i2];
|
|
8370
8521
|
|
|
8371
8522
|
// check if convex
|
|
8372
|
-
if (cross(a, b, c) < e)
|
|
8373
|
-
continue;
|
|
8523
|
+
if (cross(a, b, c) < e) continue;
|
|
8374
8524
|
|
|
8375
8525
|
// check if any other point is inside
|
|
8376
8526
|
let hasInside = false;
|
|
8377
8527
|
for (let j = 0; j < indices.length; j++)
|
|
8378
8528
|
{
|
|
8379
8529
|
const k = indices[j];
|
|
8380
|
-
if (k === i0 || k === i1 || k === i2)
|
|
8381
|
-
|
|
8530
|
+
if (k === i0 || k === i1 || k === i2) continue;
|
|
8531
|
+
|
|
8382
8532
|
const p = points[k];
|
|
8383
8533
|
hasInside = pointInTriangle(p, a, b, c);
|
|
8384
|
-
if (hasInside)
|
|
8385
|
-
break;
|
|
8534
|
+
if (hasInside) break;
|
|
8386
8535
|
}
|
|
8387
|
-
if (hasInside)
|
|
8388
|
-
continue;
|
|
8536
|
+
if (hasInside) continue;
|
|
8389
8537
|
|
|
8390
8538
|
// found valid ear
|
|
8391
8539
|
triangles.push([i0, i1, i2]);
|
|
@@ -8410,8 +8558,7 @@ function glPolyStrip(points)
|
|
|
8410
8558
|
worstIndex = i;
|
|
8411
8559
|
}
|
|
8412
8560
|
}
|
|
8413
|
-
if (worstIndex < 0)
|
|
8414
|
-
break;
|
|
8561
|
+
if (worstIndex < 0) break;
|
|
8415
8562
|
|
|
8416
8563
|
const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
|
|
8417
8564
|
const i1 = indices[worstIndex];
|
|
@@ -8642,14 +8789,16 @@ class PostProcessPlugin
|
|
|
8642
8789
|
{
|
|
8643
8790
|
/** Create global post processing shader
|
|
8644
8791
|
* @param {string} shaderCode
|
|
8645
|
-
* @param {boolean} [includeMainCanvas]
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8792
|
+
* @param {boolean} [includeMainCanvas] - combine mainCanvs onto glCanvas
|
|
8793
|
+
* @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
|
|
8794
|
+
* @example
|
|
8795
|
+
* // create the post process plugin object
|
|
8796
|
+
* new PostProcessPlugin(shaderCode);
|
|
8797
|
+
*/
|
|
8798
|
+
constructor(shaderCode, includeMainCanvas=false, feedbackTexture=false)
|
|
8651
8799
|
{
|
|
8652
8800
|
ASSERT(!postProcess, 'Post process already initialized');
|
|
8801
|
+
ASSERT(!(includeMainCanvas && feedbackTexture), 'Post process cannot both include main canvas and use feedback texture');
|
|
8653
8802
|
postProcess = this;
|
|
8654
8803
|
|
|
8655
8804
|
if (!shaderCode) // default shader pass through
|
|
@@ -8657,9 +8806,10 @@ class PostProcessPlugin
|
|
|
8657
8806
|
|
|
8658
8807
|
/** @property {WebGLProgram} - Shader for post processing */
|
|
8659
8808
|
this.shader = undefined;
|
|
8660
|
-
|
|
8661
8809
|
/** @property {WebGLTexture} - Texture for post processing */
|
|
8662
8810
|
this.texture = undefined;
|
|
8811
|
+
/** @property {WebGLVertexArrayObject} - Vertex array object */
|
|
8812
|
+
this.vao = undefined;
|
|
8663
8813
|
|
|
8664
8814
|
// setup the post processing plugin
|
|
8665
8815
|
initPostProcess();
|
|
@@ -8668,7 +8818,6 @@ class PostProcessPlugin
|
|
|
8668
8818
|
function initPostProcess()
|
|
8669
8819
|
{
|
|
8670
8820
|
if (headlessMode) return;
|
|
8671
|
-
|
|
8672
8821
|
if (!glEnable)
|
|
8673
8822
|
{
|
|
8674
8823
|
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
@@ -8679,14 +8828,14 @@ class PostProcessPlugin
|
|
|
8679
8828
|
postProcess.texture = glCreateTexture();
|
|
8680
8829
|
postProcess.shader = glCreateProgram(
|
|
8681
8830
|
'#version 300 es\n' + // specify GLSL ES version
|
|
8682
|
-
'precision highp float;'+ // use highp for
|
|
8831
|
+
'precision highp float;'+ // use highp for accuracy
|
|
8683
8832
|
'in vec2 p;'+ // position
|
|
8684
8833
|
'void main(){'+ // shader entry point
|
|
8685
8834
|
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
8686
8835
|
'}' // end of shader
|
|
8687
8836
|
,
|
|
8688
8837
|
'#version 300 es\n' + // specify GLSL ES version
|
|
8689
|
-
'precision highp float;'+ // use highp for
|
|
8838
|
+
'precision highp float;'+ // use highp for accuracy
|
|
8690
8839
|
'uniform sampler2D iChannel0;'+ // input texture
|
|
8691
8840
|
'uniform vec3 iResolution;'+ // size of output texture
|
|
8692
8841
|
'uniform float iTime;'+ // time
|
|
@@ -8697,6 +8846,17 @@ class PostProcessPlugin
|
|
|
8697
8846
|
'c.a=1.;'+ // always use full alpha
|
|
8698
8847
|
'}' // end of shader
|
|
8699
8848
|
);
|
|
8849
|
+
|
|
8850
|
+
// setup VAO for post processing
|
|
8851
|
+
postProcess.vao = glContext.createVertexArray();
|
|
8852
|
+
glContext.bindVertexArray(postProcess.vao);
|
|
8853
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
8854
|
+
|
|
8855
|
+
// configure vertex attributes
|
|
8856
|
+
const vertexByteStride = 8;
|
|
8857
|
+
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
8858
|
+
glContext.enableVertexAttribArray(pLocation);
|
|
8859
|
+
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
8700
8860
|
}
|
|
8701
8861
|
function postProcessContextLost()
|
|
8702
8862
|
{
|
|
@@ -8711,14 +8871,17 @@ class PostProcessPlugin
|
|
|
8711
8871
|
}
|
|
8712
8872
|
function postProcessRender()
|
|
8713
8873
|
{
|
|
8714
|
-
if (headlessMode) return;
|
|
8715
|
-
|
|
8716
|
-
if (!glEnable)
|
|
8717
|
-
return;
|
|
8874
|
+
if (headlessMode || !glEnable) return;
|
|
8718
8875
|
|
|
8719
8876
|
// clear out the buffer
|
|
8720
8877
|
glFlush();
|
|
8721
|
-
|
|
8878
|
+
|
|
8879
|
+
// setup shader program to draw a quad
|
|
8880
|
+
glContext.useProgram(postProcess.shader);
|
|
8881
|
+
glContext.bindVertexArray(postProcess.vao);
|
|
8882
|
+
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, true);
|
|
8883
|
+
glContext.disable(glContext.BLEND);
|
|
8884
|
+
|
|
8722
8885
|
// setup texture
|
|
8723
8886
|
glContext.activeTexture(glContext.TEXTURE0);
|
|
8724
8887
|
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
@@ -8729,29 +8892,32 @@ class PostProcessPlugin
|
|
|
8729
8892
|
workCanvas.height = mainCanvasSize.y;
|
|
8730
8893
|
glCopyToContext(workContext);
|
|
8731
8894
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
8895
|
+
mainCanvas.width |= 0
|
|
8732
8896
|
|
|
8733
8897
|
// copy work canvas to texture
|
|
8734
8898
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
|
|
8735
8899
|
}
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
// set vertex position attribute
|
|
8744
|
-
const vertexByteStride = 8;
|
|
8745
|
-
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
8746
|
-
glContext.enableVertexAttribArray(pLocation);
|
|
8747
|
-
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
8748
|
-
|
|
8900
|
+
else if (!feedbackTexture)
|
|
8901
|
+
{
|
|
8902
|
+
// copy glCanvas to texture
|
|
8903
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
|
|
8904
|
+
}
|
|
8905
|
+
|
|
8749
8906
|
// set uniforms and draw
|
|
8750
8907
|
const uniformLocation = (name)=>glContext.getUniformLocation(postProcess.shader, name);
|
|
8751
8908
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
8752
8909
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
8753
8910
|
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
8754
8911
|
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
|
|
8912
|
+
|
|
8913
|
+
if (feedbackTexture)
|
|
8914
|
+
{
|
|
8915
|
+
// pass glCanvas back to overlay texture
|
|
8916
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
|
|
8917
|
+
}
|
|
8918
|
+
|
|
8919
|
+
// force it to set instanced mode
|
|
8920
|
+
glSetInstancedMode(true);
|
|
8755
8921
|
}
|
|
8756
8922
|
}
|
|
8757
8923
|
}
|
|
@@ -9432,7 +9598,7 @@ class UISystemPlugin
|
|
|
9432
9598
|
const up = 'ArrowUp', down = 'ArrowDown', left = 'ArrowLeft', right = 'ArrowRight';
|
|
9433
9599
|
if (both)
|
|
9434
9600
|
{
|
|
9435
|
-
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
9601
|
+
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
9436
9602
|
keyIsDown(down) || keyIsDown(right) ? 1 : 0;
|
|
9437
9603
|
}
|
|
9438
9604
|
const back = vertical ? up : left;
|
|
@@ -9463,7 +9629,7 @@ class UISystemPlugin
|
|
|
9463
9629
|
* @return {boolean} */
|
|
9464
9630
|
getNavigationWasPressed()
|
|
9465
9631
|
{
|
|
9466
|
-
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
9632
|
+
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
9467
9633
|
keyWasPressed('Space') || keyWasPressed('Enter');
|
|
9468
9634
|
}
|
|
9469
9635
|
|
|
@@ -9511,7 +9677,7 @@ class UISystemPlugin
|
|
|
9511
9677
|
buttonYes.textHeight = 40;
|
|
9512
9678
|
buttonYes.navigationIndex = 1;
|
|
9513
9679
|
buttonYes.hoverColor = hsl(0,1,.5);
|
|
9514
|
-
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
9680
|
+
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
9515
9681
|
confirmMenu.addChild(buttonYes);
|
|
9516
9682
|
|
|
9517
9683
|
// no button
|
|
@@ -9541,8 +9707,8 @@ class UISystemPlugin
|
|
|
9541
9707
|
class UIObject
|
|
9542
9708
|
{
|
|
9543
9709
|
/** Create a UIObject
|
|
9544
|
-
* @param {Vector2} [pos=(
|
|
9545
|
-
* @param {Vector2} [size=(1
|
|
9710
|
+
* @param {Vector2} [pos=vec2()]
|
|
9711
|
+
* @param {Vector2} [size=vec2(1)]
|
|
9546
9712
|
*/
|
|
9547
9713
|
constructor(pos=vec2(), size=vec2())
|
|
9548
9714
|
{
|
|
@@ -9557,7 +9723,7 @@ class UIObject
|
|
|
9557
9723
|
this.size = size.copy();
|
|
9558
9724
|
/** @property {Color} - Color of the object */
|
|
9559
9725
|
this.color = uiSystem.defaultColor.copy();
|
|
9560
|
-
/** @property {Color} - Color of the object when active, uses
|
|
9726
|
+
/** @property {Color} - Color of the object when active, uses hoverColor if undefined */
|
|
9561
9727
|
this.activeColor = undefined;
|
|
9562
9728
|
/** @property {string} - Text for this ui object */
|
|
9563
9729
|
this.text = undefined;
|
|
@@ -9655,10 +9821,10 @@ class UIObject
|
|
|
9655
9821
|
|
|
9656
9822
|
// disconnect from parent and destroy children
|
|
9657
9823
|
this.destroyed = 1;
|
|
9658
|
-
this.parent
|
|
9824
|
+
this.parent?.removeChild(this);
|
|
9659
9825
|
for (const child of this.children)
|
|
9660
9826
|
{
|
|
9661
|
-
child.parent =
|
|
9827
|
+
child.parent = undefined;
|
|
9662
9828
|
child.destroy();
|
|
9663
9829
|
}
|
|
9664
9830
|
}
|
|
@@ -9682,7 +9848,7 @@ class UIObject
|
|
|
9682
9848
|
this.onUpdate();
|
|
9683
9849
|
|
|
9684
9850
|
// unset active if disabled
|
|
9685
|
-
if (this.disabled && this
|
|
9851
|
+
if (this.disabled && this === uiSystem.activeObject)
|
|
9686
9852
|
uiSystem.activeObject = undefined;
|
|
9687
9853
|
|
|
9688
9854
|
const wasHover = uiSystem.lastHoverObject === this;
|
|
@@ -9754,10 +9920,10 @@ class UIObject
|
|
|
9754
9920
|
this.interactive && this.isActiveObject() && !this.disabled ?
|
|
9755
9921
|
this.color : this.lineColor;
|
|
9756
9922
|
const color = isNavigationObject ? this.hoverColor :
|
|
9757
|
-
this.disabled ? this.disabledColor :
|
|
9758
|
-
this.interactive ?
|
|
9759
|
-
this.
|
|
9760
|
-
this.
|
|
9923
|
+
this.disabled ? this.disabledColor :
|
|
9924
|
+
this.interactive ?
|
|
9925
|
+
this.isActiveObject() ? this.activeColor || this.hoverColor :
|
|
9926
|
+
this.isHoverObject() ? this.hoverColor :
|
|
9761
9927
|
this.color : this.color;
|
|
9762
9928
|
const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
|
|
9763
9929
|
|
|
@@ -9769,7 +9935,7 @@ class UIObject
|
|
|
9769
9935
|
getTextSize()
|
|
9770
9936
|
{
|
|
9771
9937
|
return vec2(
|
|
9772
|
-
this.textWidth || this.textFitScale * this.size.x,
|
|
9938
|
+
this.textWidth || this.textFitScale * this.size.x,
|
|
9773
9939
|
this.textHeight || this.textFitScale * this.size.y);
|
|
9774
9940
|
}
|
|
9775
9941
|
|
|
@@ -9817,9 +9983,9 @@ class UIObject
|
|
|
9817
9983
|
renderDebug(visible=true)
|
|
9818
9984
|
{
|
|
9819
9985
|
// apply color based on state
|
|
9820
|
-
const color =
|
|
9986
|
+
const color =
|
|
9821
9987
|
!visible ? GREEN :
|
|
9822
|
-
this.isHoverObject() ? YELLOW :
|
|
9988
|
+
this.isHoverObject() ? YELLOW :
|
|
9823
9989
|
this.disabled ? PURPLE :
|
|
9824
9990
|
this.interactive ? RED : BLUE;
|
|
9825
9991
|
uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
|