littlejsengine 1.19.3 → 1.20.0

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 (50) hide show
  1. package/FAQ.md +841 -820
  2. package/README.md +29 -25
  3. package/dist/littlejs.d.ts +2455 -935
  4. package/dist/littlejs.esm.js +30344 -21110
  5. package/dist/littlejs.esm.min.js +4 -3
  6. package/dist/littlejs.js +30259 -21052
  7. package/dist/littlejs.min.js +2 -2
  8. package/dist/littlejs.release.js +26567 -20455
  9. package/package.json +10 -5
  10. package/plugins/audioEffects.js +28 -24
  11. package/plugins/box2d.js +2516 -2073
  12. package/plugins/drawUtilities.js +206 -31
  13. package/plugins/gltf.js +740 -0
  14. package/plugins/jsconfig.json +19 -0
  15. package/plugins/lightSystem.js +719 -332
  16. package/plugins/math3d.js +58 -17
  17. package/plugins/medalSystem.js +326 -280
  18. package/plugins/newgrounds.js +400 -199
  19. package/plugins/pathFinder.js +208 -175
  20. package/plugins/pluginExport.js +15 -0
  21. package/plugins/postProcess.js +56 -13
  22. package/plugins/render3d.js +1643 -1577
  23. package/plugins/render3dDebug.js +109 -0
  24. package/plugins/render3dExtras.js +1754 -0
  25. package/plugins/render3dVoxels.js +502 -0
  26. package/plugins/textureSheet.js +496 -458
  27. package/plugins/threejs.js +183 -163
  28. package/plugins/tweakables.js +411 -0
  29. package/plugins/tweenSystem.js +263 -145
  30. package/plugins/uiSystem.js +2024 -1687
  31. package/plugins/zzfxm.js +159 -159
  32. package/src/engine.js +191 -75
  33. package/src/engineAudio.js +229 -125
  34. package/src/engineBuild.mjs +289 -256
  35. package/src/engineDebug.js +912 -742
  36. package/src/engineDraw.js +295 -193
  37. package/src/engineEditor.js +2431 -0
  38. package/src/engineExport.js +12 -0
  39. package/src/engineInput.js +1462 -1293
  40. package/src/engineMath.js +1274 -1252
  41. package/src/engineObject.js +194 -80
  42. package/src/engineParticles.js +583 -532
  43. package/src/engineRelease.js +67 -45
  44. package/src/engineSettings.js +77 -29
  45. package/src/engineTileLayer.js +995 -741
  46. package/src/engineUtilities.js +19 -13
  47. package/src/engineWebGL.js +239 -82
  48. package/src/jsconfig.json +17 -13
  49. package/plugins/box2d.wasm.js +0 -630
  50. package/plugins/box2d.wasm.wasm +0 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * LittleJS Drawing Utilities Plugin
3
3
  * - Extra drawing functions for LittleJS
4
- * - Nine slice and three slice drawing
4
+ * - Nine slice and three slice drawing, and TileSlice to keep one as a style, like a UI skin
5
5
  * @namespace DrawUtilities
6
6
  */
7
7
 
@@ -9,22 +9,32 @@
9
9
 
10
10
  ///////////////////////////////////////////////////////////////////////////////
11
11
 
12
- /** Draw a scalable nine-slice UI element to the main canvas in screen space
13
- * This function can not apply color because it draws using the 2d context
12
+ /** Draw a scalable nine-slice UI element in screen space, drawNineSlice with screenSpace set
13
+ * - Draws with the 2D context by default, on top of what WebGL drew, like drawTextScreen
14
+ * - With no angle its pieces land on whole pixels and meet exactly, so pixel art lines up and extraSpace is not used
14
15
  * @param {Vector2} pos - Screen space position
15
16
  * @param {Vector2} size - Screen space size
16
17
  * @param {TileInfo} startTile - Top-left tile of the 3x3 block to sample (see drawNineSlice)
18
+ * @param {Color|number} [color=WHITE] - Color to modulate with; a number here is the borderSize of the order before 1.20, deprecated
17
19
  * @param {number} [borderSize] - Rendered thickness of the border sections
20
+ * @param {Color} [additiveColor] - Additive color
18
21
  * @param {number} [extraSpace] - Extra spacing adjustment
19
22
  * @param {number} [angle] - Angle to rotate by
23
+ * @param {boolean} [useWebGL] - Use WebGL for rendering
24
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas context to use
20
25
  * @memberof DrawUtilities */
