littlejsengine 1.16.2 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/littlejs.d.ts +200 -166
  2. package/dist/littlejs.esm.js +1231 -1086
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +888 -735
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +884 -731
  7. package/examples/box2d/gameObjects.js +1 -1
  8. package/examples/breakout/game.js +5 -6
  9. package/examples/electron/index.html +2 -2
  10. package/examples/electron/tiles.png +0 -0
  11. package/examples/htmlMenu/tiles.png +0 -0
  12. package/examples/index.html +1 -1
  13. package/examples/logo.png +0 -0
  14. package/examples/module/tiles.png +0 -0
  15. package/examples/platformer/gameEffects.js +23 -22
  16. package/examples/platformer/gameLevel.js +24 -25
  17. package/examples/shorts/base.html +1 -1
  18. package/examples/shorts/clock.js +3 -3
  19. package/examples/shorts/fontImage.js +4 -3
  20. package/examples/shorts/parallax.js +1 -1
  21. package/examples/shorts/sequencer.js +1 -1
  22. package/examples/shorts/shapes.js +1 -1
  23. package/examples/shorts/texture.js +10 -6
  24. package/examples/shorts/tiles.png +0 -0
  25. package/examples/shorts/tiltedView.js +2 -0
  26. package/examples/starter/index.html +2 -2
  27. package/examples/starter/tiles.png +0 -0
  28. package/examples/typescript/tiles.png +0 -0
  29. package/examples/uiSystem/game.js +1 -1
  30. package/examples/uiSystem/tiles.png +0 -0
  31. package/package.json +1 -1
  32. package/plugins/postProcess.js +47 -28
  33. package/plugins/uiSystem.js +15 -15
  34. package/reference.md +1 -1
  35. package/src/engine.js +174 -174
  36. package/src/engineAudio.js +4 -6
  37. package/src/engineDebug.js +4 -4
  38. package/src/engineDraw.js +179 -122
  39. package/src/engineExport.js +326 -334
  40. package/src/engineFont.png +0 -0
  41. package/src/engineInput.js +14 -20
  42. package/src/engineMath.js +15 -104
  43. package/src/engineObject.js +26 -25
  44. package/src/engineParticles.js +2 -2
  45. package/src/engineTileLayer.js +196 -170
  46. package/src/engineUtilities.js +100 -4
  47. package/src/engineWebGL.js +123 -72
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
33
33
  * @type {string}
34
34
  * @default
35
35
  * @memberof Engine */
36
- const engineVersion = '1.16.2';
36
+ const engineVersion = '1.17.1';
37
37
 
38
38
  /** Frames per second to update
39
39
  * @type {number}
@@ -320,7 +320,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
320
320
 
321
321
  // responsive aspect ratio with native resolution
322
322
  const innerAspect = innerWidth / innerHeight;
323
- ASSERT(canvasMinAspect <= canvasMaxAspect);
323
+ ASSERT(canvasMinAspect <= canvasMaxAspect);
324
324
  if (canvasMaxAspect && innerAspect > canvasMaxAspect)
325
325
  {
326
326
  // full height
@@ -377,7 +377,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
377
377
  debugInit();
378
378
 
379
379
  // setup canvases
380
- // transform way is still more reliable then flexbox or grid
380
+ // transform way is still more reliable than flexbox or grid
381
381
  const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
382
382
  'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
383
383
  mainCanvas.style.cssText = styleCanvas;
@@ -422,6 +422,9 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
422
422
  }));
423
423
  }
424
424
 
425
+ // load engine font image
426
+ promises.push(fontImageInit());
427
+
425
428
  if (showSplashScreen)
426
429
  {
427
430
  // draw splash screen
@@ -433,7 +436,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
433
436
  function updateSplash()
434
437
  {
435
438
  inputClear();
436
- drawEngineSplashScreen(t+=.01);
439
+ drawEngineLogo(t+=.01);
437
440
  t>1 ? resolve() : setTimeout(updateSplash, 16);
438
441
  }
439
442
  }));
@@ -449,172 +452,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
449
452
  await gameInit();
450
453
  engineUpdate();
451
454
  }
452
-
453
- ///////////////////////////////////////////////////////////////////////////
454
- // LittleJS Splash Screen
455
- function drawEngineSplashScreen(t)
456
- {
457
- const x = mainContext;
458
- const w = mainCanvas.width = innerWidth;
459
- const h = mainCanvas.height = innerHeight;
460
-
461
- {
462
- // background
463
- const p3 = percent(t, 1, .8);
464
- const p4 = percent(t, 0, .5);
465
- const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
466
- g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
467
- g.addColorStop(1,hsl(0,0,0,p3).toString());
468
- x.save();
469
- x.fillStyle = g;
470
- x.fillRect(0,0,w,h);
471
- }
472
-
473
- // draw LittleJS logo...
474
- const rect = (X, Y, W, H, C)=>
475
- {
476
- x.beginPath();
477
- x.rect(X,Y,W,C?H*p:H);
478
- x.fillStyle = C;
479
- C ? x.fill() : x.stroke();
480
- };
481
- const line = (X, Y, Z, W)=>
482
- {
483
- x.beginPath();
484
- x.lineTo(X,Y);
485
- x.lineTo(Z,W);
486
- x.stroke();
487
- };
488
- const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
489
- {
490
- const D = (A+B)/2, E = p*(B-A)/2;
491
- x.beginPath();
492
- F && x.lineTo(X,Y);
493
- x.arc(X,Y,R,D-E,D+E);
494
- x.fillStyle = C;
495
- C ? x.fill() : x.stroke();
496
- };
497
- const color = (c=0, l=0) =>
498
- hsl([.98,.3,.57,.14][c%4],.8,[0,.3,.5,.8,.9][l]).toString();
499
- const alpha = wave(1,1,t);
500
- const p = percent(alpha, .1, .5);
501
-
502
- // setup
503
- x.translate(w/2,h/2);
504
- const size = min(6, min(w,h)/99); // fit to screen
505
- x.scale(size,size);
506
- x.translate(-40,-35);
507
- x.lineJoin = x.lineCap = 'round';
508
- x.lineWidth = .1 + p*1.9;
509
-
510
- // drawing effect
511
- const p2 = percent(alpha,.1,1);
512
- x.setLineDash([99*p2,99]);
513
-
514
- // cab top
515
- rect(7,16,18,-8,color(2,2));
516
- rect(7,8,18,4,color(2,3));
517
- rect(25,8,8,8,color(2,1));
518
- rect(25,8,-18,8);
519
- rect(25,8,8,8);
520
-
521
- // cab
522
- rect(25,16,7,23,color());
523
- rect(11,39,14,-23,color(1,1));
524
- rect(11,16,14,18,color(1,2));
525
- rect(11,16,14,8,color(1,3));
526
- rect(25,16,-14,24);
527
-
528
- // cab window
529
- rect(15,29,6,-9,color(2,2));
530
- circle(15,21,5,0,PI/2,color(2,4),1);
531
- rect(21,21,-6,9);
532
-
533
- // little stack
534
- rect(37,14,9,6,color(3,2));
535
- rect(37,14,4.5,6,color(3,3));
536
- rect(37,14,9,6);
537
-
538
- // big stack
539
- rect(50,20,10,-8,color(0,1));
540
- rect(50,20,6.5,-8,color(0,2));
541
- rect(50,20,3.5,-8,color(0,3));
542
- rect(50,20,10,-8);
543
- circle(55,2,11.4,.5,PI-.5,color(3,3));
544
- circle(55,2,11.4,.5,PI/2,color(3,2),1);
545
- circle(55,2,11.4,.5,PI-.5);
546
- rect(45,7,20,-7,color(0,2));
547
- rect(45,-1,20,4,color(0,3));
548
- rect(45,-1,20,8);
549
-
550
- // engine
551
- for (let i=5; i--;)
552
- {
553
- // stagger radius to fix slight seam
554
- circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
555
- circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
556
- circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
557
- }
558
-
559
- // engine outline
560
- circle(36,30,10,PI/2,PI*3/2);
561
- circle(48,30,10,PI/2,PI*3/2);
562
- circle(60,30,10);
563
- line(36,20,60,20);
564
-
565
- // engine front light
566
- circle(60,30,4,PI,3*PI,color(3,2));
567
- circle(60,30,4,PI,2*PI,color(3,3));
568
- circle(60,30,4,PI,3*PI);
569
-
570
- // front brush
571
- for (let i=6; i--;)
572
- {
573
- x.beginPath();
574
- x.lineTo(53,54);
575
- x.lineTo(53,40);
576
- x.lineTo(53+(1+i*2.9)*p,40);
577
- x.lineTo(53+(4+i*3.5)*p,54);
578
- x.fillStyle = color(0,i%2+2);
579
- x.fill();
580
- i%2 && x.stroke();
581
- }
582
-
583
- // wheels
584
- rect(6,40,5,5);
585
- rect(6,40,5,5,color());
586
- rect(15,54,38,-14,color());
587
- for (let i=3; i--;)
588
- for (let j=2; j--;)
589
- {
590
- circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
591
- x.stroke();
592
- circle(15*i+15,47,j?7:1,0,PI,color(i,2));
593
- x.stroke();
594
- }
595
- line(6,40,68,40); // center
596
- line(77,54,4,54); // bottom
597
-
598
- // draw engine name
599
- const s = engineName;
600
- x.font = '900 16px arial';
601
- x.textAlign = 'center';
602
- x.textBaseline = 'top';
603
- x.lineWidth = .1+p*3.9;
604
- let w2 = 0;
605
- for (let i=0; i<s.length; ++i)
606
- w2 += x.measureText(s[i]).width;
607
- for (let j=2; j--;)
608
- for (let i=0, X=41-w2/2; i<s.length; ++i)
609
- {
610
- x.fillStyle = color(i,2);
611
- const w = x.measureText(s[i]).width;
612
- x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
613
- X += w;
614
- }
615
- x.restore();
616
- }
617
- ///////////////////////////////////////////////////////////////////////////
618
455
  }
619
456
 
620
457
  /** Update each engine object, remove destroyed objects, and update time
@@ -628,8 +465,7 @@ function engineObjectsUpdate()
628
465
  // recursive object update
629
466
  function updateObject(o)
630
467
  {
631
- if (o.destroyed)
632
- return;
468
+ if (o.destroyed) return;
633
469
 
634
470
  o.update();
635
471
  for (const child of o.children)
@@ -637,8 +473,7 @@ function engineObjectsUpdate()
637
473
  }
638
474
  for (const o of engineObjects)
639
475
  {
640
- if (o.parent)
641
- continue;
476
+ if (o.parent || o.destroyed) continue;
642
477
 
643
478
  // update top level objects
644
479
  o.update();
@@ -727,6 +562,171 @@ function engineObjectsRaycast(start, end, objects=engineObjects)
727
562
 
728
563
  debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
729
564
  return hitObjects;
565
+ }
566
+
567
+ ///////////////////////////////////////////////////////////////////////////////
568
+ function drawEngineLogo(t)
569
+ {
570
+ // LittleJS Logo and Splash Screen
571
+ const x = mainContext;
572
+ const w = mainCanvas.width = innerWidth;
573
+ const h = mainCanvas.height = innerHeight;
574
+
575
+ {
576
+ // background
577
+ const p3 = percent(t, 1, .8);
578
+ const p4 = percent(t, 0, .5);
579
+ const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
580
+ g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
581
+ g.addColorStop(1,hsl(0,0,0,p3).toString());
582
+ x.save();
583
+ x.fillStyle = g;
584
+ x.fillRect(0,0,w,h);
585
+ }
586
+
587
+ // draw LittleJS logo...
588
+ const rect = (X, Y, W, H, C)=>
589
+ {
590
+ x.beginPath();
591
+ x.rect(X,Y,W,C?H*p:H);
592
+ x.fillStyle = C;
593
+ C ? x.fill() : x.stroke();
594
+ };
595
+ const line = (X, Y, Z, W)=>
596
+ {
597
+ x.beginPath();
598
+ x.lineTo(X,Y);
599
+ x.lineTo(Z,W);
600
+ x.stroke();
601
+ };
602
+ const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
603
+ {
604
+ const D = (A+B)/2, E = p*(B-A)/2;
605
+ x.beginPath();
606
+ F && x.lineTo(X,Y);
607
+ x.arc(X,Y,R,D-E,D+E);
608
+ x.fillStyle = C;
609
+ C ? x.fill() : x.stroke();
610
+ };
611
+ const color = (c=0, l=0) =>
612
+ hsl([.98,.3,.57,.14][c%4],.9,[0,.3,.5,.8,.9][l]).toString();
613
+ const alpha = wave(1,1,t);
614
+ const p = percent(alpha, .1, .5);
615
+
616
+ // setup
617
+ x.translate(w/2,h/2);
618
+ const size = min(6, min(w,h)/99); // fit to screen
619
+ x.scale(size,size);
620
+ x.translate(-40,-35);
621
+ x.lineJoin = x.lineCap = 'round';
622
+ x.lineWidth = .1 + p*1.9;
623
+
624
+ // drawing effect
625
+ const p2 = percent(alpha,.1,1);
626
+ x.setLineDash([99*p2,99]);
627
+
628
+ // cab top
629
+ rect(7,16,18,-8,color(2,2));
630
+ rect(7,8,18,4,color(2,3));
631
+ rect(25,8,8,8,color(2,1));
632
+ rect(25,8,-18,8);
633
+ rect(25,8,8,8);
634
+
635
+ // cab
636
+ rect(25,16,7,24,color());
637
+ rect(11,39,14,-23,color(1,1));
638
+ rect(11,16,14,18,color(1,2));
639
+ rect(11,16,14,8,color(1,3));
640
+ rect(25,16,-14,24);
641
+
642
+ // cab window
643
+ rect(15,29,6,-9,color(2,2));
644
+ circle(15,21,5,0,PI/2,color(2,4),1);
645
+ rect(21,21,-6,9);
646
+
647
+ // little stack
648
+ rect(37,14,9,6,color(3,2));
649
+ rect(37,14,4.5,6,color(3,3));
650
+ rect(37,14,9,6);
651
+
652
+ // big stack
653
+ rect(50,20,10,-8,color(0,1));
654
+ rect(50,20,6.5,-8,color(0,2));
655
+ rect(50,20,3.5,-8,color(0,3));
656
+ rect(50,20,10,-8);
657
+ circle(55,2,11.4,.5,PI-.5,color(3,3));
658
+ circle(55,2,11.4,.5,PI/2,color(3,2),1);
659
+ circle(55,2,11.4,.5,PI-.5);
660
+ rect(45,7,20,-7,color(0,2));
661
+ rect(45,-1,20,4,color(0,3));
662
+ rect(45,-1,20,8);
663
+
664
+ // engine
665
+ for (let i=5; i--;)
666
+ {
667
+ // stagger radius to fix slight seam
668
+ circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
669
+ circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
670
+ circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
671
+ }
672
+
673
+ // engine outline
674
+ circle(36,30,10,PI/2,PI*3/2);
675
+ circle(48,30,10,PI/2,PI*3/2);
676
+ circle(60,30,10);
677
+ line(36,20,60,20);
678
+
679
+ // engine front light
680
+ circle(60,30,4,PI,3*PI,color(3,2));
681
+ circle(60,30,4,PI,2*PI,color(3,3));
682
+ circle(60,30,4,PI,3*PI);
683
+
684
+ // front brush
685
+ for (let i=6; i--;)
686
+ {
687
+ x.beginPath();
688
+ x.lineTo(53,54);
689
+ x.lineTo(53,40);
690
+ x.lineTo(53+(1+i*2.9)*p,40);
691
+ x.lineTo(53+(4+i*3.5)*p,54);
692
+ x.fillStyle = color(0,i%2+2);
693
+ x.fill();
694
+ i%2 && x.stroke();
695
+ }
696
+
697
+ // wheels
698
+ rect(6,40,5,5);
699
+ rect(6,40,5,5,color());
700
+ rect(15,54,38,-14,color());
701
+ for (let i=3; i--;)
702
+ for (let j=2; j--;)
703
+ {
704
+ circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
705
+ x.stroke();
706
+ circle(15*i+15,47,j?7:1,0,PI,color(i,2));
707
+ x.stroke();
708
+ }
709
+ line(6,40,68,40); // center
710
+ line(77,54,4,54); // bottom
711
+
712
+ // draw engine name
713
+ const s = engineName;
714
+ x.font = '900 16px arial';
715
+ x.textAlign = 'center';
716
+ x.textBaseline = 'top';
717
+ x.lineWidth = .1+p*3.9;
718
+ let w2 = 0;
719
+ for (let i=0; i<s.length; ++i)
720
+ w2 += x.measureText(s[i]).width;
721
+ for (let j=2; j--;)
722
+ for (let i=0, X=41-w2/2; i<s.length; ++i)
723
+ {
724
+ x.fillStyle = color(i,2);
725
+ const w = x.measureText(s[i]).width;
726
+ x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
727
+ X += w;
728
+ }
729
+ x.restore();
730
730
  }
731
731
  /**
732
732
  * LittleJS Debug System
@@ -793,7 +793,7 @@ function LOG(...output) { console.log(...output); }
793
793
 
794
794
  /** Draw a debug rectangle in world space
795
795
  * @param {Vector2} pos
796
- * @param {Vector2} [size=Vector2()]
796
+ * @param {Vector2} [size=vec2(0)]
797
797
  * @param {Color|string} [color]
798
798
  * @param {number} [time]
799
799
  * @param {number} [angle]
@@ -1117,7 +1117,7 @@ function debugRender()
1117
1117
  if (tileCollisionTest(mousePos))
1118
1118
  {
1119
1119
  // show floored tile pick for tile collision
1120
- drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5));
1120
+ drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(1,1,0,.5), 0, false);
1121
1121
  }
1122
1122
  }
1123
1123
 
@@ -1196,8 +1196,8 @@ function debugRender()
1196
1196
  if (debugObject)
1197
1197
  {
1198
1198
  const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
1199
- raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
1200
- drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5));
1199
+ raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3), 0, false);
1200
+ drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5), undefined, undefined, false);
1201
1201
 
1202
1202
  let debugText = 'mouse pos = ' + mousePos;
1203
1203
  if (tileCollisionLayers.length)
@@ -1448,12 +1448,11 @@ function debugProtectConstant(obj)
1448
1448
  return Object.freeze(obj);
1449
1449
  }
1450
1450
  /**
1451
- * LittleJS Utility Classes and Functions
1451
+ * LittleJS Math Classes and Functions
1452
1452
  * - General purpose math library
1453
- * - Vector2 - fast, simple, easy 2D vector class
1454
- * - Color - holds a rgba color with some math functions
1455
- * - Timer - tracks time automatically
1456
1453
  * - RandomGenerator - seeded random number generator
1454
+ * - Vector2 - fast, simple, easy 2D vector class
1455
+ * - Color - holds a rgba color with math functions
1457
1456
  * @namespace Math
1458
1457
  */
