littlejsengine 1.16.2 → 1.17.5

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 (57) hide show
  1. package/dist/littlejs.d.ts +291 -246
  2. package/dist/littlejs.esm.js +1520 -1271
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +1178 -920
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +1170 -912
  7. package/examples/box2d/gameObjects.js +4 -4
  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 +9 -8
  13. package/examples/logo.png +0 -0
  14. package/examples/module/build.bat +7 -0
  15. package/examples/module/build.js +121 -0
  16. package/examples/module/tiles.png +0 -0
  17. package/examples/platformer/game.js +1 -1
  18. package/examples/platformer/gameEffects.js +25 -29
  19. package/examples/platformer/gameLevel.js +27 -28
  20. package/examples/puzzle/game.js +2 -2
  21. package/examples/shorts/base.html +4 -1
  22. package/examples/shorts/box2dPool.js +2 -2
  23. package/examples/shorts/box2dTileLayer.js +48 -0
  24. package/examples/shorts/clock.js +3 -3
  25. package/examples/shorts/fontImage.js +4 -3
  26. package/examples/shorts/music.js +2 -2
  27. package/examples/shorts/musicPlayer.js +2 -2
  28. package/examples/shorts/parallax.js +1 -1
  29. package/examples/shorts/sequencer.js +1 -1
  30. package/examples/shorts/shapes.js +1 -1
  31. package/examples/shorts/texture.js +10 -6
  32. package/examples/shorts/tiles.png +0 -0
  33. package/examples/shorts/tiltedView.js +2 -0
  34. package/examples/starter/index.html +2 -2
  35. package/examples/starter/tiles.png +0 -0
  36. package/examples/typescript/tiles.png +0 -0
  37. package/examples/uiSystem/game.js +1 -1
  38. package/examples/uiSystem/tiles.png +0 -0
  39. package/package.json +5 -5
  40. package/plugins/box2d.js +167 -63
  41. package/plugins/postProcess.js +47 -28
  42. package/plugins/uiSystem.js +18 -18
  43. package/plugins/zzfxm.js +2 -2
  44. package/reference.md +4 -3
  45. package/src/engine.js +182 -181
  46. package/src/engineAudio.js +47 -63
  47. package/src/engineDebug.js +8 -8
  48. package/src/engineDraw.js +184 -124
  49. package/src/engineExport.js +325 -334
  50. package/src/engineFont.png +0 -0
  51. package/src/engineInput.js +18 -24
  52. package/src/engineMath.js +24 -113
  53. package/src/engineObject.js +27 -26
  54. package/src/engineParticles.js +2 -2
  55. package/src/engineTileLayer.js +229 -192
  56. package/src/engineUtilities.js +100 -4
  57. package/src/engineWebGL.js +124 -73
package/src/engineDraw.js CHANGED
@@ -77,10 +77,11 @@ let drawCount;
77
77
  * Create a tile info object using a grid based system
78
78
  * - This can take vecs or floats for easier use and conversion
79
79
  * - If an index is passed in, the tile size and index will determine the position
80
- * @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
81
81
  * @param {Vector2|number} [size] - Size of tile in pixels
82
- * @param {number} [textureIndex] - Texture index to use
82
+ * @param {TextureInfo|number} [texture] - Texture index or info to use
83
83
  * @param {number} [padding] - How many pixels padding around tiles
84
+ * @param {number} [bleed] - How many pixels smaller to draw tiles
84
85
  * @return {TileInfo}
85
86
  * @example
86
87
  * tile(2) // a tile at index 2 using the default tile size of 16
@@ -88,36 +89,43 @@ let drawCount;
88
89
  * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
89
90
  * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
90
91
  * @memberof Draw */
