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