1459
1458
 
@@ -1641,11 +1640,11 @@ function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
1641
1640
 
1642
1641
  /** Returns true if two axis aligned bounding boxes are overlapping
1643
1642
  * this can be used for simple collision detection between objects
1644
- * @param {Vector2} posA - Center of box A
1645
- * @param {Vector2} sizeA - Size of box A
1646
- * @param {Vector2} posB - Center of box B
1647
- * @param {Vector2} [sizeB=(0,0)] - Size of box B, uses a point if undefined
1648
- * @return {boolean} - True if overlapping
1643
+ * @param {Vector2} posA - Center of box A
1644
+ * @param {Vector2} sizeA - Size of box A
1645
+ * @param {Vector2} posB - Center of box B
1646
+ * @param {Vector2} [sizeB=vec2()] - Size of box B, uses a point if undefined
1647
+ * @return {boolean} - True if overlapping
1649
1648
  * @memberof Math */
1650
1649
  function isOverlapping(posA, sizeA, posB, sizeB=vec2())
1651
1650
  {
@@ -1719,7 +1718,7 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
1719
1718
  * @param {any} s
1720
1719
  * @return {boolean}
1721
1720
  * @memberof Math */
1722
- function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
1721
+ function isString(s) { return s != null && typeof s?.toString() === 'string'; }
1723
1722
 
1724
1723
  /**
1725
1724
  * Check if object is an array
@@ -1863,8 +1862,8 @@ function randInCircle(radius=1, minRadius=0)
1863
1862
  { return radius > 0 ? randVec2(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
1864
1863
 
1865
1864
  /** Returns a random color between the two passed in colors, combine components if linear
1866
- * @param {Color} [colorA=(1,1,1,1)]
1867
- * @param {Color} [colorB=(0,0,0,1)]
1865
+ * @param {Color} [colorA=WHITE]
1866
+ * @param {Color} [colorB=BLACK]
1868
1867
  * @param {boolean} [linear]
1869
1868
  * @return {Color}
1870
1869
  * @memberof Random */
@@ -1943,8 +1942,8 @@ class RandomGenerator
1943
1942
  { return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
1944
1943
 
1945
1944
  /** Returns a random color between the two passed in colors, combine components if linear
1946
- * @param {Color} [colorA=(1,1,1,1)]
1947
- * @param {Color} [colorB=(0,0,0,1)]
1945
+ * @param {Color} [colorA=WHITE]
1946
+ * @param {Color} [colorB=BLACK]
1948
1947
  * @param {boolean} [linear]
1949
1948
  * @return {Color} */
1950
1949
  randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
@@ -1988,7 +1987,7 @@ class RandomGenerator
1988
1987
  * a = vec2(5); // set a to (5, 5)
1989
1988
  * b = vec2(); // set b to (0, 0)
1990
1989
  * @memberof Math */
1991
- function vec2(x=0, y) { return new Vector2(x, y === undefined ? x : y); }
1990
+ function vec2(x=0, y) { return new Vector2(x, y ?? x); }
1992
1991
 
1993
1992
  /**
1994
1993
  * Check if object is a valid Vector2
@@ -2196,10 +2195,6 @@ class Vector2
2196
2195
  * @return {number} */
2197
2196
  area() { return abs(this.x * this.y); }
2198
2197
 
2199
- /** Returns true if this vector is (0,0)
2200
- * @return {boolean} */
2201
- isZero() { return !this.x && !this.y; }
2202
-
2203
2198
  /** Returns a new vector that is p percent between this and the vector passed in
2204
2199
  * @param {Vector2} v - other vector
2205
2200
  * @param {number} percent
@@ -2561,7 +2556,97 @@ const PURPLE = debugProtectConstant(rgb(.5,0,1));
2561
2556
  /** Color - Magenta #ff00ff
2562
2557
  * @type {Color}
2563
2558
  * @memberof Math */
2564
- const MAGENTA = debugProtectConstant(rgb(1,0,1));
2559
+ const MAGENTA = debugProtectConstant(rgb(1,0,1));
2560
+ /**
2561
+ * LittleJS Utility Classes and Functions
2562
+ * - General purpose utilities
2563
+ * - Timer - tracks time automatically
2564
+ * @namespace Utilities
2565
+ */
2566
+
2567
+ /** Formats seconds to mm:ss style for display purposes
2568
+ * @param {number} t - time in seconds
2569
+ * @return {string}
2570
+ * @memberof Utilities */
2571
+ function formatTime(t)
2572
+ {
2573
+ const sign = t < 0 ? '-' : '';
2574
+ t = abs(t)|0;
2575
+ return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
2576
+ }
2577
+
2578
+ /** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
2579
+ * @param {string} url - URL of JSON file
2580
+ * @return {Promise<object>}
2581
+ * @memberof Utilities */
2582
+ async function fetchJSON(url)
2583
+ {
2584
+ const response = await fetch(url);
2585
+ if (!response.ok)
2586
+ throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
2587
+ return response.json();
2588
+ }
2589
+
2590
+ ///////////////////////////////////////////////////////////////////////////////
2591
+
2592
+ /** Save a text file to disk
2593
+ * @param {string} text
2594
+ * @param {string} [filename]
2595
+ * @param {string} [type]
2596
+ * @memberof Utilities */
2597
+ function saveText(text, filename='text', type='text/plain')
2598
+ { saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
2599
+
2600
+ /** Save a canvas to disk
2601
+ * @param {HTMLCanvasElement|OffscreenCanvas} canvas
2602
+ * @param {string} [filename]
2603
+ * @param {string} [type]
2604
+ * @memberof Utilities */
2605
+ function saveCanvas(canvas, filename='screenshot', type='image/png')
2606
+ {
2607
+ if (canvas instanceof OffscreenCanvas)
2608
+ {
2609
+ // copy to temporary canvas and save
2610
+ const saveCanvas = document.createElement('canvas');
2611
+ saveCanvas.width = canvas.width;
2612
+ saveCanvas.height = canvas.height;
2613
+ saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
2614
+ saveDataURL(saveCanvas.toDataURL(type), filename);
2615
+ }
2616
+ else
2617
+ saveDataURL(canvas.toDataURL(type), filename);
2618
+ }
2619
+
2620
+ /** Save a data url to disk
2621
+ * @param {string} url
2622
+ * @param {string} [filename]
2623
+ * @param {number} [revokeTime] - how long before revoking the url
2624
+ * @memberof Utilities */
2625
+ function saveDataURL(url, filename='download', revokeTime)
2626
+ {
2627
+ ASSERT(isString(url), 'saveDataURL requires url string');
2628
+ ASSERT(isString(filename), 'saveDataURL requires filename string');
2629
+
2630
+ // create link for saving screenshots
2631
+ const link = document.createElement('a');
2632
+ link.download = filename;
2633
+ link.href = url;
2634
+ link.click();
2635
+ if (revokeTime !== undefined)
2636
+ setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
2637
+ }
2638
+
2639
+ /** Share content using the native share dialog if available
2640
+ * @param {string} title - title of the share
2641
+ * @param {string} url - url to share
2642
+ * @param {Function} [callback] - Called when share is complete
2643
+ * @memberof Utilities */
2644
+ function shareURL(title, url, callback)
2645
+ {
2646
+ ASSERT(isString(title), 'shareURL requires title string');
2647
+ ASSERT(isString(url), 'shareURL requires url string');
2648
+ navigator.share?.({title, url}).then(()=>callback?.());
2649
+ }
2565
2650
 
2566
2651
  ///////////////////////////////////////////////////////////////////////////////
2567
2652
 
@@ -2646,84 +2731,6 @@ class Timer
2646
2731
  * @return {number} */
2647
2732
  valueOf() { return this.get(); }
2648
2733
  }
2649
- /**
2650
- * LittleJS Utility Classes and Functions
2651
- * - General purpose math library
2652
- * - Vector2 - fast, simple, easy 2D vector class
2653
- * - Color - holds a rgba color with some math functions
2654
- * - Timer - tracks time automatically
2655
- * - RandomGenerator - seeded random number generator
2656
- * @namespace Utilities
2657
- */
2658
-
2659
- /** Formats seconds to mm:ss style for display purposes
2660
- * @param {number} t - time in seconds
2661
- * @return {string}
2662
- * @memberof Utilities */
2663
- function formatTime(t)
2664
- {
2665
- const sign = t < 0 ? '-' : '';
2666
- t = abs(t)|0;
2667
- return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
2668
- }
2669
-
2670
- /** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
2671
- * @param {string} url - URL of JSON file
2672
- * @return {Promise<object>}
2673
- * @memberof Utilities */
2674
- async function fetchJSON(url)
2675
- {
2676
- const response = await fetch(url);
2677
- if (!response.ok)
2678
- throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
2679
- return response.json();
2680
- }
2681
-
2682
- ///////////////////////////////////////////////////////////////////////////////
2683
-
2684
- /** Save a text file to disk
2685
- * @param {string} text
2686
- * @param {string} [filename]
2687
- * @param {string} [type]
2688
- * @memberof Utilities */
2689
- function saveText(text, filename='text', type='text/plain')
2690
- { saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
2691
-
2692
- /** Save a canvas to disk
2693
- * @param {HTMLCanvasElement|OffscreenCanvas} canvas
2694
- * @param {string} [filename]
2695
- * @param {string} [type]
2696
- * @memberof Utilities */
2697
- function saveCanvas(canvas, filename='screenshot', type='image/png')
2698
- {
2699
- if (canvas instanceof OffscreenCanvas)
2700
- {
2701
- // copy to temporary canvas and save
2702
- const saveCanvas = document.createElement('canvas');
2703
- saveCanvas.width = canvas.width;
2704
- saveCanvas.height = canvas.height;
2705
- saveCanvas.getContext('2d').drawImage(canvas, 0, 0);
2706
- saveDataURL(saveCanvas.toDataURL(type), filename);
2707
- }
2708
- else
2709
- saveDataURL(canvas.toDataURL(type), filename);
2710
- }
2711
-
2712
- /** Save a data url to disk
2713
- * @param {string} url
2714
- * @param {string} [filename]
2715
- * @param {number} [revokeTime] - how long before revoking the url
2716
- * @memberof Utilities */
2717
- function saveDataURL(url, filename='download', revokeTime)
2718
- {
2719
- // create link for saving screenshots
2720
- const link = document.createElement('a');
2721
- link.download = filename;
2722
- link.href = url;
2723
- link.click();
2724
- if (revokeTime !== undefined)
2725
- setTimeout(()=> URL.revokeObjectURL(url), revokeTime);
2726
- }
2727
2734
  /**
2728
2735
  * LittleJS Engine Settings
2729
2736
  * - All settings for the engine are here
@@ -3350,10 +3357,10 @@ function setDebugKey(key) { debugKey = key; }
3350
3357
  class EngineObject
3351
3358
  {
3352
3359
  /** Create an engine object and adds it to the list of objects
3353
- * @param {Vector2} [pos=(0,0)] - World space position of the object
3354
- * @param {Vector2} [size=(1,1)] - World space size of the object
3355
- * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
3356
- * @param {number} [angle] - Angle the object is rotated by
3360
+ * @param {Vector2} [pos=vec2()] - World space position of the object
3361
+ * @param {Vector2} [size=vec2(1)] - World space size of the object
3362
+ * @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
3363
+ * @param {number} [angle] - Angle the object is rotated by
3357
3364
  * @param {Color} [color=WHITE] - Color to apply to tile when rendered
3358
3365
  * @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
3359
3366
  */
@@ -3481,15 +3488,16 @@ class EngineObject
3481
3488
  // physics sanity checks
3482
3489
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
3483
3490
  ASSERT(this.damping >= 0 && this.damping <= 1);
3484
- if (!enablePhysicsSolver || !this.mass) // don't do collision for static objects
3485
- return;
3491
+
3492
+ // don't do collision for static objects or if solver disabled
3493
+ if (!enablePhysicsSolver || !this.mass) return;
3486
3494
 
3487
3495
  const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
3488
3496
  if (this.groundObject)
3489
3497
  {
3490
3498
  // apply friction in local space of ground object
3491
3499
  const friction = max(this.friction, this.groundObject.friction);
3492
- const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
3500
+ const groundSpeed = this.groundObject.velocity.x;
3493
3501
  this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
3494
3502
  this.groundObject = undefined;
3495
3503
  }
@@ -3500,19 +3508,19 @@ class EngineObject
3500
3508
  const epsilon = .001; // necessary to push slightly outside of the collision
3501
3509
  for (const o of engineObjectsCollide)
3502
3510
  {
3511
+ // skip destroyed, child objects, or self collision
3512
+ if (o.destroyed || o.parent || o === this) continue;
3513
+
3503
3514
  // non solid objects don't collide with each other
3504
- if ((!this.isSolid && !o.isSolid) || o.destroyed || o.parent || o === this)
3505
- continue;
3515
+ if (!this.isSolid && !o.isSolid) continue;
3506
3516
 
3507
3517
  // check collision
3508
- if (!this.isOverlappingObject(o))
3509
- continue;
3518
+ if (!this.isOverlappingObject(o)) continue;
3510
3519
 
3511
3520
  // notify objects of collision and check if should be resolved
3512
3521
  const collide1 = this.collideWithObject(o);
3513
3522
  const collide2 = o.collideWithObject(this);
3514
- if (!collide1 || !collide2)
3515
- continue;
3523
+ if (!collide1 || !collide2) continue;
3516
3524
 
3517
3525
  if (isOverlapping(oldPos, this.size, o.pos, o.size))
3518
3526
  {
@@ -3614,7 +3622,7 @@ class EngineObject
3614
3622
  const delta = y - this.pos.y;
3615
3623
  if (delta < maxMoveUp)
3616
3624
  if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
3617
- {
3625
+ {
3618
3626
  this.pos.y = y;
3619
3627
  debugPhysics && debugRect(this.pos, this.size, '#ff0');
3620
3628
  return;
@@ -3674,10 +3682,10 @@ class EngineObject
3674
3682
 
3675
3683
  // disconnect from parent and destroy children
3676
3684
  this.destroyed = 1;
3677
- this.parent && this.parent.removeChild(this);
3685
+ this.parent?.removeChild(this);
3678
3686
  for (const child of this.children)
3679
3687
  {
3680
- child.parent = 0;
3688
+ child.parent = undefined;
3681
3689
  child.destroy();
3682
3690
  }
3683
3691
  }
@@ -3698,15 +3706,15 @@ class EngineObject
3698
3706
  * @param {Vector2} vec - world space vector */
3699
3707
  worldToLocalVector(vec) { return vec.rotate(-this.angle); }
3700
3708
 
3701
- /** Called to check if a tile collision should be resolved
3709
+ /** Called to check if a tile collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
3702
3710
  * @param {number} tileData - the value of the tile at the position
3703
- * @param {Vector2} pos - tile where the collision occurred
3704
- * @return {boolean} - true if the collision should be resolved */
3711
+ * @param {Vector2} pos - tile where the collision occurred
3712
+ * @return {boolean} - true if the collision should be resolved by modifying it's position and velocity */
3705
3713
  collideWithTile(tileData, pos) { return tileData > 0; }
3706
3714
 
3707
- /** Called to check if a object collision should be resolved
3715
+ /** Called by the engine to check if an object collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
3708
3716
  * @param {EngineObject} object - the object to test against
3709
- * @return {boolean} - true if the collision should be resolved
3717
+ * @return {boolean} - true if the collision should be resolved by modifying it's position and velocity
3710
3718
  */
3711
3719
  collideWithObject(object) { return true; }
3712
3720
 
@@ -3747,9 +3755,9 @@ class EngineObject
3747
3755
  * @return {number} -1 if this.mirror is true, or 1 if not mirrored */
3748
3756
  getMirrorSign() { return this.mirror ? -1 : 1; }
3749
3757
 
3750
- /** Attaches a child to this with a local transform, returns child for chaining
3758
+ /** Attaches a child to this with a local transform, returns child for chaining
3751
3759
  * @param {EngineObject} child
3752
- * @param {Vector2} [localPos=(0,0)]
3760
+ * @param {Vector2} [localPos=vec2()]
3753
3761
  * @param {number} [localAngle]
3754
3762
  * @return {EngineObject} The child object added */
3755
3763
  addChild(child, localPos=vec2(), localAngle=0)
@@ -3773,7 +3781,7 @@ class EngineObject
3773
3781
  const index = this.children.indexOf(child);
3774
3782
  ASSERT(index >= 0, 'child not found in children array');
3775
3783
  index >= 0 && this.children.splice(index, 1);
3776
- child.parent = 0;
3784
+ child.parent = undefined;
3777
3785
  }
3778
3786
 
3779
3787
  /** Check if overlapping another engine object
@@ -3785,7 +3793,7 @@ class EngineObject
3785
3793
 
3786
3794
  /** Check if overlapping a point or aligned bounding box
3787
3795
  * @param {Vector2} pos - Center of box
3788
- * @param {Vector2} [size=(0,0)] - Size of box, uses a point if undefined
3796
+ * @param {Vector2} [size=vec2()] - Size of box, uses a point if undefined
3789
3797
  * @return {boolean} */
3790
3798
  isOverlapping(pos, size=vec2())
3791
3799
  { return isOverlapping(this.pos, this.size, pos, size); }
@@ -3916,10 +3924,11 @@ let drawCount;
3916
3924
  * Create a tile info object using a grid based system
3917
3925
  * - This can take vecs or floats for easier use and conversion
3918
3926
  * - If an index is passed in, the tile size and index will determine the position
3919
- * @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
3927
+ * @param {Vector2|number} [index=0] - Index of the tile in 1d or 2d form
3920
3928
  * @param {Vector2|number} [size] - Size of tile in pixels
3921
- * @param {number} [textureIndex] - Texture index to use
3929
+ * @param {TextureInfo|number} [texture] - Texture index or info to use
3922
3930
  * @param {number} [padding] - How many pixels padding around tiles
3931
+ * @param {number} [bleed] - How many pixels smaller to draw tiles
3923
3932
  * @return {TileInfo}
3924
3933
  * @example
3925
3934
  * tile(2) // a tile at index 2 using the default tile size of 16
@@ -3927,36 +3936,43 @@ let drawCount;
3927
3936
  * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
3928
3937
  * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
3929
3938
  * @memberof Draw */
3930
- function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding)
3939
+ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
3931
3940
  {
3932
- if (headlessMode)
3933
- return new TileInfo;
3941
+ ASSERT(isVector2(index) || typeof index === 'number', 'index must be a vec2 or number');
3942
+ ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
3943
+ ASSERT(isNumber(texture) || texture instanceof TextureInfo, 'texture must be a number or TextureInfo');
3944
+ ASSERT(isNumber(padding), 'padding must be a number');
3945
+
3946
+ if (headlessMode) return new TileInfo;
3934
3947
 
3935
- // if size is a number, make it a vector
3936
3948
  if (typeof size === 'number')
3937
3949
  {
3950
+ // if size is a number, make it a vector
3938
3951
  ASSERT(size > 0);
3939
3952
  size = new Vector2(size, size);
3940
3953
  }
3941
3954
 
3942
3955
  // create tile info object
3943
- const tileInfo = new TileInfo(new Vector2, size, textureIndex, padding);
3956
+ const textureInfo = typeof texture === 'number' ?
3957
+ textureInfos[texture] : texture;
3944
3958
 
3945
3959
  // get the position of the tile
3946
- const textureInfo = textureInfos[textureIndex];
3947
- ASSERT(!!textureInfo, 'Texture not loaded');
3948
3960
  const sizePaddedX = size.x + padding*2;
3949
3961
  const sizePaddedY = size.y + padding*2;
3950
- if (typeof pos === 'number')
3962
+ let x, y;
3963
+ if (typeof index === 'number')
3951
3964
  {
3952
3965
  const cols = textureInfo.size.x / sizePaddedX |0;
3953
- ASSERT(cols > 0, 'Tile size is too big for texture');
3954
- const posX = pos % cols, posY = (pos / cols) |0;
3955
- tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
3966
+ x = index % cols;
3967
+ y = index / cols |0;
3956
3968
  }
3957
3969
  else
3958
- tileInfo.pos.set(pos.x*sizePaddedX+padding, pos.y*sizePaddedY+padding);
3959
- return tileInfo;
3970
+ {
3971
+ x = index.x;
3972
+ y = index.y;
3973
+ }
3974
+ const pos = new Vector2(x*sizePaddedX + padding, y*sizePaddedY + padding);
3975
+ return new TileInfo(pos, size, textureInfo, padding, bleed);
3960
3976
  }
3961
3977
 
3962
3978
  /**
@@ -3966,24 +3982,22 @@ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=til
3966
3982
  class TileInfo
3967
3983
  {
3968
3984
  /** Create a tile info object
3969
- * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
3985
+ * @param {Vector2} [pos=vec2()] - Top left corner of tile in pixels
3970
3986
  * @param {Vector2} [size] - Size of tile in pixels
3971
- * @param {number} [textureIndex] - Texture index to use
3972
- * @param {number} [padding] - How many pixels padding around tiles
3973
- * @param {number} [bleed] - How many pixels smaller to draw tiles
3987
+ * @param {TextureInfo} [textureInfo] - Texture info to use
3988
+ * @param {number} [padding] - How many pixels padding around tiles
3989
+ * @param {number} [bleed] - How many pixels smaller to draw tiles
3974
3990
  */
3975
- constructor(pos=vec2(), size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
3991
+ constructor(pos=vec2(), size=tileDefaultSize, textureInfo=textureInfos[0], padding=tileDefaultPadding, bleed=tileDefaultBleed)
3976
3992
  {
3977
3993
  /** @property {Vector2} - Top left corner of tile in pixels */
3978
3994
  this.pos = pos.copy();
3979
3995
  /** @property {Vector2} - Size of tile in pixels */
3980
3996
  this.size = size.copy();
3981
- /** @property {number} - Texture index to use */
3982
- this.textureIndex = textureIndex;
3983
3997
  /** @property {number} - How many pixels padding around tiles */
3984
3998
  this.padding = padding;
3985
3999
  /** @property {TextureInfo} - The texture info for this tile */
3986
- this.textureInfo = textureInfos[this.textureIndex];
4000
+ this.textureInfo = textureInfo;
3987
4001
  /** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
3988
4002
  this.bleed = bleed;
3989
4003
  }
@@ -3993,7 +4007,7 @@ class TileInfo
3993
4007
  * @return {TileInfo}
3994
4008
  */
3995
4009
  offset(offset)
3996
- { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleed); }
4010
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureInfo, this.padding, this.bleed); }
3997
4011
 
3998
4012
  /** Returns a copy of this tile offset by a number of animation frames
3999
4013
  * @param {number} frame - Offset to apply in animation frames
@@ -4002,23 +4016,33 @@ class TileInfo
4002
4016
  frame(frame)
4003
4017
  {
4004
4018
  ASSERT(typeof frame === 'number');
4005
- return this.offset(new Vector2(frame*(this.size.x+this.padding*2), 0));
4019
+ const w = this.size.x + this.padding*2;
4020
+ const x = frame*w;
4021
+ ASSERT(x < this.textureInfo.size.x, 'frame extends beyond texture width!');
4022
+ return this.offset(new Vector2(x));
4006
4023
  }
4007
4024
 
4008
4025
  /**
4009
4026
  * Set this tile to use a full image in a texture info
4010
- * @param {TextureInfo} textureInfo
4027
+ * @param {TextureInfo} [textureInfo]
4011
4028
  * @return {TileInfo}
4012
4029
  */
4013
- setFullImage(textureInfo)
4030
+ setFullImage(textureInfo=this.textureInfo)
4014
4031
  {
4032
+ this.textureInfo = textureInfo;
4015
4033
  this.pos = new Vector2;
4016
4034
  this.size = textureInfo.size.copy();
4017
- this.textureInfo = textureInfo;
4018
- // do not use padding or bleed
4019
4035
  this.bleed = this.padding = 0;
4020
4036
  return this;
4021
4037
  }
4038
+
4039
+ /**
4040
+ * Returns a tile info for an index using this tile as refrence
4041
+ * @param {Vector2|number} [index=0]
4042
+ * @return {TileInfo}
4043
+ */
4044
+ tile(index)
4045
+ { return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
4022
4046
  }
4023
4047
 
4024
4048
  /**
@@ -4059,19 +4083,19 @@ class TextureInfo
4059
4083
  ///////////////////////////////////////////////////////////////////////////////
4060
4084
  // Drawing functions
4061
4085
 
4062
- /** Draw textured tile centered in world space, with color applied if using WebGL
4063
- * @param {Vector2} pos - Center of the tile in world space
4064
- * @param {Vector2} [size=(1,1)] - Size of the tile in world space
4065
- * @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
4066
- * @param {Color} [color=(1,1,1,1)] - Color to modulate with
4067
- * @param {number} [angle] - Angle to rotate by
4068
- * @param {boolean} [mirror] - Is image flipped along the Y axis?
4069
- * @param {Color} [additiveColor] - Additive color to be applied if any
4086
+ /** Draw textured tile centered in world space
4087
+ * @param {Vector2} pos - Center of the tile in world space
4088
+ * @param {Vector2} [size=vec2(1)] - Size of the tile in world space
4089
+ * @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
4090
+ * @param {Color} [color=WHITE] - Color to modulate with
4091
+ * @param {number} [angle] - Angle to rotate by
4092
+ * @param {boolean} [mirror] - Is image flipped along the Y axis?
4093
+ * @param {Color} [additiveColor] - Additive color to be applied if any
4070
4094
  * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
4071
4095
  * @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
4072
4096
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
4073
4097
  * @memberof Draw */
4074
- function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
4098
+ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
4075
4099
  angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
4076
4100
  {
4077
4101
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -4146,8 +4170,8 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
4146
4170
 
4147
4171
  /** Draw colored rect centered on pos
4148
4172
  * @param {Vector2} pos
4149
- * @param {Vector2} [size=(1,1)]
4150
- * @param {Color} [color=(1,1,1,1)]
4173
+ * @param {Vector2} [size=vec2(1)]
4174
+ * @param {Color} [color=WHITE]
4151
4175
  * @param {number} [angle]
4152
4176
  * @param {boolean} [useWebGL=glEnable]
4153
4177
  * @param {boolean} [screenSpace]
@@ -4160,9 +4184,9 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
4160
4184
 
4161
4185
  /** Draw a rect centered on pos with a gradient from top to bottom
4162
4186
  * @param {Vector2} pos
4163
- * @param {Vector2} [size=(1,1)]
4164
- * @param {Color} [colorTop=(1,1,1,1)]
4165
- * @param {Color} [colorBottom=(0,0,0,1)]
4187
+ * @param {Vector2} [size=vec2(1)]
4188
+ * @param {Color} [colorTop=WHITE]
4189
+ * @param {Color} [colorBottom=BLACK]
4166
4190
  * @param {number} [angle]
4167
4191
  * @param {boolean} [useWebGL=glEnable]
4168
4192
  * @param {boolean} [screenSpace]
@@ -4224,9 +4248,9 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
4224
4248
  /** Draw connected lines between a series of points
4225
4249
  * @param {Array<Vector2>} points
4226
4250
  * @param {number} [width]
4227
- * @param {Color} [color=(1,1,1,1)]
4251
+ * @param {Color} [color=WHITE]
4228
4252
  * @param {boolean} [wrap] - Should the last point connect to the first?
4229
- * @param {Vector2} [pos=(0,0)] - Offset to apply
4253
+ * @param {Vector2} [pos=vec2()] - Offset to apply
4230
4254
  * @param {number} [angle] - Angle to rotate by
4231
4255
  * @param {boolean} [useWebGL=glEnable]
4232
4256
  * @param {boolean} [screenSpace]
@@ -4261,13 +4285,9 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
4261
4285
  for (let i=0; i<points.length; ++i)
4262
4286
  {
4263
4287
  const point = points[i];
4264
- if (i)
4265
- context.lineTo(point.x, point.y);
4266
- else
4267
- context.moveTo(point.x, point.y);
4288
+ context.lineTo(point.x, point.y);
4268
4289
  }
4269
- if (wrap)
4270
- context.closePath();
4290
+ wrap && context.closePath();
4271
4291
  context.stroke();
4272
4292
  }, screenSpace, context);
4273
4293
  }
@@ -4277,8 +4297,8 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
4277
4297
  * @param {Vector2} posA
4278
4298
  * @param {Vector2} posB
4279
4299
  * @param {number} [width]
4280
- * @param {Color} [color=(1,1,1,1)]
4281
- * @param {Vector2} [pos=(0,0)] - Offset to apply
4300
+ * @param {Color} [color=WHITE]
4301
+ * @param {Vector2} [pos=vec2()] - Offset to apply
4282
4302
  * @param {number} [angle] - Angle to rotate by
4283
4303
  * @param {boolean} [useWebGL=glEnable]
4284
4304
  * @param {boolean} [screenSpace]
@@ -4297,12 +4317,12 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
4297
4317
 
4298
4318
  /** Draw colored regular polygon using passed in number of sides
4299
4319
  * @param {Vector2} pos
4300
- * @param {Vector2} [size=(1,1)]
4320
+ * @param {Vector2} [size=vec2(1)]
4301
4321
  * @param {number} [sides]
4302
- * @param {Color} [color=(1,1,1,1)]
4322
+ * @param {Color} [color=WHITE]
4303
4323
  * @param {number} [angle]
4304
4324
  * @param {number} [lineWidth]
4305
- * @param {Color} [lineColor=(0,0,0,1)]
4325
+ * @param {Color} [lineColor=BLACK]
4306
4326
  * @param {boolean} [useWebGL=glEnable]
4307
4327
  * @param {boolean} [screenSpace]
4308
4328
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
@@ -4325,10 +4345,10 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
4325
4345
 
4326
4346
  /** Draw colored polygon using passed in points
4327
4347
  * @param {Array<Vector2>} points - Array of Vector2 points
4328
- * @param {Color} [color=(1,1,1,1)]
4348
+ * @param {Color} [color=WHITE]
4329
4349
  * @param {number} [lineWidth]
4330
- * @param {Color} [lineColor=(0,0,0,1)]
4331
- * @param {Vector2} [pos=(0,0)] - Offset to apply
4350
+ * @param {Color} [lineColor=BLACK]
4351
+ * @param {Vector2} [pos=vec2()] - Offset to apply
4332
4352
  * @param {number} [angle] - Angle to rotate by
4333
4353
  * @param {boolean} [useWebGL=glEnable]
4334
4354
  * @param {boolean} [screenSpace]
@@ -4375,11 +4395,11 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
4375
4395
 
4376
4396
  /** Draw colored ellipse using passed in point
4377
4397
  * @param {Vector2} pos
4378
- * @param {Vector2} [size=(1,1)] - Width and height diameter
4379
- * @param {Color} [color=(1,1,1,1)]
4398
+ * @param {Vector2} [size=vec2(1)] - Width and height diameter
4399
+ * @param {Color} [color=WHITE]
4380
4400
  * @param {number} [angle]
4381
4401
  * @param {number} [lineWidth]
4382
- * @param {Color} [lineColor=(0,0,0,1)]
4402
+ * @param {Color} [lineColor=BLACK]
4383
4403
  * @param {boolean} [useWebGL=glEnable]
4384
4404
  * @param {boolean} [screenSpace]
4385
4405
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
@@ -4421,9 +4441,9 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
4421
4441
  /** Draw colored circle using passed in point
4422
4442
  * @param {Vector2} pos
4423
4443
  * @param {number} [size=1] - Diameter
4424
- * @param {Color} [color=(1,1,1,1)]
4444
+ * @param {Color} [color=WHITE]
4425
4445
  * @param {number} [lineWidth=0]
4426
- * @param {Color} [lineColor=(0,0,0,1)]
4446
+ * @param {Color} [lineColor=BLACK]
4427
4447
  * @param {boolean} [useWebGL=glEnable]
4428
4448
  * @param {boolean} [screenSpace]
4429
4449
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
@@ -4478,9 +4498,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
4478
4498
  * @param {string|number} text
4479
4499
  * @param {Vector2} pos
4480
4500
  * @param {number} [size]
4481
- * @param {Color} [color=(1,1,1,1)]
4501
+ * @param {Color} [color=WHITE]
4482
4502
  * @param {number} [lineWidth]
4483
- * @param {Color} [lineColor=(0,0,0,1)]
4503
+ * @param {Color} [lineColor=BLACK]
4484
4504
  * @param {CanvasTextAlign} [textAlign='center']
4485
4505
  * @param {string} [font=fontDefault]
4486
4506
  * @param {string} [fontStyle]
@@ -4504,10 +4524,10 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
4504
4524
  * Automatically splits new lines into rows
4505
4525
  * @param {string|number} text
4506
4526
  * @param {Vector2} pos
4507
- * @param {number} [size]
4508
- * @param {Color} [color=(1,1,1,1)]
4527
+ * @param {number} size
4528
+ * @param {Color} [color=WHITE]
4509
4529
  * @param {number} [lineWidth]
4510
- * @param {Color} [lineColor=(0,0,0,1)]
4530
+ * @param {Color} [lineColor=BLACK]
4511
4531
  * @param {CanvasTextAlign} [textAlign]
4512
4532
  * @param {string} [font=fontDefault]
4513
4533
  * @param {string} [fontStyle]
@@ -4515,7 +4535,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
4515
4535
  * @param {number} [angle]
4516
4536
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
4517
4537
  * @memberof Draw */
4518
- function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
4538
+ function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
4519
4539
  {
4520
4540
  ASSERT(isString(text), 'text must be a string');
4521
4541
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -4639,7 +4659,7 @@ function worldToScreenDelta(worldDelta)
4639
4659
 
4640
4660
  /** Convert screen space transform to world space
4641
4661
  * @param {Vector2} screenPos
4642
- * @param {Vector2} screenSize
4662
+ * @param {Vector2} screenSize
4643
4663
  * @param {number} [screenAngle]
4644
4664
  * @return {[Vector2, Vector2, number]} - [pos, size, angle]
4645
4665
  * @memberof Draw */
@@ -4700,10 +4720,9 @@ function isOnScreen(pos, size=0)
4700
4720
  * @param {boolean} [additive]
4701
4721
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
4702
4722
  * @memberof Draw */
4703
- function setBlendMode(additive=false, context)
4723
+ function setBlendMode(additive=false, context=drawContext)
4704
4724
  {
4705
4725
  glAdditive = additive;
4706
- context ||= drawContext;
4707
4726
  context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
4708
4727
  }
4709
4728
 
@@ -4718,7 +4737,6 @@ function combineCanvases()
4718
4737
  workContext.fillRect(0,0,w,h); // remove background alpha
4719
4738
  glCopyToContext(workContext);
4720
4739
  workContext.drawImage(mainCanvas, 0, 0);
4721
- mainCanvas.width |= 0;
4722
4740
  mainContext.drawImage(workCanvas, 0, 0);
4723
4741
  }
4724
4742
 
@@ -4823,17 +4841,22 @@ function setCursor(cursorStyle = 'auto')
4823
4841
 
4824
4842
  ///////////////////////////////////////////////////////////////////////////////
4825
4843
 
4844
+ /** Engine font image, 8x8 font provided by the engine
4845
+ * @type {FontImage}
4846
+ * @memberof Draw */
4826
4847
  let engineFontImage;
4827
4848
 
4828
4849
  /**
4829
- * Font Image Object - Draw text on a 2D canvas by using characters in an image
4850
+ * Font Image Object - Draw text by using tiles in an image
4830
4851
  * - 96 characters (from space to tilde) are stored in an image
4831
- * - Uses a default 8x8 font if none is supplied
4832
- * - You can also use fonts from the main tile sheet
4852
+ * - A 8x8 default engine font is supplied for general use
4853
+ * - This system is WebGL enabled for fast text rendering
4854
+ * - Fonts can also be colored and scaled along each axis
4855
+ *
4833
4856
  * @memberof Draw
4834
4857
  * @example
4835
4858
  * // use built in font
4836
- * const font = new FontImage;
4859
+ * const font = engineFontImage;
4837
4860
  *
4838
4861
  * // draw text
4839
4862
  * font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
@@ -4841,70 +4864,112 @@ let engineFontImage;
4841
4864
  class FontImage
4842
4865
  {
4843
4866
  /** Create an image font
4844
- * @param {HTMLImageElement} [image] - Image for the font, default if undefined
4845
- * @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
4846
- * @param {Vector2} [paddingSize=(0,1)] - How much space between characters
4867
+ * @param {TileInfo} tileInfo - Tile info of first characeter in font
4847
4868
  */
4848
- constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
4869
+ constructor(tileInfo)
4849
4870
  {
4850
- // load default font image
4851
- if (!image && !engineFontImage)
4852
- {
4853
- engineFontImage = new Image;
4854
- engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
4855
- }
4856
-
4857
- this.image = image || engineFontImage;
4858
- this.tileSize = tileSize;
4859
- this.paddingSize = paddingSize;
4871
+ ASSERT(!!tileInfo, 'tileInfo is required for FontImage');
4872
+
4873
+ /** @property {TileInfo} - Tile info for the font */
4874
+ this.tileInfo = tileInfo.frame(0);
4860
4875
  }
4861
4876
 
4862
4877
  /** Draw text in world space using the image font
4863
- * @param {string|number} text
4878
+ * @param {string|number} text
4864
4879
  * @param {Vector2} pos
4865
- * @param {number} [scale=.25]
4866
- * @param {boolean} [center]
4867
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
4880
+ * @param {Vector2|number} [size]
4881
+ * @param {boolean} [center=true]
4882
+ * @param {Color} [color=WHITE]
4883
+ * @param {boolean} [useWebGL=glEnable]
4884
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
4868
4885
  */
4869
- drawText(text, pos, scale=1, center, context=drawContext)
4886
+ drawText(text, pos, size=1, center, color, useWebGL, context)
4870
4887
  {
4871
- this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
4888
+ ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
4889
+
4890
+ if (typeof size === 'number')
4891
+ {
4892
+ // if size is a number, make it a vector
4893
+ ASSERT(size > 0);
4894
+ size *= cameraScale;
4895
+ size = new Vector2(size, size);
4896
+ }
4897
+ else
4898
+ size = size.scale(cameraScale);
4899
+ this.drawTextScreen(text, worldToScreen(pos), size, center, color, useWebGL, context);
4872
4900
  }
4873
4901
 
4874
4902
  /** Draw text in screen space using the image font
4875
- * @param {string|number} text
4903
+ * @param {string|number} text
4876
4904
  * @param {Vector2} pos
4877
- * @param {number} [scale]
4905
+ * @param {Vector2|number} size
4878
4906
  * @param {boolean} [center]
4879
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
4907
+ * @param {Color} [color=WHITE]
4908
+ * @param {boolean} [useWebGL=glEnable]
4909
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
4880
4910
  */
4881
- drawTextScreen(text, pos, scale=4, center=true, context=drawContext)
4911
+ drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
4882
4912
  {
4883
- context.save();
4884
- const size = this.tileSize;
4885
- const drawSize = size.add(this.paddingSize).scale(scale);
4886
- const cols = this.image.width / this.tileSize.x |0;
4887
- (text+'').split('\n').forEach((line, i)=>
4913
+ ASSERT(isString(text), 'text must be a string');
4914
+ ASSERT(isVector2(pos), 'pos must be a vec2');
4915
+ ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
4916
+ ASSERT(isColor(color), 'color must be a color');
4917
+
4918
+ // if size is a number, make it a vector
4919
+ size = typeof size === 'number' ? new Vector2(size, size) : size;
4920
+
4921
+ // precache objects for drawing
4922
+ const drawPos = new Vector2;
4923
+ const tileInfo = this.tileInfo;
4924
+ const padding = tileInfo.padding;
4925
+ const sizePaddedX = tileInfo.size.x + padding*2;
4926
+ const sizePaddedY = tileInfo.size.y + padding*2;
4927
+ const cols = tileInfo.textureInfo.size.x / sizePaddedX |0;
4928
+
4929
+ // draw each line of text
4930
+ (text+'').split('\n').forEach((line, j)=>
4888
4931
  {
4889
- const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
4890
- for (let j=line.length; j--;)
4932
+ const centerOffset = center ? (line.length-1) * size.x / 2 : 0;
4933
+ for (let i=line.length; i--;)
4891
4934
  {
4892
- // draw each character
4893
- let charCode = line[j].charCodeAt(0);
4894
- if (charCode < 32 || charCode > 127)
4895
- charCode = 127; // unknown character
4896
-
4897
- // get the character source location and draw it
4898
- const tile = charCode - 32;
4899
- const x = tile % cols;
4900
- const y = tile / cols |0;
4901
- const drawPos = pos.add(vec2(j,i).multiply(drawSize));
4902
- context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
4903
- drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
4935
+ // get the character index
4936
+ const charCode = line.charCodeAt(i);
4937
+ const index = charCode < 32 || charCode > 127 ?
4938
+ 95 : charCode - 32; // handle out of range characters
4939
+
4940
+ // get the position of the tile
4941
+ const x = index % cols;
4942
+ const y = index / cols |0;
4943
+ tileInfo.pos.x = x*sizePaddedX + padding;
4944
+ tileInfo.pos.y = y*sizePaddedY + padding;
4945
+
4946
+ // draw the tile
4947
+ drawPos.x = pos.x + i * size.x - centerOffset |0;
4948
+ drawPos.y = pos.y + j * size.y |0;
4949
+ drawTile(drawPos, size, tileInfo, color, 0, false, undefined, useWebGL, true, context);
4904
4950
  }
4905
4951
  });
4906
- context.restore();
4907
4952
  }
4953
+ }
4954
+
4955
+ // load engine font, called automatically on startup
4956
+ function fontImageInit()
4957
+ {
4958
+ return new Promise(resolve =>
4959
+ {
4960
+ // create the engine font
4961
+ const image = new Image;
4962
+ image.onerror = image.onload = ()=>
4963
+ {
4964
+ const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
4965
+ const textureInfo = new TextureInfo(image);
4966
+ const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
4967
+ engineFontImage = new FontImage(tileInfo);
4968
+ resolve();
4969
+ }
4970
+ image.crossOrigin = 'anonymous';
4971
+ image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAeAQMAAABnrVXaAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAjpJREFUOMu9kzFu2zAUhn+CAROgqrk+B2l0BWYxMjlXeYaAtFtbdA1sGgHqRQfI0CNkSG5AwYB0BQ8d5Bsomwah6CPVeGg6tEPzAxLwyI+P78cP4u9lNO9OoMKnLMOobG5020/yaj/MrRcCGh1gBbyiLTPJEYaIiom5KM9Jq7KgynMGtb6L4GL4MF2H4LQKCXTvDVw2I4MsgZT7QLExdiutH+D08VOP3INXRrWX1/mmpbkNgAPYRVANb4xpcegYvhiNbIXauQICEjBuYLfMakaakWQeXxiZ0VDtuJCKs3ztMV59QtsHJNcRxDzfdL21ty3PrfIcXTN+E+GFAv6T5nbT9jd50/WFxb5ksdAv49qS6ouymG66ji08UMT6moykYLAo+V0j23GN4m829ZySAD5K7QsBfQTvOG8eE+gTeGYRAmnNAubN3hf5Zv9tJWDHp/VTuaSm7SN4fyINQqaNO3RMVxvpSPXnOChnRNvFcGY0gnwiPswYwTKVPE0zVtX3mTEIOoFzaqLrGuJaV+Uqumb71fVk/VoOH3cdLNQP/FHi8hV0CQNoqBZsUPlLPMsdCJro9QAaQQ0woDy9BJm0eTxCFnO9srcYlhNVlfR2EyTrph1uUtbUtAJifwRgrKuYdXVHeb0YI3QpawohQHkloI3J5FuVwI5ORxC9k2Tuz9Ir1IjgeIPGMHYkAZe2RuYkmWFmt3gGbTPOmBUWVTmRmHtGrfpzG/yuQNOKa6gBB/WA9khitPgl6/GP+gl2Af6tCbvaygAAAABJRU5ErkJggg==';
4972
+ });
4908
4973
  }
4909
4974
  /**
4910
4975
  * LittleJS Input System
@@ -4961,6 +5026,10 @@ let inputPreventDefault = true;
4961
5026
  * @memberof Input */
4962
5027
  let gamepadPrimary = 0;
4963
5028
 
5029
+ /** True if a touch device has been detected
5030
+ * @memberof Input */
5031
+ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
5032
+
4964
5033
  /** Prevents input continuing to the default browser handling
4965
5034
  * This is useful to disable for html menus so the browser can handle input normally
4966
5035
  * @param {boolean} preventDefault
@@ -5159,10 +5228,6 @@ function gamepadStickCount(gamepad=gamepadPrimary)
5159
5228
  return gamepadStickData[gamepad]?.length ?? 0;
5160
5229
  }
5161
5230
 
5162
- /** True if a touch device has been detected
5163
- * @memberof Input */
5164
- const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
5165
-
5166
5231
  ///////////////////////////////////////////////////////////////////////////////
5167
5232
 
5168
5233
  /** Pulse the vibration hardware if it exists
@@ -5171,7 +5236,7 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
5171
5236
  function vibrate(pattern=100)
5172
5237
  {
5173
5238
  ASSERT(isNumber(pattern) || isArray(pattern), 'pattern must be a number or array');
5174
- vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern);
5239
+ vibrateEnable && !headlessMode && navigator?.vibrate?.(pattern);
5175
5240
  }
5176
5241
 
5177
5242
  /** Cancel any ongoing vibration
@@ -5289,8 +5354,8 @@ function inputInit()
5289
5354
  }
5290
5355
  function onMouseUp(e)
5291
5356
  {
5292
- if (isTouchDevice && touchInputEnable)
5293
- return;
5357
+ if (isTouchDevice && touchInputEnable) return;
5358
+
5294
5359
  inputData[0][e.button] = (inputData[0][e.button]&2) | 4;
5295
5360
  }
5296
5361
  function onMouseMove(e)
@@ -5476,8 +5541,7 @@ function inputUpdate()
5476
5541
  // update touch gamepad if enabled
5477
5542
  if (touchGamepadEnable && isTouchDevice)
5478
5543
  {
5479
- if (!touchGamepadTimer.isSet())
5480
- return;
5544
+ if (!touchGamepadTimer.isSet()) return;
5481
5545
 
5482
5546
  // read virtual analog stick
5483
5547
  gamepadPrimary = 0; // touch gamepad uses index 0
@@ -5515,12 +5579,10 @@ function inputUpdate()
5515
5579
  }
5516
5580
 
5517
5581
  // return if gamepads are disabled or not supported
5518
- if (!gamepadsEnable || !navigator || !navigator.getGamepads)
5519
- return;
5582
+ if (!gamepadsEnable || !navigator || !navigator.getGamepads) return;
5520
5583
 
5521
5584
  // only poll gamepads when focused or in debug mode
5522
- if (!debug && !document.hasFocus())
5523
- return;
5585
+ if (!debug && !document.hasFocus()) return;
5524
5586
 
5525
5587
  // poll gamepads
5526
5588
  const maxGamepads = 8;
@@ -5557,8 +5619,7 @@ function inputUpdate()
5557
5619
  data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
5558
5620
 
5559
5621
  // check for any input on this gamepad, analog must be full press
5560
- if (button.pressed)
5561
- if (!button.value || button.value > .9)
5622
+ if (button.pressed && (!button.value || button.value > .9))
5562
5623
  hadInput = true;
5563
5624
  }
5564
5625
 
@@ -5587,7 +5648,7 @@ function inputUpdate()
5587
5648
  }
5588
5649
 
5589
5650
  // copy dpad to left analog stick when pressed
5590
- if (gamepadDirectionEmulateStick && !dpad.isZero())
5651
+ if (gamepadDirectionEmulateStick && (dpad.x || dpad.y))
5591
5652
  sticks[0] = dpad.clampLength();
5592
5653
  }
5593
5654
 
@@ -5616,13 +5677,11 @@ function inputRender()
5616
5677
  function touchGamepadRender()
5617
5678
  {
5618
5679
  if (!touchInputEnable || !isTouchDevice || headlessMode) return;
5619
- if (!touchGamepadEnable || !touchGamepadTimer.isSet())
5620
- return;
5680
+ if (!touchGamepadEnable || !touchGamepadTimer.isSet()) return;
5621
5681
 
5622
5682
  // fade off when not touching or paused
5623
5683
  const alpha = percent(touchGamepadTimer.get(), 4, 3);
5624
- if (!alpha || paused)
5625
- return;
5684
+ if (!alpha || paused) return;
5626
5685
 
5627
5686
  // setup the canvas
5628
5687
  const context = mainContext;
@@ -5775,8 +5834,7 @@ class Sound
5775
5834
  {
5776
5835
  // remove randomness so it can be applied on playback
5777
5836
  const randomnessIndex = 1, defaultRandomness = .05;
5778
- this.randomness = zzfxSound[randomnessIndex] !== undefined ?
5779
- zzfxSound[randomnessIndex] : defaultRandomness;
5837
+ this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
5780
5838
  zzfxSound[randomnessIndex] = 0;
5781
5839
 
5782
5840
  // generate the zzfx samples
@@ -5856,7 +5914,7 @@ class Sound
5856
5914
  * @return {number} - How long the sound is in seconds (undefined if loading)
5857
5915
  */
5858
5916
  getDuration()
5859
- { return this.sampleChannels && this.sampleRate ? this.sampleChannels[0].length / this.sampleRate : 0; }
5917
+ { return this.sampleChannels?.[0].length / this.sampleRate || 0; }
5860
5918
 
5861
5919
  /** Check if sound is loaded, for sounds fetched from a url
5862
5920
  * @return {boolean} - True if sound is loaded and ready to play
@@ -5948,8 +6006,7 @@ class SoundWave extends Sound
5948
6006
  this.sampleRate = audioBuffer.sampleRate;
5949
6007
  this.sampleChannels = sampleChannels;
5950
6008
  this.loadedPercent = 1;
5951
- if (this.onloadCallback)
5952
- this.onloadCallback(this);
6009
+ this.onloadCallback?.(this);
5953
6010
  }
5954
6011
  }
5955
6012
 
@@ -6152,7 +6209,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
6152
6209
 
6153
6210
  /** Stop all queued speech
6154
6211
  * @memberof Audio */
6155
- function speakStop() {speechSynthesis && speechSynthesis.cancel();}
6212
+ function speakStop() {speechSynthesis?.cancel();}
6156
6213
 
6157
6214
  /** Get frequency of a note on a musical scale
6158
6215
  * @param {number} semitoneOffset - How many semitones away from the root note
@@ -6411,7 +6468,7 @@ function tileCollisionGetData(pos)
6411
6468
 
6412
6469
  /** Check if a tile layer collides with another object
6413
6470
  * @param {Vector2} pos
6414
- * @param {Vector2} [size=(0,0)]
6471
+ * @param {Vector2} [size=vec2()]
6415
6472
  * @param {EngineObject} [object] - An object or undefined for generic test
6416
6473
  * @param {boolean} [solidOnly] - Only check solid layers if true
6417
6474
  * @return {TileCollisionLayer}
@@ -6529,20 +6586,20 @@ function tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLa
6529
6586
  class TileLayerData
6530
6587
  {
6531
6588
  /** Create a tile layer data object, one for each tile in a TileLayer
6532
- * @param {number} [tile] - The tile to use, untextured if undefined
6589
+ * @param {number} [tile] - The tile to use, untextured if undefined
6533
6590
  * @param {number} [direction] - Integer direction of tile, in 90 degree increments
6534
- * @param {boolean} [mirror] - If the tile should be mirrored along the x axis
6535
- * @param {Color} [color] - Color of the tile */
6591
+ * @param {boolean} [mirror] - If the tile should be mirrored along the x axis
6592
+ * @param {Color} [color] - Color of the tile */
6536
6593
  constructor(tile, direction=0, mirror=false, color=new Color)
6537
6594
  {
6538
- /** @property {number} - The tile to use, untextured if undefined */
6539
- this.tile = tile;
6540
- /** @property {number} - Integer direction of tile, in 90 degree increments */
6595
+ /** @property {number} - The tile to use, untextured if undefined */
6596
+ this.tile = tile;
6597
+ /** @property {number} - Integer direction of tile, in 90 degree increments */
6541
6598
  this.direction = direction;
6542
6599
  /** @property {boolean} - If the tile should be mirrored along the x axis */
6543
- this.mirror = mirror;
6544
- /** @property {Color} - Color of the tile */
6545
- this.color = color.copy();
6600
+ this.mirror = mirror;
6601
+ /** @property {Color} - Color of the tile */
6602
+ this.color = color.copy();
6546
6603
  }
6547
6604
 
6548
6605
  /** Set this tile to clear, it will not be rendered */
@@ -6553,7 +6610,7 @@ class TileLayerData
6553
6610
  /**
6554
6611
  * Canvas Layer - cached off screen rendering system
6555
6612
  * - Contains an offscreen canvas that can be rendered to
6556
- * - WebGL rendering is optional, call useWebGL to enable
6613
+ * - WebGL rendering is optional, call updateWebGL to enable/update
6557
6614
  * @extends EngineObject
6558
6615
  * @memberof TileLayers
6559
6616
  * @example
@@ -6567,8 +6624,9 @@ class CanvasLayer extends EngineObject
6567
6624
  * @param {number} [angle] - Angle the layer is rotated by
6568
6625
  * @param {number} [renderOrder] - Objects sorted by renderOrder
6569
6626
  * @param {Vector2} [canvasSize] - Default size of canvas, can be changed later
6627
+ * @param {boolean} [useWebGL] - Should this layer use WebGL for rendering
6570
6628
  */
6571
- constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512))
6629
+ constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512), useWebGL=glEnable)
6572
6630
  {
6573
6631
  ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
6574
6632
  super(position, size, undefined, angle, WHITE, renderOrder);
@@ -6578,13 +6636,10 @@ class CanvasLayer extends EngineObject
6578
6636
  /** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
6579
6637
  this.context = this.canvas?.getContext('2d');
6580
6638
  /** @property {TextureInfo} - Texture info to use for this object rendering */
6581
- const useWebGL = false; // do not use webgl by default
6582
6639
  this.textureInfo = new TextureInfo(this.canvas, useWebGL);
6583
- /** @property {boolean} - True if WebGL texture needs to be refreshed */
6584
- this.refreshWebGL = false;
6585
6640
 
6586
6641
  // disable physics by default
6587
- this.mass = this.gravityScale = this.friction = this.restitution = 0;
6642
+ this.mass = 0;
6588
6643
  }
