littlejsengine 1.15.9 → 1.16.2

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 (54) hide show
  1. package/dist/littlejs.d.ts +154 -158
  2. package/dist/littlejs.esm.js +573 -570
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +553 -553
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +392 -396
  7. package/examples/box2d/game.js +1 -1
  8. package/examples/box2d/gameObjects.js +5 -5
  9. package/examples/breakout/game.js +3 -3
  10. package/examples/electron/game.js +1 -2
  11. package/examples/electron/index.html +2 -2
  12. package/examples/htmlMenu/game.js +0 -1
  13. package/examples/index.html +1 -1
  14. package/examples/logo.png +0 -0
  15. package/examples/logo2.png +0 -0
  16. package/examples/module/game.js +1 -2
  17. package/examples/particles/index.html +1 -1
  18. package/examples/platformer/game.js +4 -4
  19. package/examples/platformer/gameEffects.js +1 -1
  20. package/examples/puzzle/game.js +2 -2
  21. package/examples/shorts/base.html +4 -3
  22. package/examples/shorts/box2dPool.js +2 -2
  23. package/examples/shorts/empty.js +1 -1
  24. package/examples/shorts/{systemFont.js → fontImage.js} +3 -3
  25. package/examples/shorts/fps.js +1 -1
  26. package/examples/shorts/helloWorld.js +2 -2
  27. package/examples/shorts/nineSlice.js +2 -2
  28. package/examples/shorts/slidingPuzzle.js +1 -1
  29. package/examples/starter/game.js +1 -2
  30. package/examples/starter/index.html +3 -2
  31. package/examples/stress/index.html +1 -1
  32. package/examples/typescript/game.js +1 -2
  33. package/examples/typescript/game.ts +1 -2
  34. package/package.json +1 -1
  35. package/plugins/box2d.js +8 -8
  36. package/plugins/drawUtilities.js +6 -6
  37. package/plugins/postProcess.js +13 -20
  38. package/plugins/uiSystem.js +16 -4
  39. package/reference.md +9 -11
  40. package/src/engine.js +40 -54
  41. package/src/engineAudio.js +5 -7
  42. package/src/engineBuild.js +1 -0
  43. package/src/engineDebug.js +163 -161
  44. package/src/engineDraw.js +108 -130
  45. package/src/engineExport.js +20 -17
  46. package/src/engineInput.js +4 -7
  47. package/src/engineMath.js +1201 -0
  48. package/src/engineMedals.js +7 -7
  49. package/src/engineObject.js +5 -4
  50. package/src/engineRelease.js +2 -4
  51. package/src/engineSettings.js +22 -32
  52. package/src/engineTileLayer.js +9 -10
  53. package/src/engineUtilities.js +34 -1187
  54. package/src/engineWebGL.js +13 -15
package/src/engineDraw.js CHANGED
@@ -10,7 +10,6 @@
10
10
  * There are 3 canvas/contexts available to draw to...
11
11
  * mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
12
12
  * glCanvas - Used by the accelerated WebGL batch rendering system.
13
- * overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
14
13
  *
15
14
  * The WebGL rendering system is very fast with some caveats...
16
15
  * - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
@@ -32,21 +31,6 @@ let mainCanvas;
32
31
  * @memberof Draw */
33
32
  let mainContext;
34
33
 
35
- /** A canvas that appears on top of everything the same size as mainCanvas
36
- * @type {HTMLCanvasElement}
37
- * @memberof Draw */
38
- let overlayCanvas;
39
-
40
- /** 2d context for overlayCanvas
41
- * @type {CanvasRenderingContext2D}
42
- * @memberof Draw */
43
- let overlayContext;
44
-
45
- /** The default canvas to use for drawing, usually mainCanvas
46
- * @type {HTMLCanvasElement|OffscreenCanvas}
47
- * @memberof Draw */
48
- let drawCanvas;
49
-
50
34
  /** The default 2d context to use for drawing, usually mainContext
51
35
  * @type {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}
52
36
  * @memberof Draw */
@@ -62,6 +46,16 @@ let workCanvas;
62
46
  * @memberof Draw */
