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