6589
6644
 
6590
6645
  /** Destroy this canvas layer */
@@ -6599,7 +6654,7 @@ class CanvasLayer extends EngineObject
6599
6654
  // Render the layer, called automatically by the engine
6600
6655
  render()
6601
6656
  {
6602
- this.draw(this.pos, this.size, this.angle, this.color, this.mirror, this.additiveColor);
6657
+ this.draw(this.pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
6603
6658
  }
6604
6659
 
6605
6660
  /** Draw this canvas layer centered in world space, with color applied if using WebGL
@@ -6612,103 +6667,54 @@ class CanvasLayer extends EngineObject
6612
6667
  * @param {boolean} [screenSpace] - If true the pos and size are in screen space
6613
6668
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
6614
6669
  * @memberof Draw */
6615
- draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
6670
+ draw(pos, size, color=WHITE, angle=0, mirror=false, additiveColor, screenSpace=false, context)
6616
6671
  {
6617
- const useWebGL = glEnable && this.textureInfo.hasWebGL();
6618
- if (useWebGL && this.refreshWebGL)
6619
- {
6620
- // update the WebGL texture
6621
- this.textureInfo.createWebGLTexture();
6622
- this.refreshWebGL = false;
6623
- }
6624
-
6625
6672
  // draw the canvas layer as a single tile that uses the whole texture
6626
6673
  const tileInfo = new TileInfo().setFullImage(this.textureInfo);
6674
+ const useWebGL = this.hasWebGL();
6627
6675
  drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
6628
6676
  }
6629
6677
 
6630
- /**
6631
- * @callback Canvas2DDrawCallback - Function that draws to a canvas 2D context
6632
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
6633
- * @memberof TileLayers
6634
- */
6635
-
6636
- /** Draw onto the layer canvas in world space (bypass WebGL)
6678
+ /** Draw a tile onto the layer canvas in world space
6637
6679
  * @param {Vector2} pos
6638
- * @param {Vector2} size
6639
- * @param {number} angle
6640
- * @param {boolean} mirror
6641
- * @param {Canvas2DDrawCallback} drawFunction */
6642
- drawCanvas2D(pos, size, angle, mirror, drawFunction)
6680
+ * @param {Vector2} [size=vec2(1)]
6681
+ * @param {TileInfo} [tileInfo]
6682
+ * @param {Color} [color=WHITE]
6683
+ * @param {number} [angle]
6684
+ * @param {boolean} [mirror] */
6685
+ drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle=0, mirror=false)
6643
6686
  {
6644
- if (!this.context) return;
6645
-
6646
- const context = this.context;
6647
- context.save();
6648
6687
  pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
6649
6688
  size = size.multiply(this.tileInfo.size);
6650
- context.translate(pos.x, this.canvas.height - pos.y);
6651
- context.rotate(angle);
6652
- context.scale(mirror ? -size.x : size.x, size.y);
6653
- drawFunction(context);
6654
- context.restore();
6655
- }
6689
+ pos.y = this.canvas.height - pos.y;
6656
6690
 