21
- function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2, angle=0)
26
+ function drawNineSliceScreen(pos, size, startTile, color=WHITE, borderSize=32, additiveColor, extraSpace=2, angle=0, useWebGL=false, context)
22
27
  {
23
- drawNineSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true);
28
+ if (isNumber(color)) // deprecated since 1.20, the order before it: borderSize, extraSpace and angle after startTile
29
+ {
30
+ [borderSize, extraSpace, angle] = [/** @type {number} */ (color), arguments[4] ?? 2, arguments[5] ?? 0];
31
+ color = WHITE, additiveColor = undefined;
32
+ }
33
+ drawNineSlice(pos, size, startTile, /** @type {Color} */ (color), borderSize, additiveColor, extraSpace, angle, useWebGL, true, context);
24
34
  }
25
35
 
26
36
  /** Draw a scalable nine-slice UI element in world space
27
- * This function can apply color and additive color if WebGL is enabled
37
+ * Color and additive color apply in WebGL and Canvas2D (with canvasColorTiles)
28
38
  * The nine-slice samples a 3x3 block of tiles from the tilesheet, it does not
29
39
  * subdivide a single tile. Pass the top-left tile of that block as startTile;
30
40
  * the other 8 tiles (edges, corners, and center) are taken automatically from
@@ -34,19 +44,37 @@ function drawNineSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2,
34
44
  * @param {Vector2} size - World space size
35
45
  * @param {TileInfo} startTile - Top-left tile of the 3x3 block to sample the nine-slice from
36
46
  * @param {Color} [color] - Color to modulate with
37
- * @param {number} [borderSize] - Rendered thickness of the border sections
47
+ * @param {number} [borderSize] - Rendered thickness of the border sections, 1 in world space and 32 pixels in
48
+ * screen space by default
38
49
  * @param {Color} [additiveColor] - Additive color
39
- * @param {number} [extraSpace] - Extra spacing adjustment
50
+ * @param {number} [extraSpace] - Extra spacing adjustment, .05 in world space and 2 pixels in screen space by
51
+ * default
40
52
  * @param {number} [angle] - Angle to rotate by
41
53
  * @param {boolean} [useWebGL=glEnable] - Use WebGL for rendering
42
- * @param {boolean} [screenSpace] - Use screen space coordinates
43
- * @param {CanvasRenderingContext2D} [context] - Canvas context to use
54
+ * @param {boolean} [screenSpace=drawScreenSpace] - Use screen space coordinates
55
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas context to use
44
56
  * @memberof DrawUtilities */