63
47
  let workContext;
64
48
 
49
+ /** Offscreen canvas with willReadFrequently that can be used for image processing
50
+ * @type {OffscreenCanvas}
51
+ * @memberof Draw */
52
+ let workReadCanvas;
53
+
54
+ /** Offscreen canvas with willReadFrequently that can be used for image processing
55
+ * @type {OffscreenCanvasRenderingContext2D}
56
+ * @memberof Draw */
57
+ let workReadContext;
58
+
65
59
  /** The size of the main canvas (and other secondary canvases)
66
60
  * @type {Vector2}
67
61
  * @memberof Draw */
@@ -84,7 +78,7 @@ let drawCount;
84
78
  * - This can take vecs or floats for easier use and conversion
85
79
  * - If an index is passed in, the tile size and index will determine the position
86
80
  * @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
87
- * @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
81
+ * @param {Vector2|number} [size] - Size of tile in pixels
88
82
  * @param {number} [textureIndex] - Texture index to use
89
83
  * @param {number} [padding] - How many pixels padding around tiles
90
84
  * @return {TileInfo}
@@ -94,7 +88,7 @@ let drawCount;
94
88
  * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
95
89
  * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
96
90
  * @memberof Draw */
97
- function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
91
+ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding)
98
92
  {
99
93
  if (headlessMode)
100
94
  return new TileInfo;
@@ -133,13 +127,13 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
133
127
  class TileInfo
134
128
  {
135
129
  /** Create a tile info object
136
- * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
137
- * @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
138
- * @param {number} [textureIndex] - Texture index to use
139
- * @param {number} [padding] - How many pixels padding around tiles
140
- * @param {number} [bleedScale] - How many pixels smaller to draw tiles
130
+ * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
131
+ * @param {Vector2} [size] - Size of tile in pixels
132
+ * @param {number} [textureIndex] - Texture index to use
133
+ * @param {number} [padding] - How many pixels padding around tiles
134
+ * @param {number} [bleed] - How many pixels smaller to draw tiles
141
135
  */
142
- constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0, bleedScale=tileFixBleedScale)
136
+ constructor(pos=vec2(), size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
143
137
  {
144
138
  /** @property {Vector2} - Top left corner of tile in pixels */
145
139
  this.pos = pos.copy();
@@ -152,7 +146,7 @@ class TileInfo
152
146
  /** @property {TextureInfo} - The texture info for this tile */
153
147
  this.textureInfo = textureInfos[this.textureIndex];
154
148
  /** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
155
- this.bleedScale = bleedScale;
149
+ this.bleed = bleed;
156
150
  }
157
151
 
158
152
  /** Returns a copy of this tile offset by a vector
@@ -160,7 +154,7 @@ class TileInfo
160
154
  * @return {TileInfo}
161
155
  */
162
156
  offset(offset)
163
- { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleedScale); }
157
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleed); }
164
158
 
