littlejsengine 1.12.6 → 1.13.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 +15 -12
- package/dist/littlejs.d.ts +466 -236
- package/dist/littlejs.esm.js +6126 -5370
- package/dist/littlejs.esm.min.js +4 -1
- package/dist/littlejs.js +6093 -5359
- package/dist/littlejs.min.js +4 -1
- package/dist/littlejs.release.js +5614 -4907
- package/examples/box2d/game.js +2 -2
- package/examples/box2d/gameObjects.js +8 -7
- package/examples/box2d/index.html +1 -1
- package/examples/box2d/scenes.js +8 -8
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/gameObjects.js +2 -2
- package/examples/breakout/index.html +1 -1
- package/examples/breakoutTutorial/README.md +46 -43
- package/examples/breakoutTutorial/game.js +3 -3
- package/examples/breakoutTutorial/index.html +1 -1
- 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/empty/index.html +1 -1
- package/examples/htmlMenu/game.js +1 -1
- package/examples/htmlMenu/index.html +1 -1
- package/examples/index.html +486 -182
- package/examples/module/game.js +6 -3
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +12 -3
- package/examples/platformer/game.js +4 -4
- package/examples/platformer/gameCharacter.js +6 -6
- package/examples/platformer/gameEffects.js +74 -57
- package/examples/platformer/gameLevel.js +61 -70
- package/examples/platformer/gameObjects.js +4 -4
- package/examples/platformer/index.html +1 -1
- package/examples/puzzle/index.html +1 -1
- package/examples/shorts/base.html +23 -3
- package/examples/shorts/blending.js +9 -5
- package/examples/shorts/box2d.js +1 -1
- package/examples/shorts/box2dCar.js +9 -13
- package/examples/shorts/clock.js +1 -1
- package/examples/shorts/colors.js +2 -2
- package/examples/shorts/flappyGame.js +52 -0
- package/examples/shorts/helloWorld.js +6 -3
- package/examples/shorts/hillGlideGame.js +56 -0
- package/examples/shorts/landerGame.js +32 -0
- package/examples/shorts/maze.js +47 -0
- package/examples/shorts/medals.js +42 -0
- package/examples/shorts/nineSlice.js +21 -0
- package/examples/shorts/piano.js +52 -0
- package/examples/shorts/platformer.js +24 -20
- package/examples/shorts/{pong.js → pongGame.js} +1 -1
- package/examples/shorts/raycasting.js +71 -0
- package/examples/shorts/shapes.js +9 -9
- package/examples/shorts/slidingPuzzle.js +42 -0
- package/examples/shorts/spaceGame.js +56 -0
- package/examples/shorts/starfield.js +15 -0
- package/examples/shorts/systemFont.js +1 -1
- package/examples/shorts/tileLayer.js +3 -5
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +8 -7
- package/examples/shorts/topDown.js +18 -26
- package/examples/shorts/uiSystem.js +5 -7
- package/examples/starter/build.bat +6 -1
- package/examples/starter/build.js +9 -8
- package/examples/starter/game.js +7 -4
- package/examples/starter/index.html +23 -13
- package/examples/stress/index.html +7 -8
- package/examples/style.css +139 -0
- package/examples/typescript/build.js +2 -3
- package/examples/typescript/game.js +4 -4
- package/examples/typescript/game.ts +6 -3
- package/examples/typescript/index.html +1 -1
- package/examples/uiSystem/game.js +10 -25
- package/examples/uiSystem/index.html +1 -1
- package/package.json +2 -2
- package/plugins/box2d.js +22 -97
- package/plugins/drawUtilities.js +132 -0
- package/plugins/newgrounds.js +1 -1
- package/plugins/pluginExport.js +7 -1
- package/plugins/postProcess.js +9 -2
- package/plugins/uiSystem.js +155 -67
- package/plugins/zzfxm.js +1 -1
- package/reference.md +10 -10
- package/src/engine.js +56 -30
- package/src/engineAudio.js +15 -6
- package/src/engineBuild.bat +6 -1
- package/src/engineBuild.js +20 -5
- package/src/engineDebug.js +55 -28
- package/src/engineDraw.js +321 -177
- package/src/engineExport.js +27 -9
- package/src/engineInput.js +110 -38
- package/src/engineMedals.js +4 -4
- package/src/engineObject.js +71 -70
- package/src/engineParticles.js +33 -9
- package/src/engineSettings.js +69 -23
- package/src/engineTileLayer.js +312 -153
- package/src/engineUtilities.js +187 -135
- package/src/engineWebGL.js +70 -39
package/src/engineDraw.js
CHANGED
|
@@ -42,6 +42,26 @@ let overlayCanvas;
|
|
|
42
42
|
* @memberof Draw */
|
|
43
43
|
let overlayContext;
|
|
44
44
|
|
|
45
|
+
/** The default canvas to use for drawing, usually mainCanvas
|
|
46
|
+
* @type {HTMLCanvasElement|OffscreenCanvas}
|
|
47
|
+
* @memberof Draw */
|
|
48
|
+
let drawCanvas;
|
|
49
|
+
|
|
50
|
+
/** The default 2d context to use for drawing, usually mainContext
|
|
51
|
+
* @type {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D}
|
|
52
|
+
* @memberof Draw */
|
|
53
|
+
let drawContext;
|
|
54
|
+
|
|
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
|
+
|
|
45
65
|
/** The size of the main canvas (and other secondary canvases)
|
|
46
66
|
* @type {Vector2}
|
|
47
67
|
* @memberof Draw */
|
|
@@ -61,10 +81,10 @@ let drawCount;
|
|
|
61
81
|
* Create a tile info object using a grid based system
|
|
62
82
|
* - This can take vecs or floats for easier use and conversion
|
|
63
83
|
* - If an index is passed in, the tile size and index will determine the position
|
|
64
|
-
* @param {Vector2|number} [pos=0]
|
|
84
|
+
* @param {Vector2|number} [pos=0] - Index of tile in sheet
|
|
65
85
|
* @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
|
|
66
|
-
* @param {number} [textureIndex]
|
|
67
|
-
* @param {number} [padding]
|
|
86
|
+
* @param {number} [textureIndex] - Texture index to use
|
|
87
|
+
* @param {number} [padding] - How many pixels padding around tiles
|
|
68
88
|
* @return {TileInfo}
|
|
69
89
|
* @example
|
|
70
90
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -73,31 +93,36 @@ let drawCount;
|
|
|
73
93
|
* tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
|
|
74
94
|
* @memberof Draw
|
|
75
95
|
*/
|
|
76
|
-
function tile(pos=
|
|
96
|
+
function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
|
|
77
97
|
{
|
|
78
98
|
if (headlessMode)
|
|
79
99
|
return new TileInfo;
|
|
80
100
|
|
|
81
101
|
// if size is a number, make it a vector
|
|
82
|
-
if (typeof size
|
|
102
|
+
if (typeof size == 'number')
|
|
83
103
|
{
|
|
84
104
|
ASSERT(size > 0);
|
|
85
|
-
size =
|
|
105
|
+
size = new Vector2(size, size);
|
|
86
106
|
}
|
|
87
107
|
|
|
88
|
-
//
|
|
108
|
+
// create tile info object
|
|
109
|
+
const tileInfo = new TileInfo(new Vector2, size, textureIndex, padding);
|
|
110
|
+
|
|
111
|
+
// use get the pos of the tile
|
|
89
112
|
const textureInfo = textureInfos[textureIndex];
|
|
90
113
|
ASSERT(!!textureInfo, 'Texture not loaded');
|
|
91
|
-
const
|
|
92
|
-
|
|
114
|
+
const sizePaddedX = size.x + padding*2;
|
|
115
|
+
const sizePaddedY = size.y + padding*2;
|
|
116
|
+
if (typeof pos == 'number')
|
|
93
117
|
{
|
|
94
|
-
const cols = textureInfo.size.x /
|
|
95
|
-
|
|
118
|
+
const cols = textureInfo.size.x / sizePaddedX |0;
|
|
119
|
+
ASSERT(cols>0, 'Tile size is too big for texture');
|
|
120
|
+
const posX = pos % cols, posY = (pos / cols) |0;
|
|
121
|
+
tileInfo.pos.set(posX*sizePaddedX+padding, posY*sizePaddedY+padding);
|
|
96
122
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return new TileInfo(pos, size, textureIndex, padding);
|
|
123
|
+
else
|
|
124
|
+
tileInfo.pos.set(pos.x*sizePaddedX+padding, pos.y*sizePaddedY+padding);
|
|
125
|
+
return tileInfo;
|
|
101
126
|
}
|
|
102
127
|
|
|
103
128
|
/**
|
|
@@ -121,6 +146,8 @@ class TileInfo
|
|
|
121
146
|
this.textureIndex = textureIndex;
|
|
122
147
|
/** @property {number} - How many pixels padding around tiles */
|
|
123
148
|
this.padding = padding;
|
|
149
|
+
/** @property {TextureInfo} - The texture info for this tile */
|
|
150
|
+
this.textureInfo = textureInfos[this.textureIndex];
|
|
124
151
|
}
|
|
125
152
|
|
|
126
153
|
/** Returns a copy of this tile offset by a vector
|
|
@@ -137,14 +164,22 @@ class TileInfo
|
|
|
137
164
|
frame(frame)
|
|
138
165
|
{
|
|
139
166
|
ASSERT(typeof frame == 'number');
|
|
140
|
-
return this.offset(
|
|
167
|
+
return this.offset(new Vector2(frame*(this.size.x+this.padding*2), 0));
|
|
141
168
|
}
|
|
142
169
|
|
|
143
|
-
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Set this tile to use a full image
|
|
172
|
+
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
173
|
+
* @param {WebGLTexture} [glTexture] - webgl texture
|
|
174
|
+
* @return {TileInfo}
|
|
175
|
+
*/
|
|
176
|
+
setFullImage(image, glTexture)
|
|
177
|
+
{
|
|
178
|
+
this.pos = new Vector2;
|
|
179
|
+
this.size = new Vector2(image.width, image.height);
|
|
180
|
+
this.textureInfo = new TextureInfo(image, glTexture);
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
148
183
|
}
|
|
149
184
|
|
|
150
185
|
/** Texture Info - Stores info about each texture */
|
|
@@ -152,9 +187,10 @@ class TextureInfo
|
|
|
152
187
|
{
|
|
153
188
|
/**
|
|
154
189
|
* Create a TextureInfo, called automatically by the engine
|
|
155
|
-
* @param {HTMLImageElement} image
|
|
190
|
+
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
191
|
+
* @param {WebGLTexture} [glTexture] - webgl texture
|
|
156
192
|
*/
|
|
157
|
-
constructor(image)
|
|
193
|
+
constructor(image, glTexture)
|
|
158
194
|
{
|
|
159
195
|
/** @property {HTMLImageElement} - image source */
|
|
160
196
|
this.image = image;
|
|
@@ -163,42 +199,19 @@ class TextureInfo
|
|
|
163
199
|
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
164
200
|
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
165
201
|
/** @property {WebGLTexture} - webgl texture */
|
|
166
|
-
this.glTexture =
|
|
202
|
+
this.glTexture = glTexture;
|
|
167
203
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
171
|
-
|
|
172
|
-
/** Convert from screen to world space coordinates
|
|
173
|
-
* @param {Vector2} screenPos
|
|
174
|
-
* @return {Vector2}
|
|
175
|
-
* @memberof Draw */
|
|
176
|
-
function screenToWorld(screenPos)
|
|
177
|
-
{
|
|
178
|
-
return new Vector2
|
|
179
|
-
(
|
|
180
|
-
(screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale + cameraPos.x,
|
|
181
|
-
(screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale + cameraPos.y
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
204
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return new Vector2
|
|
192
|
-
(
|
|
193
|
-
(worldPos.x - cameraPos.x) * cameraScale + mainCanvasSize.x/2 - .5,
|
|
194
|
-
(worldPos.y - cameraPos.y) * -cameraScale + mainCanvasSize.y/2 - .5
|
|
195
|
-
);
|
|
205
|
+
createWebGLTexture()
|
|
206
|
+
{
|
|
207
|
+
ASSERT(!this.glTexture);
|
|
208
|
+
if (glEnable)
|
|
209
|
+
this.glTexture = glCreateTexture(this.image);
|
|
210
|
+
}
|
|
196
211
|
}
|
|
197
212
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
* @memberof Draw */
|
|
201
|
-
function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
213
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
214
|
+
// Drawing functions
|
|
202
215
|
|
|
203
216
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
204
217
|
* @param {Vector2} pos - Center of the tile in world space
|
|
@@ -212,16 +225,19 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
|
212
225
|
* @param {boolean} [screenSpace=false] - If true the pos and size are in screen space
|
|
213
226
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
214
227
|
* @memberof Draw */
|
|
215
|
-
function drawTile(pos, size=
|
|
228
|
+
function drawTile(pos, size=new Vector2(1), tileInfo, color=new Color,
|
|
216
229
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
217
230
|
{
|
|
218
231
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
219
|
-
ASSERT(
|
|
220
|
-
|
|
221
|
-
ASSERT(
|
|
222
|
-
ASSERT(
|
|
232
|
+
ASSERT(isVector2(pos) && pos.isValid(), 'drawTile pos should be a vec2');
|
|
233
|
+
ASSERT(isVector2(size) && size.isValid(), 'drawTile size should be a vec2');
|
|
234
|
+
ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
|
|
235
|
+
ASSERT(isNumber(angle), 'drawTile angle should be a number');
|
|
223
236
|
|
|
224
|
-
|
|
237
|
+
if (color.a <= 0 && (!additiveColor || additiveColor.a <= 0) || !size.x || !size.y)
|
|
238
|
+
return; // completely invisible, skip render
|
|
239
|
+
|
|
240
|
+
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
225
241
|
if (useWebGL)
|
|
226
242
|
{
|
|
227
243
|
if (screenSpace)
|
|
@@ -265,24 +281,21 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
265
281
|
{
|
|
266
282
|
// normal canvas 2D rendering method (slower)
|
|
267
283
|
showWatermark && ++drawCount;
|
|
268
|
-
size =
|
|
284
|
+
size = new Vector2(size.x, -size.y); // fix upside down sprites
|
|
269
285
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
270
286
|
{
|
|
271
287
|
if (textureInfo)
|
|
272
288
|
{
|
|
273
289
|
// calculate uvs and render
|
|
274
|
-
const x = tileInfo.pos.x
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
const h = tileInfo.size.y - 2*tileFixBleedScale;
|
|
278
|
-
context.globalAlpha = color.a; // only alpha is supported
|
|
279
|
-
context.drawImage(textureInfo.image, x, y, w, h, -.5, -.5, 1, 1);
|
|
280
|
-
context.globalAlpha = 1; // set back to full alpha
|
|
290
|
+
const x = tileInfo.pos.x, y = tileInfo.pos.y;
|
|
291
|
+
const w = tileInfo.size.x, h = tileInfo.size.y;
|
|
292
|
+
drawImageColor(context, textureInfo.image, x, y, w, h, -.5, -.5, 1, 1, color, additiveColor);
|
|
281
293
|
}
|
|
282
294
|
else
|
|
283
295
|
{
|
|
284
|
-
// if no tile info,
|
|
285
|
-
|
|
296
|
+
// if no tile info, use untextured rect
|
|
297
|
+
const c = additiveColor ? color.add(additiveColor) : color;
|
|
298
|
+
context.fillStyle = c.toString();
|
|
286
299
|
context.fillRect(-.5, -.5, 1, 1);
|
|
287
300
|
}
|
|
288
301
|
}, screenSpace, context);
|
|
@@ -295,7 +308,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
295
308
|
* @param {Color} [color=(1,1,1,1)]
|
|
296
309
|
* @param {number} [angle]
|
|
297
310
|
* @param {boolean} [useWebGL=glEnable]
|
|
298
|
-
* @param {boolean} [screenSpace
|
|
311
|
+
* @param {boolean} [screenSpace]
|
|
299
312
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
300
313
|
* @memberof Draw */
|
|
301
314
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
@@ -308,73 +321,90 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
308
321
|
* @param {Vector2} posB
|
|
309
322
|
* @param {number} [thickness]
|
|
310
323
|
* @param {Color} [color=(1,1,1,1)]
|
|
324
|
+
* @param {Vector2} [pos=(0,0)] - Offset to apply
|
|
325
|
+
* @param {number} [angle] - Angle to rotate by
|
|
311
326
|
* @param {boolean} [useWebGL=glEnable]
|
|
312
|
-
* @param {boolean} [screenSpace
|
|
327
|
+
* @param {boolean} [screenSpace]
|
|
313
328
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
314
329
|
* @memberof Draw */
|
|
315
|
-
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
330
|
+
function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL, screenSpace, context)
|
|
316
331
|
{
|
|
317
332
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
318
333
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
319
|
-
|
|
334
|
+
pos = pos.add(posA.add(halfDelta));
|
|
335
|
+
angle += halfDelta.angle();
|
|
336
|
+
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
320
337
|
}
|
|
321
338
|
|
|
322
339
|
/** Draw colored polygon using passed in points
|
|
323
|
-
* @param {Array<Vector2>}
|
|
340
|
+
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
324
341
|
* @param {Color} [color=(1,1,1,1)]
|
|
325
|
-
* @param {number} [lineWidth
|
|
342
|
+
* @param {number} [lineWidth]
|
|
326
343
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
327
|
-
* @param {
|
|
328
|
-
* @param {
|
|
344
|
+
* @param {Vector2} [pos=(0,0)] - Offset to apply
|
|
345
|
+
* @param {number} [angle] - Angle to rotate by
|
|
346
|
+
* @param {boolean} [useWebGL] - Webgl not supported
|
|
347
|
+
* @param {boolean} [screenSpace]
|
|
348
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
329
349
|
* @memberof Draw */
|
|
330
|
-
function drawPoly(points, color=new Color, lineWidth=0, lineColor=
|
|
350
|
+
function drawPoly(points, color=new Color, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=false, screenSpace=false, context=drawContext)
|
|
331
351
|
{
|
|
332
|
-
ASSERT(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (lineWidth)
|
|
352
|
+
ASSERT(isVector2(pos) && pos.isValid(), 'drawPoly pos should be a vec2');
|
|
353
|
+
ASSERT(Array.isArray(points), 'drawPoly points should be an array');
|
|
354
|
+
ASSERT(isColor(color) && isColor(lineColor), 'drawPoly color is invalid');
|
|
355
|
+
ASSERT(isNumber(lineWidth), 'drawPoly lineWidth should be a number');
|
|
356
|
+
ASSERT(isNumber(angle), 'drawPoly angle should be a number');
|
|
357
|
+
ASSERT(!useWebGL, 'drawPoly webgl not supported');
|
|
358
|
+
drawCanvas2D(pos, vec2(1), angle, false, context=>
|
|
340
359
|
{
|
|
341
|
-
context.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
360
|
+
context.beginPath();
|
|
361
|
+
for (const point of points)
|
|
362
|
+
context.lineTo(point.x, point.y);
|
|
363
|
+
context.closePath();
|
|
364
|
+
context.fillStyle = color.toString();
|
|
365
|
+
context.fill();
|
|
366
|
+
if (lineWidth)
|
|
367
|
+
{
|
|
368
|
+
context.strokeStyle = lineColor.toString();
|
|
369
|
+
context.lineWidth = lineWidth;
|
|
370
|
+
context.stroke();
|
|
371
|
+
}
|
|
372
|
+
}, screenSpace, context);
|
|
345
373
|
}
|
|
346
374
|
|
|
347
375
|
/** Draw colored ellipse using passed in point
|
|
348
376
|
* @param {Vector2} pos
|
|
349
|
-
* @param {
|
|
350
|
-
* @param {number} [height=1]
|
|
351
|
-
* @param {number} [angle=0]
|
|
377
|
+
* @param {Vector2} [size=(1,1)]
|
|
352
378
|
* @param {Color} [color=(1,1,1,1)]
|
|
353
|
-
* @param {number} [
|
|
379
|
+
* @param {number} [angle]
|
|
380
|
+
* @param {number} [lineWidth]
|
|
354
381
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
355
|
-
* @param {boolean} [
|
|
356
|
-
* @param {
|
|
382
|
+
* @param {boolean} [useWebGL] - Webgl not supported
|
|
383
|
+
* @param {boolean} [screenSpace]
|
|
384
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
357
385
|
* @memberof Draw */
|
|
358
|
-
function drawEllipse(pos,
|
|
386
|
+
function drawEllipse(pos, size=vec2(1), color=new Color, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=false, screenSpace=false, context=drawContext)
|
|
359
387
|
{
|
|
360
|
-
ASSERT(
|
|
361
|
-
|
|
388
|
+
ASSERT(isVector2(pos) && pos.isValid(), 'drawEllipse pos should be a vec2');
|
|
389
|
+
ASSERT(isVector2(size) && size.isValid(), 'drawEllipse size should be a vec2');
|
|
390
|
+
ASSERT(isColor(color) && isColor(lineColor), 'drawEllipse color is invalid');
|
|
391
|
+
ASSERT(isNumber(angle), 'drawEllipse angle should be a number');
|
|
392
|
+
ASSERT(isNumber(lineWidth), 'drawEllipse lineWidth should be a number');
|
|
393
|
+
ASSERT(lineWidth>=0 && lineWidth < size.x && lineWidth < size.y, 'drawEllipse invalid lineWidth');
|
|
394
|
+
ASSERT(!useWebGL, 'drawEllipse webgl not supported');
|
|
395
|
+
drawCanvas2D(pos, vec2(1), angle, false, context=>
|
|
362
396
|
{
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
context.strokeStyle = lineColor.toString();
|
|
375
|
-
context.lineWidth = lineWidth;
|
|
376
|
-
context.stroke();
|
|
377
|
-
}
|
|
397
|
+
context.beginPath();
|
|
398
|
+
context.ellipse(0, 0, size.y, size.x, 0, 0, 9);
|
|
399
|
+
context.fillStyle = color.toString();
|
|
400
|
+
context.fill();
|
|
401
|
+
if (lineWidth)
|
|
402
|
+
{
|
|
403
|
+
context.strokeStyle = lineColor.toString();
|
|
404
|
+
context.lineWidth = lineWidth;
|
|
405
|
+
context.stroke();
|
|
406
|
+
}
|
|
407
|
+
}, screenSpace, context);
|
|
378
408
|
}
|
|
379
409
|
|
|
380
410
|
/** Draw colored circle using passed in point
|
|
@@ -383,22 +413,23 @@ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth
|
|
|
383
413
|
* @param {Color} [color=(1,1,1,1)]
|
|
384
414
|
* @param {number} [lineWidth=0]
|
|
385
415
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
416
|
+
* @param {boolean} [useWebGL] - Webgl not supported
|
|
386
417
|
* @param {boolean} [screenSpace=false]
|
|
387
|
-
* @param {CanvasRenderingContext2D} [context=
|
|
418
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
388
419
|
* @memberof Draw */
|
|
389
|
-
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=
|
|
390
|
-
{ drawEllipse(pos, radius,
|
|
420
|
+
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=BLACK, useWebGL=false, screenSpace, context=drawContext)
|
|
421
|
+
{ drawEllipse(pos, vec2(radius), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context); }
|
|
391
422
|
|
|
392
423
|
/** Draw directly to a 2d canvas context in world space
|
|
393
424
|
* @param {Vector2} pos
|
|
394
425
|
* @param {Vector2} size
|
|
395
426
|
* @param {number} angle
|
|
396
|
-
* @param {boolean} mirror
|
|
397
|
-
* @param {Function} drawFunction
|
|
427
|
+
* @param {boolean} [mirror]
|
|
428
|
+
* @param {Function} [drawFunction]
|
|
398
429
|
* @param {boolean} [screenSpace=false]
|
|
399
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=
|
|
430
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
400
431
|
* @memberof Draw */
|
|
401
|
-
function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=
|
|
432
|
+
function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
|
|
402
433
|
{
|
|
403
434
|
if (!screenSpace)
|
|
404
435
|
{
|
|
@@ -408,12 +439,15 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
408
439
|
}
|
|
409
440
|
context.save();
|
|
410
441
|
context.translate(pos.x+.5, pos.y+.5);
|
|
411
|
-
context.rotate(angle);
|
|
442
|
+
context.rotate(angle-cameraAngle);
|
|
412
443
|
context.scale(mirror ? -size.x : size.x, -size.y);
|
|
413
444
|
drawFunction(context);
|
|
414
445
|
context.restore();
|
|
415
446
|
}
|
|
416
447
|
|
|
448
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
449
|
+
// Text Drawing Functions
|
|
450
|
+
|
|
417
451
|
/** Draw text on main canvas in world space
|
|
418
452
|
* Automatically splits new lines into rows
|
|
419
453
|
* @param {string} text
|
|
@@ -425,9 +459,9 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
425
459
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
426
460
|
* @param {string} [font=fontDefault]
|
|
427
461
|
* @param {number} [maxWidth]
|
|
428
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=
|
|
462
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
429
463
|
* @memberof Draw */
|
|
430
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth, context=
|
|
464
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth, context=drawContext)
|
|
431
465
|
{
|
|
432
466
|
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, maxWidth, context);
|
|
433
467
|
}
|
|
@@ -462,7 +496,7 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
462
496
|
* @param {number} [maxWidth]
|
|
463
497
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
464
498
|
* @memberof Draw */
|
|
465
|
-
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=
|
|
499
|
+
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth=undefined, context=overlayContext)
|
|
466
500
|
{
|
|
467
501
|
context.fillStyle = color.toString();
|
|
468
502
|
context.strokeStyle = lineColor.toString();
|
|
@@ -473,32 +507,77 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
|
|
|
473
507
|
context.lineJoin = 'round';
|
|
474
508
|
|
|
475
509
|
const lines = (text+'').split('\n');
|
|
476
|
-
|
|
477
|
-
|
|
510
|
+
let posY = pos.y;
|
|
511
|
+
posY -= (lines.length-1) * size/2; // center text vertically
|
|
478
512
|
lines.forEach(line=>
|
|
479
513
|
{
|
|
480
|
-
lineWidth && context.strokeText(line, pos.x,
|
|
481
|
-
context.fillText(line, pos.x,
|
|
482
|
-
|
|
514
|
+
lineWidth && context.strokeText(line, pos.x, posY, maxWidth);
|
|
515
|
+
context.fillText(line, pos.x, posY, maxWidth);
|
|
516
|
+
posY += size;
|
|
483
517
|
});
|
|
484
518
|
}
|
|
485
519
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
520
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
521
|
+
// Drawing utilities
|
|
522
|
+
|
|
523
|
+
/** Convert from screen to world space coordinates
|
|
524
|
+
* @param {Vector2} screenPos
|
|
525
|
+
* @return {Vector2}
|
|
490
526
|
* @memberof Draw */
|
|
491
|
-
function
|
|
527
|
+
function screenToWorld(screenPos)
|
|
492
528
|
{
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
529
|
+
let cameraPosRelativeX = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
530
|
+
let cameraPosRelativeY = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
531
|
+
if (cameraAngle)
|
|
532
|
+
{
|
|
533
|
+
// apply camera rotation
|
|
534
|
+
const cos = Math.cos(-cameraAngle), sin = Math.sin(-cameraAngle);
|
|
535
|
+
const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
|
|
536
|
+
const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
|
|
537
|
+
cameraPosRelativeX = rotatedX;
|
|
538
|
+
cameraPosRelativeY = rotatedY;
|
|
539
|
+
}
|
|
540
|
+
return new Vector2(cameraPosRelativeX + cameraPos.x, cameraPosRelativeY + cameraPos.y);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** Convert from world to screen space coordinates
|
|
544
|
+
* @param {Vector2} worldPos
|
|
545
|
+
* @return {Vector2}
|
|
546
|
+
* @memberof Draw */
|
|
547
|
+
function worldToScreen(worldPos)
|
|
548
|
+
{
|
|
549
|
+
let cameraPosRelativeX = worldPos.x - cameraPos.x;
|
|
550
|
+
let cameraPosRelativeY = worldPos.y - cameraPos.y;
|
|
551
|
+
if (cameraAngle)
|
|
497
552
|
{
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
553
|
+
// apply inverse camera rotation
|
|
554
|
+
const cos = Math.cos(cameraAngle), sin = Math.sin(cameraAngle);
|
|
555
|
+
const rotatedX = cameraPosRelativeX * cos - cameraPosRelativeY * sin;
|
|
556
|
+
const rotatedY = cameraPosRelativeX * sin + cameraPosRelativeY * cos;
|
|
557
|
+
cameraPosRelativeX = rotatedX;
|
|
558
|
+
cameraPosRelativeY = rotatedY;
|
|
501
559
|
}
|
|
560
|
+
return new Vector2
|
|
561
|
+
(
|
|
562
|
+
cameraPosRelativeX * cameraScale + mainCanvasSize.x/2 - .5,
|
|
563
|
+
cameraPosRelativeY * -cameraScale + mainCanvasSize.y/2 - .5
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** Get the camera's visible area in world space
|
|
568
|
+
* @return {Vector2}
|
|
569
|
+
* @memberof Draw */
|
|
570
|
+
function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
571
|
+
|
|
572
|
+
/** Enable normal or additive blend mode
|
|
573
|
+
* @param {boolean} [additive]
|
|
574
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
575
|
+
* @memberof Draw */
|
|
576
|
+
function setBlendMode(additive=false, context)
|
|
577
|
+
{
|
|
578
|
+
glAdditive = additive;
|
|
579
|
+
context ||= drawContext;
|
|
580
|
+
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
502
581
|
}
|
|
503
582
|
|
|
504
583
|
/** Combines all LittleJS canvases onto the main canvas and clears them
|
|
@@ -507,7 +586,7 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
507
586
|
function combineCanvases()
|
|
508
587
|
{
|
|
509
588
|
// combine canvases
|
|
510
|
-
glCopyToContext(mainContext
|
|
589
|
+
glCopyToContext(mainContext);
|
|
511
590
|
mainContext.drawImage(overlayCanvas, 0, 0);
|
|
512
591
|
|
|
513
592
|
// clear canvases
|
|
@@ -515,6 +594,102 @@ function combineCanvases()
|
|
|
515
594
|
overlayCanvas.width |= 0;
|
|
516
595
|
}
|
|
517
596
|
|
|
597
|
+
/** Helper function to draw an image with color and additive color applied
|
|
598
|
+
* This is slower then normal drawImage when color is applied
|
|
599
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
600
|
+
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
601
|
+
* @param {number} sx
|
|
602
|
+
* @param {number} sy
|
|
603
|
+
* @param {number} sWidth
|
|
604
|
+
* @param {number} sHeight
|
|
605
|
+
* @param {number} dx
|
|
606
|
+
* @param {number} dy
|
|
607
|
+
* @param {number} dWidth
|
|
608
|
+
* @param {number} dHeight
|
|
609
|
+
* @param {Color} color
|
|
610
|
+
* @param {Color} [additiveColor]
|
|
611
|
+
* @memberof Draw */
|
|
612
|
+
function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight, color, additiveColor)
|
|
613
|
+
{
|
|
614
|
+
function isWhite(c) { return c.r >= 1 && c.g >= 1 && c.b >= 1; }
|
|
615
|
+
function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
|
|
616
|
+
const sx2 = tileFixBleedScale;
|
|
617
|
+
const sy2 = tileFixBleedScale;
|
|
618
|
+
const sWidth2 = sWidth - 2*tileFixBleedScale;
|
|
619
|
+
const sHeight2 = sHeight - 2*tileFixBleedScale;
|
|
620
|
+
if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
|
|
621
|
+
{
|
|
622
|
+
// white texture with no additive alpha, no need to tint
|
|
623
|
+
context.globalAlpha = color.a;
|
|
624
|
+
context.drawImage(image, sx+sx2, sy+sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
625
|
+
context.globalAlpha = 1;
|
|
626
|
+
}
|
|
627
|
+
else
|
|
628
|
+
{
|
|
629
|
+
// copy to offscreen canvas
|
|
630
|
+
workCanvas.width = sWidth;
|
|
631
|
+
workCanvas.height = sHeight;
|
|
632
|
+
workContext.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
633
|
+
|
|
634
|
+
// tint image using offscreen work context
|
|
635
|
+
const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
|
|
636
|
+
const data = imageData.data;
|
|
637
|
+
if (additiveColor && !isBlack(additiveColor))
|
|
638
|
+
{
|
|
639
|
+
// slower path with additive color
|
|
640
|
+
const colorMultiply = [color.r, color.g, color.b, color.a];
|
|
641
|
+
const colorAdd = [additiveColor.r * 255, additiveColor.g * 255, additiveColor.b * 255, additiveColor.a * 255];
|
|
642
|
+
for (let i = 0; i < data.length; ++i)
|
|
643
|
+
data[i] = data[i] * colorMultiply[i&3] + colorAdd[i&3] |0;
|
|
644
|
+
workContext.putImageData(imageData, 0, 0);
|
|
645
|
+
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
646
|
+
}
|
|
647
|
+
else
|
|
648
|
+
{
|
|
649
|
+
// faster path with no additive color
|
|
650
|
+
for (let i = 0; i < data.length; i+=4)
|
|
651
|
+
{
|
|
652
|
+
data[i ] *= color.r;
|
|
653
|
+
data[i+1] *= color.g;
|
|
654
|
+
data[i+2] *= color.b;
|
|
655
|
+
}
|
|
656
|
+
workContext.putImageData(imageData, 0, 0);
|
|
657
|
+
context.globalAlpha = color.a;
|
|
658
|
+
context.drawImage(workCanvas, sx2, sy2, sWidth2, sHeight2, dx, dy, dWidth, dHeight);
|
|
659
|
+
context.globalAlpha = 1;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
/** Returns true if fullscreen mode is active
|
|
666
|
+
* @return {boolean}
|
|
667
|
+
* @memberof Draw */
|
|
668
|
+
function isFullscreen() { return !!document.fullscreenElement; }
|
|
669
|
+
|
|
670
|
+
/** Toggle fullscreen mode
|
|
671
|
+
* @memberof Draw */
|
|
672
|
+
function toggleFullscreen()
|
|
673
|
+
{
|
|
674
|
+
const rootElement = mainCanvas.parentElement;
|
|
675
|
+
if (isFullscreen())
|
|
676
|
+
{
|
|
677
|
+
if (document.exitFullscreen)
|
|
678
|
+
document.exitFullscreen();
|
|
679
|
+
}
|
|
680
|
+
else if (rootElement.requestFullscreen)
|
|
681
|
+
rootElement.requestFullscreen();
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/** Set the cursor style
|
|
685
|
+
* @param {string} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
|
|
686
|
+
* @memberof Draw */
|
|
687
|
+
function setCursor(cursorStyle = 'auto')
|
|
688
|
+
{
|
|
689
|
+
const rootElement = mainCanvas.parentElement;
|
|
690
|
+
rootElement.style.cursor = cursorStyle;
|
|
691
|
+
}
|
|
692
|
+
|
|
518
693
|
///////////////////////////////////////////////////////////////////////////////
|
|
519
694
|
|
|
520
695
|
let engineFontImage;
|
|
@@ -582,7 +757,7 @@ class FontImage
|
|
|
582
757
|
(text+'').split('\n').forEach((line, i)=>
|
|
583
758
|
{
|
|
584
759
|
const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
|
|
585
|
-
for(let j=line.length; j--;)
|
|
760
|
+
for (let j=line.length; j--;)
|
|
586
761
|
{
|
|
587
762
|
// draw each character
|
|
588
763
|
let charCode = line[j].charCodeAt(0);
|
|
@@ -601,35 +776,4 @@ class FontImage
|
|
|
601
776
|
|
|
602
777
|
context.restore();
|
|
603
778
|
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
607
|
-
// Display functions
|
|
608
|
-
|
|
609
|
-
/** Returns true if fullscreen mode is active
|
|
610
|
-
* @return {boolean}
|
|
611
|
-
* @memberof Draw */
|
|
612
|
-
function isFullscreen() { return !!document.fullscreenElement; }
|
|
613
|
-
|
|
614
|
-
/** Toggle fullscreen mode
|
|
615
|
-
* @memberof Draw */
|
|
616
|
-
function toggleFullscreen()
|
|
617
|
-
{
|
|
618
|
-
const rootElement = mainCanvas.parentElement;
|
|
619
|
-
if (isFullscreen())
|
|
620
|
-
{
|
|
621
|
-
if (document.exitFullscreen)
|
|
622
|
-
document.exitFullscreen();
|
|
623
|
-
}
|
|
624
|
-
else if (rootElement.requestFullscreen)
|
|
625
|
-
rootElement.requestFullscreen();
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/** Set the cursor style
|
|
629
|
-
* @param {string} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
|
|
630
|
-
* @memberof Draw */
|
|
631
|
-
function setCursor(cursorStyle = 'auto')
|
|
632
|
-
{
|
|
633
|
-
const rootElement = mainCanvas.parentElement;
|
|
634
|
-
rootElement.style.cursor = cursorStyle;
|
|
635
779
|
}
|