6657
- /** Draw a tile onto the layer canvas in world space
6658
- * @param {Vector2} pos
6659
- * @param {Vector2} [size=(1,1)]
6660
- * @param {TileInfo} [tileInfo]
6661
- * @param {Color} [color=(1,1,1,1)]
6662
- * @param {number} [angle=0]
6663
- * @param {boolean} [mirror=false] */
6664
- drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
6665
- {
6666
- this.drawCanvas2D(pos, size, angle, mirror, (context)=>
6667
- {
6668
- const textureInfo = tileInfo && tileInfo.textureInfo;
6669
- if (textureInfo)
6670
- {
6671
- context.globalAlpha = color.a; // only alpha is supported
6672
- context.drawImage(textureInfo.image,
6673
- tileInfo.pos.x, tileInfo.pos.y,
6674
- tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
6675
- context.globalAlpha = 1;
6676
- }
6677
- else
6678
- {
6679
- // untextured
6680
- context.fillStyle = color.toString();
6681
- context.fillRect(-.5, -.5, 1, 1);
6682
- }
6683
- });
6691
+ // draw the tile onto the layer canvas
6692
+ const oldMainCanvasSize = mainCanvasSize;
6693
+ mainCanvasSize = vec2(this.canvas.width, this.canvas.height);
6694
+ const useWebGL = this.hasWebGL();
6695
+ useWebGL && glSetRenderTarget(this.textureInfo.glTexture);
6696
+ const drawContext = useWebGL ? undefined : this.context;
6697
+ drawTile(pos, size, tileInfo, color, angle, mirror, undefined, useWebGL, true, drawContext);
6698
+ useWebGL && glSetRenderTarget();
6699
+ mainCanvasSize = oldMainCanvasSize;
6684
6700
  }
6685
6701
 
6686
6702
  /** Draw a rectangle onto the layer canvas in world space
6687
6703
  * @param {Vector2} pos
6688
- * @param {Vector2} [size=(1,1)]
6689
- * @param {Color} [color=(1,1,1,1)]
6690
- * @param {number} [angle=0] */
6704
+ * @param {Vector2} [size=vec2(1)]
6705
+ * @param {Color} [color=WHITE]
6706
+ * @param {number} [angle] */
6691
6707
  drawRect(pos, size, color, angle)
6692
6708
  { this.drawTile(pos, size, undefined, color, angle); }
6693
6709
 
6694
- /** Create or update the WebGL texture for this layer
6695
- * @param {boolean} [enable] - enable WebGL rendering and update the texture
6696
- * @param {boolean} [immediate] - shoulkd the texture be updated immediately
6697
- */
6698
- useWebGL(enable=true, immediate=false)
6699
- {
6700
- if (!immediate && enable && this.textureInfo.hasWebGL())
6701
- {
6702
- // refresh the texture when needed
6703
- this.refreshWebGL = true;
6704
- return;
6705
- }
6710
+ /** Create WebGL texture if necessary and copy layer canvas to it */
6711
+ updateWebGL()
6712
+ { this.textureInfo.createWebGLTexture(); }
6706
6713
 
6707
- if (enable)
6708
- this.textureInfo.createWebGLTexture();
6709
- else
6710
- this.textureInfo.destroyWebGLTexture();
6711
- }
6714
+ /** Check if this layer is using WebGL
6715
+ * @return {boolean} */
6716
+ hasWebGL()
6717
+ { return glEnable && this.textureInfo.hasWebGL(); }
6712
6718
  }