165
159
  /** Returns a copy of this tile offset by a number of animation frames
166
160
  * @param {number} frame - Offset to apply in animation frames
@@ -183,7 +177,7 @@ class TileInfo
183
177
  this.size = textureInfo.size.copy();
184
178
  this.textureInfo = textureInfo;
185
179
  // do not use padding or bleed
186
- this.bleedScale = this.padding = 0;
180
+ this.bleed = this.padding = 0;
187
181
  return this;
188
182
  }
189
183
  }
@@ -249,7 +243,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
249
243
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
250
244
 
251
245
  const textureInfo = tileInfo && tileInfo.textureInfo;
252
- const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
246
+ const bleed = tileInfo?.bleed ?? 0;
253
247
  if (useWebGL && glEnable)
254
248
  {
255
249
  ASSERT(!!glContext, 'WebGL is not enabled!');
@@ -264,13 +258,13 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
264
258
  const w = tileInfo.size.x * sizeInverse.x;
265
259
  const h = tileInfo.size.y * sizeInverse.y;
266
260
  glSetTexture(textureInfo.glTexture);
267
- if (bleedScale)
261
+ if (bleed)
268
262
  {
269
- const tileImageFixBleedX = sizeInverse.x*bleedScale;
270
- const tileImageFixBleedY = sizeInverse.y*bleedScale;
263
+ const bleedX = sizeInverse.x*bleed;
264
+ const bleedY = sizeInverse.y*bleed;
271
265
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
272
- x + tileImageFixBleedX, y + tileImageFixBleedY,
273
- x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
266
+ x + bleedX, y + bleedY,
267
+ x - bleedX + w, y - bleedY + h,
274
268
  color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
275
269
  }
276
270
  else
@@ -298,7 +292,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
298
292
  // calculate uvs and render
299
293
  const x = tileInfo.pos.x, y = tileInfo.pos.y;
300
294
  const w = tileInfo.size.x, h = tileInfo.size.y;
301
- drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor, bleedScale);
295
+ drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor, bleed);
302
296
  }
303
297
  else
304
298
  {
@@ -624,7 +618,11 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
624
618
  ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
625
619
 
626
620
  if (!screenSpace)
627
- [pos, size, angle] = worldToScreenTransform(pos, size, angle);
621
+ {
622
+ pos = worldToScreen(pos);
623
+ size = size.scale(cameraScale);
624
+ angle -= cameraAngle;
625
+ }
628
626
  context.save();
629
627
  context.translate(pos.x+.5, pos.y+.5);
630
628
  context.rotate(angle);
@@ -663,26 +661,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
663
661
  drawTextScreen(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
664
662
  }
665
663
 
666
- /** Draw text on overlay canvas in world space
667
- * Automatically splits new lines into rows
668
- * @param {string|number} text
669
- * @param {Vector2} pos
670
- * @param {number} [size]
671
- * @param {Color} [color=(1,1,1,1)]
672
- * @param {number} [lineWidth]
673
- * @param {Color} [lineColor=(0,0,0,1)]
674
- * @param {CanvasTextAlign} [textAlign='center']
675
- * @param {string} [font=fontDefault]
676
- * @param {string} [fontStyle]
677
- * @param {number} [maxWidth]
678
- * @param {number} [angle]
679
- * @memberof Draw */
680
- function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0)
681
- {
682
- drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, overlayContext);
683
- }
684
-
685
- /** Draw text on overlay canvas in screen space
664
+ /** Draw text in screen space
686
665
  * Automatically splits new lines into rows
687
666
  * @param {string|number} text
688
667
  * @param {Vector2} pos
@@ -695,9 +674,9 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
695
674
  * @param {string} [fontStyle]
696
675
  * @param {number} [maxWidth]
697
676
  * @param {number} [angle]
698
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
677
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
699
678
  * @memberof Draw */