45
- function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor, extraSpace=.05, angle=0, useWebGL=glEnable, screenSpace, context)
57
+ function drawNineSlice(pos, size, startTile, color, borderSize, additiveColor, extraSpace, angle=0, useWebGL=glEnable, screenSpace=drawScreenSpace, context)
46
58
  {
59
+ // the border and spacing defaults are in the units of the space it draws in
60
+ borderSize ??= screenSpace ? 32 : 1;
61
+ extraSpace ??= screenSpace ? 2 : .05;
62
+ if (!size.x || !size.y) return; // nothing to draw, the spacing must not make a sliver of it
63
+ if (color && color.a < 1)
64
+ extraSpace = 0; // see through, the overlap that hides a seam would draw twice and show brighter
47
65
  // setup nine slice tiles - startTile is the top-left of a 3x3 tile block,
48
- // so the center tile is one tile down and right from it
49
- const centerTile = startTile.offset(startTile.size);
66
+ // so the center tile is one tile down and right from it, stepping over
67
+ // the padding around each tile the way tile() lays out the grid
68
+ const step = startTile.size.add(vec2(startTile.padding*2));
69
+ if (screenSpace && !angle)
70
+ {
71
+ // on whole pixels, the pieces in the screen's rows and columns of the block
72
+ drawSliceSnapped(pos, size, borderSize, (col, row)=>
73
+ [startTile.offset(step.multiply(vec2(col, row))), 0], color, additiveColor, useWebGL, context);
74
+ return;
75
+ }
76
+ borderSize = min(borderSize, abs(size.x)/2, abs(size.y)/2); // a box too small for two borders splits between them
77
+ const centerTile = startTile.offset(step);
50
78
  const centerSize = size.add(vec2(extraSpace-borderSize*2));
51
79
  const cornerSize = vec2(borderSize);
52
80
  const cornerOffset = size.scale(.5).subtract(cornerSize.scale(.5));
@@ -61,7 +89,7 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
61
89
  const horizontal = i%2;
62
90
  const sidePos = cornerOffset.multiply(vec2(horizontal?i===1?1:-1:0, horizontal?0:i?-1:1));
63
91
  const sideSize = vec2(horizontal ? borderSize : centerSize.x, horizontal ? centerSize.y : borderSize);
64
- const sideTile = centerTile.offset(startTile.size.multiply(vec2(i===1?1:i===3?-1:0,i===0?-flip:i===2?flip:0)))
92
+ const sideTile = centerTile.offset(step.multiply(vec2(i===1?1:i===3?-1:0,i===0?-flip:i===2?flip:0)))
65
93
  drawTile(pos.add(sidePos.rotate(rotateAngle)), sideSize, sideTile, color, angle, false, additiveColor, useWebGL, screenSpace, context);
66
94
  }
67
95
  for (let i=4; i--;)
@@ -70,27 +98,37 @@ function drawNineSlice(pos, size, startTile, color, borderSize=1, additiveColor,
70
98
  const flipX = i>1;
71
99
  const flipY = i && i<3;
72
100
  const cornerPos = cornerOffset.multiply(vec2(flipX?-1:1, flipY?-1:1));
73
- const cornerTile = centerTile.offset(startTile.size.multiply(vec2(flipX?-1:1,flipY?flip:-flip)));
101
+ const cornerTile = centerTile.offset(step.multiply(vec2(flipX?-1:1,flipY?flip:-flip)));
74
102
  drawTile(pos.add(cornerPos.rotate(rotateAngle)), cornerSize, cornerTile, color, angle, false, additiveColor, useWebGL, screenSpace, context);
75
103
  }
76
104
  }
77
105
 
78
- /** Draw a scalable three-slice UI element to the main canvas in screen space
79
- * This function can not apply color because it draws using the 2d context
106
+ /** Draw a scalable three-slice UI element in screen space, drawThreeSlice with screenSpace set
107
+ * - Draws with the 2D context by default, on top of what WebGL drew, like drawTextScreen
108
+ * - With no angle its pieces land on whole pixels and meet exactly, so pixel art lines up and extraSpace is not used
80
109
  * @param {Vector2} pos - Screen space position
81
110
  * @param {Vector2} size - Screen space size
82
111
  * @param {TileInfo} startTile - First of 3 consecutive tiles: corner, side, center (see drawThreeSlice)
112
+ * @param {Color|number} [color=WHITE] - Color to modulate with; a number here is the borderSize of the order before 1.20, deprecated
83
113
  * @param {number} [borderSize] - Rendered thickness of the border sections
114
+ * @param {Color} [additiveColor] - Additive color
84
115
  * @param {number} [extraSpace] - Extra spacing adjustment
85
116
  * @param {number} [angle] - Angle to rotate by
117
+ * @param {boolean} [useWebGL] - Use WebGL for rendering
118
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas context to use
86
119
  * @memberof DrawUtilities */