6713
6719
 
6714
6720
  ///////////////////////////////////////////////////////////////////////////////
@@ -6727,50 +6733,67 @@ class CanvasLayer extends EngineObject
6727
6733
  class TileLayer extends CanvasLayer
6728
6734
  {
6729
6735
  /** Create a tile layer object
6730
- * @param {Vector2} position - World space position
6731
- * @param {Vector2} size - World space size
6732
- * @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
6736
+ * @param {Vector2} position - World space position
6737
+ * @param {Vector2} size - World space size
6738
+ * @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
6733
6739
  * @param {number} [renderOrder] - Objects are sorted by renderOrder
6734
6740
  */
6735
6741
  constructor(position, size, tileInfo=tile(), renderOrder=0)
6736
6742
  {
6737
6743
  const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
6738
- super(position, size, 0, renderOrder, canvasSize);
6739
-
6740
- // set tile info
6741
- this.tileInfo = tileInfo;
6742
-
6743
- // init tile data
6744
+ const useWebGL = true;
6745
+ super(position, size, 0, renderOrder, canvasSize, useWebGL);
6746
+
6747
+ /** @property {TileInfo} - Default tile info for layer */
6748
+ this.tileInfo = undefined;
6749
+ /** @property {Array<TileLayerData>} - Default tile info for layer */
6744
6750
  this.data = [];
6745
- for (let j = this.size.area(); j--;)
6746
- this.data.push(new TileLayerData);
6751
+ /** @property {boolean} - Is this layer using a webgl texture? */
6752
+ this.isUsingWebGL = false;
6747
6753
 
6748
6754
  if (headlessMode)
6749
6755
  {
6750
- // disable rendering
6751
- this.redraw = () => {};
6752
- this.render = () => {};
6753
- this.redrawStart = () => {};
6754
- this.redrawEnd = () => {};
6755
- this.drawTileData = () => {};
6756
- this.drawCanvas2D = () => {};
6757
- this.useWebGL = () => {};
6756
+ // disable rendering in headless mode
6757
+ this.render = () => {};
6758
+ this.redraw = () => {};
6759
+ this.redrawStart = () => {};
6760
+ this.redrawEnd = () => {};
6761
+ this.drawTileData = () => {};
6762
+ this.redrawTileData = () => {};
6763
+ this.drawLayerTile = () => {};
6764
+ this.drawLayerRect = () => {};
6765
+ this.clearLayerRect = () => {};
6766
+ return;
6758
6767
  }
6768
+
6769
+ if (tileInfo)
6770
+ {
6771
+ // set tile info
6772
+ this.tileInfo = tileInfo.frame(0);
6773
+ this.tileInfo.bleed = 0; // disable bleed for tile layers
6774
+ }
6775
+
6776
+ // init tile data
6777
+ for (let j = this.size.area(); j--;)
6778
+ this.data.push(new TileLayerData);
6759
6779
  }
6760
6780
 
6761
6781
  /** Set data at a given position in the array
6762
6782
  * @param {Vector2} layerPos - Local position in array
6763
- * @param {TileLayerData} data - Data to set
6783
+ * @param {TileLayerData} data - Data to set
6764
6784
  * @param {boolean} [redraw] - Force the tile to redraw if true */
6765
6785
  setData(layerPos, data, redraw=false)
6766
6786
  {
6787
+ layerPos = layerPos.floor();
6767
6788
  ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
6768
6789
  ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
6769
- if (layerPos.arrayCheck(this.size))
6770
- {
6771
- this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
6772
- redraw && this.drawTileData(layerPos);
6773
- }
6790
+
6791
+ if (!layerPos.arrayCheck(this.size)) return;
6792
+ this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
6793
+
6794
+ if (!redraw) return;
6795
+ const isRedraw = drawContext === this.context;
6796
+ isRedraw ? this.drawTileData(layerPos) : this.redrawTileData(layerPos);
6774
6797
  }
6775
6798
 
6776
6799
  /** Get data at a given position in the array
@@ -6779,7 +6802,18 @@ class TileLayer extends CanvasLayer
6779
6802
  getData(layerPos)
6780
6803
  {
6781
6804
  ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
6782
- return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
6805
+ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
6806
+ }
6807
+
6808
+ // Update the tile layer, refresh texture if needed
6809
+ update()
6810
+ {
6811
+ if (!glEnable && this.isUsingWebGL)
6812
+ {
6813
+ // redraw the layer if webgl was disabled or context lost
6814
+ this.isUsingWebGL = false;
6815
+ this.redraw();
6816
+ }
6783
6817
  }
6784
6818
 
6785
6819
  // Render the tile layer, called automatically by the engine
@@ -6787,40 +6821,35 @@ class TileLayer extends CanvasLayer
6787
6821
  {
6788
6822
  ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
6789
6823
 
6790
- if (this.refreshWebGL)
6791
- {
6792
- // update the WebGL texture
6793
- this.textureInfo.createWebGLTexture();
6794
- this.refreshWebGL = false;
6795
- }
6796
-
6797
- // draw the tile layer as a single tile
6798
- const tileInfo = new TileInfo().setFullImage(this.textureInfo);
6799
6824
  const size = this.drawSize || this.size;
6800
6825
  const pos = this.pos.add(size.scale(.5));
6801
- const useWebGL = glEnable && this.textureInfo.hasWebGL();
6802
- drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
6826
+ this.draw(pos, this.size, this.color, this.angle, this.mirror, this.additiveColor);
6803
6827
  }
6804
6828
 
6829
+ /** Called after this layer is redrawn, does nothing by default */
6830
+ onRedraw() {}
6831
+
6805
6832
  /** Draw all the tile data to an offscreen canvas
6806
- * - This may be slow in some browsers but only needs to be done once */
6833
+ * - This may be slow if not using webgl but only needs to be done once */
6807
6834
  redraw()
6808
6835
  {
6809
6836
  this.redrawStart(true);
6810
6837
  for (let x = this.size.x; x--;)
6811
6838
  for (let y = this.size.y; y--;)
6812
6839
  this.drawTileData(vec2(x,y), false);
6840
+ this.hasWebGL() && glFlush();
6841
+ this.onRedraw();
6813
6842
  this.redrawEnd();
6814
- this.useWebGL();
6815
6843
  }
6816
6844
 
6817
6845
  /** Call to start the redraw process
6818
- * - This can be used to manually update small parts of the level
6846
+ * - This can be used to manually update parts of the level
6819
6847
  * @param {boolean} [clear] - Should it clear the canvas before drawing */
6820
6848
  redrawStart(clear=false)
6821
6849
  {
6822
6850
  if (!this.context) return;
6823
-
6851
+ ASSERT(drawContext !== this.context);
6852
+
6824
6853
  // save current render settings
6825
6854
  /** @type {[CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number, Color]} */
6826
6855
  this.savedRenderSettings = [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor];
@@ -6828,33 +6857,37 @@ class TileLayer extends CanvasLayer
6828
6857
  // set the draw canvas and context to this layer
6829
6858
  // use camera settings to match this layer's canvas
6830
6859
  drawContext = this.context;
6831
- cameraPos = this.size.scale(.5);
6832
- const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
6833
- cameraScale = tileSize.x;
6834
- canvasClearColor = CLEAR_BLACK;
6860
+ const tileSize = this.tileInfo?.size ?? vec2(1);
6835
6861
  mainCanvasSize = this.size.multiply(tileSize);
6836
- if (clear)
6862
+ canvasClearColor = CLEAR_BLACK;
6863
+ cameraPos = this.size.multiply(tileSize).scale(.5);
6864
+ cameraScale = 1;
6865
+
6866
+ // set render target to this layer
6867
+ this.isUsingWebGL = this.hasWebGL();
6868
+ if (this.isUsingWebGL)
6869
+ glSetRenderTarget(this.textureInfo.glTexture, clear);
6870
+ else
6837
6871
  {
6838
- // clear and set size
6839
- this.canvas.width = mainCanvasSize.x;
6840
- this.canvas.height = mainCanvasSize.y;
6872
+ // disable smoothing for pixel art
6873
+ this.context.imageSmoothingEnabled = !tilesPixelated;
6874
+ if (clear)
6875
+ {
6876
+ // clear and set size
6877
+ this.canvas.width = mainCanvasSize.x;
6878
+ this.canvas.height = mainCanvasSize.y;
6879
+ }
6841
6880
  }
6842
-
6843
- // disable smoothing for pixel art
6844
- drawContext.imageSmoothingEnabled = !tilesPixelated;
6845
-
6846
- // setup gl rendering if enabled
6847
- glPreRender();
6848
6881
  }
6849
6882
 
6850
6883
  /** Call to end the redraw process */
6851
6884
  redrawEnd()
6852
6885
  {
6853
6886
  if (!this.context) return;
6887
+ ASSERT(drawContext === this.context);
6854
6888
 
6855
- ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
6856
- glCopyToContext(drawContext);
6857
- //saveCanvas(this.canvas);
6889
+ if (glEnable && this.textureInfo.glTexture)
6890
+ glSetRenderTarget();
6858
6891
 
6859
6892
  // set stuff back to normal
6860
6893
  [drawContext, mainCanvasSize, cameraPos, cameraScale, canvasClearColor] = this.savedRenderSettings;
@@ -6869,24 +6902,75 @@ class TileLayer extends CanvasLayer
6869
6902
  drawTileData(layerPos, clear=true)
6870
6903
  {
6871
6904
  if (!this.context) return;
6905
+ ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
6872
6906
 
6873
- // clear out where the tile was, for full opaque tiles this can be skipped
6874
- const s = this.tileInfo.size;
6875
- if (clear)
6876
- {
6877
- const pos = layerPos.multiply(s);
6878
- this.context.clearRect(pos.x, this.canvas.height-pos.y, s.x, -s.y);
6879
- }
6907
+ // clear out where the tile was, can be skipped for fully opaque tiles
6908
+ const drawSize = this.tileInfo?.size ?? vec2(1);
6909
+ const drawPos = layerPos.multiply(drawSize);
6910
+ clear && this.clearLayerRect(drawPos, drawSize);
6880
6911
 
6881
6912
  // draw the tile if it has layer data
6882
6913
  const d = this.getData(layerPos);
6883
- if (d.tile !== undefined)
6884
- {
6885
- ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
6886
- const pos = layerPos.add(vec2(.5));
6887
- const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex, this.tileInfo.padding);
6888
- drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
6889
- }
6914
+ if (!d.tile) return;
6915
+
6916
+ const tileInfo = this.tileInfo && this.tileInfo.tile(d.tile);
6917
+ this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
6918
+ }
6919
+
6920
+ /** Draw the tile at a given position in the tile grid
6921
+ * This can be used to clear tiles when they are destroyed
6922
+ * For better performance use drawTileData inside a redrawStart/End block
6923
+ * @param {Vector2} layerPos
6924
+ * @param {boolean} [clear] - should the old tile be cleared
6925
+ */
6926
+ redrawTileData(layerPos, clear=true)
6927
+ {
6928
+ if (!this.context) return;
6929
+ ASSERT(drawContext !== this.context, 'redrawStart() should not be active when calling redrawTileData(), instead use drawTileData()');
6930
+
6931
+ this.redrawStart();
6932
+ this.drawTileData(layerPos, clear);
6933
+ this.redrawEnd();
6934
+ }
6935
+
6936
+ /** Draw textured tile in layer space
6937
+ * @param {Vector2} pos - Position in pixel coordinates
6938
+ * @param {Vector2} [size=vec2(1)] - Size of the tile
6939
+ * @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
6940
+ * @param {Color} [color=WHITE] - Color to modulate with
6941
+ * @param {number} [angle] - Angle to rotate by
6942
+ * @param {boolean} [mirror] - Is image flipped along the Y axis?
6943
+ * @param {Color} [additiveColor] - Additive color to be applied if any */
6944
+ drawLayerTile(pos, size=vec2(1), tileInfo, color=WHITE,
6945
+ angle=0, mirror, additiveColor)
6946
+ {
6947
+ const drawPos = pos.add(size.scale(.5));
6948
+ drawTile(drawPos, size, tileInfo, color, angle, mirror, additiveColor);
6949
+ }
6950
+
6951
+ /** Clear a rectangle in layer space
6952
+ * @param {Vector2} pos
6953
+ * @param {Vector2} size
6954
+ * @param {Color} [color=WHITE] - Color to modulate with
6955
+ * @param {number} [angle] - Angle to rotate by
6956
+ */
6957
+ drawLayerRect(pos, size, color, angle=0)
6958
+ { this.drawLayerTile(pos, size, undefined, color, angle); }
6959
+
6960
+ /** Clear a rectangle in layer space
6961
+ * @param {Vector2} pos - position in pixel coordinates
6962
+ * @param {Vector2} size
6963
+ */
6964
+ clearLayerRect(pos, size)
6965
+ {
6966
+ ASSERT(drawContext === this.context, 'must call redrawStart() before clearing tiles');
6967
+
6968
+ const x = pos.x, y = this.canvas.height - pos.y - size.y;
6969
+ const useWebGL = this.hasWebGL();
6970
+ if (useWebGL)
6971
+ glClearRect(x, y, size.x, size.y);
6972
+ else
6973
+ this.context.clearRect(x, y, size.x, size.y);
6890
6974
  }
6891
6975
  }