91
- 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)
92
93
  {
93
- if (headlessMode)
94
- 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;
95
100
 
96
- // if size is a number, make it a vector
97
101
  if (typeof size === 'number')
98
102
  {
103
+ // if size is a number, make it a vector
99
104
  ASSERT(size > 0);
100
105
  size = new Vector2(size, size);
101
106
  }
102
107
 
103
108
  // create tile info object
104
- const tileInfo = new TileInfo(new Vector2, size, textureIndex, padding);
109
+ const textureInfo = typeof texture === 'number' ?
110
+ textureInfos[texture] : texture;
105
111
 
106
112
  // get the position of the tile
107
- const textureInfo = textureInfos[textureIndex];
108
- ASSERT(!!textureInfo, 'Texture not loaded');
109
113
  const sizePaddedX = size.x + padding*2;
110
114
  const sizePaddedY = size.y + padding*2;
111
- if (typeof pos === 'number')
115
+ let x, y;
116
+ if (typeof index === 'number')
112
117
  {
113
118
  const cols = textureInfo.size.x / sizePaddedX |0;
114
- ASSERT(cols > 0, 'Tile size is too big for texture');
115
- const posX = pos % cols, posY = (pos / cols) |0;
116
- tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
119
+ x = index % cols;
120
+ y = index / cols |0;
117
121
  }
118
122
  else
119
- tileInfo.pos.set(pos.x*sizePaddedX+padding, pos.y*sizePaddedY+padding);
120
- 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);
121
129
  }
122
130
 
123
131
  /**
@@ -127,24 +135,22 @@ function tile(pos=new Vector2, size=tileDefaultSize, textureIndex=0, padding=til
127
135
  class TileInfo
128
136
  {
129
137
  /** Create a tile info object
130
- * @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
138
+ * @param {Vector2} [pos=vec2()] - Top left corner of tile in pixels
131
139
  * @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
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
135
143
  */
136
- constructor(pos=vec2(), size=tileDefaultSize, textureIndex=0, padding=tileDefaultPadding, bleed=tileDefaultBleed)
144
+ constructor(pos=vec2(), size=tileDefaultSize, textureInfo=textureInfos[0], padding=tileDefaultPadding, bleed=tileDefaultBleed)
137
145
  {
138
146
  /** @property {Vector2} - Top left corner of tile in pixels */
139
147
  this.pos = pos.copy();
140
148
  /** @property {Vector2} - Size of tile in pixels */
141
149
  this.size = size.copy();
142
- /** @property {number} - Texture index to use */
143
- this.textureIndex = textureIndex;
144
150
  /** @property {number} - How many pixels padding around tiles */
145
151
  this.padding = padding;
146
152
  /** @property {TextureInfo} - The texture info for this tile */
147
- this.textureInfo = textureInfos[this.textureIndex];
153
+ this.textureInfo = textureInfo;
148
154
  /** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
149
155
  this.bleed = bleed;
150
156
  }
@@ -154,7 +160,7 @@ class TileInfo
154
160
  * @return {TileInfo}
155
161
  */
156
162
  offset(offset)
157
- { 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); }
158
164
 
159
165
  /** Returns a copy of this tile offset by a number of animation frames
160
166
  * @param {number} frame - Offset to apply in animation frames
@@ -163,23 +169,33 @@ class TileInfo
163
169
  frame(frame)
164
170
  {
165
171
  ASSERT(typeof frame === 'number');
166
- 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));
167
176
  }
168
177
 
169
178
  /**
170
179
  * Set this tile to use a full image in a texture info
171
- * @param {TextureInfo} textureInfo
180
+ * @param {TextureInfo} [textureInfo]
172
181
  * @return {TileInfo}
173
182
  */
174
- setFullImage(textureInfo)
183
+ setFullImage(textureInfo=this.textureInfo)
175
184
  {
185
+ this.textureInfo = textureInfo;
176
186
  this.pos = new Vector2;
177
187
  this.size = textureInfo.size.copy();
178
- this.textureInfo = textureInfo;
179
- // do not use padding or bleed
180
188
  this.bleed = this.padding = 0;
181
189
  return this;
182
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); }
183
199
  }
184
200
 
185
201
  /**
@@ -220,19 +236,19 @@ class TextureInfo
220
236
  ///////////////////////////////////////////////////////////////////////////////
221
237
  // Drawing functions
222
238
 
223
- /** Draw textured tile centered in world space, with color applied if using WebGL
224
- * @param {Vector2} pos - Center of the tile in world space
225
- * @param {Vector2} [size=(1,1)] - Size of the tile in world space
226
- * @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
227
- * @param {Color} [color=(1,1,1,1)] - Color to modulate with
228
- * @param {number} [angle] - Angle to rotate by
229
- * @param {boolean} [mirror] - Is image flipped along the Y axis?
230
- * @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
231
247
  * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
232
248
  * @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
233
249
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
234
250
  * @memberof Draw */
235
- function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
251
+ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
236
252
  angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
