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.release.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.
|
|
36
|
+
const engineVersion = '1.17.1';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -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 - Release Mode
|
|
@@ -762,6 +750,7 @@ function LOG (){}
|
|
|
762
750
|
function debugInit (){}
|
|
763
751
|
function debugUpdate (){}
|
|
764
752
|
function debugRender (){}
|
|
753
|
+
function debugRenderPost (){}
|
|
765
754
|
function debugRect (){}
|
|
766
755
|
function debugPoly (){}
|
|
767
756
|
function debugCircle (){}
|
|
@@ -771,9 +760,6 @@ function debugOverlap (){}
|
|
|
771
760
|
function debugText (){}
|
|
772
761
|
function debugClear (){}
|
|
773
762
|
function debugScreenshot (){}
|
|
774
|
-
function debugSaveCanvas (){}
|
|
775
|
-
function debugSaveText (){}
|
|
776
|
-
function debugSaveDataURL(){}
|
|
777
763
|
function debugShowErrors(){}
|
|
778
764
|
function debugVideoCaptureIsActive(){ return false; }
|
|
779
765
|
function debugVideoCaptureStart (){}
|
|
@@ -781,105 +767,104 @@ function debugVideoCaptureStop (){}
|
|
|
781
767
|
function debugVideoCaptureUpdate(){}
|
|
782
768
|
function debugProtectConstant(o){ return o; }
|
|
783
769
|
/**
|
|
784
|
-
* LittleJS
|
|
770
|
+
* LittleJS Math Classes and Functions
|
|
785
771
|
* - General purpose math library
|
|
786
|
-
* - Vector2 - fast, simple, easy 2D vector class
|
|
787
|
-
* - Color - holds a rgba color with some math functions
|
|
788
|
-
* - Timer - tracks time automatically
|
|
789
772
|
* - RandomGenerator - seeded random number generator
|
|
790
|
-
*
|
|
773
|
+
* - Vector2 - fast, simple, easy 2D vector class
|
|
774
|
+
* - Color - holds a rgba color with math functions
|
|
775
|
+
* @namespace Math
|
|
791
776
|
*/
|
|
792
777
|
|
|
793
778
|
/** The value of PI
|
|
794
779
|
* @type {number}
|
|
795
780
|
* @default Math.PI
|
|
796
|
-
* @memberof
|
|
781
|
+
* @memberof Math */
|
|
797
782
|
const PI = Math.PI;
|
|
798
783
|
|
|
799
784
|
/** Returns absolute value of value passed in
|
|
800
785
|
* @param {number} value
|
|
801
786
|
* @return {number}
|
|
802
|
-
* @memberof
|
|
787
|
+
* @memberof Math */
|
|
803
788
|
const abs = Math.abs;
|
|
804
789
|
|
|
805
790
|
/** Returns floored value of value passed in
|
|
806
791
|
* @param {number} value
|
|
807
792
|
* @return {number}
|
|
808
|
-
* @memberof
|
|
793
|
+
* @memberof Math */
|
|
809
794
|
const floor = Math.floor;
|
|
810
795
|
|
|
811
796
|
/** Returns ceiled value of value passed in
|
|
812
797
|
* @param {number} value
|
|
813
798
|
* @return {number}
|
|
814
|
-
* @memberof
|
|
799
|
+
* @memberof Math */
|
|
815
800
|
const ceil = Math.ceil;
|
|
816
801
|
|
|
817
802
|
/** Returns rounded value passed in
|
|
818
803
|
* @param {number} value
|
|
819
804
|
* @return {number}
|
|
820
|
-
* @memberof
|
|
805
|
+
* @memberof Math */
|
|
821
806
|
const round = Math.round;
|
|
822
807
|
|
|
823
808
|
/** Returns lowest value passed in
|
|
824
809
|
* @param {...number} values
|
|
825
810
|
* @return {number}
|
|
826
|
-
* @memberof
|
|
811
|
+
* @memberof Math */
|
|
827
812
|
const min = Math.min;
|
|
828
813
|
|
|
829
814
|
/** Returns highest value passed in
|
|
830
815
|
* @param {...number} values
|
|
831
816
|
* @return {number}
|
|
832
|
-
* @memberof
|
|
817
|
+
* @memberof Math */
|
|
833
818
|
const max = Math.max;
|
|
834
819
|
|
|
835
820
|
/** Returns the sign of value passed in
|
|
836
821
|
* @param {number} value
|
|
837
822
|
* @return {number}
|
|
838
|
-
* @memberof
|
|
823
|
+
* @memberof Math */
|
|
839
824
|
const sign = Math.sign;
|
|
840
825
|
|
|
841
826
|
/** Returns hypotenuse of values passed in
|
|
842
827
|
* @param {...number} values
|
|
843
828
|
* @return {number}
|
|
844
|
-
* @memberof
|
|
829
|
+
* @memberof Math */
|
|
845
830
|
const hypot = Math.hypot;
|
|
846
831
|
|
|
847
832
|
/** Returns log2 of value passed in
|
|
848
833
|
* @param {number} value
|
|
849
834
|
* @return {number}
|
|
850
|
-
* @memberof
|
|
835
|
+
* @memberof Math */
|
|
851
836
|
const log2 = Math.log2;
|
|
852
837
|
|
|
853
838
|
/** Returns sin of value passed in
|
|
854
839
|
* @param {number} value
|
|
855
840
|
* @return {number}
|
|
856
|
-
* @memberof
|
|
841
|
+
* @memberof Math */
|
|
857
842
|
const sin = Math.sin;
|
|
858
843
|
|
|
859
844
|
/** Returns cos of value passed in
|
|
860
845
|
* @param {number} value
|
|
861
846
|
* @return {number}
|
|
862
|
-
* @memberof
|
|
847
|
+
* @memberof Math */
|
|
863
848
|
const cos = Math.cos;
|
|
864
849
|
|
|
865
850
|
/** Returns tan of value passed in
|
|
866
851
|
* @param {number} value
|
|
867
852
|
* @return {number}
|
|
868
|
-
* @memberof
|
|
853
|
+
* @memberof Math */
|
|
869
854
|
const tan = Math.tan;
|
|
870
855
|
|
|
871
856
|
/** Returns atan2 of values passed in
|
|
872
857
|
* @param {number} y
|
|
873
858
|
* @param {number} x
|
|
874
859
|
* @return {number}
|
|
875
|
-
* @memberof
|
|
860
|
+
* @memberof Math */
|
|
876
861
|
const atan2 = Math.atan2;
|
|
877
862
|
|
|
878
863
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
879
864
|
* @param {number} dividend
|
|
880
865
|
* @param {number} [divisor]
|
|
881
866
|
* @return {number}
|
|
882
|
-
* @memberof
|
|
867
|
+
* @memberof Math */
|
|
883
868
|
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
884
869
|
|
|
885
870
|
/** Clamps the value between max and min
|
|
@@ -887,7 +872,7 @@ function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % di
|
|
|
887
872
|
* @param {number} [min]
|
|
888
873
|
* @param {number} [max]
|
|
889
874
|
* @return {number}
|
|
890
|
-
* @memberof
|
|
875
|
+
* @memberof Math */
|
|
891
876
|
function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
|
|
892
877
|
|
|
893
878
|
/** Returns what percentage the value is between valueA and valueB
|
|
@@ -895,7 +880,7 @@ function clamp(value, min=0, max=1) { return value < min ? min : value > max ? m
|
|
|
895
880
|
* @param {number} valueA
|
|
896
881
|
* @param {number} valueB
|
|
897
882
|
* @return {number}
|
|
898
|
-
* @memberof
|
|
883
|
+
* @memberof Math */
|
|
899
884
|
function percent(value, valueA, valueB)
|
|
900
885
|
{ return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
|
|
901
886
|
|
|
@@ -904,7 +889,7 @@ function percent(value, valueA, valueB)
|
|
|
904
889
|
* @param {number} valueB
|
|
905
890
|
* @param {number} percent
|
|
906
891
|
* @return {number}
|
|
907
|
-
* @memberof
|
|
892
|
+
* @memberof Math */
|
|
908
893
|
function lerp(valueA, valueB, percent)
|
|
909
894
|
{ return valueA + clamp(percent) * (valueB-valueA); }
|
|
910
895
|
|
|
@@ -916,7 +901,7 @@ function lerp(valueA, valueB, percent)
|
|
|
916
901
|
* @param {number} lerpA
|
|
917
902
|
* @param {number} lerpB
|
|
918
903
|
* @return {number}
|
|
919
|
-
* @memberof
|
|
904
|
+
* @memberof Math */
|
|
920
905
|
function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
921
906
|
{ return lerp(lerpA, lerpB, percent(value, percentA, percentB)); }
|
|
922
907
|
|
|
@@ -925,7 +910,7 @@ function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
|
925
910
|
* @param {number} valueB
|
|
926
911
|
* @param {number} [wrapSize]
|
|
927
912
|
* @return {number}
|
|
928
|
-
* @memberof
|
|
913
|
+
* @memberof Math */
|
|
929
914
|
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
930
915
|
{ const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
|
|
931
916
|
|
|
@@ -935,7 +920,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
935
920
|
* @param {number} percent
|
|
936
921
|
* @param {number} [wrapSize]
|
|
937
922
|
* @return {number}
|
|
938
|
-
* @memberof
|
|
923
|
+
* @memberof Math */
|
|
939
924
|
function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
940
925
|
{ return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
|
|
941
926
|
|
|
@@ -943,7 +928,7 @@ function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
|
943
928
|
* @param {number} angleA
|
|
944
929
|
* @param {number} angleB
|
|
945
930
|
* @return {number}
|
|
946
|
-
* @memberof
|
|
931
|
+
* @memberof Math */
|
|
947
932
|
function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
|
|
948
933
|
|
|
949
934
|
/** Linearly interpolates between the angles passed in with wrapping
|
|
@@ -951,35 +936,35 @@ function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*P
|
|
|
951
936
|
* @param {number} angleB
|
|
952
937
|
* @param {number} percent
|
|
953
938
|
* @return {number}
|
|
954
|
-
* @memberof
|
|
939
|
+
* @memberof Math */
|
|
955
940
|
function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
|
|
956
941
|
|
|
957
942
|
/** Applies smoothstep function to the percentage value
|
|
958
943
|
* @param {number} percent
|
|
959
944
|
* @return {number}
|
|
960
|
-
* @memberof
|
|
945
|
+
* @memberof Math */
|
|
961
946
|
function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
962
947
|
|
|
963
948
|
/** Checks if the value passed in is a power of two
|
|
964
949
|
* @param {number} value
|
|
965
950
|
* @return {boolean}
|
|
966
|
-
* @memberof
|
|
951
|
+
* @memberof Math */
|
|
967
952
|
function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
968
953
|
|
|
969
954
|
/** Returns the nearest power of two not less than the value
|
|
970
955
|
* @param {number} value
|
|
971
956
|
* @return {number}
|
|
972
|
-
* @memberof
|
|
957
|
+
* @memberof Math */
|
|
973
958
|
function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
974
959
|
|
|
975
960
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
976
961
|
* this can be used for simple collision detection between objects
|
|
977
|
-
* @param {Vector2} posA
|
|
978
|
-
* @param {Vector2} sizeA
|
|
979
|
-
* @param {Vector2} posB
|
|
980
|
-
* @param {Vector2} [sizeB=(
|
|
981
|
-
* @return {boolean}
|
|
982
|
-
* @memberof
|
|
962
|
+
* @param {Vector2} posA - Center of box A
|
|
963
|
+
* @param {Vector2} sizeA - Size of box A
|
|
964
|
+
* @param {Vector2} posB - Center of box B
|
|
965
|
+
* @param {Vector2} [sizeB=vec2()] - Size of box B, uses a point if undefined
|
|
966
|
+
* @return {boolean} - True if overlapping
|
|
967
|
+
* @memberof Math */
|
|
983
968
|
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
984
969
|
{
|
|
985
970
|
const dx = (posA.x - posB.x)*2;
|
|
@@ -995,7 +980,7 @@ function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
|
995
980
|
* @param {Vector2} pos - Center of box
|
|
996
981
|
* @param {Vector2} size - Size of box
|
|
997
982
|
* @return {boolean} - True if intersecting
|
|
998
|
-
* @memberof
|
|
983
|
+
* @memberof Math */
|
|
999
984
|
function isIntersecting(start, end, pos, size)
|
|
1000
985
|
{
|
|
1001
986
|
// Liang-Barsky algorithm
|
|
@@ -1036,52 +1021,29 @@ function isIntersecting(start, end, pos, size)
|
|
|
1036
1021
|
* @param {number} [t=time] - Value to use for time of the wave
|
|
1037
1022
|
* @param {number} [offset] - Value to use for time offset of the wave
|
|
1038
1023
|
* @return {number} - Value waving between 0 and amplitude
|
|
1039
|
-
* @memberof
|
|
1024
|
+
* @memberof Math */
|
|
1040
1025
|
function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
1041
1026
|
{ return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
|
|
1042
1027
|
|
|
1043
|
-
/** Formats seconds to mm:ss style for display purposes
|
|
1044
|
-
* @param {number} t - time in seconds
|
|
1045
|
-
* @return {string}
|
|
1046
|
-
* @memberof Utilities */
|
|
1047
|
-
function formatTime(t)
|
|
1048
|
-
{
|
|
1049
|
-
const sign = t < 0 ? '-' : '';
|
|
1050
|
-
t = abs(t)|0;
|
|
1051
|
-
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
1055
|
-
* @param {string} url - URL of JSON file
|
|
1056
|
-
* @return {Promise<object>}
|
|
1057
|
-
* @memberof Utilities */
|
|
1058
|
-
async function fetchJSON(url)
|
|
1059
|
-
{
|
|
1060
|
-
const response = await fetch(url);
|
|
1061
|
-
if (!response.ok)
|
|
1062
|
-
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
1063
|
-
return response.json();
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
1028
|
/**
|
|
1067
1029
|
* Check if object is a valid number, not NaN or undefined, but it may be infinite
|
|
1068
1030
|
* @param {any} n
|
|
1069
1031
|
* @return {boolean}
|
|
1070
|
-
* @memberof
|
|
1032
|
+
* @memberof Math */
|
|
1071
1033
|
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
1072
1034
|
|
|
1073
1035
|
/**
|
|
1074
1036
|
* Check if object is a valid string or can be converted to one
|
|
1075
1037
|
* @param {any} s
|
|
1076
1038
|
* @return {boolean}
|
|
1077
|
-
* @memberof
|
|
1078
|
-
function isString(s) { return s
|
|
1039
|
+
* @memberof Math */
|
|
1040
|
+
function isString(s) { return s != null && typeof s?.toString() === 'string'; }
|
|
1079
1041
|
|
|
1080
1042
|
/**
|
|
1081
1043
|
* Check if object is an array
|
|
1082
1044
|
* @param {any} a
|
|
1083
1045
|
* @return {boolean}
|
|
1084
|
-
* @memberof
|
|
1046
|
+
* @memberof Math */
|
|
1085
1047
|
function isArray(a) { return Array.isArray(a); }
|
|
1086
1048
|
|
|
1087
1049
|
/**
|
|
@@ -1097,7 +1059,7 @@ function isArray(a) { return Array.isArray(a); }
|
|
|
1097
1059
|
* @param {LineTestFunction} testFunction - Check if colliding
|
|
1098
1060
|
* @param {Vector2} [normal] - Optional vector to store the normal
|
|
1099
1061
|
* @return {Vector2|undefined} - Position of the collision or undefined if none found
|
|
1100
|
-
* @memberof
|
|
1062
|
+
* @memberof Math */
|
|
1101
1063
|
function lineTest(posStart, posEnd, testFunction, normal)
|
|
1102
1064
|
{
|
|
1103
1065
|
ASSERT(isVector2(posStart), 'posStart must be a vec2');
|
|
@@ -1109,8 +1071,7 @@ function lineTest(posStart, posEnd, testFunction, normal)
|
|
|
1109
1071
|
const dx = posEnd.x - posStart.x;
|
|
1110
1072
|
const dy = posEnd.y - posStart.y;
|
|
1111
1073
|
const totalLength = hypot(dx, dy);
|
|
1112
|
-
if (!totalLength)
|
|
1113
|
-
return;
|
|
1074
|
+
if (!totalLength) return;
|
|
1114
1075
|
|
|
1115
1076
|
// current integer cell we are in
|
|
1116
1077
|
const pos = posStart.floor();
|
|
@@ -1220,8 +1181,8 @@ function randInCircle(radius=1, minRadius=0)
|
|
|
1220
1181
|
{ return radius > 0 ? randVec2(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
|
|
1221
1182
|
|
|
1222
1183
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1223
|
-
* @param {Color} [colorA=
|
|
1224
|
-
* @param {Color} [colorB=
|
|
1184
|
+
* @param {Color} [colorA=WHITE]
|
|
1185
|
+
* @param {Color} [colorB=BLACK]
|
|
1225
1186
|
* @param {boolean} [linear]
|
|
1226
1187
|
* @return {Color}
|
|
1227
1188
|
* @memberof Random */
|
|
@@ -1300,8 +1261,8 @@ class RandomGenerator
|
|
|
1300
1261
|
{ return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
|
|
1301
1262
|
|
|
1302
1263
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1303
|
-
* @param {Color} [colorA=
|
|
1304
|
-
* @param {Color} [colorB=
|
|
1264
|
+
* @param {Color} [colorA=WHITE]
|
|
1265
|
+
* @param {Color} [colorB=BLACK]
|
|
1305
1266
|
* @param {boolean} [linear]
|
|
1306
1267
|
* @return {Color} */
|
|
1307
1268
|
randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
@@ -1344,14 +1305,14 @@ class RandomGenerator
|
|
|
1344
1305
|
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
1345
1306
|
* a = vec2(5); // set a to (5, 5)
|
|
1346
1307
|
* b = vec2(); // set b to (0, 0)
|
|
1347
|
-
* @memberof
|
|
1348
|
-
function vec2(x=0, y) { return new Vector2(x, y
|
|
1308
|
+
* @memberof Math */
|
|
1309
|
+
function vec2(x=0, y) { return new Vector2(x, y ?? x); }
|
|
1349
1310
|
|
|
1350
1311
|
/**
|
|
1351
1312
|
* Check if object is a valid Vector2
|
|
1352
1313
|
* @param {any} v
|
|
1353
1314
|
* @return {boolean}
|
|
1354
|
-
* @memberof
|
|
1315
|
+
* @memberof Math */
|
|
1355
1316
|
function isVector2(v) { return v instanceof Vector2 && v.isValid(); }
|
|
1356
1317
|
|
|
1357
1318
|
// vector2 asserts
|
|
@@ -1553,10 +1514,6 @@ class Vector2
|
|
|
1553
1514
|
* @return {number} */
|
|
1554
1515
|
area() { return abs(this.x * this.y); }
|
|
1555
1516
|
|
|
1556
|
-
/** Returns true if this vector is (0,0)
|
|
1557
|
-
* @return {boolean} */
|
|
1558
|
-
isZero() { return !this.x && !this.y; }
|
|
1559
|
-
|
|
1560
1517
|
/** Returns a new vector that is p percent between this and the vector passed in
|
|
1561
1518
|
* @param {Vector2} v - other vector
|
|
1562
1519
|
* @param {number} percent
|
|
@@ -1601,7 +1558,7 @@ class Vector2
|
|
|
1601
1558
|
* @param {number} [b=1] - blue
|
|
1602
1559
|
* @param {number} [a=1] - alpha
|
|
1603
1560
|
* @return {Color}
|
|
1604
|
-
* @memberof
|
|
1561
|
+
* @memberof Math
|
|
1605
1562
|
*/
|
|
1606
1563
|
function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
1607
1564
|
|
|
@@ -1612,14 +1569,14 @@ function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
|
1612
1569
|
* @param {number} [l=1] - lightness
|
|
1613
1570
|
* @param {number} [a=1] - alpha
|
|
1614
1571
|
* @return {Color}
|
|
1615
|
-
* @memberof
|
|
1572
|
+
* @memberof Math */
|
|
1616
1573
|
function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
1617
1574
|
|
|
1618
1575
|
/**
|
|
1619
1576
|
* Check if object is a valid Color
|
|
1620
1577
|
* @param {any} c
|
|
1621
1578
|
* @return {boolean}
|
|
1622
|
-
* @memberof
|
|
1579
|
+
* @memberof Math */
|
|
1623
1580
|
function isColor(c) { return c instanceof Color && c.isValid(); }
|
|
1624
1581
|
|
|
1625
1582
|
// color asserts
|
|
@@ -1800,7 +1757,7 @@ class Color
|
|
|
1800
1757
|
toString(useAlpha = true)
|
|
1801
1758
|
{
|
|
1802
1759
|
if (debug && !this.isValid())
|
|
1803
|
-
return
|
|
1760
|
+
return '#000';
|
|
1804
1761
|
const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
|
|
1805
1762
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
1806
1763
|
}
|
|
@@ -1857,68 +1814,158 @@ class Color
|
|
|
1857
1814
|
|
|
1858
1815
|
/** Color - White #ffffff
|
|
1859
1816
|
* @type {Color}
|
|
1860
|
-
* @memberof
|
|
1817
|
+
* @memberof Math */
|
|
1861
1818
|
const WHITE = debugProtectConstant(rgb());
|
|
1862
1819
|
|
|
1863
1820
|
/** Color - Clear White #757474ff with 0 alpha
|
|
1864
1821
|
* @type {Color}
|
|
1865
|
-
* @memberof
|
|
1822
|
+
* @memberof Math */
|
|
1866
1823
|
const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
1867
1824
|
|
|
1868
1825
|
/** Color - Black #000000
|
|
1869
1826
|
* @type {Color}
|
|
1870
|
-
* @memberof
|
|
1827
|
+
* @memberof Math */
|
|
1871
1828
|
const BLACK = debugProtectConstant(rgb(0,0,0));
|
|
1872
1829
|
|
|
1873
1830
|
/** Color - Clear Black #000000 with 0 alpha
|
|
1874
1831
|
* @type {Color}
|
|
1875
|
-
* @memberof
|
|
1832
|
+
* @memberof Math */
|
|
1876
1833
|
const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
|
|
1877
1834
|
|
|
1878
1835
|
/** Color - Gray #808080
|
|
1879
1836
|
* @type {Color}
|
|
1880
|
-
* @memberof
|
|
1837
|
+
* @memberof Math */
|
|
1881
1838
|
const GRAY = debugProtectConstant(rgb(.5,.5,.5));
|
|
1882
1839
|
|
|
1883
1840
|
/** Color - Red #ff0000
|
|
1884
1841
|
* @type {Color}
|
|
1885
|
-
* @memberof
|
|
1842
|
+
* @memberof Math */
|
|
1886
1843
|
const RED = debugProtectConstant(rgb(1,0,0));
|
|
1887
1844
|
|
|
1888
1845
|
/** Color - Orange #ff8000
|
|
1889
1846
|
* @type {Color}
|
|
1890
|
-
* @memberof
|
|
1847
|
+
* @memberof Math */
|
|
1891
1848
|
const ORANGE = debugProtectConstant(rgb(1,.5,0));
|
|
1892
1849
|
|
|
1893
1850
|
/** Color - Yellow #ffff00
|
|
1894
1851
|
* @type {Color}
|
|
1895
|
-
* @memberof
|
|
1852
|
+
* @memberof Math */
|
|
1896
1853
|
const YELLOW = debugProtectConstant(rgb(1,1,0));
|
|
1897
1854
|
|
|
1898
1855
|
/** Color - Green #00ff00
|
|
1899
1856
|
* @type {Color}
|
|
1900
|
-
* @memberof
|
|
1857
|
+
* @memberof Math */
|
|
1901
1858
|
const GREEN = debugProtectConstant(rgb(0,1,0));
|
|
1902
1859
|
|
|
1903
1860
|
/** Color - Cyan #00ffff
|
|
1904
1861
|
* @type {Color}
|
|
1905
|
-
* @memberof
|
|
1862
|
+
* @memberof Math */
|
|
1906
1863
|
const CYAN = debugProtectConstant(rgb(0,1,1));
|
|
1907
1864
|
|
|
1908
1865
|
/** Color - Blue #0000ff
|
|
1909
1866
|
* @type {Color}
|
|
1910
|
-
* @memberof
|
|
1867
|
+
* @memberof Math */
|
|
1911
1868
|
const BLUE = debugProtectConstant(rgb(0,0,1));
|
|
1912
1869
|
|
|
1913
1870
|
/** Color - Purple #8000ff
|
|
1914
1871
|
* @type {Color}
|
|
1915
|
-
* @memberof
|
|
1872
|
+
* @memberof Math */
|
|
1916
1873
|
const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
1917
1874
|
|
|
1918
1875
|
/** Color - Magenta #ff00ff
|
|
1919
1876
|
* @type {Color}
|
|
1877
|
+
* @memberof Math */
|
|
1878
|
+
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
1879
|
+
/**
|
|
1880
|
+
* LittleJS Utility Classes and Functions
|
|
1881
|
+
* - General purpose utilities
|
|
1882
|
+
* - Timer - tracks time automatically
|
|
1883
|
+
* @namespace Utilities
|
|
1884
|
+
*/
|
|
1885
|
+
|
|
1886
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
1887
|
+
* @param {number} t - time in seconds
|
|
1888
|
+
* @return {string}
|
|
1889
|
+
* @memberof Utilities */
|
|
1890
|
+
function formatTime(t)
|
|
1891
|
+
{
|
|
1892
|
+
const sign = t < 0 ? '-' : '';
|
|
1893
|
+
t = abs(t)|0;
|
|
1894
|
+
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
1898
|
+
* @param {string} url - URL of JSON file
|
|
1899
|
+
* @return {Promise<object>}
|
|
1900
|
+
* @memberof Utilities */
|
|
1901
|
+
async function fetchJSON(url)
|
|
1902
|
+
{
|
|
1903
|
+
const response = await fetch(url);
|
|
1904
|
+
if (!response.ok)
|
|
1905
|
+
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
1906
|
+
return response.json();
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1910
|
+
|
|
1911
|
+
/** Save a text file to disk
|
|
1912
|
+
* @param {string} text
|
|
1913
|
+
* @param {string} [filename]
|
|
1914
|
+
* @param {string} [type]
|
|
1920
1915
|
* @memberof Utilities */
|
|
1921
|
-
|
|
1916
|
+
function saveText(text, filename='text', type='text/plain')
|
|
1917
|
+
{ saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
1918
|
+
|
|
1919
|
+
/** Save a canvas to disk
|
|
1920
|
+
* @param {HTMLCanvasElement|OffscreenCanvas} canvas
|
|
1921
|
+
* @param {string} [filename]
|
|
1922
|
+
* @param {string} [type]
|
|
1923
|
+
* @memberof Utilities */
|
|
1924
|
+
function saveCanvas(canvas, filename='screenshot', type='image/png')
|
|
1925
|
+
{
|
|
1926
|
+
if (canvas instanceof OffscreenCanvas)
|
|
1927
|
+
{
|
|
1928
|
+
// copy to temporary canvas and save
|
|
1929
|
+
const saveCanvas = document.createElement('canvas');
|
|
1930
|
+
saveCanvas.width = canvas.width;
|
|
1931
|
+
saveCanvas.height = canvas.height;
|
|
1932
|
+
saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
|
|
1933
|
+
saveDataURL(saveCanvas.toDataURL(type), filename);
|
|
1934
|
+
}
|
|
1935
|
+
else
|
|
1936
|
+
saveDataURL(canvas.toDataURL(type), filename);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
/** Save a data url to disk
|
|
1940
|
+
* @param {string} url
|
|
1941
|
+
* @param {string} [filename]
|
|
1942
|
+
* @param {number} [revokeTime] - how long before revoking the url
|
|
1943
|
+
* @memberof Utilities */
|
|
1944
|
+
function saveDataURL(url, filename='download', revokeTime)
|
|
1945
|
+
{
|
|
1946
|
+
ASSERT(isString(url), 'saveDataURL requires url string');
|
|
1947
|
+
ASSERT(isString(filename), 'saveDataURL requires filename string');
|
|
1948
|
+
|
|
1949
|
+
// create link for saving screenshots
|
|
1950
|
+
const link = document.createElement('a');
|
|
1951
|
+
link.download = filename;
|
|
1952
|
+
link.href = url;
|
|
1953
|
+
link.click();
|
|
1954
|
+
if (revokeTime !== undefined)
|
|
1955
|
+
setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
/** Share content using the native share dialog if available
|
|
1959
|
+
* @param {string} title - title of the share
|
|
1960
|
+
* @param {string} url - url to share
|
|
1961
|
+
* @param {Function} [callback] - Called when share is complete
|
|
1962
|
+
* @memberof Utilities */
|
|
1963
|
+
function shareURL(title, url, callback)
|
|
1964
|
+
{
|
|
1965
|
+
ASSERT(isString(title), 'shareURL requires title string');
|
|
1966
|
+
ASSERT(isString(url), 'shareURL requires url string');
|
|
1967
|
+
navigator.share?.({title, url}).then(()=>callback?.());
|
|
1968
|
+
}
|
|
1922
1969
|
|
|
1923
1970
|
///////////////////////////////////////////////////////////////////////////////
|
|
1924
1971
|
|
|
@@ -2040,7 +2087,7 @@ let cameraScale = 32;
|
|
|
2040
2087
|
* @memberof Settings */
|
|
2041
2088
|
let canvasColorTiles = true;
|
|
2042
2089
|
|
|
2043
|
-
/** Color to clear the canvas to before render
|
|
2090
|
+
/** Color to clear the canvas to before render, does not clear if alpha is 0
|
|
2044
2091
|
* @type {Color}
|
|
2045
2092
|
* @memberof Draw */
|
|
2046
2093
|
let canvasClearColor = CLEAR_BLACK;
|
|
@@ -2349,7 +2396,7 @@ function setCameraScale(scale) { cameraScale = scale; }
|
|
|
2349
2396
|
* @memberof Settings */
|
|
2350
2397
|
function setCanvasColorTiles(colorTiles) { canvasColorTiles = colorTiles; }
|
|
2351
2398
|
|
|
2352
|
-
/** Set color to clear the canvas to before render
|
|
2399
|
+
/** Set color to clear the canvas to before render, does not clear if alpha is 0
|
|
2353
2400
|
* @param {Color} color
|
|
2354
2401
|
* @memberof Settings */
|
|
2355
2402
|
function setCanvasClearColor(color) { canvasClearColor = color.copy(); }
|
|
@@ -2629,10 +2676,10 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
2629
2676
|
class EngineObject
|
|
2630
2677
|
{
|
|
2631
2678
|
/** Create an engine object and adds it to the list of objects
|
|
2632
|
-
* @param {Vector2} [pos=(
|
|
2633
|
-
* @param {Vector2} [size=(1
|
|
2634
|
-
* @param {TileInfo} [tileInfo]
|
|
2635
|
-
* @param {number} [angle]
|
|
2679
|
+
* @param {Vector2} [pos=vec2()] - World space position of the object
|
|
2680
|
+
* @param {Vector2} [size=vec2(1)] - World space size of the object
|
|
2681
|
+
* @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
|
|
2682
|
+
* @param {number} [angle] - Angle the object is rotated by
|
|
2636
2683
|
* @param {Color} [color=WHITE] - Color to apply to tile when rendered
|
|
2637
2684
|
* @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
|
|
2638
2685
|
*/
|
|
@@ -2760,15 +2807,16 @@ class EngineObject
|
|
|
2760
2807
|
// physics sanity checks
|
|
2761
2808
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
2762
2809
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
2763
|
-
|
|
2764
|
-
|
|
2810
|
+
|
|
2811
|
+
// don't do collision for static objects or if solver disabled
|
|
2812
|
+
if (!enablePhysicsSolver || !this.mass) return;
|
|
2765
2813
|
|
|
2766
2814
|
const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
|
|
2767
2815
|
if (this.groundObject)
|
|
2768
2816
|
{
|
|
2769
2817
|
// apply friction in local space of ground object
|
|
2770
2818
|
const friction = max(this.friction, this.groundObject.friction);
|
|
2771
|
-
const groundSpeed = this.groundObject.velocity
|
|
2819
|
+
const groundSpeed = this.groundObject.velocity.x;
|
|
2772
2820
|
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
|
|
2773
2821
|
this.groundObject = undefined;
|
|
2774
2822
|
}
|
|
@@ -2779,19 +2827,19 @@ class EngineObject
|
|
|
2779
2827
|
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
2780
2828
|
for (const o of engineObjectsCollide)
|
|
2781
2829
|
{
|
|
2830
|
+
// skip destroyed, child objects, or self collision
|
|
2831
|
+
if (o.destroyed || o.parent || o === this) continue;
|
|
2832
|
+
|
|
2782
2833
|
// non solid objects don't collide with each other
|
|
2783
|
-
if (
|
|
2784
|
-
continue;
|
|
2834
|
+
if (!this.isSolid && !o.isSolid) continue;
|
|
2785
2835
|
|
|
2786
2836
|
// check collision
|
|
2787
|
-
if (!this.isOverlappingObject(o))
|
|
2788
|
-
continue;
|
|
2837
|
+
if (!this.isOverlappingObject(o)) continue;
|
|
2789
2838
|
|
|
2790
2839
|
// notify objects of collision and check if should be resolved
|
|
2791
2840
|
const collide1 = this.collideWithObject(o);
|
|
2792
2841
|
const collide2 = o.collideWithObject(this);
|
|
2793
|
-
if (!collide1 || !collide2)
|
|
2794
|
-
continue;
|
|
2842
|
+
if (!collide1 || !collide2) continue;
|
|
2795
2843
|
|
|
2796
2844
|
if (isOverlapping(oldPos, this.size, o.pos, o.size))
|
|
2797
2845
|
{
|
|
@@ -2893,7 +2941,7 @@ class EngineObject
|
|
|
2893
2941
|
const delta = y - this.pos.y;
|
|
2894
2942
|
if (delta < maxMoveUp)
|
|
2895
2943
|
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
2896
|
-
{
|
|
2944
|
+
{
|
|
2897
2945
|
this.pos.y = y;
|
|
2898
2946
|
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
2899
2947
|
return;
|
|
@@ -2953,10 +3001,10 @@ class EngineObject
|
|
|
2953
3001
|
|
|
2954
3002
|
// disconnect from parent and destroy children
|
|
2955
3003
|
this.destroyed = 1;
|
|
2956
|
-
this.parent
|
|
3004
|
+
this.parent?.removeChild(this);
|
|
2957
3005
|
for (const child of this.children)
|
|
2958
3006
|
{
|
|
2959
|
-
child.parent =
|
|
3007
|
+
child.parent = undefined;
|
|
2960
3008
|
child.destroy();
|
|
2961
3009
|
}
|
|
2962
3010
|
}
|
|
@@ -2977,15 +3025,15 @@ class EngineObject
|
|
|
2977
3025
|
* @param {Vector2} vec - world space vector */
|
|
2978
3026
|
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
2979
3027
|
|
|
2980
|
-
/** Called to check if a tile collision should be resolved
|
|
3028
|
+
/** Called to check if a tile collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
|
|
2981
3029
|
* @param {number} tileData - the value of the tile at the position
|
|
2982
|
-
* @param {Vector2} pos
|
|
2983
|
-
* @return {boolean}
|
|
3030
|
+
* @param {Vector2} pos - tile where the collision occurred
|
|
3031
|
+
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity */
|
|
2984
3032
|
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
2985
3033
|
|
|
2986
|
-
/** Called to check if
|
|
3034
|
+
/** Called by the engine to check if an object collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
|
|
2987
3035
|
* @param {EngineObject} object - the object to test against
|
|
2988
|
-
* @return {boolean}
|
|
3036
|
+
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity
|
|
2989
3037
|
*/
|
|
2990
3038
|
collideWithObject(object) { return true; }
|
|
2991
3039
|
|
|
@@ -3026,9 +3074,9 @@ class EngineObject
|
|
|
3026
3074
|
* @return {number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
3027
3075
|
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
3028
3076
|
|
|
3029
|
-
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3077
|
+
/** Attaches a child to this with a local transform, returns child for chaining
|
|
3030
3078
|
* @param {EngineObject} child
|
|
3031
|
-
* @param {Vector2} [localPos=(
|
|
3079
|
+
* @param {Vector2} [localPos=vec2()]
|
|
3032
3080
|
* @param {number} [localAngle]
|
|
3033
3081
|
* @return {EngineObject} The child object added */
|
|
3034
3082
|
addChild(child, localPos=vec2(), localAngle=0)
|
|
@@ -3052,7 +3100,7 @@ class EngineObject
|
|
|
3052
3100
|
const index = this.children.indexOf(child);
|
|
3053
3101
|
ASSERT(index >= 0, 'child not found in children array');
|
|
3054
3102
|
index >= 0 && this.children.splice(index, 1);
|
|
3055
|
-
child.parent =
|
|
3103
|
+
child.parent = undefined;
|
|
3056
3104
|
}
|
|
3057
3105
|
|
|
3058
3106
|
/** Check if overlapping another engine object
|
|
@@ -3064,7 +3112,7 @@ class EngineObject
|
|
|
3064
3112
|
|
|
3065
3113
|
/** Check if overlapping a point or aligned bounding box
|
|
3066
3114
|
* @param {Vector2} pos - Center of box
|
|
3067
|
-
* @param {Vector2} [size=(
|
|
3115
|
+
* @param {Vector2} [size=vec2()] - Size of box, uses a point if undefined
|
|
3068
3116
|
* @return {boolean} */
|
|
3069
3117
|
isOverlapping(pos, size=vec2())
|
|
3070
3118
|
{ return isOverlapping(this.pos, this.size, pos, size); }
|
|
@@ -3107,8 +3155,7 @@ class EngineObject
|
|
|
3107
3155
|
/** Render debug info for this object */
|
|
3108
3156
|
renderDebugInfo()
|
|
3109
3157
|
{
|
|
3110
|
-
if (!debug)
|
|
3111
|
-
return;
|
|
3158
|
+
if (!debug) return;
|
|
3112
3159
|
|
|
3113
3160
|
// show object info for debugging
|
|
3114
3161
|
const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
|
|
@@ -3150,11 +3197,6 @@ let mainCanvas;
|
|
|
3150
3197
|
* @memberof Draw */
|
|
3151
3198
|
let mainContext;
|
|
3152
3199
|
|
|
3153
|
-
/** The default canvas to use for drawing, usually mainCanvas
|
|
3154
|
-
* @type {HTMLCanvasElement|OffscreenCanvas}
|
|
3155
|
-
* @memberof Draw */
|
|
3156
|
-
let drawCanvas;
|
|
3157
|
-
|
|
3158
3200
|
/** The default 2d context to use for drawing, usually mainContext
|
|
3159
3201
|
* @type {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}
|
|
3160
3202
|
* @memberof Draw */
|
|
@@ -3201,10 +3243,11 @@ let drawCount;
|
|
|
3201
3243
|
* Create a tile info object using a grid based system
|
|
3202
3244
|
* - This can take vecs or floats for easier use and conversion
|
|
3203
3245
|
* - If an index is passed in, the tile size and index will determine the position
|
|
3204
|
-
* @param {Vector2|number} [
|
|
3246
|
+
* @param {Vector2|number} [index=0] - Index of the tile in 1d or 2d form
|
|
3205
3247
|
* @param {Vector2|number} [size] - Size of tile in pixels
|
|
3206
|
-
* @param {number} [
|
|
3248
|
+
* @param {TextureInfo|number} [texture] - Texture index or info to use
|
|
3207
3249
|
* @param {number} [padding] - How many pixels padding around tiles
|
|
3250
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3208
3251
|
* @return {TileInfo}
|
|
3209
3252
|
* @example
|
|
3210
3253
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -3212,36 +3255,43 @@ let drawCount;
|
|
|
3212
3255
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
3213
3256
|
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
3214
3257
|
* @memberof Draw */
|
|
3215
|
-
function tile(
|
|
3258
|
+
function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3216
3259
|
{
|
|
3217
|
-
|
|
3218
|
-
|
|
3260
|
+
ASSERT(isVector2(index) || typeof index === 'number', 'index must be a vec2 or number');
|
|
3261
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
3262
|
+
ASSERT(isNumber(texture) || texture instanceof TextureInfo, 'texture must be a number or TextureInfo');
|
|
3263
|
+
ASSERT(isNumber(padding), 'padding must be a number');
|
|
3264
|
+
|
|
3265
|
+
if (headlessMode) return new TileInfo;
|
|
3219
3266
|
|
|
3220
|
-
// if size is a number, make it a vector
|
|
3221
3267
|
if (typeof size === 'number')
|
|
3222
3268
|
{
|
|
3269
|
+
// if size is a number, make it a vector
|
|
3223
3270
|
ASSERT(size > 0);
|
|
3224
3271
|
size = new Vector2(size, size);
|
|
3225
3272
|
}
|
|
3226
3273
|
|
|
3227
3274
|
// create tile info object
|
|
3228
|
-
const
|
|
3275
|
+
const textureInfo = typeof texture === 'number' ?
|
|
3276
|
+
textureInfos[texture] : texture;
|
|
3229
3277
|
|
|
3230
3278
|
// get the position of the tile
|
|
3231
|
-
const textureInfo = textureInfos[textureIndex];
|
|
3232
|
-
ASSERT(!!textureInfo, 'Texture not loaded');
|
|
3233
3279
|
const sizePaddedX = size.x + padding*2;
|
|
3234
3280
|
const sizePaddedY = size.y + padding*2;
|
|
3235
|
-
|
|
3281
|
+
let x, y;
|
|
3282
|
+
if (typeof index === 'number')
|
|
3236
3283
|
{
|
|
3237
3284
|
const cols = textureInfo.size.x / sizePaddedX |0;
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
|
|
3285
|
+
x = index % cols;
|
|
3286
|
+
y = index / cols |0;
|
|
3241
3287
|
}
|
|
3242
3288
|
else
|
|
3243
|
-
|
|
3244
|
-
|
|
3289
|
+
{
|
|
3290
|
+
x = index.x;
|
|
3291
|
+
y = index.y;
|
|
3292
|
+
}
|
|
3293
|
+
const pos = new Vector2(x*sizePaddedX + padding, y*sizePaddedY + padding);
|
|
3294
|
+
return new TileInfo(pos, size, textureInfo, padding, bleed);
|
|
3245
3295
|
}
|
|
3246
3296
|
|
|
3247
3297
|
/**
|
|
@@ -3251,24 +3301,22 @@ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=til
|
|
|
3251
3301
|
class TileInfo
|
|
3252
3302
|
{
|
|
3253
3303
|
/** Create a tile info object
|
|
3254
|
-
* @param {Vector2} [pos=(
|
|
3304
|
+
* @param {Vector2} [pos=vec2()] - Top left corner of tile in pixels
|
|
3255
3305
|
* @param {Vector2} [size] - Size of tile in pixels
|
|
3256
|
-
* @param {
|
|
3257
|
-
* @param {number}
|
|
3258
|
-
* @param {number}
|
|
3306
|
+
* @param {TextureInfo} [textureInfo] - Texture info to use
|
|
3307
|
+
* @param {number} [padding] - How many pixels padding around tiles
|
|
3308
|
+
* @param {number} [bleed] - How many pixels smaller to draw tiles
|
|
3259
3309
|
*/
|
|
3260
|
-
constructor(pos=vec2(), size=tileDefaultSize,
|
|
3310
|
+
constructor(pos=vec2(), size=tileDefaultSize, textureInfo=textureInfos[0], padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
3261
3311
|
{
|
|
3262
3312
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
3263
3313
|
this.pos = pos.copy();
|
|
3264
3314
|
/** @property {Vector2} - Size of tile in pixels */
|
|
3265
3315
|
this.size = size.copy();
|
|
3266
|
-
/** @property {number} - Texture index to use */
|
|
3267
|
-
this.textureIndex = textureIndex;
|
|
3268
3316
|
/** @property {number} - How many pixels padding around tiles */
|
|
3269
3317
|
this.padding = padding;
|
|
3270
3318
|
/** @property {TextureInfo} - The texture info for this tile */
|
|
3271
|
-
this.textureInfo =
|
|
3319
|
+
this.textureInfo = textureInfo;
|
|
3272
3320
|
/** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
|
|
3273
3321
|
this.bleed = bleed;
|
|
3274
3322
|
}
|
|
@@ -3278,7 +3326,7 @@ class TileInfo
|
|
|
3278
3326
|
* @return {TileInfo}
|
|
3279
3327
|
*/
|
|
3280
3328
|
offset(offset)
|
|
3281
|
-
{ return new TileInfo(this.pos.add(offset), this.size, this.
|
|
3329
|
+
{ return new TileInfo(this.pos.add(offset), this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3282
3330
|
|
|
3283
3331
|
/** Returns a copy of this tile offset by a number of animation frames
|
|
3284
3332
|
* @param {number} frame - Offset to apply in animation frames
|
|
@@ -3287,23 +3335,33 @@ class TileInfo
|
|
|
3287
3335
|
frame(frame)
|
|
3288
3336
|
{
|
|
3289
3337
|
ASSERT(typeof frame === 'number');
|
|
3290
|
-
|
|
3338
|
+
const w = this.size.x + this.padding*2;
|
|
3339
|
+
const x = frame*w;
|
|
3340
|
+
ASSERT(x < this.textureInfo.size.x, 'frame extends beyond texture width!');
|
|
3341
|
+
return this.offset(new Vector2(x));
|
|
3291
3342
|
}
|
|
3292
3343
|
|
|
3293
3344
|
/**
|
|
3294
3345
|
* Set this tile to use a full image in a texture info
|
|
3295
|
-
* @param {TextureInfo} textureInfo
|
|
3346
|
+
* @param {TextureInfo} [textureInfo]
|
|
3296
3347
|
* @return {TileInfo}
|
|
3297
3348
|
*/
|
|
3298
|
-
setFullImage(textureInfo)
|
|
3349
|
+
setFullImage(textureInfo=this.textureInfo)
|
|
3299
3350
|
{
|
|
3351
|
+
this.textureInfo = textureInfo;
|
|
3300
3352
|
this.pos = new Vector2;
|
|
3301
3353
|
this.size = textureInfo.size.copy();
|
|
3302
|
-
this.textureInfo = textureInfo;
|
|
3303
|
-
// do not use padding or bleed
|
|
3304
3354
|
this.bleed = this.padding = 0;
|
|
3305
3355
|
return this;
|
|
3306
3356
|
}
|
|
3357
|
+
|
|
3358
|
+
/**
|
|
3359
|
+
* Returns a tile info for an index using this tile as refrence
|
|
3360
|
+
* @param {Vector2|number} [index=0]
|
|
3361
|
+
* @return {TileInfo}
|
|
3362
|
+
*/
|
|
3363
|
+
tile(index)
|
|
3364
|
+
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3307
3365
|
}
|
|
3308
3366
|
|
|
3309
3367
|
/**
|
|
@@ -3344,19 +3402,19 @@ class TextureInfo
|
|
|
3344
3402
|
///////////////////////////////////////////////////////////////////////////////
|
|
3345
3403
|
// Drawing functions
|
|
3346
3404
|
|
|
3347
|
-
/** Draw textured tile centered in world space
|
|
3348
|
-
* @param {Vector2} pos
|
|
3349
|
-
* @param {Vector2} [size=(1
|
|
3350
|
-
* @param {TileInfo} [tileInfo]
|
|
3351
|
-
* @param {Color} [color=
|
|
3352
|
-
* @param {number} [angle]
|
|
3353
|
-
* @param {boolean} [mirror]
|
|
3354
|
-
* @param {Color} [additiveColor]
|
|
3405
|
+
/** Draw textured tile centered in world space
|
|
3406
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
3407
|
+
* @param {Vector2} [size=vec2(1)] - Size of the tile in world space
|
|
3408
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
3409
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
3410
|
+
* @param {number} [angle] - Angle to rotate by
|
|
3411
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
3412
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
3355
3413
|
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
|
|
3356
3414
|
* @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
|
|
3357
3415
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
3358
3416
|
* @memberof Draw */
|
|
3359
|
-
function drawTile(pos, size=
|
|
3417
|
+
function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
3360
3418
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
3361
3419
|
{
|
|
3362
3420
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3431,8 +3489,8 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3431
3489
|
|
|
3432
3490
|
/** Draw colored rect centered on pos
|
|
3433
3491
|
* @param {Vector2} pos
|
|
3434
|
-
* @param {Vector2} [size=(1
|
|
3435
|
-
* @param {Color} [color=
|
|
3492
|
+
* @param {Vector2} [size=vec2(1)]
|
|
3493
|
+
* @param {Color} [color=WHITE]
|
|
3436
3494
|
* @param {number} [angle]
|
|
3437
3495
|
* @param {boolean} [useWebGL=glEnable]
|
|
3438
3496
|
* @param {boolean} [screenSpace]
|
|
@@ -3445,9 +3503,9 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
3445
3503
|
|
|
3446
3504
|
/** Draw a rect centered on pos with a gradient from top to bottom
|
|
3447
3505
|
* @param {Vector2} pos
|
|
3448
|
-
* @param {Vector2} [size=(1
|
|
3449
|
-
* @param {Color} [colorTop=
|
|
3450
|
-
* @param {Color} [colorBottom=
|
|
3506
|
+
* @param {Vector2} [size=vec2(1)]
|
|
3507
|
+
* @param {Color} [colorTop=WHITE]
|
|
3508
|
+
* @param {Color} [colorBottom=BLACK]
|
|
3451
3509
|
* @param {number} [angle]
|
|
3452
3510
|
* @param {boolean} [useWebGL=glEnable]
|
|
3453
3511
|
* @param {boolean} [screenSpace]
|
|
@@ -3509,9 +3567,9 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3509
3567
|
/** Draw connected lines between a series of points
|
|
3510
3568
|
* @param {Array<Vector2>} points
|
|
3511
3569
|
* @param {number} [width]
|
|
3512
|
-
* @param {Color} [color=
|
|
3570
|
+
* @param {Color} [color=WHITE]
|
|
3513
3571
|
* @param {boolean} [wrap] - Should the last point connect to the first?
|
|
3514
|
-
* @param {Vector2} [pos=(
|
|
3572
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
3515
3573
|
* @param {number} [angle] - Angle to rotate by
|
|
3516
3574
|
* @param {boolean} [useWebGL=glEnable]
|
|
3517
3575
|
* @param {boolean} [screenSpace]
|
|
@@ -3546,13 +3604,9 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3546
3604
|
for (let i=0; i<points.length; ++i)
|
|
3547
3605
|
{
|
|
3548
3606
|
const point = points[i];
|
|
3549
|
-
|
|
3550
|
-
context.lineTo(point.x, point.y);
|
|
3551
|
-
else
|
|
3552
|
-
context.moveTo(point.x, point.y);
|
|
3607
|
+
context.lineTo(point.x, point.y);
|
|
3553
3608
|
}
|
|
3554
|
-
|
|
3555
|
-
context.closePath();
|
|
3609
|
+
wrap && context.closePath();
|
|
3556
3610
|
context.stroke();
|
|
3557
3611
|
}, screenSpace, context);
|
|
3558
3612
|
}
|
|
@@ -3562,8 +3616,8 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3562
3616
|
* @param {Vector2} posA
|
|
3563
3617
|
* @param {Vector2} posB
|
|
3564
3618
|
* @param {number} [width]
|
|
3565
|
-
* @param {Color} [color=
|
|
3566
|
-
* @param {Vector2} [pos=(
|
|
3619
|
+
* @param {Color} [color=WHITE]
|
|
3620
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
3567
3621
|
* @param {number} [angle] - Angle to rotate by
|
|
3568
3622
|
* @param {boolean} [useWebGL=glEnable]
|
|
3569
3623
|
* @param {boolean} [screenSpace]
|
|
@@ -3582,12 +3636,12 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
3582
3636
|
|
|
3583
3637
|
/** Draw colored regular polygon using passed in number of sides
|
|
3584
3638
|
* @param {Vector2} pos
|
|
3585
|
-
* @param {Vector2} [size=(1
|
|
3639
|
+
* @param {Vector2} [size=vec2(1)]
|
|
3586
3640
|
* @param {number} [sides]
|
|
3587
|
-
* @param {Color} [color=
|
|
3641
|
+
* @param {Color} [color=WHITE]
|
|
3588
3642
|
* @param {number} [angle]
|
|
3589
3643
|
* @param {number} [lineWidth]
|
|
3590
|
-
* @param {Color} [lineColor=
|
|
3644
|
+
* @param {Color} [lineColor=BLACK]
|
|
3591
3645
|
* @param {boolean} [useWebGL=glEnable]
|
|
3592
3646
|
* @param {boolean} [screenSpace]
|
|
3593
3647
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -3610,10 +3664,10 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
3610
3664
|
|
|
3611
3665
|
/** Draw colored polygon using passed in points
|
|
3612
3666
|
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
3613
|
-
* @param {Color} [color=
|
|
3667
|
+
* @param {Color} [color=WHITE]
|
|
3614
3668
|
* @param {number} [lineWidth]
|
|
3615
|
-
* @param {Color} [lineColor=
|
|
3616
|
-
* @param {Vector2} [pos=(
|
|
3669
|
+
* @param {Color} [lineColor=BLACK]
|
|
3670
|
+
* @param {Vector2} [pos=vec2()] - Offset to apply
|
|
3617
3671
|
* @param {number} [angle] - Angle to rotate by
|
|
3618
3672
|
* @param {boolean} [useWebGL=glEnable]
|
|
3619
3673
|
* @param {boolean} [screenSpace]
|
|
@@ -3660,11 +3714,11 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
3660
3714
|
|
|
3661
3715
|
/** Draw colored ellipse using passed in point
|
|
3662
3716
|
* @param {Vector2} pos
|
|
3663
|
-
* @param {Vector2} [size=(1
|
|
3664
|
-
* @param {Color} [color=
|
|
3717
|
+
* @param {Vector2} [size=vec2(1)] - Width and height diameter
|
|
3718
|
+
* @param {Color} [color=WHITE]
|
|
3665
3719
|
* @param {number} [angle]
|
|
3666
3720
|
* @param {number} [lineWidth]
|
|
3667
|
-
* @param {Color} [lineColor=
|
|
3721
|
+
* @param {Color} [lineColor=BLACK]
|
|
3668
3722
|
* @param {boolean} [useWebGL=glEnable]
|
|
3669
3723
|
* @param {boolean} [screenSpace]
|
|
3670
3724
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -3706,9 +3760,9 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
3706
3760
|
/** Draw colored circle using passed in point
|
|
3707
3761
|
* @param {Vector2} pos
|
|
3708
3762
|
* @param {number} [size=1] - Diameter
|
|
3709
|
-
* @param {Color} [color=
|
|
3763
|
+
* @param {Color} [color=WHITE]
|
|
3710
3764
|
* @param {number} [lineWidth=0]
|
|
3711
|
-
* @param {Color} [lineColor=
|
|
3765
|
+
* @param {Color} [lineColor=BLACK]
|
|
3712
3766
|
* @param {boolean} [useWebGL=glEnable]
|
|
3713
3767
|
* @param {boolean} [screenSpace]
|
|
3714
3768
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
@@ -3742,7 +3796,11 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
3742
3796
|
ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
|
|
3743
3797
|
|
|
3744
3798
|
if (!screenSpace)
|
|
3745
|
-
|
|
3799
|
+
{
|
|
3800
|
+
pos = worldToScreen(pos);
|
|
3801
|
+
size = size.scale(cameraScale);
|
|
3802
|
+
angle -= cameraAngle;
|
|
3803
|
+
}
|
|
3746
3804
|
context.save();
|
|
3747
3805
|
context.translate(pos.x+.5, pos.y+.5);
|
|
3748
3806
|
context.rotate(angle);
|
|
@@ -3759,9 +3817,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
3759
3817
|
* @param {string|number} text
|
|
3760
3818
|
* @param {Vector2} pos
|
|
3761
3819
|
* @param {number} [size]
|
|
3762
|
-
* @param {Color} [color=
|
|
3820
|
+
* @param {Color} [color=WHITE]
|
|
3763
3821
|
* @param {number} [lineWidth]
|
|
3764
|
-
* @param {Color} [lineColor=
|
|
3822
|
+
* @param {Color} [lineColor=BLACK]
|
|
3765
3823
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
3766
3824
|
* @param {string} [font=fontDefault]
|
|
3767
3825
|
* @param {string} [fontStyle]
|
|
@@ -3785,18 +3843,18 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
3785
3843
|
* Automatically splits new lines into rows
|
|
3786
3844
|
* @param {string|number} text
|
|
3787
3845
|
* @param {Vector2} pos
|
|
3788
|
-
* @param {number}
|
|
3789
|
-
* @param {Color} [color=
|
|
3846
|
+
* @param {number} size
|
|
3847
|
+
* @param {Color} [color=WHITE]
|
|
3790
3848
|
* @param {number} [lineWidth]
|
|
3791
|
-
* @param {Color} [lineColor=
|
|
3849
|
+
* @param {Color} [lineColor=BLACK]
|
|
3792
3850
|
* @param {CanvasTextAlign} [textAlign]
|
|
3793
3851
|
* @param {string} [font=fontDefault]
|
|
3794
3852
|
* @param {string} [fontStyle]
|
|
3795
3853
|
* @param {number} [maxWidth]
|
|
3796
3854
|
* @param {number} [angle]
|
|
3797
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=
|
|
3855
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3798
3856
|
* @memberof Draw */
|
|
3799
|
-
function drawTextScreen(text, pos, size
|
|
3857
|
+
function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
|
|
3800
3858
|
{
|
|
3801
3859
|
ASSERT(isString(text), 'text must be a string');
|
|
3802
3860
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3920,7 +3978,7 @@ function worldToScreenDelta(worldDelta)
|
|
|
3920
3978
|
|
|
3921
3979
|
/** Convert screen space transform to world space
|
|
3922
3980
|
* @param {Vector2} screenPos
|
|
3923
|
-
* @param {Vector2} screenSize
|
|
3981
|
+
* @param {Vector2} screenSize
|
|
3924
3982
|
* @param {number} [screenAngle]
|
|
3925
3983
|
* @return {[Vector2, Vector2, number]} - [pos, size, angle]
|
|
3926
3984
|
* @memberof Draw */
|
|
@@ -3937,25 +3995,6 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
|
|
|
3937
3995
|
];
|
|
3938
3996
|
}
|
|
3939
3997
|
|
|
3940
|
-
/** Convert world space transform to screen space
|
|
3941
|
-
* @param {Vector2} worldPos
|
|
3942
|
-
* @param {Vector2} worldSize
|
|
3943
|
-
* @param {number} [worldAngle]
|
|
3944
|
-
* @return {[Vector2, Vector2, number]} - [pos, size, angle]
|
|
3945
|
-
* @memberof Draw */
|
|
3946
|
-
function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
|
|
3947
|
-
{
|
|
3948
|
-
ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
|
|
3949
|
-
ASSERT(isVector2(worldSize), 'worldSize must be a vec2');
|
|
3950
|
-
ASSERT(isNumber(worldAngle), 'worldAngle must be a number');
|
|
3951
|
-
|
|
3952
|
-
return [
|
|
3953
|
-
worldToScreen(worldPos),
|
|
3954
|
-
worldSize.scale(cameraScale),
|
|
3955
|
-
worldAngle - cameraAngle
|
|
3956
|
-
];
|
|
3957
|
-
}
|
|
3958
|
-
|
|
3959
3998
|
/** Get the size of the camera window in world space
|
|
3960
3999
|
* @return {Vector2}
|
|
3961
4000
|
* @memberof Draw */
|
|
@@ -4000,10 +4039,9 @@ function isOnScreen(pos, size=0)
|
|
|
4000
4039
|
* @param {boolean} [additive]
|
|
4001
4040
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4002
4041
|
* @memberof Draw */
|
|
4003
|
-
function setBlendMode(additive=false, context)
|
|
4042
|
+
function setBlendMode(additive=false, context=drawContext)
|
|
4004
4043
|
{
|
|
4005
4044
|
glAdditive = additive;
|
|
4006
|
-
context ||= drawContext;
|
|
4007
4045
|
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
4008
4046
|
}
|
|
4009
4047
|
|
|
@@ -4018,7 +4056,6 @@ function combineCanvases()
|
|
|
4018
4056
|
workContext.fillRect(0,0,w,h); // remove background alpha
|
|
4019
4057
|
glCopyToContext(workContext);
|
|
4020
4058
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
4021
|
-
mainCanvas.width |= 0;
|
|
4022
4059
|
mainContext.drawImage(workCanvas, 0, 0);
|
|
4023
4060
|
}
|
|
4024
4061
|
|
|
@@ -4123,17 +4160,22 @@ function setCursor(cursorStyle = 'auto')
|
|
|
4123
4160
|
|
|
4124
4161
|
///////////////////////////////////////////////////////////////////////////////
|
|
4125
4162
|
|
|
4163
|
+
/** Engine font image, 8x8 font provided by the engine
|
|
4164
|
+
* @type {FontImage}
|
|
4165
|
+
* @memberof Draw */
|
|
4126
4166
|
let engineFontImage;
|
|
4127
4167
|
|
|
4128
4168
|
/**
|
|
4129
|
-
* Font Image Object - Draw text
|
|
4169
|
+
* Font Image Object - Draw text by using tiles in an image
|
|
4130
4170
|
* - 96 characters (from space to tilde) are stored in an image
|
|
4131
|
-
* -
|
|
4132
|
-
* -
|
|
4171
|
+
* - A 8x8 default engine font is supplied for general use
|
|
4172
|
+
* - This system is WebGL enabled for fast text rendering
|
|
4173
|
+
* - Fonts can also be colored and scaled along each axis
|
|
4174
|
+
*
|
|
4133
4175
|
* @memberof Draw
|
|
4134
4176
|
* @example
|
|
4135
4177
|
* // use built in font
|
|
4136
|
-
* const font =
|
|
4178
|
+
* const font = engineFontImage;
|
|
4137
4179
|
*
|
|
4138
4180
|
* // draw text
|
|
4139
4181
|
* font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
|
|
@@ -4141,70 +4183,112 @@ let engineFontImage;
|
|
|
4141
4183
|
class FontImage
|
|
4142
4184
|
{
|
|
4143
4185
|
/** Create an image font
|
|
4144
|
-
* @param {
|
|
4145
|
-
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
4146
|
-
* @param {Vector2} [paddingSize=(0,1)] - How much space between characters
|
|
4186
|
+
* @param {TileInfo} tileInfo - Tile info of first characeter in font
|
|
4147
4187
|
*/
|
|
4148
|
-
constructor(
|
|
4188
|
+
constructor(tileInfo)
|
|
4149
4189
|
{
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
{
|
|
4153
|
-
|
|
4154
|
-
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
4155
|
-
}
|
|
4156
|
-
|
|
4157
|
-
this.image = image || engineFontImage;
|
|
4158
|
-
this.tileSize = tileSize;
|
|
4159
|
-
this.paddingSize = paddingSize;
|
|
4190
|
+
ASSERT(!!tileInfo, 'tileInfo is required for FontImage');
|
|
4191
|
+
|
|
4192
|
+
/** @property {TileInfo} - Tile info for the font */
|
|
4193
|
+
this.tileInfo = tileInfo.frame(0);
|
|
4160
4194
|
}
|
|
4161
4195
|
|
|
4162
4196
|
/** Draw text in world space using the image font
|
|
4163
|
-
* @param {string|number}
|
|
4197
|
+
* @param {string|number} text
|
|
4164
4198
|
* @param {Vector2} pos
|
|
4165
|
-
* @param {number}
|
|
4166
|
-
* @param {boolean} [center]
|
|
4167
|
-
* @param {
|
|
4199
|
+
* @param {Vector2|number} [size]
|
|
4200
|
+
* @param {boolean} [center=true]
|
|
4201
|
+
* @param {Color} [color=WHITE]
|
|
4202
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
4203
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4168
4204
|
*/
|
|
4169
|
-
drawText(text, pos,
|
|
4205
|
+
drawText(text, pos, size=1, center, color, useWebGL, context)
|
|
4170
4206
|
{
|
|
4171
|
-
|
|
4207
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4208
|
+
|
|
4209
|
+
if (typeof size === 'number')
|
|
4210
|
+
{
|
|
4211
|
+
// if size is a number, make it a vector
|
|
4212
|
+
ASSERT(size > 0);
|
|
4213
|
+
size *= cameraScale;
|
|
4214
|
+
size = new Vector2(size, size);
|
|
4215
|
+
}
|
|
4216
|
+
else
|
|
4217
|
+
size = size.scale(cameraScale);
|
|
4218
|
+
this.drawTextScreen(text, worldToScreen(pos), size, center, color, useWebGL, context);
|
|
4172
4219
|
}
|
|
4173
4220
|
|
|
4174
4221
|
/** Draw text in screen space using the image font
|
|
4175
|
-
* @param {string|number}
|
|
4222
|
+
* @param {string|number} text
|
|
4176
4223
|
* @param {Vector2} pos
|
|
4177
|
-
* @param {number}
|
|
4224
|
+
* @param {Vector2|number} size
|
|
4178
4225
|
* @param {boolean} [center]
|
|
4179
|
-
* @param {
|
|
4226
|
+
* @param {Color} [color=WHITE]
|
|
4227
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
4228
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
4180
4229
|
*/
|
|
4181
|
-
drawTextScreen(text, pos,
|
|
4230
|
+
drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
|
|
4182
4231
|
{
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4232
|
+
ASSERT(isString(text), 'text must be a string');
|
|
4233
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
4234
|
+
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4235
|
+
ASSERT(isColor(color), 'color must be a color');
|
|
4236
|
+
|
|
4237
|
+
// if size is a number, make it a vector
|
|
4238
|
+
size = typeof size === 'number' ? new Vector2(size, size) : size;
|
|
4239
|
+
|
|
4240
|
+
// precache objects for drawing
|
|
4241
|
+
const drawPos = new Vector2;
|
|
4242
|
+
const tileInfo = this.tileInfo;
|
|
4243
|
+
const padding = tileInfo.padding;
|
|
4244
|
+
const sizePaddedX = tileInfo.size.x + padding*2;
|
|
4245
|
+
const sizePaddedY = tileInfo.size.y + padding*2;
|
|
4246
|
+
const cols = tileInfo.textureInfo.size.x / sizePaddedX |0;
|
|
4247
|
+
|
|
4248
|
+
// draw each line of text
|
|
4249
|
+
(text+'').split('\n').forEach((line, j)=>
|
|
4188
4250
|
{
|
|
4189
|
-
const centerOffset = center ? line.length * size.x
|
|
4190
|
-
for (let
|
|
4251
|
+
const centerOffset = center ? (line.length-1) * size.x / 2 : 0;
|
|
4252
|
+
for (let i=line.length; i--;)
|
|
4191
4253
|
{
|
|
4192
|
-
//
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
charCode
|
|
4196
|
-
|
|
4197
|
-
// get the
|
|
4198
|
-
const
|
|
4199
|
-
const
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4254
|
+
// get the character index
|
|
4255
|
+
const charCode = line.charCodeAt(i);
|
|
4256
|
+
const index = charCode < 32 || charCode > 127 ?
|
|
4257
|
+
95 : charCode - 32; // handle out of range characters
|
|
4258
|
+
|
|
4259
|
+
// get the position of the tile
|
|
4260
|
+
const x = index % cols;
|
|
4261
|
+
const y = index / cols |0;
|
|
4262
|
+
tileInfo.pos.x = x*sizePaddedX + padding;
|
|
4263
|
+
tileInfo.pos.y = y*sizePaddedY + padding;
|
|
4264
|
+
|
|
4265
|
+
// draw the tile
|
|
4266
|
+
drawPos.x = pos.x + i * size.x - centerOffset |0;
|
|
4267
|
+
drawPos.y = pos.y + j * size.y |0;
|
|
4268
|
+
drawTile(drawPos, size, tileInfo, color, 0, false, undefined, useWebGL, true, context);
|
|
4204
4269
|
}
|
|
4205
4270
|
});
|
|
4206
|
-
context.restore();
|
|
4207
4271
|
}
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
// load engine font, called automatically on startup
|
|
4275
|
+
function fontImageInit()
|
|
4276
|
+
{
|
|
4277
|
+
return new Promise(resolve =>
|
|
4278
|
+
{
|
|
4279
|
+
// create the engine font
|
|
4280
|
+
const image = new Image;
|
|
4281
|
+
image.onerror = image.onload = ()=>
|
|
4282
|
+
{
|
|
4283
|
+
const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
|
|
4284
|
+
const textureInfo = new TextureInfo(image);
|
|
4285
|
+
const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
|
|
4286
|
+
engineFontImage = new FontImage(tileInfo);
|
|
4287
|
+
resolve();
|
|
4288
|
+
}
|
|
4289
|
+
image.crossOrigin = 'anonymous';
|
|
4290
|
+
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAeAQMAAABnrVXaAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAjpJREFUOMu9kzFu2zAUhn+CAROgqrk+B2l0BWYxMjlXeYaAtFtbdA1sGgHqRQfI0CNkSG5AwYB0BQ8d5Bsomwah6CPVeGg6tEPzAxLwyI+P78cP4u9lNO9OoMKnLMOobG5020/yaj/MrRcCGh1gBbyiLTPJEYaIiom5KM9Jq7KgynMGtb6L4GL4MF2H4LQKCXTvDVw2I4MsgZT7QLExdiutH+D08VOP3INXRrWX1/mmpbkNgAPYRVANb4xpcegYvhiNbIXauQICEjBuYLfMakaakWQeXxiZ0VDtuJCKs3ztMV59QtsHJNcRxDzfdL21ty3PrfIcXTN+E+GFAv6T5nbT9jd50/WFxb5ksdAv49qS6ouymG66ji08UMT6moykYLAo+V0j23GN4m829ZySAD5K7QsBfQTvOG8eE+gTeGYRAmnNAubN3hf5Zv9tJWDHp/VTuaSm7SN4fyINQqaNO3RMVxvpSPXnOChnRNvFcGY0gnwiPswYwTKVPE0zVtX3mTEIOoFzaqLrGuJaV+Uqumb71fVk/VoOH3cdLNQP/FHi8hV0CQNoqBZsUPlLPMsdCJro9QAaQQ0woDy9BJm0eTxCFnO9srcYlhNVlfR2EyTrph1uUtbUtAJifwRgrKuYdXVHeb0YI3QpawohQHkloI3J5FuVwI5ORxC9k2Tuz9Ir1IjgeIPGMHYkAZe2RuYkmWFmt3gGbTPOmBUWVTmRmHtGrfpzG/yuQNOKa6gBB/WA9khitPgl6/GP+gl2Af6tCbvaygAAAABJRU5ErkJggg==';
|
|
4291
|
+
});
|
|
4208
4292
|
}
|
|
4209
4293
|
/**
|
|
4210
4294
|
* LittleJS Input System
|
|
@@ -4261,6 +4345,10 @@ let inputPreventDefault = true;
|
|
|
4261
4345
|
* @memberof Input */
|
|
4262
4346
|
let gamepadPrimary = 0;
|
|
4263
4347
|
|
|
4348
|
+
/** True if a touch device has been detected
|
|
4349
|
+
* @memberof Input */
|
|
4350
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
4351
|
+
|
|
4264
4352
|
/** Prevents input continuing to the default browser handling
|
|
4265
4353
|
* This is useful to disable for html menus so the browser can handle input normally
|
|
4266
4354
|
* @param {boolean} preventDefault
|
|
@@ -4459,10 +4547,6 @@ function gamepadStickCount(gamepad=gamepadPrimary)
|
|
|
4459
4547
|
return gamepadStickData[gamepad]?.length ?? 0;
|
|
4460
4548
|
}
|
|
4461
4549
|
|
|
4462
|
-
/** True if a touch device has been detected
|
|
4463
|
-
* @memberof Input */
|
|
4464
|
-
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
4465
|
-
|
|
4466
4550
|
///////////////////////////////////////////////////////////////////////////////
|
|
4467
4551
|
|
|
4468
4552
|
/** Pulse the vibration hardware if it exists
|
|
@@ -4471,7 +4555,7 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
|
4471
4555
|
function vibrate(pattern=100)
|
|
4472
4556
|
{
|
|
4473
4557
|
ASSERT(isNumber(pattern) || isArray(pattern), 'pattern must be a number or array');
|
|
4474
|
-
vibrateEnable && !headlessMode && navigator
|
|
4558
|
+
vibrateEnable && !headlessMode && navigator?.vibrate?.(pattern);
|
|
4475
4559
|
}
|
|
4476
4560
|
|
|
4477
4561
|
/** Cancel any ongoing vibration
|
|
@@ -4571,8 +4655,7 @@ function inputInit()
|
|
|
4571
4655
|
}
|
|
4572
4656
|
function onMouseDown(e)
|
|
4573
4657
|
{
|
|
4574
|
-
if (isTouchDevice && touchInputEnable)
|
|
4575
|
-
return;
|
|
4658
|
+
if (isTouchDevice && touchInputEnable) return;
|
|
4576
4659
|
|
|
4577
4660
|
// fix stalled audio requiring user interaction
|
|
4578
4661
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
@@ -4590,8 +4673,8 @@ function inputInit()
|
|
|
4590
4673
|
}
|
|
4591
4674
|
function onMouseUp(e)
|
|
4592
4675
|
{
|
|
4593
|
-
if (isTouchDevice && touchInputEnable)
|
|
4594
|
-
|
|
4676
|
+
if (isTouchDevice && touchInputEnable) return;
|
|
4677
|
+
|
|
4595
4678
|
inputData[0][e.button] = (inputData[0][e.button]&2) | 4;
|
|
4596
4679
|
}
|
|
4597
4680
|
function onMouseMove(e)
|
|
@@ -4623,8 +4706,7 @@ function inputInit()
|
|
|
4623
4706
|
let wasTouching;
|
|
4624
4707
|
function handleTouch(e)
|
|
4625
4708
|
{
|
|
4626
|
-
if (!touchInputEnable)
|
|
4627
|
-
return;
|
|
4709
|
+
if (!touchInputEnable) return;
|
|
4628
4710
|
|
|
4629
4711
|
// route touch to gamepad
|
|
4630
4712
|
if (touchGamepadEnable)
|
|
@@ -4688,8 +4770,7 @@ function inputInit()
|
|
|
4688
4770
|
}
|
|
4689
4771
|
|
|
4690
4772
|
// don't process touch gamepad if paused
|
|
4691
|
-
if (paused)
|
|
4692
|
-
return;
|
|
4773
|
+
if (paused) return;
|
|
4693
4774
|
|
|
4694
4775
|
// get center of left and right sides
|
|
4695
4776
|
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
@@ -4779,8 +4860,7 @@ function inputUpdate()
|
|
|
4779
4860
|
// update touch gamepad if enabled
|
|
4780
4861
|
if (touchGamepadEnable && isTouchDevice)
|
|
4781
4862
|
{
|
|
4782
|
-
if (!touchGamepadTimer.isSet())
|
|
4783
|
-
return;
|
|
4863
|
+
if (!touchGamepadTimer.isSet()) return;
|
|
4784
4864
|
|
|
4785
4865
|
// read virtual analog stick
|
|
4786
4866
|
gamepadPrimary = 0; // touch gamepad uses index 0
|
|
@@ -4818,12 +4898,10 @@ function inputUpdate()
|
|
|
4818
4898
|
}
|
|
4819
4899
|
|
|
4820
4900
|
// return if gamepads are disabled or not supported
|
|
4821
|
-
if (!gamepadsEnable || !navigator || !navigator.getGamepads)
|
|
4822
|
-
return;
|
|
4901
|
+
if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
|
|
4823
4902
|
|
|
4824
4903
|
// only poll gamepads when focused or in debug mode
|
|
4825
|
-
if (!debug && !document.hasFocus())
|
|
4826
|
-
return;
|
|
4904
|
+
if (!debug && !document.hasFocus()) return;
|
|
4827
4905
|
|
|
4828
4906
|
// poll gamepads
|
|
4829
4907
|
const maxGamepads = 8;
|
|
@@ -4860,8 +4938,7 @@ function inputUpdate()
|
|
|
4860
4938
|
data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
4861
4939
|
|
|
4862
4940
|
// check for any input on this gamepad, analog must be full press
|
|
4863
|
-
if (button.pressed)
|
|
4864
|
-
if (!button.value || button.value > .9)
|
|
4941
|
+
if (button.pressed && (!button.value || button.value > .9))
|
|
4865
4942
|
hadInput = true;
|
|
4866
4943
|
}
|
|
4867
4944
|
|
|
@@ -4890,7 +4967,7 @@ function inputUpdate()
|
|
|
4890
4967
|
}
|
|
4891
4968
|
|
|
4892
4969
|
// copy dpad to left analog stick when pressed
|
|
4893
|
-
if (gamepadDirectionEmulateStick &&
|
|
4970
|
+
if (gamepadDirectionEmulateStick && (dpad.x || dpad.y))
|
|
4894
4971
|
sticks[0] = dpad.clampLength();
|
|
4895
4972
|
}
|
|
4896
4973
|
|
|
@@ -4919,13 +4996,11 @@ function inputRender()
|
|
|
4919
4996
|
function touchGamepadRender()
|
|
4920
4997
|
{
|
|
4921
4998
|
if (!touchInputEnable || !isTouchDevice || headlessMode) return;
|
|
4922
|
-
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
4923
|
-
return;
|
|
4999
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet()) return;
|
|
4924
5000
|
|
|
4925
5001
|
// fade off when not touching or paused
|
|
4926
5002
|
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
4927
|
-
if (!alpha || paused)
|
|
4928
|
-
return;
|
|
5003
|
+
if (!alpha || paused) return;
|
|
4929
5004
|
|
|
4930
5005
|
// setup the canvas
|
|
4931
5006
|
const context = mainContext;
|
|
@@ -5078,8 +5153,7 @@ class Sound
|
|
|
5078
5153
|
{
|
|
5079
5154
|
// remove randomness so it can be applied on playback
|
|
5080
5155
|
const randomnessIndex = 1, defaultRandomness = .05;
|
|
5081
|
-
this.randomness = zzfxSound[randomnessIndex]
|
|
5082
|
-
zzfxSound[randomnessIndex] : defaultRandomness;
|
|
5156
|
+
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
5083
5157
|
zzfxSound[randomnessIndex] = 0;
|
|
5084
5158
|
|
|
5085
5159
|
// generate the zzfx samples
|
|
@@ -5159,7 +5233,7 @@ class Sound
|
|
|
5159
5233
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
5160
5234
|
*/
|
|
5161
5235
|
getDuration()
|
|
5162
|
-
{ return this.sampleChannels
|
|
5236
|
+
{ return this.sampleChannels?.[0].length / this.sampleRate || 0; }
|
|
5163
5237
|
|
|
5164
5238
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
5165
5239
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -5251,8 +5325,7 @@ class SoundWave extends Sound
|
|
|
5251
5325
|
this.sampleRate = audioBuffer.sampleRate;
|
|
5252
5326
|
this.sampleChannels = sampleChannels;
|
|
5253
5327
|
this.loadedPercent = 1;
|
|
5254
|
-
|
|
5255
|
-
this.onloadCallback(this);
|
|
5328
|
+
this.onloadCallback?.(this);
|
|
5256
5329
|
}
|
|
5257
5330
|
}
|
|
5258
5331
|
|
|
@@ -5375,8 +5448,7 @@ class SoundInstance
|
|
|
5375
5448
|
/** Pause this sound instance */
|
|
5376
5449
|
pause()
|
|
5377
5450
|
{
|
|
5378
|
-
if (this.isPaused())
|
|
5379
|
-
return;
|
|
5451
|
+
if (this.isPaused()) return;
|
|
5380
5452
|
|
|
5381
5453
|
// save current time and stop sound
|
|
5382
5454
|
this.pausedTime = this.getCurrentTime();
|
|
@@ -5388,8 +5460,7 @@ class SoundInstance
|
|
|
5388
5460
|
/** Unpauses this sound instance */
|
|
5389
5461
|
resume()
|
|
5390
5462
|
{
|
|
5391
|
-
if (!this.isPaused())
|
|
5392
|
-
return;
|
|
5463
|
+
if (!this.isPaused()) return;
|
|
5393
5464
|
|
|
5394
5465
|
// restart sound from paused time
|
|
5395
5466
|
this.start(this.pausedTime);
|
|
@@ -5457,7 +5528,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
5457
5528
|
|
|
5458
5529
|
/** Stop all queued speech
|
|
5459
5530
|
* @memberof Audio */
|
|
5460
|
-
function speakStop() {speechSynthesis
|
|
5531
|
+
function speakStop() {speechSynthesis?.cancel();}
|
|
5461
5532
|
|
|
5462
5533
|
/** Get frequency of a note on a musical scale
|
|
5463
5534
|
* @param {number} semitoneOffset - How many semitones away from the root note
|
|
@@ -5716,7 +5787,7 @@ function tileCollisionGetData(pos)
|
|
|
5716
5787
|
|
|
5717
5788
|
/** Check if a tile layer collides with another object
|
|
5718
5789
|
* @param {Vector2} pos
|
|
5719
|
-
* @param {Vector2} [size=(
|
|
5790
|
+
* @param {Vector2} [size=vec2()]
|
|
5720
5791
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
5721
5792
|
* @param {boolean} [solidOnly] - Only check solid layers if true
|
|
5722
5793
|
* @return {TileCollisionLayer}
|
|
@@ -5834,20 +5905,20 @@ function tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLa
|
|
|
5834
5905
|
class TileLayerData
|
|
5835
5906
|
{
|
|
5836
5907
|
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
5837
|
-
* @param {number} [tile]
|
|
5908
|
+
* @param {number} [tile] - The tile to use, untextured if undefined
|
|
5838
5909
|
* @param {number} [direction] - Integer direction of tile, in 90 degree increments
|
|
5839
|
-
* @param {boolean} [mirror]
|
|
5840
|
-
* @param {Color} [color]
|
|
5910
|
+
* @param {boolean} [mirror] - If the tile should be mirrored along the x axis
|
|
5911
|
+
* @param {Color} [color] - Color of the tile */
|
|
5841
5912
|
constructor(tile, direction=0, mirror=false, color=new Color)
|
|
5842
5913
|
{
|
|
5843
|
-
/** @property {number}
|
|
5844
|
-
this.tile
|
|
5845
|
-
/** @property {number}
|
|
5914
|
+
/** @property {number} - The tile to use, untextured if undefined */
|
|
5915
|
+
this.tile = tile;
|
|
5916
|
+
/** @property {number} - Integer direction of tile, in 90 degree increments */
|
|
5846
5917
|
this.direction = direction;
|
|
5847
5918
|
/** @property {boolean} - If the tile should be mirrored along the x axis */
|
|
5848
|
-
this.mirror
|
|
5849
|
-
/** @property {Color}
|
|
5850
|
-
this.color
|
|
5919
|
+
this.mirror = mirror;
|
|
5920
|
+
/** @property {Color} - Color of the tile */
|
|
5921
|
+
this.color = color.copy();
|
|
5851
5922
|
}
|
|
5852
5923
|
|
|
5853
5924
|
/** Set this tile to clear, it will not be rendered */
|
|
@@ -5858,7 +5929,7 @@ class TileLayerData
|
|
|
5858
5929
|
/**
|
|
5859
5930
|
* Canvas Layer - cached off screen rendering system
|
|
5860
5931
|
* - Contains an offscreen canvas that can be rendered to
|
|
5861
|
-
* - WebGL rendering is optional, call
|
|
5932
|
+
* - WebGL rendering is optional, call updateWebGL to enable/update
|
|
5862
5933
|
* @extends EngineObject
|
|
5863
5934
|
* @memberof TileLayers
|
|
5864
5935
|
* @example
|
|
@@ -5872,8 +5943,9 @@ class CanvasLayer extends EngineObject
|
|
|
5872
5943
|
* @param {number} [angle] - Angle the layer is rotated by
|
|
5873
5944
|
* @param {number} [renderOrder] - Objects sorted by renderOrder
|
|
5874
5945
|
* @param {Vector2} [canvasSize] - Default size of canvas, can be changed later
|
|
5946
|
+
* @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
|
|
5875
5947
|
*/
|
|
5876
|
-
constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512))
|
|
5948
|
+
constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512), useWebGL=glEnable)
|
|
5877
5949
|
{
|
|
5878
5950
|
ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
|
|
5879
5951
|
super(position, size, undefined, angle, WHITE, renderOrder);
|
|
@@ -5883,13 +5955,10 @@ class CanvasLayer extends EngineObject
|
|
|
5883
5955
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
|
|
5884
5956
|
this.context = this.canvas?.getContext('2d');
|
|
5885
5957
|
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
5886
|
-
const useWebGL = false; // do not use webgl by default
|
|
5887
5958
|
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
5888
|
-
/** @property {boolean} - True if WebGL texture needs to be refreshed */
|
|
5889
|
-
this.refreshWebGL = false;
|
|
5890
5959
|
|
|
5891
5960
|
// disable physics by default
|
|
5892
|
-
this.mass =
|
|
5961
|
+
this.mass = 0;
|
|
5893
5962
|
}
|
|
5894
5963
|
|
|
5895
5964
|
/** Destroy this canvas layer */
|
|
@@ -5904,7 +5973,7 @@ class CanvasLayer extends EngineObject
|
|
|
5904
5973
|
// Render the layer, called automatically by the engine
|
|
5905
5974
|
render()
|
|
5906
5975
|
{
|
|
5907
|
-
this.draw(this.pos, this.size, this.
|
|
5976
|
+
this.draw(this.pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
5908
5977
|
}
|
|
5909
5978
|
|
|
5910
5979
|
/** Draw this canvas layer centered in world space, with color applied if using WebGL
|
|
@@ -5917,103 +5986,54 @@ class CanvasLayer extends EngineObject
|
|
|
5917
5986
|
* @param {boolean} [screenSpace] - If true the pos and size are in screen space
|
|
5918
5987
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
5919
5988
|
* @memberof Draw */
|
|
5920
|
-
draw(pos, size,
|
|
5989
|
+
draw(pos, size, color=WHITE, angle=0, mirror=false, additiveColor, screenSpace=false, context)
|
|
5921
5990
|
{
|
|
5922
|
-
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
5923
|
-
if (useWebGL && this.refreshWebGL)
|
|
5924
|
-
{
|
|
5925
|
-
// update the WebGL texture
|
|
5926
|
-
this.textureInfo.createWebGLTexture();
|
|
5927
|
-
this.refreshWebGL = false;
|
|
5928
|
-
}
|
|
5929
|
-
|
|
5930
5991
|
// draw the canvas layer as a single tile that uses the whole texture
|
|
5931
5992
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
5993
|
+
const useWebGL = this.hasWebGL();
|
|
5932
5994
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
5933
5995
|
}
|
|
5934
5996
|
|
|
5935
|
-
/**
|
|
5936
|
-
* @callback Canvas2DDrawCallback - Function that draws to a canvas 2D context
|
|
5937
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
5938
|
-
* @memberof TileLayers
|
|
5939
|
-
*/
|
|
5940
|
-
|
|
5941
|
-
/** Draw onto the layer canvas in world space (bypass WebGL)
|
|
5997
|
+
/** Draw a tile onto the layer canvas in world space
|
|
5942
5998
|
* @param {Vector2} pos
|
|
5943
|
-
* @param {Vector2} size
|
|
5944
|
-
* @param {
|
|
5945
|
-
* @param {
|
|
5946
|
-
* @param {
|
|
5947
|
-
|
|
5999
|
+
* @param {Vector2} [size=vec2(1)]
|
|
6000
|
+
* @param {TileInfo} [tileInfo]
|
|
6001
|
+
* @param {Color} [color=WHITE]
|
|
6002
|
+
* @param {number} [angle]
|
|
6003
|
+
* @param {boolean} [mirror] */
|
|
6004
|
+
drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle=0, mirror=false)
|
|
5948
6005
|
{
|
|
5949
|
-
if (!this.context) return;
|
|
5950
|
-
|
|
5951
|
-
const context = this.context;
|
|
5952
|
-
context.save();
|
|
5953
6006
|
pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
|
|
5954
6007
|
size = size.multiply(this.tileInfo.size);
|
|
5955
|
-
|
|
5956
|
-
context.rotate(angle);
|
|
5957
|
-
context.scale(mirror ? -size.x : size.x, size.y);
|
|
5958
|
-
drawFunction(context);
|
|
5959
|
-
context.restore();
|
|
5960
|
-
}
|
|
6008
|
+
pos.y = this.canvas.height - pos.y;
|
|
5961
6009
|
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
this.drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
5972
|
-
{
|
|
5973
|
-
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
5974
|
-
if (textureInfo)
|
|
5975
|
-
{
|
|
5976
|
-
context.globalAlpha = color.a; // only alpha is supported
|
|
5977
|
-
context.drawImage(textureInfo.image,
|
|
5978
|
-
tileInfo.pos.x, tileInfo.pos.y,
|
|
5979
|
-
tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
|
|
5980
|
-
context.globalAlpha = 1;
|
|
5981
|
-
}
|
|
5982
|
-
else
|
|
5983
|
-
{
|
|
5984
|
-
// untextured
|
|
5985
|
-
context.fillStyle = color.toString();
|
|
5986
|
-
context.fillRect(-.5, -.5, 1, 1);
|
|
5987
|
-
}
|
|
5988
|
-
});
|
|
6010
|
+
// draw the tile onto the layer canvas
|
|
6011
|
+
const oldMainCanvasSize = mainCanvasSize;
|
|
6012
|
+
mainCanvasSize = vec2(this.canvas.width, this.canvas.height);
|
|
6013
|
+
const useWebGL = this.hasWebGL();
|
|
6014
|
+
useWebGL && glSetRenderTarget(this.textureInfo.glTexture);
|
|
6015
|
+
const drawContext = useWebGL ? undefined : this.context;
|
|
6016
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, undefined, useWebGL, true, drawContext);
|
|
6017
|
+
useWebGL && glSetRenderTarget();
|
|
6018
|
+
mainCanvasSize = oldMainCanvasSize;
|
|
5989
6019
|
}
|
|
5990
6020
|
|
|
5991
6021
|
/** Draw a rectangle onto the layer canvas in world space
|
|
5992
6022
|
* @param {Vector2} pos
|
|
5993
|
-
* @param {Vector2} [size=(1
|
|
5994
|
-
* @param {Color} [color=
|
|
5995
|
-
* @param {number} [angle
|
|
6023
|
+
* @param {Vector2} [size=vec2(1)]
|
|
6024
|
+
* @param {Color} [color=WHITE]
|
|
6025
|
+
* @param {number} [angle] */
|
|
5996
6026
|
drawRect(pos, size, color, angle)
|
|
5997
6027
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
5998
6028
|
|
|
5999
|
-
/** Create
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
*/
|
|
6003
|
-
useWebGL(enable=true, immediate=false)
|
|
6004
|
-
{
|
|
6005
|
-
if (!immediate && enable && this.textureInfo.hasWebGL())
|
|
6006
|
-
{
|
|
6007
|
-
// refresh the texture when needed
|
|
6008
|
-
this.refreshWebGL = true;
|
|
6009
|
-
return;
|
|
6010
|
-
}
|
|
6029
|
+
/** Create WebGL texture if necessary and copy layer canvas to it */
|
|
6030
|
+
updateWebGL()
|
|
6031
|
+
{ this.textureInfo.createWebGLTexture(); }
|
|
6011
6032
|
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
}
|
|
6033
|
+
/** Check if this layer is using WebGL
|
|
6034
|
+
* @return {boolean} */
|
|
6035
|
+
hasWebGL()
|
|
6036
|
+
{ return glEnable && this.textureInfo.hasWebGL(); }
|
|
6017
6037
|
}
|
|
6018
6038
|
|
|
6019
6039
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -6032,50 +6052,67 @@ class CanvasLayer extends EngineObject
|
|
|
6032
6052
|
class TileLayer extends CanvasLayer
|
|
6033
6053
|
{
|
|
6034
6054
|
/** Create a tile layer object
|
|
6035
|
-
* @param {Vector2} position
|
|
6036
|
-
* @param {Vector2} size
|
|
6037
|
-
* @param {TileInfo} [tileInfo]
|
|
6055
|
+
* @param {Vector2} position - World space position
|
|
6056
|
+
* @param {Vector2} size - World space size
|
|
6057
|
+
* @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
|
|
6038
6058
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6039
6059
|
*/
|
|
6040
6060
|
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
6041
6061
|
{
|
|
6042
6062
|
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6063
|
+
const useWebGL = true;
|
|
6064
|
+
super(position, size, 0, renderOrder, canvasSize, useWebGL);
|
|
6065
|
+
|
|
6066
|
+
/** @property {TileInfo} - Default tile info for layer */
|
|
6067
|
+
this.tileInfo = undefined;
|
|
6068
|
+
/** @property {Array<TileLayerData>} - Default tile info for layer */
|
|
6049
6069
|
this.data = [];
|
|
6050
|
-
|
|
6051
|
-
|
|
6070
|
+
/** @property {boolean} - Is this layer using a webgl texture? */
|
|
6071
|
+
this.isUsingWebGL = false;
|
|
6052
6072
|
|
|
6053
6073
|
if (headlessMode)
|
|
6054
6074
|
{
|
|
6055
|
-
// disable rendering
|
|
6056
|
-
this.
|
|
6057
|
-
this.
|
|
6058
|
-
this.redrawStart
|
|
6059
|
-
this.redrawEnd
|
|
6060
|
-
this.drawTileData
|
|
6061
|
-
this.
|
|
6062
|
-
this.
|
|
6075
|
+
// disable rendering in headless mode
|
|
6076
|
+
this.render = () => {};
|
|
6077
|
+
this.redraw = () => {};
|
|
6078
|
+
this.redrawStart = () => {};
|
|
6079
|
+
this.redrawEnd = () => {};
|
|
6080
|
+
this.drawTileData = () => {};
|
|
6081
|
+
this.redrawTileData = () => {};
|
|
6082
|
+
this.drawLayerTile = () => {};
|
|
6083
|
+
this.drawLayerRect = () => {};
|
|
6084
|
+
this.clearLayerRect = () => {};
|
|
6085
|
+
return;
|
|
6063
6086
|
}
|
|
6087
|
+
|
|
6088
|
+
if (tileInfo)
|
|
6089
|
+
{
|
|
6090
|
+
// set tile info
|
|
6091
|
+
this.tileInfo = tileInfo.frame(0);
|
|
6092
|
+
this.tileInfo.bleed = 0; // disable bleed for tile layers
|
|
6093
|
+
}
|
|
6094
|
+
|
|
6095
|
+
// init tile data
|
|
6096
|
+
for (let j = this.size.area(); j--;)
|
|
6097
|
+
this.data.push(new TileLayerData);
|
|
6064
6098
|
}
|
|
6065
6099
|
|
|
6066
6100
|
/** Set data at a given position in the array
|
|
6067
6101
|
* @param {Vector2} layerPos - Local position in array
|
|
6068
|
-
* @param {TileLayerData} data
|
|
6102
|
+
* @param {TileLayerData} data - Data to set
|
|
6069
6103
|
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
6070
6104
|
setData(layerPos, data, redraw=false)
|
|
6071
6105
|
{
|
|
6106
|
+
layerPos = layerPos.floor();
|
|
6072
6107
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6073
6108
|
ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6109
|
+
|
|
6110
|
+
if (!layerPos.arrayCheck(this.size)) return;
|
|
6111
|
+
this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
|
|
6112
|
+
|
|
6113
|
+
if (!redraw) return;
|
|
6114
|
+
const isRedraw = drawContext === this.context;
|
|
6115
|
+
isRedraw ? this.drawTileData(layerPos) : this.redrawTileData(layerPos);
|
|
6079
6116
|
}
|
|
6080
6117
|
|
|
6081
6118
|
/** Get data at a given position in the array
|
|
@@ -6084,7 +6121,18 @@ class TileLayer extends CanvasLayer
|
|
|
6084
6121
|
getData(layerPos)
|
|
6085
6122
|
{
|
|
6086
6123
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6087
|
-
return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
|
|
6124
|
+
return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
|
|
6125
|
+
}
|
|
6126
|
+
|
|
6127
|
+
// Update the tile layer, refresh texture if needed
|
|
6128
|
+
update()
|
|
6129
|
+
{
|
|
6130
|
+
if (!glEnable && this.isUsingWebGL)
|
|
6131
|
+
{
|
|
6132
|
+
// redraw the layer if webgl was disabled or context lost
|
|
6133
|
+
this.isUsingWebGL = false;
|
|
6134
|
+
this.redraw();
|
|
6135
|
+
}
|
|
6088
6136
|
}
|
|
6089
6137
|
|
|
6090
6138
|
// Render the tile layer, called automatically by the engine
|
|
@@ -6092,78 +6140,76 @@ class TileLayer extends CanvasLayer
|
|
|
6092
6140
|
{
|
|
6093
6141
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
6094
6142
|
|
|
6095
|
-
if (this.refreshWebGL)
|
|
6096
|
-
{
|
|
6097
|
-
// update the WebGL texture
|
|
6098
|
-
this.textureInfo.createWebGLTexture();
|
|
6099
|
-
this.refreshWebGL = false;
|
|
6100
|
-
}
|
|
6101
|
-
|
|
6102
|
-
// draw the tile layer as a single tile
|
|
6103
|
-
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
6104
6143
|
const size = this.drawSize || this.size;
|
|
6105
6144
|
const pos = this.pos.add(size.scale(.5));
|
|
6106
|
-
|
|
6107
|
-
drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
|
|
6145
|
+
this.draw(pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
6108
6146
|
}
|
|
6109
6147
|
|
|
6148
|
+
/** Called after this layer is redrawn, does nothing by default */
|
|
6149
|
+
onRedraw() {}
|
|
6150
|
+
|
|
6110
6151
|
/** Draw all the tile data to an offscreen canvas
|
|
6111
|
-
* - This may be slow
|
|
6152
|
+
* - This may be slow if not using webgl but only needs to be done once */
|
|
6112
6153
|
redraw()
|
|
6113
6154
|
{
|
|
6114
6155
|
this.redrawStart(true);
|
|
6115
6156
|
for (let x = this.size.x; x--;)
|
|
6116
6157
|
for (let y = this.size.y; y--;)
|
|
6117
6158
|
this.drawTileData(vec2(x,y), false);
|
|
6159
|
+
this.hasWebGL() && glFlush();
|
|
6160
|
+
this.onRedraw();
|
|
6118
6161
|
this.redrawEnd();
|
|
6119
|
-
this.useWebGL();
|
|
6120
6162
|
}
|
|
6121
6163
|
|
|
6122
6164
|
/** Call to start the redraw process
|
|
6123
|
-
* - This can be used to manually update
|
|
6165
|
+
* - This can be used to manually update parts of the level
|
|
6124
6166
|
* @param {boolean} [clear] - Should it clear the canvas before drawing */
|
|
6125
6167
|
redrawStart(clear=false)
|
|
6126
6168
|
{
|
|
6127
6169
|
if (!this.context) return;
|
|
6128
|
-
|
|
6170
|
+
ASSERT(drawContext !== this.context);
|
|
6171
|
+
|
|
6129
6172
|
// save current render settings
|
|
6130
|
-
/** @type {[
|
|
6131
|
-
this.savedRenderSettings = [
|
|
6173
|
+
/** @type {[CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
|
|
6174
|
+
this.savedRenderSettings = [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
|
|
6132
6175
|
|
|
6133
6176
|
// set the draw canvas and context to this layer
|
|
6134
6177
|
// use camera settings to match this layer's canvas
|
|
6135
|
-
drawCanvas = this.canvas;
|
|
6136
6178
|
drawContext = this.context;
|
|
6137
|
-
|
|
6138
|
-
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
6139
|
-
cameraScale = tileSize.x;
|
|
6140
|
-
canvasClearColor = CLEAR_BLACK;
|
|
6179
|
+
const tileSize = this.tileInfo?.size ?? vec2(1);
|
|
6141
6180
|
mainCanvasSize = this.size.multiply(tileSize);
|
|
6142
|
-
|
|
6181
|
+
canvasClearColor = CLEAR_BLACK;
|
|
6182
|
+
cameraPos = this.size.multiply(tileSize).scale(.5);
|
|
6183
|
+
cameraScale = 1;
|
|
6184
|
+
|
|
6185
|
+
// set render target to this layer
|
|
6186
|
+
this.isUsingWebGL = this.hasWebGL();
|
|
6187
|
+
if (this.isUsingWebGL)
|
|
6188
|
+
glSetRenderTarget(this.textureInfo.glTexture, clear);
|
|
6189
|
+
else
|
|
6143
6190
|
{
|
|
6144
|
-
//
|
|
6145
|
-
|
|
6146
|
-
|
|
6191
|
+
// disable smoothing for pixel art
|
|
6192
|
+
this.context.imageSmoothingEnabled = !tilesPixelated;
|
|
6193
|
+
if (clear)
|
|
6194
|
+
{
|
|
6195
|
+
// clear and set size
|
|
6196
|
+
this.canvas.width = mainCanvasSize.x;
|
|
6197
|
+
this.canvas.height = mainCanvasSize.y;
|
|
6198
|
+
}
|
|
6147
6199
|
}
|
|
6148
|
-
|
|
6149
|
-
// disable smoothing for pixel art
|
|
6150
|
-
drawContext.imageSmoothingEnabled = !tilesPixelated;
|
|
6151
|
-
|
|
6152
|
-
// setup gl rendering if enabled
|
|
6153
|
-
glPreRender();
|
|
6154
6200
|
}
|
|
6155
6201
|
|
|
6156
6202
|
/** Call to end the redraw process */
|
|
6157
6203
|
redrawEnd()
|
|
6158
6204
|
{
|
|
6159
6205
|
if (!this.context) return;
|
|
6206
|
+
ASSERT(drawContext === this.context);
|
|
6160
6207
|
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
//debugSaveCanvas(this.canvas);
|
|
6208
|
+
if (glEnable && this.textureInfo.glTexture)
|
|
6209
|
+
glSetRenderTarget();
|
|
6164
6210
|
|
|
6165
6211
|
// set stuff back to normal
|
|
6166
|
-
[
|
|
6212
|
+
[drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
|
|
6167
6213
|
}
|
|
6168
6214
|
|
|
6169
6215
|
/** Draw the tile at a given position in the tile grid
|
|
@@ -6175,24 +6221,75 @@ class TileLayer extends CanvasLayer
|
|
|
6175
6221
|
drawTileData(layerPos, clear=true)
|
|
6176
6222
|
{
|
|
6177
6223
|
if (!this.context) return;
|
|
6224
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
6178
6225
|
|
|
6179
|
-
// clear out where the tile was, for
|
|
6180
|
-
const
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
const pos = layerPos.multiply(s);
|
|
6184
|
-
this.context.clearRect(pos.x, this.canvas.height-pos.y, s.x, -s.y);
|
|
6185
|
-
}
|
|
6226
|
+
// clear out where the tile was, can be skipped for fully opaque tiles
|
|
6227
|
+
const drawSize = this.tileInfo?.size ?? vec2(1);
|
|
6228
|
+
const drawPos = layerPos.multiply(drawSize);
|
|
6229
|
+
clear && this.clearLayerRect(drawPos, drawSize);
|
|
6186
6230
|
|
|
6187
6231
|
// draw the tile if it has layer data
|
|
6188
6232
|
const d = this.getData(layerPos);
|
|
6189
|
-
if (d.tile
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6233
|
+
if (!d.tile) return;
|
|
6234
|
+
|
|
6235
|
+
const tileInfo = this.tileInfo && this.tileInfo.tile(d.tile);
|
|
6236
|
+
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
6237
|
+
}
|
|
6238
|
+
|
|
6239
|
+
/** Draw the tile at a given position in the tile grid
|
|
6240
|
+
* This can be used to clear tiles when they are destroyed
|
|
6241
|
+
* For better performance use drawTileData inside a redrawStart/End block
|
|
6242
|
+
* @param {Vector2} layerPos
|
|
6243
|
+
* @param {boolean} [clear] - should the old tile be cleared
|
|
6244
|
+
*/
|
|
6245
|
+
redrawTileData(layerPos, clear=true)
|
|
6246
|
+
{
|
|
6247
|
+
if (!this.context) return;
|
|
6248
|
+
ASSERT(drawContext !== this.context, 'redrawStart() should not be active when calling redrawTileData(), instead use drawTileData()');
|
|
6249
|
+
|
|
6250
|
+
this.redrawStart();
|
|
6251
|
+
this.drawTileData(layerPos, clear);
|
|
6252
|
+
this.redrawEnd();
|
|
6253
|
+
}
|
|
6254
|
+
|
|
6255
|
+
/** Draw textured tile in layer space
|
|
6256
|
+
* @param {Vector2} pos - Position in pixel coordinates
|
|
6257
|
+
* @param {Vector2} [size=vec2(1)] - Size of the tile
|
|
6258
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
6259
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
6260
|
+
* @param {number} [angle] - Angle to rotate by
|
|
6261
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
6262
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any */
|
|
6263
|
+
drawLayerTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
6264
|
+
angle=0, mirror, additiveColor)
|
|
6265
|
+
{
|
|
6266
|
+
const drawPos = pos.add(size.scale(.5));
|
|
6267
|
+
drawTile(drawPos, size, tileInfo, color, angle, mirror, additiveColor);
|
|
6268
|
+
}
|
|
6269
|
+
|
|
6270
|
+
/** Clear a rectangle in layer space
|
|
6271
|
+
* @param {Vector2} pos
|
|
6272
|
+
* @param {Vector2} size
|
|
6273
|
+
* @param {Color} [color=WHITE] - Color to modulate with
|
|
6274
|
+
* @param {number} [angle] - Angle to rotate by
|
|
6275
|
+
*/
|
|
6276
|
+
drawLayerRect(pos, size, color, angle=0)
|
|
6277
|
+
{ this.drawLayerTile(pos, size, undefined, color, angle); }
|
|
6278
|
+
|
|
6279
|
+
/** Clear a rectangle in layer space
|
|
6280
|
+
* @param {Vector2} pos - position in pixel coordinates
|
|
6281
|
+
* @param {Vector2} size
|
|
6282
|
+
*/
|
|
6283
|
+
clearLayerRect(pos, size)
|
|
6284
|
+
{
|
|
6285
|
+
ASSERT(drawContext === this.context, 'must call redrawStart() before clearing tiles');
|
|
6286
|
+
|
|
6287
|
+
const x = pos.x, y = this.canvas.height - pos.y - size.y;
|
|
6288
|
+
const useWebGL = this.hasWebGL();
|
|
6289
|
+
if (useWebGL)
|
|
6290
|
+
glClearRect(x, y, size.x, size.y);
|
|
6291
|
+
else
|
|
6292
|
+
this.context.clearRect(x, y, size.x, size.y);
|
|
6196
6293
|
}
|
|
6197
6294
|
}
|
|
6198
6295
|
|
|
@@ -6201,7 +6298,6 @@ class TileLayer extends CanvasLayer
|
|
|
6201
6298
|
* Tile Collision Layer - a tile layer with collision
|
|
6202
6299
|
* - adds collision data and functions to TileLayer
|
|
6203
6300
|
* - there can be multiple tile collision layers
|
|
6204
|
-
* - tile collision layers should not overlap each other
|
|
6205
6301
|
* @extends TileLayer
|
|
6206
6302
|
* @memberof TileLayers
|
|
6207
6303
|
*/
|
|
@@ -6231,8 +6327,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6231
6327
|
/** Destroy this tile layer */
|
|
6232
6328
|
destroy()
|
|
6233
6329
|
{
|
|
6234
|
-
if (this.destroyed)
|
|
6235
|
-
return;
|
|
6330
|
+
if (this.destroyed) return;
|
|
6236
6331
|
|
|
6237
6332
|
// remove from collision layers array and destroy
|
|
6238
6333
|
const index = tileCollisionLayers.indexOf(this);
|
|
@@ -6274,7 +6369,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6274
6369
|
|
|
6275
6370
|
/** Check if collision with another object should occur
|
|
6276
6371
|
* @param {Vector2} pos
|
|
6277
|
-
* @param {Vector2} [size=(
|
|
6372
|
+
* @param {Vector2} [size=vec2()]
|
|
6278
6373
|
* @param {EngineObject} [object]
|
|
6279
6374
|
* @return {boolean} */
|
|
6280
6375
|
collisionTest(pos, size=new Vector2, object)
|
|
@@ -6594,7 +6689,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6594
6689
|
particle.mirror = randBool();
|
|
6595
6690
|
|
|
6596
6691
|
// call particle create callback
|
|
6597
|
-
this.particleCreateCallback
|
|
6692
|
+
this.particleCreateCallback?.(particle);
|
|
6598
6693
|
|
|
6599
6694
|
// return the newly created particle
|
|
6600
6695
|
return particle;
|
|
@@ -6679,7 +6774,7 @@ class Particle extends EngineObject
|
|
|
6679
6774
|
const c = this.colorEnd;
|
|
6680
6775
|
this.color.set(c.r, c.g, c.b, c.a);
|
|
6681
6776
|
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
6682
|
-
this.destroyCallback
|
|
6777
|
+
this.destroyCallback?.(this);
|
|
6683
6778
|
this.destroyed = 1;
|
|
6684
6779
|
}
|
|
6685
6780
|
}
|
|
@@ -6769,10 +6864,11 @@ function medalsInit(saveName)
|
|
|
6769
6864
|
|
|
6770
6865
|
// engine automatically renders medals
|
|
6771
6866
|
engineAddPlugin(undefined, medalsRender);
|
|
6867
|
+
|
|
6868
|
+
// plugin functions
|
|
6772
6869
|
function medalsRender()
|
|
6773
6870
|
{
|
|
6774
|
-
if (!medalsDisplayQueue.length)
|
|
6775
|
-
return;
|
|
6871
|
+
if (!medalsDisplayQueue.length) return;
|
|
6776
6872
|
|
|
6777
6873
|
// update first medal in queue
|
|
6778
6874
|
const medal = medalsDisplayQueue[0];
|
|
@@ -6862,8 +6958,7 @@ class Medal
|
|
|
6862
6958
|
/** Unlocks a medal if not already unlocked */
|
|
6863
6959
|
unlock()
|
|
6864
6960
|
{
|
|
6865
|
-
if (medalsPreventUnlock || this.unlocked)
|
|
6866
|
-
return;
|
|
6961
|
+
if (medalsPreventUnlock || this.unlocked) return;
|
|
6867
6962
|
|
|
6868
6963
|
// save the medal
|
|
6869
6964
|
ASSERT(medalsSaveName, 'save name must be set');
|
|
@@ -6957,7 +7052,7 @@ let glContext;
|
|
|
6957
7052
|
let glAntialias = true;
|
|
6958
7053
|
|
|
6959
7054
|
// WebGL internal variables not exposed to documentation
|
|
6960
|
-
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
|
|
7055
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glInstancedVAO, glPolyVAO, glFramebuffer, glRenderTarget, glCanBeEnabled = true;
|
|
6961
7056
|
|
|
6962
7057
|
// WebGL internal constants
|
|
6963
7058
|
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
@@ -7033,7 +7128,7 @@ function glInit(rootElement)
|
|
|
7033
7128
|
// setup instanced rendering shader program
|
|
7034
7129
|
glShader = glCreateProgram(
|
|
7035
7130
|
'#version 300 es\n' + // specify GLSL ES version
|
|
7036
|
-
'precision highp float;'+ // use highp for
|
|
7131
|
+
'precision highp float;'+ // use highp for accuracy
|
|
7037
7132
|
'uniform mat4 m;'+ // transform matrix
|
|
7038
7133
|
'in vec2 g;'+ // in: geometry
|
|
7039
7134
|
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
@@ -7048,7 +7143,7 @@ function glInit(rootElement)
|
|
|
7048
7143
|
'}' // end of shader
|
|
7049
7144
|
,
|
|
7050
7145
|
'#version 300 es\n' + // specify GLSL ES version
|
|
7051
|
-
'precision highp float;'+ // use highp for
|
|
7146
|
+
'precision highp float;'+ // use highp for accuracy
|
|
7052
7147
|
'uniform sampler2D s;'+ // texture
|
|
7053
7148
|
'in vec2 v;'+ // in: uv
|
|
7054
7149
|
'in vec4 d,e;'+ // in: color, additiveColor
|
|
@@ -7086,87 +7181,96 @@ function glInit(rootElement)
|
|
|
7086
7181
|
glColorData = new Uint32Array(glInstanceData);
|
|
7087
7182
|
glArrayBuffer = glContext.createBuffer();
|
|
7088
7183
|
glGeometryBuffer = glContext.createBuffer();
|
|
7184
|
+
glFramebuffer = glContext.createFramebuffer();
|
|
7185
|
+
glBatchCount = 0;
|
|
7089
7186
|
|
|
7090
7187
|
// create the geometry buffer, triangle strip square
|
|
7091
|
-
const geometry = new Float32Array([
|
|
7188
|
+
const geometry = new Float32Array([0,0,1,0,0,1,1,1]);
|
|
7092
7189
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7093
7190
|
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
7191
|
+
|
|
7192
|
+
let offset, shader, stride;
|
|
7193
|
+
const initVertexAttrib = (name, type, typeSize, size, divisor=0)=>
|
|
7194
|
+
{
|
|
7195
|
+
const location = glContext.getAttribLocation(shader, name);
|
|
7196
|
+
const normalize = typeSize === 1;
|
|
7197
|
+
const fixedStride = typeSize && stride;
|
|
7198
|
+
glContext.enableVertexAttribArray(location);
|
|
7199
|
+
glContext.vertexAttribPointer(location, size, type, normalize, fixedStride, offset);
|
|
7200
|
+
glContext.vertexAttribDivisor(location, divisor);
|
|
7201
|
+
offset += size*typeSize;
|
|
7202
|
+
}
|
|
7203
|
+
|
|
7204
|
+
// setup VAO for instanced rendering
|
|
7205
|
+
glInstancedVAO = glContext.createVertexArray();
|
|
7206
|
+
glContext.bindVertexArray(glInstancedVAO);
|
|
7207
|
+
|
|
7208
|
+
// configure instanced vertex attributes
|
|
7209
|
+
offset = 0, shader = glShader, stride = gl_INSTANCE_BYTE_STRIDE;
|
|
7210
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7211
|
+
initVertexAttrib('g', glContext.FLOAT, 0, 2); // geometry
|
|
7212
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7213
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7214
|
+
initVertexAttrib('p', glContext.FLOAT, 4, 4, 1); // position & size
|
|
7215
|
+
initVertexAttrib('u', glContext.FLOAT, 4, 4, 1); // texture coords
|
|
7216
|
+
initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4, 1); // color
|
|
7217
|
+
initVertexAttrib('a', glContext.UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
7218
|
+
initVertexAttrib('r', glContext.FLOAT, 4, 1, 1); // rotation
|
|
7219
|
+
|
|
7220
|
+
// setup VAO for poly rendering
|
|
7221
|
+
glPolyVAO = glContext.createVertexArray();
|
|
7222
|
+
glContext.bindVertexArray(glPolyVAO);
|
|
7223
|
+
|
|
7224
|
+
// configure poly vertex attributes
|
|
7225
|
+
offset = 0, shader = glPolyShader, stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
7226
|
+
initVertexAttrib('p', glContext.FLOAT, 4, 2); // position
|
|
7227
|
+
initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7094
7228
|
}
|
|
7095
7229
|
}
|
|
7096
7230
|
|
|
7097
|
-
function glSetInstancedMode()
|
|
7231
|
+
function glSetInstancedMode(force=false)
|
|
7098
7232
|
{
|
|
7099
|
-
if (!glPolyMode)
|
|
7100
|
-
return;
|
|
7233
|
+
if (!force && !glPolyMode) return;
|
|
7101
7234
|
|
|
7102
7235
|
// setup instanced mode
|
|
7103
7236
|
glFlush();
|
|
7104
7237
|
glPolyMode = false;
|
|
7105
7238
|
glContext.useProgram(glShader);
|
|
7106
|
-
|
|
7107
|
-
// set vertex attributes
|
|
7108
|
-
let offset = 0;
|
|
7109
|
-
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
7110
|
-
{
|
|
7111
|
-
const location = glContext.getAttribLocation(glShader, name);
|
|
7112
|
-
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
7113
|
-
const divisor = typeSize && 1; // only if not geometry
|
|
7114
|
-
const normalize = typeSize === 1; // only if color
|
|
7115
|
-
glContext.enableVertexAttribArray(location);
|
|
7116
|
-
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
7117
|
-
glContext.vertexAttribDivisor(location, divisor);
|
|
7118
|
-
offset += size*typeSize;
|
|
7119
|
-
}
|
|
7120
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7121
|
-
initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
|
|
7122
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7123
|
-
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7124
|
-
initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
|
|
7125
|
-
initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
|
|
7126
|
-
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7127
|
-
initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
|
|
7128
|
-
initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
|
|
7239
|
+
glContext.bindVertexArray(glInstancedVAO);
|
|
7129
7240
|
}
|
|
7130
7241
|
|
|
7131
7242
|
function glSetPolyMode()
|
|
7132
7243
|
{
|
|
7133
|
-
if (glPolyMode)
|
|
7134
|
-
return;
|
|
7244
|
+
if (glPolyMode) return;
|
|
7135
7245
|
|
|
7136
7246
|
// setup poly mode
|
|
7137
7247
|
glFlush();
|
|
7138
7248
|
glPolyMode = true;
|
|
7139
7249
|
glContext.useProgram(glPolyShader);
|
|
7140
|
-
|
|
7141
|
-
// set vertex attributes
|
|
7142
|
-
let offset = 0;
|
|
7143
|
-
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
7144
|
-
{
|
|
7145
|
-
const location = glContext.getAttribLocation(glPolyShader, name);
|
|
7146
|
-
const normalize = typeSize === 1; // only normalize if color
|
|
7147
|
-
const stride = gl_POLY_VERTEX_BYTE_STRIDE;
|
|
7148
|
-
glContext.enableVertexAttribArray(location);
|
|
7149
|
-
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
7150
|
-
glContext.vertexAttribDivisor(location, 0);
|
|
7151
|
-
offset += size*typeSize;
|
|
7152
|
-
}
|
|
7153
|
-
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7154
|
-
glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
7155
|
-
initVertexAttribArray('p', glContext.FLOAT, 4, 2); // position
|
|
7156
|
-
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
7250
|
+
glContext.bindVertexArray(glPolyVAO);
|
|
7157
7251
|
}
|
|
7158
7252
|
|
|
7159
7253
|
// Setup WebGL render each frame, called automatically by engine
|
|
7160
7254
|
// Also used by tile layer rendering when redrawing tiles
|
|
7161
|
-
function glPreRender()
|
|
7255
|
+
function glPreRender(clear=true)
|
|
7162
7256
|
{
|
|
7163
7257
|
if (!glEnable || !glContext) return;
|
|
7164
7258
|
|
|
7165
|
-
|
|
7166
|
-
|
|
7259
|
+
ASSERT(!glBatchCount, 'glPreRender called with unflushed batch.');
|
|
7260
|
+
|
|
7261
|
+
if (!glRenderTarget)
|
|
7262
|
+
{
|
|
7263
|
+
// set to same size as main canvas
|
|
7264
|
+
glCanvas.width = mainCanvasSize.x;
|
|
7265
|
+
glCanvas.height = mainCanvasSize.y;
|
|
7266
|
+
}
|
|
7267
|
+
glContext.viewport(0, 0, mainCanvasSize.x, mainCanvasSize.y);
|
|
7268
|
+
clear && glClearCanvas();
|
|
7167
7269
|
|
|
7168
7270
|
// build the transform matrix
|
|
7169
7271
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
7272
|
+
if (glRenderTarget)
|
|
7273
|
+
s.y = -s.y; // invert y when using render target
|
|
7170
7274
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
7171
7275
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
7172
7276
|
const ca = cos(cameraAngle);
|
|
@@ -7195,12 +7299,14 @@ function glPreRender()
|
|
|
7195
7299
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7196
7300
|
}
|
|
7197
7301
|
|
|
7302
|
+
// rebind the array buffer
|
|
7303
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
7304
|
+
|
|
7198
7305
|
// start with additive blending off
|
|
7199
7306
|
glAdditive = glBatchAdditive = false;
|
|
7200
7307
|
|
|
7201
|
-
// force it to set instanced mode
|
|
7202
|
-
|
|
7203
|
-
glSetInstancedMode();
|
|
7308
|
+
// force it to set instanced mode
|
|
7309
|
+
glSetInstancedMode(true);
|
|
7204
7310
|
}
|
|
7205
7311
|
|
|
7206
7312
|
/** Clear the canvas and setup the viewport
|
|
@@ -7209,13 +7315,9 @@ function glClearCanvas()
|
|
|
7209
7315
|
{
|
|
7210
7316
|
if (!glContext) return;
|
|
7211
7317
|
|
|
7212
|
-
// clear
|
|
7213
|
-
glCanvas.width = drawCanvas.width;
|
|
7214
|
-
glCanvas.height = drawCanvas.height;
|
|
7215
|
-
glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
|
|
7318
|
+
// clear using the canvasClearColor
|
|
7216
7319
|
const color = canvasClearColor;
|
|
7217
|
-
|
|
7218
|
-
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
7320
|
+
glContext.clearColor(color.r, color.g, color.b, color.a);
|
|
7219
7321
|
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
7220
7322
|
}
|
|
7221
7323
|
|
|
@@ -7227,8 +7329,7 @@ function glClearCanvas()
|
|
|
7227
7329
|
function glSetTexture(texture, wrap=false)
|
|
7228
7330
|
{
|
|
7229
7331
|
// must flush cache with the old texture to set a new one
|
|
7230
|
-
if (!glContext || texture === glActiveTexture)
|
|
7231
|
-
return;
|
|
7332
|
+
if (!glContext || texture === glActiveTexture) return;
|
|
7232
7333
|
|
|
7233
7334
|
glFlush();
|
|
7234
7335
|
glActiveTexture = texture;
|
|
@@ -7293,7 +7394,7 @@ function glCreateTexture(image)
|
|
|
7293
7394
|
// build the texture
|
|
7294
7395
|
const texture = glContext.createTexture();
|
|
7295
7396
|
let mipMap = false;
|
|
7296
|
-
if (image
|
|
7397
|
+
if (image?.width)
|
|
7297
7398
|
{
|
|
7298
7399
|
glSetTextureData(texture, image);
|
|
7299
7400
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
@@ -7314,7 +7415,9 @@ function glCreateTexture(image)
|
|
|
7314
7415
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, minFilter);
|
|
7315
7416
|
if (mipMap)
|
|
7316
7417
|
glContext.generateMipmap(glContext.TEXTURE_2D);
|
|
7317
|
-
|
|
7418
|
+
|
|
7419
|
+
// rebind active texture
|
|
7420
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7318
7421
|
return texture;
|
|
7319
7422
|
}
|
|
7320
7423
|
|
|
@@ -7324,6 +7427,7 @@ function glCreateTexture(image)
|
|
|
7324
7427
|
function glDeleteTexture(texture)
|
|
7325
7428
|
{
|
|
7326
7429
|
if (!glContext) return;
|
|
7430
|
+
|
|
7327
7431
|
glContext.deleteTexture(texture);
|
|
7328
7432
|
}
|
|
7329
7433
|
|
|
@@ -7336,10 +7440,12 @@ function glSetTextureData(texture, image)
|
|
|
7336
7440
|
if (!glContext) return;
|
|
7337
7441
|
|
|
7338
7442
|
// build the texture
|
|
7339
|
-
ASSERT(
|
|
7443
|
+
ASSERT(image?.width > 0, 'Invalid image data.');
|
|
7340
7444
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
7341
7445
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
7342
|
-
|
|
7446
|
+
|
|
7447
|
+
// rebind active texture
|
|
7448
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
7343
7449
|
}
|
|
7344
7450
|
|
|
7345
7451
|
/** Tells WebGL to create or update the glTexture and start tracking it
|
|
@@ -7408,8 +7514,7 @@ function glFlush()
|
|
|
7408
7514
|
* @memberof WebGL */
|
|
7409
7515
|
function glCopyToContext(context)
|
|
7410
7516
|
{
|
|
7411
|
-
if (!glEnable || !glContext)
|
|
7412
|
-
return;
|
|
7517
|
+
if (!glEnable || !glContext) return;
|
|
7413
7518
|
|
|
7414
7519
|
glFlush();
|
|
7415
7520
|
context.drawImage(glCanvas, 0, 0);
|
|
@@ -7559,6 +7664,48 @@ function glDrawColoredPoints(points, pointColors)
|
|
|
7559
7664
|
glBatchCount += vertCount;
|
|
7560
7665
|
}
|
|
7561
7666
|
|
|
7667
|
+
/** Set the WebGL render target to the given texture or back to the canvas
|
|
7668
|
+
* @param {WebGLTexture} [texture] - a texture or undefined to use normal glCanvas
|
|
7669
|
+
* @param {boolean} [clear] - should the render target be cleared
|
|
7670
|
+
* @memberof WebGL */
|
|
7671
|
+
function glSetRenderTarget(texture, clear=false)
|
|
7672
|
+
{
|
|
7673
|
+
if (texture)
|
|
7674
|
+
{
|
|
7675
|
+
glRenderTarget = texture;
|
|
7676
|
+
glContext.bindFramebuffer(glContext.FRAMEBUFFER, glFramebuffer);
|
|
7677
|
+
glContext.framebufferTexture2D(glContext.FRAMEBUFFER,
|
|
7678
|
+
glContext.COLOR_ATTACHMENT0, glContext.TEXTURE_2D, texture, 0);
|
|
7679
|
+
glPreRender(clear);
|
|
7680
|
+
}
|
|
7681
|
+
else
|
|
7682
|
+
{
|
|
7683
|
+
glFlush();
|
|
7684
|
+
glRenderTarget = undefined;
|
|
7685
|
+
glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
|
|
7686
|
+
}
|
|
7687
|
+
}
|
|
7688
|
+
|
|
7689
|
+
/** Clear out a rectangle area of the WebGL canvas or render target
|
|
7690
|
+
* @param {number} x
|
|
7691
|
+
* @param {number} y
|
|
7692
|
+
* @param {number} width
|
|
7693
|
+
* @param {number} height
|
|
7694
|
+
* @memberof WebGL */
|
|
7695
|
+
function glClearRect(x, y, width, height)
|
|
7696
|
+
{
|
|
7697
|
+
if (!glEnable) return;
|
|
7698
|
+
|
|
7699
|
+
// Enable scissor test to clear only the specified area
|
|
7700
|
+
glContext.enable(glContext.SCISSOR_TEST);
|
|
7701
|
+
glContext.scissor(x, y, width, height);
|
|
7702
|
+
glContext.clearColor(0, 0, 0, 0);
|
|
7703
|
+
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
7704
|
+
glContext.disable(glContext.SCISSOR_TEST);
|
|
7705
|
+
}
|
|
7706
|
+
|
|
7707
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7708
|
+
|
|
7562
7709
|
// WebGL internal function to convert polygon to outline triangle strip
|
|
7563
7710
|
function glMakeOutline(points, width, wrap=true)
|
|
7564
7711
|
{
|
|
@@ -7692,23 +7839,20 @@ function glPolyStrip(points)
|
|
|
7692
7839
|
const a = points[i0], b = points[i1], c = points[i2];
|
|
7693
7840
|
|
|
7694
7841
|
// check if convex
|
|
7695
|
-
if (cross(a, b, c) < e)
|
|
7696
|
-
continue;
|
|
7842
|
+
if (cross(a, b, c) < e) continue;
|
|
7697
7843
|
|
|
7698
7844
|
// check if any other point is inside
|
|
7699
7845
|
let hasInside = false;
|
|
7700
7846
|
for (let j = 0; j < indices.length; j++)
|
|
7701
7847
|
{
|
|
7702
7848
|
const k = indices[j];
|
|
7703
|
-
if (k === i0 || k === i1 || k === i2)
|
|
7704
|
-
|
|
7849
|
+
if (k === i0 || k === i1 || k === i2) continue;
|
|
7850
|
+
|
|
7705
7851
|
const p = points[k];
|
|
7706
7852
|
hasInside = pointInTriangle(p, a, b, c);
|
|
7707
|
-
if (hasInside)
|
|
7708
|
-
break;
|
|
7853
|
+
if (hasInside) break;
|
|
7709
7854
|
}
|
|
7710
|
-
if (hasInside)
|
|
7711
|
-
continue;
|
|
7855
|
+
if (hasInside) continue;
|
|
7712
7856
|
|
|
7713
7857
|
// found valid ear
|
|
7714
7858
|
triangles.push([i0, i1, i2]);
|
|
@@ -7733,8 +7877,7 @@ function glPolyStrip(points)
|
|
|
7733
7877
|
worstIndex = i;
|
|
7734
7878
|
}
|
|
7735
7879
|
}
|
|
7736
|
-
if (worstIndex < 0)
|
|
7737
|
-
break;
|
|
7880
|
+
if (worstIndex < 0) break;
|
|
7738
7881
|
|
|
7739
7882
|
const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
|
|
7740
7883
|
const i1 = indices[worstIndex];
|
|
@@ -7965,14 +8108,16 @@ class PostProcessPlugin
|
|
|
7965
8108
|
{
|
|
7966
8109
|
/** Create global post processing shader
|
|
7967
8110
|
* @param {string} shaderCode
|
|
7968
|
-
* @param {boolean} [includeMainCanvas]
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
8111
|
+
* @param {boolean} [includeMainCanvas] - combine mainCanvs onto glCanvas
|
|
8112
|
+
* @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
|
|
8113
|
+
* @example
|
|
8114
|
+
* // create the post process plugin object
|
|
8115
|
+
* new PostProcessPlugin(shaderCode);
|
|
8116
|
+
*/
|
|
8117
|
+
constructor(shaderCode, includeMainCanvas=false, feedbackTexture=false)
|
|
7974
8118
|
{
|
|
7975
8119
|
ASSERT(!postProcess, 'Post process already initialized');
|
|
8120
|
+
ASSERT(!(includeMainCanvas && feedbackTexture), 'Post process cannot both include main canvas and use feedback texture');
|
|
7976
8121
|
postProcess = this;
|
|
7977
8122
|
|
|
7978
8123
|
if (!shaderCode) // default shader pass through
|
|
@@ -7980,9 +8125,10 @@ class PostProcessPlugin
|
|
|
7980
8125
|
|
|
7981
8126
|
/** @property {WebGLProgram} - Shader for post processing */
|
|
7982
8127
|
this.shader = undefined;
|
|
7983
|
-
|
|
7984
8128
|
/** @property {WebGLTexture} - Texture for post processing */
|
|
7985
8129
|
this.texture = undefined;
|
|
8130
|
+
/** @property {WebGLVertexArrayObject} - Vertex array object */
|
|
8131
|
+
this.vao = undefined;
|
|
7986
8132
|
|
|
7987
8133
|
// setup the post processing plugin
|
|
7988
8134
|
initPostProcess();
|
|
@@ -7991,7 +8137,6 @@ class PostProcessPlugin
|
|
|
7991
8137
|
function initPostProcess()
|
|
7992
8138
|
{
|
|
7993
8139
|
if (headlessMode) return;
|
|
7994
|
-
|
|
7995
8140
|
if (!glEnable)
|
|
7996
8141
|
{
|
|
7997
8142
|
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
@@ -8002,14 +8147,14 @@ class PostProcessPlugin
|
|
|
8002
8147
|
postProcess.texture = glCreateTexture();
|
|
8003
8148
|
postProcess.shader = glCreateProgram(
|
|
8004
8149
|
'#version 300 es\n' + // specify GLSL ES version
|
|
8005
|
-
'precision highp float;'+ // use highp for
|
|
8150
|
+
'precision highp float;'+ // use highp for accuracy
|
|
8006
8151
|
'in vec2 p;'+ // position
|
|
8007
8152
|
'void main(){'+ // shader entry point
|
|
8008
8153
|
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
8009
8154
|
'}' // end of shader
|
|
8010
8155
|
,
|
|
8011
8156
|
'#version 300 es\n' + // specify GLSL ES version
|
|
8012
|
-
'precision highp float;'+ // use highp for
|
|
8157
|
+
'precision highp float;'+ // use highp for accuracy
|
|
8013
8158
|
'uniform sampler2D iChannel0;'+ // input texture
|
|
8014
8159
|
'uniform vec3 iResolution;'+ // size of output texture
|
|
8015
8160
|
'uniform float iTime;'+ // time
|
|
@@ -8020,6 +8165,17 @@ class PostProcessPlugin
|
|
|
8020
8165
|
'c.a=1.;'+ // always use full alpha
|
|
8021
8166
|
'}' // end of shader
|
|
8022
8167
|
);
|
|
8168
|
+
|
|
8169
|
+
// setup VAO for post processing
|
|
8170
|
+
postProcess.vao = glContext.createVertexArray();
|
|
8171
|
+
glContext.bindVertexArray(postProcess.vao);
|
|
8172
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
8173
|
+
|
|
8174
|
+
// configure vertex attributes
|
|
8175
|
+
const vertexByteStride = 8;
|
|
8176
|
+
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
8177
|
+
glContext.enableVertexAttribArray(pLocation);
|
|
8178
|
+
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
8023
8179
|
}
|
|
8024
8180
|
function postProcessContextLost()
|
|
8025
8181
|
{
|
|
@@ -8034,14 +8190,17 @@ class PostProcessPlugin
|
|
|
8034
8190
|
}
|
|
8035
8191
|
function postProcessRender()
|
|
8036
8192
|
{
|
|
8037
|
-
if (headlessMode) return;
|
|
8038
|
-
|
|
8039
|
-
if (!glEnable)
|
|
8040
|
-
return;
|
|
8193
|
+
if (headlessMode || !glEnable) return;
|
|
8041
8194
|
|
|
8042
8195
|
// clear out the buffer
|
|
8043
8196
|
glFlush();
|
|
8044
|
-
|
|
8197
|
+
|
|
8198
|
+
// setup shader program to draw a quad
|
|
8199
|
+
glContext.useProgram(postProcess.shader);
|
|
8200
|
+
glContext.bindVertexArray(postProcess.vao);
|
|
8201
|
+
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, true);
|
|
8202
|
+
glContext.disable(glContext.BLEND);
|
|
8203
|
+
|
|
8045
8204
|
// setup texture
|
|
8046
8205
|
glContext.activeTexture(glContext.TEXTURE0);
|
|
8047
8206
|
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
@@ -8052,29 +8211,32 @@ class PostProcessPlugin
|
|
|
8052
8211
|
workCanvas.height = mainCanvasSize.y;
|
|
8053
8212
|
glCopyToContext(workContext);
|
|
8054
8213
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
8214
|
+
mainCanvas.width |= 0
|
|
8055
8215
|
|
|
8056
8216
|
// copy work canvas to texture
|
|
8057
8217
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
|
|
8058
8218
|
}
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
// set vertex position attribute
|
|
8067
|
-
const vertexByteStride = 8;
|
|
8068
|
-
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
8069
|
-
glContext.enableVertexAttribArray(pLocation);
|
|
8070
|
-
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
8071
|
-
|
|
8219
|
+
else if (!feedbackTexture)
|
|
8220
|
+
{
|
|
8221
|
+
// copy glCanvas to texture
|
|
8222
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
|
|
8223
|
+
}
|
|
8224
|
+
|
|
8072
8225
|
// set uniforms and draw
|
|
8073
8226
|
const uniformLocation = (name)=>glContext.getUniformLocation(postProcess.shader, name);
|
|
8074
8227
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
8075
8228
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
8076
8229
|
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
8077
8230
|
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
|
|
8231
|
+
|
|
8232
|
+
if (feedbackTexture)
|
|
8233
|
+
{
|
|
8234
|
+
// pass glCanvas back to overlay texture
|
|
8235
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
|
|
8236
|
+
}
|
|
8237
|
+
|
|
8238
|
+
// force it to set instanced mode
|
|
8239
|
+
glSetInstancedMode(true);
|
|
8078
8240
|
}
|
|
8079
8241
|
}
|
|
8080
8242
|
}
|
|
@@ -8755,7 +8917,7 @@ class UISystemPlugin
|
|
|
8755
8917
|
const up = 'ArrowUp', down = 'ArrowDown', left = 'ArrowLeft', right = 'ArrowRight';
|
|
8756
8918
|
if (both)
|
|
8757
8919
|
{
|
|
8758
|
-
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
8920
|
+
return keyIsDown(up) || keyIsDown(left) ? -1 :
|
|
8759
8921
|
keyIsDown(down) || keyIsDown(right) ? 1 : 0;
|
|
8760
8922
|
}
|
|
8761
8923
|
const back = vertical ? up : left;
|
|
@@ -8786,7 +8948,7 @@ class UISystemPlugin
|
|
|
8786
8948
|
* @return {boolean} */
|
|
8787
8949
|
getNavigationWasPressed()
|
|
8788
8950
|
{
|
|
8789
|
-
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
8951
|
+
return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
|
|
8790
8952
|
keyWasPressed('Space') || keyWasPressed('Enter');
|
|
8791
8953
|
}
|
|
8792
8954
|
|
|
@@ -8834,7 +8996,7 @@ class UISystemPlugin
|
|
|
8834
8996
|
buttonYes.textHeight = 40;
|
|
8835
8997
|
buttonYes.navigationIndex = 1;
|
|
8836
8998
|
buttonYes.hoverColor = hsl(0,1,.5);
|
|
8837
|
-
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
8999
|
+
buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
|
|
8838
9000
|
confirmMenu.addChild(buttonYes);
|
|
8839
9001
|
|
|
8840
9002
|
// no button
|
|
@@ -8864,8 +9026,8 @@ class UISystemPlugin
|
|
|
8864
9026
|
class UIObject
|
|
8865
9027
|
{
|
|
8866
9028
|
/** Create a UIObject
|
|
8867
|
-
* @param {Vector2} [pos=(
|
|
8868
|
-
* @param {Vector2} [size=(1
|
|
9029
|
+
* @param {Vector2} [pos=vec2()]
|
|
9030
|
+
* @param {Vector2} [size=vec2(1)]
|
|
8869
9031
|
*/
|
|
8870
9032
|
constructor(pos=vec2(), size=vec2())
|
|
8871
9033
|
{
|
|
@@ -8880,7 +9042,7 @@ class UIObject
|
|
|
8880
9042
|
this.size = size.copy();
|
|
8881
9043
|
/** @property {Color} - Color of the object */
|
|
8882
9044
|
this.color = uiSystem.defaultColor.copy();
|
|
8883
|
-
/** @property {Color} - Color of the object when active, uses
|
|
9045
|
+
/** @property {Color} - Color of the object when active, uses hoverColor if undefined */
|
|
8884
9046
|
this.activeColor = undefined;
|
|
8885
9047
|
/** @property {string} - Text for this ui object */
|
|
8886
9048
|
this.text = undefined;
|
|
@@ -8978,10 +9140,10 @@ class UIObject
|
|
|
8978
9140
|
|
|
8979
9141
|
// disconnect from parent and destroy children
|
|
8980
9142
|
this.destroyed = 1;
|
|
8981
|
-
this.parent
|
|
9143
|
+
this.parent?.removeChild(this);
|
|
8982
9144
|
for (const child of this.children)
|
|
8983
9145
|
{
|
|
8984
|
-
child.parent =
|
|
9146
|
+
child.parent = undefined;
|
|
8985
9147
|
child.destroy();
|
|
8986
9148
|
}
|
|
8987
9149
|
}
|
|
@@ -9005,7 +9167,7 @@ class UIObject
|
|
|
9005
9167
|
this.onUpdate();
|
|
9006
9168
|
|
|
9007
9169
|
// unset active if disabled
|
|
9008
|
-
if (this.disabled && this
|
|
9170
|
+
if (this.disabled && this === uiSystem.activeObject)
|
|
9009
9171
|
uiSystem.activeObject = undefined;
|
|
9010
9172
|
|
|
9011
9173
|
const wasHover = uiSystem.lastHoverObject === this;
|
|
@@ -9077,10 +9239,10 @@ class UIObject
|
|
|
9077
9239
|
this.interactive && this.isActiveObject() && !this.disabled ?
|
|
9078
9240
|
this.color : this.lineColor;
|
|
9079
9241
|
const color = isNavigationObject ? this.hoverColor :
|
|
9080
|
-
this.disabled ? this.disabledColor :
|
|
9081
|
-
this.interactive ?
|
|
9082
|
-
this.
|
|
9083
|
-
this.
|
|
9242
|
+
this.disabled ? this.disabledColor :
|
|
9243
|
+
this.interactive ?
|
|
9244
|
+
this.isActiveObject() ? this.activeColor || this.hoverColor :
|
|
9245
|
+
this.isHoverObject() ? this.hoverColor :
|
|
9084
9246
|
this.color : this.color;
|
|
9085
9247
|
const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
|
|
9086
9248
|
|
|
@@ -9092,7 +9254,7 @@ class UIObject
|
|
|
9092
9254
|
getTextSize()
|
|
9093
9255
|
{
|
|
9094
9256
|
return vec2(
|
|
9095
|
-
this.textWidth || this.textFitScale * this.size.x,
|
|
9257
|
+
this.textWidth || this.textFitScale * this.size.x,
|
|
9096
9258
|
this.textHeight || this.textFitScale * this.size.y);
|
|
9097
9259
|
}
|
|
9098
9260
|
|
|
@@ -9140,9 +9302,9 @@ class UIObject
|
|
|
9140
9302
|
renderDebug(visible=true)
|
|
9141
9303
|
{
|
|
9142
9304
|
// apply color based on state
|
|
9143
|
-
const color =
|
|
9305
|
+
const color =
|
|
9144
9306
|
!visible ? GREEN :
|
|
9145
|
-
this.isHoverObject() ? YELLOW :
|
|
9307
|
+
this.isHoverObject() ? YELLOW :
|
|
9146
9308
|
this.disabled ? PURPLE :
|
|
9147
9309
|
this.interactive ? RED : BLUE;
|
|
9148
9310
|
uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
|