6892
6976
 
@@ -6895,7 +6979,6 @@ class TileLayer extends CanvasLayer
6895
6979
  * Tile Collision Layer - a tile layer with collision
6896
6980
  * - adds collision data and functions to TileLayer
6897
6981
  * - there can be multiple tile collision layers
6898
- * - tile collision layers should not overlap each other
6899
6982
  * @extends TileLayer
6900
6983
  * @memberof TileLayers
6901
6984
  */
@@ -6967,7 +7050,7 @@ class TileCollisionLayer extends TileLayer
6967
7050
 
6968
7051
  /** Check if collision with another object should occur
6969
7052
  * @param {Vector2} pos
6970
- * @param {Vector2} [size=(0,0)]
7053
+ * @param {Vector2} [size=vec2()]
6971
7054
  * @param {EngineObject} [object]
6972
7055
  * @return {boolean} */
6973
7056
  collisionTest(pos, size=new Vector2, object)
@@ -7287,7 +7370,7 @@ class ParticleEmitter extends EngineObject
7287
7370
  particle.mirror = randBool();
7288
7371
 
7289
7372
  // call particle create callback
7290
- this.particleCreateCallback && this.particleCreateCallback(particle);
7373
+ this.particleCreateCallback?.(particle);
7291
7374
 
7292
7375
  // return the newly created particle
7293
7376
  return particle;
@@ -7372,7 +7455,7 @@ class Particle extends EngineObject
7372
7455
  const c = this.colorEnd;
7373
7456
  this.color.set(c.r, c.g, c.b, c.a);
7374
7457
  this.size.set(this.sizeEnd, this.sizeEnd);
7375
- this.destroyCallback && this.destroyCallback(this);
7458
+ this.destroyCallback?.(this);
7376
7459
  this.destroyed = 1;
7377
7460
  }
7378
7461
  }
@@ -7650,7 +7733,7 @@ let glContext;
7650
7733
  let glAntialias = true;
7651
7734
 
7652
7735
  // WebGL internal variables not exposed to documentation
7653
- let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
7736
+ let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glInstancedVAO, glPolyVAO, glFramebuffer, glRenderTarget, glCanBeEnabled = true;
7654
7737
 
7655
7738
  // WebGL internal constants
7656
7739
  const gl_ARRAY_BUFFER_SIZE = 5e5;
@@ -7726,7 +7809,7 @@ function glInit(rootElement)
7726
7809
  // setup instanced rendering shader program
7727
7810
  glShader = glCreateProgram(
7728
7811
  '#version 300 es\n' + // specify GLSL ES version
7729
- 'precision highp float;'+ // use highp for better accuracy
7812
+ 'precision highp float;'+ // use highp for accuracy
7730
7813
  'uniform mat4 m;'+ // transform matrix
7731
7814
  'in vec2 g;'+ // in: geometry
7732
7815
  'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
@@ -7741,7 +7824,7 @@ function glInit(rootElement)
7741
7824
  '}' // end of shader
7742
7825
  ,
7743
7826
  '#version 300 es\n' + // specify GLSL ES version
7744
- 'precision highp float;'+ // use highp for better accuracy
7827
+ 'precision highp float;'+ // use highp for accuracy
7745
7828
  'uniform sampler2D s;'+ // texture
7746
7829
  'in vec2 v;'+ // in: uv
7747
7830
  'in vec4 d,e;'+ // in: color, additiveColor
@@ -7779,45 +7862,62 @@ function glInit(rootElement)
7779
7862
  glColorData = new Uint32Array(glInstanceData);
7780
7863
  glArrayBuffer = glContext.createBuffer();
7781
7864
  glGeometryBuffer = glContext.createBuffer();
7865
+ glFramebuffer = glContext.createFramebuffer();
7866
+ glBatchCount = 0;
7782
7867
 
7783
7868
  // create the geometry buffer, triangle strip square
7784
- const geometry = new Float32Array([glBatchCount=0,0,1,0,0,1,1,1]);
7869
+ const geometry = new Float32Array([0,0,1,0,0,1,1,1]);
7785
7870
  glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
7786
7871
  glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
7872
+
7873
+ let offset, shader, stride;
7874
+ const initVertexAttrib = (name, type, typeSize, size, divisor=0)=>
7875
+ {
7876
+ const location = glContext.getAttribLocation(shader, name);
7877
+ const normalize = typeSize === 1;
7878
+ const fixedStride = typeSize && stride;
7879
+ glContext.enableVertexAttribArray(location);
7880
+ glContext.vertexAttribPointer(location, size, type, normalize, fixedStride, offset);
7881
+ glContext.vertexAttribDivisor(location, divisor);
7882
+ offset += size*typeSize;
7883
+ }
7884
+
7885
+ // setup VAO for instanced rendering
7886
+ glInstancedVAO = glContext.createVertexArray();
7887
+ glContext.bindVertexArray(glInstancedVAO);
7888
+
7889
+ // configure instanced vertex attributes
7890
+ offset = 0, shader = glShader, stride = gl_INSTANCE_BYTE_STRIDE;
7891
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
7892
+ initVertexAttrib('g', glContext.FLOAT, 0, 2); // geometry
7893
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
7894
+ glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
7895
+ initVertexAttrib('p', glContext.FLOAT, 4, 4, 1); // position & size
7896
+ initVertexAttrib('u', glContext.FLOAT, 4, 4, 1); // texture coords
7897
+ initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4, 1); // color
7898
+ initVertexAttrib('a', glContext.UNSIGNED_BYTE, 1, 4, 1); // additiveColor
7899
+ initVertexAttrib('r', glContext.FLOAT, 4, 1, 1); // rotation
7900
+
7901
+ // setup VAO for poly rendering
7902
+ glPolyVAO = glContext.createVertexArray();
7903
+ glContext.bindVertexArray(glPolyVAO);
7904
+
7905
+ // configure poly vertex attributes
7906
+ offset = 0, shader = glPolyShader, stride = gl_POLY_VERTEX_BYTE_STRIDE;
7907
+ initVertexAttrib('p', glContext.FLOAT, 4, 2); // position
7908
+ initVertexAttrib('c', glContext.UNSIGNED_BYTE, 1, 4); // color
7787
7909
  }
7788
7910
  }
7789
7911
 
7790
- function glSetInstancedMode()
7912
+ function glSetInstancedMode(force=false)
7791
7913
  {
7792
- if (!glPolyMode) return;
7914
+ if (!force && !glPolyMode) return;
7793
7915
 
7794
7916
  // setup instanced mode
7795
7917
  glFlush();
7796
7918
  glPolyMode = false;
7797
7919
  glContext.useProgram(glShader);
7798
-
7799
- // set vertex attributes
7800
- let offset = 0;
7801
- const initVertexAttribArray = (name, type, typeSize, size)=>
7802
- {
7803
- const location = glContext.getAttribLocation(glShader, name);
7804
- const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
7805
- const divisor = typeSize && 1; // only if not geometry
7806
- const normalize = typeSize === 1; // only if color
7807
- glContext.enableVertexAttribArray(location);
7808
- glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
7809
- glContext.vertexAttribDivisor(location, divisor);
7810
- offset += size*typeSize;
7811
- }
7812
- glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
7813
- initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
7814
- glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
7815
- glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
7816
- initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
7817
- initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
7818
- initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
7819
- initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
7820
- initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
7920
+ glContext.bindVertexArray(glInstancedVAO);
7821
7921
  }
7822
7922
 
7823
7923
  function glSetPolyMode()
@@ -7828,36 +7928,30 @@ function glSetPolyMode()
7828
7928
  glFlush();
7829
7929
  glPolyMode = true;
7830
7930
  glContext.useProgram(glPolyShader);
7831
-
7832
- // set vertex attributes
7833
- let offset = 0;
7834
- const initVertexAttribArray = (name, type, typeSize, size)=>
7835
- {
7836
- const location = glContext.getAttribLocation(glPolyShader, name);
7837
- const normalize = typeSize === 1; // only normalize if color
7838
- const stride = gl_POLY_VERTEX_BYTE_STRIDE;
7839
- glContext.enableVertexAttribArray(location);
7840
- glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
7841
- glContext.vertexAttribDivisor(location, 0);
7842
- offset += size*typeSize;
7843
- }
7844
- glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
7845
- glContext.bufferData(glContext.ARRAY_BUFFER, gl_ARRAY_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
7846
- initVertexAttribArray('p', glContext.FLOAT, 4, 2); // position
7847
- initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
7931
+ glContext.bindVertexArray(glPolyVAO);
7848
7932
  }
7849
7933
 
7850
7934
  // Setup WebGL render each frame, called automatically by engine
7851
7935
  // Also used by tile layer rendering when redrawing tiles
7852
- function glPreRender()
7936
+ function glPreRender(clear=true)
7853
7937
  {
7854
7938
  if (!glEnable || !glContext) return;
7855
7939
 
7856
- // clear the canvas
7857
- glClearCanvas();
7940
+ ASSERT(!glBatchCount, 'glPreRender called with unflushed batch.');
7941
+
7942
+ if (!glRenderTarget)
7943
+ {
7944
+ // set to same size as main canvas
7945
+ glCanvas.width = mainCanvasSize.x;
7946
+ glCanvas.height = mainCanvasSize.y;
7947
+ }
7948
+ glContext.viewport(0, 0, mainCanvasSize.x, mainCanvasSize.y);
7949
+ clear && glClearCanvas();
7858
7950
 
7859
7951
  // build the transform matrix
7860
7952
  const s = vec2(2*cameraScale).divide(mainCanvasSize);
7953
+ if (glRenderTarget)
7954
+ s.y = -s.y; // invert y when using render target
7861
7955
  const rotatedCam = cameraPos.rotate(-cameraAngle);
7862
7956
  const p = vec2(-1).subtract(rotatedCam.multiply(s));
7863
7957
  const ca = cos(cameraAngle);
@@ -7886,12 +7980,14 @@ function glPreRender()
7886
7980
  glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
7887
7981
  }
7888
7982
 
7983
+ // rebind the array buffer
7984
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
7985
+
7889
7986
  // start with additive blending off
7890
7987
  glAdditive = glBatchAdditive = false;
7891
7988
 
7892
- // force it to set instanced mode by first setting poly mode true
7893
- glPolyMode = true;
7894
- glSetInstancedMode();
7989
+ // force it to set instanced mode
7990
+ glSetInstancedMode(true);
7895
7991
  }
7896
7992
 
7897
7993
  /** Clear the canvas and setup the viewport
@@ -7900,13 +7996,9 @@ function glClearCanvas()
7900
7996
  {
7901
7997
  if (!glContext) return;
7902
7998
 
7903
- // clear and set to same size as main canvas
7904
- glCanvas.width = mainCanvasSize.x;
7905
- glCanvas.height = mainCanvasSize.y;
7906
- glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
7999
+ // clear using the canvasClearColor
7907
8000
  const color = canvasClearColor;
7908
- if (color.a > 0)
7909
- glContext.clearColor(color.r, color.g, color.b, color.a);
8001
+ glContext.clearColor(color.r, color.g, color.b, color.a);
7910
8002
  glContext.clear(glContext.COLOR_BUFFER_BIT);
7911
8003
  }
7912
8004
 
@@ -7983,7 +8075,7 @@ function glCreateTexture(image)
7983
8075
  // build the texture
7984
8076
  const texture = glContext.createTexture();
7985
8077
  let mipMap = false;
7986
- if (image && image.width)
8078
+ if (image?.width)
7987
8079
  {
7988
8080
  glSetTextureData(texture, image);
7989
8081
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
@@ -8004,7 +8096,9 @@ function glCreateTexture(image)
8004
8096
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, minFilter);
8005
8097
  if (mipMap)
8006
8098
  glContext.generateMipmap(glContext.TEXTURE_2D);
8007
- glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture); // rebind active texture
8099
+
8100
+ // rebind active texture
8101
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
8008
8102
  return texture;
8009
8103
  }
8010
8104
 
@@ -8027,10 +8121,12 @@ function glSetTextureData(texture, image)
8027
8121
  if (!glContext) return;
8028
8122
 
8029
8123
  // build the texture
8030
- ASSERT(!!image && image.width > 0, 'Invalid image data.');
8124
+ ASSERT(image?.width > 0, 'Invalid image data.');
8031
8125
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
8032
8126
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
8033
- glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture); // rebind active texture
8127
+
8128
+ // rebind active texture
8129
+ glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
8034
8130
  }
8035
8131
 
8036
8132
  /** Tells WebGL to create or update the glTexture and start tracking it
@@ -8249,6 +8345,48 @@ function glDrawColoredPoints(points, pointColors)
8249
8345
  glBatchCount += vertCount;
8250
8346
  }
8251
8347
 
8348
+ /** Set the WebGL render target to the given texture or back to the canvas
8349
+ * @param {WebGLTexture} [texture] - a texture or undefined to use normal glCanvas
8350
+ * @param {boolean} [clear] - should the render target be cleared
8351
+ * @memberof WebGL */
8352
+ function glSetRenderTarget(texture, clear=false)
8353
+ {
8354
+ if (texture)
8355
+ {
8356
+ glRenderTarget = texture;
8357
+ glContext.bindFramebuffer(glContext.FRAMEBUFFER, glFramebuffer);
8358
+ glContext.framebufferTexture2D(glContext.FRAMEBUFFER,
8359
+ glContext.COLOR_ATTACHMENT0, glContext.TEXTURE_2D, texture, 0);
8360
+ glPreRender(clear);
8361
+ }
8362
+ else
8363
+ {
8364
+ glFlush();
8365
+ glRenderTarget = undefined;
8366
+ glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
8367
+ }
8368
+ }
8369
+
8370
+ /** Clear out a rectangle area of the WebGL canvas or render target
8371
+ * @param {number} x
8372
+ * @param {number} y
8373
+ * @param {number} width
8374
+ * @param {number} height
8375
+ * @memberof WebGL */
8376
+ function glClearRect(x, y, width, height)
8377
+ {
8378
+ if (!glEnable) return;
8379
+
8380
+ // Enable scissor test to clear only the specified area
8381
+ glContext.enable(glContext.SCISSOR_TEST);
8382
+ glContext.scissor(x, y, width, height);
8383
+ glContext.clearColor(0, 0, 0, 0);
8384
+ glContext.clear(glContext.COLOR_BUFFER_BIT);
8385
+ glContext.disable(glContext.SCISSOR_TEST);
8386
+ }
8387
+
8388
+ ///////////////////////////////////////////////////////////////////////////////
8389
+
8252
8390
  // WebGL internal function to convert polygon to outline triangle strip