237
253
  {
238
254
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -242,7 +258,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
242
258
  ASSERT(!additiveColor || isColor(additiveColor), 'additiveColor must be a color');
243
259
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
244
260
 
245
- const textureInfo = tileInfo && tileInfo.textureInfo;
261
+ const textureInfo = tileInfo?.textureInfo;
246
262
  const bleed = tileInfo?.bleed ?? 0;
247
263
  if (useWebGL && glEnable)
248
264
  {
@@ -307,8 +323,8 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
307
323
 
308
324
  /** Draw colored rect centered on pos
309
325
  * @param {Vector2} pos
310
- * @param {Vector2} [size=(1,1)]
311
- * @param {Color} [color=(1,1,1,1)]
326
+ * @param {Vector2} [size=vec2(1)]
327
+ * @param {Color} [color=WHITE]
312
328
  * @param {number} [angle]
313
329
  * @param {boolean} [useWebGL=glEnable]
314
330
  * @param {boolean} [screenSpace]
@@ -321,9 +337,9 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
321
337
 
322
338
  /** Draw a rect centered on pos with a gradient from top to bottom
323
339
  * @param {Vector2} pos
324
- * @param {Vector2} [size=(1,1)]
325
- * @param {Color} [colorTop=(1,1,1,1)]
326
- * @param {Color} [colorBottom=(0,0,0,1)]
340
+ * @param {Vector2} [size=vec2(1)]
341
+ * @param {Color} [colorTop=WHITE]
342
+ * @param {Color} [colorBottom=BLACK]
327
343
  * @param {number} [angle]
328
344
  * @param {boolean} [useWebGL=glEnable]
329
345
  * @param {boolean} [screenSpace]
@@ -385,9 +401,9 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
385
401
  /** Draw connected lines between a series of points
386
402
  * @param {Array<Vector2>} points
387
403
  * @param {number} [width]
388
- * @param {Color} [color=(1,1,1,1)]
404
+ * @param {Color} [color=WHITE]
389
405
  * @param {boolean} [wrap] - Should the last point connect to the first?
390
- * @param {Vector2} [pos=(0,0)] - Offset to apply
406
+ * @param {Vector2} [pos=vec2()] - Offset to apply
391
407
  * @param {number} [angle] - Angle to rotate by
392
408
  * @param {boolean} [useWebGL=glEnable]
393
409
  * @param {boolean} [screenSpace]
@@ -422,13 +438,9 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
422
438
  for (let i=0; i<points.length; ++i)
423
439
  {
424
440
  const point = points[i];
425
- if (i)
426
- context.lineTo(point.x, point.y);
427
- else
428
- context.moveTo(point.x, point.y);
441
+ context.lineTo(point.x, point.y);
429
442
  }
430
- if (wrap)
431
- context.closePath();
443
+ wrap && context.closePath();
432
444
  context.stroke();
433
445
  }, screenSpace, context);
434
446
  }
@@ -438,8 +450,8 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
438
450
  * @param {Vector2} posA
439
451
  * @param {Vector2} posB
440
452
  * @param {number} [width]
441
- * @param {Color} [color=(1,1,1,1)]
442
- * @param {Vector2} [pos=(0,0)] - Offset to apply
453
+ * @param {Color} [color=WHITE]
454
+ * @param {Vector2} [pos=vec2()] - Offset to apply
443
455
  * @param {number} [angle] - Angle to rotate by
444
456
  * @param {boolean} [useWebGL=glEnable]
445
457
  * @param {boolean} [screenSpace]
@@ -458,12 +470,12 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
458
470
 
459
471
  /** Draw colored regular polygon using passed in number of sides
460
472
  * @param {Vector2} pos
461
- * @param {Vector2} [size=(1,1)]
473
+ * @param {Vector2} [size=vec2(1)]
462
474
  * @param {number} [sides]
463
- * @param {Color} [color=(1,1,1,1)]
475
+ * @param {Color} [color=WHITE]
464
476
  * @param {number} [angle]
465
477
  * @param {number} [lineWidth]
466
- * @param {Color} [lineColor=(0,0,0,1)]
478
+ * @param {Color} [lineColor=BLACK]
467
479
  * @param {boolean} [useWebGL=glEnable]
468
480
  * @param {boolean} [screenSpace]
469
481
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
@@ -486,10 +498,10 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
486
498
 
487
499
  /** Draw colored polygon using passed in points
488
500
  * @param {Array<Vector2>} points - Array of Vector2 points
489
- * @param {Color} [color=(1,1,1,1)]
501
+ * @param {Color} [color=WHITE]
490
502
  * @param {number} [lineWidth]
491
- * @param {Color} [lineColor=(0,0,0,1)]
492
- * @param {Vector2} [pos=(0,0)] - Offset to apply
503
+ * @param {Color} [lineColor=BLACK]
504
+ * @param {Vector2} [pos=vec2()] - Offset to apply
493
505
  * @param {number} [angle] - Angle to rotate by
494
506
  * @param {boolean} [useWebGL=glEnable]
495
507
  * @param {boolean} [screenSpace]
@@ -536,11 +548,11 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
536
548
 
537
549
  /** Draw colored ellipse using passed in point
538
550
  * @param {Vector2} pos
539
- * @param {Vector2} [size=(1,1)] - Width and height diameter
540
- * @param {Color} [color=(1,1,1,1)]
551
+ * @param {Vector2} [size=vec2(1)] - Width and height diameter
552
+ * @param {Color} [color=WHITE]
541
553
  * @param {number} [angle]
542
554
  * @param {number} [lineWidth]
543
- * @param {Color} [lineColor=(0,0,0,1)]
555
+ * @param {Color} [lineColor=BLACK]
544
556
  * @param {boolean} [useWebGL=glEnable]
545
557
  * @param {boolean} [screenSpace]
546
558
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
@@ -552,9 +564,12 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
552
564
  ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
553
565
  ASSERT(isNumber(angle), 'angle must be a number');
554
566
  ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
555
- ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'invalid lineWidth');
567
+ ASSERT(lineWidth >= 0, 'lineWidth must be a positive value or 0');
556
568
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
557
569
 
570
+ // clamp line width to prevent artifacts
571
+ lineWidth = clamp(lineWidth, 0, Math.min(size.x, size.y));
572
+
558
573
  if (useWebGL && glEnable)
559
574
  {
560
575
  // draw as a regular polygon
@@ -582,9 +597,9 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
582
597
  /** Draw colored circle using passed in point
583
598
  * @param {Vector2} pos
584
599
  * @param {number} [size=1] - Diameter
585
- * @param {Color} [color=(1,1,1,1)]
600
+ * @param {Color} [color=WHITE]
586
601
  * @param {number} [lineWidth=0]
587
- * @param {Color} [lineColor=(0,0,0,1)]
602
+ * @param {Color} [lineColor=BLACK]
588
603
  * @param {boolean} [useWebGL=glEnable]
589
604
  * @param {boolean} [screenSpace]
590
605
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
@@ -639,9 +654,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
639
654
  * @param {string|number} text
640
655
  * @param {Vector2} pos
641
656
  * @param {number} [size]
642
- * @param {Color} [color=(1,1,1,1)]
657
+ * @param {Color} [color=WHITE]
643
658
  * @param {number} [lineWidth]
644
- * @param {Color} [lineColor=(0,0,0,1)]
659
+ * @param {Color} [lineColor=BLACK]
645
660
  * @param {CanvasTextAlign} [textAlign='center']
646
661
  * @param {string} [font=fontDefault]
647
662
  * @param {string} [fontStyle]
@@ -665,10 +680,10 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
665
680
  * Automatically splits new lines into rows
666
681
  * @param {string|number} text
667
682
  * @param {Vector2} pos
668
- * @param {number} [size]
669
- * @param {Color} [color=(1,1,1,1)]
683
+ * @param {number} size
684
+ * @param {Color} [color=WHITE]
670
685
  * @param {number} [lineWidth]
671
- * @param {Color} [lineColor=(0,0,0,1)]
686
+ * @param {Color} [lineColor=BLACK]
672
687
  * @param {CanvasTextAlign} [textAlign]
673
688
  * @param {string} [font=fontDefault]
674
689
  * @param {string} [fontStyle]
@@ -676,7 +691,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
676
691
  * @param {number} [angle]
677
692
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
678
693
  * @memberof Draw */
679
- function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
694
+ function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
680
695
  {
681
696
  ASSERT(isString(text), 'text must be a string');
682
697
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -800,7 +815,7 @@ function worldToScreenDelta(worldDelta)
800
815
 
801
816
  /** Convert screen space transform to world space
802
817
  * @param {Vector2} screenPos
803
- * @param {Vector2} screenSize
818
+ * @param {Vector2} screenSize
804
819
  * @param {number} [screenAngle]
805
820
  * @return {[Vector2, Vector2, number]} - [pos, size, angle]
806
821
  * @memberof Draw */
@@ -861,10 +876,9 @@ function isOnScreen(pos, size=0)
861
876
  * @param {boolean} [additive]
862
877
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
863
878
  * @memberof Draw */
864
- function setBlendMode(additive=false, context)
879
+ function setBlendMode(additive=false, context=drawContext)
865
880
  {
866
881
  glAdditive = additive;
867
- context ||= drawContext;
868
882
  context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
869
883
  }
870
884
 
@@ -879,7 +893,6 @@ function combineCanvases()
879
893
  workContext.fillRect(0,0,w,h); // remove background alpha
880
894
  glCopyToContext(workContext);
881
895
  workContext.drawImage(mainCanvas, 0, 0);
882
- mainCanvas.width |= 0;
883
896
  mainContext.drawImage(workCanvas, 0, 0);
884
897
  }
885
898
 
@@ -984,17 +997,22 @@ function setCursor(cursorStyle = 'auto')
984
997
 
985
998
  ///////////////////////////////////////////////////////////////////////////////
986
999
 
1000
+ /** Engine font image, 8x8 font provided by the engine
1001
+ * @type {FontImage}
1002
+ * @memberof Draw */
987
1003
  let engineFontImage;
988
1004
 
989
1005
  /**
990
- * Font Image Object - Draw text on a 2D canvas by using characters in an image
1006
+ * Font Image Object - Draw text by using tiles in an image
991
1007
  * - 96 characters (from space to tilde) are stored in an image
992
- * - Uses a default 8x8 font if none is supplied
993
- * - You can also use fonts from the main tile sheet
1008
+ * - A 8x8 default engine font is supplied for general use
1009
+ * - This system is WebGL enabled for fast text rendering
1010
+ * - Fonts can also be colored and scaled along each axis
1011
+ *
994
1012
  * @memberof Draw
995
1013
  * @example
996
1014
  * // use built in font
997
- * const font = new FontImage;
1015
+ * const font = engineFontImage;
998
1016
  *
999
1017
  * // draw text
1000
1018
  * font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
@@ -1002,68 +1020,110 @@ let engineFontImage;
1002
1020
  class FontImage
1003
1021
  {
1004
1022
  /** Create an image font
1005
- * @param {HTMLImageElement} [image] - Image for the font, default if undefined
1006
- * @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
1007
- * @param {Vector2} [paddingSize=(0,1)] - How much space between characters
1023
+ * @param {TileInfo} tileInfo - Tile info of first characeter in font
1008
1024
  */
1009
- constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
1025
+ constructor(tileInfo)
1010
1026
  {
1011
- // load default font image
1012
- if (!image && !engineFontImage)
1013
- {
1014
- engineFontImage = new Image;
1015
- engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
1016
- }
1017
-
1018
- this.image = image || engineFontImage;
1019
- this.tileSize = tileSize;
1020
- this.paddingSize = paddingSize;
1027
+ ASSERT(!!tileInfo, 'tileInfo is required for FontImage');
1028
+
1029
+ /** @property {TileInfo} - Tile info for the font */
1030
+ this.tileInfo = tileInfo.frame(0);
1021
1031
  }
1022
1032
 
1023
1033
  /** Draw text in world space using the image font
1024
- * @param {string|number} text
1034
+ * @param {string|number} text
1025
1035
  * @param {Vector2} pos
1026
- * @param {number} [scale=.25]
1027
- * @param {boolean} [center]
1028
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
1036
+ * @param {Vector2|number} [size]
1037
+ * @param {boolean} [center=true]
1038
+ * @param {Color} [color=WHITE]
1039
+ * @param {boolean} [useWebGL=glEnable]
1040
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
1029
1041
  */
1030
- drawText(text, pos, scale=1, center, context=drawContext)
1042
+ drawText(text, pos, size=1, center, color, useWebGL, context)
1031
1043
  {
1032
- this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
1044
+ ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
1045
+
1046
+ if (typeof size === 'number')
1047
+ {
1048
+ // if size is a number, make it a vector
1049
+ ASSERT(size > 0);
1050
+ size *= cameraScale;
1051
+ size = new Vector2(size, size);
1052
+ }
1053
+ else
1054
+ size = size.scale(cameraScale);
1055
+ this.drawTextScreen(text, worldToScreen(pos), size, center, color, useWebGL, context);
1033
1056
  }
1034
1057
 
1035
1058
  /** Draw text in screen space using the image font
1036
- * @param {string|number} text
1059
+ * @param {string|number} text
1037
1060
  * @param {Vector2} pos
1038
- * @param {number} [scale]
1061
+ * @param {Vector2|number} size
1039
1062
  * @param {boolean} [center]
1040
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
1063
+ * @param {Color} [color=WHITE]
1064
+ * @param {boolean} [useWebGL=glEnable]
1065
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
1041
1066
  */
1042
- drawTextScreen(text, pos, scale=4, center=true, context=drawContext)
1067
+ drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
1043
1068
  {
1044
- context.save();
1045
- const size = this.tileSize;
1046
- const drawSize = size.add(this.paddingSize).scale(scale);
1047
- const cols = this.image.width / this.tileSize.x |0;
1048
- (text+'').split('\n').forEach((line, i)=>
1069
+ ASSERT(isString(text), 'text must be a string');
1070
+ ASSERT(isVector2(pos), 'pos must be a vec2');
1071
+ ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
1072
+ ASSERT(isColor(color), 'color must be a color');
1073
+
1074
+ // if size is a number, make it a vector
1075
+ size = typeof size === 'number' ? new Vector2(size, size) : size;
1076
+
1077
+ // precache objects for drawing
1078
+ const drawPos = new Vector2;
1079
+ const tileInfo = this.tileInfo;
1080
+ const padding = tileInfo.padding;
1081
+ const sizePaddedX = tileInfo.size.x + padding*2;
1082
+ const sizePaddedY = tileInfo.size.y + padding*2;
1083
+ const cols = tileInfo.textureInfo.size.x / sizePaddedX |0;
1084
+
1085
+ // draw each line of text
1086
+ (text+'').split('\n').forEach((line, j)=>
1049
1087
  {
1050
- const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
1051
- for (let j=line.length; j--;)
1088
+ const centerOffset = center ? (line.length-1) * size.x / 2 : 0;
1089
+ for (let i=line.length; i--;)
1052
1090
  {
1053
- // draw each character
1054
- let charCode = line[j].charCodeAt(0);
1055
- if (charCode < 32 || charCode > 127)
1056
- charCode = 127; // unknown character
1057
-
1058
- // get the character source location and draw it
1059
- const tile = charCode - 32;
1060
- const x = tile % cols;
1061
- const y = tile / cols |0;
1062
- const drawPos = pos.add(vec2(j,i).multiply(drawSize));
1063
- context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
1064
- drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
1091
+ // get the character index
1092
+ const charCode = line.charCodeAt(i);
1093
+ const index = charCode < 32 || charCode > 127 ?
1094
+ 95 : charCode - 32; // handle out of range characters
1095
+
1096
+ // get the position of the tile
1097
+ const x = index % cols;
1098
+ const y = index / cols |0;
1099
+ tileInfo.pos.x = x*sizePaddedX + padding;
1100
+ tileInfo.pos.y = y*sizePaddedY + padding;
1101
+
1102
+ // draw the tile
1103
+ drawPos.x = pos.x + i * size.x - centerOffset |0;
1104
+ drawPos.y = pos.y + j * size.y |0;
1105
+ drawTile(drawPos, size, tileInfo, color, 0, false, undefined, useWebGL, true, context);
1065
1106
  }
1066
1107
  });
1067
- context.restore();
1068
1108
  }
1109
+ }
1110
+
1111
+ // load engine font, called automatically on startup
1112
+ function fontImageInit()
1113
+ {
1114
+ return new Promise(resolve =>
1115
+ {
1116
+ // create the engine font
1117
+ const image = new Image;
1118
+ image.onerror = image.onload = ()=>
1119
+ {
1120
+ const tilePos=vec2(), tileSize=vec2(8), padding=1, bleed=0;
1121
+ const textureInfo = new TextureInfo(image);
1122
+ const tileInfo = new TileInfo(tilePos, tileSize, textureInfo, padding, bleed);
1123
+ engineFontImage = new FontImage(tileInfo);
1124
+ resolve();
1125
+ }
1126
+ image.crossOrigin = 'anonymous';
1127
+ 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==';
1128
+ });
1069
1129
  }