700
- function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=overlayContext)
679
+ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
701
680
  {
702
681
  ASSERT(isString(text), 'text must be a string');
703
682
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -741,16 +720,16 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
741
720
  * @memberof Draw */
742
721
  function screenToWorld(screenPos)
743
722
  {
723
+ ASSERT(isVector2(screenPos), 'screenPos must be a vec2');
724
+
744
725
  let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
745
726
  let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
746
727
  if (cameraAngle)
747
728
  {
748
729
  // apply camera rotation
749
730
  const c = cos(-cameraAngle), s = sin(-cameraAngle);
750
- const rotatedX = x * c - y * s;
751
- const rotatedY = x * s + y * c;
752
- x = rotatedX;
753
- y = rotatedY;
731
+ const xr = x * c - y * s, yr = x * s + y * c;
732
+ x = xr; y = yr;
754
733
  }
755
734
  return new Vector2(x + cameraPos.x, y + cameraPos.y);
756
735
  }
@@ -761,16 +740,16 @@ function screenToWorld(screenPos)
761
740
  * @memberof Draw */
762
741
  function worldToScreen(worldPos)
763
742
  {
743
+ ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
744
+
764
745
  let x = worldPos.x - cameraPos.x;
765
746
  let y = worldPos.y - cameraPos.y;
766
747
  if (cameraAngle)
767
748
  {
768
749
  // apply inverse camera rotation
769
750
  const c = cos(cameraAngle), s = sin(cameraAngle);
770
- const rotatedX = x * c - y * s;
771
- const rotatedY = x * s + y * c;
772
- x = rotatedX;
773
- y = rotatedY;
751
+ const xr = x * c - y * s, yr = x * s + y * c;
752
+ x = xr; y = yr;
774
753
  }
775
754
  return new Vector2
776
755
  (
@@ -785,16 +764,16 @@ function worldToScreen(worldPos)
785
764
  * @memberof Draw */
786
765
  function screenToWorldDelta(screenDelta)
787
766
  {
767
+ ASSERT(isVector2(screenDelta), 'screenDelta must be a vec2');
768
+
788
769
  let x = screenDelta.x / cameraScale;
789
770
  let y = screenDelta.y / -cameraScale;
790
771
  if (cameraAngle)
791
772
  {
792
773
  // apply camera rotation
793
774
  const c = cos(-cameraAngle), s = sin(-cameraAngle);
794
- const rotatedX = x * c - y * s;
795
- const rotatedY = x * s + y * c;
796
- x = rotatedX;
797
- y = rotatedY;
775
+ const xr = x * c - y * s, yr = x * s + y * c;
776
+ x = xr; y = yr;
798
777
  }
799
778
  return new Vector2(x, y);
800
779
  }
@@ -805,16 +784,16 @@ function screenToWorldDelta(screenDelta)
805
784
  * @memberof Draw */
806
785
  function worldToScreenDelta(worldDelta)
807
786
  {
787
+ ASSERT(isVector2(worldDelta), 'worldDelta must be a vec2');
788
+
808
789
  let x = worldDelta.x;
809
790
  let y = worldDelta.y;
810
791
  if (cameraAngle)
811
792
  {
812
793
  // apply inverse camera rotation
813
794
  const c = cos(cameraAngle), s = sin(cameraAngle);
814
- const rotatedX = x * c - y * s;
815
- const rotatedY = x * s + y * c;
816
- x = rotatedX;
817
- y = rotatedY;
795
+ const xr = x * c - y * s, yr = x * s + y * c;
796
+ x = xr; y = yr;
818
797
  }
819
798
  return new Vector2(x * cameraScale, y * -cameraScale);
820
799
  }
@@ -827,6 +806,10 @@ function worldToScreenDelta(worldDelta)
827
806
  * @memberof Draw */
828
807
  function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
829
808
  {
809
+ ASSERT(isVector2(screenPos), 'screenPos must be a vec2');
810
+ ASSERT(isVector2(screenSize), 'screenSize must be a vec2');
811
+ ASSERT(isNumber(screenAngle), 'screenAngle must be a number');
812
+
830
813
  return [
831
814
  screenToWorld(screenPos),
832
815
  screenSize.scale(1/cameraScale),
@@ -834,28 +817,13 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
834
817
  ];
835
818
  }
836
819
 
837
- /** Convert world space transform to screen space
838
- * @param {Vector2} worldPos
839
- * @param {Vector2} worldSize
840
- * @param {number} [worldAngle]
841
- * @return {[Vector2, Vector2, number]} - [pos, size, angle]
842
- * @memberof Draw */
843
- function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
844
- {
845
- return [
846
- worldToScreen(worldPos),
847
- worldSize.scale(cameraScale),
848
- worldAngle - cameraAngle
849
- ];
850
- }
851
-
852
820
  /** Get the size of the camera window in world space
853
821
  * @return {Vector2}
854
822
  * @memberof Draw */
855
823
  function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
856
824
 
857
- /** Check if a point or circle is on screen
858
- * If size is a Vector2, uses the largest dimension as diameter
825
+ /** Check if a box, point, or circle is on screen with a circle test
826
+ * If size is a Vector2, uses the length as diameter
859
827
  * This can be used to cull offscreen objects from render or update
860
828
  * @param {Vector2} pos - world space position
861
829
  * @param {Vector2|number} size - world space size or diameter
@@ -863,12 +831,30 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
863
831
  * @memberof Draw */
864
832
  function isOnScreen(pos, size=0)
865
833
  {
866
- pos = worldToScreen(pos);
834
+ ASSERT(isVector2(pos), 'pos must be a vec2');
835
+ ASSERT(isVector2(size) || isNumber(size), 'size must be a vec2 or number');
836
+
837
+ // optimized circle on screen test
838
+ // pos = worldToScreen(pos);
839
+ let x = pos.x - cameraPos.x;
840
+ let y = pos.y - cameraPos.y;
841
+ if (cameraAngle)
842
+ {
843
+ // apply inverse camera rotation
844
+ const c = cos(cameraAngle), s = sin(cameraAngle);
845
+ const xr = x * c - y * s, yr = x * s + y * c;
846
+ x = xr; y = yr;
847
+ }
848
+ x *= cameraScale*2; y *= -cameraScale*2;
849
+
867
850
  if (size instanceof Vector2)
868
- size = max(size.x, size.y); // use largest dimension
869
- size *= cameraScale/2;
870
- return pos.x + size > 0 && pos.x - size < mainCanvasSize.x &&
871
- pos.y + size > 0 && pos.y - size < mainCanvasSize.y;
851
+ size = size.length(); // use length of vector as diameter
852
+ size *= cameraScale;
853
+
854
+ // check against screen bounds
855
+ const w = mainCanvasSize.x, h = mainCanvasSize.y;
856
+ return x + size > -w && x - size < w &&
857
+ y + size > -h && y - size < h;
872
858
  }
873
859
 
874
860
  /** Enable normal or additive blend mode
@@ -882,18 +868,19 @@ function setBlendMode(additive=false, context)
882
868
  context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
883
869
  }
884
870
 
885
- /** Combines all LittleJS canvases onto the main canvas and clears them
886
- * This is necessary for things like saving a screenshot
871
+ /** Combines LittleJS canvases onto the main canvas
872
+ * This is necessary for things like screenshots and video
887
873
  * @memberof Draw */
888
874
  function combineCanvases()
889
875
  {
890
- // combine canvases
891
- glCopyToContext(mainContext);
892
- mainContext.drawImage(overlayCanvas, 0, 0);
893
-
894
- // clear canvases
895
- glClearCanvas();
896
- overlayCanvas.width |= 0;
876
+ const w = mainCanvasSize.x, h = mainCanvasSize.y;
877
+ workCanvas.width = w;
878
+ workCanvas.height = h;
879
+ workContext.fillRect(0,0,w,h); // remove background alpha
880
+ glCopyToContext(workContext);
881
+ workContext.drawImage(mainCanvas, 0, 0);
882
+ mainCanvas.width |= 0;
883
+ mainContext.drawImage(workCanvas, 0, 0);
897
884
  }
898
885
 
899
886
  /** Helper function to draw an image with color and additive color applied
@@ -910,18 +897,18 @@ function combineCanvases()
910
897
  * @param {number} dHeight
911
898
  * @param {Color} color
912
899
  * @param {Color} [additiveColor]
913
- * @param {number} [bleedScale] - How much to shrink the source, used to fix bleeding
900
+ * @param {number} [bleed] - How many pixels to shrink the source, used to fix bleeding
914
901
  * @memberof Draw */
915
- function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleedScale=0)
902
+ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor, bleed=0)
916
903
  {
917
904
  function isWhite(c) { return c.r >= 1 && c.g >= 1 && c.b >= 1; }
918
905
  function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
919
- const sx2 = bleedScale;
920
- const sy2 = bleedScale;
906
+ const sx2 = bleed;
907
+ const sy2 = bleed;
921
908
  sWidth = max(1,sWidth|0);
922
909
  sHeight = max(1,sHeight|0);
923
- const sWidth2 = sWidth - 2*bleedScale;
924
- const sHeight2 = sHeight - 2*bleedScale;
910
+ const sWidth2 = sWidth - 2*bleed;
911
+ const sHeight2 = sHeight - 2*bleed;
925
912
  if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
926
913
  {
927
914
  // white texture with no additive alpha, no need to tint
@@ -932,12 +919,12 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
932
919
  else
933
920
  {
934
921
  // copy to offscreen canvas
935
- workCanvas.width = sWidth;
936
- workCanvas.height = sHeight;
937
- workContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
922
+ workReadCanvas.width = sWidth;
923
+ workReadCanvas.height = sHeight;
924
+ workReadContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
938
925
 
939
926
  // tint image using offscreen work context
940
- const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
927
+ const imageData = workReadContext.getImageData(0, 0, sWidth, sHeight);
941
928
  const data = imageData.data;
942
929
  if (additiveColor && !isBlack(additiveColor))
943
930
  {
@@ -946,8 +933,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
946
933
  const colorAdd = [additiveColor.r * 255, additiveColor.g * 255, additiveColor.b * 255, additiveColor.a * 255];
947
934
  for (let i = 0; i < data.length; ++i)
948
935
  data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
949
- workContext.putImageData(imageData, 0, 0);
950
- context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
936
+ workReadContext.putImageData(imageData, 0, 0);
937
+ context.drawImage(workReadCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
951
938
  }
952
939
  else
953
940
  {
@@ -958,9 +945,9 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
958
945
  data[i+1] *= color.g;
959
946
  data[i+2] *= color.b;
960
947
  }
961
- workContext.putImageData(imageData, 0, 0);
948
+ workReadContext.putImageData(imageData, 0, 0);
962
949
  context.globalAlpha = color.a;
963
- context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
950
+ context.drawImage(workReadCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
964
951
  context.globalAlpha = 1;
965
952
  }
966
953
  }
@@ -1022,7 +1009,7 @@ class FontImage
1022
1009
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
1023
1010
  {
1024
1011
  // load default font image
1025
- if (!engineFontImage)
1012
+ if (!image && !engineFontImage)
1026
1013
  {
1027
1014
  engineFontImage = new Image;
1028
1015
  engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
@@ -1045,23 +1032,14 @@ class FontImage
1045
1032
  this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
1046
1033
  }
1047
1034
 
1048
- /** Draw text on overlay canvas in world space using the image font
1049
- * @param {string|number} text
1050
- * @param {Vector2} pos
1051
- * @param {number} [scale]
1052
- * @param {boolean} [center]
1053
- */
1054
- drawTextOverlay(text, pos, scale=4, center)
1055
- { this.drawText(text, pos, scale, center, overlayContext); }
1056
-
1057
- /** Draw text on overlay canvas in screen space using the image font
1035
+ /** Draw text in screen space using the image font
1058
1036
  * @param {string|number} text
1059
1037
  * @param {Vector2} pos
1060
1038
  * @param {number} [scale]
1061
1039
  * @param {boolean} [center]
1062
1040
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
1063
1041
  */
1064
- drawTextScreen(text, pos, scale=4, center=true, context=overlayContext)
1042
+ drawTextScreen(text, pos, scale=4, center=true, context=drawContext)
1065
1043
  {
1066
1044
  context.save();
1067
1045
  const size = this.tileSize;
@@ -27,7 +27,7 @@ export
27
27
  // Globals
28
28
  debug,
29
29
  debugOverlay,
30
- showWatermark,
30
+ debugWatermark,
31
31
 
32
32
  // Debug
33
33
  ASSERT,
@@ -41,13 +41,10 @@ export
41
41
  debugText,
42
42
  debugClear,
43
43
  debugScreenshot,
44
- debugSaveCanvas,
45
- debugSaveText,
46
- debugSaveDataURL,
47
44
  debugShowErrors,
48
- debugVideoCaptureIsActive,
49
45
  debugVideoCaptureStart,
50
46
  debugVideoCaptureStop,
47
+ debugVideoCaptureIsActive,
51
48
 
52
49
  // Settings
53
50
  cameraPos,
@@ -60,13 +57,13 @@ export
60
57
  canvasMaxAspect,
61
58
  canvasFixedSize,
62
59
  canvasPixelated,
63
- overlayCanvasPixelated,
64
60
  tilesPixelated,
65
61
  fontDefault,
66
62
  showSplashScreen,
67
63
  headlessMode,
68
- tileSizeDefault,
69
- tileFixBleedScale,
64
+ tileDefaultSize,
65
+ tileDefaultPadding,
66
+ tileDefaultBleed,
70
67
  enablePhysicsSolver,
71
68
  objectDefaultMass,
72
69
  objectDefaultDamping,
@@ -105,14 +102,14 @@ export
105
102
  setCanvasMaxAspect,
106
103
  setCanvasFixedSize,
107
104
  setCanvasPixelated,
108
- setOverlayCanvasPixelated,
109
105
  setTilesPixelated,
110
106
  setFontDefault,
111
107
  setShowSplashScreen,
112
108
  setHeadlessMode,
113
109
  setGLEnable,
114
- setTileSizeDefault,
115
- setTileFixBleedScale,
110
+ setTileDefaultSize,
111
+ setTileDefaultPadding,
112
+ setTileDefaultBleed,
116
113
  setEnablePhysicsSolver,
117
114
  setObjectDefaultMass,
118
115
  setObjectDefaultDamping,
@@ -141,10 +138,10 @@ export
141
138
  setMedalDisplaySlideTime,
142
139
  setMedalDisplaySize,
143
140
  setMedalsPreventUnlock,
144
- setShowWatermark,
141
+ setDebugWatermark,
145
142
  setDebugKey,
146
143
 
147
- // Utilities
144
+ // Math
148
145
  PI,
149
146
  abs,
150
147
  floor,
@@ -172,8 +169,13 @@ export
172
169
  isOverlapping,
173
170
  isIntersecting,
174
171
  wave,
172
+
173
+ // Utilities
175
174
  formatTime,
176
175
  fetchJSON,
176
+ saveText,
177
+ saveCanvas,
178
+ saveDataURL,
177
179
 
178
180
  // Random
179
181
  rand,
@@ -219,10 +221,11 @@ export
219
221
  TextureInfo,
220
222
  mainCanvas,
221
223
  mainContext,
222
- drawCanvas,
223
224
  drawContext,
224
- overlayCanvas,
225
- overlayContext,
225
+ workCanvas,
226
+ workContext,
227
+ workReadCanvas,
228
+ workReadContext,
226
229
  mainCanvasSize,
227
230
  textureInfos,
228
231
  drawCount,
@@ -230,6 +233,7 @@ export
230
233
  worldToScreen,
231
234
  screenToWorldDelta,
232
235
  worldToScreenDelta,
236
+ screenToWorldTransform,
233
237
  drawTile,
234
238
  drawRect,
235
239
  drawRectGradient,
@@ -240,7 +244,6 @@ export
240
244
  drawCircle,
241
245
  drawCanvas2D,
242
246
  drawText,
243
- drawTextOverlay,
244
247
  drawTextScreen,
245
248
  setBlendMode,
246
249
  combineCanvases,
@@ -365,8 +365,7 @@ function inputInit()
365
365
  }
366
366
  function onMouseDown(e)
367
367
  {
368
- if (isTouchDevice && touchInputEnable)
369
- return;
368
+ if (isTouchDevice && touchInputEnable) return;
370
369
 
371
370
  // fix stalled audio requiring user interaction
372
371
  if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
@@ -417,8 +416,7 @@ function inputInit()
417
416
  let wasTouching;
418
417
  function handleTouch(e)
419
418
  {
420
- if (!touchInputEnable)
421
- return;
419
+ if (!touchInputEnable) return;
422
420
 
423
421
  // route touch to gamepad
424
422
  if (touchGamepadEnable)
@@ -482,8 +480,7 @@ function inputInit()
482
480
  }
483
481
 
484
482
  // don't process touch gamepad if paused
485
- if (paused)
486
- return;
483
+ if (paused) return;
487
484
 
488
485
  // get center of left and right sides
489
486
  const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
@@ -722,7 +719,7 @@ function inputRender()
722
719
  return;
723
720
 
724
721
  // setup the canvas
725
- const context = overlayContext;
722
+ const context = mainContext;
726
723
  context.save();
727
724
  context.globalAlpha = alpha*touchGamepadAlpha;
728
725
  context.strokeStyle = '#fff';