littlejsengine 1.13.4 → 1.14.4

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 (58) hide show
  1. package/README.md +4 -3
  2. package/dist/littlejs.d.ts +395 -249
  3. package/dist/littlejs.esm.js +1550 -754
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +1528 -744
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +1504 -720
  8. package/examples/box2d/game.js +4 -4
  9. package/examples/box2d/gameObjects.js +1 -1
  10. package/examples/breakout/game.js +30 -25
  11. package/examples/breakoutTutorial/README.md +1 -1
  12. package/examples/breakoutTutorial/game.js +1 -1
  13. package/examples/electron/build.js +1 -1
  14. package/examples/electron/index.html +2 -2
  15. package/examples/empty/game.js +1 -1
  16. package/examples/htmlMenu/index.html +7 -7
  17. package/examples/index.html +208 -97
  18. package/examples/platformer/gameEffects.js +20 -25
  19. package/examples/platformer/gameLevel.js +6 -1
  20. package/examples/shorts/base.html +1 -1
  21. package/examples/shorts/flappyGame.js +2 -1
  22. package/examples/shorts/helloWorld.js +1 -1
  23. package/examples/shorts/music.js +106 -0
  24. package/examples/shorts/parallax.js +71 -0
  25. package/examples/shorts/piano.js +7 -8
  26. package/examples/shorts/postProcess.js +25 -8
  27. package/examples/shorts/shapes.js +12 -18
  28. package/examples/shorts/song.mp3 +0 -0
  29. package/examples/shorts/uiSystem.js +15 -8
  30. package/examples/starter/index.html +2 -2
  31. package/examples/stress/index.html +14 -7
  32. package/examples/style.css +14 -4
  33. package/examples/typescript/build.js +1 -1
  34. package/examples/uiSystem/game.js +11 -11
  35. package/package.json +1 -1
  36. package/plugins/box2d.js +12 -10
  37. package/plugins/drawUtilities.js +5 -5
  38. package/plugins/newgrounds.js +6 -6
  39. package/plugins/pluginExport.js +1 -1
  40. package/plugins/uiSystem.js +103 -79
  41. package/plugins/zzfxm.js +10 -10
  42. package/reference.md +94 -56
  43. package/src/engine.js +28 -24
  44. package/src/engineAudio.js +284 -109
  45. package/src/engineBuild.js +11 -11
  46. package/src/engineDebug.js +29 -29
  47. package/src/engineDraw.js +285 -112
  48. package/src/engineExport.js +28 -16
  49. package/src/engineInput.js +57 -67
  50. package/src/engineMedals.js +25 -25
  51. package/src/engineObject.js +28 -25
  52. package/src/engineParticles.js +59 -59
  53. package/src/engineRelease.js +1 -1
  54. package/src/engineSettings.js +20 -13
  55. package/src/engineTileLayer.js +34 -35
  56. package/src/engineUtilities.js +66 -59
  57. package/src/engineWebGL.js +466 -66
  58. /package/examples/shorts/{playSound.js → sound.js} +0 -0
package/src/engineDraw.js CHANGED
@@ -1,21 +1,21 @@
1
- /**
1
+ /**
2
2
  * LittleJS Drawing System
3
3
  * - Hybrid system with both Canvas2D and WebGL available
4
4
  * - Super fast tile sheet rendering with WebGL
5
5
  * - Can apply rotation, mirror, color and additive color
6
6
  * - Font rendering system with built in engine font
7
7
  * - Many useful utility functions
8
- *
8
+ *
9
9
  * LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
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
13
  * overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
14
- *
14
+ *
15
15
  * The WebGL rendering system is very fast with some caveats...
16
16
  * - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
17
17
  * - Group additive rendering together using renderOrder to mitigate this issue
18
- *
18
+ *
19
19
  * The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
20
20
  * @namespace Draw
21
21
  */
@@ -62,7 +62,7 @@ let workCanvas;
62
62
  * @memberof Draw */
63
63
  let workContext;
64
64
 