8253
8391
  function glMakeOutline(points, width, wrap=true)
8254
8392
  {
@@ -8382,23 +8520,20 @@ function glPolyStrip(points)
8382
8520
  const a = points[i0], b = points[i1], c = points[i2];
8383
8521
 
8384
8522
  // check if convex
8385
- if (cross(a, b, c) < e)
8386
- continue;
8523
+ if (cross(a, b, c) < e) continue;
8387
8524
 
8388
8525
  // check if any other point is inside
8389
8526
  let hasInside = false;
8390
8527
  for (let j = 0; j < indices.length; j++)
8391
8528
  {
8392
8529
  const k = indices[j];
8393
- if (k === i0 || k === i1 || k === i2)
8394
- continue;
8530
+ if (k === i0 || k === i1 || k === i2) continue;
8531
+
8395
8532
  const p = points[k];
8396
8533
  hasInside = pointInTriangle(p, a, b, c);
8397
- if (hasInside)
8398
- break;
8534
+ if (hasInside) break;
8399
8535
  }
8400
- if (hasInside)
8401
- continue;
8536
+ if (hasInside) continue;
8402
8537
 
8403
8538
  // found valid ear
8404
8539
  triangles.push([i0, i1, i2]);
@@ -8423,8 +8558,7 @@ function glPolyStrip(points)
8423
8558
  worstIndex = i;
8424
8559
  }
8425
8560
  }
8426
- if (worstIndex < 0)
8427
- break;
8561
+ if (worstIndex < 0) break;
8428
8562
 
8429
8563
  const i0 = indices[(worstIndex + indices.length - 1) % indices.length];
8430
8564
  const i1 = indices[worstIndex];
@@ -8655,14 +8789,16 @@ class PostProcessPlugin
8655
8789
  {
8656
8790
  /** Create global post processing shader
8657
8791
  * @param {string} shaderCode
8658
- * @param {boolean} [includeMainCanvas]
8659
- * @example
8660
- * // create the post process plugin object
8661
- * new PostProcessPlugin(shaderCode);
8662
- */
8663
- constructor(shaderCode, includeMainCanvas=true)
8792
+ * @param {boolean} [includeMainCanvas] - combine mainCanvs onto glCanvas
8793
+ * @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
8794
+ * @example
8795
+ * // create the post process plugin object
8796
+ * new PostProcessPlugin(shaderCode);
8797
+ */
8798
+ constructor(shaderCode, includeMainCanvas=false, feedbackTexture=false)
8664
8799
  {
8665
8800
  ASSERT(!postProcess, 'Post process already initialized');
8801
+ ASSERT(!(includeMainCanvas && feedbackTexture), 'Post process cannot both include main canvas and use feedback texture');
8666
8802
  postProcess = this;
8667
8803
 
8668
8804
  if (!shaderCode) // default shader pass through
@@ -8670,9 +8806,10 @@ class PostProcessPlugin
8670
8806
 
8671
8807
  /** @property {WebGLProgram} - Shader for post processing */
8672
8808
  this.shader = undefined;
8673
-
8674
8809
  /** @property {WebGLTexture} - Texture for post processing */
8675
8810
  this.texture = undefined;
8811
+ /** @property {WebGLVertexArrayObject} - Vertex array object */
8812
+ this.vao = undefined;
8676
8813
 
8677
8814
  // setup the post processing plugin
8678
8815
  initPostProcess();
@@ -8681,7 +8818,6 @@ class PostProcessPlugin
8681
8818
  function initPostProcess()
8682
8819
  {
8683
8820
  if (headlessMode) return;
8684
-
8685
8821
  if (!glEnable)
8686
8822
  {
8687
8823
  console.warn('PostProcessPlugin: WebGL not enabled!');
@@ -8692,14 +8828,14 @@ class PostProcessPlugin
8692
8828
  postProcess.texture = glCreateTexture();
8693
8829
  postProcess.shader = glCreateProgram(
8694
8830
  '#version 300 es\n' + // specify GLSL ES version
8695
- 'precision highp float;'+ // use highp for better accuracy
8831
+ 'precision highp float;'+ // use highp for accuracy
8696
8832
  'in vec2 p;'+ // position
8697
8833
  'void main(){'+ // shader entry point
8698
8834
  'gl_Position=vec4(p+p-1.,1,1);'+ // set position
8699
8835
  '}' // end of shader
8700
8836
  ,
8701
8837
  '#version 300 es\n' + // specify GLSL ES version
8702
- 'precision highp float;'+ // use highp for better accuracy
8838
+ 'precision highp float;'+ // use highp for accuracy
8703
8839
  'uniform sampler2D iChannel0;'+ // input texture
8704
8840
  'uniform vec3 iResolution;'+ // size of output texture
8705
8841
  'uniform float iTime;'+ // time
@@ -8710,6 +8846,17 @@ class PostProcessPlugin
8710
8846
  'c.a=1.;'+ // always use full alpha
8711
8847
  '}' // end of shader
8712
8848
  );
8849
+
8850
+ // setup VAO for post processing
8851
+ postProcess.vao = glContext.createVertexArray();
8852
+ glContext.bindVertexArray(postProcess.vao);
8853
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
8854
+
8855
+ // configure vertex attributes
8856
+ const vertexByteStride = 8;
8857
+ const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
8858
+ glContext.enableVertexAttribArray(pLocation);
8859
+ glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
8713
8860
  }
8714
8861
  function postProcessContextLost()
8715
8862
  {
@@ -8724,14 +8871,17 @@ class PostProcessPlugin
8724
8871
  }
8725
8872
  function postProcessRender()
8726
8873
  {
8727
- if (headlessMode) return;
8728
-
8729
- if (!glEnable)
8730
- return;
8874
+ if (headlessMode || !glEnable) return;
8731
8875
 
8732
8876
  // clear out the buffer
8733
8877
  glFlush();
8734
-
8878
+
8879
+ // setup shader program to draw a quad
8880
+ glContext.useProgram(postProcess.shader);
8881
+ glContext.bindVertexArray(postProcess.vao);
8882
+ glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, true);
8883
+ glContext.disable(glContext.BLEND);
8884
+
8735
8885
  // setup texture
8736
8886
  glContext.activeTexture(glContext.TEXTURE0);
8737
8887
  glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
@@ -8742,29 +8892,32 @@ class PostProcessPlugin
8742
8892
  workCanvas.height = mainCanvasSize.y;
8743
8893
  glCopyToContext(workContext);
8744
8894
  workContext.drawImage(mainCanvas, 0, 0);
8895
+ mainCanvas.width |= 0
8745
8896
 
8746
8897
  // copy work canvas to texture
8747
8898
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
8748
8899
  }
8749
-
8750
- // setup shader program to draw a quad
8751
- glContext.useProgram(postProcess.shader);
8752
- glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
8753
- glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, 1);
8754
- glContext.disable(glContext.BLEND);
8755
-
8756
- // set vertex position attribute
8757
- const vertexByteStride = 8;
8758
- const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
8759
- glContext.enableVertexAttribArray(pLocation);
8760
- glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
8761
-
8900
+ else if (!feedbackTexture)
8901
+ {
8902
+ // copy glCanvas to texture
8903
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
8904
+ }
8905
+
8762
8906
  // set uniforms and draw
8763
8907
  const uniformLocation = (name)=>glContext.getUniformLocation(postProcess.shader, name);
8764
8908
  glContext.uniform1i(uniformLocation('iChannel0'), 0);
8765
8909
  glContext.uniform1f(uniformLocation('iTime'), time);
8766
8910
  glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
8767
8911
  glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
8912
+
8913
+ if (feedbackTexture)
8914
+ {
8915
+ // pass glCanvas back to overlay texture
8916
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
8917
+ }
8918
+
8919
+ // force it to set instanced mode
8920
+ glSetInstancedMode(true);
8768
8921
  }
8769
8922
  }
8770
8923
  }
@@ -9445,7 +9598,7 @@ class UISystemPlugin
9445
9598
  const up = 'ArrowUp', down = 'ArrowDown', left = 'ArrowLeft', right = 'ArrowRight';
9446
9599
  if (both)
9447
9600
  {
9448
- return keyIsDown(up) || keyIsDown(left) ? -1 :
9601
+ return keyIsDown(up) || keyIsDown(left) ? -1 :
9449
9602
  keyIsDown(down) || keyIsDown(right) ? 1 : 0;
9450
9603
  }
9451
9604
  const back = vertical ? up : left;
@@ -9476,7 +9629,7 @@ class UISystemPlugin
9476
9629
  * @return {boolean} */
9477
9630
  getNavigationWasPressed()
9478
9631
  {
9479
- return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
9632
+ return isUsingGamepad ? gamepadWasPressed(0, gamepadPrimary) :
9480
9633
  keyWasPressed('Space') || keyWasPressed('Enter');
9481
9634
  }
9482
9635
 
@@ -9524,7 +9677,7 @@ class UISystemPlugin
9524
9677
  buttonYes.textHeight = 40;
9525
9678
  buttonYes.navigationIndex = 1;
9526
9679
  buttonYes.hoverColor = hsl(0,1,.5);
9527
- buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
9680
+ buttonYes.onClick = ()=> { closeMenu(); yesCallback && yesCallback(); };
9528
9681
  confirmMenu.addChild(buttonYes);
9529
9682
 
9530
9683
  // no button
@@ -9554,8 +9707,8 @@ class UISystemPlugin
9554
9707
  class UIObject