87
- function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2, angle=0)
120
+ function drawThreeSliceScreen(pos, size, startTile, color=WHITE, borderSize=32, additiveColor, extraSpace=2, angle=0, useWebGL=false, context)
88
121
  {
89
- drawThreeSlice(pos, size, startTile, WHITE, borderSize, BLACK, extraSpace, angle, false, true);
122
+ if (isNumber(color)) // deprecated since 1.20, the order before it: borderSize, extraSpace and angle after startTile
123
+ {
124
+ [borderSize, extraSpace, angle] = [/** @type {number} */ (color), arguments[4] ?? 2, arguments[5] ?? 0];
125
+ color = WHITE, additiveColor = undefined;
126
+ }
127
+ drawThreeSlice(pos, size, startTile, /** @type {Color} */ (color), borderSize, additiveColor, extraSpace, angle, useWebGL, true, context);
90
128
  }
91
129
 
92
130
  /** Draw a scalable three-slice UI element in world space
93
- * This function can apply color and additive color if WebGL is enabled
131
+ * Color and additive color apply in WebGL and Canvas2D (with canvasColorTiles)
94
132
  * The three-slice samples 3 consecutive tiles from the tilesheet, it does not
95
133
  * subdivide a single tile. Pass the first tile as startTile; the three tiles
96
134
  * are used in order as corner, side, and center, then rotated and mirrored to
@@ -99,20 +137,40 @@ function drawThreeSliceScreen(pos, size, startTile, borderSize=32, extraSpace=2,
99
137
  * @param {Vector2} size - World space size
100
138
  * @param {TileInfo} startTile - First of 3 consecutive tiles (corner, side, center) for the three-slice
101
139
  * @param {Color} [color] - Color to modulate with
102
- * @param {number} [borderSize] - Rendered thickness of the border sections
140
+ * @param {number} [borderSize] - Rendered thickness of the border sections, 1 in world space and 32 pixels in
141
+ * screen space by default
103
142
  * @param {Color} [additiveColor] - Additive color
104
- * @param {number} [extraSpace] - Extra spacing adjustment
143
+ * @param {number} [extraSpace] - Extra spacing adjustment, .05 in world space and 2 pixels in screen space by
144
+ * default
105
145
  * @param {number} [angle] - Angle to rotate by
106
146
  * @param {boolean} [useWebGL=glEnable] - Use WebGL for rendering
107
- * @param {boolean} [screenSpace] - Use screen space coordinates
108
- * @param {CanvasRenderingContext2D} [context] - Canvas context to use
147
+ * @param {boolean} [screenSpace=drawScreenSpace] - Use screen space coordinates
148
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas context to use
109
149
  * @memberof DrawUtilities */
110
- function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor, extraSpace=.05, angle=0, useWebGL=glEnable, screenSpace, context)
150
+ function drawThreeSlice(pos, size, startTile, color, borderSize, additiveColor, extraSpace, angle=0, useWebGL=glEnable, screenSpace=drawScreenSpace, context)
111
151
  {
152
+ // the border and spacing defaults are in the units of the space it draws in
153
+ borderSize ??= screenSpace ? 32 : 1;
154
+ extraSpace ??= screenSpace ? 2 : .05;
155
+ if (!size.x || !size.y) return; // nothing to draw, the spacing must not make a sliver of it
156
+ if (color && color.a < 1)
157
+ extraSpace = 0; // see through, the overlap that hides a seam would draw twice and show brighter
112
158
  // setup three slice tiles - 3 tiles in a row starting at startTile
113
159
  const cornerTile = startTile.frame(0);
114
160
  const sideTile = startTile.frame(1);
115
161
  const centerTile = startTile.frame(2);
162
+ if (screenSpace && !angle)
163
+ {
164
+ // on whole pixels, each corner and side the top left one's tile turned a quarter more going clockwise
165
+ const turns = [[0,0,1],[3,-1,1],[3,2,2]]; // by column in each row, -1 for the center
166
+ drawSliceSnapped(pos, size, borderSize, (col, row)=>
167
+ {
168
+ const turn = turns[row][col], corner = col !== 1 && row !== 1;
169
+ return turn < 0 ? [centerTile, 0] : [corner ? cornerTile : sideTile, turn*PI/2];
170
+ }, color, additiveColor, useWebGL, context);
171
+ return;
172
+ }
173
+ borderSize = min(borderSize, abs(size.x)/2, abs(size.y)/2); // a box too small for two borders splits between them
116
174
  const centerSize = size.add(vec2(extraSpace-borderSize*2));
117
175
  const cornerSize = vec2(borderSize);
118
176
  const cornerOffset = size.scale(.5).subtract(cornerSize.scale(.5));
@@ -141,21 +199,138 @@ function drawThreeSlice(pos, size, startTile, color, borderSize=1, additiveColor
141
199
  }
142
200
  }