65
- /** The size of the main canvas (and other secondary canvases)
65
+ /** The size of the main canvas (and other secondary canvases)
66
66
  * @type {Vector2}
67
67
  * @memberof Draw */
68
68
  let mainCanvasSize = vec2();
@@ -72,12 +72,14 @@ let mainCanvasSize = vec2();
72
72
  * @memberof Draw */
73
73
  let textureInfos = [];
74
74
 
75
- // Keep track of how many draw calls there were each frame for debugging
75
+ /** Keeps track of how many draw calls there were each frame for debugging
76
+ * @type {number}
77
+ * @memberof Draw */
76
78
  let drawCount;
77
79
 
78
80
  ///////////////////////////////////////////////////////////////////////////////
79
81
 
80
- /**
82
+ /**
81
83
  * Create a tile info object using a grid based system
82
84
  * - This can take vecs or floats for easier use and conversion
83
85
  * - If an index is passed in, the tile size and index will determine the position
@@ -91,15 +93,14 @@ let drawCount;
91
93
  * tile(5, 8) // a tile at index 5 using a tile size of 8
92
94
  * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
93
95
  * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
94
- * @memberof Draw
95
- */
96
+ * @memberof Draw */
96
97
  function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
97
98
  {
98
99
  if (headlessMode)
99
100
  return new TileInfo;
100
101
 
101
102
  // if size is a number, make it a vector
102
- if (typeof size == 'number')
103
+ if (typeof size === 'number')
103
104
  {
104
105
  ASSERT(size > 0);
105
106
  size = new Vector2(size, size);
@@ -108,24 +109,24 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
108
109
  // create tile info object
109
110
  const tileInfo = new TileInfo(new Vector2, size, textureIndex, padding);
110
111
 
111
- // use get the pos of the tile
112
+ // get the position of the tile
112
113
  const textureInfo = textureInfos[textureIndex];
113
114
  ASSERT(!!textureInfo, 'Texture not loaded');
114
115
  const sizePaddedX = size.x + padding*2;
115
116
  const sizePaddedY = size.y + padding*2;
116
- if (typeof pos == 'number')
117
+ if (typeof pos === 'number')
117
118
  {
118
119
  const cols = textureInfo.size.x / sizePaddedX |0;
119
- ASSERT(cols>0, 'Tile size is too big for texture');
120
+ ASSERT(cols > 0, 'Tile size is too big for texture');
120
121
  const posX = pos % cols, posY = (pos / cols) |0;
121
122
  tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
122
123
  }
123
124
  else
124
125
  tileInfo.pos.set(pos.x*sizePaddedX+padding, pos.y*sizePaddedY+padding);
125
- return tileInfo;
126
+ return tileInfo;
126
127
  }
127
128
 
128
- /**
129
+ /**
129
130
  * Tile Info - Stores info about how to draw a tile
130
131
  */
131
132
  class TileInfo
@@ -163,14 +164,14 @@ class TileInfo
163
164
  */
164
165
  frame(frame)
165
166
  {
166
- ASSERT(typeof frame == 'number');
167
+ ASSERT(typeof frame === 'number');
167
168
  return this.offset(new Vector2(frame*(this.size.x+this.padding*2), 0));
168
169
  }
169
170
 
170
171
  /**
171
172
  * Set this tile to use a full image
172
173
  * @param {HTMLImageElement|OffscreenCanvas} image
173
- * @param {WebGLTexture} [glTexture] - webgl texture
174
+ * @param {WebGLTexture} [glTexture] - WebGL texture
174
175
  * @return {TileInfo}
175
176
  */
176
177
  setFullImage(image, glTexture)
@@ -188,7 +189,7 @@ class TextureInfo
188
189
  /**
189
190
  * Create a TextureInfo, called automatically by the engine
190
191
  * @param {HTMLImageElement|OffscreenCanvas} image
191
- * @param {WebGLTexture} [glTexture] - webgl texture
192
+ * @param {WebGLTexture} [glTexture] - WebGL texture
192
193
  */
193
194
  constructor(image, glTexture)
194
195
  {
@@ -198,7 +199,7 @@ class TextureInfo
198
199
  this.size = vec2(image.width, image.height);
199
200
  /** @property {Vector2} - inverse of the size, cached for rendering */
200
201
  this.sizeInverse = vec2(1/image.width, 1/image.height);
201
- /** @property {WebGLTexture} - webgl texture */
202
+ /** @property {WebGLTexture} - WebGL texture */
202
203
  this.glTexture = glTexture;
203
204
  }
204
205
 
@@ -214,28 +215,25 @@ class TextureInfo
214
215
  // Drawing functions
215
216
 
216
217
  /** Draw textured tile centered in world space, with color applied if using WebGL
217
- * @param {Vector2} pos - Center of the tile in world space
218
- * @param {Vector2} [size=(1,1)] - Size of the tile in world space
219
- * @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
220
- * @param {Color} [color=(1,1,1,1)] - Color to modulate with
221
- * @param {number} [angle] - Angle to rotate by
222
- * @param {boolean} [mirror] - If true image is flipped along the Y axis
223
- * @param {Color} [additiveColor] - Additive color to be applied if any
224
- * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
225
- * @param {boolean} [screenSpace=false] - If true the pos and size are in screen space
218
+ * @param {Vector2} pos - Center of the tile in world space
219
+ * @param {Vector2} [size=(1,1)] - Size of the tile in world space
220
+ * @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
221
+ * @param {Color} [color=(1,1,1,1)] - Color to modulate with
222
+ * @param {number} [angle] - Angle to rotate by
223
+ * @param {boolean} [mirror] - Is image flipped along the Y axis?
224
+ * @param {Color} [additiveColor] - Additive color to be applied if any
225
+ * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
226
+ * @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
226
227
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
227
228
  * @memberof Draw */
228
- function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
229
+ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
229
230
  angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
230
231
  {
231
- ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
232
- ASSERT(isVector2(pos) && pos.isValid(), 'drawTile pos should be a vec2');
233
- ASSERT(isVector2(size) && size.isValid(), 'drawTile size should be a vec2');
232
+ ASSERT(isVector2(pos), 'drawTile pos should be a vec2');
233
+ ASSERT(isVector2(size), 'drawTile size should be a vec2');
234
234
  ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
235
235
  ASSERT(isNumber(angle), 'drawTile angle should be a number');
236
-
237
- if (color.a <= 0 && (!additiveColor || additiveColor.a <= 0) || !size.x || !size.y)
238
- return; // completely invisible, skip render
236
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
239
237
 
240
238
  const textureInfo = tileInfo && tileInfo.textureInfo;
241
239
  if (useWebGL)
@@ -259,28 +257,28 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
259
257
  {
260
258
  const tileImageFixBleedX = sizeInverse.x*tileFixBleedScale;
261
259
  const tileImageFixBleedY = sizeInverse.y*tileFixBleedScale;
262
- glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
263
- x + tileImageFixBleedX, y + tileImageFixBleedY,
264
- x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
265
- color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
260
+ glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
261
+ x + tileImageFixBleedX, y + tileImageFixBleedY,
262
+ x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
263
+ color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
266
264
  }
267
265
  else
268
266
  {
269
- glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
270
- x, y, x + w, y + h,
271
- color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
267
+ glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
268
+ x, y, x + w, y + h,
269
+ color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
272
270
  }
273
271
  }
274
272
  else
275
273
  {
276
274
  // if no tile info, force untextured
277
- glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
275
+ glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
278
276
  }
279
277
  }