9555
9708
  {
9556
9709
  /** Create a UIObject
9557
- * @param {Vector2} [pos=(0,0)]
9558
- * @param {Vector2} [size=(1,1)]
9710
+ * @param {Vector2} [pos=vec2()]
9711
+ * @param {Vector2} [size=vec2(1)]
9559
9712
  */
9560
9713
  constructor(pos=vec2(), size=vec2())
9561
9714
  {
@@ -9570,7 +9723,7 @@ class UIObject
9570
9723
  this.size = size.copy();
9571
9724
  /** @property {Color} - Color of the object */
9572
9725
  this.color = uiSystem.defaultColor.copy();
9573
- /** @property {Color} - Color of the object when active, uses color if undefined */
9726
+ /** @property {Color} - Color of the object when active, uses hoverColor if undefined */
9574
9727
  this.activeColor = undefined;
9575
9728
  /** @property {string} - Text for this ui object */
9576
9729
  this.text = undefined;
@@ -9668,10 +9821,10 @@ class UIObject
9668
9821
 
9669
9822
  // disconnect from parent and destroy children
9670
9823
  this.destroyed = 1;
9671
- this.parent && this.parent.removeChild(this);
9824
+ this.parent?.removeChild(this);
9672
9825
  for (const child of this.children)
9673
9826
  {
9674
- child.parent = 0;
9827
+ child.parent = undefined;
9675
9828
  child.destroy();
9676
9829
  }
9677
9830
  }
@@ -9767,10 +9920,10 @@ class UIObject
9767
9920
  this.interactive && this.isActiveObject() && !this.disabled ?
9768
9921
  this.color : this.lineColor;
9769
9922
  const color = isNavigationObject ? this.hoverColor :
9770
- this.disabled ? this.disabledColor :
9771
- this.interactive ?
9772
- this.isHoverObject() ? this.hoverColor :
9773
- this.isActiveObject() ? this.activeColor || this.color :
9923
+ this.disabled ? this.disabledColor :
9924
+ this.interactive ?
9925
+ this.isActiveObject() ? this.activeColor || this.hoverColor :
9926
+ this.isHoverObject() ? this.hoverColor :
9774
9927
  this.color : this.color;
9775
9928
  const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
9776
9929
 
@@ -9782,7 +9935,7 @@ class UIObject
9782
9935
  getTextSize()
9783
9936
  {
9784
9937
  return vec2(
9785
- this.textWidth || this.textFitScale * this.size.x,
9938
+ this.textWidth || this.textFitScale * this.size.x,
9786
9939
  this.textHeight || this.textFitScale * this.size.y);
9787
9940
  }
9788
9941
 
@@ -9830,9 +9983,9 @@ class UIObject
9830
9983
  renderDebug(visible=true)
9831
9984
  {
9832
9985
  // apply color based on state
9833
- const color =
9986
+ const color =
9834
9987
  !visible ? GREEN :
9835
- this.isHoverObject() ? YELLOW :
9988
+ this.isHoverObject() ? YELLOW :
9836
9989
  this.disabled ? PURPLE :
9837
9990
  this.interactive ? RED : BLUE;
9838
9991
  uiSystem.drawRect(this.pos, this.size, CLEAR_BLACK, 4, color);
@@ -12354,357 +12507,349 @@ function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor
12354
12507
 
12355
12508
  export
12356
12509
  {
12357
- // Engine
12358
- engineName,
12359
- engineVersion,
12360
- frameRate,
12361
- timeDelta,
12362
- engineObjects,
12363
- frame,
12364
- time,
12365
- timeReal,
12366
- paused,
12367
- getPaused,
12368
- setPaused,
12369
- engineInit,
12370
- engineObjectsUpdate,
12371
- engineObjectsDestroy,
12372
- engineObjectsCollect,
12373
- engineObjectsCallback,
12374
- engineObjectsRaycast,
12375
- engineAddPlugin,
12376
-
12377
- // Globals
12378
- debug,
12379
- debugOverlay,
12380
- debugWatermark,
12381
-
12382
- // Debug
12383
- ASSERT,
12384
- LOG,
12385
- debugRect,
12386
- debugPoly,
12387
- debugCircle,
12388
- debugPoint,
12389
- debugLine,
12390
- debugOverlap,
12391
- debugText,
12392
- debugClear,
12393
- debugScreenshot,
12394
- debugShowErrors,
12395
- debugVideoCaptureStart,
12396
- debugVideoCaptureStop,
12397
- debugVideoCaptureIsActive,
12398
-
12399
- // Settings
12400
- cameraPos,
12401
- cameraAngle,
12402
- cameraScale,
12403
- canvasColorTiles,
12404
- canvasClearColor,
12405
- canvasMaxSize,
12406
- canvasMinAspect,
12407
- canvasMaxAspect,
12408
- canvasFixedSize,
12409
- canvasPixelated,
12410
- tilesPixelated,
12411
- fontDefault,
12412
- showSplashScreen,
12413
- headlessMode,
12414
- tileDefaultSize,
12415
- tileDefaultPadding,
12416
- tileDefaultBleed,
12417
- enablePhysicsSolver,
12418
- objectDefaultMass,
12419
- objectDefaultDamping,
12420
- objectDefaultAngleDamping,
12421
- objectDefaultRestitution,
12422
- objectDefaultFriction,
12423
- objectMaxSpeed,
12424
- gravity,
12425
- particleEmitRateScale,
12426
- glEnable,
12427
- gamepadsEnable,
12428
- gamepadDirectionEmulateStick,
12429
- inputWASDEmulateDirection,
12430
- touchGamepadEnable,
12431
- touchGamepadCenterButton,
12432
- touchGamepadAnalog,
12433
- touchGamepadSize,
12434
- touchGamepadAlpha,
12435
- vibrateEnable,
12436
- soundEnable,
12437
- soundVolume,
12438
- soundDefaultRange,
12439
- soundDefaultTaper,
12440
- medalDisplayTime,
12441
- medalDisplaySlideTime,
12442
- medalDisplaySize,
12443
-
12444
- // Setters for globals
12445
- setCameraPos,
12446
- setCameraAngle,
12447
- setCameraScale,
12448
- setCanvasColorTiles,
12449
- setCanvasClearColor,
12450
- setCanvasMaxSize,
12451
- setCanvasMinAspect,
12452
- setCanvasMaxAspect,
12453
- setCanvasFixedSize,
12454
- setCanvasPixelated,
12455
- setTilesPixelated,
12456
- setFontDefault,
12457
- setShowSplashScreen,
12458
- setHeadlessMode,
12459
- setGLEnable,
12460
- setTileDefaultSize,
12461
- setTileDefaultPadding,
12462
- setTileDefaultBleed,
12463
- setEnablePhysicsSolver,
12464
- setObjectDefaultMass,
12465
- setObjectDefaultDamping,
12466
- setObjectDefaultAngleDamping,
12467
- setObjectDefaultRestitution,
12468
- setObjectDefaultFriction,
12469
- setObjectMaxSpeed,
12470
- setGravity,
12471
- setParticleEmitRateScale,
12472
- setTouchInputEnable,
12473
- setGamepadsEnable,
12474
- setGamepadDirectionEmulateStick,
12475
- setInputWASDEmulateDirection,
12476
- setTouchGamepadEnable,
12477
- setTouchGamepadCenterButton,
12478
- setTouchGamepadButtonCount,
12479
- setTouchGamepadAnalog,
12480
- setTouchGamepadSize,
12481
- setTouchGamepadAlpha,
12482
- setVibrateEnable,
12483
- setSoundEnable,
12484
- setSoundVolume,
12485
- setSoundDefaultRange,
12486
- setSoundDefaultTaper,
12487
- setMedalDisplayTime,
12488
- setMedalDisplaySlideTime,
12489
- setMedalDisplaySize,
12490
- setMedalsPreventUnlock,
12491
- setDebugWatermark,
12492
- setDebugKey,
12493
-
12494
- // Math
12495
- PI,
12496
- abs,
12497
- floor,
12498
- ceil,
12499
- round,
12500
- min,
12501
- max,
12502
- sign,
12503
- hypot,
12504
- log2,
12505
- sin,
12506
- cos,
12507
- tan,
12508
- atan2,
12509
- mod,
12510
- clamp,
12511
- percent,
12512
- distanceWrap,
12513
- lerpWrap,
12514
- distanceAngle,
12515
- lerpAngle,
12516
- lerp,
12517
- smoothStep,
12518
- nearestPowerOfTwo,
12519
- isOverlapping,
12520
- isIntersecting,
12521
- wave,
12522
-
12523
- // Utilities
12524
- formatTime,
12525
- fetchJSON,
12526
- saveText,
12527
- saveCanvas,
12528
- saveDataURL,
12529
-
12530
- // Random
12531
- rand,
12532
- randInt,
12533
- randBool,
12534
- randSign,
12535
- randInCircle,
12536
- randVec2,
12537
- randColor,
12538
-
12539
- // Utility Classes
12540
- RandomGenerator,
12541
- Vector2,
12542
- Color,
12543
- Timer,
12544
- vec2,
12545
- rgb,
12546
- hsl,
12547
- isColor,
12548
- isVector2,
12549
- isNumber,
12550
- isString,
12551
- isArray,
12552
-
12553
- // Default Colors
12554
- WHITE,
12555
- CLEAR_WHITE,
12556
- BLACK,
12557
- CLEAR_BLACK,
12558
- GRAY,
12559
- RED,
12560
- ORANGE,
12561
- YELLOW,
12562
- GREEN,
12563
- CYAN,
12564
- BLUE,
12565
- PURPLE,
12566
- MAGENTA,
12567
-
12568
- // Draw
12569
- tile,
12570
- TileInfo,
12571
- TextureInfo,
12572
- mainCanvas,
12573
- mainContext,
12574
- drawContext,
12575
- workCanvas,
12576
- workContext,
12577
- workReadCanvas,
12578
- workReadContext,
12579
- mainCanvasSize,
12580
- textureInfos,
12581
- drawCount,
12582
- screenToWorld,
12583
- worldToScreen,
12584
- screenToWorldDelta,
12585
- worldToScreenDelta,
12586
- screenToWorldTransform,
12587
- drawTile,
12588
- drawRect,
12589
- drawRectGradient,
12590
- drawLineList,
12591
- drawLine,
12592
- drawPoly,
12593
- drawEllipse,
12594
- drawCircle,
12595
- drawCanvas2D,
12596
- drawText,
12597
- drawTextScreen,
12598
- setBlendMode,
12599
- combineCanvases,
12600
- engineFontImage,
12601
- FontImage,
12602
- isFullscreen,
12603
- toggleFullscreen,
12604
- setCursor,
12605
- getCameraSize,
12606
-
12607
- // WebGL
12608
- glCanvas,
12609
- glContext,
12610
- glClearCanvas,
12611
- glSetTexture,
12612
- glCompileShader,
12613
- glCreateProgram,
12614
- glCreateTexture,
12615
- glDeleteTexture,
12616
- glSetTextureData,
12617
- glFlush,
12618
- glCopyToContext,
12619
- glSetAntialias,
12620
- glDraw,
12621
- glDrawPointsTransform,
12622
- glDrawOutlineTransform,
12623
- glDrawPoints,
12624
- glDrawColoredPoints,
12625
- glAntialias,
12626
- glShader,
12627
- glPolyShader,
12628
- glPolyMode,
12629
- glAdditive,
12630
- glBatchAdditive,
12631
- glActiveTexture,
12632
- glArrayBuffer,
12633
- glGeometryBuffer,
12634
- glPositionData,
12635
- glColorData,
12636
- glBatchCount,
12637
-
12638
- // Input
12639
- keyIsDown,
12640
- keyWasPressed,
12641
- keyWasReleased,
12642
- keyDirection,
12643
- inputClear,
12644
- inputClearKey,
12645
- mouseIsDown,
12646
- mouseWasPressed,
12647
- mouseWasReleased,
12648
- mousePos,
12649
- mousePosScreen,
12650
- mouseDelta,
12651
- mouseDeltaScreen,
12652
- mouseWheel,
12653
- mouseInWindow,
12654
- isUsingGamepad,
12655
- inputPreventDefault,
12656
- gamepadPrimary,
12657
- setInputPreventDefault,
12658
- gamepadIsDown,
12659
- gamepadWasPressed,
12660
- gamepadWasReleased,
12661
- gamepadStick,
12662
- gamepadDpad,
12663
- gamepadConnected,
12664
- vibrate,
12665
- vibrateStop,
12666
- isTouchDevice,
12667
- pointerLockRequest,
12668
- pointerLockExit,
12669
- pointerLockIsActive,
12670
-
12671
- // Audio
12672
- audioContext,
12673
- audioMasterGain,
12674
- audioDefaultSampleRate,
12675
- Sound,
12676
- SoundWave,
12677
- SoundInstance,
12678
- speak,
12679
- speakStop,
12680
- getNoteFrequency,
12681
- playSamples,
12682
- zzfx,
12683
- zzfxG,
12684
-
12685
- // Base Object
12686
- EngineObject,
12687
-
12688
- // Tiles
12689
- tileCollisionLayers,
12690
- tileCollisionGetData,
12691
- tileCollisionTest,
12692
- tileCollisionRaycast,
12693
- tileLayersLoad,
12694
- TileLayerData,
12695
- CanvasLayer,
12696
- TileLayer,
12697
- TileCollisionLayer,
12698
-
12699
- // Particles
12700
- ParticleEmitter,
12701
- Particle,
12702
-
12703
- // Medals
12704
- medals,
12705
- medalsPreventUnlock,
12706
- medalsInit,
12707
- Medal,
12510
+ // Engine
12511
+ engineName,
12512
+ engineVersion,
12513
+ frameRate,
12514
+ timeDelta,
12515
+ engineObjects,
12516
+ frame,
12517
+ time,
12518
+ timeReal,
12519
+ paused,
12520
+ getPaused,
12521
+ setPaused,
12522
+ engineInit,
12523
+ engineObjectsUpdate,
12524
+ engineObjectsDestroy,
12525
+ engineObjectsCollect,
12526
+ engineObjectsCallback,
12527
+ engineObjectsRaycast,
12528
+ engineAddPlugin,
12529
+
12530
+ // Globals
12531
+ debug,
12532
+ debugOverlay,
12533
+ debugWatermark,
12534
+
12535
+ // Debug
12536
+ ASSERT,
12537
+ LOG,
12538
+ debugRect,
12539
+ debugPoly,
12540
+ debugCircle,
12541
+ debugPoint,
12542
+ debugLine,
12543
+ debugOverlap,
12544
+ debugText,
12545
+ debugClear,
12546
+ debugScreenshot,
12547
+ debugShowErrors,
12548
+ debugVideoCaptureStart,
12549
+ debugVideoCaptureStop,
12550
+ debugVideoCaptureIsActive,
12551
+
12552
+ // Settings
12553
+ cameraPos,
12554
+ cameraAngle,
12555
+ cameraScale,
12556
+ canvasColorTiles,
12557
+ canvasClearColor,
12558
+ canvasMaxSize,
12559
+ canvasMinAspect,
12560
+ canvasMaxAspect,
12561
+ canvasFixedSize,
12562
+ canvasPixelated,
12563
+ tilesPixelated,
12564
+ fontDefault,
12565
+ showSplashScreen,
12566
+ headlessMode,
12567
+ tileDefaultSize,
12568
+ tileDefaultPadding,
12569
+ tileDefaultBleed,
12570
+ enablePhysicsSolver,
12571
+ objectDefaultMass,
12572
+ objectDefaultDamping,
12573
+ objectDefaultAngleDamping,
12574
+ objectDefaultRestitution,
12575
+ objectDefaultFriction,
12576
+ objectMaxSpeed,
12577
+ gravity,
12578
+ particleEmitRateScale,
12579
+ glEnable,
12580
+ gamepadsEnable,
12581
+ gamepadDirectionEmulateStick,
12582
+ inputWASDEmulateDirection,
12583
+ touchGamepadEnable,
12584
+ touchGamepadCenterButton,
12585
+ touchGamepadAnalog,
12586
+ touchGamepadSize,
12587
+ touchGamepadAlpha,
12588
+ vibrateEnable,
12589
+ soundEnable,
12590
+ soundVolume,
12591
+ soundDefaultRange,
12592
+ soundDefaultTaper,
12593
+ medalDisplayTime,
12594
+ medalDisplaySlideTime,
12595
+ medalDisplaySize,
12596
+
12597
+ // Setters for globals
12598
+ setCameraPos,
12599
+ setCameraAngle,
12600
+ setCameraScale,
12601
+ setCanvasColorTiles,
12602
+ setCanvasClearColor,
12603
+ setCanvasMaxSize,
12604
+ setCanvasMinAspect,
12605
+ setCanvasMaxAspect,
12606
+ setCanvasFixedSize,
12607
+ setCanvasPixelated,
12608
+ setTilesPixelated,
12609
+ setFontDefault,
12610
+ setShowSplashScreen,
12611
+ setHeadlessMode,
12612
+ setGLEnable,
12613
+ setTileDefaultSize,
12614
+ setTileDefaultPadding,
12615
+ setTileDefaultBleed,
12616
+ setEnablePhysicsSolver,
12617
+ setObjectDefaultMass,
12618
+ setObjectDefaultDamping,
12619
+ setObjectDefaultAngleDamping,
12620
+ setObjectDefaultRestitution,
12621
+ setObjectDefaultFriction,
12622
+ setObjectMaxSpeed,
12623
+ setGravity,
12624
+ setParticleEmitRateScale,
12625
+ setTouchInputEnable,
12626
+ setGamepadsEnable,
12627
+ setGamepadDirectionEmulateStick,
12628
+ setInputWASDEmulateDirection,
12629
+ setTouchGamepadEnable,
12630
+ setTouchGamepadCenterButton,
12631
+ setTouchGamepadButtonCount,
12632
+ setTouchGamepadAnalog,
12633
+ setTouchGamepadSize,
12634
+ setTouchGamepadAlpha,
12635
+ setVibrateEnable,
12636
+ setSoundEnable,
12637
+ setSoundVolume,
12638
+ setSoundDefaultRange,
12639
+ setSoundDefaultTaper,
12640
+ setMedalDisplayTime,
12641
+ setMedalDisplaySlideTime,
12642
+ setMedalDisplaySize,
12643
+ setMedalsPreventUnlock,
12644
+ setDebugWatermark,
12645
+ setDebugKey,
12646
+
12647
+ // Math
12648
+ PI,
12649
+ abs,
12650
+ floor,
12651
+ ceil,
12652
+ round,
12653
+ min,
12654
+ max,
12655
+ sign,
12656
+ hypot,
12657
+ log2,
12658
+ sin,
12659
+ cos,
12660
+ tan,
12661
+ atan2,
12662
+ mod,
12663
+ clamp,
12664
+ percent,
12665
+ distanceWrap,
12666
+ lerpWrap,
12667
+ distanceAngle,
12668
+ lerpAngle,
12669
+ lerp,
12670
+ smoothStep,
12671
+ nearestPowerOfTwo,
12672
+ isOverlapping,
12673
+ isIntersecting,
12674
+ wave,
12675
+
12676
+ // Utilities
12677
+ formatTime,
12678
+ fetchJSON,
12679
+ saveText,
12680
+ saveCanvas,
12681
+ saveDataURL,
12682
+ shareURL,
12683
+
12684
+ // Random
12685
+ rand,
12686
+ randInt,
12687
+ randBool,
12688
+ randSign,
12689
+ randInCircle,
12690
+ randVec2,
12691
+ randColor,
12692
+
12693
+ // Utility Classes
12694
+ RandomGenerator,
12695
+ Vector2,
12696
+ Color,
12697
+ Timer,
12698
+ vec2,
12699
+ rgb,
12700
+ hsl,
12701
+ isColor,
12702
+ isVector2,
12703
+ isNumber,
12704
+ isString,
12705
+ isArray,
12706
+
12707
+ // Default Colors
12708
+ WHITE,
12709
+ CLEAR_WHITE,
12710
+ BLACK,
12711
+ CLEAR_BLACK,
12712
+ GRAY,
12713
+ RED,
12714
+ ORANGE,
12715
+ YELLOW,
12716
+ GREEN,
12717
+ CYAN,
12718
+ BLUE,
12719
+ PURPLE,
12720
+ MAGENTA,
12721
+
12722
+ // Draw
12723
+ tile,
12724
+ TileInfo,
12725
+ TextureInfo,
12726
+ mainCanvas,
12727
+ mainContext,
12728
+ drawContext,
12729
+ workCanvas,
12730
+ workContext,
12731
+ workReadCanvas,
12732
+ workReadContext,
12733
+ mainCanvasSize,
12734
+ textureInfos,
12735
+ drawCount,
12736
+ screenToWorld,
12737
+ worldToScreen,
12738
+ screenToWorldDelta,
12739
+ worldToScreenDelta,
12740
+ screenToWorldTransform,
12741
+ drawTile,
12742
+ drawRect,
12743
+ drawRectGradient,
12744
+ drawLineList,
12745
+ drawLine,
12746
+ drawPoly,
12747
+ drawEllipse,
12748
+ drawCircle,
12749
+ drawCanvas2D,
12750
+ drawText,
12751
+ drawTextScreen,
12752
+ setBlendMode,
12753
+ combineCanvases,
12754
+ engineFontImage,
12755
+ FontImage,
12756
+ isFullscreen,
12757
+ toggleFullscreen,
12758
+ setCursor,
12759
+ getCameraSize,
12760
+
12761
+ // WebGL
12762
+ glCanvas,
12763
+ glContext,
12764
+ glAntialias,
12765
+ glClearCanvas,
12766
+ glSetTexture,
12767
+ glCompileShader,
12768
+ glCreateProgram,
12769
+ glCreateTexture,
12770
+ glDeleteTexture,
12771
+ glSetTextureData,
12772
+ glFlush,
12773
+ glCopyToContext,
12774
+ glSetAntialias,
12775
+ glDraw,
12776
+ glDrawPointsTransform,
12777
+ glDrawOutlineTransform,
12778
+ glDrawPoints,
12779
+ glDrawColoredPoints,
12780
+ glSetRenderTarget,
12781
+ glClearRect,
12782
+
12783
+ // Input
12784
+ keyIsDown,
12785
+ keyWasPressed,
12786
+ keyWasReleased,
12787
+ keyDirection,
12788
+ inputClear,
12789
+ inputClearKey,
12790
+ mouseIsDown,
12791
+ mouseWasPressed,
12792
+ mouseWasReleased,
12793
+ mousePos,
12794
+ mousePosScreen,
12795
+ mouseDelta,
12796
+ mouseDeltaScreen,
12797
+ mouseWheel,
12798
+ mouseInWindow,
12799
+ isUsingGamepad,
12800
+ inputPreventDefault,
12801
+ gamepadPrimary,
12802
+ isTouchDevice,
12803
+ setInputPreventDefault,
12804
+ gamepadIsDown,
12805
+ gamepadWasPressed,
12806
+ gamepadWasReleased,
12807
+ gamepadStick,
12808
+ gamepadDpad,
12809
+ gamepadConnected,
12810
+ vibrate,
12811
+ vibrateStop,
12812
+ pointerLockRequest,
12813
+ pointerLockExit,
12814
+ pointerLockIsActive,
12815
+
12816
+ // Audio
12817
+ audioContext,
12818
+ audioMasterGain,
12819
+ audioDefaultSampleRate,
12820
+ Sound,
12821
+ SoundWave,
12822
+ SoundInstance,
12823
+ speak,
12824
+ speakStop,
12825
+ getNoteFrequency,
12826
+ playSamples,
12827
+ zzfx,
12828
+ zzfxG,
12829
+
12830
+ // Base Object
12831
+ EngineObject,
12832
+
12833
+ // Tiles
12834
+ tileCollisionLayers,
12835
+ tileCollisionGetData,
12836
+ tileCollisionTest,
12837
+ tileCollisionRaycast,
12838
+ tileLayersLoad,
12839
+ TileLayerData,
12840
+ CanvasLayer,
12841
+ TileLayer,
12842
+ TileCollisionLayer,
12843
+
12844
+ // Particles
12845
+ ParticleEmitter,
12846
+ Particle,
12847
+
12848
+ // Medals
12849
+ medals,
12850
+ medalsPreventUnlock,
12851
+ medalsInit,
12852
+ Medal,
12708
12853
  }
12709
12854
  /**
12710
12855
  * LittleJS Module Plugins Export