littlejsengine 1.14.16 → 1.14.23
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/dist/littlejs.d.ts +173 -50
- package/dist/littlejs.esm.js +892 -419
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +879 -419
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +854 -420
- package/examples/box2d/game.js +3 -2
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +5 -5
- package/examples/electron/game.js +1 -41
- package/examples/electron/index.html +2 -2
- package/examples/htmlMenu/game.js +1 -1
- package/examples/index.html +69 -55
- package/examples/module/game.js +6 -8
- package/examples/particles/index.html +6 -9
- package/examples/platformer/gameCharacter.js +1 -1
- package/examples/platformer/gameEffects.js +8 -3
- package/examples/platformer/gameObjects.js +3 -3
- package/examples/shorts/animation.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/cameraDrag.js +22 -0
- package/examples/shorts/debugDraw.js +38 -0
- package/examples/shorts/fps.js +90 -0
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/hillGlideGame.js +1 -1
- package/examples/shorts/landerGame.js +1 -1
- package/examples/shorts/musicPlayer.js +1 -0
- package/examples/shorts/parallax.js +1 -1
- package/examples/shorts/postProcess.js +1 -1
- package/examples/shorts/sequencer.js +5 -5
- package/examples/shorts/shader.js +29 -0
- package/examples/shorts/spaceGame.js +1 -1
- package/examples/shorts/spriteAtlas.js +6 -6
- package/examples/shorts/starfield.js +1 -1
- package/examples/shorts/tileLayer.js +1 -1
- package/examples/shorts/tileRaycast.js +39 -0
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +14 -6
- package/examples/shorts/topDown.js +1 -1
- package/examples/starter/game.js +6 -8
- package/examples/starter/index.html +2 -2
- package/examples/style.css +1 -0
- package/examples/typescript/game.js +5 -4
- package/examples/typescript/game.ts +5 -7
- package/examples/uiSystem/game.js +3 -2
- package/package.json +1 -1
- package/plugins/postProcess.js +71 -51
- package/plugins/uiSystem.js +76 -23
- package/src/engine.js +30 -16
- package/src/engineAudio.js +33 -23
- package/src/engineDebug.js +27 -0
- package/src/engineDraw.js +103 -49
- package/src/engineExport.js +13 -0
- package/src/engineInput.js +14 -16
- package/src/engineObject.js +31 -6
- package/src/engineParticles.js +37 -16
- package/src/engineRelease.js +2 -1
- package/src/engineSettings.js +20 -1
- package/src/engineTileLayer.js +89 -94
- package/src/engineUtilities.js +206 -58
- package/src/engineWebGL.js +145 -69
- package/examples/shorts/raycasting.js +0 -79
package/dist/littlejs.esm.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.14.
|
|
36
|
+
const engineVersion = '1.14.24';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -94,7 +94,17 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
|
94
94
|
///////////////////////////////////////////////////////////////////////////////
|
|
95
95
|
// plugin hooks
|
|
96
96
|
|
|
97
|
-
const
|
|
97
|
+
const pluginList = [];
|
|
98
|
+
class EnginePlugin
|
|
99
|
+
{
|
|
100
|
+
constructor(update, render, glContextLost, glContextRestored)
|
|
101
|
+
{
|
|
102
|
+
this.update = update;
|
|
103
|
+
this.render = render;
|
|
104
|
+
this.glContextLost = glContextLost;
|
|
105
|
+
this.glContextRestored = glContextRestored;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
98
108
|
|
|
99
109
|
/**
|
|
100
110
|
* @callback PluginCallback - Update or render function for a plugin
|
|
@@ -102,15 +112,21 @@ const pluginUpdateList = [], pluginRenderList = [];
|
|
|
102
112
|
*/
|
|
103
113
|
|
|
104
114
|
/** Add a new update function for a plugin
|
|
105
|
-
* @param {PluginCallback} [
|
|
106
|
-
* @param {PluginCallback} [
|
|
115
|
+
* @param {PluginCallback} [update]
|
|
116
|
+
* @param {PluginCallback} [render]
|
|
117
|
+
* @param {PluginCallback} [glContextLost]
|
|
118
|
+
* @param {PluginCallback} [glContextRestored]
|
|
107
119
|
* @memberof Engine */
|
|
108
|
-
function engineAddPlugin(
|
|
120
|
+
function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
109
121
|
{
|
|
110
|
-
|
|
111
|
-
ASSERT(!
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
// make sure plugin functions are unique
|
|
123
|
+
ASSERT(!pluginList.find(p=>
|
|
124
|
+
p.update === update && p.render === render &&
|
|
125
|
+
p.glContextLost === glContextLost &&
|
|
126
|
+
p.glContextRestored === glContextRestored));
|
|
127
|
+
|
|
128
|
+
const plugin = new EnginePlugin(update, render, glContextLost, glContextRestored);
|
|
129
|
+
pluginList.push(plugin);
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -195,7 +211,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
195
211
|
wasUpdated = true;
|
|
196
212
|
updateCanvas();
|
|
197
213
|
inputUpdate();
|
|
198
|
-
|
|
214
|
+
pluginList.forEach(plugin=>plugin.update?.());
|
|
199
215
|
|
|
200
216
|
// update object transforms even when paused
|
|
201
217
|
for (const o of engineObjects)
|
|
@@ -228,7 +244,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
228
244
|
updateCanvas();
|
|
229
245
|
inputUpdate();
|
|
230
246
|
gameUpdate();
|
|
231
|
-
|
|
247
|
+
pluginList.forEach(plugin=>plugin.update?.());
|
|
232
248
|
engineObjectsUpdate();
|
|
233
249
|
|
|
234
250
|
// do post update
|
|
@@ -262,7 +278,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
262
278
|
for (const o of engineObjects)
|
|
263
279
|
o.destroyed || o.render();
|
|
264
280
|
gameRenderPost();
|
|
265
|
-
|
|
281
|
+
pluginList.forEach(plugin=>plugin.render?.());
|
|
266
282
|
touchGamepadRender();
|
|
267
283
|
debugRender();
|
|
268
284
|
glFlush();
|
|
@@ -389,7 +405,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
389
405
|
image.onerror = image.onload = ()=>
|
|
390
406
|
{
|
|
391
407
|
const textureInfo = new TextureInfo(image);
|
|
392
|
-
textureInfo.createWebGLTexture();
|
|
393
408
|
textureInfos[textureIndex] = textureInfo;
|
|
394
409
|
resolve();
|
|
395
410
|
}
|
|
@@ -405,7 +420,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
405
420
|
{
|
|
406
421
|
const textureInfo = new TextureInfo(new Image);
|
|
407
422
|
textureInfos[0] = textureInfo;
|
|
408
|
-
textureInfo.createWebGLTexture();
|
|
409
423
|
resolve();
|
|
410
424
|
}));
|
|
411
425
|
}
|
|
@@ -502,7 +516,7 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
502
516
|
|
|
503
517
|
/**
|
|
504
518
|
* @callback ObjectCallbackFunction - Function that processes an object
|
|
505
|
-
* @param {EngineObject}
|
|
519
|
+
* @param {EngineObject} object
|
|
506
520
|
* @memberof Engine
|
|
507
521
|
*/
|
|
508
522
|
|
|
@@ -550,7 +564,7 @@ function drawEngineSplashScreen(t)
|
|
|
550
564
|
// background
|
|
551
565
|
const p3 = percent(t, 1, .8);
|
|
552
566
|
const p4 = percent(t, 0, .5);
|
|
553
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,
|
|
567
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
554
568
|
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
555
569
|
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
556
570
|
x.save();
|
|
@@ -1325,6 +1339,33 @@ function debugVideoCaptureUpdate()
|
|
|
1325
1339
|
combineCanvases();
|
|
1326
1340
|
debugVideoCaptureTrack.requestFrame();
|
|
1327
1341
|
debugVideoCaptureIcon.textContent = '● REC ' + formatTime(debugVideoCaptureTimer);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// make color constants immutable with debug assertions
|
|
1345
|
+
function debugProtectConstant(obj)
|
|
1346
|
+
{
|
|
1347
|
+
if (debug)
|
|
1348
|
+
{
|
|
1349
|
+
// get properties and store original values
|
|
1350
|
+
const props = Object.keys(obj), values = {};
|
|
1351
|
+
props.forEach(prop => values[prop] = obj[prop]);
|
|
1352
|
+
|
|
1353
|
+
// replace with getters/setters that assert
|
|
1354
|
+
props.forEach(prop =>
|
|
1355
|
+
{
|
|
1356
|
+
Object.defineProperty(obj, prop, {
|
|
1357
|
+
get: () => values[prop],
|
|
1358
|
+
set: (value) =>
|
|
1359
|
+
{
|
|
1360
|
+
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
1361
|
+
},
|
|
1362
|
+
enumerable: true
|
|
1363
|
+
});
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// freeze the object to prevent adding new properties
|
|
1368
|
+
return Object.freeze(obj);
|
|
1328
1369
|
}
|
|
1329
1370
|
/**
|
|
1330
1371
|
* LittleJS Utility Classes and Functions
|
|
@@ -1336,7 +1377,7 @@ function debugVideoCaptureUpdate()
|
|
|
1336
1377
|
* @namespace Utilities
|
|
1337
1378
|
*/
|
|
1338
1379
|
|
|
1339
|
-
/**
|
|
1380
|
+
/** The value of PI
|
|
1340
1381
|
* @type {number}
|
|
1341
1382
|
* @default Math.PI
|
|
1342
1383
|
* @memberof Utilities */
|
|
@@ -1346,25 +1387,80 @@ const PI = Math.PI;
|
|
|
1346
1387
|
* @param {number} value
|
|
1347
1388
|
* @return {number}
|
|
1348
1389
|
* @memberof Utilities */
|
|
1349
|
-
|
|
1390
|
+
const abs = Math.abs;
|
|
1391
|
+
|
|
1392
|
+
/** Returns floored value of value passed in
|
|
1393
|
+
* @param {number} value
|
|
1394
|
+
* @return {number}
|
|
1395
|
+
* @memberof Utilities */
|
|
1396
|
+
const floor = Math.floor;
|
|
1397
|
+
|
|
1398
|
+
/** Returns ceiled value of value passed in
|
|
1399
|
+
* @param {number} value
|
|
1400
|
+
* @return {number}
|
|
1401
|
+
* @memberof Utilities */
|
|
1402
|
+
const ceil = Math.ceil;
|
|
1403
|
+
|
|
1404
|
+
/** Returns rounded value passed in
|
|
1405
|
+
* @param {number} value
|
|
1406
|
+
* @return {number}
|
|
1407
|
+
* @memberof Utilities */
|
|
1408
|
+
const round = Math.round;
|
|
1350
1409
|
|
|
1351
1410
|
/** Returns lowest value passed in
|
|
1352
1411
|
* @param {...number} values
|
|
1353
1412
|
* @return {number}
|
|
1354
1413
|
* @memberof Utilities */
|
|
1355
|
-
|
|
1414
|
+
const min = Math.min;
|
|
1356
1415
|
|
|
1357
1416
|
/** Returns highest value passed in
|
|
1358
1417
|
* @param {...number} values
|
|
1359
1418
|
* @return {number}
|
|
1360
1419
|
* @memberof Utilities */
|
|
1361
|
-
|
|
1420
|
+
const max = Math.max;
|
|
1362
1421
|
|
|
1363
1422
|
/** Returns the sign of value passed in
|
|
1364
1423
|
* @param {number} value
|
|
1365
1424
|
* @return {number}
|
|
1366
1425
|
* @memberof Utilities */
|
|
1367
|
-
|
|
1426
|
+
const sign = Math.sign;
|
|
1427
|
+
|
|
1428
|
+
/** Returns hypotenuse of values passed in
|
|
1429
|
+
* @param {...number} values
|
|
1430
|
+
* @return {number}
|
|
1431
|
+
* @memberof Utilities */
|
|
1432
|
+
const hypot = Math.hypot;
|
|
1433
|
+
|
|
1434
|
+
/** Returns log2 of value passed in
|
|
1435
|
+
* @param {number} value
|
|
1436
|
+
* @return {number}
|
|
1437
|
+
* @memberof Utilities */
|
|
1438
|
+
const log2 = Math.log2;
|
|
1439
|
+
|
|
1440
|
+
/** Returns sin of value passed in
|
|
1441
|
+
* @param {number} value
|
|
1442
|
+
* @return {number}
|
|
1443
|
+
* @memberof Utilities */
|
|
1444
|
+
const sin = Math.sin;
|
|
1445
|
+
|
|
1446
|
+
/** Returns cos of value passed in
|
|
1447
|
+
* @param {number} value
|
|
1448
|
+
* @return {number}
|
|
1449
|
+
* @memberof Utilities */
|
|
1450
|
+
const cos = Math.cos;
|
|
1451
|
+
|
|
1452
|
+
/** Returns tan of value passed in
|
|
1453
|
+
* @param {number} value
|
|
1454
|
+
* @return {number}
|
|
1455
|
+
* @memberof Utilities */
|
|
1456
|
+
const tan = Math.tan;
|
|
1457
|
+
|
|
1458
|
+
/** Returns atan2 of values passed in
|
|
1459
|
+
* @param {number} y
|
|
1460
|
+
* @param {number} x
|
|
1461
|
+
* @return {number}
|
|
1462
|
+
* @memberof Utilities */
|
|
1463
|
+
const atan2 = Math.atan2;
|
|
1368
1464
|
|
|
1369
1465
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
1370
1466
|
* @param {number} dividend
|
|
@@ -1469,7 +1565,7 @@ function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
|
1469
1565
|
* @param {number} value
|
|
1470
1566
|
* @return {number}
|
|
1471
1567
|
* @memberof Utilities */
|
|
1472
|
-
function nearestPowerOfTwo(value) { return 2**
|
|
1568
|
+
function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
1473
1569
|
|
|
1474
1570
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
1475
1571
|
* this can be used for simple collision detection between objects
|
|
@@ -1537,7 +1633,7 @@ function isIntersecting(start, end, pos, size)
|
|
|
1537
1633
|
* @return {number} - Value waving between 0 and amplitude
|
|
1538
1634
|
* @memberof Utilities */
|
|
1539
1635
|
function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
1540
|
-
{ return amplitude/2 * (1 -
|
|
1636
|
+
{ return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
|
|
1541
1637
|
|
|
1542
1638
|
/** Formats seconds to mm:ss style for display purposes
|
|
1543
1639
|
* @param {number} t - time in seconds
|
|
@@ -1576,6 +1672,96 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
|
1576
1672
|
* @memberof Utilities */
|
|
1577
1673
|
function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
|
|
1578
1674
|
|
|
1675
|
+
/**
|
|
1676
|
+
* @callback LineTestFunction - Checks if a position is colliding
|
|
1677
|
+
* @param {Vector2} pos
|
|
1678
|
+
* @memberof Draw
|
|
1679
|
+
*/
|
|
1680
|
+
|
|
1681
|
+
/**
|
|
1682
|
+
* Casts a ray and returns position of the first collision found, or undefined if none are found
|
|
1683
|
+
* @param {Vector2} posStart
|
|
1684
|
+
* @param {Vector2} posEnd
|
|
1685
|
+
* @param {LineTestFunction} testFunction - Check if colliding
|
|
1686
|
+
* @param {Vector2} [normal] - Optional vector to store the normal
|
|
1687
|
+
* @return {Vector2|undefined} - Position of the collision or undefined if none found
|
|
1688
|
+
* @memberof Utilities */
|
|
1689
|
+
function lineTest(posStart, posEnd, testFunction, normal)
|
|
1690
|
+
{
|
|
1691
|
+
ASSERT(isVector2(posStart), 'posStart must be a vec2');
|
|
1692
|
+
ASSERT(isVector2(posEnd), 'posEnd must be a vec2');
|
|
1693
|
+
ASSERT(typeof testFunction === 'function', 'testFunction must be a function');
|
|
1694
|
+
ASSERT(!normal || isVector2(normal), 'normal must be a vec2');
|
|
1695
|
+
|
|
1696
|
+
// get ray direction and length
|
|
1697
|
+
const dx = posEnd.x - posStart.x;
|
|
1698
|
+
const dy = posEnd.y - posStart.y;
|
|
1699
|
+
const totalLength = hypot(dx, dy);
|
|
1700
|
+
if (!totalLength)
|
|
1701
|
+
return;
|
|
1702
|
+
|
|
1703
|
+
// current integer cell we are in
|
|
1704
|
+
const pos = posStart.floor();
|
|
1705
|
+
|
|
1706
|
+
// normalize ray direction
|
|
1707
|
+
const dirX = dx / totalLength;
|
|
1708
|
+
const dirY = dy / totalLength;
|
|
1709
|
+
|
|
1710
|
+
// step direction in grid
|
|
1711
|
+
const stepX = sign(dirX);
|
|
1712
|
+
const stepY = sign(dirY);
|
|
1713
|
+
|
|
1714
|
+
// distance along the ray to cross one full cell in X or Y
|
|
1715
|
+
const tDeltaX = dirX ? abs(1 / dirX) : Infinity;
|
|
1716
|
+
const tDeltaY = dirY ? abs(1 / dirY) : Infinity;
|
|
1717
|
+
|
|
1718
|
+
// distance along the ray from start to the first grid boundary
|
|
1719
|
+
const nextGridX = stepX > 0 ? pos.x + 1 : pos.x;
|
|
1720
|
+
const nextGridY = stepY > 0 ? pos.y + 1 : pos.y;
|
|
1721
|
+
const tMaxX = dirX ? (nextGridX - posStart.x) / dirX : Infinity;
|
|
1722
|
+
const tMaxY = dirY ? (nextGridY - posStart.y) / dirY : Infinity;
|
|
1723
|
+
|
|
1724
|
+
// use line drawing algorithm to test for collisions
|
|
1725
|
+
let t = 0, tX = tMaxX, tY = tMaxY, wasX = tDeltaX < tDeltaY;
|
|
1726
|
+
while (t < totalLength)
|
|
1727
|
+
{
|
|
1728
|
+
if (testFunction(pos))
|
|
1729
|
+
{
|
|
1730
|
+
// set hit point
|
|
1731
|
+
const hitPos = vec2(posStart.x + dirX*t, posStart.y + dirY*t);
|
|
1732
|
+
|
|
1733
|
+
// move inside of tile if on positive edge
|
|
1734
|
+
const e = 1e-9;
|
|
1735
|
+
if (wasX)
|
|
1736
|
+
{
|
|
1737
|
+
if (stepX < 0)
|
|
1738
|
+
hitPos.x -= e;
|
|
1739
|
+
}
|
|
1740
|
+
if (stepY < 0)
|
|
1741
|
+
hitPos.y -= e;
|
|
1742
|
+
|
|
1743
|
+
// set normal
|
|
1744
|
+
if (normal)
|
|
1745
|
+
wasX ? normal.set(-stepX,0) : normal.set(0,-stepY);
|
|
1746
|
+
return hitPos;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// advance to the next grid boundary
|
|
1750
|
+
if (wasX = tX < tY)
|
|
1751
|
+
{
|
|
1752
|
+
pos.x += stepX;
|
|
1753
|
+
t = tX;
|
|
1754
|
+
tX += tDeltaX;
|
|
1755
|
+
}
|
|
1756
|
+
else
|
|
1757
|
+
{
|
|
1758
|
+
pos.y += stepY;
|
|
1759
|
+
t = tY;
|
|
1760
|
+
tY += tDeltaY;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1579
1765
|
///////////////////////////////////////////////////////////////////////////////
|
|
1580
1766
|
|
|
1581
1767
|
/** Random global functions
|
|
@@ -1594,7 +1780,7 @@ function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valu
|
|
|
1594
1780
|
* @param {number} [valueB]
|
|
1595
1781
|
* @return {number}
|
|
1596
1782
|
* @memberof Random */
|
|
1597
|
-
function randInt(valueA, valueB=0) { return
|
|
1783
|
+
function randInt(valueA, valueB=0) { return floor(rand(valueA,valueB)); }
|
|
1598
1784
|
|
|
1599
1785
|
/** Randomly returns true or false given the chance of true passed in
|
|
1600
1786
|
* @param {number} [chance]
|
|
@@ -1673,7 +1859,7 @@ class RandomGenerator
|
|
|
1673
1859
|
* @param {number} valueA
|
|
1674
1860
|
* @param {number} [valueB]
|
|
1675
1861
|
* @return {number} */
|
|
1676
|
-
int(valueA, valueB=0) { return
|
|
1862
|
+
int(valueA, valueB=0) { return floor(this.float(valueA, valueB)); }
|
|
1677
1863
|
|
|
1678
1864
|
/** Randomly returns true or false given the chance of true passed in
|
|
1679
1865
|
* @param {number} [chance]
|
|
@@ -1700,6 +1886,39 @@ class RandomGenerator
|
|
|
1700
1886
|
* @return {Vector2} */
|
|
1701
1887
|
vec2(valueA=1, valueB=0)
|
|
1702
1888
|
{ return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
|
|
1889
|
+
|
|
1890
|
+
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1891
|
+
* @param {Color} [colorA=(1,1,1,1)]
|
|
1892
|
+
* @param {Color} [colorB=(0,0,0,1)]
|
|
1893
|
+
* @param {boolean} [linear]
|
|
1894
|
+
* @return {Color} */
|
|
1895
|
+
randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
1896
|
+
{
|
|
1897
|
+
return linear ? colorA.lerp(colorB, this.float()) :
|
|
1898
|
+
new Color(
|
|
1899
|
+
this.float(colorA.r,colorB.r),
|
|
1900
|
+
this.float(colorA.g,colorB.g),
|
|
1901
|
+
this.float(colorA.b,colorB.b),
|
|
1902
|
+
this.float(colorA.a,colorB.a));
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
/** Returns a new color that has each component randomly adjusted
|
|
1906
|
+
* @param {Color} color
|
|
1907
|
+
* @param {number} [amount]
|
|
1908
|
+
* @param {number} [alphaAmount]
|
|
1909
|
+
* @return {Color} */
|
|
1910
|
+
mutateColor(color, amount=.05, alphaAmount=0)
|
|
1911
|
+
{
|
|
1912
|
+
ASSERT_NUMBER_VALID(amount);
|
|
1913
|
+
ASSERT_NUMBER_VALID(alphaAmount);
|
|
1914
|
+
return new Color
|
|
1915
|
+
(
|
|
1916
|
+
color.r + this.float(amount, -amount),
|
|
1917
|
+
color.g + this.float(amount, -amount),
|
|
1918
|
+
color.b + this.float(amount, -amount),
|
|
1919
|
+
color.a + this.float(alphaAmount, -alphaAmount)
|
|
1920
|
+
).clamp();
|
|
1921
|
+
}
|
|
1703
1922
|
}
|
|
1704
1923
|
|
|
1705
1924
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1851,7 +2070,7 @@ class Vector2
|
|
|
1851
2070
|
|
|
1852
2071
|
/** Returns the clockwise angle of this vector, up is angle 0
|
|
1853
2072
|
* @return {number} */
|
|
1854
|
-
angle() { return
|
|
2073
|
+
angle() { return atan2(this.x, this.y); }
|
|
1855
2074
|
|
|
1856
2075
|
/** Sets this vector with clockwise angle and length passed in
|
|
1857
2076
|
* @param {number} [angle]
|
|
@@ -1861,8 +2080,8 @@ class Vector2
|
|
|
1861
2080
|
{
|
|
1862
2081
|
ASSERT_NUMBER_VALID(angle);
|
|
1863
2082
|
ASSERT_NUMBER_VALID(length);
|
|
1864
|
-
this.x = length*
|
|
1865
|
-
this.y = length*
|
|
2083
|
+
this.x = length*sin(angle);
|
|
2084
|
+
this.y = length*cos(angle);
|
|
1866
2085
|
return this;
|
|
1867
2086
|
}
|
|
1868
2087
|
|
|
@@ -1872,7 +2091,7 @@ class Vector2
|
|
|
1872
2091
|
rotate(angle)
|
|
1873
2092
|
{
|
|
1874
2093
|
ASSERT_NUMBER_VALID(angle);
|
|
1875
|
-
const c =
|
|
2094
|
+
const c = cos(-angle), s = sin(-angle);
|
|
1876
2095
|
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
1877
2096
|
}
|
|
1878
2097
|
|
|
@@ -1904,7 +2123,7 @@ class Vector2
|
|
|
1904
2123
|
|
|
1905
2124
|
/** Returns a copy of this vector with each axis floored
|
|
1906
2125
|
* @return {Vector2} */
|
|
1907
|
-
floor() { return new Vector2(
|
|
2126
|
+
floor() { return new Vector2(floor(this.x), floor(this.y)); }
|
|
1908
2127
|
|
|
1909
2128
|
/** Returns new vec2 with modded values
|
|
1910
2129
|
* @param {number} [divisor]
|
|
@@ -2212,67 +2431,67 @@ class Color
|
|
|
2212
2431
|
/** Color - White #ffffff
|
|
2213
2432
|
* @type {Color}
|
|
2214
2433
|
* @memberof Utilities */
|
|
2215
|
-
const WHITE =
|
|
2434
|
+
const WHITE = debugProtectConstant(rgb());
|
|
2216
2435
|
|
|
2217
2436
|
/** Color - Clear White #757474ff with 0 alpha
|
|
2218
2437
|
* @type {Color}
|
|
2219
2438
|
* @memberof Utilities */
|
|
2220
|
-
const CLEAR_WHITE =
|
|
2439
|
+
const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
2221
2440
|
|
|
2222
2441
|
/** Color - Black #000000
|
|
2223
2442
|
* @type {Color}
|
|
2224
2443
|
* @memberof Utilities */
|
|
2225
|
-
const BLACK =
|
|
2444
|
+
const BLACK = debugProtectConstant(rgb(0,0,0));
|
|
2226
2445
|
|
|
2227
2446
|
/** Color - Clear Black #000000 with 0 alpha
|
|
2228
2447
|
* @type {Color}
|
|
2229
2448
|
* @memberof Utilities */
|
|
2230
|
-
const CLEAR_BLACK =
|
|
2449
|
+
const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
|
|
2231
2450
|
|
|
2232
2451
|
/** Color - Gray #808080
|
|
2233
2452
|
* @type {Color}
|
|
2234
2453
|
* @memberof Utilities */
|
|
2235
|
-
const GRAY =
|
|
2454
|
+
const GRAY = debugProtectConstant(rgb(.5,.5,.5));
|
|
2236
2455
|
|
|
2237
2456
|
/** Color - Red #ff0000
|
|
2238
2457
|
* @type {Color}
|
|
2239
2458
|
* @memberof Utilities */
|
|
2240
|
-
const RED =
|
|
2459
|
+
const RED = debugProtectConstant(rgb(1,0,0));
|
|
2241
2460
|
|
|
2242
2461
|
/** Color - Orange #ff8000
|
|
2243
2462
|
* @type {Color}
|
|
2244
2463
|
* @memberof Utilities */
|
|
2245
|
-
const ORANGE =
|
|
2464
|
+
const ORANGE = debugProtectConstant(rgb(1,.5,0));
|
|
2246
2465
|
|
|
2247
2466
|
/** Color - Yellow #ffff00
|
|
2248
2467
|
* @type {Color}
|
|
2249
2468
|
* @memberof Utilities */
|
|
2250
|
-
const YELLOW =
|
|
2469
|
+
const YELLOW = debugProtectConstant(rgb(1,1,0));
|
|
2251
2470
|
|
|
2252
2471
|
/** Color - Green #00ff00
|
|
2253
2472
|
* @type {Color}
|
|
2254
2473
|
* @memberof Utilities */
|
|
2255
|
-
const GREEN =
|
|
2474
|
+
const GREEN = debugProtectConstant(rgb(0,1,0));
|
|
2256
2475
|
|
|
2257
2476
|
/** Color - Cyan #00ffff
|
|
2258
2477
|
* @type {Color}
|
|
2259
2478
|
* @memberof Utilities */
|
|
2260
|
-
const CYAN =
|
|
2479
|
+
const CYAN = debugProtectConstant(rgb(0,1,1));
|
|
2261
2480
|
|
|
2262
2481
|
/** Color - Blue #0000ff
|
|
2263
2482
|
* @type {Color}
|
|
2264
2483
|
* @memberof Utilities */
|
|
2265
|
-
const BLUE =
|
|
2484
|
+
const BLUE = debugProtectConstant(rgb(0,0,1));
|
|
2266
2485
|
|
|
2267
2486
|
/** Color - Purple #8000ff
|
|
2268
2487
|
* @type {Color}
|
|
2269
2488
|
* @memberof Utilities */
|
|
2270
|
-
const PURPLE =
|
|
2489
|
+
const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
2271
2490
|
|
|
2272
2491
|
/** Color - Magenta #ff00ff
|
|
2273
2492
|
* @type {Color}
|
|
2274
2493
|
* @memberof Utilities */
|
|
2275
|
-
const MAGENTA =
|
|
2494
|
+
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
2276
2495
|
|
|
2277
2496
|
///////////////////////////////////////////////////////////////////////////////
|
|
2278
2497
|
|
|
@@ -2335,41 +2554,11 @@ class Timer
|
|
|
2335
2554
|
|
|
2336
2555
|
/** Returns this timer expressed as a string
|
|
2337
2556
|
* @return {string} */
|
|
2338
|
-
toString() { return this.isSet() ?
|
|
2557
|
+
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
2339
2558
|
|
|
2340
2559
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2341
2560
|
* @return {number} */
|
|
2342
2561
|
valueOf() { return this.get(); }
|
|
2343
|
-
}
|
|
2344
|
-
|
|
2345
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2346
|
-
// Helper functions used by the engine
|
|
2347
|
-
|
|
2348
|
-
// make color constants immutable with debug assertions
|
|
2349
|
-
function protectEngineConstant(obj)
|
|
2350
|
-
{
|
|
2351
|
-
if (debug)
|
|
2352
|
-
{
|
|
2353
|
-
// get properties and store original values
|
|
2354
|
-
const props = Object.keys(obj), values = {};
|
|
2355
|
-
props.forEach(prop => values[prop] = obj[prop]);
|
|
2356
|
-
|
|
2357
|
-
// replace with getters/setters that assert
|
|
2358
|
-
props.forEach(prop =>
|
|
2359
|
-
{
|
|
2360
|
-
Object.defineProperty(obj, prop, {
|
|
2361
|
-
get: () => values[prop],
|
|
2362
|
-
set: (value) =>
|
|
2363
|
-
{
|
|
2364
|
-
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
2365
|
-
},
|
|
2366
|
-
enumerable: true
|
|
2367
|
-
});
|
|
2368
|
-
});
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
// freeze the object to prevent adding new properties
|
|
2372
|
-
return Object.freeze(obj);
|
|
2373
2562
|
}
|
|
2374
2563
|
/**
|
|
2375
2564
|
* LittleJS Engine Settings
|
|
@@ -2587,6 +2776,14 @@ let touchInputEnable = true;
|
|
|
2587
2776
|
* @memberof Settings */
|
|
2588
2777
|
let touchGamepadEnable = false;
|
|
2589
2778
|
|
|
2779
|
+
/** True if touch gamepad should have start button in the center
|
|
2780
|
+
* - When the game is paused, any touch will press the button
|
|
2781
|
+
* - This can function as a way to pause/unpause the game
|
|
2782
|
+
* @type {boolean}
|
|
2783
|
+
* @default
|
|
2784
|
+
* @memberof Settings */
|
|
2785
|
+
let touchGamepadCenterButton = true;
|
|
2786
|
+
|
|
2590
2787
|
/** True if touch gamepad should be analog stick or false to use if 8 way dpad
|
|
2591
2788
|
* @type {boolean}
|
|
2592
2789
|
* @default
|
|
@@ -2762,9 +2959,14 @@ function setHeadlessMode(headless) { headlessMode = headless; }
|
|
|
2762
2959
|
* @memberof Settings */
|
|
2763
2960
|
function setGLEnable(enable)
|
|
2764
2961
|
{
|
|
2962
|
+
if (enable && !glCanBeEnabled)
|
|
2963
|
+
{
|
|
2964
|
+
console.warn('Can not enable WebGL if it was disabled on start.');
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2765
2967
|
glEnable = enable;
|
|
2766
2968
|
if (glCanvas) // hide glCanvas if WebGL is disabled
|
|
2767
|
-
glCanvas.style.
|
|
2969
|
+
glCanvas.style.display = enable ? '' : 'none';
|
|
2768
2970
|
}
|
|
2769
2971
|
|
|
2770
2972
|
/** Set how many sided polygons to use when drawing circles and ellipses with WebGL
|
|
@@ -2852,6 +3054,12 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
|
|
|
2852
3054
|
* @memberof Settings */
|
|
2853
3055
|
function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
|
|
2854
3056
|
|
|
3057
|
+
/** True if touch gamepad should have start button in the center
|
|
3058
|
+
* - This can function as a way to pause/unpause the game
|
|
3059
|
+
* @param {boolean} enable
|
|
3060
|
+
* @memberof Settings */
|
|
3061
|
+
function setTouchGamepadCenterButton(enable) { touchGamepadCenterButton = enable; }
|
|
3062
|
+
|
|
2855
3063
|
/** Set if touch gamepad should be analog stick or 8 way dpad
|
|
2856
3064
|
* @param {boolean} analog
|
|
2857
3065
|
* @memberof Settings */
|
|
@@ -3213,6 +3421,27 @@ class EngineObject
|
|
|
3213
3421
|
// test which side we bounced off (or both if a corner)
|
|
3214
3422
|
const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
3215
3423
|
const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
3424
|
+
|
|
3425
|
+
if (blockedLayerX)
|
|
3426
|
+
{
|
|
3427
|
+
// try to move up a tiny bit
|
|
3428
|
+
const epsilon = 1e-3;
|
|
3429
|
+
const maxMoveUp = .1;
|
|
3430
|
+
const y = floor(oldPos.y-this.size.y/2+1) +
|
|
3431
|
+
this.size.y/2 + epsilon;
|
|
3432
|
+
const delta = y - this.pos.y;
|
|
3433
|
+
if (delta < maxMoveUp)
|
|
3434
|
+
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
3435
|
+
{
|
|
3436
|
+
this.pos.y = y;
|
|
3437
|
+
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
// move to previous position and bounce
|
|
3442
|
+
this.pos.x = oldPos.x;
|
|
3443
|
+
this.velocity.x *= -this.restitution;
|
|
3444
|
+
}
|
|
3216
3445
|
if (blockedLayerY || !blockedLayerX)
|
|
3217
3446
|
{
|
|
3218
3447
|
// bounce velocity
|
|
@@ -3236,12 +3465,6 @@ class EngineObject
|
|
|
3236
3465
|
this.groundObject = undefined;
|
|
3237
3466
|
}
|
|
3238
3467
|
}
|
|
3239
|
-
if (blockedLayerX)
|
|
3240
|
-
{
|
|
3241
|
-
// move to previous position and bounce
|
|
3242
|
-
this.pos.x = oldPos.x;
|
|
3243
|
-
this.velocity.x *= -this.restitution;
|
|
3244
|
-
}
|
|
3245
3468
|
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
3246
3469
|
}
|
|
3247
3470
|
}
|
|
@@ -3299,6 +3522,16 @@ class EngineObject
|
|
|
3299
3522
|
*/
|
|
3300
3523
|
collideWithObject(object) { return true; }
|
|
3301
3524
|
|
|
3525
|
+
/** Get this object's up vector
|
|
3526
|
+
* @param {number} [scale] - length of the vector
|
|
3527
|
+
* @return {Vector2} */
|
|
3528
|
+
getUp(scale=1) { return vec2().setAngle(this.angle, scale); }
|
|
3529
|
+
|
|
3530
|
+
/** Get this object's right vector
|
|
3531
|
+
* @param {number} [scale] - length of the vector
|
|
3532
|
+
* @return {Vector2} */
|
|
3533
|
+
getRight(scale=1) { return vec2().setAngle(this.angle+PI/2, scale); }
|
|
3534
|
+
|
|
3302
3535
|
/** How long since the object was created
|
|
3303
3536
|
* @return {number} */
|
|
3304
3537
|
getAliveTime() { return time - this.spawnTime; }
|
|
@@ -3571,16 +3804,15 @@ class TileInfo
|
|
|
3571
3804
|
}
|
|
3572
3805
|
|
|
3573
3806
|
/**
|
|
3574
|
-
* Set this tile to use a full image
|
|
3575
|
-
* @param {
|
|
3576
|
-
* @param {WebGLTexture} [glTexture] - WebGL texture
|
|
3807
|
+
* Set this tile to use a full image in a texture info
|
|
3808
|
+
* @param {TextureInfo} textureInfo
|
|
3577
3809
|
* @return {TileInfo}
|
|
3578
3810
|
*/
|
|
3579
|
-
setFullImage(
|
|
3811
|
+
setFullImage(textureInfo)
|
|
3580
3812
|
{
|
|
3581
3813
|
this.pos = new Vector2;
|
|
3582
|
-
this.size =
|
|
3583
|
-
this.textureInfo =
|
|
3814
|
+
this.size = textureInfo.size.copy();
|
|
3815
|
+
this.textureInfo = textureInfo;
|
|
3584
3816
|
// do not use padding or bleed
|
|
3585
3817
|
this.bleedScale = this.padding = 0;
|
|
3586
3818
|
return this;
|
|
@@ -3596,26 +3828,30 @@ class TextureInfo
|
|
|
3596
3828
|
/**
|
|
3597
3829
|
* Create a TextureInfo, called automatically by the engine
|
|
3598
3830
|
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
3599
|
-
* @param {
|
|
3831
|
+
* @param {boolean} [useWebGL] - Should use WebGL if available?
|
|
3600
3832
|
*/
|
|
3601
|
-
constructor(image,
|
|
3833
|
+
constructor(image, useWebGL=true)
|
|
3602
3834
|
{
|
|
3603
|
-
/** @property {HTMLImageElement} - image source */
|
|
3835
|
+
/** @property {HTMLImageElement|OffscreenCanvas} - image source */
|
|
3604
3836
|
this.image = image;
|
|
3605
3837
|
/** @property {Vector2} - size of the image */
|
|
3606
|
-
this.size = vec2(image.width, image.height);
|
|
3838
|
+
this.size = image ? vec2(image.width, image.height) : vec2();
|
|
3607
3839
|
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
3608
|
-
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
3840
|
+
this.sizeInverse = image ? vec2(1/image.width, 1/image.height) : vec2();
|
|
3609
3841
|
/** @property {WebGLTexture} - WebGL texture */
|
|
3610
|
-
this.glTexture =
|
|
3842
|
+
this.glTexture = undefined;
|
|
3843
|
+
useWebGL && this.createWebGLTexture();
|
|
3611
3844
|
}
|
|
3612
3845
|
|
|
3613
|
-
|
|
3614
|
-
{
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3846
|
+
/** Creates the WebGL texture, updates if already created */
|
|
3847
|
+
createWebGLTexture() { glRegisterTextureInfo(this); }
|
|
3848
|
+
|
|
3849
|
+
/** Destroys the WebGL texture */
|
|
3850
|
+
destroyWebGLTexture() { glUnregisterTextureInfo(this); }
|
|
3851
|
+
|
|
3852
|
+
/** Check if the texture is webgl enabled
|
|
3853
|
+
* @return {boolean} */
|
|
3854
|
+
hasWebGL() { return !!this.glTexture; }
|
|
3619
3855
|
}
|
|
3620
3856
|
|
|
3621
3857
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3647,6 +3883,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3647
3883
|
const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
|
|
3648
3884
|
if (useWebGL)
|
|
3649
3885
|
{
|
|
3886
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3650
3887
|
if (screenSpace)
|
|
3651
3888
|
{
|
|
3652
3889
|
// convert to world space
|
|
@@ -3742,6 +3979,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3742
3979
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3743
3980
|
if (useWebGL)
|
|
3744
3981
|
{
|
|
3982
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3745
3983
|
if (screenSpace)
|
|
3746
3984
|
{
|
|
3747
3985
|
// convert to world space
|
|
@@ -3753,7 +3991,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3753
3991
|
const halfSizeX = size.x/2, halfSizeY = size.y/2;
|
|
3754
3992
|
const colorTopInt = colorTop.rgbaInt();
|
|
3755
3993
|
const colorBottomInt = colorBottom.rgbaInt();
|
|
3756
|
-
const c =
|
|
3994
|
+
const c = cos(-angle), s = sin(-angle);
|
|
3757
3995
|
for (let i=4; i--;)
|
|
3758
3996
|
{
|
|
3759
3997
|
const x = i & 1 ? halfSizeX : -halfSizeX;
|
|
@@ -3804,6 +4042,7 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3804
4042
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3805
4043
|
if (useWebGL)
|
|
3806
4044
|
{
|
|
4045
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3807
4046
|
let scale = 1;
|
|
3808
4047
|
if (screenSpace)
|
|
3809
4048
|
{
|
|
@@ -3853,6 +4092,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
3853
4092
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
3854
4093
|
const size = vec2(width, halfDelta.length()*2);
|
|
3855
4094
|
pos = pos.add(posA.add(halfDelta));
|
|
4095
|
+
if (screenSpace)
|
|
4096
|
+
halfDelta.y *= -1; // flip angle Y if screen space
|
|
3856
4097
|
angle += halfDelta.angle();
|
|
3857
4098
|
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
3858
4099
|
}
|
|
@@ -3880,7 +4121,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
3880
4121
|
for (let i=sides; i--;)
|
|
3881
4122
|
{
|
|
3882
4123
|
const a = (i/sides)*PI*2;
|
|
3883
|
-
points.push(vec2(
|
|
4124
|
+
points.push(vec2(sin(a)*sizeX, cos(a)*sizeY));
|
|
3884
4125
|
}
|
|
3885
4126
|
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
|
|
3886
4127
|
}
|
|
@@ -3907,6 +4148,7 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
3907
4148
|
|
|
3908
4149
|
if (useWebGL)
|
|
3909
4150
|
{
|
|
4151
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3910
4152
|
let scale = 1;
|
|
3911
4153
|
if (screenSpace)
|
|
3912
4154
|
{
|
|
@@ -4048,12 +4290,13 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
4048
4290
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
4049
4291
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
4050
4292
|
* @param {string} [font=fontDefault]
|
|
4293
|
+
* @param {string} [fontStyle]
|
|
4051
4294
|
* @param {number} [maxWidth]
|
|
4052
4295
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
4053
4296
|
* @memberof Draw */
|
|
4054
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth, context=drawContext)
|
|
4297
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, context=drawContext)
|
|
4055
4298
|
{
|
|
4056
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, maxWidth, context);
|
|
4299
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, context);
|
|
4057
4300
|
}
|
|
4058
4301
|
|
|
4059
4302
|
/** Draw text on overlay canvas in world space
|
|
@@ -4066,11 +4309,12 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
4066
4309
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
4067
4310
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
4068
4311
|
* @param {string} [font=fontDefault]
|
|
4312
|
+
* @param {string} [fontStyle]
|
|
4069
4313
|
* @param {number} [maxWidth]
|
|
4070
4314
|
* @memberof Draw */
|
|
4071
|
-
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth)
|
|
4315
|
+
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth)
|
|
4072
4316
|
{
|
|
4073
|
-
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, maxWidth, overlayContext);
|
|
4317
|
+
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, overlayContext);
|
|
4074
4318
|
}
|
|
4075
4319
|
|
|
4076
4320
|
/** Draw text on overlay canvas in screen space
|
|
@@ -4083,10 +4327,11 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
4083
4327
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
4084
4328
|
* @param {CanvasTextAlign} [textAlign]
|
|
4085
4329
|
* @param {string} [font=fontDefault]
|
|
4330
|
+
* @param {string} [fontStyle]
|
|
4086
4331
|
* @param {number} [maxWidth]
|
|
4087
4332
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
4088
4333
|
* @memberof Draw */
|
|
4089
|
-
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
|
|
4334
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, context=overlayContext)
|
|
4090
4335
|
{
|
|
4091
4336
|
ASSERT(isString(text), 'text must be a string');
|
|
4092
4337
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -4094,15 +4339,15 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
4094
4339
|
ASSERT(isColor(color), 'color must be a color');
|
|
4095
4340
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
4096
4341
|
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
4097
|
-
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
4098
4342
|
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
4099
4343
|
ASSERT(isString(font), 'font must be a string');
|
|
4344
|
+
ASSERT(isString(fontStyle), 'fontStyle must be a string');
|
|
4100
4345
|
|
|
4101
4346
|
context.fillStyle = color.toString();
|
|
4102
4347
|
context.strokeStyle = lineColor.toString();
|
|
4103
4348
|
context.lineWidth = lineWidth;
|
|
4104
4349
|
context.textAlign = textAlign;
|
|
4105
|
-
context.font = size + 'px '+ font;
|
|
4350
|
+
context.font = fontStyle + ' ' + size + 'px '+ font;
|
|
4106
4351
|
context.textBaseline = 'middle';
|
|
4107
4352
|
|
|
4108
4353
|
const lines = (text+'').split('\n');
|
|
@@ -4125,18 +4370,18 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
4125
4370
|
* @memberof Draw */
|
|
4126
4371
|
function screenToWorld(screenPos)
|
|
4127
4372
|
{
|
|
4128
|
-
let
|
|
4129
|
-
let
|
|
4373
|
+
let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
4374
|
+
let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
4130
4375
|
if (cameraAngle)
|
|
4131
4376
|
{
|
|
4132
4377
|
// apply camera rotation
|
|
4133
|
-
const
|
|
4134
|
-
const rotatedX =
|
|
4135
|
-
const rotatedY =
|
|
4136
|
-
|
|
4137
|
-
|
|
4378
|
+
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
4379
|
+
const rotatedX = x * c - y * s;
|
|
4380
|
+
const rotatedY = x * s + y * c;
|
|
4381
|
+
x = rotatedX;
|
|
4382
|
+
y = rotatedY;
|
|
4138
4383
|
}
|
|
4139
|
-
return new Vector2(
|
|
4384
|
+
return new Vector2(x + cameraPos.x, y + cameraPos.y);
|
|
4140
4385
|
}
|
|
4141
4386
|
|
|
4142
4387
|
/** Convert from world to screen space coordinates
|
|
@@ -4145,24 +4390,64 @@ function screenToWorld(screenPos)
|
|
|
4145
4390
|
* @memberof Draw */
|
|
4146
4391
|
function worldToScreen(worldPos)
|
|
4147
4392
|
{
|
|
4148
|
-
let
|
|
4149
|
-
let
|
|
4393
|
+
let x = worldPos.x - cameraPos.x;
|
|
4394
|
+
let y = worldPos.y - cameraPos.y;
|
|
4150
4395
|
if (cameraAngle)
|
|
4151
4396
|
{
|
|
4152
4397
|
// apply inverse camera rotation
|
|
4153
|
-
const
|
|
4154
|
-
const rotatedX =
|
|
4155
|
-
const rotatedY =
|
|
4156
|
-
|
|
4157
|
-
|
|
4398
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
4399
|
+
const rotatedX = x * c - y * s;
|
|
4400
|
+
const rotatedY = x * s + y * c;
|
|
4401
|
+
x = rotatedX;
|
|
4402
|
+
y = rotatedY;
|
|
4158
4403
|
}
|
|
4159
4404
|
return new Vector2
|
|
4160
4405
|
(
|
|
4161
|
-
|
|
4162
|
-
|
|
4406
|
+
x * cameraScale + mainCanvasSize.x/2 - .5,
|
|
4407
|
+
y * -cameraScale + mainCanvasSize.y/2 - .5
|
|
4163
4408
|
);
|
|
4164
4409
|
}
|
|
4165
4410
|
|
|
4411
|
+
/** Convert from screen to world space coordinates for a directional vector (no translation)
|
|
4412
|
+
* @param {Vector2} screenDelta
|
|
4413
|
+
* @return {Vector2}
|
|
4414
|
+
* @memberof Draw */
|
|
4415
|
+
function screenToWorldDelta(screenDelta)
|
|
4416
|
+
{
|
|
4417
|
+
let x = screenDelta.x / cameraScale;
|
|
4418
|
+
let y = screenDelta.y / -cameraScale;
|
|
4419
|
+
if (cameraAngle)
|
|
4420
|
+
{
|
|
4421
|
+
// apply camera rotation
|
|
4422
|
+
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
4423
|
+
const rotatedX = x * c - y * s;
|
|
4424
|
+
const rotatedY = x * s + y * c;
|
|
4425
|
+
x = rotatedX;
|
|
4426
|
+
y = rotatedY;
|
|
4427
|
+
}
|
|
4428
|
+
return new Vector2(x, y);
|
|
4429
|
+
}
|
|
4430
|
+
|
|
4431
|
+
/** Convert from screen to world space coordinates for a directional vector (no translation)
|
|
4432
|
+
* @param {Vector2} worldDelta
|
|
4433
|
+
* @return {Vector2}
|
|
4434
|
+
* @memberof Draw */
|
|
4435
|
+
function worldToScreenDelta(worldDelta)
|
|
4436
|
+
{
|
|
4437
|
+
let x = worldDelta.x;
|
|
4438
|
+
let y = worldDelta.y;
|
|
4439
|
+
if (cameraAngle)
|
|
4440
|
+
{
|
|
4441
|
+
// apply inverse camera rotation
|
|
4442
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
4443
|
+
const rotatedX = x * c - y * s;
|
|
4444
|
+
const rotatedY = x * s + y * c;
|
|
4445
|
+
x = rotatedX;
|
|
4446
|
+
y = rotatedY;
|
|
4447
|
+
}
|
|
4448
|
+
return new Vector2(x * cameraScale, y * -cameraScale);
|
|
4449
|
+
}
|
|
4450
|
+
|
|
4166
4451
|
/** Get the camera's visible area in world space
|
|
4167
4452
|
* @return {Vector2}
|
|
4168
4453
|
* @memberof Draw */
|
|
@@ -4215,6 +4500,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
4215
4500
|
function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
|
|
4216
4501
|
const sx2 = bleedScale;
|
|
4217
4502
|
const sy2 = bleedScale;
|
|
4503
|
+
sWidth = max(1,sWidth|0);
|
|
4504
|
+
sHeight = max(1,sHeight|0);
|
|
4218
4505
|
const sWidth2 = sWidth - 2*bleedScale;
|
|
4219
4506
|
const sHeight2 = sHeight - 2*bleedScale;
|
|
4220
4507
|
if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
|
|
@@ -4229,7 +4516,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
4229
4516
|
// copy to offscreen canvas
|
|
4230
4517
|
workCanvas.width = sWidth;
|
|
4231
4518
|
workCanvas.height = sHeight;
|
|
4232
|
-
workContext.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
4519
|
+
workContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
4233
4520
|
|
|
4234
4521
|
// tint image using offscreen work context
|
|
4235
4522
|
const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
|
|
@@ -4310,11 +4597,11 @@ let engineFontImage;
|
|
|
4310
4597
|
class FontImage
|
|
4311
4598
|
{
|
|
4312
4599
|
/** Create an image font
|
|
4313
|
-
* @param {HTMLImageElement} [image]
|
|
4314
|
-
* @param {Vector2} [tileSize=(8,8)]
|
|
4315
|
-
* @param {Vector2} [paddingSize=(0,1)] - How much
|
|
4600
|
+
* @param {HTMLImageElement} [image] - Image for the font, default if undefined
|
|
4601
|
+
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
4602
|
+
* @param {Vector2} [paddingSize=(0,1)] - How much space between characters
|
|
4316
4603
|
*/
|
|
4317
|
-
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1)
|
|
4604
|
+
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
|
|
4318
4605
|
{
|
|
4319
4606
|
// load default font image
|
|
4320
4607
|
if (!engineFontImage)
|
|
@@ -4566,9 +4853,9 @@ function inputUpdate()
|
|
|
4566
4853
|
if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
4567
4854
|
inputClear();
|
|
4568
4855
|
|
|
4569
|
-
// update mouse world space position
|
|
4856
|
+
// update mouse world space position and delta
|
|
4570
4857
|
mousePos = screenToWorld(mousePosScreen);
|
|
4571
|
-
mouseDelta = mouseDeltaScreen
|
|
4858
|
+
mouseDelta = screenToWorldDelta(mouseDeltaScreen);
|
|
4572
4859
|
|
|
4573
4860
|
// update gamepads if enabled
|
|
4574
4861
|
gamepadsUpdate();
|
|
@@ -4600,7 +4887,6 @@ function inputInit()
|
|
|
4600
4887
|
document.addEventListener('wheel', onMouseWheel);
|
|
4601
4888
|
document.addEventListener('contextmenu', onContextMenu);
|
|
4602
4889
|
document.addEventListener('blur', onBlur);
|
|
4603
|
-
document.addEventListener('mouseleave', onMouseLeave);
|
|
4604
4890
|
|
|
4605
4891
|
// init touch input
|
|
4606
4892
|
if (isTouchDevice && touchInputEnable)
|
|
@@ -4642,7 +4928,10 @@ function inputInit()
|
|
|
4642
4928
|
|
|
4643
4929
|
isUsingGamepad = false;
|
|
4644
4930
|
inputData[0][e.button] = 3;
|
|
4931
|
+
|
|
4932
|
+
let mousePosScreenLast = mousePosScreen;
|
|
4645
4933
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
4934
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4646
4935
|
inputPreventDefault && e.button && e.preventDefault();
|
|
4647
4936
|
}
|
|
4648
4937
|
function onMouseUp(e)
|
|
@@ -4653,18 +4942,13 @@ function inputInit()
|
|
|
4653
4942
|
}
|
|
4654
4943
|
function onMouseMove(e)
|
|
4655
4944
|
{
|
|
4945
|
+
let mousePosScreenLast = mousePosScreen;
|
|
4656
4946
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
4657
|
-
mouseDeltaScreen = mouseDeltaScreen.add(
|
|
4947
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4658
4948
|
}
|
|
4659
4949
|
function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
|
|
4660
4950
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
4661
4951
|
function onBlur() { inputClear(); } // reset input when focus is lost
|
|
4662
|
-
function onMouseLeave()
|
|
4663
|
-
{
|
|
4664
|
-
// set mouse position and delta when leaving canvas
|
|
4665
|
-
mousePosScreen = vec2(-1);
|
|
4666
|
-
mouseDeltaScreen = vec2(0);
|
|
4667
|
-
}
|
|
4668
4952
|
}
|
|
4669
4953
|
|
|
4670
4954
|
// convert a mouse or touch event position to screen space
|
|
@@ -4708,8 +4992,8 @@ function gamepadsUpdate()
|
|
|
4708
4992
|
else if (touchGamepadStick.lengthSquared() > .3)
|
|
4709
4993
|
{
|
|
4710
4994
|
// convert to 8 way dpad
|
|
4711
|
-
sticks[0].x =
|
|
4712
|
-
sticks[0].y = -
|
|
4995
|
+
sticks[0].x = round(touchGamepadStick.x);
|
|
4996
|
+
sticks[0].y = -round(touchGamepadStick.y);
|
|
4713
4997
|
sticks[0] = sticks[0].clampLength();
|
|
4714
4998
|
}
|
|
4715
4999
|
|
|
@@ -4837,11 +5121,11 @@ function touchInputInit()
|
|
|
4837
5121
|
{
|
|
4838
5122
|
// set event pos and pass it along
|
|
4839
5123
|
const pos = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
4840
|
-
const
|
|
5124
|
+
const mousePosScreenLast = mousePosScreen;
|
|
4841
5125
|
mousePosScreen = mouseEventToScreen(pos);
|
|
4842
5126
|
if (wasTouching)
|
|
4843
5127
|
{
|
|
4844
|
-
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(
|
|
5128
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4845
5129
|
isUsingGamepad = touchGamepadEnable;
|
|
4846
5130
|
}
|
|
4847
5131
|
else
|
|
@@ -4873,7 +5157,7 @@ function touchInputInit()
|
|
|
4873
5157
|
if (touching)
|
|
4874
5158
|
{
|
|
4875
5159
|
touchGamepadTimer.set();
|
|
4876
|
-
if (
|
|
5160
|
+
if (touchGamepadCenterButton && !wasTouching && paused)
|
|
4877
5161
|
{
|
|
4878
5162
|
// touch anywhere to press start when paused
|
|
4879
5163
|
touchGamepadButtons[9] = 1;
|
|
@@ -4912,7 +5196,8 @@ function touchInputInit()
|
|
|
4912
5196
|
if (button < touchGamepadButtonCount)
|
|
4913
5197
|
touchGamepadButtons[button] = 1;
|
|
4914
5198
|
}
|
|
4915
|
-
else if (
|
|
5199
|
+
else if (touchGamepadCenterButton && !wasTouching &&
|
|
5200
|
+
startCenter.distance(touchPos) < touchGamepadSize)
|
|
4916
5201
|
{
|
|
4917
5202
|
// virtual start button in center
|
|
4918
5203
|
touchGamepadButtons[9] = 1;
|
|
@@ -5327,8 +5612,16 @@ class SoundInstance
|
|
|
5327
5612
|
this.stop();
|
|
5328
5613
|
this.gainNode = audioContext.createGain();
|
|
5329
5614
|
this.source = playSamples(this.sound.sampleChannels, this.volume, this.rate, this.pan, this.loop, this.sound.sampleRate, this.gainNode, offset, this.onendedCallback);
|
|
5330
|
-
this.
|
|
5331
|
-
|
|
5615
|
+
if (this.source)
|
|
5616
|
+
{
|
|
5617
|
+
this.startTime = audioContext.currentTime - offset;
|
|
5618
|
+
this.pausedTime = undefined;
|
|
5619
|
+
}
|
|
5620
|
+
else
|
|
5621
|
+
{
|
|
5622
|
+
this.startTime = undefined;
|
|
5623
|
+
this.pausedTime = 0;
|
|
5624
|
+
}
|
|
5332
5625
|
}
|
|
5333
5626
|
|
|
5334
5627
|
/** Set the volume of this sound instance
|
|
@@ -5477,12 +5770,19 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
5477
5770
|
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
5478
5771
|
* @param {number} [offset] - Offset in seconds to start playback from
|
|
5479
5772
|
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
5480
|
-
* @return {AudioBufferSourceNode} - The
|
|
5773
|
+
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
5481
5774
|
* @memberof Audio */
|
|
5482
5775
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=audioDefaultSampleRate, gainNode, offset=0, onended)
|
|
5483
5776
|
{
|
|
5484
5777
|
if (!soundEnable || headlessMode) return;
|
|
5485
5778
|
|
|
5779
|
+
if (!audioIsRunning())
|
|
5780
|
+
{
|
|
5781
|
+
// fix stalled audio, this sound won't be able to play
|
|
5782
|
+
audioContext.resume();
|
|
5783
|
+
return;
|
|
5784
|
+
}
|
|
5785
|
+
|
|
5486
5786
|
// create buffer and source
|
|
5487
5787
|
const channelCount = sampleChannels.length;
|
|
5488
5788
|
const sampleLength = sampleChannels[0].length;
|
|
@@ -5508,14 +5808,9 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
5508
5808
|
if (onended)
|
|
5509
5809
|
source.addEventListener('ended', ()=> onended(source));
|
|
5510
5810
|
|
|
5811
|
+
// play and return sound
|
|
5511
5812
|
const startOffset = offset * rate;
|
|
5512
|
-
|
|
5513
|
-
{
|
|
5514
|
-
// fix stalled audio and start
|
|
5515
|
-
audioContext.resume().then(()=>source.start(0, startOffset));
|
|
5516
|
-
}
|
|
5517
|
-
else
|
|
5518
|
-
source.start(0, startOffset);
|
|
5813
|
+
source.start(0, startOffset);
|
|
5519
5814
|
return source;
|
|
5520
5815
|
}
|
|
5521
5816
|
|
|
@@ -5598,10 +5893,10 @@ function zzfxG
|
|
|
5598
5893
|
|
|
5599
5894
|
// biquad LP/HP filter
|
|
5600
5895
|
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
5601
|
-
|
|
5602
|
-
a0 = 1 + alpha, a1 = -2*
|
|
5603
|
-
b0 = (1 + sign(filter) *
|
|
5604
|
-
b1 = -(sign(filter) +
|
|
5896
|
+
cosw = cos(w), alpha = sin(w) / 2 / quality,
|
|
5897
|
+
a0 = 1 + alpha, a1 = -2*cosw / a0, a2 = (1 - alpha) / a0,
|
|
5898
|
+
b0 = (1 + sign(filter) * cosw) / 2 / a0,
|
|
5899
|
+
b1 = -(sign(filter) + cosw) / a0, b2 = b0,
|
|
5605
5900
|
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
5606
5901
|
|
|
5607
5902
|
// scale by sample rate
|
|
@@ -5624,15 +5919,15 @@ function zzfxG
|
|
|
5624
5919
|
if (!(++crush%(bitCrush*100|0))) // bit crush
|
|
5625
5920
|
{
|
|
5626
5921
|
s = shape? shape>1? shape>2? shape>3? shape>4? // wave shape
|
|
5627
|
-
(t/PI2%1 < shapeCurve/2? 1 : -1) :
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
1-(2*t/PI2%2+2)%2:
|
|
5631
|
-
1-4*abs(
|
|
5632
|
-
|
|
5922
|
+
(t/PI2%1 < shapeCurve/2? 1 : -1) : // 5 square duty
|
|
5923
|
+
sin(t**3) : // 4 noise
|
|
5924
|
+
max(min(tan(t),1),-1): // 3 tan
|
|
5925
|
+
1-(2*t/PI2%2+2)%2: // 2 saw
|
|
5926
|
+
1-4*abs(round(t/PI2)-t/PI2): // 1 triangle
|
|
5927
|
+
sin(t); // 0 sin
|
|
5633
5928
|
|
|
5634
5929
|
s = (repeatTime ?
|
|
5635
|
-
1 - tremolo + tremolo*
|
|
5930
|
+
1 - tremolo + tremolo*sin(PI2*i/repeatTime) // tremolo
|
|
5636
5931
|
: 1) *
|
|
5637
5932
|
(shape>4?s:sign(s)*abs(s)**shapeCurve) * // shape curve
|
|
5638
5933
|
(i < attack ? i/attack : // attack
|
|
@@ -5654,8 +5949,8 @@ function zzfxG
|
|
|
5654
5949
|
}
|
|
5655
5950
|
|
|
5656
5951
|
f = (frequency += slide += deltaSlide) *// frequency
|
|
5657
|
-
|
|
5658
|
-
t += f + f*noise*
|
|
5952
|
+
cos(modulation*modOffset++); // modulation
|
|
5953
|
+
t += f + f*noise*sin(i**5); // noise
|
|
5659
5954
|
|
|
5660
5955
|
if (jump && ++jump > pitchJumpTime) // pitch jump
|
|
5661
5956
|
{
|
|
@@ -5721,23 +6016,22 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
|
|
|
5721
6016
|
}
|
|
5722
6017
|
}
|
|
5723
6018
|
|
|
5724
|
-
/** Return the
|
|
5725
|
-
* This does not return the exact intersection, but the center of the tile hit.
|
|
6019
|
+
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
5726
6020
|
* @param {Vector2} posStart
|
|
5727
6021
|
* @param {Vector2} posEnd
|
|
5728
6022
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
6023
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
5729
6024
|
* @param {boolean} [solidOnly=true] - Only check solid layers if true
|
|
5730
|
-
* @return {Vector2}
|
|
6025
|
+
* @return {Vector2|undefined} - position of the center of the tile hit or undefined if no hit
|
|
5731
6026
|
* @memberof TileLayers */
|
|
5732
|
-
function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
|
|
6027
|
+
function tileCollisionRaycast(posStart, posEnd, object, normal, solidOnly=true)
|
|
5733
6028
|
{
|
|
5734
6029
|
for (const layer of tileCollisionLayers)
|
|
5735
6030
|
{
|
|
5736
6031
|
if (!solidOnly || layer.isSolid)
|
|
5737
6032
|
{
|
|
5738
|
-
const hitPos = layer.collisionRaycast(posStart, posEnd, object)
|
|
5739
|
-
if (hitPos)
|
|
5740
|
-
return hitPos;
|
|
6033
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, object, normal)
|
|
6034
|
+
if (hitPos) return hitPos;
|
|
5741
6035
|
}
|
|
5742
6036
|
}
|
|
5743
6037
|
}
|
|
@@ -5871,10 +6165,15 @@ class CanvasLayer extends EngineObject
|
|
|
5871
6165
|
/** @property {HTMLCanvasElement} - The canvas used by this layer */
|
|
5872
6166
|
this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
5873
6167
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
|
|
5874
|
-
this.context =
|
|
5875
|
-
/** @property {
|
|
5876
|
-
|
|
5877
|
-
this.
|
|
6168
|
+
this.context = this.canvas?.getContext('2d');
|
|
6169
|
+
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
6170
|
+
const useWebGL = false; // do not use webgl by default
|
|
6171
|
+
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
6172
|
+
/** @property {boolean} - True if WebGL texture needs to be refreshed */
|
|
6173
|
+
this.refreshWebGL = false;
|
|
6174
|
+
|
|
6175
|
+
// disable physics by default
|
|
6176
|
+
this.mass = this.gravityScale = this.friction = this.restitution = 0;
|
|
5878
6177
|
}
|
|
5879
6178
|
|
|
5880
6179
|
/** Destroy this canvas layer */
|
|
@@ -5883,9 +6182,7 @@ class CanvasLayer extends EngineObject
|
|
|
5883
6182
|
if (this.destroyed)
|
|
5884
6183
|
return;
|
|
5885
6184
|
|
|
5886
|
-
|
|
5887
|
-
if (this.glTexture)
|
|
5888
|
-
glDeleteTexture(this.glTexture);
|
|
6185
|
+
this.textureInfo.destroyWebGLTexture();
|
|
5889
6186
|
super.destroy();
|
|
5890
6187
|
}
|
|
5891
6188
|
|
|
@@ -5907,9 +6204,16 @@ class CanvasLayer extends EngineObject
|
|
|
5907
6204
|
* @memberof Draw */
|
|
5908
6205
|
draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
|
|
5909
6206
|
{
|
|
6207
|
+
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
6208
|
+
if (useWebGL && this.refreshWebGL)
|
|
6209
|
+
{
|
|
6210
|
+
// update the WebGL texture
|
|
6211
|
+
this.textureInfo.createWebGLTexture();
|
|
6212
|
+
this.refreshWebGL = false;
|
|
6213
|
+
}
|
|
6214
|
+
|
|
5910
6215
|
// draw the canvas layer as a single tile that uses the whole texture
|
|
5911
|
-
const
|
|
5912
|
-
const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
|
|
6216
|
+
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
5913
6217
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
5914
6218
|
}
|
|
5915
6219
|
|
|
@@ -5927,6 +6231,8 @@ class CanvasLayer extends EngineObject
|
|
|
5927
6231
|
* @param {Canvas2DDrawCallback} drawFunction */
|
|
5928
6232
|
drawCanvas2D(pos, size, angle, mirror, drawFunction)
|
|
5929
6233
|
{
|
|
6234
|
+
if (!this.context) return;
|
|
6235
|
+
|
|
5930
6236
|
const context = this.context;
|
|
5931
6237
|
context.save();
|
|
5932
6238
|
pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
|
|
@@ -5976,18 +6282,22 @@ class CanvasLayer extends EngineObject
|
|
|
5976
6282
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
5977
6283
|
|
|
5978
6284
|
/** Create or update the WebGL texture for this layer
|
|
5979
|
-
* @param {boolean} [enable] - enable WebGL rendering and update the texture
|
|
5980
|
-
|
|
6285
|
+
* @param {boolean} [enable] - enable WebGL rendering and update the texture
|
|
6286
|
+
* @param {boolean} [immediate] - shoulkd the texture be updated immediately
|
|
6287
|
+
*/
|
|
6288
|
+
useWebGL(enable=true, immediate=false)
|
|
5981
6289
|
{
|
|
5982
|
-
if (
|
|
6290
|
+
if (!immediate && enable && this.textureInfo.hasWebGL())
|
|
5983
6291
|
{
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
this.glTexture = glCreateTexture(this.canvas);
|
|
6292
|
+
// refresh the texture when needed
|
|
6293
|
+
this.refreshWebGL = true;
|
|
6294
|
+
return;
|
|
5988
6295
|
}
|
|
6296
|
+
|
|
6297
|
+
if (enable)
|
|
6298
|
+
this.textureInfo.createWebGLTexture();
|
|
5989
6299
|
else
|
|
5990
|
-
this.
|
|
6300
|
+
this.textureInfo.destroyWebGLTexture();
|
|
5991
6301
|
}
|
|
5992
6302
|
}
|
|
5993
6303
|
|
|
@@ -6011,24 +6321,14 @@ class TileLayer extends CanvasLayer
|
|
|
6011
6321
|
* @param {Vector2} size - World space size
|
|
6012
6322
|
* @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
|
|
6013
6323
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6014
|
-
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
6015
6324
|
*/
|
|
6016
|
-
constructor(position, size, tileInfo=tile(), renderOrder=0
|
|
6325
|
+
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
6017
6326
|
{
|
|
6018
|
-
|
|
6019
|
-
|
|
6327
|
+
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
6328
|
+
super(position, size, 0, renderOrder, canvasSize);
|
|
6329
|
+
|
|
6330
|
+
// set tile info
|
|
6020
6331
|
this.tileInfo = tileInfo;
|
|
6021
|
-
const canvasSize = size.multiply(tileInfo.size);
|
|
6022
|
-
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
6023
|
-
this.canvas = new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
6024
|
-
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
6025
|
-
this.context = this.canvas.getContext('2d');
|
|
6026
|
-
/** @property {WebGLTexture} - Texture if using WebGL for this layer */
|
|
6027
|
-
this.glTexture = useWebGL ? glCreateTexture(this.canvas) : undefined;
|
|
6028
|
-
// set no friction by default, applied friction is max of both objects
|
|
6029
|
-
this.friction = 0;
|
|
6030
|
-
// set no restitution by default, applied restitution is max of both objects
|
|
6031
|
-
this.restitution = 0;
|
|
6032
6332
|
|
|
6033
6333
|
// init tile data
|
|
6034
6334
|
this.data = [];
|
|
@@ -6077,11 +6377,18 @@ class TileLayer extends CanvasLayer
|
|
|
6077
6377
|
{
|
|
6078
6378
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
6079
6379
|
|
|
6380
|
+
if (this.refreshWebGL)
|
|
6381
|
+
{
|
|
6382
|
+
// update the WebGL texture
|
|
6383
|
+
this.textureInfo.createWebGLTexture();
|
|
6384
|
+
this.refreshWebGL = false;
|
|
6385
|
+
}
|
|
6386
|
+
|
|
6080
6387
|
// draw the tile layer as a single tile
|
|
6081
|
-
const tileInfo = new TileInfo().setFullImage(this.
|
|
6388
|
+
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
6082
6389
|
const size = this.drawSize || this.size;
|
|
6083
6390
|
const pos = this.pos.add(size.scale(.5));
|
|
6084
|
-
const useWebGL = glEnable && this.
|
|
6391
|
+
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
6085
6392
|
drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
|
|
6086
6393
|
}
|
|
6087
6394
|
|
|
@@ -6094,8 +6401,7 @@ class TileLayer extends CanvasLayer
|
|
|
6094
6401
|
for (let y = this.size.y; y--;)
|
|
6095
6402
|
this.drawTileData(vec2(x,y), false);
|
|
6096
6403
|
this.redrawEnd();
|
|
6097
|
-
|
|
6098
|
-
this.useWebGL(); // update WebGL texture
|
|
6404
|
+
this.useWebGL();
|
|
6099
6405
|
}
|
|
6100
6406
|
|
|
6101
6407
|
/** Call to start the redraw process
|
|
@@ -6103,6 +6409,8 @@ class TileLayer extends CanvasLayer
|
|
|
6103
6409
|
* @param {boolean} [clear] - Should it clear the canvas before drawing */
|
|
6104
6410
|
redrawStart(clear=false)
|
|
6105
6411
|
{
|
|
6412
|
+
if (!this.context) return;
|
|
6413
|
+
|
|
6106
6414
|
// save current render settings
|
|
6107
6415
|
/** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number]} */
|
|
6108
6416
|
this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale];
|
|
@@ -6112,8 +6420,9 @@ class TileLayer extends CanvasLayer
|
|
|
6112
6420
|
drawCanvas = this.canvas;
|
|
6113
6421
|
drawContext = this.context;
|
|
6114
6422
|
cameraPos = this.size.scale(.5);
|
|
6115
|
-
|
|
6116
|
-
|
|
6423
|
+
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
6424
|
+
cameraScale = tileSize.x;
|
|
6425
|
+
mainCanvasSize = this.size.multiply(tileSize);
|
|
6117
6426
|
if (clear)
|
|
6118
6427
|
{
|
|
6119
6428
|
// clear and set size
|
|
@@ -6131,6 +6440,8 @@ class TileLayer extends CanvasLayer
|
|
|
6131
6440
|
/** Call to end the redraw process */
|
|
6132
6441
|
redrawEnd()
|
|
6133
6442
|
{
|
|
6443
|
+
if (!this.context) return;
|
|
6444
|
+
|
|
6134
6445
|
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
6135
6446
|
glCopyToContext(drawContext);
|
|
6136
6447
|
//debugSaveCanvas(this.canvas);
|
|
@@ -6147,6 +6458,8 @@ class TileLayer extends CanvasLayer
|
|
|
6147
6458
|
*/
|
|
6148
6459
|
drawTileData(layerPos, clear=true)
|
|
6149
6460
|
{
|
|
6461
|
+
if (!this.context) return;
|
|
6462
|
+
|
|
6150
6463
|
// clear out where the tile was, for full opaque tiles this can be skipped
|
|
6151
6464
|
const s = this.tileInfo.size;
|
|
6152
6465
|
if (clear)
|
|
@@ -6183,11 +6496,10 @@ class TileCollisionLayer extends TileLayer
|
|
|
6183
6496
|
* @param {Vector2} size - World space size
|
|
6184
6497
|
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
6185
6498
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6186
|
-
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
6187
6499
|
*/
|
|
6188
|
-
constructor(position, size, tileInfo=tile(), renderOrder=0
|
|
6500
|
+
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
6189
6501
|
{
|
|
6190
|
-
super(position, size.floor(), tileInfo, renderOrder
|
|
6502
|
+
super(position, size.floor(), tileInfo, renderOrder);
|
|
6191
6503
|
|
|
6192
6504
|
/** @property {Array<number>} - The tile collision grid */
|
|
6193
6505
|
this.collisionData = [];
|
|
@@ -6270,66 +6582,44 @@ class TileCollisionLayer extends TileLayer
|
|
|
6270
6582
|
// check if the object should collide with this tile
|
|
6271
6583
|
const tileData = this.collisionData[y*this.size.x+x];
|
|
6272
6584
|
if (tileData)
|
|
6273
|
-
if (!object || object.collideWithTile(tileData,
|
|
6585
|
+
if (!object || object.collideWithTile(tileData,
|
|
6586
|
+
hitPos.set(x + this.pos.x, y + this.pos.y)))
|
|
6274
6587
|
return true;
|
|
6275
6588
|
}
|
|
6276
6589
|
return false;
|
|
6277
6590
|
}
|
|
6278
6591
|
|
|
6279
|
-
/** Return the
|
|
6280
|
-
* This does not return the exact intersection, but the center of the tile hit.
|
|
6592
|
+
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
6281
6593
|
* @param {Vector2} posStart
|
|
6282
6594
|
* @param {Vector2} posEnd
|
|
6283
|
-
* @param {EngineObject} [object]
|
|
6284
|
-
* @
|
|
6285
|
-
|
|
6595
|
+
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
6596
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
6597
|
+
* @return {Vector2|undefined} */
|
|
6598
|
+
collisionRaycast(posStart, posEnd, object, normal)
|
|
6286
6599
|
{
|
|
6287
6600
|
ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
|
|
6288
6601
|
ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
const
|
|
6292
|
-
const posStartY = posStart.y - this.pos.y;
|
|
6293
|
-
const posEndX = posEnd.x - this.pos.x;
|
|
6294
|
-
const posEndY = posEnd.y - this.pos.y;
|
|
6295
|
-
|
|
6296
|
-
// test if a ray collides with tiles from start to end
|
|
6297
|
-
const deltaX = posEndX - posStartX;
|
|
6298
|
-
const deltaY = posEndY - posStartY;
|
|
6299
|
-
const totalLength = (deltaX**2 + deltaY**2)**.5;
|
|
6300
|
-
const unitX = abs(totalLength/deltaX);
|
|
6301
|
-
const unitY = abs(totalLength/deltaY);
|
|
6302
|
-
|
|
6303
|
-
// setup iteration variables
|
|
6304
|
-
const pos = posStart.floor(), signDeltaX = sign(deltaX), signDeltaY = sign(deltaY);
|
|
6305
|
-
let xi = unitX * (deltaX < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1) || 0;
|
|
6306
|
-
let yi = unitY * (deltaY < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1) || 0;
|
|
6307
|
-
|
|
6308
|
-
// use line drawing algorithm to test for collisions
|
|
6309
|
-
while (true)
|
|
6602
|
+
|
|
6603
|
+
const localPos = new Vector2;
|
|
6604
|
+
const collisionTest = (pos)=>
|
|
6310
6605
|
{
|
|
6311
6606
|
// check for tile collision
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
pos.x += .5; pos.y += .5;
|
|
6316
|
-
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
6317
|
-
debugRaycast && debugPoint(pos, '#ff0');
|
|
6318
|
-
return pos;
|
|
6319
|
-
}
|
|
6320
|
-
|
|
6321
|
-
// check if past the end
|
|
6322
|
-
if (xi >= totalLength && yi >= totalLength)
|
|
6323
|
-
break;
|
|
6324
|
-
|
|
6325
|
-
// get coordinates of next tile to check
|
|
6326
|
-
if (xi > yi)
|
|
6327
|
-
pos.y += signDeltaY, yi += unitY;
|
|
6328
|
-
else
|
|
6329
|
-
pos.x += signDeltaX, xi += unitX;
|
|
6607
|
+
localPos.set(pos.x - this.pos.x, pos.y - this.pos.y);
|
|
6608
|
+
const tileData = this.getCollisionData(localPos);
|
|
6609
|
+
return tileData && (!object || object.collideWithTile(tileData, pos));
|
|
6330
6610
|
}
|
|
6331
|
-
|
|
6332
6611
|
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
6612
|
+
const hitPos = lineTest(posStart, posEnd, collisionTest, normal);
|
|
6613
|
+
if (hitPos)
|
|
6614
|
+
{
|
|
6615
|
+
const tilePos = hitPos.floor().add(vec2(.5));
|
|
6616
|
+
debugRaycast && debugRect(tilePos, vec2(1), '#f008');
|
|
6617
|
+
debugRaycast && debugLine(posStart, hitPos, '#f00', .02);
|
|
6618
|
+
debugRaycast && debugPoint(hitPos, '#0f0');
|
|
6619
|
+
debugRaycast && normal &&
|
|
6620
|
+
debugLine(hitPos, hitPos.add(normal), '#ff0', .02);
|
|
6621
|
+
return hitPos;
|
|
6622
|
+
}
|
|
6333
6623
|
}
|
|
6334
6624
|
}
|
|
6335
6625
|
/**
|
|
@@ -6355,7 +6645,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6355
6645
|
* tile(0, 16), // tileInfo
|
|
6356
6646
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
6357
6647
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
6358
|
-
*
|
|
6648
|
+
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
6359
6649
|
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
6360
6650
|
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
6361
6651
|
* );
|
|
@@ -6484,6 +6774,12 @@ class ParticleEmitter extends EngineObject
|
|
|
6484
6774
|
this.particleCreateCallback = undefined;
|
|
6485
6775
|
/** @property {number} - Track particle emit time */
|
|
6486
6776
|
this.emitTimeBuffer = 0;
|
|
6777
|
+
/** @property {number} - Percentage of velocity to pass to particles (0-1) */
|
|
6778
|
+
this.velocityInheritance = 0;
|
|
6779
|
+
|
|
6780
|
+
// track previous position and angle
|
|
6781
|
+
this.previousAngle = this.angle;
|
|
6782
|
+
this.previousPos = this.pos.copy();
|
|
6487
6783
|
}
|
|
6488
6784
|
|
|
6489
6785
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
@@ -6492,6 +6788,18 @@ class ParticleEmitter extends EngineObject
|
|
|
6492
6788
|
// only do default update to apply parent transforms
|
|
6493
6789
|
this.parent && super.update();
|
|
6494
6790
|
|
|
6791
|
+
if (this.velocityInheritance)
|
|
6792
|
+
{
|
|
6793
|
+
// pass emitter velocity to particles
|
|
6794
|
+
const p = this.velocityInheritance;
|
|
6795
|
+
this.velocity.x = p * (this.pos.x - this.previousPos.x);
|
|
6796
|
+
this.velocity.y = p * (this.pos.y - this.previousPos.y);
|
|
6797
|
+
this.angleVelocity = p * (this.angle - this.previousAngle);
|
|
6798
|
+
this.previousAngle = this.angle;
|
|
6799
|
+
this.previousPos.x = this.pos.x;
|
|
6800
|
+
this.previousPos.y = this.pos.y;
|
|
6801
|
+
}
|
|
6802
|
+
|
|
6495
6803
|
// update emitter
|
|
6496
6804
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
6497
6805
|
{
|
|
@@ -6549,7 +6857,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6549
6857
|
const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
|
|
6550
6858
|
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
6551
6859
|
particle.angleVelocity = angleSpeed;
|
|
6552
|
-
if (!this.localSpace)
|
|
6860
|
+
if (!this.localSpace && this.velocityInheritance > 0)
|
|
6553
6861
|
{
|
|
6554
6862
|
// apply emitter velocity to particle
|
|
6555
6863
|
particle.velocity.x += this.velocity.x;
|
|
@@ -6647,6 +6955,16 @@ class Particle extends EngineObject
|
|
|
6647
6955
|
this.velocity.y *= s;
|
|
6648
6956
|
}
|
|
6649
6957
|
}
|
|
6958
|
+
|
|
6959
|
+
if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
|
|
6960
|
+
{
|
|
6961
|
+
// destroy particle when its time runs out
|
|
6962
|
+
const c = this.colorEnd;
|
|
6963
|
+
this.color.set(c.r, c.g, c.b, c.a);
|
|
6964
|
+
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
6965
|
+
this.destroyCallback && this.destroyCallback(this);
|
|
6966
|
+
this.destroyed = 1;
|
|
6967
|
+
}
|
|
6650
6968
|
}
|
|
6651
6969
|
|
|
6652
6970
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
@@ -6655,7 +6973,7 @@ class Particle extends EngineObject
|
|
|
6655
6973
|
// lerp color and size
|
|
6656
6974
|
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
6657
6975
|
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
6658
|
-
|
|
6976
|
+
const size = vec2(radius);
|
|
6659
6977
|
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
6660
6978
|
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
6661
6979
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
@@ -6675,7 +6993,7 @@ class Particle extends EngineObject
|
|
|
6675
6993
|
{
|
|
6676
6994
|
// in local space of emitter
|
|
6677
6995
|
const a = this.localSpaceEmitter.angle;
|
|
6678
|
-
const c =
|
|
6996
|
+
const c = cos(a), s = sin(a);
|
|
6679
6997
|
pos = this.localSpaceEmitter.pos.add(
|
|
6680
6998
|
new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
|
|
6681
6999
|
angle += this.localSpaceEmitter.angle;
|
|
@@ -6691,22 +7009,15 @@ class Particle extends EngineObject
|
|
|
6691
7009
|
{
|
|
6692
7010
|
// stretch in direction of motion
|
|
6693
7011
|
const trailLength = speed * this.trailScale;
|
|
6694
|
-
|
|
6695
|
-
angle =
|
|
6696
|
-
drawTile(pos,
|
|
7012
|
+
size.y = max(size.x, trailLength);
|
|
7013
|
+
angle = atan2(direction.x, direction.y);
|
|
7014
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6697
7015
|
}
|
|
6698
7016
|
}
|
|
6699
7017
|
else
|
|
6700
|
-
drawTile(pos,
|
|
7018
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6701
7019
|
this.additive && setBlendMode();
|
|
6702
|
-
debugParticles && debugRect(pos,
|
|
6703
|
-
|
|
6704
|
-
if (p1 === 1)
|
|
6705
|
-
{
|
|
6706
|
-
// destroy particle when its time runs out
|
|
6707
|
-
this.destroyCallback && this.destroyCallback(this);
|
|
6708
|
-
this.destroyed = 1;
|
|
6709
|
-
}
|
|
7020
|
+
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
6710
7021
|
}
|
|
6711
7022
|
}
|
|
6712
7023
|
/**
|
|
@@ -6929,7 +7240,7 @@ let glContext;
|
|
|
6929
7240
|
let glAntialias = true;
|
|
6930
7241
|
|
|
6931
7242
|
// WebGL internal variables not exposed to documentation
|
|
6932
|
-
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount;
|
|
7243
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
|
|
6933
7244
|
|
|
6934
7245
|
// WebGL internal constants
|
|
6935
7246
|
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
@@ -6945,7 +7256,14 @@ const gl_MAX_POLY_VERTEXES = gl_ARRAY_BUFFER_SIZE / gl_POLY_VERTEX_BYTE_STRIDE |
|
|
|
6945
7256
|
// Initialize WebGL, called automatically by the engine
|
|
6946
7257
|
function glInit()
|
|
6947
7258
|
{
|
|
6948
|
-
if
|
|
7259
|
+
// keep set of texture infos so they can be restored if context is lost
|
|
7260
|
+
glTextureInfos = new Set;
|
|
7261
|
+
|
|
7262
|
+
if (!glEnable || headlessMode)
|
|
7263
|
+
{
|
|
7264
|
+
glCanBeEnabled = false;
|
|
7265
|
+
return;
|
|
7266
|
+
}
|
|
6949
7267
|
|
|
6950
7268
|
// create the canvas and textures
|
|
6951
7269
|
glCanvas = document.createElement('canvas');
|
|
@@ -6956,74 +7274,108 @@ function glInit()
|
|
|
6956
7274
|
console.warn('WebGL2 not supported, falling back to 2D canvas rendering!');
|
|
6957
7275
|
glCanvas = glContext = undefined;
|
|
6958
7276
|
glEnable = false;
|
|
7277
|
+
glCanBeEnabled = false;
|
|
6959
7278
|
return;
|
|
6960
7279
|
}
|
|
6961
7280
|
|
|
6962
|
-
//
|
|
7281
|
+
// attach the WebGL canvas
|
|
6963
7282
|
const rootElement = mainCanvas.parentElement;
|
|
6964
7283
|
rootElement.appendChild(glCanvas);
|
|
7284
|
+
|
|
7285
|
+
// startup webgl
|
|
7286
|
+
initWebGL();
|
|
7287
|
+
|
|
7288
|
+
// setup context lost and restore handlers
|
|
7289
|
+
glCanvas.addEventListener('webglcontextlost', (e)=>
|
|
7290
|
+
{
|
|
7291
|
+
glEnable = false; // disable WebGL rendering
|
|
7292
|
+
glCanvas.style.display = 'none'; // hide the gl canvas
|
|
7293
|
+
e.preventDefault(); // prevent default to allow restoration
|
|
7294
|
+
LOG('WebGL context lost! Switching to Canvas2d rendering.');
|
|
7295
|
+
|
|
7296
|
+
// remove WebGL textures
|
|
7297
|
+
for (const info of glTextureInfos)
|
|
7298
|
+
info.glTexture = undefined;
|
|
7299
|
+
glActiveTexture = undefined;
|
|
7300
|
+
pluginList.forEach(plugin=>plugin.glContextLost?.());
|
|
7301
|
+
});
|
|
7302
|
+
glCanvas.addEventListener('webglcontextrestored', ()=>
|
|
7303
|
+
{
|
|
7304
|
+
glEnable = true; // re-enable WebGL rendering
|
|
7305
|
+
glCanvas.style.display = ''; // show the gl canvas
|
|
7306
|
+
LOG('WebGL context restored, reinitializing...');
|
|
6965
7307
|
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
6973
|
-
'in float r;'+ // in: rotation
|
|
6974
|
-
'out vec2 v;'+ // out: uv
|
|
6975
|
-
'out vec4 d,e;'+ // out: color, additiveColor
|
|
6976
|
-
'void main(){'+ // shader entry point
|
|
6977
|
-
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
6978
|
-
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
6979
|
-
'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
|
|
6980
|
-
'd=c;e=a;'+ // pass colors to fragment shader
|
|
6981
|
-
'}' // end of shader
|
|
6982
|
-
,
|
|
6983
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
6984
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
6985
|
-
'uniform sampler2D s;'+ // texture
|
|
6986
|
-
'in vec2 v;'+ // in: uv
|
|
6987
|
-
'in vec4 d,e;'+ // in: color, additiveColor
|
|
6988
|
-
'out vec4 c;'+ // out: color
|
|
6989
|
-
'void main(){'+ // shader entry point
|
|
6990
|
-
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
6991
|
-
'}' // end of shader
|
|
6992
|
-
);
|
|
7308
|
+
// reinit WebGL and restore textures
|
|
7309
|
+
initWebGL();
|
|
7310
|
+
for (const info of glTextureInfos)
|
|
7311
|
+
info.glTexture = glCreateTexture(info.image);
|
|
7312
|
+
pluginList.forEach(plugin=>plugin.glContextRestored?.());
|
|
7313
|
+
});
|
|
6993
7314
|
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7315
|
+
function initWebGL()
|
|
7316
|
+
{
|
|
7317
|
+
// setup instanced rendering shader program
|
|
7318
|
+
glShader = glCreateProgram(
|
|
7319
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7320
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7321
|
+
'uniform mat4 m;'+ // transform matrix
|
|
7322
|
+
'in vec2 g;'+ // in: geometry
|
|
7323
|
+
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
7324
|
+
'in float r;'+ // in: rotation
|
|
7325
|
+
'out vec2 v;'+ // out: uv
|
|
7326
|
+
'out vec4 d,e;'+ // out: color, additiveColor
|
|
7327
|
+
'void main(){'+ // shader entry point
|
|
7328
|
+
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
7329
|
+
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
7330
|
+
'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
|
|
7331
|
+
'd=c;e=a;'+ // pass colors to fragment shader
|
|
7332
|
+
'}' // end of shader
|
|
7333
|
+
,
|
|
7334
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7335
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7336
|
+
'uniform sampler2D s;'+ // texture
|
|
7337
|
+
'in vec2 v;'+ // in: uv
|
|
7338
|
+
'in vec4 d,e;'+ // in: color, additiveColor
|
|
7339
|
+
'out vec4 c;'+ // out: color
|
|
7340
|
+
'void main(){'+ // shader entry point
|
|
7341
|
+
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
7342
|
+
'}' // end of shader
|
|
7343
|
+
);
|
|
7015
7344
|
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7345
|
+
// setup poly rendering shaders
|
|
7346
|
+
glPolyShader = glCreateProgram(
|
|
7347
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7348
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7349
|
+
'uniform mat4 m;'+ // transform matrix
|
|
7350
|
+
'in vec2 p;'+ // in: position
|
|
7351
|
+
'in vec4 c;'+ // in: color
|
|
7352
|
+
'out vec4 d;'+ // out: color
|
|
7353
|
+
'void main(){'+ // shader entry point
|
|
7354
|
+
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
7355
|
+
'd=c;'+ // pass color to fragment shader
|
|
7356
|
+
'}' // end of shader
|
|
7357
|
+
,
|
|
7358
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7359
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7360
|
+
'in vec4 d;'+ // in: color
|
|
7361
|
+
'out vec4 c;'+ // out: color
|
|
7362
|
+
'void main(){'+ // shader entry point
|
|
7363
|
+
'c=d;'+ // set color
|
|
7364
|
+
'}' // end of shader
|
|
7365
|
+
);
|
|
7022
7366
|
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7367
|
+
// init buffers
|
|
7368
|
+
const glInstanceData = new ArrayBuffer(gl_ARRAY_BUFFER_SIZE);
|
|
7369
|
+
glPositionData = new Float32Array(glInstanceData);
|
|
7370
|
+
glColorData = new Uint32Array(glInstanceData);
|
|
7371
|
+
glArrayBuffer = glContext.createBuffer();
|
|
7372
|
+
glGeometryBuffer = glContext.createBuffer();
|
|
7373
|
+
|
|
7374
|
+
// create the geometry buffer, triangle strip square
|
|
7375
|
+
const geometry = new Float32Array([glBatchCount=0,0,1,0,0,1,1,1]);
|
|
7376
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7377
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
7378
|
+
}
|
|
7027
7379
|
}
|
|
7028
7380
|
|
|
7029
7381
|
function glSetInstancedMode()
|
|
@@ -7101,8 +7453,8 @@ function glPreRender()
|
|
|
7101
7453
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
7102
7454
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
7103
7455
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
7104
|
-
const ca =
|
|
7105
|
-
const sa =
|
|
7456
|
+
const ca = cos(cameraAngle);
|
|
7457
|
+
const sa = sin(cameraAngle);
|
|
7106
7458
|
const transform = [
|
|
7107
7459
|
s.x * ca, s.y * sa, 0, 0,
|
|
7108
7460
|
-s.x * sa, s.y * ca, 0, 0,
|
|
@@ -7130,7 +7482,7 @@ function glPreRender()
|
|
|
7130
7482
|
// start with additive blending off
|
|
7131
7483
|
glAdditive = glBatchAdditive = false;
|
|
7132
7484
|
|
|
7133
|
-
// force it to
|
|
7485
|
+
// force it to set instanced mode by first setting poly mode true
|
|
7134
7486
|
glPolyMode = true;
|
|
7135
7487
|
glSetInstancedMode();
|
|
7136
7488
|
}
|
|
@@ -7271,6 +7623,41 @@ function glSetTextureData(texture, image)
|
|
|
7271
7623
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture); // rebind active texture
|
|
7272
7624
|
}
|
|
7273
7625
|
|
|
7626
|
+
/** Tells WebGL to create or update the glTexture and start tracking it
|
|
7627
|
+
* @param {TextureInfo} textureInfo
|
|
7628
|
+
* @memberof WebGL */
|
|
7629
|
+
function glRegisterTextureInfo(textureInfo)
|
|
7630
|
+
{
|
|
7631
|
+
if (headlessMode) return;
|
|
7632
|
+
|
|
7633
|
+
// add texture info to tracking list even if gl is not enabled
|
|
7634
|
+
glTextureInfos.add(textureInfo);
|
|
7635
|
+
|
|
7636
|
+
if (!glContext) return;
|
|
7637
|
+
|
|
7638
|
+
// create or set the texture data
|
|
7639
|
+
if (textureInfo.glTexture)
|
|
7640
|
+
glSetTextureData(textureInfo.glTexture, textureInfo.image);
|
|
7641
|
+
else
|
|
7642
|
+
textureInfo.glTexture = glCreateTexture(textureInfo.image);
|
|
7643
|
+
}
|
|
7644
|
+
|
|
7645
|
+
/** Tells WebGL to destroy the glTexture and stop tracking it
|
|
7646
|
+
* @param {TextureInfo} textureInfo
|
|
7647
|
+
* @memberof WebGL */
|
|
7648
|
+
function glUnregisterTextureInfo(textureInfo)
|
|
7649
|
+
{
|
|
7650
|
+
if (headlessMode) return;
|
|
7651
|
+
|
|
7652
|
+
// delete texture info from tracking list even if gl is not enabled
|
|
7653
|
+
glTextureInfos.delete(textureInfo);
|
|
7654
|
+
|
|
7655
|
+
// unset and destroy the texture
|
|
7656
|
+
const glTexture = textureInfo.glTexture;
|
|
7657
|
+
textureInfo.glTexture = undefined;
|
|
7658
|
+
glDeleteTexture(glTexture);
|
|
7659
|
+
}
|
|
7660
|
+
|
|
7274
7661
|
/** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
|
|
7275
7662
|
* @memberof WebGL */
|
|
7276
7663
|
function glFlush()
|
|
@@ -7372,8 +7759,8 @@ function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
|
|
|
7372
7759
|
// transform the point
|
|
7373
7760
|
const px = p.x*sx;
|
|
7374
7761
|
const py = p.y*sy;
|
|
7375
|
-
const sa =
|
|
7376
|
-
const ca =
|
|
7762
|
+
const sa = sin(-angle);
|
|
7763
|
+
const ca = cos(-angle);
|
|
7377
7764
|
pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
|
|
7378
7765
|
}
|
|
7379
7766
|
const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
|
|
@@ -7861,80 +8248,97 @@ class PostProcessPlugin
|
|
|
7861
8248
|
/** Create global post processing shader
|
|
7862
8249
|
* @param {string} shaderCode
|
|
7863
8250
|
* @param {boolean} [includeOverlay]
|
|
8251
|
+
* @param {boolean} [includeMainCanvas]
|
|
7864
8252
|
* @example
|
|
7865
8253
|
* // create the post process plugin object
|
|
7866
8254
|
* new PostProcessPlugin(shaderCode);
|
|
7867
8255
|
*/
|
|
7868
|
-
constructor(shaderCode, includeOverlay=false)
|
|
8256
|
+
constructor(shaderCode, includeOverlay=false, includeMainCanvas=true)
|
|
7869
8257
|
{
|
|
7870
8258
|
ASSERT(!postProcess, 'Post process already initialized');
|
|
7871
8259
|
postProcess = this;
|
|
7872
8260
|
|
|
7873
|
-
if (headlessMode) return;
|
|
7874
|
-
|
|
7875
|
-
if (!glEnable)
|
|
7876
|
-
{
|
|
7877
|
-
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
7878
|
-
return;
|
|
7879
|
-
}
|
|
7880
|
-
|
|
7881
8261
|
if (!shaderCode) // default shader pass through
|
|
7882
8262
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
7883
8263
|
|
|
7884
8264
|
/** @property {WebGLProgram} - Shader for post processing */
|
|
7885
|
-
this.shader =
|
|
7886
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
7887
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
7888
|
-
'in vec2 p;'+ // position
|
|
7889
|
-
'void main(){'+ // shader entry point
|
|
7890
|
-
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
7891
|
-
'}' // end of shader
|
|
7892
|
-
,
|
|
7893
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
7894
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
7895
|
-
'uniform sampler2D iChannel0;'+ // input texture
|
|
7896
|
-
'uniform vec3 iResolution;'+ // size of output texture
|
|
7897
|
-
'uniform float iTime;'+ // time
|
|
7898
|
-
'out vec4 c;'+ // out color
|
|
7899
|
-
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
7900
|
-
'void main(){'+ // shader entry point
|
|
7901
|
-
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
7902
|
-
'c.a=1.;'+ // always use full alpha
|
|
7903
|
-
'}' // end of shader
|
|
7904
|
-
);
|
|
8265
|
+
this.shader = undefined;
|
|
7905
8266
|
|
|
7906
8267
|
/** @property {WebGLTexture} - Texture for post processing */
|
|
7907
|
-
this.texture =
|
|
8268
|
+
this.texture = undefined;
|
|
7908
8269
|
|
|
7909
|
-
|
|
7910
|
-
|
|
8270
|
+
// setup the post processing plugin
|
|
8271
|
+
initPostProcess();
|
|
8272
|
+
engineAddPlugin(undefined, postProcessRender, postProcessContextLost, postProcessContextRestored);
|
|
7911
8273
|
|
|
7912
|
-
|
|
7913
|
-
engineAddPlugin(undefined, postProcessRender);
|
|
7914
|
-
function postProcessRender()
|
|
8274
|
+
function initPostProcess()
|
|
7915
8275
|
{
|
|
7916
8276
|
if (headlessMode) return;
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
if (glEnable)
|
|
7920
|
-
{
|
|
7921
|
-
glFlush(); // clear out the buffer
|
|
7922
|
-
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
7923
|
-
}
|
|
7924
|
-
else
|
|
8277
|
+
|
|
8278
|
+
if (!glEnable)
|
|
7925
8279
|
{
|
|
7926
|
-
|
|
7927
|
-
|
|
8280
|
+
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
8281
|
+
return;
|
|
7928
8282
|
}
|
|
7929
8283
|
|
|
7930
|
-
|
|
8284
|
+
// create resources
|
|
8285
|
+
postProcess.texture = glCreateTexture();
|
|
8286
|
+
postProcess.shader = glCreateProgram(
|
|
8287
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
8288
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
8289
|
+
'in vec2 p;'+ // position
|
|
8290
|
+
'void main(){'+ // shader entry point
|
|
8291
|
+
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
8292
|
+
'}' // end of shader
|
|
8293
|
+
,
|
|
8294
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
8295
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
8296
|
+
'uniform sampler2D iChannel0;'+ // input texture
|
|
8297
|
+
'uniform vec3 iResolution;'+ // size of output texture
|
|
8298
|
+
'uniform float iTime;'+ // time
|
|
8299
|
+
'out vec4 c;'+ // out color
|
|
8300
|
+
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
8301
|
+
'void main(){'+ // shader entry point
|
|
8302
|
+
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
8303
|
+
'c.a=1.;'+ // always use full alpha
|
|
8304
|
+
'}' // end of shader
|
|
8305
|
+
);
|
|
8306
|
+
}
|
|
8307
|
+
function postProcessContextLost()
|
|
8308
|
+
{
|
|
8309
|
+
postProcess.shader = undefined;
|
|
8310
|
+
postProcess.texture = undefined;
|
|
8311
|
+
LOG('PostProcessPlugin: WebGL context lost');
|
|
8312
|
+
}
|
|
8313
|
+
function postProcessContextRestored()
|
|
8314
|
+
{
|
|
8315
|
+
initPostProcess();
|
|
8316
|
+
LOG('PostProcessPlugin: WebGL context restored');
|
|
8317
|
+
}
|
|
8318
|
+
function postProcessRender()
|
|
8319
|
+
{
|
|
8320
|
+
if (headlessMode) return;
|
|
8321
|
+
|
|
8322
|
+
if (!glEnable)
|
|
8323
|
+
return;
|
|
8324
|
+
|
|
8325
|
+
// clear out the buffer
|
|
8326
|
+
glFlush();
|
|
8327
|
+
|
|
8328
|
+
if (includeMainCanvas || includeOverlay)
|
|
7931
8329
|
{
|
|
7932
|
-
// copy
|
|
7933
|
-
mainContext.drawImage(
|
|
7934
|
-
|
|
8330
|
+
// copy WebGL to the main canvas
|
|
8331
|
+
mainContext.drawImage(glCanvas, 0, 0);
|
|
8332
|
+
|
|
8333
|
+
if (includeOverlay)
|
|
8334
|
+
{
|
|
8335
|
+
// copy overlay canvas so it will be included in post processing
|
|
8336
|
+
mainContext.drawImage(overlayCanvas, 0, 0);
|
|
8337
|
+
overlayCanvas.width |= 0; // clear overlay canvas
|
|
8338
|
+
}
|
|
7935
8339
|
}
|
|
7936
8340
|
|
|
7937
|
-
// setup shader program to draw
|
|
8341
|
+
// setup shader program to draw a quad
|
|
7938
8342
|
glContext.useProgram(postProcess.shader);
|
|
7939
8343
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7940
8344
|
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, 1);
|
|
@@ -7943,7 +8347,10 @@ class PostProcessPlugin
|
|
|
7943
8347
|
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
7944
8348
|
glContext.activeTexture(glContext.TEXTURE0);
|
|
7945
8349
|
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
7946
|
-
|
|
8350
|
+
if (includeMainCanvas || includeOverlay)
|
|
8351
|
+
{
|
|
8352
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, mainCanvas);
|
|
8353
|
+
}
|
|
7947
8354
|
|
|
7948
8355
|
// set vertex position attribute
|
|
7949
8356
|
const vertexByteStride = 8;
|
|
@@ -8173,6 +8580,8 @@ class UISystemPlugin
|
|
|
8173
8580
|
this.defaultHoverColor = hsl(0,0,.9);
|
|
8174
8581
|
/** @property {Color} - Default color for disabled UI elements */
|
|
8175
8582
|
this.defaultDisabledColor = hsl(0,0,.3);
|
|
8583
|
+
/** @property {Color} - Uses a gradient fill combined with color */
|
|
8584
|
+
this.defaultGradientColor = undefined;
|
|
8176
8585
|
/** @property {number} - Default line width for UI elements */
|
|
8177
8586
|
this.defaultLineWidth = 4;
|
|
8178
8587
|
/** @property {number} - Default rounded rect corner radius for UI elements */
|
|
@@ -8180,7 +8589,7 @@ class UISystemPlugin
|
|
|
8180
8589
|
/** @property {number} - Default scale to use for fitting text to object */
|
|
8181
8590
|
this.defaultTextScale = .8;
|
|
8182
8591
|
/** @property {string} - Default font for UI elements */
|
|
8183
|
-
this.defaultFont =
|
|
8592
|
+
this.defaultFont = fontDefault;
|
|
8184
8593
|
/** @property {Sound} - Default sound when interactive UI element is pressed */
|
|
8185
8594
|
this.defaultSoundPress = undefined;
|
|
8186
8595
|
/** @property {Sound} - Default sound when interactive UI element is released */
|
|
@@ -8197,7 +8606,9 @@ class UISystemPlugin
|
|
|
8197
8606
|
this.hoverObject = undefined;
|
|
8198
8607
|
/** @property {UIObject} - Hover object at start of update */
|
|
8199
8608
|
this.lastHoverObject = undefined;
|
|
8200
|
-
|
|
8609
|
+
/** @property {number} - If set ui coords will be renormalized to this canvas height */
|
|
8610
|
+
this.nativeHeight = 0;
|
|
8611
|
+
|
|
8201
8612
|
engineAddPlugin(uiUpdate, uiRender);
|
|
8202
8613
|
|
|
8203
8614
|
// setup recursive update and render
|
|
@@ -8237,6 +8648,17 @@ class UISystemPlugin
|
|
|
8237
8648
|
}
|
|
8238
8649
|
function uiRender()
|
|
8239
8650
|
{
|
|
8651
|
+
const context = uiSystem.uiContext;
|
|
8652
|
+
context.save();
|
|
8653
|
+
if (uiSystem.nativeHeight)
|
|
8654
|
+
{
|
|
8655
|
+
// convert to native height
|
|
8656
|
+
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
8657
|
+
context.translate(-s*mainCanvasSize.x/2,0);
|
|
8658
|
+
context.scale(s,s);
|
|
8659
|
+
context.translate(mainCanvasSize.x/2/s,0);
|
|
8660
|
+
}
|
|
8661
|
+
|
|
8240
8662
|
function renderObject(o)
|
|
8241
8663
|
{
|
|
8242
8664
|
if (!o.visible)
|
|
@@ -8248,6 +8670,7 @@ class UISystemPlugin
|
|
|
8248
8670
|
renderObject(c);
|
|
8249
8671
|
}
|
|
8250
8672
|
uiSystem.uiObjects.forEach(o=> o.parent || renderObject(o));
|
|
8673
|
+
context.restore();
|
|
8251
8674
|
}
|
|
8252
8675
|
}
|
|
8253
8676
|
|
|
@@ -8257,8 +8680,9 @@ class UISystemPlugin
|
|
|
8257
8680
|
* @param {Color} [color=uiSystem.defaultColor]
|
|
8258
8681
|
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
8259
8682
|
* @param {Color} [lineColor=uiSystem.defaultLineColor]
|
|
8260
|
-
* @param {number} [cornerRadius=uiSystem.defaultCornerRadius]
|
|
8261
|
-
|
|
8683
|
+
* @param {number} [cornerRadius=uiSystem.defaultCornerRadius]
|
|
8684
|
+
* @param {Color} [gradientColor=uiSystem.defaultGradientColor] */
|
|
8685
|
+
drawRect(pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, cornerRadius=uiSystem.defaultCornerRadius, gradientColor=uiSystem.defaultGradientColor)
|
|
8262
8686
|
{
|
|
8263
8687
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
8264
8688
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -8268,7 +8692,18 @@ class UISystemPlugin
|
|
|
8268
8692
|
ASSERT(isNumber(cornerRadius), 'cornerRadius must be a number');
|
|
8269
8693
|
|
|
8270
8694
|
const context = uiSystem.uiContext;
|
|
8271
|
-
|
|
8695
|
+
if (gradientColor)
|
|
8696
|
+
{
|
|
8697
|
+
const g = context.createLinearGradient(
|
|
8698
|
+
pos.x, pos.y-size.y/2, pos.x, pos.y+size.y/2);
|
|
8699
|
+
const c = color.toString();
|
|
8700
|
+
g.addColorStop(0, c);
|
|
8701
|
+
g.addColorStop(.5, gradientColor.toString());
|
|
8702
|
+
g.addColorStop(1, c);
|
|
8703
|
+
context.fillStyle = g;
|
|
8704
|
+
}
|
|
8705
|
+
else
|
|
8706
|
+
context.fillStyle = color.toString();
|
|
8272
8707
|
context.beginPath();
|
|
8273
8708
|
if (cornerRadius && context['roundRect'])
|
|
8274
8709
|
context['roundRect'](pos.x-size.x/2, pos.y-size.y/2, size.x, size.y, cornerRadius);
|
|
@@ -8325,10 +8760,11 @@ class UISystemPlugin
|
|
|
8325
8760
|
* @param {Color} [lineColor=uiSystem.defaultLineColor]
|
|
8326
8761
|
* @param {string} [align]
|
|
8327
8762
|
* @param {string} [font=uiSystem.defaultFont]
|
|
8763
|
+
* @param {string} [fontStyle]
|
|
8328
8764
|
* @param {boolean} [applyMaxWidth=true] */
|
|
8329
|
-
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, applyMaxWidth=true)
|
|
8765
|
+
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, fontStyle='', applyMaxWidth=true)
|
|
8330
8766
|
{
|
|
8331
|
-
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, applyMaxWidth ? size.x : undefined, uiSystem.uiContext);
|
|
8767
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, uiSystem.uiContext);
|
|
8332
8768
|
}
|
|
8333
8769
|
|
|
8334
8770
|
/**
|
|
@@ -8389,17 +8825,21 @@ class UIObject
|
|
|
8389
8825
|
/** @property {boolean} - Is this object disabled? */
|
|
8390
8826
|
this.disabled = false;
|
|
8391
8827
|
/** @property {Color} - Color for text */
|
|
8392
|
-
this.textColor = uiSystem.defaultTextColor.copy()
|
|
8828
|
+
this.textColor = uiSystem.defaultTextColor.copy();
|
|
8393
8829
|
/** @property {Color} - Color used when hovering over the object */
|
|
8394
|
-
this.hoverColor = uiSystem.defaultHoverColor.copy()
|
|
8830
|
+
this.hoverColor = uiSystem.defaultHoverColor.copy();
|
|
8395
8831
|
/** @property {Color} - Color for line drawing */
|
|
8396
|
-
this.lineColor = uiSystem.defaultLineColor.copy()
|
|
8832
|
+
this.lineColor = uiSystem.defaultLineColor.copy();
|
|
8833
|
+
/** @property {Color} - Uses a gradient fill combined with color */
|
|
8834
|
+
this.gradientColor = uiSystem.defaultGradientColor ? uiSystem.defaultGradientColor.copy() : undefined;
|
|
8397
8835
|
/** @property {number} - Width for line drawing */
|
|
8398
8836
|
this.lineWidth = uiSystem.defaultLineWidth;
|
|
8399
8837
|
/** @property {number} - Corner radius for rounded rects */
|
|
8400
8838
|
this.cornerRadius = uiSystem.defaultCornerRadius;
|
|
8401
8839
|
/** @property {string} - Font for this objecct */
|
|
8402
8840
|
this.font = uiSystem.defaultFont;
|
|
8841
|
+
/** @property {string} - Font style for this object or undefined */
|
|
8842
|
+
this.fontStyle = undefined;
|
|
8403
8843
|
/** @property {number} - Override for text width */
|
|
8404
8844
|
this.textWidth = undefined;
|
|
8405
8845
|
/** @property {number} - Override for text height */
|
|
@@ -8424,6 +8864,8 @@ class UIObject
|
|
|
8424
8864
|
this.interactive = false;
|
|
8425
8865
|
/** @property {boolean} - Activate when dragged over with mouse held down */
|
|
8426
8866
|
this.dragActivate = false;
|
|
8867
|
+
/** @property {boolean} - True if this can be a hover object */
|
|
8868
|
+
this.canBeHover = true;
|
|
8427
8869
|
uiSystem.uiObjects.push(this);
|
|
8428
8870
|
}
|
|
8429
8871
|
|
|
@@ -8447,6 +8889,26 @@ class UIObject
|
|
|
8447
8889
|
child.parent = undefined;
|
|
8448
8890
|
}
|
|
8449
8891
|
|
|
8892
|
+
/** Check if the mouse is overlapping a box in screen space
|
|
8893
|
+
* @return {boolean} - True if overlapping
|
|
8894
|
+
*/
|
|
8895
|
+
isMouseOverlapping()
|
|
8896
|
+
{
|
|
8897
|
+
const size = !isTouchDevice ? this.size :
|
|
8898
|
+
this.size.add(vec2(this.extraTouchSize || 0));
|
|
8899
|
+
if (!uiSystem.nativeHeight)
|
|
8900
|
+
return isOverlapping(this.pos, size, mousePosScreen);
|
|
8901
|
+
|
|
8902
|
+
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
8903
|
+
const sInv = 1/s;
|
|
8904
|
+
let pos = mousePosScreen.copy();
|
|
8905
|
+
pos.x += s*mainCanvasSize.x/2;
|
|
8906
|
+
pos.x *= sInv;
|
|
8907
|
+
pos.y *= sInv;
|
|
8908
|
+
pos.x -= sInv*mainCanvasSize.x/2;
|
|
8909
|
+
return isOverlapping(this.pos, size, pos);
|
|
8910
|
+
}
|
|
8911
|
+
|
|
8450
8912
|
/** Update the object, called automatically by plugin once each frame */
|
|
8451
8913
|
update()
|
|
8452
8914
|
{
|
|
@@ -8454,13 +8916,10 @@ class UIObject
|
|
|
8454
8916
|
const isActive = this.isActiveObject();
|
|
8455
8917
|
const mouseDown = mouseIsDown(0);
|
|
8456
8918
|
const mousePress = this.dragActivate ? mouseDown : mouseWasPressed(0);
|
|
8457
|
-
if (
|
|
8919
|
+
if (this.canBeHover)
|
|
8458
8920
|
if (mousePress || isActive || (!mouseDown && !isTouchDevice))
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
if (isOverlapping(this.pos, size, mousePosScreen))
|
|
8462
|
-
uiSystem.hoverObject = this;
|
|
8463
|
-
}
|
|
8921
|
+
if (!uiSystem.hoverObject && this.isMouseOverlapping())
|
|
8922
|
+
uiSystem.hoverObject = this;
|
|
8464
8923
|
if (this.isHoverObject())
|
|
8465
8924
|
{
|
|
8466
8925
|
if (!this.disabled)
|
|
@@ -8585,11 +9044,13 @@ class UIText extends UIObject
|
|
|
8585
9044
|
|
|
8586
9045
|
// make text not outlined by default
|
|
8587
9046
|
this.lineWidth = 0;
|
|
9047
|
+
// text can not be a hover object by default
|
|
9048
|
+
this.canBeHover = false;
|
|
8588
9049
|
}
|
|
8589
9050
|
render()
|
|
8590
9051
|
{
|
|
8591
9052
|
const textSize = this.getTextSize();
|
|
8592
|
-
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
|
|
9053
|
+
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle);
|
|
8593
9054
|
}
|
|
8594
9055
|
}
|
|
8595
9056
|
|
|
@@ -8655,7 +9116,7 @@ class UIButton extends UIObject
|
|
|
8655
9116
|
|
|
8656
9117
|
// set properties
|
|
8657
9118
|
this.text = text;
|
|
8658
|
-
this.color = color.copy()
|
|
9119
|
+
this.color = color.copy();
|
|
8659
9120
|
this.interactive = true;
|
|
8660
9121
|
}
|
|
8661
9122
|
render()
|
|
@@ -8665,7 +9126,7 @@ class UIButton extends UIObject
|
|
|
8665
9126
|
// draw the text scaled to fit
|
|
8666
9127
|
const textSize = this.getTextSize();
|
|
8667
9128
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
8668
|
-
this.textColor, 0, undefined, this.align, this.font);
|
|
9129
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
8669
9130
|
}
|
|
8670
9131
|
}
|
|
8671
9132
|
|
|
@@ -8719,7 +9180,7 @@ class UICheckbox extends UIObject
|
|
|
8719
9180
|
const textSize = this.getTextSize();
|
|
8720
9181
|
const pos = this.pos.add(vec2(this.size.x,0));
|
|
8721
9182
|
uiSystem.drawText(this.text, pos, textSize,
|
|
8722
|
-
this.textColor, 0, undefined, 'left', this.font, false);
|
|
9183
|
+
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false);
|
|
8723
9184
|
}
|
|
8724
9185
|
}
|
|
8725
9186
|
|
|
@@ -8797,14 +9258,13 @@ class UIScrollbar extends UIObject
|
|
|
8797
9258
|
const handlePos = isHorizontal ?
|
|
8798
9259
|
vec2(lerp(p1, p2, this.value), this.pos.y) :
|
|
8799
9260
|
vec2(this.pos.x, lerp(p2, p1, this.value))
|
|
8800
|
-
const handleColor = this.disabled ? this.disabledColor :
|
|
8801
|
-
this.interactive && this.isActiveObject() ? this.color : this.handleColor;
|
|
9261
|
+
const handleColor = this.disabled ? this.disabledColor : this.handleColor;
|
|
8802
9262
|
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
8803
9263
|
|
|
8804
9264
|
// draw the text scaled to fit on the scrollbar
|
|
8805
9265
|
const textSize = this.getTextSize();
|
|
8806
9266
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
8807
|
-
this.textColor, 0, undefined, this.align, this.font);
|
|
9267
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
8808
9268
|
}
|
|
8809
9269
|
}
|
|
8810
9270
|
/**
|
|
@@ -10822,6 +11282,7 @@ export
|
|
|
10822
11282
|
gamepadDirectionEmulateStick,
|
|
10823
11283
|
inputWASDEmulateDirection,
|
|
10824
11284
|
touchGamepadEnable,
|
|
11285
|
+
touchGamepadCenterButton,
|
|
10825
11286
|
touchGamepadAnalog,
|
|
10826
11287
|
touchGamepadSize,
|
|
10827
11288
|
touchGamepadAlpha,
|
|
@@ -10865,6 +11326,7 @@ export
|
|
|
10865
11326
|
setGamepadDirectionEmulateStick,
|
|
10866
11327
|
setInputWASDEmulateDirection,
|
|
10867
11328
|
setTouchGamepadEnable,
|
|
11329
|
+
setTouchGamepadCenterButton,
|
|
10868
11330
|
setTouchGamepadAnalog,
|
|
10869
11331
|
setTouchGamepadSize,
|
|
10870
11332
|
setTouchGamepadAlpha,
|
|
@@ -10883,9 +11345,18 @@ export
|
|
|
10883
11345
|
// Utilities
|
|
10884
11346
|
PI,
|
|
10885
11347
|
abs,
|
|
11348
|
+
floor,
|
|
11349
|
+
ceil,
|
|
11350
|
+
round,
|
|
10886
11351
|
min,
|
|
10887
11352
|
max,
|
|
10888
11353
|
sign,
|
|
11354
|
+
hypot,
|
|
11355
|
+
log2,
|
|
11356
|
+
sin,
|
|
11357
|
+
cos,
|
|
11358
|
+
tan,
|
|
11359
|
+
atan2,
|
|
10889
11360
|
mod,
|
|
10890
11361
|
clamp,
|
|
10891
11362
|
percent,
|
|
@@ -10953,6 +11424,8 @@ export
|
|
|
10953
11424
|
drawCount,
|
|
10954
11425
|
screenToWorld,
|
|
10955
11426
|
worldToScreen,
|
|
11427
|
+
screenToWorldDelta,
|
|
11428
|
+
worldToScreenDelta,
|
|
10956
11429
|
drawTile,
|
|
10957
11430
|
drawRect,
|
|
10958
11431
|
drawRectGradient,
|