littlejsengine 1.13.1 → 1.14.2
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/README.md +15 -11
- package/copyToGithub.bat +28 -0
- package/dist/littlejs.d.ts +430 -304
- package/dist/littlejs.esm.js +1745 -982
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1716 -965
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1673 -922
- package/examples/box2d/game.js +2 -2
- package/examples/box2d/gameObjects.js +2 -2
- package/examples/box2d/scenes.js +1 -1
- package/examples/breakout/game.js +30 -25
- package/examples/breakoutTutorial/README.md +44 -41
- package/examples/breakoutTutorial/game.js +2 -2
- package/examples/electron/build.bat +7 -0
- package/examples/electron/build.js +124 -0
- package/examples/electron/electron.js +35 -0
- package/examples/electron/game.js +140 -0
- package/examples/electron/index.html +13 -0
- package/examples/electron/package.json +12 -0
- package/examples/electron/tiles.png +0 -0
- package/examples/empty/game.js +1 -0
- package/examples/htmlMenu/game.js +1 -1
- package/examples/index.html +332 -160
- package/examples/module/game.js +5 -2
- package/examples/particles/index.html +12 -3
- package/examples/platformer/gameEffects.js +20 -19
- package/examples/platformer/gameLevel.js +6 -1
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/blending.js +9 -5
- package/examples/shorts/box2d.js +1 -1
- package/examples/shorts/box2dCar.js +1 -1
- package/examples/shorts/clock.js +1 -1
- package/examples/shorts/colors.js +2 -2
- package/examples/shorts/flappyGame.js +2 -1
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/maze.js +1 -1
- package/examples/shorts/music.js +106 -0
- package/examples/shorts/nineSlice.js +9 -9
- package/examples/shorts/parallax.js +71 -0
- package/examples/shorts/piano.js +9 -10
- package/examples/shorts/postProcess.js +25 -8
- package/examples/shorts/raycasting.js +2 -2
- package/examples/shorts/shapes.js +7 -15
- package/examples/shorts/slidingPuzzle.js +2 -2
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/starfield.js +5 -7
- package/examples/shorts/systemFont.js +1 -1
- package/examples/shorts/uiSystem.js +16 -8
- package/examples/starter/build.js +9 -8
- package/examples/starter/game.js +5 -2
- package/examples/starter/index.html +2 -2
- package/examples/stress/index.html +13 -8
- package/examples/style.css +19 -6
- package/examples/typescript/build.js +1 -1
- package/examples/typescript/game.js +2 -2
- package/examples/typescript/game.ts +5 -2
- package/examples/uiSystem/game.js +13 -12
- package/package.json +2 -2
- package/plugins/box2d.js +24 -97
- package/plugins/drawUtilities.js +29 -23
- package/plugins/newgrounds.js +6 -6
- package/plugins/pluginExport.js +1 -1
- package/plugins/postProcess.js +7 -0
- package/plugins/uiSystem.js +106 -82
- package/plugins/zzfxm.js +10 -10
- package/reference.md +90 -54
- package/src/engine.js +28 -23
- package/src/engineAudio.js +285 -110
- package/src/engineBuild.js +9 -9
- package/src/engineDebug.js +28 -28
- package/src/engineDraw.js +346 -202
- package/src/engineExport.js +28 -16
- package/src/engineInput.js +58 -68
- package/src/engineMedals.js +29 -29
- package/src/engineObject.js +28 -25
- package/src/engineParticles.js +67 -60
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +70 -16
- package/src/engineTileLayer.js +34 -34
- package/src/engineUtilities.js +69 -62
- package/src/engineWebGL.js +467 -65
- /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
|
*/
|
|
@@ -52,7 +52,17 @@ let drawCanvas;
|
|
|
52
52
|
* @memberof Draw */
|
|
53
53
|
let drawContext;
|
|
54
54
|
|
|
55
|
-
/**
|
|
55
|
+
/** Offscreen canvas that can be used for image processing
|
|
56
|
+
* @type {OffscreenCanvas}
|
|
57
|
+
* @memberof Draw */
|
|
58
|
+
let workCanvas;
|
|
59
|
+
|
|
60
|
+
/** Offscreen canvas that can be used for image processing
|
|
61
|
+
* @type {OffscreenCanvasRenderingContext2D}
|
|
62
|
+
* @memberof Draw */
|
|
63
|
+
let workContext;
|
|
64
|
+
|
|
65
|
+
/** The size of the main canvas (and other secondary canvases)
|
|
56
66
|
* @type {Vector2}
|
|
57
67
|
* @memberof Draw */
|
|
58
68
|
let mainCanvasSize = vec2();
|
|
@@ -67,29 +77,28 @@ let drawCount;
|
|
|
67
77
|
|
|
68
78
|
///////////////////////////////////////////////////////////////////////////////
|
|
69
79
|
|
|
70
|
-
/**
|
|
80
|
+
/**
|
|
71
81
|
* Create a tile info object using a grid based system
|
|
72
82
|
* - This can take vecs or floats for easier use and conversion
|
|
73
83
|
* - If an index is passed in, the tile size and index will determine the position
|
|
74
|
-
* @param {Vector2|number} [pos=0]
|
|
84
|
+
* @param {Vector2|number} [pos=0] - Index of tile in sheet
|
|
75
85
|
* @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
|
|
76
|
-
* @param {number} [textureIndex]
|
|
77
|
-
* @param {number} [padding]
|
|
86
|
+
* @param {number} [textureIndex] - Texture index to use
|
|
87
|
+
* @param {number} [padding] - How many pixels padding around tiles
|
|
78
88
|
* @return {TileInfo}
|
|
79
89
|
* @example
|
|
80
90
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
81
91
|
* tile(5, 8) // a tile at index 5 using a tile size of 8
|
|
82
92
|
* tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
|
|
83
93
|
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
84
|
-
* @memberof Draw
|
|
85
|
-
*/
|
|
94
|
+
* @memberof Draw */
|
|
86
95
|
function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
|
|
87
96
|
{
|
|
88
97
|
if (headlessMode)
|
|
89
98
|
return new TileInfo;
|
|
90
99
|
|
|
91
100
|
// if size is a number, make it a vector
|
|
92
|
-
if (typeof size
|
|
101
|
+
if (typeof size === 'number')
|
|
93
102
|
{
|
|
94
103
|
ASSERT(size > 0);
|
|
95
104
|
size = new Vector2(size, size);
|
|
@@ -98,24 +107,24 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
|
|
|
98
107
|
// create tile info object
|
|
99
108
|
const tileInfo = new TileInfo(new Vector2, size, textureIndex, padding);
|
|
100
109
|
|
|
101
|
-
//
|
|
110
|
+
// get the position of the tile
|
|
102
111
|
const textureInfo = textureInfos[textureIndex];
|
|
103
112
|
ASSERT(!!textureInfo, 'Texture not loaded');
|
|
104
113
|
const sizePaddedX = size.x + padding*2;
|
|
105
114
|
const sizePaddedY = size.y + padding*2;
|
|
106
|
-
if (typeof pos
|
|
115
|
+
if (typeof pos === 'number')
|
|
107
116
|
{
|
|
108
117
|
const cols = textureInfo.size.x / sizePaddedX |0;
|
|
109
|
-
ASSERT(cols>0, 'Tile size is too big for texture');
|
|
118
|
+
ASSERT(cols > 0, 'Tile size is too big for texture');
|
|
110
119
|
const posX = pos % cols, posY = (pos / cols) |0;
|
|
111
120
|
tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
|
|
112
121
|
}
|
|
113
122
|
else
|
|
114
123
|
tileInfo.pos.set(pos.x*sizePaddedX+padding, pos.y*sizePaddedY+padding);
|
|
115
|
-
return tileInfo;
|
|
124
|
+
return tileInfo;
|
|
116
125
|
}
|
|
117
126
|
|
|
118
|
-
/**
|
|
127
|
+
/**
|
|
119
128
|
* Tile Info - Stores info about how to draw a tile
|
|
120
129
|
*/
|
|
121
130
|
class TileInfo
|
|
@@ -153,14 +162,14 @@ class TileInfo
|
|
|
153
162
|
*/
|
|
154
163
|
frame(frame)
|
|
155
164
|
{
|
|
156
|
-
ASSERT(typeof frame
|
|
165
|
+
ASSERT(typeof frame === 'number');
|
|
157
166
|
return this.offset(new Vector2(frame*(this.size.x+this.padding*2), 0));
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
/**
|
|
161
170
|
* Set this tile to use a full image
|
|
162
171
|
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
163
|
-
* @param {WebGLTexture} [glTexture] -
|
|
172
|
+
* @param {WebGLTexture} [glTexture] - WebGL texture
|
|
164
173
|
* @return {TileInfo}
|
|
165
174
|
*/
|
|
166
175
|
setFullImage(image, glTexture)
|
|
@@ -178,7 +187,7 @@ class TextureInfo
|
|
|
178
187
|
/**
|
|
179
188
|
* Create a TextureInfo, called automatically by the engine
|
|
180
189
|
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
181
|
-
* @param {WebGLTexture} [glTexture] -
|
|
190
|
+
* @param {WebGLTexture} [glTexture] - WebGL texture
|
|
182
191
|
*/
|
|
183
192
|
constructor(image, glTexture)
|
|
184
193
|
{
|
|
@@ -188,7 +197,7 @@ class TextureInfo
|
|
|
188
197
|
this.size = vec2(image.width, image.height);
|
|
189
198
|
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
190
199
|
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
191
|
-
/** @property {WebGLTexture} -
|
|
200
|
+
/** @property {WebGLTexture} - WebGL texture */
|
|
192
201
|
this.glTexture = glTexture;
|
|
193
202
|
}
|
|
194
203
|
|
|
@@ -201,76 +210,28 @@ class TextureInfo
|
|
|
201
210
|
}
|
|
202
211
|
|
|
203
212
|
///////////////////////////////////////////////////////////////////////////////
|
|
204
|
-
|
|
205
|
-
/** Convert from screen to world space coordinates
|
|
206
|
-
* @param {Vector2} screenPos
|
|
207
|
-
* @return {Vector2}
|
|
208
|
-
* @memberof Draw */
|
|
209
|
-
function screenToWorld(screenPos)
|
|
210
|
-
{
|
|
211
|
-
let cameraPosRelativeX = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
212
|
-
let cameraPosRelativeY = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
213
|
-
if (cameraAngle)
|
|
214
|
-
{
|
|
215
|
-
// apply camera rotation
|
|
216
|
-
const cos = Math.cos(-cameraAngle), sin = Math.sin(-cameraAngle);
|
|
217
|
-
const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
|
|
218
|
-
const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
|
|
219
|
-
cameraPosRelativeX = rotatedX;
|
|
220
|
-
cameraPosRelativeY = rotatedY;
|
|
221
|
-
}
|
|
222
|
-
return new Vector2(cameraPosRelativeX + cameraPos.x, cameraPosRelativeY + cameraPos.y);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/** Convert from world to screen space coordinates
|
|
226
|
-
* @param {Vector2} worldPos
|
|
227
|
-
* @return {Vector2}
|
|
228
|
-
* @memberof Draw */
|
|
229
|
-
function worldToScreen(worldPos)
|
|
230
|
-
{
|
|
231
|
-
let cameraPosRelativeX = worldPos.x - cameraPos.x;
|
|
232
|
-
let cameraPosRelativeY = worldPos.y - cameraPos.y;
|
|
233
|
-
if (cameraAngle)
|
|
234
|
-
{
|
|
235
|
-
// apply inverse camera rotation
|
|
236
|
-
const cos = Math.cos(cameraAngle), sin = Math.sin(cameraAngle);
|
|
237
|
-
const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
|
|
238
|
-
const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
|
|
239
|
-
cameraPosRelativeX = rotatedX;
|
|
240
|
-
cameraPosRelativeY = rotatedY;
|
|
241
|
-
}
|
|
242
|
-
return new Vector2
|
|
243
|
-
(
|
|
244
|
-
cameraPosRelativeX * cameraScale + mainCanvasSize.x/2 - .5,
|
|
245
|
-
cameraPosRelativeY * -cameraScale + mainCanvasSize.y/2 - .5
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/** Get the camera's visible area in world space
|
|
250
|
-
* @return {Vector2}
|
|
251
|
-
* @memberof Draw */
|
|
252
|
-
function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
213
|
+
// Drawing functions
|
|
253
214
|
|
|
254
215
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
255
|
-
* @param {Vector2}
|
|
256
|
-
* @param {Vector2}
|
|
257
|
-
* @param {TileInfo}[tileInfo]
|
|
258
|
-
* @param {Color}
|
|
259
|
-
* @param {number}
|
|
260
|
-
* @param {boolean}
|
|
261
|
-
* @param {Color}
|
|
262
|
-
* @param {boolean}
|
|
263
|
-
* @param {boolean}
|
|
216
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
217
|
+
* @param {Vector2} [size=(1,1)] - Size of the tile in world space
|
|
218
|
+
* @param {TileInfo} [tileInfo] - Tile info to use, untextured if undefined
|
|
219
|
+
* @param {Color} [color=(1,1,1,1)] - Color to modulate with
|
|
220
|
+
* @param {number} [angle] - Angle to rotate by
|
|
221
|
+
* @param {boolean} [mirror] - Is image flipped along the Y axis?
|
|
222
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
223
|
+
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering?
|
|
224
|
+
* @param {boolean} [screenSpace=false] - Are the pos and size are in screen space?
|
|
264
225
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
265
226
|
* @memberof Draw */
|
|
266
|
-
function drawTile(pos, size=new Vector2(1), tileInfo, color=
|
|
227
|
+
function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
267
228
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
268
229
|
{
|
|
269
|
-
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
270
230
|
ASSERT(isVector2(pos) && pos.isValid(), 'drawTile pos should be a vec2');
|
|
271
231
|
ASSERT(isVector2(size) && size.isValid(), 'drawTile size should be a vec2');
|
|
272
232
|
ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
|
|
273
233
|
ASSERT(isNumber(angle), 'drawTile angle should be a number');
|
|
234
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
274
235
|
|
|
275
236
|
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
276
237
|
if (useWebGL)
|
|
@@ -294,22 +255,22 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
|
294
255
|
{
|
|
295
256
|
const tileImageFixBleedX = sizeInverse.x*tileFixBleedScale;
|
|
296
257
|
const tileImageFixBleedY = sizeInverse.y*tileFixBleedScale;
|
|
297
|
-
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
298
|
-
x + tileImageFixBleedX, y + tileImageFixBleedY,
|
|
299
|
-
x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
|
|
300
|
-
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
258
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
259
|
+
x + tileImageFixBleedX, y + tileImageFixBleedY,
|
|
260
|
+
x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
|
|
261
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
301
262
|
}
|
|
302
263
|
else
|
|
303
264
|
{
|
|
304
|
-
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
305
|
-
x, y, x + w, y + h,
|
|
306
|
-
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
265
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
266
|
+
x, y, x + w, y + h,
|
|
267
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
307
268
|
}
|
|
308
269
|
}
|
|
309
270
|
else
|
|
310
271
|
{
|
|
311
272
|
// if no tile info, force untextured
|
|
312
|
-
glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
|
|
273
|
+
glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
|
|
313
274
|
}
|
|
314
275
|
}
|
|
315
276
|
else
|
|
@@ -322,18 +283,15 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
|
322
283
|
if (textureInfo)
|
|
323
284
|
{
|
|
324
285
|
// calculate uvs and render
|
|
325
|
-
const x = tileInfo.pos.x
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
const h = tileInfo.size.y - 2*tileFixBleedScale;
|
|
329
|
-
context.globalAlpha = color.a; // only alpha is supported
|
|
330
|
-
context.drawImage(textureInfo.image, x, y, w, h, -.5, -.5, 1, 1);
|
|
331
|
-
context.globalAlpha = 1; // set back to full alpha
|
|
286
|
+
const x = tileInfo.pos.x, y = tileInfo.pos.y;
|
|
287
|
+
const w = tileInfo.size.x, h = tileInfo.size.y;
|
|
288
|
+
drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor);
|
|
332
289
|
}
|
|
333
290
|
else
|
|
334
291
|
{
|
|
335
|
-
// if no tile info,
|
|
336
|
-
|
|
292
|
+
// if no tile info, use untextured rect
|
|
293
|
+
const c = additiveColor ? color.add(additiveColor) : color;
|
|
294
|
+
context.fillStyle = c.toString();
|
|
337
295
|
context.fillRect(-.5, -.5, 1, 1);
|
|
338
296
|
}
|
|
339
297
|
}, screenSpace, context);
|
|
@@ -346,12 +304,12 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
|
346
304
|
* @param {Color} [color=(1,1,1,1)]
|
|
347
305
|
* @param {number} [angle]
|
|
348
306
|
* @param {boolean} [useWebGL=glEnable]
|
|
349
|
-
* @param {boolean} [screenSpace
|
|
307
|
+
* @param {boolean} [screenSpace]
|
|
350
308
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
351
309
|
* @memberof Draw */
|
|
352
310
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
353
|
-
{
|
|
354
|
-
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
311
|
+
{
|
|
312
|
+
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
355
313
|
}
|
|
356
314
|
|
|
357
315
|
/** Draw colored line between two points
|
|
@@ -359,72 +317,138 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
359
317
|
* @param {Vector2} posB
|
|
360
318
|
* @param {number} [thickness]
|
|
361
319
|
* @param {Color} [color=(1,1,1,1)]
|
|
320
|
+
* @param {Vector2} [pos=(0,0)] - Offset to apply
|
|
321
|
+
* @param {number} [angle] - Angle to rotate by
|
|
362
322
|
* @param {boolean} [useWebGL=glEnable]
|
|
363
|
-
* @param {boolean} [screenSpace
|
|
323
|
+
* @param {boolean} [screenSpace]
|
|
364
324
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
365
325
|
* @memberof Draw */
|
|
366
|
-
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
326
|
+
function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL, screenSpace, context)
|
|
367
327
|
{
|
|
368
328
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
369
329
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
370
|
-
|
|
330
|
+
pos = pos.add(posA.add(halfDelta));
|
|
331
|
+
angle += halfDelta.angle();
|
|
332
|
+
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Draw colored regular polygon using passed in number of sides
|
|
336
|
+
* @param {Vector2} pos
|
|
337
|
+
* @param {Vector2} [size=(1,1)]
|
|
338
|
+
* @param {number} [sides]
|
|
339
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
340
|
+
* @param {number} [angle]
|
|
341
|
+
* @param {number} [lineWidth]
|
|
342
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
343
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
344
|
+
* @param {boolean} [screenSpace]
|
|
345
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
346
|
+
* @memberof Draw */
|
|
347
|
+
function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
|
|
348
|
+
{
|
|
349
|
+
// build regular polygon points
|
|
350
|
+
const points = [];
|
|
351
|
+
for (let i=sides; i--;)
|
|
352
|
+
{
|
|
353
|
+
const a = (i/sides)*PI*2;
|
|
354
|
+
points.push(vec2(Math.sin(a)*size.x, Math.cos(a)*size.y));
|
|
355
|
+
}
|
|
356
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
|
|
371
357
|
}
|
|
372
358
|
|
|
373
359
|
/** Draw colored polygon using passed in points
|
|
374
|
-
* @param {Array<Vector2>}
|
|
360
|
+
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
375
361
|
* @param {Color} [color=(1,1,1,1)]
|
|
376
|
-
* @param {number} [lineWidth
|
|
362
|
+
* @param {number} [lineWidth]
|
|
377
363
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
378
|
-
* @param {
|
|
379
|
-
* @param {
|
|
364
|
+
* @param {Vector2} [pos=(0,0)] - Offset to apply
|
|
365
|
+
* @param {number} [angle] - Angle to rotate by
|
|
366
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
367
|
+
* @param {boolean} [screenSpace]
|
|
368
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
380
369
|
* @memberof Draw */
|
|
381
|
-
function drawPoly(points, color=
|
|
370
|
+
function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
|
|
382
371
|
{
|
|
383
|
-
ASSERT(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
context
|
|
389
|
-
|
|
390
|
-
if (lineWidth)
|
|
372
|
+
ASSERT(isVector2(pos) && pos.isValid(), 'drawPoly pos should be a vec2');
|
|
373
|
+
ASSERT(Array.isArray(points), 'drawPoly points should be an array');
|
|
374
|
+
ASSERT(isColor(color) && isColor(lineColor), 'drawPoly color is invalid');
|
|
375
|
+
ASSERT(isNumber(lineWidth), 'drawPoly lineWidth should be a number');
|
|
376
|
+
ASSERT(isNumber(angle), 'drawPoly angle should be a number');
|
|
377
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
378
|
+
if (useWebGL)
|
|
391
379
|
{
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
380
|
+
let scale = 1;
|
|
381
|
+
if (screenSpace)
|
|
382
|
+
{
|
|
383
|
+
// convert to world space
|
|
384
|
+
pos = screenToWorld(pos);
|
|
385
|
+
scale = 1/cameraScale;
|
|
386
|
+
}
|
|
387
|
+
glDrawPointsTransform(points, color.rgbaInt(), pos.x, pos.y, scale, scale, angle);
|
|
388
|
+
if (lineWidth > 0)
|
|
389
|
+
glDrawOutlineTransform(points, lineColor.rgbaInt(), lineWidth, pos.x, pos.y, scale, scale, angle);
|
|
390
|
+
}
|
|
391
|
+
else
|
|
392
|
+
{
|
|
393
|
+
drawCanvas2D(pos, vec2(1), angle, false, context=>
|
|
394
|
+
{
|
|
395
|
+
context.fillStyle = color.toString();
|
|
396
|
+
context.beginPath();
|
|
397
|
+
for (const point of points)
|
|
398
|
+
context.lineTo(point.x, point.y);
|
|
399
|
+
context.closePath();
|
|
400
|
+
context.fill();
|
|
401
|
+
if (lineWidth)
|
|
402
|
+
{
|
|
403
|
+
context.strokeStyle = lineColor.toString();
|
|
404
|
+
context.lineWidth = lineWidth;
|
|
405
|
+
context.stroke();
|
|
406
|
+
}
|
|
407
|
+
}, screenSpace, context);
|
|
395
408
|
}
|
|
396
409
|
}
|
|
397
410
|
|
|
398
411
|
/** Draw colored ellipse using passed in point
|
|
399
412
|
* @param {Vector2} pos
|
|
400
|
-
* @param {
|
|
401
|
-
* @param {number} [height=1]
|
|
402
|
-
* @param {number} [angle=0]
|
|
413
|
+
* @param {Vector2} [size=(1,1)]
|
|
403
414
|
* @param {Color} [color=(1,1,1,1)]
|
|
404
|
-
* @param {number} [
|
|
415
|
+
* @param {number} [angle]
|
|
416
|
+
* @param {number} [lineWidth]
|
|
405
417
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
406
|
-
* @param {boolean} [
|
|
407
|
-
* @param {
|
|
418
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
419
|
+
* @param {boolean} [screenSpace]
|
|
420
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
408
421
|
* @memberof Draw */
|
|
409
|
-
function drawEllipse(pos,
|
|
422
|
+
function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
410
423
|
{
|
|
411
|
-
ASSERT(
|
|
412
|
-
|
|
424
|
+
ASSERT(isVector2(pos) && pos.isValid(), 'drawEllipse pos should be a vec2');
|
|
425
|
+
ASSERT(isVector2(size) && size.isValid(), 'drawEllipse size should be a vec2');
|
|
426
|
+
ASSERT(isColor(color) && isColor(lineColor), 'drawEllipse color is invalid');
|
|
427
|
+
ASSERT(isNumber(angle), 'drawEllipse angle should be a number');
|
|
428
|
+
ASSERT(isNumber(lineWidth), 'drawEllipse lineWidth should be a number');
|
|
429
|
+
ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
|
|
430
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
431
|
+
if (useWebGL)
|
|
413
432
|
{
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
lineWidth *= cameraScale;
|
|
433
|
+
// draw as a regular polygon
|
|
434
|
+
const sides = glCircleSides;
|
|
435
|
+
drawRegularPoly(pos, size, sides, color, lineWidth, lineColor, angle, useWebGL, screenSpace, context);
|
|
418
436
|
}
|
|
419
|
-
|
|
420
|
-
context.beginPath();
|
|
421
|
-
context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
|
|
422
|
-
context.fill();
|
|
423
|
-
if (lineWidth)
|
|
437
|
+
else
|
|
424
438
|
{
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
439
|
+
drawCanvas2D(pos, vec2(1), angle, false, context=>
|
|
440
|
+
{
|
|
441
|
+
context.fillStyle = color.toString();
|
|
442
|
+
context.beginPath();
|
|
443
|
+
context.ellipse(0, 0, size.x, size.y, 0, 0, 9);
|
|
444
|
+
context.fill();
|
|
445
|
+
if (lineWidth)
|
|
446
|
+
{
|
|
447
|
+
context.strokeStyle = lineColor.toString();
|
|
448
|
+
context.lineWidth = lineWidth;
|
|
449
|
+
context.stroke();
|
|
450
|
+
}
|
|
451
|
+
}, screenSpace, context);
|
|
428
452
|
}
|
|
429
453
|
}
|
|
430
454
|
|
|
@@ -434,11 +458,12 @@ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth
|
|
|
434
458
|
* @param {Color} [color=(1,1,1,1)]
|
|
435
459
|
* @param {number} [lineWidth=0]
|
|
436
460
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
437
|
-
* @param {boolean} [
|
|
438
|
-
* @param {
|
|
461
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
462
|
+
* @param {boolean} [screenSpace]
|
|
463
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
439
464
|
* @memberof Draw */
|
|
440
|
-
function drawCircle(pos, radius=1, color=
|
|
441
|
-
{ drawEllipse(pos, radius,
|
|
465
|
+
function drawCircle(pos, radius=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
466
|
+
{ drawEllipse(pos, vec2(radius), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context); }
|
|
442
467
|
|
|
443
468
|
/** Draw directly to a 2d canvas context in world space
|
|
444
469
|
* @param {Vector2} pos
|
|
@@ -446,7 +471,7 @@ function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new C
|
|
|
446
471
|
* @param {number} angle
|
|
447
472
|
* @param {boolean} [mirror]
|
|
448
473
|
* @param {Function} [drawFunction]
|
|
449
|
-
* @param {boolean}
|
|
474
|
+
* @param {boolean} [screenSpace=false]
|
|
450
475
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
451
476
|
* @memberof Draw */
|
|
452
477
|
function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
|
|
@@ -465,6 +490,9 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
465
490
|
context.restore();
|
|
466
491
|
}
|
|
467
492
|
|
|
493
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
494
|
+
// Text Drawing Functions
|
|
495
|
+
|
|
468
496
|
/** Draw text on main canvas in world space
|
|
469
497
|
* Automatically splits new lines into rows
|
|
470
498
|
* @param {string} text
|
|
@@ -513,7 +541,7 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
513
541
|
* @param {number} [maxWidth]
|
|
514
542
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
515
543
|
* @memberof Draw */
|
|
516
|
-
function drawTextScreen(text, pos, size=1, color=
|
|
544
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
|
|
517
545
|
{
|
|
518
546
|
context.fillStyle = color.toString();
|
|
519
547
|
context.strokeStyle = lineColor.toString();
|
|
@@ -534,22 +562,67 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
|
|
|
534
562
|
});
|
|
535
563
|
}
|
|
536
564
|
|
|
565
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
566
|
+
// Drawing utilities
|
|
567
|
+
|
|
568
|
+
/** Convert from screen to world space coordinates
|
|
569
|
+
* @param {Vector2} screenPos
|
|
570
|
+
* @return {Vector2}
|
|
571
|
+
* @memberof Draw */
|
|
572
|
+
function screenToWorld(screenPos)
|
|
573
|
+
{
|
|
574
|
+
let cameraPosRelativeX = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
575
|
+
let cameraPosRelativeY = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
576
|
+
if (cameraAngle)
|
|
577
|
+
{
|
|
578
|
+
// apply camera rotation
|
|
579
|
+
const cos = Math.cos(-cameraAngle), sin = Math.sin(-cameraAngle);
|
|
580
|
+
const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
|
|
581
|
+
const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
|
|
582
|
+
cameraPosRelativeX = rotatedX;
|
|
583
|
+
cameraPosRelativeY = rotatedY;
|
|
584
|
+
}
|
|
585
|
+
return new Vector2(cameraPosRelativeX + cameraPos.x, cameraPosRelativeY + cameraPos.y);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/** Convert from world to screen space coordinates
|
|
589
|
+
* @param {Vector2} worldPos
|
|
590
|
+
* @return {Vector2}
|
|
591
|
+
* @memberof Draw */
|
|
592
|
+
function worldToScreen(worldPos)
|
|
593
|
+
{
|
|
594
|
+
let cameraPosRelativeX = worldPos.x - cameraPos.x;
|
|
595
|
+
let cameraPosRelativeY = worldPos.y - cameraPos.y;
|
|
596
|
+
if (cameraAngle)
|
|
597
|
+
{
|
|
598
|
+
// apply inverse camera rotation
|
|
599
|
+
const cos = Math.cos(cameraAngle), sin = Math.sin(cameraAngle);
|
|
600
|
+
const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
|
|
601
|
+
const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
|
|
602
|
+
cameraPosRelativeX = rotatedX;
|
|
603
|
+
cameraPosRelativeY = rotatedY;
|
|
604
|
+
}
|
|
605
|
+
return new Vector2
|
|
606
|
+
(
|
|
607
|
+
cameraPosRelativeX * cameraScale + mainCanvasSize.x/2 - .5,
|
|
608
|
+
cameraPosRelativeY * -cameraScale + mainCanvasSize.y/2 - .5
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Get the camera's visible area in world space
|
|
613
|
+
* @return {Vector2}
|
|
614
|
+
* @memberof Draw */
|
|
615
|
+
function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
616
|
+
|
|
537
617
|
/** Enable normal or additive blend mode
|
|
538
618
|
* @param {boolean} [additive]
|
|
539
|
-
* @param {boolean} [useWebGL=glEnable]
|
|
540
619
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
541
620
|
* @memberof Draw */
|
|
542
|
-
function setBlendMode(additive=false,
|
|
621
|
+
function setBlendMode(additive=false, context)
|
|
543
622
|
{
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
else
|
|
548
|
-
{
|
|
549
|
-
if (!context)
|
|
550
|
-
context = drawContext;
|
|
551
|
-
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
552
|
-
}
|
|
623
|
+
glAdditive = additive;
|
|
624
|
+
context ||= drawContext;
|
|
625
|
+
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
553
626
|
}
|
|
554
627
|
|
|
555
628
|
/** Combines all LittleJS canvases onto the main canvas and clears them
|
|
@@ -566,11 +639,107 @@ function combineCanvases()
|
|
|
566
639
|
overlayCanvas.width |= 0;
|
|
567
640
|
}
|
|
568
641
|
|
|
642
|
+
/** Helper function to draw an image with color and additive color applied
|
|
643
|
+
* This is slower then normal drawImage when color is applied
|
|
644
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
645
|
+
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
646
|
+
* @param {number} sx
|
|
647
|
+
* @param {number} sy
|
|
648
|
+
* @param {number} sWidth
|
|
649
|
+
* @param {number} sHeight
|
|
650
|
+
* @param {number} dx
|
|
651
|
+
* @param {number} dy
|
|
652
|
+
* @param {number} dWidth
|
|
653
|
+
* @param {number} dHeight
|
|
654
|
+
* @param {Color} color
|
|
655
|
+
* @param {Color} [additiveColor]
|
|
656
|
+
* @memberof Draw */
|
|
657
|
+
function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor)
|
|
658
|
+
{
|
|
659
|
+
function isWhite(c) { return c.r >= 1 && c.g >= 1 && c.b >= 1; }
|
|
660
|
+
function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
|
|
661
|
+
const sx2 = tileFixBleedScale;
|
|
662
|
+
const sy2 = tileFixBleedScale;
|
|
663
|
+
const sWidth2 = sWidth - 2*tileFixBleedScale;
|
|
664
|
+
const sHeight2 = sHeight - 2*tileFixBleedScale;
|
|
665
|
+
if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
|
|
666
|
+
{
|
|
667
|
+
// white texture with no additive alpha, no need to tint
|
|
668
|
+
context.globalAlpha = color.a;
|
|
669
|
+
context.drawImage(image, sx+sx2, sy+sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
670
|
+
context.globalAlpha = 1;
|
|
671
|
+
}
|
|
672
|
+
else
|
|
673
|
+
{
|
|
674
|
+
// copy to offscreen canvas
|
|
675
|
+
workCanvas.width = sWidth;
|
|
676
|
+
workCanvas.height = sHeight;
|
|
677
|
+
workContext.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
678
|
+
|
|
679
|
+
// tint image using offscreen work context
|
|
680
|
+
const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
|
|
681
|
+
const data = imageData.data;
|
|
682
|
+
if (additiveColor && !isBlack(additiveColor))
|
|
683
|
+
{
|
|
684
|
+
// slower path with additive color
|
|
685
|
+
const colorMultiply = [color.r, color.g, color.b, color.a];
|
|
686
|
+
const colorAdd = [additiveColor.r * 255, additiveColor.g * 255, additiveColor.b * 255, additiveColor.a * 255];
|
|
687
|
+
for (let i = 0; i < data.length; ++i)
|
|
688
|
+
data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
|
|
689
|
+
workContext.putImageData(imageData, 0, 0);
|
|
690
|
+
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
691
|
+
}
|
|
692
|
+
else
|
|
693
|
+
{
|
|
694
|
+
// faster path with no additive color
|
|
695
|
+
for (let i = 0; i < data.length; i+=4)
|
|
696
|
+
{
|
|
697
|
+
data[i ] *= color.r;
|
|
698
|
+
data[i+1] *= color.g;
|
|
699
|
+
data[i+2] *= color.b;
|
|
700
|
+
}
|
|
701
|
+
workContext.putImageData(imageData, 0, 0);
|
|
702
|
+
context.globalAlpha = color.a;
|
|
703
|
+
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
704
|
+
context.globalAlpha = 1;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
/** Returns true if fullscreen mode is active
|
|
711
|
+
* @return {boolean}
|
|
712
|
+
* @memberof Draw */
|
|
713
|
+
function isFullscreen() { return !!document.fullscreenElement; }
|
|
714
|
+
|
|
715
|
+
/** Toggle fullscreen mode
|
|
716
|
+
* @memberof Draw */
|
|
717
|
+
function toggleFullscreen()
|
|
718
|
+
{
|
|
719
|
+
const rootElement = mainCanvas.parentElement;
|
|
720
|
+
if (isFullscreen())
|
|
721
|
+
{
|
|
722
|
+
if (document.exitFullscreen)
|
|
723
|
+
document.exitFullscreen();
|
|
724
|
+
}
|
|
725
|
+
else if (rootElement.requestFullscreen)
|
|
726
|
+
rootElement.requestFullscreen();
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** Set the cursor style
|
|
730
|
+
* @param {string} [cursorStyle] - CSS cursor style (auto, none, crosshair, etc)
|
|
731
|
+
* @memberof Draw */
|
|
732
|
+
function setCursor(cursorStyle = 'auto')
|
|
733
|
+
{
|
|
734
|
+
const rootElement = mainCanvas.parentElement;
|
|
735
|
+
rootElement.style.cursor = cursorStyle;
|
|
736
|
+
}
|
|
737
|
+
|
|
569
738
|
///////////////////////////////////////////////////////////////////////////////
|
|
570
739
|
|
|
571
740
|
let engineFontImage;
|
|
572
741
|
|
|
573
|
-
/**
|
|
742
|
+
/**
|
|
574
743
|
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
575
744
|
* - 96 characters (from space to tilde) are stored in an image
|
|
576
745
|
* - Uses a default 8x8 font if none is supplied
|
|
@@ -578,7 +747,7 @@ let engineFontImage;
|
|
|
578
747
|
* @example
|
|
579
748
|
* // use built in font
|
|
580
749
|
* const font = new FontImage;
|
|
581
|
-
*
|
|
750
|
+
*
|
|
582
751
|
* // draw text
|
|
583
752
|
* font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
|
|
584
753
|
*/
|
|
@@ -588,7 +757,6 @@ class FontImage
|
|
|
588
757
|
* @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
|
|
589
758
|
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
590
759
|
* @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
|
|
591
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
592
760
|
*/
|
|
593
761
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
|
|
594
762
|
{
|
|
@@ -602,7 +770,6 @@ class FontImage
|
|
|
602
770
|
this.image = image || engineFontImage;
|
|
603
771
|
this.tileSize = tileSize;
|
|
604
772
|
this.paddingSize = paddingSize;
|
|
605
|
-
this.context = context;
|
|
606
773
|
}
|
|
607
774
|
|
|
608
775
|
/** Draw text in world space using the image font
|
|
@@ -610,30 +777,39 @@ class FontImage
|
|
|
610
777
|
* @param {Vector2} pos
|
|
611
778
|
* @param {number} [scale=.25]
|
|
612
779
|
* @param {boolean} [center]
|
|
780
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}[context=drawContext]
|
|
613
781
|
*/
|
|
614
|
-
drawText(text, pos, scale=1, center)
|
|
782
|
+
drawText(text, pos, scale=1, center, context=drawContext)
|
|
615
783
|
{
|
|
616
|
-
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
784
|
+
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center, context);
|
|
617
785
|
}
|
|
618
786
|
|
|
619
|
-
/** Draw text in
|
|
787
|
+
/** Draw text on overlay canvas in world space using the image font
|
|
788
|
+
* @param {string} text
|
|
789
|
+
* @param {Vector2} pos
|
|
790
|
+
* @param {number} [scale]
|
|
791
|
+
* @param {boolean} [center]
|
|
792
|
+
*/
|
|
793
|
+
drawTextOverlay(text, pos, scale=4, center)
|
|
794
|
+
{ this.drawText(text, pos, scale, center, overlayContext); }
|
|
795
|
+
|
|
796
|
+
/** Draw text on overlay canvas in screen space using the image font
|
|
620
797
|
* @param {string} text
|
|
621
798
|
* @param {Vector2} pos
|
|
622
799
|
* @param {number} [scale]
|
|
623
800
|
* @param {boolean} [center]
|
|
801
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
624
802
|
*/
|
|
625
|
-
drawTextScreen(text, pos, scale=4, center)
|
|
803
|
+
drawTextScreen(text, pos, scale=4, center, context=overlayContext)
|
|
626
804
|
{
|
|
627
|
-
const context = this.context;
|
|
628
805
|
context.save();
|
|
629
|
-
|
|
630
806
|
const size = this.tileSize;
|
|
631
807
|
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
632
808
|
const cols = this.image.width / this.tileSize.x |0;
|
|
633
809
|
(text+'').split('\n').forEach((line, i)=>
|
|
634
810
|
{
|
|
635
811
|
const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
|
|
636
|
-
for(let j=line.length; j--;)
|
|
812
|
+
for (let j=line.length; j--;)
|
|
637
813
|
{
|
|
638
814
|
// draw each character
|
|
639
815
|
let charCode = line[j].charCodeAt(0);
|
|
@@ -645,42 +821,10 @@ class FontImage
|
|
|
645
821
|
const x = tile % cols;
|
|
646
822
|
const y = tile / cols |0;
|
|
647
823
|
const drawPos = pos.add(vec2(j,i).multiply(drawSize));
|
|
648
|
-
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
824
|
+
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
649
825
|
drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
|
|
650
826
|
}
|
|
651
827
|
});
|
|
652
|
-
|
|
653
828
|
context.restore();
|
|
654
829
|
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
658
|
-
// Display functions
|
|
659
|
-
|
|
660
|
-
/** Returns true if fullscreen mode is active
|
|
661
|
-
* @return {boolean}
|
|
662
|
-
* @memberof Draw */
|
|
663
|
-
function isFullscreen() { return !!document.fullscreenElement; }
|
|
664
|
-
|
|
665
|
-
/** Toggle fullscreen mode
|
|
666
|
-
* @memberof Draw */
|
|
667
|
-
function toggleFullscreen()
|
|
668
|
-
{
|
|
669
|
-
const rootElement = mainCanvas.parentElement;
|
|
670
|
-
if (isFullscreen())
|
|
671
|
-
{
|
|
672
|
-
if (document.exitFullscreen)
|
|
673
|
-
document.exitFullscreen();
|
|
674
|
-
}
|
|
675
|
-
else if (rootElement.requestFullscreen)
|
|
676
|
-
rootElement.requestFullscreen();
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/** Set the cursor style
|
|
680
|
-
* @param {string} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
|
|
681
|
-
* @memberof Draw */
|
|
682
|
-
function setCursor(cursorStyle = 'auto')
|
|
683
|
-
{
|
|
684
|
-
const rootElement = mainCanvas.parentElement;
|
|
685
|
-
rootElement.style.cursor = cursorStyle;
|
|
686
830
|
}
|