280
278
  else
281
279
  {
282
280
  // normal canvas 2D rendering method (slower)
283
- showWatermark && ++drawCount;
281
+ ++drawCount;
284
282
  size = new Vector2(size.x, -size.y); // fix upside down sprites
285
283
  drawCanvas2D(pos, size, angle, mirror, (context)=>
286
284
  {
@@ -312,14 +310,128 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
312
310
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
313
311
  * @memberof Draw */
314
312
  function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
315
- {
316
- drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
313
+ {
314
+ drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
315
+ }
316
+
317
+ /** Draw a rect centered on pos with a gradient from top to bottom
318
+ * @param {Vector2} pos
319
+ * @param {Vector2} [size=(1,1)]
320
+ * @param {Color} [colorTop=(1,1,1,1)]
321
+ * @param {Color} [colorBottom=(0,0,0,1)]
322
+ * @param {number} [angle]
323
+ * @param {boolean} [useWebGL=glEnable]
324
+ * @param {boolean} [screenSpace]
325
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
326
+ * @memberof Draw */
327
+ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
328
+ {
329
+ ASSERT(isVector2(pos), 'drawRectGradient pos should be a vec2');
330
+ ASSERT(isVector2(size), 'drawRectGradient size should be a vec2');
331
+ ASSERT(isColor(colorTop) && isColor(colorBottom), 'drawRectGradient color is invalid');
332
+ ASSERT(isNumber(angle), 'drawRectGradient angle should be a number');
333
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
334
+ if (useWebGL)
335
+ {
336
+ if (screenSpace)
337
+ {
338
+ // convert to world space
339
+ pos = screenToWorld(pos);
340
+ size = size.scale(1/cameraScale);
341
+ }
342
+ // build 4 corner points for the rectangle
343
+ const points = [], colors = [];
344
+ const halfSizeX = size.x/2, halfSizeY = size.y/2;
345
+ const colorTopInt = colorTop.rgbaInt();
346
+ const colorBottomInt = colorBottom.rgbaInt();
347
+ const c = Math.cos(-angle), s = Math.sin(-angle);
348
+ for (let i=4; i--;)
349
+ {
350
+ const x = i & 1 ? halfSizeX : -halfSizeX;
351
+ const y = i & 2 ? halfSizeY : -halfSizeY;
352
+ const rx = x * c - y * s;
353
+ const ry = x * s + y * c;
354
+ const color = i & 2 ? colorTopInt : colorBottomInt;
355
+ points.push(vec2(pos.x + rx, pos.y + ry));
356
+ colors.push(color);
357
+ }
358
+ glDrawColoredPoints(points, colors);
359
+ }
360
+ else
361
+ {
362
+ // normal canvas 2D rendering method (slower)
363
+ ++drawCount;
364
+ size = new Vector2(size.x, -size.y); // fix upside down sprites
365
+ drawCanvas2D(pos, size, angle, false, (context)=>
366
+ {
367
+ // if no tile info, use untextured rect
368
+ const gradient = context.createLinearGradient(0, -.5, 0, .5);
369
+ gradient.addColorStop(0, colorTop.toString());
370
+ gradient.addColorStop(1, colorBottom.toString());
371
+ context.fillStyle = gradient;
372
+ context.fillRect(-.5, -.5, 1, 1);
373
+ }, screenSpace, context);
374
+ }
375
+ }
376
+
377
+ /** Draw connected lines between a series of points
378
+ * @param {Array<Vector2>} points
379
+ * @param {number} [width]
380
+ * @param {Color} [color=(1,1,1,1)]
381
+ * @param {boolean} [wrap] - Should the last point connect to the first?
382
+ * @param {Vector2} [pos=(0,0)] - Offset to apply
383
+ * @param {number} [angle] - Angle to rotate by
384
+ * @param {boolean} [useWebGL=glEnable]
385
+ * @param {boolean} [screenSpace]
386
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
387
+ * @memberof Draw */
388
+ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
389
+ {
390
+ ASSERT(Array.isArray(points), 'drawLineList points should be an array');
391
+ ASSERT(isNumber(width), 'drawLineList width should be a number');
392
+ ASSERT(isColor(color), 'drawLineList color is invalid');
393
+ ASSERT(isVector2(pos), 'drawLineList pos should be a vec2');
394
+ ASSERT(isNumber(angle), 'drawLineList angle should be a number');
395
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
396
+ if (useWebGL)
397
+ {
398
+ let scale = 1;
399
+ if (screenSpace)
400
+ {
401
+ // convert to world space
402
+ pos = screenToWorld(pos);
403
+ scale = 1/cameraScale;
404
+ }
405
+ glDrawOutlineTransform(points, color.rgbaInt(), width, pos.x, pos.y, scale, scale, angle, wrap);
406
+ }
407
+ else
408
+ {
409
+ // normal canvas 2D rendering method (slower)
410
+ ++drawCount;
411
+ drawCanvas2D(pos, vec2(1), angle, false, (context)=>
412
+ {
413
+ context.strokeStyle = color.toString();
414
+ context.lineWidth = width;
415
+ context.beginPath();
416
+ for (let i=0; i<points.length; ++i)
417
+ {
418
+ const point = points[i];
419
+ if (i)
420
+ context.lineTo(point.x, point.y);
421
+ else
422
+ context.moveTo(point.x, point.y);
423
+ }
424
+ if (wrap)
425
+ context.closePath();
426
+ context.stroke();
427
+ }, screenSpace, context);
428
+ }
317
429
  }
318
430
 
319
431
  /** Draw colored line between two points
320
432
  * @param {Vector2} posA
321
433
  * @param {Vector2} posB
322
- * @param {number} [thickness]
434
+ * @param {number} [width]
323
435
  * @param {Color} [color=(1,1,1,1)]
324
436
  * @param {Vector2} [pos=(0,0)] - Offset to apply
325
437
  * @param {number} [angle] - Angle to rotate by
@@ -327,15 +439,43 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
327
439
  * @param {boolean} [screenSpace]
328
440
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
329
441
  * @memberof Draw */
330
- function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL, screenSpace, context)
442
+ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, screenSpace, context)
331
443
  {
332
444
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
333
- const size = vec2(thickness, halfDelta.length()*2);
445
+ const size = vec2(width, halfDelta.length()*2);
334
446
  pos = pos.add(posA.add(halfDelta));
335
447
  angle += halfDelta.angle();
336
448
  drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
337
449
  }
338
450
 
451
+ /** Draw colored regular polygon using passed in number of sides
452
+ * @param {Vector2} pos
453
+ * @param {Vector2} [size=(1,1)]
454
+ * @param {number} [sides]
455
+ * @param {Color} [color=(1,1,1,1)]
456
+ * @param {number} [angle]
457
+ * @param {number} [lineWidth]
458
+ * @param {Color} [lineColor=(0,0,0,1)]
459
+ * @param {boolean} [useWebGL=glEnable]
460
+ * @param {boolean} [screenSpace]
461
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
462
+ * @memberof Draw */
463
+ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
464
+ {
465
+ ASSERT(isVector2(size), 'drawRegularPoly size should be a vec2');
466
+ ASSERT(isNumber(sides), 'drawRegularPoly sides should be a number');
467
+
468
+ // build regular polygon points
469
+ const points = [];
470
+ const sizeX = size.x/2, sizeY = size.y/2;
471
+ for (let i=sides; i--;)
472
+ {
473
+ const a = (i/sides)*PI*2;
474
+ points.push(vec2(Math.sin(a)*sizeX, Math.cos(a)*sizeY));
475
+ }
476
+ drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
477
+ }
478
+
339
479
  /** Draw colored polygon using passed in points
340
480
  * @param {Array<Vector2>} points - Array of Vector2 points
341
481
  * @param {Color} [color=(1,1,1,1)]
@@ -343,82 +483,110 @@ function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL
343
483
  * @param {Color} [lineColor=(0,0,0,1)]
344
484
  * @param {Vector2} [pos=(0,0)] - Offset to apply
345
485
  * @param {number} [angle] - Angle to rotate by
346
- * @param {boolean} [useWebGL] - Webgl not supported
486
+ * @param {boolean} [useWebGL=glEnable]
347
487
  * @param {boolean} [screenSpace]
348
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
488
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
349
489
  * @memberof Draw */
350
- function drawPoly(points, color=new Color, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=false, screenSpace=false, context=drawContext)
490
+ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
351
491
  {
352
- ASSERT(isVector2(pos) && pos.isValid(), 'drawPoly pos should be a vec2');
492
+ ASSERT(isVector2(pos), 'drawPoly pos should be a vec2');
353
493
  ASSERT(Array.isArray(points), 'drawPoly points should be an array');
354
494
  ASSERT(isColor(color) && isColor(lineColor), 'drawPoly color is invalid');
355
495
  ASSERT(isNumber(lineWidth), 'drawPoly lineWidth should be a number');
356
496
  ASSERT(isNumber(angle), 'drawPoly angle should be a number');
357
- ASSERT(!useWebGL, 'drawPoly webgl not supported');
358
- drawCanvas2D(pos, vec2(1), angle, false, context=>
497
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
498
+ if (useWebGL)
359
499
  {
360
- context.beginPath();
361
- for (const point of points)
362
- context.lineTo(point.x, point.y);
363
- context.closePath();
364
- context.fillStyle = color.toString();
365
- context.fill();
366
- if (lineWidth)
500
+ let scale = 1;
501
+ if (screenSpace)
367
502
  {
368
- context.strokeStyle = lineColor.toString();
369
- context.lineWidth = lineWidth;
370
- context.stroke();
503
+ // convert to world space
504
+ pos = screenToWorld(pos);
505
+ scale = 1/cameraScale;
371
506
  }
372
- }, screenSpace, context);
507
+ glDrawPointsTransform(points, color.rgbaInt(), pos.x, pos.y, scale, scale, angle);
508
+ if (lineWidth > 0)
509
+ glDrawOutlineTransform(points, lineColor.rgbaInt(), lineWidth, pos.x, pos.y, scale, scale, angle);
510
+ }
511
+ else
512
+ {
513
+ drawCanvas2D(pos, vec2(1), angle, false, context=>
514
+ {
515
+ context.fillStyle = color.toString();
516
+ context.beginPath();
517
+ for (const point of points)
518
+ context.lineTo(point.x, point.y);
519
+ context.closePath();
520
+ context.fill();
521
+ if (lineWidth)
522
+ {
523
+ context.strokeStyle = lineColor.toString();
524
+ context.lineWidth = lineWidth;
525
+ context.stroke();
526
+ }
527
+ }, screenSpace, context);
528
+ }
373
529
  }
374
530
 
375
531
  /** Draw colored ellipse using passed in point
376
532
  * @param {Vector2} pos
377
- * @param {Vector2} [size=(1,1)]
533
+ * @param {Vector2} [size=(1,1)] - Width and height diameter
378
534
  * @param {Color} [color=(1,1,1,1)]
379
535
  * @param {number} [angle]
380
536
  * @param {number} [lineWidth]
381
537
  * @param {Color} [lineColor=(0,0,0,1)]
382
- * @param {boolean} [useWebGL] - Webgl not supported
538
+ * @param {boolean} [useWebGL=glEnable]
383
539
  * @param {boolean} [screenSpace]
384
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
540
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
385
541
  * @memberof Draw */
386
- function drawEllipse(pos, size=vec2(1), color=new Color, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=false, screenSpace=false, context=drawContext)
542
+ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
387
543
  {
388
- ASSERT(isVector2(pos) && pos.isValid(), 'drawEllipse pos should be a vec2');
389
- ASSERT(isVector2(size) && size.isValid(), 'drawEllipse size should be a vec2');
544
+ ASSERT(isVector2(pos), 'drawEllipse pos should be a vec2');
545
+ ASSERT(isVector2(size), 'drawEllipse size should be a vec2');
390
546
  ASSERT(isColor(color) && isColor(lineColor), 'drawEllipse color is invalid');
391
547
  ASSERT(isNumber(angle), 'drawEllipse angle should be a number');
392
548
  ASSERT(isNumber(lineWidth), 'drawEllipse lineWidth should be a number');
393
- ASSERT(lineWidth>=0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
394
- ASSERT(!useWebGL, 'drawEllipse webgl not supported');
395
- drawCanvas2D(pos, vec2(1), angle, false, context=>
549
+ ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
550
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
551
+ if (useWebGL)
396
552
  {
397
- context.beginPath();
398
- context.ellipse(0, 0, size.y, size.x, 0, 0, 9);
399
- context.fillStyle = color.toString();
400
- context.fill();
401
- if (lineWidth)
553
+ // draw as a regular polygon
554
+ const sides = glCircleSides;
555
+ drawRegularPoly(pos, size, sides, color, lineWidth, lineColor, angle, useWebGL, screenSpace, context);
556
+ }
557
+ else
558
+ {
559
+ drawCanvas2D(pos, vec2(1), angle, false, context=>
402
560
  {
403
- context.strokeStyle = lineColor.toString();
404
- context.lineWidth = lineWidth;
405
- context.stroke();
406
- }
407
- }, screenSpace, context);
561
+ context.fillStyle = color.toString();
562
+ context.beginPath();
563
+ context.ellipse(0, 0, size.x/2, size.y/2, 0, 0, 9);
564
+ context.fill();
565
+ if (lineWidth)
566
+ {
567
+ context.strokeStyle = lineColor.toString();
568
+ context.lineWidth = lineWidth;
569
+ context.stroke();
570
+ }
571
+ }, screenSpace, context);
572
+ }
408
573
  }
