littlejsengine 1.16.1 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/littlejs.d.ts +287 -240
  2. package/dist/littlejs.esm.js +1419 -1259
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +1076 -910
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +977 -815
  7. package/examples/box2d/gameObjects.js +6 -6
  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/logo2.png +0 -0
  15. package/examples/module/tiles.png +0 -0
  16. package/examples/platformer/gameEffects.js +24 -23
  17. package/examples/platformer/gameLevel.js +24 -25
  18. package/examples/shorts/base.html +2 -1
  19. package/examples/shorts/clock.js +3 -3
  20. package/examples/shorts/fontImage.js +4 -3
  21. package/examples/shorts/parallax.js +1 -1
  22. package/examples/shorts/sequencer.js +1 -1
  23. package/examples/shorts/shapes.js +1 -1
  24. package/examples/shorts/texture.js +10 -6
  25. package/examples/shorts/tiles.png +0 -0
  26. package/examples/shorts/tiltedView.js +2 -0
  27. package/examples/starter/index.html +3 -2
  28. package/examples/starter/tiles.png +0 -0
  29. package/examples/typescript/tiles.png +0 -0
  30. package/examples/uiSystem/game.js +1 -1
  31. package/examples/uiSystem/tiles.png +0 -0
  32. package/package.json +1 -1
  33. package/plugins/postProcess.js +47 -28
  34. package/plugins/uiSystem.js +16 -16
  35. package/reference.md +6 -6
  36. package/src/engine.js +186 -198
  37. package/src/engineAudio.js +6 -10
  38. package/src/engineBuild.js +1 -0
  39. package/src/engineDebug.js +100 -98
  40. package/src/engineDraw.js +185 -148
  41. package/src/engineExport.js +327 -333
  42. package/src/engineFont.png +0 -0
  43. package/src/engineInput.js +17 -26
  44. package/src/engineMath.js +1112 -0
  45. package/src/engineMedals.js +4 -4
  46. package/src/engineObject.js +27 -27
  47. package/src/engineParticles.js +2 -2
  48. package/src/engineRelease.js +1 -3
  49. package/src/engineSettings.js +2 -2
  50. package/src/engineTileLayer.js +200 -176
  51. package/src/engineUtilities.js +48 -1097
  52. package/src/engineWebGL.js +127 -79
package/src/engineDraw.js CHANGED
@@ -31,11 +31,6 @@ let mainCanvas;
31
31
  * @memberof Draw */
32
32
  let mainContext;
33
33
 
34
- /** The default canvas to use for drawing, usually mainCanvas
35
- * @type {HTMLCanvasElement|OffscreenCanvas}
36
- * @memberof Draw */
37
- let drawCanvas;
38
-
39
34
  /** The default 2d context to use for drawing, usually mainContext
40
35
  * @type {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}
41
36
  * @memberof Draw */
@@ -82,10 +77,11 @@ let drawCount;
82
77
  * Create a tile info object using a grid based system
83
78
  * - This can take vecs or floats for easier use and conversion
84
79
  * - If an index is passed in, the tile size and index will determine the position
85
- * @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
80
+ * @param {Vector2|number} [index=0] - Index of the tile in 1d or 2d form
86
81
  * @param {Vector2|number} [size] - Size of tile in pixels
87
- * @param {number} [textureIndex] - Texture index to use
82
+ * @param {TextureInfo|number} [texture] - Texture index or info to use
88
83
  * @param {number} [padding] - How many pixels padding around tiles
84
+ * @param {number} [bleed] - How many pixels smaller to draw tiles
89
85
  * @return {TileInfo}
90
86
  * @example
91
87
  * tile(2) // a tile at index 2 using the default tile size of 16
@@ -93,36 +89,43 @@ let drawCount;
93
89
  * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
94
90
  * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
95
91
  * @memberof Draw */