143
201
 
202
+ ///////////////////////////////////////////////////////////////////////////////
203
+
204
+ /**
205
+ * A tile drawn as a box of any size, kept as a style to draw with, like a UI skin
206
+ * - 9 slices is a nine-slice from the 3x3 block of tiles at tileInfo, see drawNineSlice
207
+ * - 3 slices is a three-slice from the 3 tiles in a row at tileInfo, see drawThreeSlice
208
+ * - 1 slice is the whole tile stretched over the box, a plain image
209
+ * - The UI system draws a widget's background with one, see uiSystem.defaultSlice
210
+ * @memberof DrawUtilities
211
+ * @example
212
+ * const panel = new TileSlice(tile(0, 16), 9, .5); // a border of half a world unit
213
+ * panel.draw(vec2(0, 5), vec2(10, 4));
214
+ * uiSystem.defaultSlice = new TileSlice(tile(0, 16), 9, 16); // UI sizes are pixels, every widget made after this
215
+ */
216
+ class TileSlice
217
+ {
218
+ /** Create a tile slice style
219
+ * @param {TileInfo} tileInfo - The tile, or the first of the tiles, to draw with
220
+ * @param {number} [slices] - 9 for a nine-slice, 3 for a three-slice, 1 for the whole tile
221
+ * @param {number} [borderSize] - Drawn thickness of the edges and corners, undefined for the draw's own default
222
+ * @param {number} [extraSpace] - Extra spacing adjustment of the slices, undefined for the draw's own default */
223
+ constructor(tileInfo, slices=9, borderSize, extraSpace)
224
+ {
225
+ ASSERT(tileInfo instanceof TileInfo, 'tileInfo must be a TileInfo');
226
+ ASSERT(slices === 9 || slices === 3 || slices === 1, 'slices must be 9, 3 or 1');
227
+ ASSERT(borderSize === undefined || isNumber(borderSize), 'borderSize must be a number');
228
+
229
+ /** @property {TileInfo} - The tile, or the first of the tiles, to draw with */
230
+ this.tileInfo = tileInfo;
231
+ /** @property {number} - 9 for a nine-slice, 3 for a three-slice, 1 for the whole tile */
232
+ this.slices = slices;
233
+ /** @property {number|undefined} - Drawn thickness of the edges and corners, undefined for the draw's default
234
+ * @type {number|undefined} */
235
+ this.borderSize = borderSize;
236
+ /** @property {number|undefined} - Extra spacing adjustment of the slices, undefined for the draw's default
237
+ * @type {number|undefined} */
238
+ this.extraSpace = extraSpace;
239
+ }
240
+
241
+ /** Draw it as a box in world space, or in screen space
242
+ * @param {Vector2} pos - Center position
243
+ * @param {Vector2} size - Size of the box
244
+ * @param {Color} [color] - Color to modulate with
245
+ * @param {Color} [additiveColor] - Additive color
246
+ * @param {number} [angle] - Angle to rotate by
247
+ * @param {boolean} [useWebGL=glEnable] - Use WebGL for rendering
248
+ * @param {boolean} [screenSpace=drawScreenSpace] - Are pos and size in screen space?
249
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas context to use */
250
+ draw(pos, size, color=WHITE, additiveColor, angle=0, useWebGL=glEnable, screenSpace=drawScreenSpace, context)
251
+ {
252
+ if (screenSpace) // with the screen space defaults for the border and spacing
253
+ return this.drawScreen(pos, size, color, additiveColor, angle, useWebGL, context);
254
+ if (this.slices === 9)
255
+ drawNineSlice(pos, size, this.tileInfo, color, this.borderSize, additiveColor, this.extraSpace, angle, useWebGL, screenSpace, context);
256
+ else if (this.slices === 3)
257
+ drawThreeSlice(pos, size, this.tileInfo, color, this.borderSize, additiveColor, this.extraSpace, angle, useWebGL, screenSpace, context);
258
+ else
259
+ drawTile(pos, size, this.tileInfo, color, angle, false, additiveColor, useWebGL, screenSpace, context);
260
+ }
261
+
262
+ /** Draw it as a box in screen space, with the 2D context by default, on top of what WebGL drew, like drawTextScreen
263
+ * @param {Vector2} pos - Screen space center position
264
+ * @param {Vector2} size - Screen space size
265
+ * @param {Color} [color] - Color to modulate with
266
+ * @param {Color} [additiveColor] - Additive color
267
+ * @param {number} [angle] - Angle to rotate by
268
+ * @param {boolean} [useWebGL] - Use WebGL for rendering
269
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas context to use */
270
+ drawScreen(pos, size, color=WHITE, additiveColor, angle=0, useWebGL=false, context)
271
+ {
272
+ if (this.slices === 9)
273
+ drawNineSliceScreen(pos, size, this.tileInfo, color, this.borderSize, additiveColor, this.extraSpace, angle, useWebGL, context);
274
+ else if (this.slices === 3)
275
+ drawThreeSliceScreen(pos, size, this.tileInfo, color, this.borderSize, additiveColor, this.extraSpace, angle, useWebGL, context);
276
+ else
277
+ drawTile(pos, size, this.tileInfo, color, angle, false, additiveColor, useWebGL, true, context);
278
+ }
279
+ }
280
+
281
+ // draw a slice box in screen space with no angle on whole pixels: the edges and the border are rounded, and every
282
+ // piece is cut from them, so the pieces meet exactly and their texels line up where they do, with no overlap to hide
283
+ // a seam; pieceTile(col, row) gives the tile and angle for the piece in that column and row of the box, top left 0
284
+ function drawSliceSnapped(pos, size, borderSize, pieceTile, color, additiveColor, useWebGL, context)
285
+ {
286
+ // the pixels it lands on are the device's: a 2D context's own scale and offset, like the UI under nativeHeight
287
+ // centered on an odd sized canvas, or the pixel ratio; the draws land half a pixel past, see shift below
288
+ const canvas2D = !useWebGL || !glEnable || !!context;
289
+ const transform = canvas2D && (context || drawContext)?.getTransform?.();
290
+ const ratio = getCanvasPixelRatio();
291
+ const [scaleX, scaleY, offsetX, offsetY] = transform ?
292
+ [transform.a || 1, transform.d || 1, transform.e || 0, transform.f || 0] : [ratio, ratio, 0, 0];
293
+ const edges = (center, length, scale, offset)=>
294
+ {
295
+ // snapped in device order, which a mirrored context reverses, then put back in the box's own order
296
+ const border = max(1, round(borderSize * abs(scale)));
297
+ const a = round((center - length/2) * scale + offset), b = a + round(length * scale);
298
+ const start = min(a, b), end = max(a, b);
299
+ const inner = min(start + border, floor((start + end)/2)); // a box too small for two borders splits
300
+ const device = [start, inner, max(end - border, inner), end];
301
+ return (scale < 0 ? device.reverse() : device).map(edge=> (edge - offset) / scale);
302
+ };
303
+ const xs = edges(pos.x, size.x, scaleX, offsetX), ys = edges(pos.y, size.y, scaleY, offsetY);
304
+ const shift = .5; // a screen space draw lands half a pixel past its position, in Canvas2D and WebGL alike
305
+ for (let row = 3; row--;)
306
+ for (let col = 3; col--;)
307
+ {
308
+ const w = xs[col+1] - xs[col], h = ys[row+1] - ys[row];
309
+ if (w <= 0 || h <= 0) continue;
310
+ const [tileInfo, angle] = pieceTile(col, row);
311
+ const turned = round(angle / (PI/2)) % 2; // a quarter turn swaps the sides it is drawn with
312
+ const piecePos = vec2(xs[col] + w/2 - shift, ys[row] + h/2 - shift);
313
+ drawTile(piecePos, turned ? vec2(h, w) : vec2(w, h), tileInfo, color, angle, false, additiveColor, useWebGL, true, context);
314
+ }
315
+ }
316
+
144
317
  /** Draw a crescent / moon-phase shape built from a polygon
145
318
  * Routes through drawPoly, so it supports WebGL, screen space, color, and outlines
319
+ * - At angle 0 the lit side faces up while waxing (percent below .5) and down while waning, and the lit width
320
+ * grows evenly with the phase, not as the real moon's does
146
321
  * @param {Vector2} pos - Center position
147
322
  * @param {number} [size] - Diameter
148
323
  * @param {number} [percent] - Moon phase over a full cycle (0=new, .25=first quarter, .5=full, .75=last quarter), wraps
149
324
  * @param {Color} [color] - Fill color
150
325
  * @param {number} [angle] - Angle to rotate by
151
- * @param {boolean} [invert] - Flip which side is illuminated
326
+ * @param {boolean} [invert] - Draw the unlit part of the disk instead of the lit part
152
327
  * @param {number} [lineWidth] - Outline width, 0 for no outline
153
328
  * @param {Color} [lineColor] - Outline color
154
329
  * @param {boolean} [useWebGL=glEnable] - Use WebGL for rendering
155
- * @param {boolean} [screenSpace] - Use screen space coordinates
156
- * @param {CanvasRenderingContext2D} [context] - Canvas context to use
330
+ * @param {boolean} [screenSpace=drawScreenSpace] - Use screen space coordinates
331
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas context to use
157
332
  * @memberof DrawUtilities */