409
574
 
410
575
  /** Draw colored circle using passed in point
411
576
  * @param {Vector2} pos
412
- * @param {number} [radius=1]
577
+ * @param {number} [size=1] - Diameter
413
578
  * @param {Color} [color=(1,1,1,1)]
414
579
  * @param {number} [lineWidth=0]
415
580
  * @param {Color} [lineColor=(0,0,0,1)]
416
- * @param {boolean} [useWebGL] - Webgl not supported
417
- * @param {boolean} [screenSpace=false]
418
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
581
+ * @param {boolean} [useWebGL=glEnable]
582
+ * @param {boolean} [screenSpace]
583
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
419
584
  * @memberof Draw */
420
- function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=BLACK, useWebGL=false, screenSpace, context=drawContext)
421
- { drawEllipse(pos, vec2(radius), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context); }
585
+ function drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
586
+ {
587
+ ASSERT(isNumber(size), 'drawCircle size should be a number');
588
+ drawEllipse(pos, vec2(size), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context);
589
+ }
422
590
 
423
591
  /** Draw directly to a 2d canvas context in world space
424
592
  * @param {Vector2} pos
@@ -426,7 +594,7 @@ function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=BLACK
426
594
  * @param {number} angle
427
595
  * @param {boolean} [mirror]
428
596
  * @param {Function} [drawFunction]
429
- * @param {boolean} [screenSpace=false]
597
+ * @param {boolean} [screenSpace=false]
430
598
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
431
599
  * @memberof Draw */