96
- function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding)
92
+ function tile(index=new Vector2, size=tileDefaultSize, texture=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
97
93
  {
98
- if (headlessMode)
99
- return new TileInfo;
94
+ ASSERT(isVector2(index) || typeof index === 'number', 'index must be a vec2 or number');
95
+ ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
96
+ ASSERT(isNumber(texture) || texture instanceof TextureInfo, 'texture must be a number or TextureInfo');
97
+ ASSERT(isNumber(padding), 'padding must be a number');
98
+
99
+ if (headlessMode) return new TileInfo;
100
100
 
101
- // if size is a number, make it a vector
102
101
  if (typeof size === 'number')
103
102
  {
103
+ // if size is a number, make it a vector
104
104
  ASSERT(size > 0);
105
105
  size = new Vector2(size, size);
106
106
  }
107
107
 
108
108
  // create tile info object
109
- const tileInfo = new TileInfo(new Vector2, size, textureIndex, padding);
109
+ const textureInfo = typeof texture === 'number' ?
110
+ textureInfos[texture] : texture;
110
111
 
111
112
  // get the position of the tile
112
- const textureInfo = textureInfos[textureIndex];
113
- ASSERT(!!textureInfo, 'Texture not loaded');
114
113
  const sizePaddedX = size.x + padding*2;
115
114
  const sizePaddedY = size.y + padding*2;
116
- if (typeof pos === 'number')
115
+ let x, y;
116
+ if (typeof index === 'number')
117
117
  {
118
118
  const cols = textureInfo.size.x / sizePaddedX |0;
119
- ASSERT(cols > 0, 'Tile size is too big for texture');
120
- const posX = pos % cols, posY = (pos / cols) |0;
121
- tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
119
+ x = index % cols;
120
+ y = index / cols |0;
122
121
  }
123
122
  else
124
- tileInfo.pos.set(pos.x*sizePaddedX+padding, pos.y*sizePaddedY+padding);
125
- return tileInfo;
123
+ {
124
+ x = index.x;
125
+ y = index.y;
126
+ }
127
+ const pos = new Vector2(x*sizePaddedX + padding, y*sizePaddedY + padding);
128
+ return new TileInfo(pos, size, textureInfo, padding, bleed);
126
129
  }
127
130
 
128
131
  /**
@@ -132,24 +135,22 @@ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=til
132
135
  class TileInfo
133
136
  {
134
137
  /** Create a tile info object
135
- * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
138
+ * @param {Vector2} [pos=vec2()] - Top left corner of tile in pixels
136
139
  * @param {Vector2} [size] - Size of tile in pixels
137
- * @param {number} [textureIndex] - Texture index to use
138
- * @param {number} [padding] - How many pixels padding around tiles
139
- * @param {number} [bleed] - How many pixels smaller to draw tiles
140
+ * @param {TextureInfo} [textureInfo] - Texture info to use
141
+ * @param {number} [padding] - How many pixels padding around tiles
142
+ * @param {number} [bleed] - How many pixels smaller to draw tiles
140
143
  */
141
- constructor(pos=vec2(), size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
144
+ constructor(pos=vec2(), size=tileDefaultSize, textureInfo=textureInfos[0], padding=tileDefaultPadding, bleed=tileDefaultBleed)
142
145
  {
143
146
  /** @property {Vector2} - Top left corner of tile in pixels */
144
147
  this.pos = pos.copy();
145
148
  /** @property {Vector2} - Size of tile in pixels */
146
149
  this.size = size.copy();
147
- /** @property {number} - Texture index to use */
148
- this.textureIndex = textureIndex;
149
150
  /** @property {number} - How many pixels padding around tiles */
150
151
  this.padding = padding;
151
152
  /** @property {TextureInfo} - The texture info for this tile */
152
- this.textureInfo = textureInfos[this.textureIndex];
153
+ this.textureInfo = textureInfo;
153
154
  /** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
154
155
  this.bleed = bleed;
155
156
  }
@@ -159,7 +160,7 @@ class TileInfo
159
160
  * @return {TileInfo}
160
161
  */
161
162
  offset(offset)
162
- { return new TileInfo(this.pos.add(offset), this.size, this.textureIndex, this.padding, this.bleed); }
163
+ { return new TileInfo(this.pos.add(offset), this.size, this.textureInfo, this.padding, this.bleed); }
163
164
 
164
165
  /** Returns a copy of this tile offset by a number of animation frames
165
166
  * @param {number} frame - Offset to apply in animation frames
@@ -168,23 +169,33 @@ class TileInfo
168
169
  frame(frame)
169
170
  {
170
171
  ASSERT(typeof frame === 'number');
171
- return this.offset(new Vector2(frame*(this.size.x+this.padding*2), 0));
172
+ const w = this.size.x + this.padding*2;
173
+ const x = frame*w;
174
+ ASSERT(x < this.textureInfo.size.x, 'frame extends beyond texture width!');
175
+ return this.offset(new Vector2(x));
172
176
  }
173
177
 
174
178
  /**
175
179
  * Set this tile to use a full image in a texture info
176
- * @param {TextureInfo} textureInfo
180
+ * @param {TextureInfo} [textureInfo]
177
181
  * @return {TileInfo}
178
182
  */
179
- setFullImage(textureInfo)
183
+ setFullImage(textureInfo=this.textureInfo)
180
184
  {
185
+ this.textureInfo = textureInfo;
181
186
  this.pos = new Vector2;
182
187
  this.size = textureInfo.size.copy();
183
- this.textureInfo = textureInfo;
184
- // do not use padding or bleed
185
188
  this.bleed = this.padding = 0;
186
189
  return this;
187
190
  }
191
+
192
+ /**
193
+ * Returns a tile info for an index using this tile as refrence
194
+ * @param {Vector2|number} [index=0]
195
+ * @return {TileInfo}
196
+ */
197
+ tile(index)
198
+ { return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
188
199
  }
189
200
 
190
201
  /**
@@ -225,19 +236,19 @@ class TextureInfo
225
236
  ///////////////////////////////////////////////////////////////////////////////
226
237
  // Drawing functions
227
238
 
228
- /** Draw textured tile centered in world space, with color applied if using WebGL
229
- * @param {Vector2} pos - Center of the tile in world space
230
- * @param {Vector2} [size=(1,1)] - Size of the tile in world space
231
- * @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
232
- * @param {Color} [color=(1,1,1,1)] - Color to modulate with
233
- * @param {number} [angle] - Angle to rotate by
234
- * @param {boolean} [mirror] - Is image flipped along the Y axis?
235
- * @param {Color} [additiveColor] - Additive color to be applied if any
239
+ /** Draw textured tile centered in world space
240
+ * @param {Vector2} pos - Center of the tile in world space
241
+ * @param {Vector2} [size=vec2(1)] - Size of the tile in world space
242
+ * @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
243
+ * @param {Color} [color=WHITE] - Color to modulate with
244
+ * @param {number} [angle] - Angle to rotate by
245
+ * @param {boolean} [mirror] - Is image flipped along the Y axis?
246
+ * @param {Color} [additiveColor] - Additive color to be applied if any
236
247
  * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
237
248
  * @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
238
249
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
239
250
  * @memberof Draw */
240
- function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
251
+ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
241
252
  angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
242
253
  {
243
254
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -312,8 +323,8 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
312
323
 
313
324
  /** Draw colored rect centered on pos
314
325
  * @param {Vector2} pos
315
- * @param {Vector2} [size=(1,1)]
316
- * @param {Color} [color=(1,1,1,1)]
326
+ * @param {Vector2} [size=vec2(1)]
327
+ * @param {Color} [color=WHITE]
317
328
  * @param {number} [angle]
318
329
  * @param {boolean} [useWebGL=glEnable]
319
330
  * @param {boolean} [screenSpace]
@@ -326,9 +337,9 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
326
337
 
327
338
  /** Draw a rect centered on pos with a gradient from top to bottom
328
339
  * @param {Vector2} pos
329
- * @param {Vector2} [size=(1,1)]
330
- * @param {Color} [colorTop=(1,1,1,1)]
331
- * @param {Color} [colorBottom=(0,0,0,1)]
340
+ * @param {Vector2} [size=vec2(1)]
341
+ * @param {Color} [colorTop=WHITE]
342
+ * @param {Color} [colorBottom=BLACK]
332
343
  * @param {number} [angle]
333
344
  * @param {boolean} [useWebGL=glEnable]
334
345
  * @param {boolean} [screenSpace]
@@ -390,9 +401,9 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
390
401
  /** Draw connected lines between a series of points
391
402
  * @param {Array<Vector2>} points
392
403
  * @param {number} [width]
393
- * @param {Color} [color=(1,1,1,1)]
404
+ * @param {Color} [color=WHITE]
394
405
  * @param {boolean} [wrap] - Should the last point connect to the first?
395
- * @param {Vector2} [pos=(0,0)] - Offset to apply
406
+ * @param {Vector2} [pos=vec2()] - Offset to apply
396
407
  * @param {number} [angle] - Angle to rotate by
397
408
  * @param {boolean} [useWebGL=glEnable]
398
409
  * @param {boolean} [screenSpace]
@@ -427,13 +438,9 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
427
438
  for (let i=0; i<points.length; ++i)
428
439
  {
429
440
  const point = points[i];
430
- if (i)
431
- context.lineTo(point.x, point.y);
432
- else
433
- context.moveTo(point.x, point.y);
441
+ context.lineTo(point.x, point.y);
434
442
  }
435
- if (wrap)
436
- context.closePath();
443
+ wrap && context.closePath();
437
444
  context.stroke();
438
445
  }, screenSpace, context);
439
446
  }
@@ -443,8 +450,8 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
443
450
  * @param {Vector2} posA
444
451
  * @param {Vector2} posB
445
452
  * @param {number} [width]
446
- * @param {Color} [color=(1,1,1,1)]
447
- * @param {Vector2} [pos=(0,0)] - Offset to apply
453
+ * @param {Color} [color=WHITE]
454
+ * @param {Vector2} [pos=vec2()] - Offset to apply
448
455
  * @param {number} [angle] - Angle to rotate by
449
456
  * @param {boolean} [useWebGL=glEnable]
450
457
  * @param {boolean} [screenSpace]
@@ -463,12 +470,12 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
463
470
 
464
471
  /** Draw colored regular polygon using passed in number of sides
465
472
  * @param {Vector2} pos
466
- * @param {Vector2} [size=(1,1)]
473
+ * @param {Vector2} [size=vec2(1)]
467
474
  * @param {number} [sides]
468
- * @param {Color} [color=(1,1,1,1)]
475
+ * @param {Color} [color=WHITE]
469
476
  * @param {number} [angle]
470
477
  * @param {number} [lineWidth]
471
- * @param {Color} [lineColor=(0,0,0,1)]
478
+ * @param {Color} [lineColor=BLACK]
472
479
  * @param {boolean} [useWebGL=glEnable]
473
480
  * @param {boolean} [screenSpace]
474
481
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
@@ -491,10 +498,10 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
491
498
 
492
499
  /** Draw colored polygon using passed in points
493
500
  * @param {Array<Vector2>} points - Array of Vector2 points
494
- * @param {Color} [color=(1,1,1,1)]
501
+ * @param {Color} [color=WHITE]
495
502
  * @param {number} [lineWidth]
496
- * @param {Color} [lineColor=(0,0,0,1)]
497
- * @param {Vector2} [pos=(0,0)] - Offset to apply
503
+ * @param {Color} [lineColor=BLACK]
504
+ * @param {Vector2} [pos=vec2()] - Offset to apply
498
505
  * @param {number} [angle] - Angle to rotate by
499
506
  * @param {boolean} [useWebGL=glEnable]
500
507
  * @param {boolean} [screenSpace]
@@ -541,11 +548,11 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
541
548
 
542
549
  /** Draw colored ellipse using passed in point
543
550
  * @param {Vector2} pos
544
- * @param {Vector2} [size=(1,1)] - Width and height diameter
545
- * @param {Color} [color=(1,1,1,1)]
551
+ * @param {Vector2} [size=vec2(1)] - Width and height diameter
552
+ * @param {Color} [color=WHITE]
546
553
  * @param {number} [angle]
547
554
  * @param {number} [lineWidth]
548
- * @param {Color} [lineColor=(0,0,0,1)]
555
+ * @param {Color} [lineColor=BLACK]
549
556
  * @param {boolean} [useWebGL=glEnable]
550
557
  * @param {boolean} [screenSpace]
551
558
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
@@ -587,9 +594,9 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
587
594
  /** Draw colored circle using passed in point
588
595
  * @param {Vector2} pos
589
596
  * @param {number} [size=1] - Diameter
590
- * @param {Color} [color=(1,1,1,1)]
597
+ * @param {Color} [color=WHITE]
591
598
  * @param {number} [lineWidth=0]
592
- * @param {Color} [lineColor=(0,0,0,1)]
599
+ * @param {Color} [lineColor=BLACK]
593
600
  * @param {boolean} [useWebGL=glEnable]
594
601
  * @param {boolean} [screenSpace]
595
602
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
@@ -623,7 +630,11 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
623
630
  ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
624
631
 
625
632
  if (!screenSpace)
626
- [pos, size, angle] = worldToScreenTransform(pos, size, angle);
633
+ {
634
+ pos = worldToScreen(pos);
635
+ size = size.scale(cameraScale);
636
+ angle -= cameraAngle;
637
+ }
627
638
  context.save();
628
639
  context.translate(pos.x+.5, pos.y+.5);
629
640
  context.rotate(angle);
@@ -640,9 +651,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
640
651
  * @param {string|number} text
641
652
  * @param {Vector2} pos
642
653
  * @param {number} [size]
643
- * @param {Color} [color=(1,1,1,1)]
654
+ * @param {Color} [color=WHITE]
644
655
  * @param {number} [lineWidth]
645
- * @param {Color} [lineColor=(0,0,0,1)]
656
+ * @param {Color} [lineColor=BLACK]
646
657
  * @param {CanvasTextAlign} [textAlign='center']
647
658
  * @param {string} [font=fontDefault]
648
659
  * @param {string} [fontStyle]
@@ -666,18 +677,18 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
666
677
  * Automatically splits new lines into rows
667
678
  * @param {string|number} text
668
679
  * @param {Vector2} pos
669
- * @param {number} [size]
670
- * @param {Color} [color=(1,1,1,1)]
680
+ * @param {number} size
681
+ * @param {Color} [color=WHITE]
671
682
  * @param {number} [lineWidth]
672
- * @param {Color} [lineColor=(0,0,0,1)]
683
+ * @param {Color} [lineColor=BLACK]
673
684
  * @param {CanvasTextAlign} [textAlign]
674
685
  * @param {string} [font=fontDefault]
675
686
  * @param {string} [fontStyle]
676
687
  * @param {number} [maxWidth]
677
688
  * @param {number} [angle]
678
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
689
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
679
690
  * @memberof Draw */
680
- function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=mainContext)
691
+ function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
681
692
  {
682
693
  ASSERT(isString(text), 'text must be a string');
683
694
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -801,7 +812,7 @@ function worldToScreenDelta(worldDelta)
801
812
 
802
813
  /** Convert screen space transform to world space
803
814
  * @param {Vector2} screenPos
804
- * @param {Vector2} screenSize
815
+ * @param {Vector2} screenSize
805
816
  * @param {number} [screenAngle]
806
817
  * @return {[Vector2, Vector2, number]} - [pos, size, angle]
807
818
  * @memberof Draw */
@@ -818,25 +829,6 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
818
829
  ];
819
830
  }
820
831
 
821
- /** Convert world space transform to screen space
822
- * @param {Vector2} worldPos
823
- * @param {Vector2} worldSize
824
- * @param {number} [worldAngle]
825
- * @return {[Vector2, Vector2, number]} - [pos, size, angle]
826
- * @memberof Draw */
827
- function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
828
- {
829
- ASSERT(isVector2(worldPos), 'worldPos must be a vec2');
830
- ASSERT(isVector2(worldSize), 'worldSize must be a vec2');
831
- ASSERT(isNumber(worldAngle), 'worldAngle must be a number');
832
-
833
- return [
834
- worldToScreen(worldPos),
835
- worldSize.scale(cameraScale),
836
- worldAngle - cameraAngle
837
- ];
838
- }
839
-
840
832
  /** Get the size of the camera window in world space
841
833
  * @return {Vector2}
842
834
  * @memberof Draw */
@@ -881,10 +873,9 @@ function isOnScreen(pos, size=0)
881
873
  * @param {boolean} [additive]
882
874
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
883
875
  * @memberof Draw */
884
- function setBlendMode(additive=false, context)
876
+ function setBlendMode(additive=false, context=drawContext)
885
877
  {
886
878
  glAdditive = additive;
887
- context ||= drawContext;
888
879
  context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
889
880
  }
890
881
 
@@ -899,7 +890,6 @@ function combineCanvases()
899
890
  workContext.fillRect(0,0,w,h); // remove background alpha
900
891
  glCopyToContext(workContext);
901
892
  workContext.drawImage(mainCanvas, 0, 0);
902
- mainCanvas.width |= 0;
903
893
  mainContext.drawImage(workCanvas, 0, 0);
904
894
  }
905
895
 
@@ -1004,17 +994,22 @@ function setCursor(cursorStyle = 'auto')
1004
994
 
1005
995
  ///////////////////////////////////////////////////////////////////////////////
1006
996
 
997
+ /** Engine font image, 8x8 font provided by the engine
998
+ * @type {FontImage}
999
+ * @memberof Draw */
1007
1000
  let engineFontImage;
1008
1001
 
1009
1002
  /**
1010
- * Font Image Object - Draw text on a 2D canvas by using characters in an image
1003
+ * Font Image Object - Draw text by using tiles in an image
1011
1004
  * - 96 characters (from space to tilde) are stored in an image
1012
- * - Uses a default 8x8 font if none is supplied
1013
- * - You can also use fonts from the main tile sheet
1005
+ * - A 8x8 default engine font is supplied for general use
1006
+ * - This system is WebGL enabled for fast text rendering
1007
+ * - Fonts can also be colored and scaled along each axis
1008
+ *
1014
1009
  * @memberof Draw
1015
1010
  * @example
1016
1011
  * // use built in font
1017
- * const font = new FontImage;
1012
+ * const font = engineFontImage;
1018
1013
  *
1019
1014
  * // draw text
1020
1015
  * font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
@@ -1022,68 +1017,110 @@ let engineFontImage;
1022
1017
  class FontImage
1023
1018
  {
1024
1019
  /** Create an image font
1025
- * @param {HTMLImageElement} [image] - Image for the font, default if undefined
1026
- * @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
1027
- * @param {Vector2} [paddingSize=(0,1)] - How much space between characters
1020
+ * @param {TileInfo} tileInfo - Tile info of first characeter in font
1028
1021
  */
1029
- constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
1022
+ constructor(tileInfo)
1030
1023
  {
1031
- // load default font image
1032
- if (!image && !engineFontImage)
1033
- {
1034
- engineFontImage = new Image;
1035
- engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
1036
- }
1037
-
1038
- this.image = image || engineFontImage;
1039
- this.tileSize = tileSize;
1040
- this.paddingSize = paddingSize;
1024
+ ASSERT(!!tileInfo, 'tileInfo is required for FontImage');
1025
+
1026
+ /** @property {TileInfo} - Tile info for the font */
1027
+ this.tileInfo = tileInfo.frame(0);
1041
1028
  }
1042
1029
 
1043
1030
  /** Draw text in world space using the image font
1044
- * @param {string|number} text
1031
+ * @param {string|number} text
1045
1032
  * @param {Vector2} pos
1046
- * @param {number} [scale=.25]
1047
- * @param {boolean} [center]
1048
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
1033
+ * @param {Vector2|number} [size]
1034
+ * @param {boolean} [center=true]
1035
+ * @param {Color} [color=WHITE]
1036
+ * @param {boolean} [useWebGL=glEnable]
1037
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
1049
1038
  */
1050
- drawText(text, pos, scale=1, center, context=drawContext)
1039
+ drawText(text, pos, size=1, center, color, useWebGL, context)
1051
1040
  {
1052
- this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
1041
+ ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
1042
+
1043
+ if (typeof size === 'number')
1044
+ {
1045
+ // if size is a number, make it a vector
1046
+ ASSERT(size > 0);
1047
+ size *= cameraScale;
1048
+ size = new Vector2(size, size);
1049
+ }
1050
+ else
1051
+ size = size.scale(cameraScale);
1052
+ this.drawTextScreen(text, worldToScreen(pos), size, center, color, useWebGL, context);
1053
1053
  }
1054
1054
 
1055
1055
  /** Draw text in screen space using the image font
1056
- * @param {string|number} text
1056
+ * @param {string|number} text
1057
1057
  * @param {Vector2} pos
1058
- * @param {number} [scale]
1058
+ * @param {Vector2|number} size
1059
1059
  * @param {boolean} [center]
1060
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
1060
+ * @param {Color} [color=WHITE]
1061
+ * @param {boolean} [useWebGL=glEnable]
1062
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
1061
1063
  */
1062
- drawTextScreen(text, pos, scale=4, center=true, context=mainContext)
1064
+ drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
1063
1065
  {
1064
- context.save();
1065
- const size = this.tileSize;
1066
- const drawSize = size.add(this.paddingSize).scale(scale);
1067
- const cols = this.image.width / this.tileSize.x |0;
1068
- (text+'').split('\n').forEach((line, i)=>
1066
+ ASSERT(isString(text), 'text must be a string');
1067
+ ASSERT(isVector2(pos), 'pos must be a vec2');
1068
+ ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
1069
+ ASSERT(isColor(color), 'color must be a color');
1070
+
1071
+ // if size is a number, make it a vector
1072
+ size = typeof size === 'number' ? new Vector2(size, size) : size;
1073
+
1074
+ // precache objects for drawing
1075
+ const drawPos = new Vector2;
1076
+ const tileInfo = this.tileInfo;
1077
+ const padding = tileInfo.padding;
1078
+ const sizePaddedX = tileInfo.size.x + padding*2;
1079
+ const sizePaddedY = tileInfo.size.y + padding*2;
1080
+ const cols = tileInfo.textureInfo.size.x / sizePaddedX |0;
1081
+
1082
+ // draw each line of text
1083
+ (text+'').split('\n').forEach((line, j)=>
1069
1084
  {
1070
- const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
1071
- for (let j=line.length; j--;)
1085
+ const centerOffset = center ? (line.length-1) * size.x / 2 : 0;
1086
+ for (let i=line.length; i--;)
1072
1087
  {
1073
- // draw each character
1074
- let charCode = line[j].charCodeAt(0);
1075
- if (charCode < 32 || charCode > 127)
1076
- charCode = 127; // unknown character
1077
-
1078
- // get the character source location and draw it
1079
- const tile = charCode - 32;
1080
- const x = tile % cols;
1081
- const y = tile / cols |0;
1082
- const drawPos = pos.add(vec2(j,i).multiply(drawSize));
1083
- context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
1084
- drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
1088
+ // get the character index
1089
+ const charCode = line.charCodeAt(i);
1090
+ const index = charCode < 32 || charCode > 127 ?
1091
+ 95 : charCode - 32; // handle out of range characters
1092
+
1093
+ // get the position of the tile
1094
+ const x = index % cols;
1095
+ const y = index / cols |0;
1096
+ tileInfo.pos.x = x*sizePaddedX + padding;
1097
+ tileInfo.pos.y = y*sizePaddedY + padding;
1098
+
1099
+ // draw the tile
1100
+ drawPos.x = pos.x + i * size.x - centerOffset |0;
1101
+ drawPos.y = pos.y + j * size.y |0;
1102
+ drawTile(drawPos, size, tileInfo, color, 0, false, undefined, useWebGL, true, context);
1085
1103
  }
1086
1104
  });
1087
- context.restore();
1088
1105
  }
1106
+ }
1107
+
1108
+ // load engine font, called automatically on startup
1109
+ function fontImageInit()
1110
+ {
1111
+ return new Promise(resolve =>
1112
+ {
1113
+ // create the engine font
1114
+ const image = new Image;
1115
+ image.onerror = image.onload = ()=>
1116
+ {
1117
+ const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
1118
+ const textureInfo = new TextureInfo(image);
1119
+ const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
1120
+ engineFontImage = new FontImage(tileInfo);
1121
+ resolve();
1122
+ }
1123
+ image.crossOrigin = 'anonymous';
1124
+ 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==';
1125
+ });
1089
1126
  }