158
- function drawCrescent(pos, size=1, percent=0, color=WHITE, angle=0, invert=false, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
333
+ function drawCrescent(pos, size=1, percent=0, color=WHITE, angle=0, invert=false, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=drawScreenSpace, context)
159
334
  {
160
335
  // build local-space points and let drawPoly apply pos/angle so screen space works
161
336
  const points = getCrescentPoints(vec2(), size, percent, 0, invert);
@@ -168,7 +343,7 @@ function drawCrescent(pos, size=1, percent=0, color=WHITE, angle=0, invert=false
168
343
  * @param {number} [size] - Diameter
169
344
  * @param {number} [percent] - Moon phase over a full cycle (0=new, .25=first quarter, .5=full, .75=last quarter), wraps
170
345
  * @param {number} [angle] - Angle to rotate by
171
- * @param {boolean} [invert] - Flip which side is illuminated
346
+ * @param {boolean} [invert] - The unlit part of the disk instead of the lit part
172
347
  * @param {number} [sides=glCircleSides] - Number of sides for a full circle (halved per arc)
173
348
  * @return {Array<Vector2>} - List of points making up the crescent
174
349
  * @memberof DrawUtilities */
@@ -182,7 +357,7 @@ function getCrescentPoints(pos, size=1, percent=0, angle=0, invert=false, sides=
182
357
  if (p >= 2) // second half of cycle flips orientation
183
358
  angle += PI;
184
359
  p = p <= 2 ? p-1 : 3-p;
185
- if (invert) // flip the illuminated side
360
+ if (invert) // the unlit part: the other side of the same curve
186
361
  {
187
362
  p = -p;
188
363
  angle += PI;