432
600
  function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
@@ -496,7 +664,7 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
496
664
  * @param {number} [maxWidth]
497
665
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
498
666
  * @memberof Draw */
499
- function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth=undefined, context=overlayContext)
667
+ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
500
668
  {
501
669
  context.fillStyle = color.toString();
502
670
  context.strokeStyle = lineColor.toString();
@@ -504,7 +672,6 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
504
672
  context.textAlign = textAlign;
505
673
  context.font = size + 'px '+ font;
506
674
  context.textBaseline = 'middle';
507
- context.lineJoin = 'round';
508
675
 
509
676
  const lines = (text+'').split('\n');
510
677
  let posY = pos.y;
@@ -528,7 +695,7 @@ function screenToWorld(screenPos)
528
695
  {
529
696
  let cameraPosRelativeX = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
530
697
  let cameraPosRelativeY = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
531
- if (cameraAngle)
698
+ if (cameraAngle)
532
699
  {
533
700
  // apply camera rotation
534
701
  const cos = Math.cos(-cameraAngle), sin = Math.sin(-cameraAngle);
@@ -621,7 +788,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
621
788
  {
622
789
  // white texture with no additive alpha, no need to tint
623
790
  context.globalAlpha = color.a;
624
- context.drawImage(image, sx+sx2, sy+sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
791
+ context.drawImage(image, sx+sx2, sy+sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
625
792
  context.globalAlpha = 1;
626
793
  }
627
794
  else
@@ -642,7 +809,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
642
809
  for (let i = 0; i < data.length; ++i)
643
810
  data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
644
811
  workContext.putImageData(imageData, 0, 0);
645
- context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
812
+ context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
646
813
  }
647
814
  else
648
815
  {
@@ -655,7 +822,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
655
822
  }
656
823
  workContext.putImageData(imageData, 0, 0);
657
824
  context.globalAlpha = color.a;
658
- context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
825
+ context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
659
826
  context.globalAlpha = 1;
660
827
  }
661
828
  }
@@ -682,7 +849,7 @@ function toggleFullscreen()
682
849
  }
683
850
 
684
851
  /** Set the cursor style
685
- * @param {string} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
852
+ * @param {string} [cursorStyle] - CSS cursor style (auto, none, crosshair, etc)
686
853
  * @memberof Draw */
687
854
  function setCursor(cursorStyle = 'auto')
688
855
  {
@@ -694,7 +861,7 @@ function setCursor(cursorStyle = 'auto')
694
861
 
695
862
  let engineFontImage;
696
863
 
697
- /**
864
+ /**
698
865
  * Font Image Object - Draw text on a 2D canvas by using characters in an image
699
866
  * - 96 characters (from space to tilde) are stored in an image
700
867
  * - Uses a default 8x8 font if none is supplied
@@ -702,9 +869,9 @@ let engineFontImage;
702
869
  * @example
703
870
  * // use built in font
704
871
  * const font = new FontImage;
705
- *
872
+ *
706
873
  * // draw text
707
- * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
874
+ * font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
708
875
  */
709
876
  class FontImage
710
877
  {
@@ -712,7 +879,6 @@ class FontImage
712
879
  * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
713
880
  * @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
714
881
  * @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
715
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext] - context to draw to
716
882
  */
717
883
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
718
884
  {
@@ -726,7 +892,6 @@ class FontImage
726
892
  this.image = image || engineFontImage;
727
893
  this.tileSize = tileSize;
728
894
  this.paddingSize = paddingSize;
729
- this.context = context;
730
895
  }
731
896
 
732
897
  /** Draw text in world space using the image font
@@ -734,23 +899,32 @@ class FontImage
734
899
  * @param {Vector2} pos
735
900
  * @param {number} [scale=.25]
736
901
  * @param {boolean} [center]
902
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}[context=drawContext]
737
903
  */
738
- drawText(text, pos, scale=1, center)
904
+ drawText(text, pos, scale=1, center, context=drawContext)
739
905
  {
740
- this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
906
+ this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
741
907
  }
742
908
 
743
- /** Draw text in screen space using the image font
909
+ /** Draw text on overlay canvas in world space using the image font
910
+ * @param {string} text
911
+ * @param {Vector2} pos
912
+ * @param {number} [scale]
913
+ * @param {boolean} [center]
914
+ */
915
+ drawTextOverlay(text, pos, scale=4, center)
916
+ { this.drawText(text, pos, scale, center, overlayContext); }
917
+
918
+ /** Draw text on overlay canvas in screen space using the image font
744
919
  * @param {string} text
745
920
  * @param {Vector2} pos
746
921
  * @param {number} [scale]
747
922
  * @param {boolean} [center]
923
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
748
924
  */
749
- drawTextScreen(text, pos, scale=4, center)
925
+ drawTextScreen(text, pos, scale=4, center, context=overlayContext)
750
926
  {
751
- const context = this.context;
752
927
  context.save();
753
-
754
928
  const size = this.tileSize;
755
929
  const drawSize = size.add(this.paddingSize).scale(scale);
756
930
  const cols = this.image.width / this.tileSize.x |0;
@@ -769,11 +943,10 @@ class FontImage
769
943
  const x = tile % cols;
770
944
  const y = tile / cols |0;
771
945
  const drawPos = pos.add(vec2(j,i).multiply(drawSize));
772
- context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
946
+ context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
773
947
  drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
774
948
  }
775
949
  });
776
-
777
950
  context.restore();
778
951
  }
779
952
  }