littlejsengine 1.14.19 → 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 +118 -18
- package/dist/littlejs.esm.js +506 -199
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +493 -199
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +468 -200
- 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 +12 -6
- package/examples/module/game.js +5 -7
- package/examples/particles/index.html +6 -9
- package/examples/platformer/gameCharacter.js +1 -1
- package/examples/platformer/gameEffects.js +7 -2
- 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/postProcess.js +1 -1
- package/examples/shorts/sequencer.js +3 -3
- package/examples/shorts/spaceGame.js +1 -1
- package/examples/shorts/spriteAtlas.js +6 -6
- package/examples/shorts/starfield.js +1 -1
- package/examples/shorts/tileRaycast.js +39 -0
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +1 -1
- package/examples/starter/game.js +5 -7
- package/examples/starter/index.html +2 -2
- package/examples/typescript/game.js +3 -2
- package/examples/typescript/game.ts +5 -7
- package/examples/uiSystem/game.js +2 -2
- package/package.json +1 -1
- package/src/engine.js +3 -3
- package/src/engineAudio.js +13 -13
- package/src/engineDebug.js +27 -0
- package/src/engineDraw.js +68 -20
- package/src/engineExport.js +13 -0
- package/src/engineInput.js +14 -16
- package/src/engineObject.js +31 -6
- package/src/engineParticles.js +36 -15
- package/src/engineRelease.js +2 -1
- package/src/engineSettings.js +20 -1
- package/src/engineTileLayer.js +62 -59
- package/src/engineUtilities.js +206 -58
- package/src/engineWebGL.js +13 -8
- 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}
|
|
@@ -516,7 +516,7 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
516
516
|
|
|
517
517
|
/**
|
|
518
518
|
* @callback ObjectCallbackFunction - Function that processes an object
|
|
519
|
-
* @param {EngineObject}
|
|
519
|
+
* @param {EngineObject} object
|
|
520
520
|
* @memberof Engine
|
|
521
521
|
*/
|
|
522
522
|
|
|
@@ -564,7 +564,7 @@ function drawEngineSplashScreen(t)
|
|
|
564
564
|
// background
|
|
565
565
|
const p3 = percent(t, 1, .8);
|
|
566
566
|
const p4 = percent(t, 0, .5);
|
|
567
|
-
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);
|
|
568
568
|
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
569
569
|
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
570
570
|
x.save();
|
|
@@ -1339,6 +1339,33 @@ function debugVideoCaptureUpdate()
|
|
|
1339
1339
|
combineCanvases();
|
|
1340
1340
|
debugVideoCaptureTrack.requestFrame();
|
|
1341
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);
|
|
1342
1369
|
}
|
|
1343
1370
|
/**
|
|
1344
1371
|
* LittleJS Utility Classes and Functions
|
|
@@ -1350,7 +1377,7 @@ function debugVideoCaptureUpdate()
|
|
|
1350
1377
|
* @namespace Utilities
|
|
1351
1378
|
*/
|
|
1352
1379
|
|
|
1353
|
-
/**
|
|
1380
|
+
/** The value of PI
|
|
1354
1381
|
* @type {number}
|
|
1355
1382
|
* @default Math.PI
|
|
1356
1383
|
* @memberof Utilities */
|
|
@@ -1360,25 +1387,80 @@ const PI = Math.PI;
|
|
|
1360
1387
|
* @param {number} value
|
|
1361
1388
|
* @return {number}
|
|
1362
1389
|
* @memberof Utilities */
|
|
1363
|
-
|
|
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;
|
|
1364
1409
|
|
|
1365
1410
|
/** Returns lowest value passed in
|
|
1366
1411
|
* @param {...number} values
|
|
1367
1412
|
* @return {number}
|
|
1368
1413
|
* @memberof Utilities */
|
|
1369
|
-
|
|
1414
|
+
const min = Math.min;
|
|
1370
1415
|
|
|
1371
1416
|
/** Returns highest value passed in
|
|
1372
1417
|
* @param {...number} values
|
|
1373
1418
|
* @return {number}
|
|
1374
1419
|
* @memberof Utilities */
|
|
1375
|
-
|
|
1420
|
+
const max = Math.max;
|
|
1376
1421
|
|
|
1377
1422
|
/** Returns the sign of value passed in
|
|
1378
1423
|
* @param {number} value
|
|
1379
1424
|
* @return {number}
|
|
1380
1425
|
* @memberof Utilities */
|
|
1381
|
-
|
|
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;
|
|
1382
1464
|
|
|
1383
1465
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
1384
1466
|
* @param {number} dividend
|
|
@@ -1483,7 +1565,7 @@ function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
|
1483
1565
|
* @param {number} value
|
|
1484
1566
|
* @return {number}
|
|
1485
1567
|
* @memberof Utilities */
|
|
1486
|
-
function nearestPowerOfTwo(value) { return 2**
|
|
1568
|
+
function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
1487
1569
|
|
|
1488
1570
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
1489
1571
|
* this can be used for simple collision detection between objects
|
|
@@ -1551,7 +1633,7 @@ function isIntersecting(start, end, pos, size)
|
|
|
1551
1633
|
* @return {number} - Value waving between 0 and amplitude
|
|
1552
1634
|
* @memberof Utilities */
|
|
1553
1635
|
function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
1554
|
-
{ return amplitude/2 * (1 -
|
|
1636
|
+
{ return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
|
|
1555
1637
|
|
|
1556
1638
|
/** Formats seconds to mm:ss style for display purposes
|
|
1557
1639
|
* @param {number} t - time in seconds
|
|
@@ -1590,6 +1672,96 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
|
1590
1672
|
* @memberof Utilities */
|
|
1591
1673
|
function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
|
|
1592
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
|
+
|
|
1593
1765
|
///////////////////////////////////////////////////////////////////////////////
|
|
1594
1766
|
|
|
1595
1767
|
/** Random global functions
|
|
@@ -1608,7 +1780,7 @@ function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valu
|
|
|
1608
1780
|
* @param {number} [valueB]
|
|
1609
1781
|
* @return {number}
|
|
1610
1782
|
* @memberof Random */
|
|
1611
|
-
function randInt(valueA, valueB=0) { return
|
|
1783
|
+
function randInt(valueA, valueB=0) { return floor(rand(valueA,valueB)); }
|
|
1612
1784
|
|
|
1613
1785
|
/** Randomly returns true or false given the chance of true passed in
|
|
1614
1786
|
* @param {number} [chance]
|
|
@@ -1687,7 +1859,7 @@ class RandomGenerator
|
|
|
1687
1859
|
* @param {number} valueA
|
|
1688
1860
|
* @param {number} [valueB]
|
|
1689
1861
|
* @return {number} */
|
|
1690
|
-
int(valueA, valueB=0) { return
|
|
1862
|
+
int(valueA, valueB=0) { return floor(this.float(valueA, valueB)); }
|
|
1691
1863
|
|
|
1692
1864
|
/** Randomly returns true or false given the chance of true passed in
|
|
1693
1865
|
* @param {number} [chance]
|
|
@@ -1714,6 +1886,39 @@ class RandomGenerator
|
|
|
1714
1886
|
* @return {Vector2} */
|
|
1715
1887
|
vec2(valueA=1, valueB=0)
|
|
1716
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
|
+
}
|
|
1717
1922
|
}
|
|
1718
1923
|
|
|
1719
1924
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1865,7 +2070,7 @@ class Vector2
|
|
|
1865
2070
|
|
|
1866
2071
|
/** Returns the clockwise angle of this vector, up is angle 0
|
|
1867
2072
|
* @return {number} */
|
|
1868
|
-
angle() { return
|
|
2073
|
+
angle() { return atan2(this.x, this.y); }
|
|
1869
2074
|
|
|
1870
2075
|
/** Sets this vector with clockwise angle and length passed in
|
|
1871
2076
|
* @param {number} [angle]
|
|
@@ -1875,8 +2080,8 @@ class Vector2
|
|
|
1875
2080
|
{
|
|
1876
2081
|
ASSERT_NUMBER_VALID(angle);
|
|
1877
2082
|
ASSERT_NUMBER_VALID(length);
|
|
1878
|
-
this.x = length*
|
|
1879
|
-
this.y = length*
|
|
2083
|
+
this.x = length*sin(angle);
|
|
2084
|
+
this.y = length*cos(angle);
|
|
1880
2085
|
return this;
|
|
1881
2086
|
}
|
|
1882
2087
|
|
|
@@ -1886,7 +2091,7 @@ class Vector2
|
|
|
1886
2091
|
rotate(angle)
|
|
1887
2092
|
{
|
|
1888
2093
|
ASSERT_NUMBER_VALID(angle);
|
|
1889
|
-
const c =
|
|
2094
|
+
const c = cos(-angle), s = sin(-angle);
|
|
1890
2095
|
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
1891
2096
|
}
|
|
1892
2097
|
|
|
@@ -1918,7 +2123,7 @@ class Vector2
|
|
|
1918
2123
|
|
|
1919
2124
|
/** Returns a copy of this vector with each axis floored
|
|
1920
2125
|
* @return {Vector2} */
|
|
1921
|
-
floor() { return new Vector2(
|
|
2126
|
+
floor() { return new Vector2(floor(this.x), floor(this.y)); }
|
|
1922
2127
|
|
|
1923
2128
|
/** Returns new vec2 with modded values
|
|
1924
2129
|
* @param {number} [divisor]
|
|
@@ -2226,67 +2431,67 @@ class Color
|
|
|
2226
2431
|
/** Color - White #ffffff
|
|
2227
2432
|
* @type {Color}
|
|
2228
2433
|
* @memberof Utilities */
|
|
2229
|
-
const WHITE =
|
|
2434
|
+
const WHITE = debugProtectConstant(rgb());
|
|
2230
2435
|
|
|
2231
2436
|
/** Color - Clear White #757474ff with 0 alpha
|
|
2232
2437
|
* @type {Color}
|
|
2233
2438
|
* @memberof Utilities */
|
|
2234
|
-
const CLEAR_WHITE =
|
|
2439
|
+
const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
2235
2440
|
|
|
2236
2441
|
/** Color - Black #000000
|
|
2237
2442
|
* @type {Color}
|
|
2238
2443
|
* @memberof Utilities */
|
|
2239
|
-
const BLACK =
|
|
2444
|
+
const BLACK = debugProtectConstant(rgb(0,0,0));
|
|
2240
2445
|
|
|
2241
2446
|
/** Color - Clear Black #000000 with 0 alpha
|
|
2242
2447
|
* @type {Color}
|
|
2243
2448
|
* @memberof Utilities */
|
|
2244
|
-
const CLEAR_BLACK =
|
|
2449
|
+
const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
|
|
2245
2450
|
|
|
2246
2451
|
/** Color - Gray #808080
|
|
2247
2452
|
* @type {Color}
|
|
2248
2453
|
* @memberof Utilities */
|
|
2249
|
-
const GRAY =
|
|
2454
|
+
const GRAY = debugProtectConstant(rgb(.5,.5,.5));
|
|
2250
2455
|
|
|
2251
2456
|
/** Color - Red #ff0000
|
|
2252
2457
|
* @type {Color}
|
|
2253
2458
|
* @memberof Utilities */
|
|
2254
|
-
const RED =
|
|
2459
|
+
const RED = debugProtectConstant(rgb(1,0,0));
|
|
2255
2460
|
|
|
2256
2461
|
/** Color - Orange #ff8000
|
|
2257
2462
|
* @type {Color}
|
|
2258
2463
|
* @memberof Utilities */
|
|
2259
|
-
const ORANGE =
|
|
2464
|
+
const ORANGE = debugProtectConstant(rgb(1,.5,0));
|
|
2260
2465
|
|
|
2261
2466
|
/** Color - Yellow #ffff00
|
|
2262
2467
|
* @type {Color}
|
|
2263
2468
|
* @memberof Utilities */
|
|
2264
|
-
const YELLOW =
|
|
2469
|
+
const YELLOW = debugProtectConstant(rgb(1,1,0));
|
|
2265
2470
|
|
|
2266
2471
|
/** Color - Green #00ff00
|
|
2267
2472
|
* @type {Color}
|
|
2268
2473
|
* @memberof Utilities */
|
|
2269
|
-
const GREEN =
|
|
2474
|
+
const GREEN = debugProtectConstant(rgb(0,1,0));
|
|
2270
2475
|
|
|
2271
2476
|
/** Color - Cyan #00ffff
|
|
2272
2477
|
* @type {Color}
|
|
2273
2478
|
* @memberof Utilities */
|
|
2274
|
-
const CYAN =
|
|
2479
|
+
const CYAN = debugProtectConstant(rgb(0,1,1));
|
|
2275
2480
|
|
|
2276
2481
|
/** Color - Blue #0000ff
|
|
2277
2482
|
* @type {Color}
|
|
2278
2483
|
* @memberof Utilities */
|
|
2279
|
-
const BLUE =
|
|
2484
|
+
const BLUE = debugProtectConstant(rgb(0,0,1));
|
|
2280
2485
|
|
|
2281
2486
|
/** Color - Purple #8000ff
|
|
2282
2487
|
* @type {Color}
|
|
2283
2488
|
* @memberof Utilities */
|
|
2284
|
-
const PURPLE =
|
|
2489
|
+
const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
2285
2490
|
|
|
2286
2491
|
/** Color - Magenta #ff00ff
|
|
2287
2492
|
* @type {Color}
|
|
2288
2493
|
* @memberof Utilities */
|
|
2289
|
-
const MAGENTA =
|
|
2494
|
+
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
2290
2495
|
|
|
2291
2496
|
///////////////////////////////////////////////////////////////////////////////
|
|
2292
2497
|
|
|
@@ -2349,41 +2554,11 @@ class Timer
|
|
|
2349
2554
|
|
|
2350
2555
|
/** Returns this timer expressed as a string
|
|
2351
2556
|
* @return {string} */
|
|
2352
|
-
toString() { return this.isSet() ?
|
|
2557
|
+
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
2353
2558
|
|
|
2354
2559
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2355
2560
|
* @return {number} */
|
|
2356
2561
|
valueOf() { return this.get(); }
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2360
|
-
// Helper functions used by the engine
|
|
2361
|
-
|
|
2362
|
-
// make color constants immutable with debug assertions
|
|
2363
|
-
function protectEngineConstant(obj)
|
|
2364
|
-
{
|
|
2365
|
-
if (debug)
|
|
2366
|
-
{
|
|
2367
|
-
// get properties and store original values
|
|
2368
|
-
const props = Object.keys(obj), values = {};
|
|
2369
|
-
props.forEach(prop => values[prop] = obj[prop]);
|
|
2370
|
-
|
|
2371
|
-
// replace with getters/setters that assert
|
|
2372
|
-
props.forEach(prop =>
|
|
2373
|
-
{
|
|
2374
|
-
Object.defineProperty(obj, prop, {
|
|
2375
|
-
get: () => values[prop],
|
|
2376
|
-
set: (value) =>
|
|
2377
|
-
{
|
|
2378
|
-
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
2379
|
-
},
|
|
2380
|
-
enumerable: true
|
|
2381
|
-
});
|
|
2382
|
-
});
|
|
2383
|
-
}
|
|
2384
|
-
|
|
2385
|
-
// freeze the object to prevent adding new properties
|
|
2386
|
-
return Object.freeze(obj);
|
|
2387
2562
|
}
|
|
2388
2563
|
/**
|
|
2389
2564
|
* LittleJS Engine Settings
|
|
@@ -2601,6 +2776,14 @@ let touchInputEnable = true;
|
|
|
2601
2776
|
* @memberof Settings */
|
|
2602
2777
|
let touchGamepadEnable = false;
|
|
2603
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
|
+
|
|
2604
2787
|
/** True if touch gamepad should be analog stick or false to use if 8 way dpad
|
|
2605
2788
|
* @type {boolean}
|
|
2606
2789
|
* @default
|
|
@@ -2776,9 +2959,14 @@ function setHeadlessMode(headless) { headlessMode = headless; }
|
|
|
2776
2959
|
* @memberof Settings */
|
|
2777
2960
|
function setGLEnable(enable)
|
|
2778
2961
|
{
|
|
2962
|
+
if (enable && !glCanBeEnabled)
|
|
2963
|
+
{
|
|
2964
|
+
console.warn('Can not enable WebGL if it was disabled on start.');
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2779
2967
|
glEnable = enable;
|
|
2780
2968
|
if (glCanvas) // hide glCanvas if WebGL is disabled
|
|
2781
|
-
glCanvas.style.
|
|
2969
|
+
glCanvas.style.display = enable ? '' : 'none';
|
|
2782
2970
|
}
|
|
2783
2971
|
|
|
2784
2972
|
/** Set how many sided polygons to use when drawing circles and ellipses with WebGL
|
|
@@ -2866,6 +3054,12 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
|
|
|
2866
3054
|
* @memberof Settings */
|
|
2867
3055
|
function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
|
|
2868
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
|
+
|
|
2869
3063
|
/** Set if touch gamepad should be analog stick or 8 way dpad
|
|
2870
3064
|
* @param {boolean} analog
|
|
2871
3065
|
* @memberof Settings */
|
|
@@ -3227,6 +3421,27 @@ class EngineObject
|
|
|
3227
3421
|
// test which side we bounced off (or both if a corner)
|
|
3228
3422
|
const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
3229
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
|
+
}
|
|
3230
3445
|
if (blockedLayerY || !blockedLayerX)
|
|
3231
3446
|
{
|
|
3232
3447
|
// bounce velocity
|
|
@@ -3250,12 +3465,6 @@ class EngineObject
|
|
|
3250
3465
|
this.groundObject = undefined;
|
|
3251
3466
|
}
|
|
3252
3467
|
}
|
|
3253
|
-
if (blockedLayerX)
|
|
3254
|
-
{
|
|
3255
|
-
// move to previous position and bounce
|
|
3256
|
-
this.pos.x = oldPos.x;
|
|
3257
|
-
this.velocity.x *= -this.restitution;
|
|
3258
|
-
}
|
|
3259
3468
|
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
3260
3469
|
}
|
|
3261
3470
|
}
|
|
@@ -3313,6 +3522,16 @@ class EngineObject
|
|
|
3313
3522
|
*/
|
|
3314
3523
|
collideWithObject(object) { return true; }
|
|
3315
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
|
+
|
|
3316
3535
|
/** How long since the object was created
|
|
3317
3536
|
* @return {number} */
|
|
3318
3537
|
getAliveTime() { return time - this.spawnTime; }
|
|
@@ -3664,6 +3883,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3664
3883
|
const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
|
|
3665
3884
|
if (useWebGL)
|
|
3666
3885
|
{
|
|
3886
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3667
3887
|
if (screenSpace)
|
|
3668
3888
|
{
|
|
3669
3889
|
// convert to world space
|
|
@@ -3759,6 +3979,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3759
3979
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3760
3980
|
if (useWebGL)
|
|
3761
3981
|
{
|
|
3982
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3762
3983
|
if (screenSpace)
|
|
3763
3984
|
{
|
|
3764
3985
|
// convert to world space
|
|
@@ -3770,7 +3991,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3770
3991
|
const halfSizeX = size.x/2, halfSizeY = size.y/2;
|
|
3771
3992
|
const colorTopInt = colorTop.rgbaInt();
|
|
3772
3993
|
const colorBottomInt = colorBottom.rgbaInt();
|
|
3773
|
-
const c =
|
|
3994
|
+
const c = cos(-angle), s = sin(-angle);
|
|
3774
3995
|
for (let i=4; i--;)
|
|
3775
3996
|
{
|
|
3776
3997
|
const x = i & 1 ? halfSizeX : -halfSizeX;
|
|
@@ -3821,6 +4042,7 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3821
4042
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3822
4043
|
if (useWebGL)
|
|
3823
4044
|
{
|
|
4045
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3824
4046
|
let scale = 1;
|
|
3825
4047
|
if (screenSpace)
|
|
3826
4048
|
{
|
|
@@ -3870,6 +4092,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
3870
4092
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
3871
4093
|
const size = vec2(width, halfDelta.length()*2);
|
|
3872
4094
|
pos = pos.add(posA.add(halfDelta));
|
|
4095
|
+
if (screenSpace)
|
|
4096
|
+
halfDelta.y *= -1; // flip angle Y if screen space
|
|
3873
4097
|
angle += halfDelta.angle();
|
|
3874
4098
|
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
3875
4099
|
}
|
|
@@ -3897,7 +4121,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
3897
4121
|
for (let i=sides; i--;)
|
|
3898
4122
|
{
|
|
3899
4123
|
const a = (i/sides)*PI*2;
|
|
3900
|
-
points.push(vec2(
|
|
4124
|
+
points.push(vec2(sin(a)*sizeX, cos(a)*sizeY));
|
|
3901
4125
|
}
|
|
3902
4126
|
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
|
|
3903
4127
|
}
|
|
@@ -3924,6 +4148,7 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
3924
4148
|
|
|
3925
4149
|
if (useWebGL)
|
|
3926
4150
|
{
|
|
4151
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3927
4152
|
let scale = 1;
|
|
3928
4153
|
if (screenSpace)
|
|
3929
4154
|
{
|
|
@@ -4145,18 +4370,18 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
4145
4370
|
* @memberof Draw */
|
|
4146
4371
|
function screenToWorld(screenPos)
|
|
4147
4372
|
{
|
|
4148
|
-
let
|
|
4149
|
-
let
|
|
4373
|
+
let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
4374
|
+
let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
4150
4375
|
if (cameraAngle)
|
|
4151
4376
|
{
|
|
4152
4377
|
// apply camera rotation
|
|
4153
|
-
const
|
|
4154
|
-
const rotatedX =
|
|
4155
|
-
const rotatedY =
|
|
4156
|
-
|
|
4157
|
-
|
|
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;
|
|
4158
4383
|
}
|
|
4159
|
-
return new Vector2(
|
|
4384
|
+
return new Vector2(x + cameraPos.x, y + cameraPos.y);
|
|
4160
4385
|
}
|
|
4161
4386
|
|
|
4162
4387
|
/** Convert from world to screen space coordinates
|
|
@@ -4165,24 +4390,64 @@ function screenToWorld(screenPos)
|
|
|
4165
4390
|
* @memberof Draw */
|
|
4166
4391
|
function worldToScreen(worldPos)
|
|
4167
4392
|
{
|
|
4168
|
-
let
|
|
4169
|
-
let
|
|
4393
|
+
let x = worldPos.x - cameraPos.x;
|
|
4394
|
+
let y = worldPos.y - cameraPos.y;
|
|
4170
4395
|
if (cameraAngle)
|
|
4171
4396
|
{
|
|
4172
4397
|
// apply inverse camera rotation
|
|
4173
|
-
const
|
|
4174
|
-
const rotatedX =
|
|
4175
|
-
const rotatedY =
|
|
4176
|
-
|
|
4177
|
-
|
|
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;
|
|
4178
4403
|
}
|
|
4179
4404
|
return new Vector2
|
|
4180
4405
|
(
|
|
4181
|
-
|
|
4182
|
-
|
|
4406
|
+
x * cameraScale + mainCanvasSize.x/2 - .5,
|
|
4407
|
+
y * -cameraScale + mainCanvasSize.y/2 - .5
|
|
4183
4408
|
);
|
|
4184
4409
|
}
|
|
4185
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
|
+
|
|
4186
4451
|
/** Get the camera's visible area in world space
|
|
4187
4452
|
* @return {Vector2}
|
|
4188
4453
|
* @memberof Draw */
|
|
@@ -4235,6 +4500,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
4235
4500
|
function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
|
|
4236
4501
|
const sx2 = bleedScale;
|
|
4237
4502
|
const sy2 = bleedScale;
|
|
4503
|
+
sWidth = max(1,sWidth|0);
|
|
4504
|
+
sHeight = max(1,sHeight|0);
|
|
4238
4505
|
const sWidth2 = sWidth - 2*bleedScale;
|
|
4239
4506
|
const sHeight2 = sHeight - 2*bleedScale;
|
|
4240
4507
|
if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
|
|
@@ -4249,7 +4516,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
4249
4516
|
// copy to offscreen canvas
|
|
4250
4517
|
workCanvas.width = sWidth;
|
|
4251
4518
|
workCanvas.height = sHeight;
|
|
4252
|
-
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);
|
|
4253
4520
|
|
|
4254
4521
|
// tint image using offscreen work context
|
|
4255
4522
|
const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
|
|
@@ -4586,9 +4853,9 @@ function inputUpdate()
|
|
|
4586
4853
|
if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
4587
4854
|
inputClear();
|
|
4588
4855
|
|
|
4589
|
-
// update mouse world space position
|
|
4856
|
+
// update mouse world space position and delta
|
|
4590
4857
|
mousePos = screenToWorld(mousePosScreen);
|
|
4591
|
-
mouseDelta = mouseDeltaScreen
|
|
4858
|
+
mouseDelta = screenToWorldDelta(mouseDeltaScreen);
|
|
4592
4859
|
|
|
4593
4860
|
// update gamepads if enabled
|
|
4594
4861
|
gamepadsUpdate();
|
|
@@ -4620,7 +4887,6 @@ function inputInit()
|
|
|
4620
4887
|
document.addEventListener('wheel', onMouseWheel);
|
|
4621
4888
|
document.addEventListener('contextmenu', onContextMenu);
|
|
4622
4889
|
document.addEventListener('blur', onBlur);
|
|
4623
|
-
document.addEventListener('mouseleave', onMouseLeave);
|
|
4624
4890
|
|
|
4625
4891
|
// init touch input
|
|
4626
4892
|
if (isTouchDevice && touchInputEnable)
|
|
@@ -4662,7 +4928,10 @@ function inputInit()
|
|
|
4662
4928
|
|
|
4663
4929
|
isUsingGamepad = false;
|
|
4664
4930
|
inputData[0][e.button] = 3;
|
|
4931
|
+
|
|
4932
|
+
let mousePosScreenLast = mousePosScreen;
|
|
4665
4933
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
4934
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4666
4935
|
inputPreventDefault && e.button && e.preventDefault();
|
|
4667
4936
|
}
|
|
4668
4937
|
function onMouseUp(e)
|
|
@@ -4673,18 +4942,13 @@ function inputInit()
|
|
|
4673
4942
|
}
|
|
4674
4943
|
function onMouseMove(e)
|
|
4675
4944
|
{
|
|
4945
|
+
let mousePosScreenLast = mousePosScreen;
|
|
4676
4946
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
4677
|
-
mouseDeltaScreen = mouseDeltaScreen.add(
|
|
4947
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4678
4948
|
}
|
|
4679
4949
|
function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
|
|
4680
4950
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
4681
4951
|
function onBlur() { inputClear(); } // reset input when focus is lost
|
|
4682
|
-
function onMouseLeave()
|
|
4683
|
-
{
|
|
4684
|
-
// set mouse position and delta when leaving canvas
|
|
4685
|
-
mousePosScreen = vec2(-1);
|
|
4686
|
-
mouseDeltaScreen = vec2(0);
|
|
4687
|
-
}
|
|
4688
4952
|
}
|
|
4689
4953
|
|
|
4690
4954
|
// convert a mouse or touch event position to screen space
|
|
@@ -4728,8 +4992,8 @@ function gamepadsUpdate()
|
|
|
4728
4992
|
else if (touchGamepadStick.lengthSquared() > .3)
|
|
4729
4993
|
{
|
|
4730
4994
|
// convert to 8 way dpad
|
|
4731
|
-
sticks[0].x =
|
|
4732
|
-
sticks[0].y = -
|
|
4995
|
+
sticks[0].x = round(touchGamepadStick.x);
|
|
4996
|
+
sticks[0].y = -round(touchGamepadStick.y);
|
|
4733
4997
|
sticks[0] = sticks[0].clampLength();
|
|
4734
4998
|
}
|
|
4735
4999
|
|
|
@@ -4857,11 +5121,11 @@ function touchInputInit()
|
|
|
4857
5121
|
{
|
|
4858
5122
|
// set event pos and pass it along
|
|
4859
5123
|
const pos = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
4860
|
-
const
|
|
5124
|
+
const mousePosScreenLast = mousePosScreen;
|
|
4861
5125
|
mousePosScreen = mouseEventToScreen(pos);
|
|
4862
5126
|
if (wasTouching)
|
|
4863
5127
|
{
|
|
4864
|
-
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(
|
|
5128
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4865
5129
|
isUsingGamepad = touchGamepadEnable;
|
|
4866
5130
|
}
|
|
4867
5131
|
else
|
|
@@ -4893,7 +5157,7 @@ function touchInputInit()
|
|
|
4893
5157
|
if (touching)
|
|
4894
5158
|
{
|
|
4895
5159
|
touchGamepadTimer.set();
|
|
4896
|
-
if (
|
|
5160
|
+
if (touchGamepadCenterButton && !wasTouching && paused)
|
|
4897
5161
|
{
|
|
4898
5162
|
// touch anywhere to press start when paused
|
|
4899
5163
|
touchGamepadButtons[9] = 1;
|
|
@@ -4932,7 +5196,8 @@ function touchInputInit()
|
|
|
4932
5196
|
if (button < touchGamepadButtonCount)
|
|
4933
5197
|
touchGamepadButtons[button] = 1;
|
|
4934
5198
|
}
|
|
4935
|
-
else if (
|
|
5199
|
+
else if (touchGamepadCenterButton && !wasTouching &&
|
|
5200
|
+
startCenter.distance(touchPos) < touchGamepadSize)
|
|
4936
5201
|
{
|
|
4937
5202
|
// virtual start button in center
|
|
4938
5203
|
touchGamepadButtons[9] = 1;
|
|
@@ -5628,10 +5893,10 @@ function zzfxG
|
|
|
5628
5893
|
|
|
5629
5894
|
// biquad LP/HP filter
|
|
5630
5895
|
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
5631
|
-
|
|
5632
|
-
a0 = 1 + alpha, a1 = -2*
|
|
5633
|
-
b0 = (1 + sign(filter) *
|
|
5634
|
-
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,
|
|
5635
5900
|
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
5636
5901
|
|
|
5637
5902
|
// scale by sample rate
|
|
@@ -5654,15 +5919,15 @@ function zzfxG
|
|
|
5654
5919
|
if (!(++crush%(bitCrush*100|0))) // bit crush
|
|
5655
5920
|
{
|
|
5656
5921
|
s = shape? shape>1? shape>2? shape>3? shape>4? // wave shape
|
|
5657
|
-
(t/PI2%1 < shapeCurve/2? 1 : -1) :
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
1-(2*t/PI2%2+2)%2:
|
|
5661
|
-
1-4*abs(
|
|
5662
|
-
|
|
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
|
|
5663
5928
|
|
|
5664
5929
|
s = (repeatTime ?
|
|
5665
|
-
1 - tremolo + tremolo*
|
|
5930
|
+
1 - tremolo + tremolo*sin(PI2*i/repeatTime) // tremolo
|
|
5666
5931
|
: 1) *
|
|
5667
5932
|
(shape>4?s:sign(s)*abs(s)**shapeCurve) * // shape curve
|
|
5668
5933
|
(i < attack ? i/attack : // attack
|
|
@@ -5684,8 +5949,8 @@ function zzfxG
|
|
|
5684
5949
|
}
|
|
5685
5950
|
|
|
5686
5951
|
f = (frequency += slide += deltaSlide) *// frequency
|
|
5687
|
-
|
|
5688
|
-
t += f + f*noise*
|
|
5952
|
+
cos(modulation*modOffset++); // modulation
|
|
5953
|
+
t += f + f*noise*sin(i**5); // noise
|
|
5689
5954
|
|
|
5690
5955
|
if (jump && ++jump > pitchJumpTime) // pitch jump
|
|
5691
5956
|
{
|
|
@@ -5751,23 +6016,22 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
|
|
|
5751
6016
|
}
|
|
5752
6017
|
}
|
|
5753
6018
|
|
|
5754
|
-
/** Return the
|
|
5755
|
-
* 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.
|
|
5756
6020
|
* @param {Vector2} posStart
|
|
5757
6021
|
* @param {Vector2} posEnd
|
|
5758
6022
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
6023
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
5759
6024
|
* @param {boolean} [solidOnly=true] - Only check solid layers if true
|
|
5760
|
-
* @return {Vector2}
|
|
6025
|
+
* @return {Vector2|undefined} - position of the center of the tile hit or undefined if no hit
|
|
5761
6026
|
* @memberof TileLayers */
|
|
5762
|
-
function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
|
|
6027
|
+
function tileCollisionRaycast(posStart, posEnd, object, normal, solidOnly=true)
|
|
5763
6028
|
{
|
|
5764
6029
|
for (const layer of tileCollisionLayers)
|
|
5765
6030
|
{
|
|
5766
6031
|
if (!solidOnly || layer.isSolid)
|
|
5767
6032
|
{
|
|
5768
|
-
const hitPos = layer.collisionRaycast(posStart, posEnd, object)
|
|
5769
|
-
if (hitPos)
|
|
5770
|
-
return hitPos;
|
|
6033
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, object, normal)
|
|
6034
|
+
if (hitPos) return hitPos;
|
|
5771
6035
|
}
|
|
5772
6036
|
}
|
|
5773
6037
|
}
|
|
@@ -5905,6 +6169,8 @@ class CanvasLayer extends EngineObject
|
|
|
5905
6169
|
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
5906
6170
|
const useWebGL = false; // do not use webgl by default
|
|
5907
6171
|
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
6172
|
+
/** @property {boolean} - True if WebGL texture needs to be refreshed */
|
|
6173
|
+
this.refreshWebGL = false;
|
|
5908
6174
|
|
|
5909
6175
|
// disable physics by default
|
|
5910
6176
|
this.mass = this.gravityScale = this.friction = this.restitution = 0;
|
|
@@ -5938,8 +6204,15 @@ class CanvasLayer extends EngineObject
|
|
|
5938
6204
|
* @memberof Draw */
|
|
5939
6205
|
draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
|
|
5940
6206
|
{
|
|
5941
|
-
// draw the canvas layer as a single tile that uses the whole texture
|
|
5942
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
|
+
|
|
6215
|
+
// draw the canvas layer as a single tile that uses the whole texture
|
|
5943
6216
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
5944
6217
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
5945
6218
|
}
|
|
@@ -6009,9 +6282,18 @@ class CanvasLayer extends EngineObject
|
|
|
6009
6282
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
6010
6283
|
|
|
6011
6284
|
/** Create or update the WebGL texture for this layer
|
|
6012
|
-
* @param {boolean} [enable] - enable WebGL rendering and update the texture
|
|
6013
|
-
|
|
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)
|
|
6014
6289
|
{
|
|
6290
|
+
if (!immediate && enable && this.textureInfo.hasWebGL())
|
|
6291
|
+
{
|
|
6292
|
+
// refresh the texture when needed
|
|
6293
|
+
this.refreshWebGL = true;
|
|
6294
|
+
return;
|
|
6295
|
+
}
|
|
6296
|
+
|
|
6015
6297
|
if (enable)
|
|
6016
6298
|
this.textureInfo.createWebGLTexture();
|
|
6017
6299
|
else
|
|
@@ -6042,7 +6324,7 @@ class TileLayer extends CanvasLayer
|
|
|
6042
6324
|
*/
|
|
6043
6325
|
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
6044
6326
|
{
|
|
6045
|
-
const canvasSize = size.multiply(tileInfo.size);
|
|
6327
|
+
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
6046
6328
|
super(position, size, 0, renderOrder, canvasSize);
|
|
6047
6329
|
|
|
6048
6330
|
// set tile info
|
|
@@ -6095,6 +6377,13 @@ class TileLayer extends CanvasLayer
|
|
|
6095
6377
|
{
|
|
6096
6378
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
6097
6379
|
|
|
6380
|
+
if (this.refreshWebGL)
|
|
6381
|
+
{
|
|
6382
|
+
// update the WebGL texture
|
|
6383
|
+
this.textureInfo.createWebGLTexture();
|
|
6384
|
+
this.refreshWebGL = false;
|
|
6385
|
+
}
|
|
6386
|
+
|
|
6098
6387
|
// draw the tile layer as a single tile
|
|
6099
6388
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
6100
6389
|
const size = this.drawSize || this.size;
|
|
@@ -6131,8 +6420,9 @@ class TileLayer extends CanvasLayer
|
|
|
6131
6420
|
drawCanvas = this.canvas;
|
|
6132
6421
|
drawContext = this.context;
|
|
6133
6422
|
cameraPos = this.size.scale(.5);
|
|
6134
|
-
|
|
6135
|
-
|
|
6423
|
+
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
6424
|
+
cameraScale = tileSize.x;
|
|
6425
|
+
mainCanvasSize = this.size.multiply(tileSize);
|
|
6136
6426
|
if (clear)
|
|
6137
6427
|
{
|
|
6138
6428
|
// clear and set size
|
|
@@ -6292,66 +6582,44 @@ class TileCollisionLayer extends TileLayer
|
|
|
6292
6582
|
// check if the object should collide with this tile
|
|
6293
6583
|
const tileData = this.collisionData[y*this.size.x+x];
|
|
6294
6584
|
if (tileData)
|
|
6295
|
-
if (!object || object.collideWithTile(tileData,
|
|
6585
|
+
if (!object || object.collideWithTile(tileData,
|
|
6586
|
+
hitPos.set(x + this.pos.x, y + this.pos.y)))
|
|
6296
6587
|
return true;
|
|
6297
6588
|
}
|
|
6298
6589
|
return false;
|
|
6299
6590
|
}
|
|
6300
6591
|
|
|
6301
|
-
/** Return the
|
|
6302
|
-
* 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.
|
|
6303
6593
|
* @param {Vector2} posStart
|
|
6304
6594
|
* @param {Vector2} posEnd
|
|
6305
|
-
* @param {EngineObject} [object]
|
|
6306
|
-
* @
|
|
6307
|
-
|
|
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)
|
|
6308
6599
|
{
|
|
6309
6600
|
ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
|
|
6310
6601
|
ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
const
|
|
6314
|
-
const posStartY = posStart.y - this.pos.y;
|
|
6315
|
-
const posEndX = posEnd.x - this.pos.x;
|
|
6316
|
-
const posEndY = posEnd.y - this.pos.y;
|
|
6317
|
-
|
|
6318
|
-
// test if a ray collides with tiles from start to end
|
|
6319
|
-
const deltaX = posEndX - posStartX;
|
|
6320
|
-
const deltaY = posEndY - posStartY;
|
|
6321
|
-
const totalLength = (deltaX**2 + deltaY**2)**.5;
|
|
6322
|
-
const unitX = abs(totalLength/deltaX);
|
|
6323
|
-
const unitY = abs(totalLength/deltaY);
|
|
6324
|
-
|
|
6325
|
-
// setup iteration variables
|
|
6326
|
-
const pos = posStart.floor(), signDeltaX = sign(deltaX), signDeltaY = sign(deltaY);
|
|
6327
|
-
let xi = unitX * (deltaX < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1) || 0;
|
|
6328
|
-
let yi = unitY * (deltaY < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1) || 0;
|
|
6329
|
-
|
|
6330
|
-
// use line drawing algorithm to test for collisions
|
|
6331
|
-
while (true)
|
|
6602
|
+
|
|
6603
|
+
const localPos = new Vector2;
|
|
6604
|
+
const collisionTest = (pos)=>
|
|
6332
6605
|
{
|
|
6333
6606
|
// check for tile collision
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
pos.x += .5; pos.y += .5;
|
|
6338
|
-
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
6339
|
-
debugRaycast && debugPoint(pos, '#ff0');
|
|
6340
|
-
return pos;
|
|
6341
|
-
}
|
|
6342
|
-
|
|
6343
|
-
// check if past the end
|
|
6344
|
-
if (xi >= totalLength && yi >= totalLength)
|
|
6345
|
-
break;
|
|
6346
|
-
|
|
6347
|
-
// get coordinates of next tile to check
|
|
6348
|
-
if (xi > yi)
|
|
6349
|
-
pos.y += signDeltaY, yi += unitY;
|
|
6350
|
-
else
|
|
6351
|
-
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));
|
|
6352
6610
|
}
|
|
6353
|
-
|
|
6354
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
|
+
}
|
|
6355
6623
|
}
|
|
6356
6624
|
}
|
|
6357
6625
|
/**
|
|
@@ -6506,6 +6774,12 @@ class ParticleEmitter extends EngineObject
|
|
|
6506
6774
|
this.particleCreateCallback = undefined;
|
|
6507
6775
|
/** @property {number} - Track particle emit time */
|
|
6508
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();
|
|
6509
6783
|
}
|
|
6510
6784
|
|
|
6511
6785
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
@@ -6514,6 +6788,18 @@ class ParticleEmitter extends EngineObject
|
|
|
6514
6788
|
// only do default update to apply parent transforms
|
|
6515
6789
|
this.parent && super.update();
|
|
6516
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
|
+
|
|
6517
6803
|
// update emitter
|
|
6518
6804
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
6519
6805
|
{
|
|
@@ -6571,7 +6857,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6571
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);
|
|
6572
6858
|
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
6573
6859
|
particle.angleVelocity = angleSpeed;
|
|
6574
|
-
if (!this.localSpace)
|
|
6860
|
+
if (!this.localSpace && this.velocityInheritance > 0)
|
|
6575
6861
|
{
|
|
6576
6862
|
// apply emitter velocity to particle
|
|
6577
6863
|
particle.velocity.x += this.velocity.x;
|
|
@@ -6669,6 +6955,16 @@ class Particle extends EngineObject
|
|
|
6669
6955
|
this.velocity.y *= s;
|
|
6670
6956
|
}
|
|
6671
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
|
+
}
|
|
6672
6968
|
}
|
|
6673
6969
|
|
|
6674
6970
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
@@ -6677,7 +6973,7 @@ class Particle extends EngineObject
|
|
|
6677
6973
|
// lerp color and size
|
|
6678
6974
|
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
6679
6975
|
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
6680
|
-
|
|
6976
|
+
const size = vec2(radius);
|
|
6681
6977
|
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
6682
6978
|
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
6683
6979
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
@@ -6697,7 +6993,7 @@ class Particle extends EngineObject
|
|
|
6697
6993
|
{
|
|
6698
6994
|
// in local space of emitter
|
|
6699
6995
|
const a = this.localSpaceEmitter.angle;
|
|
6700
|
-
const c =
|
|
6996
|
+
const c = cos(a), s = sin(a);
|
|
6701
6997
|
pos = this.localSpaceEmitter.pos.add(
|
|
6702
6998
|
new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
|
|
6703
6999
|
angle += this.localSpaceEmitter.angle;
|
|
@@ -6713,22 +7009,15 @@ class Particle extends EngineObject
|
|
|
6713
7009
|
{
|
|
6714
7010
|
// stretch in direction of motion
|
|
6715
7011
|
const trailLength = speed * this.trailScale;
|
|
6716
|
-
|
|
6717
|
-
angle =
|
|
6718
|
-
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);
|
|
6719
7015
|
}
|
|
6720
7016
|
}
|
|
6721
7017
|
else
|
|
6722
|
-
drawTile(pos,
|
|
7018
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6723
7019
|
this.additive && setBlendMode();
|
|
6724
|
-
debugParticles && debugRect(pos,
|
|
6725
|
-
|
|
6726
|
-
if (p1 === 1)
|
|
6727
|
-
{
|
|
6728
|
-
// destroy particle when its time runs out
|
|
6729
|
-
this.destroyCallback && this.destroyCallback(this);
|
|
6730
|
-
this.destroyed = 1;
|
|
6731
|
-
}
|
|
7020
|
+
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
6732
7021
|
}
|
|
6733
7022
|
}
|
|
6734
7023
|
/**
|
|
@@ -6951,7 +7240,7 @@ let glContext;
|
|
|
6951
7240
|
let glAntialias = true;
|
|
6952
7241
|
|
|
6953
7242
|
// WebGL internal variables not exposed to documentation
|
|
6954
|
-
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos;
|
|
7243
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
|
|
6955
7244
|
|
|
6956
7245
|
// WebGL internal constants
|
|
6957
7246
|
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
@@ -6970,7 +7259,11 @@ function glInit()
|
|
|
6970
7259
|
// keep set of texture infos so they can be restored if context is lost
|
|
6971
7260
|
glTextureInfos = new Set;
|
|
6972
7261
|
|
|
6973
|
-
if (!glEnable || headlessMode)
|
|
7262
|
+
if (!glEnable || headlessMode)
|
|
7263
|
+
{
|
|
7264
|
+
glCanBeEnabled = false;
|
|
7265
|
+
return;
|
|
7266
|
+
}
|
|
6974
7267
|
|
|
6975
7268
|
// create the canvas and textures
|
|
6976
7269
|
glCanvas = document.createElement('canvas');
|
|
@@ -6981,10 +7274,11 @@ function glInit()
|
|
|
6981
7274
|
console.warn('WebGL2 not supported, falling back to 2D canvas rendering!');
|
|
6982
7275
|
glCanvas = glContext = undefined;
|
|
6983
7276
|
glEnable = false;
|
|
7277
|
+
glCanBeEnabled = false;
|
|
6984
7278
|
return;
|
|
6985
7279
|
}
|
|
6986
7280
|
|
|
6987
|
-
//
|
|
7281
|
+
// attach the WebGL canvas
|
|
6988
7282
|
const rootElement = mainCanvas.parentElement;
|
|
6989
7283
|
rootElement.appendChild(glCanvas);
|
|
6990
7284
|
|
|
@@ -7007,7 +7301,7 @@ function glInit()
|
|
|
7007
7301
|
});
|
|
7008
7302
|
glCanvas.addEventListener('webglcontextrestored', ()=>
|
|
7009
7303
|
{
|
|
7010
|
-
glEnable = true; //
|
|
7304
|
+
glEnable = true; // re-enable WebGL rendering
|
|
7011
7305
|
glCanvas.style.display = ''; // show the gl canvas
|
|
7012
7306
|
LOG('WebGL context restored, reinitializing...');
|
|
7013
7307
|
|
|
@@ -7159,8 +7453,8 @@ function glPreRender()
|
|
|
7159
7453
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
7160
7454
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
7161
7455
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
7162
|
-
const ca =
|
|
7163
|
-
const sa =
|
|
7456
|
+
const ca = cos(cameraAngle);
|
|
7457
|
+
const sa = sin(cameraAngle);
|
|
7164
7458
|
const transform = [
|
|
7165
7459
|
s.x * ca, s.y * sa, 0, 0,
|
|
7166
7460
|
-s.x * sa, s.y * ca, 0, 0,
|
|
@@ -7465,8 +7759,8 @@ function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
|
|
|
7465
7759
|
// transform the point
|
|
7466
7760
|
const px = p.x*sx;
|
|
7467
7761
|
const py = p.y*sy;
|
|
7468
|
-
const sa =
|
|
7469
|
-
const ca =
|
|
7762
|
+
const sa = sin(-angle);
|
|
7763
|
+
const ca = cos(-angle);
|
|
7470
7764
|
pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
|
|
7471
7765
|
}
|
|
7472
7766
|
const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
|
|
@@ -10988,6 +11282,7 @@ export
|
|
|
10988
11282
|
gamepadDirectionEmulateStick,
|
|
10989
11283
|
inputWASDEmulateDirection,
|
|
10990
11284
|
touchGamepadEnable,
|
|
11285
|
+
touchGamepadCenterButton,
|
|
10991
11286
|
touchGamepadAnalog,
|
|
10992
11287
|
touchGamepadSize,
|
|
10993
11288
|
touchGamepadAlpha,
|
|
@@ -11031,6 +11326,7 @@ export
|
|
|
11031
11326
|
setGamepadDirectionEmulateStick,
|
|
11032
11327
|
setInputWASDEmulateDirection,
|
|
11033
11328
|
setTouchGamepadEnable,
|
|
11329
|
+
setTouchGamepadCenterButton,
|
|
11034
11330
|
setTouchGamepadAnalog,
|
|
11035
11331
|
setTouchGamepadSize,
|
|
11036
11332
|
setTouchGamepadAlpha,
|
|
@@ -11049,9 +11345,18 @@ export
|
|
|
11049
11345
|
// Utilities
|
|
11050
11346
|
PI,
|
|
11051
11347
|
abs,
|
|
11348
|
+
floor,
|
|
11349
|
+
ceil,
|
|
11350
|
+
round,
|
|
11052
11351
|
min,
|
|
11053
11352
|
max,
|
|
11054
11353
|
sign,
|
|
11354
|
+
hypot,
|
|
11355
|
+
log2,
|
|
11356
|
+
sin,
|
|
11357
|
+
cos,
|
|
11358
|
+
tan,
|
|
11359
|
+
atan2,
|
|
11055
11360
|
mod,
|
|
11056
11361
|
clamp,
|
|
11057
11362
|
percent,
|
|
@@ -11119,6 +11424,8 @@ export
|
|
|
11119
11424
|
drawCount,
|
|
11120
11425
|
screenToWorld,
|
|
11121
11426
|
worldToScreen,
|
|
11427
|
+
screenToWorldDelta,
|
|
11428
|
+
worldToScreenDelta,
|
|
11122
11429
|
drawTile,
|
|
11123
11430
|
drawRect,
|
|
11124
11431
|
drawRectGradient,
|