littlejsengine 1.14.19 → 1.14.28
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 +217 -38
- package/dist/littlejs.esm.js +879 -324
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +861 -324
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +799 -325
- package/examples/box2d/gameObjects.js +6 -10
- package/examples/box2d/scenes.js +2 -2
- package/examples/breakout/gameObjects.js +1 -1
- 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 +19 -10
- package/examples/module/game.js +5 -7
- package/examples/particles/index.html +6 -9
- package/examples/platformer/gameCharacter.js +4 -3
- package/examples/platformer/gameEffects.js +7 -2
- package/examples/platformer/gameObjects.js +6 -11
- package/examples/shorts/animation.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/box2d.js +2 -2
- package/examples/shorts/box2dCar.js +18 -7
- package/examples/shorts/box2dPool.js +108 -0
- package/examples/shorts/cameraDrag.js +22 -0
- package/examples/shorts/debugDraw.js +37 -0
- package/examples/shorts/flappyGame.js +1 -0
- package/examples/shorts/fps.js +90 -0
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/hillGlideGame.js +1 -3
- package/examples/shorts/input.js +59 -0
- package/examples/shorts/landerGame.js +1 -3
- package/examples/shorts/medals.js +15 -9
- package/examples/shorts/music.js +15 -17
- package/examples/shorts/musicPlayer.js +24 -24
- package/examples/shorts/platformer.js +0 -2
- package/examples/shorts/postProcess.js +1 -1
- package/examples/shorts/sequencer.js +3 -3
- package/examples/shorts/slidingPuzzle.js +1 -1
- package/examples/shorts/spaceGame.js +1 -3
- package/examples/shorts/spriteAtlas.js +6 -8
- 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 -4
- package/examples/shorts/timers.js +1 -2
- package/examples/shorts/topDown.js +0 -2
- 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/plugins/box2d.js +170 -50
- package/plugins/pluginExport.js +2 -0
- package/plugins/uiSystem.js +55 -27
- package/src/engine.js +19 -16
- package/src/engineAudio.js +27 -15
- package/src/engineDebug.js +64 -0
- package/src/engineDraw.js +88 -32
- package/src/engineExport.js +16 -0
- package/src/engineInput.js +35 -25
- package/src/engineMedals.js +2 -2
- package/src/engineObject.js +55 -10
- package/src/engineParticles.js +36 -20
- package/src/engineRelease.js +2 -1
- package/src/engineSettings.js +20 -1
- package/src/engineTileLayer.js +64 -59
- package/src/engineUtilities.js +213 -59
- 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.
|
|
36
|
+
const engineVersion = '1.15.0';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -164,7 +164,7 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
164
164
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
165
165
|
{
|
|
166
166
|
ASSERT(!mainContext, 'engine already initialized');
|
|
167
|
-
ASSERT(
|
|
167
|
+
ASSERT(isArray(imageSources), 'pass in images as array');
|
|
168
168
|
|
|
169
169
|
// allow passing in empty functions
|
|
170
170
|
gameInit ||= ()=>{};
|
|
@@ -456,21 +456,24 @@ function engineObjectsUpdate()
|
|
|
456
456
|
// recursive object update
|
|
457
457
|
function updateObject(o)
|
|
458
458
|
{
|
|
459
|
-
if (
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
459
|
+
if (o.destroyed)
|
|
460
|
+
return;
|
|
461
|
+
|
|
462
|
+
o.update();
|
|
463
|
+
for (const child of o.children)
|
|
464
|
+
updateObject(child);
|
|
465
465
|
}
|
|
466
466
|
for (const o of engineObjects)
|
|
467
467
|
{
|
|
468
|
+
if (o.parent)
|
|
469
|
+
continue;
|
|
470
|
+
|
|
468
471
|
// update top level objects
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
472
|
+
o.update();
|
|
473
|
+
o.updatePhysics();
|
|
474
|
+
for (const child of o.children)
|
|
475
|
+
updateObject(child);
|
|
476
|
+
o.updateTransforms();
|
|
474
477
|
}
|
|
475
478
|
|
|
476
479
|
// remove destroyed objects
|
|
@@ -503,7 +506,7 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
503
506
|
else if (size instanceof Vector2) // bounding box test
|
|
504
507
|
{
|
|
505
508
|
for (const o of objects)
|
|
506
|
-
isOverlapping(pos, size
|
|
509
|
+
o.isOverlapping(pos, size) && collectedObjects.push(o);
|
|
507
510
|
}
|
|
508
511
|
else // circle test
|
|
509
512
|
{
|
|
@@ -516,7 +519,7 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
516
519
|
|
|
517
520
|
/**
|
|
518
521
|
* @callback ObjectCallbackFunction - Function that processes an object
|
|
519
|
-
* @param {EngineObject}
|
|
522
|
+
* @param {EngineObject} object
|
|
520
523
|
* @memberof Engine
|
|
521
524
|
*/
|
|
522
525
|
|
|
@@ -564,7 +567,7 @@ function drawEngineSplashScreen(t)
|
|
|
564
567
|
// background
|
|
565
568
|
const p3 = percent(t, 1, .8);
|
|
566
569
|
const p4 = percent(t, 0, .5);
|
|
567
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,
|
|
570
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
568
571
|
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
569
572
|
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
570
573
|
x.save();
|
|
@@ -790,6 +793,12 @@ function LOG(...output) { console.log(...output); }
|
|
|
790
793
|
* @memberof Debug */
|
|
791
794
|
function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
792
795
|
{
|
|
796
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
797
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
798
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
799
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
800
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
801
|
+
|
|
793
802
|
if (typeof size === 'number')
|
|
794
803
|
size = vec2(size); // allow passing in floats
|
|
795
804
|
if (isColor(color))
|
|
@@ -810,6 +819,12 @@ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
|
810
819
|
* @memberof Debug */
|
|
811
820
|
function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
812
821
|
{
|
|
822
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
823
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
824
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
825
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
826
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
827
|
+
|
|
813
828
|
if (isColor(color))
|
|
814
829
|
color = color.toString();
|
|
815
830
|
pos = pos.copy();
|
|
@@ -827,6 +842,11 @@ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
|
827
842
|
* @memberof Debug */
|
|
828
843
|
function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
|
|
829
844
|
{
|
|
845
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
846
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
847
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
848
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
849
|
+
|
|
830
850
|
if (isColor(color))
|
|
831
851
|
color = color.toString();
|
|
832
852
|
pos = pos.copy();
|
|
@@ -852,6 +872,10 @@ function debugPoint(pos, color, time, angle)
|
|
|
852
872
|
* @memberof Debug */
|
|
853
873
|
function debugLine(posA, posB, color, width=.1, time)
|
|
854
874
|
{
|
|
875
|
+
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
876
|
+
ASSERT(isVector2(posB), 'posB must be sa vec2');
|
|
877
|
+
ASSERT(isNumber(width), 'width must be a number');
|
|
878
|
+
|
|
855
879
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
856
880
|
const size = vec2(width, halfDelta.length()*2);
|
|
857
881
|
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
|
|
@@ -866,6 +890,11 @@ function debugLine(posA, posB, color, width=.1, time)
|
|
|
866
890
|
* @memberof Debug */
|
|
867
891
|
function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
868
892
|
{
|
|
893
|
+
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
894
|
+
ASSERT(isVector2(posB), 'posB must be a vec2');
|
|
895
|
+
ASSERT(isVector2(sizeA), 'sizeA must be a vec2');
|
|
896
|
+
ASSERT(isVector2(sizeB), 'sizeB must be a vec2');
|
|
897
|
+
|
|
869
898
|
const minPos = vec2(
|
|
870
899
|
min(posA.x - sizeA.x/2, posB.x - sizeB.x/2),
|
|
871
900
|
min(posA.y - sizeA.y/2, posB.y - sizeB.y/2)
|
|
@@ -888,6 +917,14 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
|
888
917
|
* @memberof Debug */
|
|
889
918
|
function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace')
|
|
890
919
|
{
|
|
920
|
+
ASSERT(isString(text), 'text must be a string');
|
|
921
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
922
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
923
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
924
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
925
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
926
|
+
ASSERT(isString(font), 'font must be a string');
|
|
927
|
+
|
|
891
928
|
if (isColor(color))
|
|
892
929
|
color = color.toString();
|
|
893
930
|
pos = pos.copy();
|
|
@@ -1339,6 +1376,36 @@ function debugVideoCaptureUpdate()
|
|
|
1339
1376
|
combineCanvases();
|
|
1340
1377
|
debugVideoCaptureTrack.requestFrame();
|
|
1341
1378
|
debugVideoCaptureIcon.textContent = '● REC ' + formatTime(debugVideoCaptureTimer);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1382
|
+
// debug utility functions
|
|
1383
|
+
|
|
1384
|
+
// make color constants immutable with debug assertions
|
|
1385
|
+
function debugProtectConstant(obj)
|
|
1386
|
+
{
|
|
1387
|
+
if (debug)
|
|
1388
|
+
{
|
|
1389
|
+
// get properties and store original values
|
|
1390
|
+
const props = Object.keys(obj), values = {};
|
|
1391
|
+
props.forEach(prop => values[prop] = obj[prop]);
|
|
1392
|
+
|
|
1393
|
+
// replace with getters/setters that assert
|
|
1394
|
+
props.forEach(prop =>
|
|
1395
|
+
{
|
|
1396
|
+
Object.defineProperty(obj, prop, {
|
|
1397
|
+
get: () => values[prop],
|
|
1398
|
+
set: (value) =>
|
|
1399
|
+
{
|
|
1400
|
+
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
1401
|
+
},
|
|
1402
|
+
enumerable: true
|
|
1403
|
+
});
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// freeze the object to prevent adding new properties
|
|
1408
|
+
return Object.freeze(obj);
|
|
1342
1409
|
}
|
|
1343
1410
|
/**
|
|
1344
1411
|
* LittleJS Utility Classes and Functions
|
|
@@ -1350,7 +1417,7 @@ function debugVideoCaptureUpdate()
|
|
|
1350
1417
|
* @namespace Utilities
|
|
1351
1418
|
*/
|
|
1352
1419
|
|
|
1353
|
-
/**
|
|
1420
|
+
/** The value of PI
|
|
1354
1421
|
* @type {number}
|
|
1355
1422
|
* @default Math.PI
|
|
1356
1423
|
* @memberof Utilities */
|
|
@@ -1360,25 +1427,80 @@ const PI = Math.PI;
|
|
|
1360
1427
|
* @param {number} value
|
|
1361
1428
|
* @return {number}
|
|
1362
1429
|
* @memberof Utilities */
|
|
1363
|
-
|
|
1430
|
+
const abs = Math.abs;
|
|
1431
|
+
|
|
1432
|
+
/** Returns floored value of value passed in
|
|
1433
|
+
* @param {number} value
|
|
1434
|
+
* @return {number}
|
|
1435
|
+
* @memberof Utilities */
|
|
1436
|
+
const floor = Math.floor;
|
|
1437
|
+
|
|
1438
|
+
/** Returns ceiled value of value passed in
|
|
1439
|
+
* @param {number} value
|
|
1440
|
+
* @return {number}
|
|
1441
|
+
* @memberof Utilities */
|
|
1442
|
+
const ceil = Math.ceil;
|
|
1443
|
+
|
|
1444
|
+
/** Returns rounded value passed in
|
|
1445
|
+
* @param {number} value
|
|
1446
|
+
* @return {number}
|
|
1447
|
+
* @memberof Utilities */
|
|
1448
|
+
const round = Math.round;
|
|
1364
1449
|
|
|
1365
1450
|
/** Returns lowest value passed in
|
|
1366
1451
|
* @param {...number} values
|
|
1367
1452
|
* @return {number}
|
|
1368
1453
|
* @memberof Utilities */
|
|
1369
|
-
|
|
1454
|
+
const min = Math.min;
|
|
1370
1455
|
|
|
1371
1456
|
/** Returns highest value passed in
|
|
1372
1457
|
* @param {...number} values
|
|
1373
1458
|
* @return {number}
|
|
1374
1459
|
* @memberof Utilities */
|
|
1375
|
-
|
|
1460
|
+
const max = Math.max;
|
|
1376
1461
|
|
|
1377
1462
|
/** Returns the sign of value passed in
|
|
1378
1463
|
* @param {number} value
|
|
1379
1464
|
* @return {number}
|
|
1380
1465
|
* @memberof Utilities */
|
|
1381
|
-
|
|
1466
|
+
const sign = Math.sign;
|
|
1467
|
+
|
|
1468
|
+
/** Returns hypotenuse of values passed in
|
|
1469
|
+
* @param {...number} values
|
|
1470
|
+
* @return {number}
|
|
1471
|
+
* @memberof Utilities */
|
|
1472
|
+
const hypot = Math.hypot;
|
|
1473
|
+
|
|
1474
|
+
/** Returns log2 of value passed in
|
|
1475
|
+
* @param {number} value
|
|
1476
|
+
* @return {number}
|
|
1477
|
+
* @memberof Utilities */
|
|
1478
|
+
const log2 = Math.log2;
|
|
1479
|
+
|
|
1480
|
+
/** Returns sin of value passed in
|
|
1481
|
+
* @param {number} value
|
|
1482
|
+
* @return {number}
|
|
1483
|
+
* @memberof Utilities */
|
|
1484
|
+
const sin = Math.sin;
|
|
1485
|
+
|
|
1486
|
+
/** Returns cos of value passed in
|
|
1487
|
+
* @param {number} value
|
|
1488
|
+
* @return {number}
|
|
1489
|
+
* @memberof Utilities */
|
|
1490
|
+
const cos = Math.cos;
|
|
1491
|
+
|
|
1492
|
+
/** Returns tan of value passed in
|
|
1493
|
+
* @param {number} value
|
|
1494
|
+
* @return {number}
|
|
1495
|
+
* @memberof Utilities */
|
|
1496
|
+
const tan = Math.tan;
|
|
1497
|
+
|
|
1498
|
+
/** Returns atan2 of values passed in
|
|
1499
|
+
* @param {number} y
|
|
1500
|
+
* @param {number} x
|
|
1501
|
+
* @return {number}
|
|
1502
|
+
* @memberof Utilities */
|
|
1503
|
+
const atan2 = Math.atan2;
|
|
1382
1504
|
|
|
1383
1505
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
1384
1506
|
* @param {number} dividend
|
|
@@ -1483,7 +1605,7 @@ function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
|
1483
1605
|
* @param {number} value
|
|
1484
1606
|
* @return {number}
|
|
1485
1607
|
* @memberof Utilities */
|
|
1486
|
-
function nearestPowerOfTwo(value) { return 2**
|
|
1608
|
+
function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
1487
1609
|
|
|
1488
1610
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
1489
1611
|
* this can be used for simple collision detection between objects
|
|
@@ -1551,7 +1673,7 @@ function isIntersecting(start, end, pos, size)
|
|
|
1551
1673
|
* @return {number} - Value waving between 0 and amplitude
|
|
1552
1674
|
* @memberof Utilities */
|
|
1553
1675
|
function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
1554
|
-
{ return amplitude/2 * (1 -
|
|
1676
|
+
{ return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
|
|
1555
1677
|
|
|
1556
1678
|
/** Formats seconds to mm:ss style for display purposes
|
|
1557
1679
|
* @param {number} t - time in seconds
|
|
@@ -1590,6 +1712,103 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
|
1590
1712
|
* @memberof Utilities */
|
|
1591
1713
|
function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
|
|
1592
1714
|
|
|
1715
|
+
/**
|
|
1716
|
+
* Check if object is an array
|
|
1717
|
+
* @param {any} a
|
|
1718
|
+
* @return {boolean}
|
|
1719
|
+
* @memberof Utilities */
|
|
1720
|
+
function isArray(a) { return Array.isArray(a); }
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* @callback LineTestFunction - Checks if a position is colliding
|
|
1724
|
+
* @param {Vector2} pos
|
|
1725
|
+
* @memberof Draw
|
|
1726
|
+
*/
|
|
1727
|
+
|
|
1728
|
+
/**
|
|
1729
|
+
* Casts a ray and returns position of the first collision found, or undefined if none are found
|
|
1730
|
+
* @param {Vector2} posStart
|
|
1731
|
+
* @param {Vector2} posEnd
|
|
1732
|
+
* @param {LineTestFunction} testFunction - Check if colliding
|
|
1733
|
+
* @param {Vector2} [normal] - Optional vector to store the normal
|
|
1734
|
+
* @return {Vector2|undefined} - Position of the collision or undefined if none found
|
|
1735
|
+
* @memberof Utilities */
|
|
1736
|
+
function lineTest(posStart, posEnd, testFunction, normal)
|
|
1737
|
+
{
|
|
1738
|
+
ASSERT(isVector2(posStart), 'posStart must be a vec2');
|
|
1739
|
+
ASSERT(isVector2(posEnd), 'posEnd must be a vec2');
|
|
1740
|
+
ASSERT(typeof testFunction === 'function', 'testFunction must be a function');
|
|
1741
|
+
ASSERT(!normal || isVector2(normal), 'normal must be a vec2');
|
|
1742
|
+
|
|
1743
|
+
// get ray direction and length
|
|
1744
|
+
const dx = posEnd.x - posStart.x;
|
|
1745
|
+
const dy = posEnd.y - posStart.y;
|
|
1746
|
+
const totalLength = hypot(dx, dy);
|
|
1747
|
+
if (!totalLength)
|
|
1748
|
+
return;
|
|
1749
|
+
|
|
1750
|
+
// current integer cell we are in
|
|
1751
|
+
const pos = posStart.floor();
|
|
1752
|
+
|
|
1753
|
+
// normalize ray direction
|
|
1754
|
+
const dirX = dx / totalLength;
|
|
1755
|
+
const dirY = dy / totalLength;
|
|
1756
|
+
|
|
1757
|
+
// step direction in grid
|
|
1758
|
+
const stepX = sign(dirX);
|
|
1759
|
+
const stepY = sign(dirY);
|
|
1760
|
+
|
|
1761
|
+
// distance along the ray to cross one full cell in X or Y
|
|
1762
|
+
const tDeltaX = dirX ? abs(1 / dirX) : Infinity;
|
|
1763
|
+
const tDeltaY = dirY ? abs(1 / dirY) : Infinity;
|
|
1764
|
+
|
|
1765
|
+
// distance along the ray from start to the first grid boundary
|
|
1766
|
+
const nextGridX = stepX > 0 ? pos.x + 1 : pos.x;
|
|
1767
|
+
const nextGridY = stepY > 0 ? pos.y + 1 : pos.y;
|
|
1768
|
+
const tMaxX = dirX ? (nextGridX - posStart.x) / dirX : Infinity;
|
|
1769
|
+
const tMaxY = dirY ? (nextGridY - posStart.y) / dirY : Infinity;
|
|
1770
|
+
|
|
1771
|
+
// use line drawing algorithm to test for collisions
|
|
1772
|
+
let t = 0, tX = tMaxX, tY = tMaxY, wasX = tDeltaX < tDeltaY;
|
|
1773
|
+
while (t < totalLength)
|
|
1774
|
+
{
|
|
1775
|
+
if (testFunction(pos))
|
|
1776
|
+
{
|
|
1777
|
+
// set hit point
|
|
1778
|
+
const hitPos = vec2(posStart.x + dirX*t, posStart.y + dirY*t);
|
|
1779
|
+
|
|
1780
|
+
// move inside of tile if on positive edge
|
|
1781
|
+
const e = 1e-9;
|
|
1782
|
+
if (wasX)
|
|
1783
|
+
{
|
|
1784
|
+
if (stepX < 0)
|
|
1785
|
+
hitPos.x -= e;
|
|
1786
|
+
}
|
|
1787
|
+
if (stepY < 0)
|
|
1788
|
+
hitPos.y -= e;
|
|
1789
|
+
|
|
1790
|
+
// set normal
|
|
1791
|
+
if (normal)
|
|
1792
|
+
wasX ? normal.set(-stepX,0) : normal.set(0,-stepY);
|
|
1793
|
+
return hitPos;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// advance to the next grid boundary
|
|
1797
|
+
if (wasX = tX < tY)
|
|
1798
|
+
{
|
|
1799
|
+
pos.x += stepX;
|
|
1800
|
+
t = tX;
|
|
1801
|
+
tX += tDeltaX;
|
|
1802
|
+
}
|
|
1803
|
+
else
|
|
1804
|
+
{
|
|
1805
|
+
pos.y += stepY;
|
|
1806
|
+
t = tY;
|
|
1807
|
+
tY += tDeltaY;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1593
1812
|
///////////////////////////////////////////////////////////////////////////////
|
|
1594
1813
|
|
|
1595
1814
|
/** Random global functions
|
|
@@ -1608,7 +1827,7 @@ function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valu
|
|
|
1608
1827
|
* @param {number} [valueB]
|
|
1609
1828
|
* @return {number}
|
|
1610
1829
|
* @memberof Random */
|
|
1611
|
-
function randInt(valueA, valueB=0) { return
|
|
1830
|
+
function randInt(valueA, valueB=0) { return floor(rand(valueA,valueB)); }
|
|
1612
1831
|
|
|
1613
1832
|
/** Randomly returns true or false given the chance of true passed in
|
|
1614
1833
|
* @param {number} [chance]
|
|
@@ -1687,7 +1906,7 @@ class RandomGenerator
|
|
|
1687
1906
|
* @param {number} valueA
|
|
1688
1907
|
* @param {number} [valueB]
|
|
1689
1908
|
* @return {number} */
|
|
1690
|
-
int(valueA, valueB=0) { return
|
|
1909
|
+
int(valueA, valueB=0) { return floor(this.float(valueA, valueB)); }
|
|
1691
1910
|
|
|
1692
1911
|
/** Randomly returns true or false given the chance of true passed in
|
|
1693
1912
|
* @param {number} [chance]
|
|
@@ -1714,6 +1933,39 @@ class RandomGenerator
|
|
|
1714
1933
|
* @return {Vector2} */
|
|
1715
1934
|
vec2(valueA=1, valueB=0)
|
|
1716
1935
|
{ return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
|
|
1936
|
+
|
|
1937
|
+
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1938
|
+
* @param {Color} [colorA=(1,1,1,1)]
|
|
1939
|
+
* @param {Color} [colorB=(0,0,0,1)]
|
|
1940
|
+
* @param {boolean} [linear]
|
|
1941
|
+
* @return {Color} */
|
|
1942
|
+
randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
1943
|
+
{
|
|
1944
|
+
return linear ? colorA.lerp(colorB, this.float()) :
|
|
1945
|
+
new Color(
|
|
1946
|
+
this.float(colorA.r,colorB.r),
|
|
1947
|
+
this.float(colorA.g,colorB.g),
|
|
1948
|
+
this.float(colorA.b,colorB.b),
|
|
1949
|
+
this.float(colorA.a,colorB.a));
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
/** Returns a new color that has each component randomly adjusted
|
|
1953
|
+
* @param {Color} color
|
|
1954
|
+
* @param {number} [amount]
|
|
1955
|
+
* @param {number} [alphaAmount]
|
|
1956
|
+
* @return {Color} */
|
|
1957
|
+
mutateColor(color, amount=.05, alphaAmount=0)
|
|
1958
|
+
{
|
|
1959
|
+
ASSERT_NUMBER_VALID(amount);
|
|
1960
|
+
ASSERT_NUMBER_VALID(alphaAmount);
|
|
1961
|
+
return new Color
|
|
1962
|
+
(
|
|
1963
|
+
color.r + this.float(amount, -amount),
|
|
1964
|
+
color.g + this.float(amount, -amount),
|
|
1965
|
+
color.b + this.float(amount, -amount),
|
|
1966
|
+
color.a + this.float(alphaAmount, -alphaAmount)
|
|
1967
|
+
).clamp();
|
|
1968
|
+
}
|
|
1717
1969
|
}
|
|
1718
1970
|
|
|
1719
1971
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1865,7 +2117,7 @@ class Vector2
|
|
|
1865
2117
|
|
|
1866
2118
|
/** Returns the clockwise angle of this vector, up is angle 0
|
|
1867
2119
|
* @return {number} */
|
|
1868
|
-
angle() { return
|
|
2120
|
+
angle() { return atan2(this.x, this.y); }
|
|
1869
2121
|
|
|
1870
2122
|
/** Sets this vector with clockwise angle and length passed in
|
|
1871
2123
|
* @param {number} [angle]
|
|
@@ -1875,8 +2127,8 @@ class Vector2
|
|
|
1875
2127
|
{
|
|
1876
2128
|
ASSERT_NUMBER_VALID(angle);
|
|
1877
2129
|
ASSERT_NUMBER_VALID(length);
|
|
1878
|
-
this.x = length*
|
|
1879
|
-
this.y = length*
|
|
2130
|
+
this.x = length*sin(angle);
|
|
2131
|
+
this.y = length*cos(angle);
|
|
1880
2132
|
return this;
|
|
1881
2133
|
}
|
|
1882
2134
|
|
|
@@ -1886,7 +2138,7 @@ class Vector2
|
|
|
1886
2138
|
rotate(angle)
|
|
1887
2139
|
{
|
|
1888
2140
|
ASSERT_NUMBER_VALID(angle);
|
|
1889
|
-
const c =
|
|
2141
|
+
const c = cos(-angle), s = sin(-angle);
|
|
1890
2142
|
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
1891
2143
|
}
|
|
1892
2144
|
|
|
@@ -1918,7 +2170,7 @@ class Vector2
|
|
|
1918
2170
|
|
|
1919
2171
|
/** Returns a copy of this vector with each axis floored
|
|
1920
2172
|
* @return {Vector2} */
|
|
1921
|
-
floor() { return new Vector2(
|
|
2173
|
+
floor() { return new Vector2(floor(this.x), floor(this.y)); }
|
|
1922
2174
|
|
|
1923
2175
|
/** Returns new vec2 with modded values
|
|
1924
2176
|
* @param {number} [divisor]
|
|
@@ -2166,7 +2418,6 @@ class Color
|
|
|
2166
2418
|
* @return {string} */
|
|
2167
2419
|
toString(useAlpha = true)
|
|
2168
2420
|
{
|
|
2169
|
-
ASSERT(typeof useAlpha === 'boolean', 'Use alpha boolean is invalid.', useAlpha);
|
|
2170
2421
|
if (debug && !this.isValid())
|
|
2171
2422
|
return `#000`;
|
|
2172
2423
|
const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
|
|
@@ -2226,67 +2477,67 @@ class Color
|
|
|
2226
2477
|
/** Color - White #ffffff
|
|
2227
2478
|
* @type {Color}
|
|
2228
2479
|
* @memberof Utilities */
|
|
2229
|
-
const WHITE =
|
|
2480
|
+
const WHITE = debugProtectConstant(rgb());
|
|
2230
2481
|
|
|
2231
2482
|
/** Color - Clear White #757474ff with 0 alpha
|
|
2232
2483
|
* @type {Color}
|
|
2233
2484
|
* @memberof Utilities */
|
|
2234
|
-
const CLEAR_WHITE =
|
|
2485
|
+
const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
2235
2486
|
|
|
2236
2487
|
/** Color - Black #000000
|
|
2237
2488
|
* @type {Color}
|
|
2238
2489
|
* @memberof Utilities */
|
|
2239
|
-
const BLACK =
|
|
2490
|
+
const BLACK = debugProtectConstant(rgb(0,0,0));
|
|
2240
2491
|
|
|
2241
2492
|
/** Color - Clear Black #000000 with 0 alpha
|
|
2242
2493
|
* @type {Color}
|
|
2243
2494
|
* @memberof Utilities */
|
|
2244
|
-
const CLEAR_BLACK =
|
|
2495
|
+
const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
|
|
2245
2496
|
|
|
2246
2497
|
/** Color - Gray #808080
|
|
2247
2498
|
* @type {Color}
|
|
2248
2499
|
* @memberof Utilities */
|
|
2249
|
-
const GRAY =
|
|
2500
|
+
const GRAY = debugProtectConstant(rgb(.5,.5,.5));
|
|
2250
2501
|
|
|
2251
2502
|
/** Color - Red #ff0000
|
|
2252
2503
|
* @type {Color}
|
|
2253
2504
|
* @memberof Utilities */
|
|
2254
|
-
const RED =
|
|
2505
|
+
const RED = debugProtectConstant(rgb(1,0,0));
|
|
2255
2506
|
|
|
2256
2507
|
/** Color - Orange #ff8000
|
|
2257
2508
|
* @type {Color}
|
|
2258
2509
|
* @memberof Utilities */
|
|
2259
|
-
const ORANGE =
|
|
2510
|
+
const ORANGE = debugProtectConstant(rgb(1,.5,0));
|
|
2260
2511
|
|
|
2261
2512
|
/** Color - Yellow #ffff00
|
|
2262
2513
|
* @type {Color}
|
|
2263
2514
|
* @memberof Utilities */
|
|
2264
|
-
const YELLOW =
|
|
2515
|
+
const YELLOW = debugProtectConstant(rgb(1,1,0));
|
|
2265
2516
|
|
|
2266
2517
|
/** Color - Green #00ff00
|
|
2267
2518
|
* @type {Color}
|
|
2268
2519
|
* @memberof Utilities */
|
|
2269
|
-
const GREEN =
|
|
2520
|
+
const GREEN = debugProtectConstant(rgb(0,1,0));
|
|
2270
2521
|
|
|
2271
2522
|
/** Color - Cyan #00ffff
|
|
2272
2523
|
* @type {Color}
|
|
2273
2524
|
* @memberof Utilities */
|
|
2274
|
-
const CYAN =
|
|
2525
|
+
const CYAN = debugProtectConstant(rgb(0,1,1));
|
|
2275
2526
|
|
|
2276
2527
|
/** Color - Blue #0000ff
|
|
2277
2528
|
* @type {Color}
|
|
2278
2529
|
* @memberof Utilities */
|
|
2279
|
-
const BLUE =
|
|
2530
|
+
const BLUE = debugProtectConstant(rgb(0,0,1));
|
|
2280
2531
|
|
|
2281
2532
|
/** Color - Purple #8000ff
|
|
2282
2533
|
* @type {Color}
|
|
2283
2534
|
* @memberof Utilities */
|
|
2284
|
-
const PURPLE =
|
|
2535
|
+
const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
2285
2536
|
|
|
2286
2537
|
/** Color - Magenta #ff00ff
|
|
2287
2538
|
* @type {Color}
|
|
2288
2539
|
* @memberof Utilities */
|
|
2289
|
-
const MAGENTA =
|
|
2540
|
+
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
2290
2541
|
|
|
2291
2542
|
///////////////////////////////////////////////////////////////////////////////
|
|
2292
2543
|
|
|
@@ -2349,41 +2600,11 @@ class Timer
|
|
|
2349
2600
|
|
|
2350
2601
|
/** Returns this timer expressed as a string
|
|
2351
2602
|
* @return {string} */
|
|
2352
|
-
toString() { return this.isSet() ?
|
|
2603
|
+
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
2353
2604
|
|
|
2354
2605
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2355
2606
|
* @return {number} */
|
|
2356
2607
|
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
2608
|
}
|
|
2388
2609
|
/**
|
|
2389
2610
|
* LittleJS Engine Settings
|
|
@@ -2601,6 +2822,14 @@ let touchInputEnable = true;
|
|
|
2601
2822
|
* @memberof Settings */
|
|
2602
2823
|
let touchGamepadEnable = false;
|
|
2603
2824
|
|
|
2825
|
+
/** True if touch gamepad should have start button in the center
|
|
2826
|
+
* - When the game is paused, any touch will press the button
|
|
2827
|
+
* - This can function as a way to pause/unpause the game
|
|
2828
|
+
* @type {boolean}
|
|
2829
|
+
* @default
|
|
2830
|
+
* @memberof Settings */
|
|
2831
|
+
let touchGamepadCenterButton = true;
|
|
2832
|
+
|
|
2604
2833
|
/** True if touch gamepad should be analog stick or false to use if 8 way dpad
|
|
2605
2834
|
* @type {boolean}
|
|
2606
2835
|
* @default
|
|
@@ -2776,9 +3005,14 @@ function setHeadlessMode(headless) { headlessMode = headless; }
|
|
|
2776
3005
|
* @memberof Settings */
|
|
2777
3006
|
function setGLEnable(enable)
|
|
2778
3007
|
{
|
|
3008
|
+
if (enable && !glCanBeEnabled)
|
|
3009
|
+
{
|
|
3010
|
+
console.warn('Can not enable WebGL if it was disabled on start.');
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
2779
3013
|
glEnable = enable;
|
|
2780
3014
|
if (glCanvas) // hide glCanvas if WebGL is disabled
|
|
2781
|
-
glCanvas.style.
|
|
3015
|
+
glCanvas.style.display = enable ? '' : 'none';
|
|
2782
3016
|
}
|
|
2783
3017
|
|
|
2784
3018
|
/** Set how many sided polygons to use when drawing circles and ellipses with WebGL
|
|
@@ -2866,6 +3100,12 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
|
|
|
2866
3100
|
* @memberof Settings */
|
|
2867
3101
|
function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
|
|
2868
3102
|
|
|
3103
|
+
/** True if touch gamepad should have start button in the center
|
|
3104
|
+
* - This can function as a way to pause/unpause the game
|
|
3105
|
+
* @param {boolean} enable
|
|
3106
|
+
* @memberof Settings */
|
|
3107
|
+
function setTouchGamepadCenterButton(enable) { touchGamepadCenterButton = enable; }
|
|
3108
|
+
|
|
2869
3109
|
/** Set if touch gamepad should be analog stick or 8 way dpad
|
|
2870
3110
|
* @param {boolean} analog
|
|
2871
3111
|
* @memberof Settings */
|
|
@@ -3075,11 +3315,10 @@ class EngineObject
|
|
|
3075
3315
|
}
|
|
3076
3316
|
|
|
3077
3317
|
/** Update the object physics, called automatically by engine once each frame */
|
|
3078
|
-
|
|
3318
|
+
updatePhysics()
|
|
3079
3319
|
{
|
|
3080
3320
|
// child objects do not have physics
|
|
3081
|
-
|
|
3082
|
-
return;
|
|
3321
|
+
ASSERT(!this.parent);
|
|
3083
3322
|
|
|
3084
3323
|
if (this.clampSpeed)
|
|
3085
3324
|
{
|
|
@@ -3129,7 +3368,7 @@ class EngineObject
|
|
|
3129
3368
|
continue;
|
|
3130
3369
|
|
|
3131
3370
|
// check collision
|
|
3132
|
-
if (!
|
|
3371
|
+
if (!this.isOverlappingObject(o))
|
|
3133
3372
|
continue;
|
|
3134
3373
|
|
|
3135
3374
|
// notify objects of collision and check if should be resolved
|
|
@@ -3227,6 +3466,27 @@ class EngineObject
|
|
|
3227
3466
|
// test which side we bounced off (or both if a corner)
|
|
3228
3467
|
const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
3229
3468
|
const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
3469
|
+
|
|
3470
|
+
if (blockedLayerX)
|
|
3471
|
+
{
|
|
3472
|
+
// try to move up a tiny bit
|
|
3473
|
+
const epsilon = 1e-3;
|
|
3474
|
+
const maxMoveUp = .1;
|
|
3475
|
+
const y = floor(oldPos.y-this.size.y/2+1) +
|
|
3476
|
+
this.size.y/2 + epsilon;
|
|
3477
|
+
const delta = y - this.pos.y;
|
|
3478
|
+
if (delta < maxMoveUp)
|
|
3479
|
+
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
3480
|
+
{
|
|
3481
|
+
this.pos.y = y;
|
|
3482
|
+
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
// move to previous position and bounce
|
|
3487
|
+
this.pos.x = oldPos.x;
|
|
3488
|
+
this.velocity.x *= -this.restitution;
|
|
3489
|
+
}
|
|
3230
3490
|
if (blockedLayerY || !blockedLayerX)
|
|
3231
3491
|
{
|
|
3232
3492
|
// bounce velocity
|
|
@@ -3250,18 +3510,15 @@ class EngineObject
|
|
|
3250
3510
|
this.groundObject = undefined;
|
|
3251
3511
|
}
|
|
3252
3512
|
}
|
|
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
3513
|
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
3260
3514
|
}
|
|
3261
3515
|
}
|
|
3262
3516
|
}
|
|
3263
3517
|
}
|
|
3264
3518
|
|
|
3519
|
+
/** Update the object, called automatically by engine once each frame. Does nothing by default. */
|
|
3520
|
+
update() {}
|
|
3521
|
+
|
|
3265
3522
|
/** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
|
|
3266
3523
|
render()
|
|
3267
3524
|
{
|
|
@@ -3313,10 +3570,24 @@ class EngineObject
|
|
|
3313
3570
|
*/
|
|
3314
3571
|
collideWithObject(object) { return true; }
|
|
3315
3572
|
|
|
3573
|
+
/** Get this object's up vector
|
|
3574
|
+
* @param {number} [scale] - length of the vector
|
|
3575
|
+
* @return {Vector2} */
|
|
3576
|
+
getUp(scale=1) { return vec2().setAngle(this.angle, scale); }
|
|
3577
|
+
|
|
3578
|
+
/** Get this object's right vector
|
|
3579
|
+
* @param {number} [scale] - length of the vector
|
|
3580
|
+
* @return {Vector2} */
|
|
3581
|
+
getRight(scale=1) { return vec2().setAngle(this.angle+PI/2, scale); }
|
|
3582
|
+
|
|
3316
3583
|
/** How long since the object was created
|
|
3317
3584
|
* @return {number} */
|
|
3318
3585
|
getAliveTime() { return time - this.spawnTime; }
|
|
3319
3586
|
|
|
3587
|
+
/** Get the speed of this object
|
|
3588
|
+
* @return {number} */
|
|
3589
|
+
getSpeed() { return this.velocity.length(); }
|
|
3590
|
+
|
|
3320
3591
|
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
3321
3592
|
* @param {Vector2} acceleration */
|
|
3322
3593
|
applyAcceleration(acceleration)
|
|
@@ -3361,6 +3632,20 @@ class EngineObject
|
|
|
3361
3632
|
child.parent = 0;
|
|
3362
3633
|
}
|
|
3363
3634
|
|
|
3635
|
+
/** Check if overlapping another engine object
|
|
3636
|
+
* Collisions are resoloved to prevent overlaps
|
|
3637
|
+
* @param {EngineObject} object
|
|
3638
|
+
* @return {boolean} */
|
|
3639
|
+
isOverlappingObject(object)
|
|
3640
|
+
{ return this.isOverlapping(object.pos, object.size); }
|
|
3641
|
+
|
|
3642
|
+
/** Check if overlapping a point or aligned bounding box
|
|
3643
|
+
* @param {Vector2} pos - Center of box
|
|
3644
|
+
* @param {Vector2} [size=(0,0)] - Size of box, uses a point if undefined
|
|
3645
|
+
* @return {boolean} */
|
|
3646
|
+
isOverlapping(pos, size=vec2())
|
|
3647
|
+
{ return isOverlapping(this.pos, this.size, pos, size); }
|
|
3648
|
+
|
|
3364
3649
|
/** Set how this object collides
|
|
3365
3650
|
* @param {boolean} [collideSolidObjects] - Does it collide with solid objects?
|
|
3366
3651
|
* @param {boolean} [isSolid] - Does it collide with and block other objects? (expensive in large numbers)
|
|
@@ -3664,6 +3949,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3664
3949
|
const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
|
|
3665
3950
|
if (useWebGL)
|
|
3666
3951
|
{
|
|
3952
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3667
3953
|
if (screenSpace)
|
|
3668
3954
|
{
|
|
3669
3955
|
// convert to world space
|
|
@@ -3759,6 +4045,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3759
4045
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3760
4046
|
if (useWebGL)
|
|
3761
4047
|
{
|
|
4048
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3762
4049
|
if (screenSpace)
|
|
3763
4050
|
{
|
|
3764
4051
|
// convert to world space
|
|
@@ -3770,7 +4057,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3770
4057
|
const halfSizeX = size.x/2, halfSizeY = size.y/2;
|
|
3771
4058
|
const colorTopInt = colorTop.rgbaInt();
|
|
3772
4059
|
const colorBottomInt = colorBottom.rgbaInt();
|
|
3773
|
-
const c =
|
|
4060
|
+
const c = cos(-angle), s = sin(-angle);
|
|
3774
4061
|
for (let i=4; i--;)
|
|
3775
4062
|
{
|
|
3776
4063
|
const x = i & 1 ? halfSizeX : -halfSizeX;
|
|
@@ -3813,7 +4100,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3813
4100
|
* @memberof Draw */
|
|
3814
4101
|
function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
|
|
3815
4102
|
{
|
|
3816
|
-
ASSERT(
|
|
4103
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
3817
4104
|
ASSERT(isNumber(width), 'width must be a number');
|
|
3818
4105
|
ASSERT(isColor(color), 'color is invalid');
|
|
3819
4106
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3821,6 +4108,7 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3821
4108
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3822
4109
|
if (useWebGL)
|
|
3823
4110
|
{
|
|
4111
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3824
4112
|
let scale = 1;
|
|
3825
4113
|
if (screenSpace)
|
|
3826
4114
|
{
|
|
@@ -3870,6 +4158,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
3870
4158
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
3871
4159
|
const size = vec2(width, halfDelta.length()*2);
|
|
3872
4160
|
pos = pos.add(posA.add(halfDelta));
|
|
4161
|
+
if (screenSpace)
|
|
4162
|
+
halfDelta.y *= -1; // flip angle Y if screen space
|
|
3873
4163
|
angle += halfDelta.angle();
|
|
3874
4164
|
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
3875
4165
|
}
|
|
@@ -3897,7 +4187,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
3897
4187
|
for (let i=sides; i--;)
|
|
3898
4188
|
{
|
|
3899
4189
|
const a = (i/sides)*PI*2;
|
|
3900
|
-
points.push(vec2(
|
|
4190
|
+
points.push(vec2(sin(a)*sizeX, cos(a)*sizeY));
|
|
3901
4191
|
}
|
|
3902
4192
|
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
|
|
3903
4193
|
}
|
|
@@ -3916,7 +4206,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
3916
4206
|
function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
|
|
3917
4207
|
{
|
|
3918
4208
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3919
|
-
ASSERT(
|
|
4209
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
3920
4210
|
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
3921
4211
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
3922
4212
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
@@ -3924,6 +4214,7 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
3924
4214
|
|
|
3925
4215
|
if (useWebGL)
|
|
3926
4216
|
{
|
|
4217
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3927
4218
|
let scale = 1;
|
|
3928
4219
|
if (screenSpace)
|
|
3929
4220
|
{
|
|
@@ -4067,11 +4358,12 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
4067
4358
|
* @param {string} [font=fontDefault]
|
|
4068
4359
|
* @param {string} [fontStyle]
|
|
4069
4360
|
* @param {number} [maxWidth]
|
|
4361
|
+
* @param {number} [angle]
|
|
4070
4362
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
4071
4363
|
* @memberof Draw */
|
|
4072
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, context=drawContext)
|
|
4364
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0, context=drawContext)
|
|
4073
4365
|
{
|
|
4074
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, context);
|
|
4366
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
|
|
4075
4367
|
}
|
|
4076
4368
|
|
|
4077
4369
|
/** Draw text on overlay canvas in world space
|
|
@@ -4086,10 +4378,11 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
4086
4378
|
* @param {string} [font=fontDefault]
|
|
4087
4379
|
* @param {string} [fontStyle]
|
|
4088
4380
|
* @param {number} [maxWidth]
|
|
4381
|
+
* @param {number} [angle]
|
|
4089
4382
|
* @memberof Draw */
|
|
4090
|
-
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth)
|
|
4383
|
+
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0)
|
|
4091
4384
|
{
|
|
4092
|
-
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, overlayContext);
|
|
4385
|
+
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, overlayContext);
|
|
4093
4386
|
}
|
|
4094
4387
|
|
|
4095
4388
|
/** Draw text on overlay canvas in screen space
|
|
@@ -4104,9 +4397,10 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
4104
4397
|
* @param {string} [font=fontDefault]
|
|
4105
4398
|
* @param {string} [fontStyle]
|
|
4106
4399
|
* @param {number} [maxWidth]
|
|
4400
|
+
* @param {number} [angle]
|
|
4107
4401
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
4108
4402
|
* @memberof Draw */
|
|
4109
|
-
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, context=overlayContext)
|
|
4403
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=overlayContext)
|
|
4110
4404
|
{
|
|
4111
4405
|
ASSERT(isString(text), 'text must be a string');
|
|
4112
4406
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -4117,6 +4411,7 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
4117
4411
|
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
4118
4412
|
ASSERT(isString(font), 'font must be a string');
|
|
4119
4413
|
ASSERT(isString(fontStyle), 'fontStyle must be a string');
|
|
4414
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
4120
4415
|
|
|
4121
4416
|
context.fillStyle = color.toString();
|
|
4122
4417
|
context.strokeStyle = lineColor.toString();
|
|
@@ -4126,14 +4421,18 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
4126
4421
|
context.textBaseline = 'middle';
|
|
4127
4422
|
|
|
4128
4423
|
const lines = (text+'').split('\n');
|
|
4129
|
-
|
|
4130
|
-
|
|
4424
|
+
const posY = pos.y - (lines.length-1) * size/2; // center vertically
|
|
4425
|
+
context.save();
|
|
4426
|
+
context.translate(pos.x, posY);
|
|
4427
|
+
context.rotate(-angle);
|
|
4428
|
+
let yOffset = 0;
|
|
4131
4429
|
lines.forEach(line=>
|
|
4132
4430
|
{
|
|
4133
|
-
lineWidth && context.strokeText(line,
|
|
4134
|
-
context.fillText(line,
|
|
4135
|
-
|
|
4431
|
+
lineWidth && context.strokeText(line, 0, yOffset, maxWidth);
|
|
4432
|
+
context.fillText(line, 0, yOffset, maxWidth);
|
|
4433
|
+
yOffset += size;
|
|
4136
4434
|
});
|
|
4435
|
+
context.restore();
|
|
4137
4436
|
}
|
|
4138
4437
|
|
|
4139
4438
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -4145,18 +4444,18 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
4145
4444
|
* @memberof Draw */
|
|
4146
4445
|
function screenToWorld(screenPos)
|
|
4147
4446
|
{
|
|
4148
|
-
let
|
|
4149
|
-
let
|
|
4447
|
+
let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
4448
|
+
let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
4150
4449
|
if (cameraAngle)
|
|
4151
4450
|
{
|
|
4152
4451
|
// apply camera rotation
|
|
4153
|
-
const
|
|
4154
|
-
const rotatedX =
|
|
4155
|
-
const rotatedY =
|
|
4156
|
-
|
|
4157
|
-
|
|
4452
|
+
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
4453
|
+
const rotatedX = x * c - y * s;
|
|
4454
|
+
const rotatedY = x * s + y * c;
|
|
4455
|
+
x = rotatedX;
|
|
4456
|
+
y = rotatedY;
|
|
4158
4457
|
}
|
|
4159
|
-
return new Vector2(
|
|
4458
|
+
return new Vector2(x + cameraPos.x, y + cameraPos.y);
|
|
4160
4459
|
}
|
|
4161
4460
|
|
|
4162
4461
|
/** Convert from world to screen space coordinates
|
|
@@ -4165,24 +4464,64 @@ function screenToWorld(screenPos)
|
|
|
4165
4464
|
* @memberof Draw */
|
|
4166
4465
|
function worldToScreen(worldPos)
|
|
4167
4466
|
{
|
|
4168
|
-
let
|
|
4169
|
-
let
|
|
4467
|
+
let x = worldPos.x - cameraPos.x;
|
|
4468
|
+
let y = worldPos.y - cameraPos.y;
|
|
4170
4469
|
if (cameraAngle)
|
|
4171
4470
|
{
|
|
4172
4471
|
// apply inverse camera rotation
|
|
4173
|
-
const
|
|
4174
|
-
const rotatedX =
|
|
4175
|
-
const rotatedY =
|
|
4176
|
-
|
|
4177
|
-
|
|
4472
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
4473
|
+
const rotatedX = x * c - y * s;
|
|
4474
|
+
const rotatedY = x * s + y * c;
|
|
4475
|
+
x = rotatedX;
|
|
4476
|
+
y = rotatedY;
|
|
4178
4477
|
}
|
|
4179
4478
|
return new Vector2
|
|
4180
4479
|
(
|
|
4181
|
-
|
|
4182
|
-
|
|
4480
|
+
x * cameraScale + mainCanvasSize.x/2 - .5,
|
|
4481
|
+
y * -cameraScale + mainCanvasSize.y/2 - .5
|
|
4183
4482
|
);
|
|
4184
4483
|
}
|
|
4185
4484
|
|
|
4485
|
+
/** Convert from screen to world space coordinates for a directional vector (no translation)
|
|
4486
|
+
* @param {Vector2} screenDelta
|
|
4487
|
+
* @return {Vector2}
|
|
4488
|
+
* @memberof Draw */
|
|
4489
|
+
function screenToWorldDelta(screenDelta)
|
|
4490
|
+
{
|
|
4491
|
+
let x = screenDelta.x / cameraScale;
|
|
4492
|
+
let y = screenDelta.y / -cameraScale;
|
|
4493
|
+
if (cameraAngle)
|
|
4494
|
+
{
|
|
4495
|
+
// apply camera rotation
|
|
4496
|
+
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
4497
|
+
const rotatedX = x * c - y * s;
|
|
4498
|
+
const rotatedY = x * s + y * c;
|
|
4499
|
+
x = rotatedX;
|
|
4500
|
+
y = rotatedY;
|
|
4501
|
+
}
|
|
4502
|
+
return new Vector2(x, y);
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
/** Convert from screen to world space coordinates for a directional vector (no translation)
|
|
4506
|
+
* @param {Vector2} worldDelta
|
|
4507
|
+
* @return {Vector2}
|
|
4508
|
+
* @memberof Draw */
|
|
4509
|
+
function worldToScreenDelta(worldDelta)
|
|
4510
|
+
{
|
|
4511
|
+
let x = worldDelta.x;
|
|
4512
|
+
let y = worldDelta.y;
|
|
4513
|
+
if (cameraAngle)
|
|
4514
|
+
{
|
|
4515
|
+
// apply inverse camera rotation
|
|
4516
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
4517
|
+
const rotatedX = x * c - y * s;
|
|
4518
|
+
const rotatedY = x * s + y * c;
|
|
4519
|
+
x = rotatedX;
|
|
4520
|
+
y = rotatedY;
|
|
4521
|
+
}
|
|
4522
|
+
return new Vector2(x * cameraScale, y * -cameraScale);
|
|
4523
|
+
}
|
|
4524
|
+
|
|
4186
4525
|
/** Get the camera's visible area in world space
|
|
4187
4526
|
* @return {Vector2}
|
|
4188
4527
|
* @memberof Draw */
|
|
@@ -4235,6 +4574,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
4235
4574
|
function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
|
|
4236
4575
|
const sx2 = bleedScale;
|
|
4237
4576
|
const sy2 = bleedScale;
|
|
4577
|
+
sWidth = max(1,sWidth|0);
|
|
4578
|
+
sHeight = max(1,sHeight|0);
|
|
4238
4579
|
const sWidth2 = sWidth - 2*bleedScale;
|
|
4239
4580
|
const sHeight2 = sHeight - 2*bleedScale;
|
|
4240
4581
|
if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
|
|
@@ -4249,7 +4590,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
4249
4590
|
// copy to offscreen canvas
|
|
4250
4591
|
workCanvas.width = sWidth;
|
|
4251
4592
|
workCanvas.height = sHeight;
|
|
4252
|
-
workContext.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
4593
|
+
workContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
4253
4594
|
|
|
4254
4595
|
// tint image using offscreen work context
|
|
4255
4596
|
const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
|
|
@@ -4523,6 +4864,11 @@ let mouseDeltaScreen = vec2();
|
|
|
4523
4864
|
* @memberof Input */
|
|
4524
4865
|
let mouseWheel = 0;
|
|
4525
4866
|
|
|
4867
|
+
/** True if mouse was inside the document window, set to false when mouse leaves
|
|
4868
|
+
* @type {boolean}
|
|
4869
|
+
* @memberof Input */
|
|
4870
|
+
let mouseInWindow = true;
|
|
4871
|
+
|
|
4526
4872
|
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
4527
4873
|
* @type {boolean}
|
|
4528
4874
|
* @memberof Input */
|
|
@@ -4586,9 +4932,9 @@ function inputUpdate()
|
|
|
4586
4932
|
if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
4587
4933
|
inputClear();
|
|
4588
4934
|
|
|
4589
|
-
// update mouse world space position
|
|
4935
|
+
// update mouse world space position and delta
|
|
4590
4936
|
mousePos = screenToWorld(mousePosScreen);
|
|
4591
|
-
mouseDelta = mouseDeltaScreen
|
|
4937
|
+
mouseDelta = screenToWorldDelta(mouseDeltaScreen);
|
|
4592
4938
|
|
|
4593
4939
|
// update gamepads if enabled
|
|
4594
4940
|
gamepadsUpdate();
|
|
@@ -4617,10 +4963,10 @@ function inputInit()
|
|
|
4617
4963
|
document.addEventListener('mousedown', onMouseDown);
|
|
4618
4964
|
document.addEventListener('mouseup', onMouseUp);
|
|
4619
4965
|
document.addEventListener('mousemove', onMouseMove);
|
|
4966
|
+
document.addEventListener('mouseleave', onMouseLeave);
|
|
4620
4967
|
document.addEventListener('wheel', onMouseWheel);
|
|
4621
4968
|
document.addEventListener('contextmenu', onContextMenu);
|
|
4622
4969
|
document.addEventListener('blur', onBlur);
|
|
4623
|
-
document.addEventListener('mouseleave', onMouseLeave);
|
|
4624
4970
|
|
|
4625
4971
|
// init touch input
|
|
4626
4972
|
if (isTouchDevice && touchInputEnable)
|
|
@@ -4642,14 +4988,14 @@ function inputInit()
|
|
|
4642
4988
|
if (inputWASDEmulateDirection)
|
|
4643
4989
|
inputData[0][remapKey(e.code)] = 4;
|
|
4644
4990
|
}
|
|
4645
|
-
function remapKey(
|
|
4991
|
+
function remapKey(k)
|
|
4646
4992
|
{
|
|
4647
4993
|
// handle remapping wasd keys to directions
|
|
4648
4994
|
return inputWASDEmulateDirection ?
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4995
|
+
k === 'KeyW' ? 'ArrowUp' :
|
|
4996
|
+
k === 'KeyS' ? 'ArrowDown' :
|
|
4997
|
+
k === 'KeyA' ? 'ArrowLeft' :
|
|
4998
|
+
k === 'KeyD' ? 'ArrowRight' : k : k;
|
|
4653
4999
|
}
|
|
4654
5000
|
function onMouseDown(e)
|
|
4655
5001
|
{
|
|
@@ -4662,7 +5008,10 @@ function inputInit()
|
|
|
4662
5008
|
|
|
4663
5009
|
isUsingGamepad = false;
|
|
4664
5010
|
inputData[0][e.button] = 3;
|
|
5011
|
+
|
|
5012
|
+
const mousePosScreenLast = mousePosScreen;
|
|
4665
5013
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
5014
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4666
5015
|
inputPreventDefault && e.button && e.preventDefault();
|
|
4667
5016
|
}
|
|
4668
5017
|
function onMouseUp(e)
|
|
@@ -4673,18 +5022,20 @@ function inputInit()
|
|
|
4673
5022
|
}
|
|
4674
5023
|
function onMouseMove(e)
|
|
4675
5024
|
{
|
|
5025
|
+
mouseInWindow = true;
|
|
5026
|
+
const mousePosScreenLast = mousePosScreen;
|
|
4676
5027
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
4677
|
-
|
|
5028
|
+
|
|
5029
|
+
// when pointer is locked use movementX/Y for delta
|
|
5030
|
+
const movement = pointerLockIsActive() ?
|
|
5031
|
+
vec2(e.movementX, e.movementY) :
|
|
5032
|
+
mousePosScreen.subtract(mousePosScreenLast);
|
|
5033
|
+
mouseDeltaScreen = mouseDeltaScreen.add(movement);
|
|
4678
5034
|
}
|
|
5035
|
+
function onMouseLeave() { mouseInWindow = false; } // mouse moved off window
|
|
4679
5036
|
function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
|
|
4680
5037
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
4681
5038
|
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
5039
|
}
|
|
4689
5040
|
|
|
4690
5041
|
// convert a mouse or touch event position to screen space
|
|
@@ -4728,8 +5079,8 @@ function gamepadsUpdate()
|
|
|
4728
5079
|
else if (touchGamepadStick.lengthSquared() > .3)
|
|
4729
5080
|
{
|
|
4730
5081
|
// convert to 8 way dpad
|
|
4731
|
-
sticks[0].x =
|
|
4732
|
-
sticks[0].y = -
|
|
5082
|
+
sticks[0].x = round(touchGamepadStick.x);
|
|
5083
|
+
sticks[0].y = -round(touchGamepadStick.y);
|
|
4733
5084
|
sticks[0] = sticks[0].clampLength();
|
|
4734
5085
|
}
|
|
4735
5086
|
|
|
@@ -4820,7 +5171,7 @@ let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick =
|
|
|
4820
5171
|
function touchGamepadButtonCenter()
|
|
4821
5172
|
{
|
|
4822
5173
|
// draw right face buttons
|
|
4823
|
-
|
|
5174
|
+
const center = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
4824
5175
|
if (touchGamepadButtonCount <= 2)
|
|
4825
5176
|
center.x += touchGamepadSize/2;
|
|
4826
5177
|
return center;
|
|
@@ -4830,14 +5181,13 @@ function touchGamepadButtonCenter()
|
|
|
4830
5181
|
function touchInputInit()
|
|
4831
5182
|
{
|
|
4832
5183
|
// add non passive touch event listeners
|
|
4833
|
-
let handleTouch = handleTouchDefault;
|
|
4834
5184
|
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
4835
5185
|
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
4836
5186
|
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
4837
5187
|
|
|
4838
5188
|
// handle all touch events the same way
|
|
4839
5189
|
let wasTouching;
|
|
4840
|
-
function
|
|
5190
|
+
function handleTouch(e)
|
|
4841
5191
|
{
|
|
4842
5192
|
if (!touchInputEnable)
|
|
4843
5193
|
return;
|
|
@@ -4857,11 +5207,11 @@ function touchInputInit()
|
|
|
4857
5207
|
{
|
|
4858
5208
|
// set event pos and pass it along
|
|
4859
5209
|
const pos = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
4860
|
-
const
|
|
5210
|
+
const mousePosScreenLast = mousePosScreen;
|
|
4861
5211
|
mousePosScreen = mouseEventToScreen(pos);
|
|
4862
5212
|
if (wasTouching)
|
|
4863
5213
|
{
|
|
4864
|
-
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(
|
|
5214
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4865
5215
|
isUsingGamepad = touchGamepadEnable;
|
|
4866
5216
|
}
|
|
4867
5217
|
else
|
|
@@ -4893,7 +5243,7 @@ function touchInputInit()
|
|
|
4893
5243
|
if (touching)
|
|
4894
5244
|
{
|
|
4895
5245
|
touchGamepadTimer.set();
|
|
4896
|
-
if (
|
|
5246
|
+
if (touchGamepadCenterButton && !wasTouching && paused)
|
|
4897
5247
|
{
|
|
4898
5248
|
// touch anywhere to press start when paused
|
|
4899
5249
|
touchGamepadButtons[9] = 1;
|
|
@@ -4932,7 +5282,8 @@ function touchInputInit()
|
|
|
4932
5282
|
if (button < touchGamepadButtonCount)
|
|
4933
5283
|
touchGamepadButtons[button] = 1;
|
|
4934
5284
|
}
|
|
4935
|
-
else if (
|
|
5285
|
+
else if (touchGamepadCenterButton && !wasTouching &&
|
|
5286
|
+
startCenter.distance(touchPos) < touchGamepadSize)
|
|
4936
5287
|
{
|
|
4937
5288
|
// virtual start button in center
|
|
4938
5289
|
touchGamepadButtons[9] = 1;
|
|
@@ -4987,7 +5338,7 @@ function touchGamepadRender()
|
|
|
4987
5338
|
const buttonSize = touchGamepadButtonCount > 1 ? touchGamepadSize/4 : touchGamepadSize/2;
|
|
4988
5339
|
for (let i=0; i<touchGamepadButtonCount; i++)
|
|
4989
5340
|
{
|
|
4990
|
-
|
|
5341
|
+
const j = mod(i-1, 4);
|
|
4991
5342
|
let button = touchGamepadButtonCount > 2 ?
|
|
4992
5343
|
j : min(j, touchGamepadButtonCount-1);
|
|
4993
5344
|
// fix button locations (swap 2 and 3 to match gamepad layout)
|
|
@@ -5089,6 +5440,10 @@ class Sound
|
|
|
5089
5440
|
{
|
|
5090
5441
|
if (!soundEnable || headlessMode) return;
|
|
5091
5442
|
|
|
5443
|
+
ASSERT(!zzfxSound || isArray(zzfxSound), 'zzfxSound is invalid');
|
|
5444
|
+
ASSERT(isNumber(range), 'range must be a number');
|
|
5445
|
+
ASSERT(isNumber(taper), 'taper must be a number');
|
|
5446
|
+
|
|
5092
5447
|
/** @property {number} - World space max range of sound */
|
|
5093
5448
|
this.range = range;
|
|
5094
5449
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
@@ -5127,6 +5482,12 @@ class Sound
|
|
|
5127
5482
|
*/
|
|
5128
5483
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false)
|
|
5129
5484
|
{
|
|
5485
|
+
ASSERT(!pos || isVector2(pos), 'pos must be a vec2');
|
|
5486
|
+
ASSERT(isNumber(volume), 'volume must be a number');
|
|
5487
|
+
ASSERT(isNumber(pitch), 'pitch must be a number');
|
|
5488
|
+
ASSERT(isNumber(randomnessScale), 'randomnessScale must be a number');
|
|
5489
|
+
|
|
5490
|
+
|
|
5130
5491
|
if (!soundEnable || headlessMode) return;
|
|
5131
5492
|
if (!this.sampleChannels) return;
|
|
5132
5493
|
|
|
@@ -5172,6 +5533,7 @@ class Sound
|
|
|
5172
5533
|
*/
|
|
5173
5534
|
playNote(semitoneOffset=0, pos, volume)
|
|
5174
5535
|
{
|
|
5536
|
+
ASSERT(isNumber(semitoneOffset), 'semitoneOffset must be a number');
|
|
5175
5537
|
const pitch = getNoteFrequency(semitoneOffset, 1);
|
|
5176
5538
|
return this.play(pos, volume, pitch, 0);
|
|
5177
5539
|
}
|
|
@@ -5180,7 +5542,7 @@ class Sound
|
|
|
5180
5542
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
5181
5543
|
*/
|
|
5182
5544
|
getDuration()
|
|
5183
|
-
{ return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
|
|
5545
|
+
{ return this.sampleChannels && this.sampleRate ? this.sampleChannels[0].length / this.sampleRate : 0; }
|
|
5184
5546
|
|
|
5185
5547
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
5186
5548
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -5222,6 +5584,7 @@ class SoundWave extends Sound
|
|
|
5222
5584
|
super(undefined, range, taper);
|
|
5223
5585
|
if (!soundEnable || headlessMode) return;
|
|
5224
5586
|
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
5587
|
+
ASSERT(isNumber(randomness), 'randomness must be a number');
|
|
5225
5588
|
|
|
5226
5589
|
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
5227
5590
|
this.onloadCallback = onloadCallback;
|
|
@@ -5438,7 +5801,7 @@ class SoundInstance
|
|
|
5438
5801
|
/** Get the total duration of this sound
|
|
5439
5802
|
* @return {number} - Total duration in seconds
|
|
5440
5803
|
*/
|
|
5441
|
-
getDuration() { return this.sound.getDuration() / this.rate; }
|
|
5804
|
+
getDuration() { return this.rate ? this.sound.getDuration() / this.rate : 0; }
|
|
5442
5805
|
|
|
5443
5806
|
/** Get source of this sound instance
|
|
5444
5807
|
* @return {AudioBufferSourceNode}
|
|
@@ -5628,10 +5991,10 @@ function zzfxG
|
|
|
5628
5991
|
|
|
5629
5992
|
// biquad LP/HP filter
|
|
5630
5993
|
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
5631
|
-
|
|
5632
|
-
a0 = 1 + alpha, a1 = -2*
|
|
5633
|
-
b0 = (1 + sign(filter) *
|
|
5634
|
-
b1 = -(sign(filter) +
|
|
5994
|
+
cosw = cos(w), alpha = sin(w) / 2 / quality,
|
|
5995
|
+
a0 = 1 + alpha, a1 = -2*cosw / a0, a2 = (1 - alpha) / a0,
|
|
5996
|
+
b0 = (1 + sign(filter) * cosw) / 2 / a0,
|
|
5997
|
+
b1 = -(sign(filter) + cosw) / a0, b2 = b0,
|
|
5635
5998
|
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
5636
5999
|
|
|
5637
6000
|
// scale by sample rate
|
|
@@ -5654,15 +6017,15 @@ function zzfxG
|
|
|
5654
6017
|
if (!(++crush%(bitCrush*100|0))) // bit crush
|
|
5655
6018
|
{
|
|
5656
6019
|
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
|
-
|
|
6020
|
+
(t/PI2%1 < shapeCurve/2? 1 : -1) : // 5 square duty
|
|
6021
|
+
sin(t**3) : // 4 noise
|
|
6022
|
+
max(min(tan(t),1),-1): // 3 tan
|
|
6023
|
+
1-(2*t/PI2%2+2)%2: // 2 saw
|
|
6024
|
+
1-4*abs(round(t/PI2)-t/PI2): // 1 triangle
|
|
6025
|
+
sin(t); // 0 sin
|
|
5663
6026
|
|
|
5664
6027
|
s = (repeatTime ?
|
|
5665
|
-
1 - tremolo + tremolo*
|
|
6028
|
+
1 - tremolo + tremolo*sin(PI2*i/repeatTime) // tremolo
|
|
5666
6029
|
: 1) *
|
|
5667
6030
|
(shape>4?s:sign(s)*abs(s)**shapeCurve) * // shape curve
|
|
5668
6031
|
(i < attack ? i/attack : // attack
|
|
@@ -5684,8 +6047,8 @@ function zzfxG
|
|
|
5684
6047
|
}
|
|
5685
6048
|
|
|
5686
6049
|
f = (frequency += slide += deltaSlide) *// frequency
|
|
5687
|
-
|
|
5688
|
-
t += f + f*noise*
|
|
6050
|
+
cos(modulation*modOffset++); // modulation
|
|
6051
|
+
t += f + f*noise*sin(i**5); // noise
|
|
5689
6052
|
|
|
5690
6053
|
if (jump && ++jump > pitchJumpTime) // pitch jump
|
|
5691
6054
|
{
|
|
@@ -5751,23 +6114,23 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
|
|
|
5751
6114
|
}
|
|
5752
6115
|
}
|
|
5753
6116
|
|
|
5754
|
-
/** Return the
|
|
5755
|
-
*
|
|
6117
|
+
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
6118
|
+
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
5756
6119
|
* @param {Vector2} posStart
|
|
5757
6120
|
* @param {Vector2} posEnd
|
|
5758
6121
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
6122
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
5759
6123
|
* @param {boolean} [solidOnly=true] - Only check solid layers if true
|
|
5760
|
-
* @return {Vector2}
|
|
6124
|
+
* @return {Vector2|undefined} - position of the center of the tile hit or undefined if no hit
|
|
5761
6125
|
* @memberof TileLayers */
|
|
5762
|
-
function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
|
|
6126
|
+
function tileCollisionRaycast(posStart, posEnd, object, normal, solidOnly=true)
|
|
5763
6127
|
{
|
|
5764
6128
|
for (const layer of tileCollisionLayers)
|
|
5765
6129
|
{
|
|
5766
6130
|
if (!solidOnly || layer.isSolid)
|
|
5767
6131
|
{
|
|
5768
|
-
const hitPos = layer.collisionRaycast(posStart, posEnd, object)
|
|
5769
|
-
if (hitPos)
|
|
5770
|
-
return hitPos;
|
|
6132
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, object, normal)
|
|
6133
|
+
if (hitPos) return hitPos;
|
|
5771
6134
|
}
|
|
5772
6135
|
}
|
|
5773
6136
|
}
|
|
@@ -5905,6 +6268,8 @@ class CanvasLayer extends EngineObject
|
|
|
5905
6268
|
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
5906
6269
|
const useWebGL = false; // do not use webgl by default
|
|
5907
6270
|
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
6271
|
+
/** @property {boolean} - True if WebGL texture needs to be refreshed */
|
|
6272
|
+
this.refreshWebGL = false;
|
|
5908
6273
|
|
|
5909
6274
|
// disable physics by default
|
|
5910
6275
|
this.mass = this.gravityScale = this.friction = this.restitution = 0;
|
|
@@ -5938,8 +6303,15 @@ class CanvasLayer extends EngineObject
|
|
|
5938
6303
|
* @memberof Draw */
|
|
5939
6304
|
draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
|
|
5940
6305
|
{
|
|
5941
|
-
// draw the canvas layer as a single tile that uses the whole texture
|
|
5942
6306
|
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
6307
|
+
if (useWebGL && this.refreshWebGL)
|
|
6308
|
+
{
|
|
6309
|
+
// update the WebGL texture
|
|
6310
|
+
this.textureInfo.createWebGLTexture();
|
|
6311
|
+
this.refreshWebGL = false;
|
|
6312
|
+
}
|
|
6313
|
+
|
|
6314
|
+
// draw the canvas layer as a single tile that uses the whole texture
|
|
5943
6315
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
5944
6316
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
5945
6317
|
}
|
|
@@ -6009,9 +6381,18 @@ class CanvasLayer extends EngineObject
|
|
|
6009
6381
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
6010
6382
|
|
|
6011
6383
|
/** Create or update the WebGL texture for this layer
|
|
6012
|
-
* @param {boolean} [enable] - enable WebGL rendering and update the texture
|
|
6013
|
-
|
|
6384
|
+
* @param {boolean} [enable] - enable WebGL rendering and update the texture
|
|
6385
|
+
* @param {boolean} [immediate] - shoulkd the texture be updated immediately
|
|
6386
|
+
*/
|
|
6387
|
+
useWebGL(enable=true, immediate=false)
|
|
6014
6388
|
{
|
|
6389
|
+
if (!immediate && enable && this.textureInfo.hasWebGL())
|
|
6390
|
+
{
|
|
6391
|
+
// refresh the texture when needed
|
|
6392
|
+
this.refreshWebGL = true;
|
|
6393
|
+
return;
|
|
6394
|
+
}
|
|
6395
|
+
|
|
6015
6396
|
if (enable)
|
|
6016
6397
|
this.textureInfo.createWebGLTexture();
|
|
6017
6398
|
else
|
|
@@ -6042,7 +6423,7 @@ class TileLayer extends CanvasLayer
|
|
|
6042
6423
|
*/
|
|
6043
6424
|
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
6044
6425
|
{
|
|
6045
|
-
const canvasSize = size.multiply(tileInfo.size);
|
|
6426
|
+
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
6046
6427
|
super(position, size, 0, renderOrder, canvasSize);
|
|
6047
6428
|
|
|
6048
6429
|
// set tile info
|
|
@@ -6095,6 +6476,13 @@ class TileLayer extends CanvasLayer
|
|
|
6095
6476
|
{
|
|
6096
6477
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
6097
6478
|
|
|
6479
|
+
if (this.refreshWebGL)
|
|
6480
|
+
{
|
|
6481
|
+
// update the WebGL texture
|
|
6482
|
+
this.textureInfo.createWebGLTexture();
|
|
6483
|
+
this.refreshWebGL = false;
|
|
6484
|
+
}
|
|
6485
|
+
|
|
6098
6486
|
// draw the tile layer as a single tile
|
|
6099
6487
|
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
6100
6488
|
const size = this.drawSize || this.size;
|
|
@@ -6131,8 +6519,9 @@ class TileLayer extends CanvasLayer
|
|
|
6131
6519
|
drawCanvas = this.canvas;
|
|
6132
6520
|
drawContext = this.context;
|
|
6133
6521
|
cameraPos = this.size.scale(.5);
|
|
6134
|
-
|
|
6135
|
-
|
|
6522
|
+
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
6523
|
+
cameraScale = tileSize.x;
|
|
6524
|
+
mainCanvasSize = this.size.multiply(tileSize);
|
|
6136
6525
|
if (clear)
|
|
6137
6526
|
{
|
|
6138
6527
|
// clear and set size
|
|
@@ -6292,66 +6681,45 @@ class TileCollisionLayer extends TileLayer
|
|
|
6292
6681
|
// check if the object should collide with this tile
|
|
6293
6682
|
const tileData = this.collisionData[y*this.size.x+x];
|
|
6294
6683
|
if (tileData)
|
|
6295
|
-
if (!object || object.collideWithTile(tileData,
|
|
6684
|
+
if (!object || object.collideWithTile(tileData,
|
|
6685
|
+
hitPos.set(x + this.pos.x, y + this.pos.y)))
|
|
6296
6686
|
return true;
|
|
6297
6687
|
}
|
|
6298
6688
|
return false;
|
|
6299
6689
|
}
|
|
6300
6690
|
|
|
6301
|
-
/** Return the
|
|
6302
|
-
*
|
|
6691
|
+
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
6692
|
+
* The point will be inside the colliding tile if it hits (may have a tiny shift)
|
|
6303
6693
|
* @param {Vector2} posStart
|
|
6304
6694
|
* @param {Vector2} posEnd
|
|
6305
|
-
* @param {EngineObject} [object]
|
|
6306
|
-
* @
|
|
6307
|
-
|
|
6695
|
+
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
6696
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
6697
|
+
* @return {Vector2|undefined} */
|
|
6698
|
+
collisionRaycast(posStart, posEnd, object, normal)
|
|
6308
6699
|
{
|
|
6309
6700
|
ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
|
|
6310
6701
|
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)
|
|
6702
|
+
|
|
6703
|
+
const localPos = new Vector2;
|
|
6704
|
+
const collisionTest = (pos)=>
|
|
6332
6705
|
{
|
|
6333
6706
|
// 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;
|
|
6707
|
+
localPos.set(pos.x - this.pos.x, pos.y - this.pos.y);
|
|
6708
|
+
const tileData = this.getCollisionData(localPos);
|
|
6709
|
+
return tileData && (!object || object.collideWithTile(tileData, pos));
|
|
6352
6710
|
}
|
|
6353
|
-
|
|
6354
6711
|
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
6712
|
+
const hitPos = lineTest(posStart, posEnd, collisionTest, normal);
|
|
6713
|
+
if (hitPos)
|
|
6714
|
+
{
|
|
6715
|
+
const tilePos = hitPos.floor().add(vec2(.5));
|
|
6716
|
+
debugRaycast && debugRect(tilePos, vec2(1), '#f008');
|
|
6717
|
+
debugRaycast && debugLine(posStart, hitPos, '#f00', .02);
|
|
6718
|
+
debugRaycast && debugPoint(hitPos, '#0f0');
|
|
6719
|
+
debugRaycast && normal &&
|
|
6720
|
+
debugLine(hitPos, hitPos.add(normal), '#ff0', .02);
|
|
6721
|
+
return hitPos;
|
|
6722
|
+
}
|
|
6355
6723
|
}
|
|
6356
6724
|
}
|
|
6357
6725
|
/**
|
|
@@ -6506,19 +6874,34 @@ class ParticleEmitter extends EngineObject
|
|
|
6506
6874
|
this.particleCreateCallback = undefined;
|
|
6507
6875
|
/** @property {number} - Track particle emit time */
|
|
6508
6876
|
this.emitTimeBuffer = 0;
|
|
6877
|
+
/** @property {number} - Percentage of velocity to pass to particles (0-1) */
|
|
6878
|
+
this.velocityInheritance = 0;
|
|
6879
|
+
|
|
6880
|
+
// track previous position and angle
|
|
6881
|
+
this.previousAngle = this.angle;
|
|
6882
|
+
this.previousPos = this.pos.copy();
|
|
6509
6883
|
}
|
|
6510
6884
|
|
|
6511
6885
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
6512
6886
|
update()
|
|
6513
6887
|
{
|
|
6514
|
-
|
|
6515
|
-
|
|
6888
|
+
if (this.velocityInheritance)
|
|
6889
|
+
{
|
|
6890
|
+
// pass emitter velocity to particles
|
|
6891
|
+
const p = this.velocityInheritance;
|
|
6892
|
+
this.velocity.x = p * (this.pos.x - this.previousPos.x);
|
|
6893
|
+
this.velocity.y = p * (this.pos.y - this.previousPos.y);
|
|
6894
|
+
this.angleVelocity = p * (this.angle - this.previousAngle);
|
|
6895
|
+
this.previousAngle = this.angle;
|
|
6896
|
+
this.previousPos.x = this.pos.x;
|
|
6897
|
+
this.previousPos.y = this.pos.y;
|
|
6898
|
+
}
|
|
6516
6899
|
|
|
6517
6900
|
// update emitter
|
|
6518
6901
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
6519
6902
|
{
|
|
6520
6903
|
// emit particles
|
|
6521
|
-
if (this.emitRate
|
|
6904
|
+
if (this.emitRate && particleEmitRateScale)
|
|
6522
6905
|
{
|
|
6523
6906
|
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
6524
6907
|
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
@@ -6571,7 +6954,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6571
6954
|
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
6955
|
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
6573
6956
|
particle.angleVelocity = angleSpeed;
|
|
6574
|
-
if (!this.localSpace)
|
|
6957
|
+
if (!this.localSpace && this.velocityInheritance > 0)
|
|
6575
6958
|
{
|
|
6576
6959
|
// apply emitter velocity to particle
|
|
6577
6960
|
particle.velocity.x += this.velocity.x;
|
|
@@ -6656,8 +7039,6 @@ class Particle extends EngineObject
|
|
|
6656
7039
|
/** Update the object physics, called automatically by engine once each frame */
|
|
6657
7040
|
update()
|
|
6658
7041
|
{
|
|
6659
|
-
super.update();
|
|
6660
|
-
|
|
6661
7042
|
if (this.collideTiles || this.collideSolidObjects)
|
|
6662
7043
|
{
|
|
6663
7044
|
// only apply max circular speed if particle can collide
|
|
@@ -6669,6 +7050,16 @@ class Particle extends EngineObject
|
|
|
6669
7050
|
this.velocity.y *= s;
|
|
6670
7051
|
}
|
|
6671
7052
|
}
|
|
7053
|
+
|
|
7054
|
+
if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
|
|
7055
|
+
{
|
|
7056
|
+
// destroy particle when its time runs out
|
|
7057
|
+
const c = this.colorEnd;
|
|
7058
|
+
this.color.set(c.r, c.g, c.b, c.a);
|
|
7059
|
+
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
7060
|
+
this.destroyCallback && this.destroyCallback(this);
|
|
7061
|
+
this.destroyed = 1;
|
|
7062
|
+
}
|
|
6672
7063
|
}
|
|
6673
7064
|
|
|
6674
7065
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
@@ -6677,7 +7068,7 @@ class Particle extends EngineObject
|
|
|
6677
7068
|
// lerp color and size
|
|
6678
7069
|
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
6679
7070
|
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
6680
|
-
|
|
7071
|
+
const size = vec2(radius);
|
|
6681
7072
|
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
6682
7073
|
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
6683
7074
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
@@ -6697,7 +7088,7 @@ class Particle extends EngineObject
|
|
|
6697
7088
|
{
|
|
6698
7089
|
// in local space of emitter
|
|
6699
7090
|
const a = this.localSpaceEmitter.angle;
|
|
6700
|
-
const c =
|
|
7091
|
+
const c = cos(a), s = sin(a);
|
|
6701
7092
|
pos = this.localSpaceEmitter.pos.add(
|
|
6702
7093
|
new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
|
|
6703
7094
|
angle += this.localSpaceEmitter.angle;
|
|
@@ -6713,22 +7104,15 @@ class Particle extends EngineObject
|
|
|
6713
7104
|
{
|
|
6714
7105
|
// stretch in direction of motion
|
|
6715
7106
|
const trailLength = speed * this.trailScale;
|
|
6716
|
-
|
|
6717
|
-
angle =
|
|
6718
|
-
drawTile(pos,
|
|
7107
|
+
size.y = max(size.x, trailLength);
|
|
7108
|
+
angle = atan2(direction.x, direction.y);
|
|
7109
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6719
7110
|
}
|
|
6720
7111
|
}
|
|
6721
7112
|
else
|
|
6722
|
-
drawTile(pos,
|
|
7113
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6723
7114
|
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
|
-
}
|
|
7115
|
+
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
6732
7116
|
}
|
|
6733
7117
|
}
|
|
6734
7118
|
/**
|
|
@@ -6898,11 +7282,11 @@ class Medal
|
|
|
6898
7282
|
const descriptionSize = height*.3;
|
|
6899
7283
|
const pos = vec2(x + medalDisplayIconSize + 2*gap.x, y + gap.y*2 + nameSize/2);
|
|
6900
7284
|
const textWidth = width - medalDisplayIconSize - 3*gap.x;
|
|
6901
|
-
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, textWidth);
|
|
7285
|
+
drawTextScreen(this.name, pos, nameSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
|
|
6902
7286
|
|
|
6903
7287
|
// draw the description
|
|
6904
7288
|
pos.y = y + height - gap.y*2 - descriptionSize/2;
|
|
6905
|
-
drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, textWidth);
|
|
7289
|
+
drawTextScreen(this.description, pos, descriptionSize, BLACK, 0, undefined, 'left', undefined, undefined, textWidth);
|
|
6906
7290
|
context.restore();
|
|
6907
7291
|
}
|
|
6908
7292
|
|
|
@@ -6951,7 +7335,7 @@ let glContext;
|
|
|
6951
7335
|
let glAntialias = true;
|
|
6952
7336
|
|
|
6953
7337
|
// WebGL internal variables not exposed to documentation
|
|
6954
|
-
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos;
|
|
7338
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
|
|
6955
7339
|
|
|
6956
7340
|
// WebGL internal constants
|
|
6957
7341
|
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
@@ -6970,7 +7354,11 @@ function glInit()
|
|
|
6970
7354
|
// keep set of texture infos so they can be restored if context is lost
|
|
6971
7355
|
glTextureInfos = new Set;
|
|
6972
7356
|
|
|
6973
|
-
if (!glEnable || headlessMode)
|
|
7357
|
+
if (!glEnable || headlessMode)
|
|
7358
|
+
{
|
|
7359
|
+
glCanBeEnabled = false;
|
|
7360
|
+
return;
|
|
7361
|
+
}
|
|
6974
7362
|
|
|
6975
7363
|
// create the canvas and textures
|
|
6976
7364
|
glCanvas = document.createElement('canvas');
|
|
@@ -6981,10 +7369,11 @@ function glInit()
|
|
|
6981
7369
|
console.warn('WebGL2 not supported, falling back to 2D canvas rendering!');
|
|
6982
7370
|
glCanvas = glContext = undefined;
|
|
6983
7371
|
glEnable = false;
|
|
7372
|
+
glCanBeEnabled = false;
|
|
6984
7373
|
return;
|
|
6985
7374
|
}
|
|
6986
7375
|
|
|
6987
|
-
//
|
|
7376
|
+
// attach the WebGL canvas
|
|
6988
7377
|
const rootElement = mainCanvas.parentElement;
|
|
6989
7378
|
rootElement.appendChild(glCanvas);
|
|
6990
7379
|
|
|
@@ -7007,7 +7396,7 @@ function glInit()
|
|
|
7007
7396
|
});
|
|
7008
7397
|
glCanvas.addEventListener('webglcontextrestored', ()=>
|
|
7009
7398
|
{
|
|
7010
|
-
glEnable = true; //
|
|
7399
|
+
glEnable = true; // re-enable WebGL rendering
|
|
7011
7400
|
glCanvas.style.display = ''; // show the gl canvas
|
|
7012
7401
|
LOG('WebGL context restored, reinitializing...');
|
|
7013
7402
|
|
|
@@ -7159,8 +7548,8 @@ function glPreRender()
|
|
|
7159
7548
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
7160
7549
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
7161
7550
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
7162
|
-
const ca =
|
|
7163
|
-
const sa =
|
|
7551
|
+
const ca = cos(cameraAngle);
|
|
7552
|
+
const sa = sin(cameraAngle);
|
|
7164
7553
|
const transform = [
|
|
7165
7554
|
s.x * ca, s.y * sa, 0, 0,
|
|
7166
7555
|
-s.x * sa, s.y * ca, 0, 0,
|
|
@@ -7465,8 +7854,8 @@ function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
|
|
|
7465
7854
|
// transform the point
|
|
7466
7855
|
const px = p.x*sx;
|
|
7467
7856
|
const py = p.y*sy;
|
|
7468
|
-
const sa =
|
|
7469
|
-
const ca =
|
|
7857
|
+
const sa = sin(-angle);
|
|
7858
|
+
const ca = cos(-angle);
|
|
7470
7859
|
pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
|
|
7471
7860
|
}
|
|
7472
7861
|
const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
|
|
@@ -8383,12 +8772,12 @@ class UISystemPlugin
|
|
|
8383
8772
|
/** Draw a rectangle to the UI context
|
|
8384
8773
|
* @param {Vector2} pos
|
|
8385
8774
|
* @param {Vector2} size
|
|
8386
|
-
* @param {Color} [color
|
|
8387
|
-
* @param {number} [lineWidth
|
|
8388
|
-
* @param {Color} [lineColor
|
|
8389
|
-
* @param {number} [cornerRadius
|
|
8390
|
-
* @param {Color} [gradientColor
|
|
8391
|
-
drawRect(pos, size, color=
|
|
8775
|
+
* @param {Color} [color]
|
|
8776
|
+
* @param {number} [lineWidth]
|
|
8777
|
+
* @param {Color} [lineColor]
|
|
8778
|
+
* @param {number} [cornerRadius]
|
|
8779
|
+
* @param {Color} [gradientColor] */
|
|
8780
|
+
drawRect(pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, cornerRadius=0, gradientColor)
|
|
8392
8781
|
{
|
|
8393
8782
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
8394
8783
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -8467,10 +8856,14 @@ class UISystemPlugin
|
|
|
8467
8856
|
* @param {string} [align]
|
|
8468
8857
|
* @param {string} [font=uiSystem.defaultFont]
|
|
8469
8858
|
* @param {string} [fontStyle]
|
|
8470
|
-
* @param {boolean} [applyMaxWidth=true]
|
|
8471
|
-
|
|
8859
|
+
* @param {boolean} [applyMaxWidth=true]
|
|
8860
|
+
* @param {Vector2} [textShadow]
|
|
8861
|
+
*/
|
|
8862
|
+
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, fontStyle='', applyMaxWidth=true, textShadow=undefined)
|
|
8472
8863
|
{
|
|
8473
|
-
|
|
8864
|
+
if (textShadow)
|
|
8865
|
+
drawTextScreen(text, pos.add(textShadow), size.y, BLACK, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, 0, uiSystem.uiContext);
|
|
8866
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, 0, uiSystem.uiContext);
|
|
8474
8867
|
}
|
|
8475
8868
|
|
|
8476
8869
|
/**
|
|
@@ -8497,6 +8890,25 @@ class UISystemPlugin
|
|
|
8497
8890
|
setCallback(onDragLeave, 'dragleave');
|
|
8498
8891
|
setCallback(onDragOver, 'dragover');
|
|
8499
8892
|
}
|
|
8893
|
+
|
|
8894
|
+
/** Convert a screen space position to native UI position
|
|
8895
|
+
* @param {Vector2} pos
|
|
8896
|
+
* @return {Vector2}
|
|
8897
|
+
*/
|
|
8898
|
+
screenToNative(pos)
|
|
8899
|
+
{
|
|
8900
|
+
if (!uiSystem.nativeHeight)
|
|
8901
|
+
return pos;
|
|
8902
|
+
|
|
8903
|
+
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
8904
|
+
const sInv = 1/s;
|
|
8905
|
+
const p = pos.copy();
|
|
8906
|
+
p.x += s*mainCanvasSize.x/2;
|
|
8907
|
+
p.x *= sInv;
|
|
8908
|
+
p.y *= sInv;
|
|
8909
|
+
p.x -= sInv*mainCanvasSize.x/2;
|
|
8910
|
+
return p;
|
|
8911
|
+
}
|
|
8500
8912
|
}
|
|
8501
8913
|
|
|
8502
8914
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -8573,6 +8985,9 @@ class UIObject
|
|
|
8573
8985
|
/** @property {boolean} - True if this can be a hover object */
|
|
8574
8986
|
this.canBeHover = true;
|
|
8575
8987
|
uiSystem.uiObjects.push(this);
|
|
8988
|
+
|
|
8989
|
+
/** @property {Vector2} - How much to offset the text shadow or undefined */
|
|
8990
|
+
this.textShadow = undefined;
|
|
8576
8991
|
}
|
|
8577
8992
|
|
|
8578
8993
|
/** Add a child UIObject to this object
|
|
@@ -8600,24 +9015,20 @@ class UIObject
|
|
|
8600
9015
|
*/
|
|
8601
9016
|
isMouseOverlapping()
|
|
8602
9017
|
{
|
|
9018
|
+
if (!mouseInWindow) return false;
|
|
9019
|
+
|
|
8603
9020
|
const size = !isTouchDevice ? this.size :
|
|
8604
9021
|
this.size.add(vec2(this.extraTouchSize || 0));
|
|
8605
|
-
|
|
8606
|
-
return isOverlapping(this.pos, size, mousePosScreen);
|
|
8607
|
-
|
|
8608
|
-
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
8609
|
-
const sInv = 1/s;
|
|
8610
|
-
let pos = mousePosScreen.copy();
|
|
8611
|
-
pos.x += s*mainCanvasSize.x/2;
|
|
8612
|
-
pos.x *= sInv;
|
|
8613
|
-
pos.y *= sInv;
|
|
8614
|
-
pos.x -= sInv*mainCanvasSize.x/2;
|
|
9022
|
+
const pos = uiSystem.screenToNative(mousePosScreen);
|
|
8615
9023
|
return isOverlapping(this.pos, size, pos);
|
|
8616
9024
|
}
|
|
8617
9025
|
|
|
8618
9026
|
/** Update the object, called automatically by plugin once each frame */
|
|
8619
9027
|
update()
|
|
8620
9028
|
{
|
|
9029
|
+
// call the custom update callback
|
|
9030
|
+
this.onUpdate();
|
|
9031
|
+
|
|
8621
9032
|
const wasHover = uiSystem.lastHoverObject === this;
|
|
8622
9033
|
const isActive = this.isActiveObject();
|
|
8623
9034
|
const mouseDown = mouseIsDown(0);
|
|
@@ -8674,7 +9085,7 @@ class UIObject
|
|
|
8674
9085
|
|
|
8675
9086
|
const lineColor = this.interactive && this.isActiveObject() && !this.disabled ? this.color : this.lineColor;
|
|
8676
9087
|
const color = this.disabled ? this.disabledColor : this.interactive ? this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
|
|
8677
|
-
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
|
|
9088
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius, this.gradientColor);
|
|
8678
9089
|
}
|
|
8679
9090
|
|
|
8680
9091
|
/** Special update when object is not visible */
|
|
@@ -8701,6 +9112,9 @@ class UIObject
|
|
|
8701
9112
|
/** @return {boolean} - Is the mouse held onto this element */
|
|
8702
9113
|
isActiveObject() { return uiSystem.activeObject === this; }
|
|
8703
9114
|
|
|
9115
|
+
/** Called each frame when object updates */
|
|
9116
|
+
onUpdate() {}
|
|
9117
|
+
|
|
8704
9118
|
/** Called when the mouse enters the object */
|
|
8705
9119
|
onEnter() {}
|
|
8706
9120
|
|
|
@@ -8755,8 +9169,9 @@ class UIText extends UIObject
|
|
|
8755
9169
|
}
|
|
8756
9170
|
render()
|
|
8757
9171
|
{
|
|
9172
|
+
// only render the text
|
|
8758
9173
|
const textSize = this.getTextSize();
|
|
8759
|
-
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle);
|
|
9174
|
+
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
8760
9175
|
}
|
|
8761
9176
|
}
|
|
8762
9177
|
|
|
@@ -8832,7 +9247,7 @@ class UIButton extends UIObject
|
|
|
8832
9247
|
// draw the text scaled to fit
|
|
8833
9248
|
const textSize = this.getTextSize();
|
|
8834
9249
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
8835
|
-
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
9250
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
8836
9251
|
}
|
|
8837
9252
|
}
|
|
8838
9253
|
|
|
@@ -8886,7 +9301,7 @@ class UICheckbox extends UIObject
|
|
|
8886
9301
|
const textSize = this.getTextSize();
|
|
8887
9302
|
const pos = this.pos.add(vec2(this.size.x,0));
|
|
8888
9303
|
uiSystem.drawText(this.text, pos, textSize,
|
|
8889
|
-
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false);
|
|
9304
|
+
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false, this.textShadow);
|
|
8890
9305
|
}
|
|
8891
9306
|
}
|
|
8892
9307
|
|
|
@@ -8941,9 +9356,11 @@ class UIScrollbar extends UIObject
|
|
|
8941
9356
|
const p1 = centerPos - handleWidth/2;
|
|
8942
9357
|
const p2 = centerPos + handleWidth/2;
|
|
8943
9358
|
const oldValue = this.value;
|
|
9359
|
+
|
|
9360
|
+
const p = uiSystem.screenToNative(mousePosScreen);
|
|
8944
9361
|
this.value = isHorizontal ?
|
|
8945
|
-
percent(
|
|
8946
|
-
percent(
|
|
9362
|
+
percent(p.x, p1, p2) :
|
|
9363
|
+
percent(p.y, p2, p1);
|
|
8947
9364
|
this.value === oldValue || this.onChange();
|
|
8948
9365
|
}
|
|
8949
9366
|
}
|
|
@@ -8965,20 +9382,20 @@ class UIScrollbar extends UIObject
|
|
|
8965
9382
|
vec2(lerp(p1, p2, this.value), this.pos.y) :
|
|
8966
9383
|
vec2(this.pos.x, lerp(p2, p1, this.value))
|
|
8967
9384
|
const handleColor = this.disabled ? this.disabledColor : this.handleColor;
|
|
8968
|
-
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
9385
|
+
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
8969
9386
|
|
|
8970
9387
|
// draw the text scaled to fit on the scrollbar
|
|
8971
9388
|
const textSize = this.getTextSize();
|
|
8972
9389
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
8973
|
-
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
9390
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
8974
9391
|
}
|
|
8975
9392
|
}
|
|
8976
9393
|
/**
|
|
8977
9394
|
* LittleJS Box2D Physics Plugin
|
|
8978
9395
|
* - Box2dObject extends EngineObject with Box2D physics
|
|
8979
|
-
* - Call box2dInit()
|
|
9396
|
+
* - Call box2dInit() to enable
|
|
8980
9397
|
* - You will also need to include box2d.wasm.js
|
|
8981
|
-
* - Uses a super fast web assembly port of Box2D
|
|
9398
|
+
* - Uses a super fast web assembly port of Box2D v2.3.1
|
|
8982
9399
|
* - More info: https://github.com/kripken/box2d.js
|
|
8983
9400
|
* - Functions to create polygon, circle, and edge shapes
|
|
8984
9401
|
* - Contact begin and end callbacks
|
|
@@ -9008,9 +9425,9 @@ function box2dSetDebug(enable) { box2dDebug = enable; }
|
|
|
9008
9425
|
///////////////////////////////////////////////////////////////////////////////
|
|
9009
9426
|
/**
|
|
9010
9427
|
* Box2D Object - extend with your own custom physics objects
|
|
9011
|
-
* - A LittleJS object with Box2D physics
|
|
9012
|
-
* - Each object has a Box2D body which can have multiple fixtures and joints
|
|
9428
|
+
* - A LittleJS object with Box2D physics, dynamic by default
|
|
9013
9429
|
* - Provides interface for Box2D body and fixture functions
|
|
9430
|
+
* - Each object can have multiple fixtures and joints
|
|
9014
9431
|
* @extends EngineObject
|
|
9015
9432
|
* @memberof Box2D
|
|
9016
9433
|
*/
|
|
@@ -9024,7 +9441,7 @@ class Box2dObject extends EngineObject
|
|
|
9024
9441
|
* @param {Color} [color]
|
|
9025
9442
|
* @param {number} [bodyType]
|
|
9026
9443
|
* @param {number} [renderOrder] */
|
|
9027
|
-
constructor(pos, size, tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
|
|
9444
|
+
constructor(pos=vec2(), size=vec2(), tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
|
|
9028
9445
|
{
|
|
9029
9446
|
super(pos, size, tileInfo, angle, color, renderOrder);
|
|
9030
9447
|
|
|
@@ -9036,24 +9453,27 @@ class Box2dObject extends EngineObject
|
|
|
9036
9453
|
this.body = box2d.world.CreateBody(bodyDef);
|
|
9037
9454
|
this.body.object = this;
|
|
9038
9455
|
this.lineColor = BLACK;
|
|
9456
|
+
box2d.objects.push(this);
|
|
9457
|
+
|
|
9458
|
+
// edge lists and loops for drawing
|
|
9459
|
+
this.edgeLists = [];
|
|
9460
|
+
this.edgeLoops = [];
|
|
9039
9461
|
}
|
|
9040
9462
|
|
|
9041
9463
|
/** Destroy this object and its physics body */
|
|
9042
9464
|
destroy()
|
|
9043
9465
|
{
|
|
9466
|
+
if (this.destroyed)
|
|
9467
|
+
return;
|
|
9468
|
+
|
|
9044
9469
|
// destroy physics body, fixtures, and joints
|
|
9045
|
-
this.body
|
|
9046
|
-
this.body
|
|
9470
|
+
ASSERT(this.body, 'Box2dObject has no body to destroy');
|
|
9471
|
+
box2d.world.DestroyBody(this.body);
|
|
9047
9472
|
super.destroy();
|
|
9048
9473
|
}
|
|
9049
9474
|
|
|
9050
|
-
/**
|
|
9051
|
-
|
|
9052
|
-
{
|
|
9053
|
-
// use box2d physics update instead of normal engine update
|
|
9054
|
-
this.pos = box2d.vec2From(this.body.GetPosition());
|
|
9055
|
-
this.angle = -this.body.GetAngle();
|
|
9056
|
-
}
|
|
9475
|
+
/** Box2d objects updated with Box2d world step */
|
|
9476
|
+
updatePhysics() {}
|
|
9057
9477
|
|
|
9058
9478
|
/** Render the object, uses box2d drawing if no tile info exists */
|
|
9059
9479
|
render()
|
|
@@ -9079,10 +9499,23 @@ class Box2dObject extends EngineObject
|
|
|
9079
9499
|
* @param {Color} [lineColor]
|
|
9080
9500
|
* @param {number} [lineWidth]
|
|
9081
9501
|
* @param {CanvasRenderingContext2D} [context] */
|
|
9082
|
-
drawFixtures(color=WHITE, lineColor, lineWidth=.1, context)
|
|
9502
|
+
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
9083
9503
|
{
|
|
9084
|
-
|
|
9085
|
-
|
|
9504
|
+
// draw non-edge fixtures
|
|
9505
|
+
this.getFixtureList().forEach((fixture)=>
|
|
9506
|
+
{
|
|
9507
|
+
const shape = box2d.castObjectType(fixture.GetShape());
|
|
9508
|
+
if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
|
|
9509
|
+
{
|
|
9510
|
+
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, context);
|
|
9511
|
+
}
|
|
9512
|
+
});
|
|
9513
|
+
|
|
9514
|
+
// draw edges using a single draw line for better connections
|
|
9515
|
+
this.edgeLists.forEach(points=>
|
|
9516
|
+
drawLineList(points, lineWidth, lineColor, false, this.pos, this.angle));
|
|
9517
|
+
this.edgeLoops.forEach(points=>
|
|
9518
|
+
drawLineList(points, lineWidth, lineColor, true, this.pos, this.angle));
|
|
9086
9519
|
}
|
|
9087
9520
|
|
|
9088
9521
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -9107,6 +9540,10 @@ class Box2dObject extends EngineObject
|
|
|
9107
9540
|
* @param {boolean} [isSensor] */
|
|
9108
9541
|
addShape(shape, density=1, friction=.2, restitution=0, isSensor=false)
|
|
9109
9542
|
{
|
|
9543
|
+
ASSERT(isNumber(density), 'density must be a number');
|
|
9544
|
+
ASSERT(isNumber(friction), 'friction must be a number');
|
|
9545
|
+
ASSERT(isNumber(restitution), 'restitution must be a number');
|
|
9546
|
+
|
|
9110
9547
|
const fd = new box2d.instance.b2FixtureDef();
|
|
9111
9548
|
fd.set_shape(shape);
|
|
9112
9549
|
fd.set_density(density);
|
|
@@ -9126,6 +9563,11 @@ class Box2dObject extends EngineObject
|
|
|
9126
9563
|
* @param {boolean} [isSensor] */
|
|
9127
9564
|
addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
|
|
9128
9565
|
{
|
|
9566
|
+
ASSERT(isVector2(size), 'size must be a Vector2');
|
|
9567
|
+
ASSERT(size.x > 0 && size.y > 0, 'size must be positive');
|
|
9568
|
+
ASSERT(isVector2(offset), 'offset must be a Vector2');
|
|
9569
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
9570
|
+
|
|
9129
9571
|
const shape = new box2d.instance.b2PolygonShape();
|
|
9130
9572
|
shape.SetAsBox(size.x/2, size.y/2, box2d.vec2dTo(offset), angle);
|
|
9131
9573
|
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
@@ -9139,6 +9581,8 @@ class Box2dObject extends EngineObject
|
|
|
9139
9581
|
* @param {boolean} [isSensor] */
|
|
9140
9582
|
addPoly(points, density, friction, restitution, isSensor)
|
|
9141
9583
|
{
|
|
9584
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
9585
|
+
|
|
9142
9586
|
function box2dCreatePolygonShape(points)
|
|
9143
9587
|
{
|
|
9144
9588
|
function box2dCreatePointList(points)
|
|
@@ -9174,6 +9618,9 @@ class Box2dObject extends EngineObject
|
|
|
9174
9618
|
* @param {boolean} [isSensor] */
|
|
9175
9619
|
addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
|
|
9176
9620
|
{
|
|
9621
|
+
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
|
|
9622
|
+
ASSERT(isNumber(sides) && sides>2, 'sides must be a positive number greater than 2');
|
|
9623
|
+
|
|
9177
9624
|
const points = [];
|
|
9178
9625
|
const radius = diameter/2;
|
|
9179
9626
|
for (let i=sides; i--;)
|
|
@@ -9189,6 +9636,8 @@ class Box2dObject extends EngineObject
|
|
|
9189
9636
|
* @param {boolean} [isSensor] */
|
|
9190
9637
|
addRandomPoly(diameter=1, density, friction, restitution, isSensor)
|
|
9191
9638
|
{
|
|
9639
|
+
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
|
|
9640
|
+
|
|
9192
9641
|
const sides = randInt(3, 9);
|
|
9193
9642
|
const points = [];
|
|
9194
9643
|
const radius = diameter/2;
|
|
@@ -9206,6 +9655,9 @@ class Box2dObject extends EngineObject
|
|
|
9206
9655
|
* @param {boolean} [isSensor] */
|
|
9207
9656
|
addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
|
|
9208
9657
|
{
|
|
9658
|
+
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
|
|
9659
|
+
ASSERT(isVector2(offset), 'offset must be a Vector2');
|
|
9660
|
+
|
|
9209
9661
|
const shape = new box2d.instance.b2CircleShape();
|
|
9210
9662
|
shape.set_m_p(box2d.vec2dTo(offset));
|
|
9211
9663
|
shape.set_m_radius(diameter/2);
|
|
@@ -9221,53 +9673,63 @@ class Box2dObject extends EngineObject
|
|
|
9221
9673
|
* @param {boolean} [isSensor] */
|
|
9222
9674
|
addEdge(point1, point2, density, friction, restitution, isSensor)
|
|
9223
9675
|
{
|
|
9676
|
+
ASSERT(isVector2(point1), 'point1 must be a Vector2');
|
|
9677
|
+
ASSERT(isVector2(point2), 'point2 must be a Vector2');
|
|
9678
|
+
|
|
9224
9679
|
const shape = new box2d.instance.b2EdgeShape();
|
|
9225
9680
|
shape.Set(box2d.vec2dTo(point1), box2d.vec2dTo(point2));
|
|
9226
9681
|
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
9227
9682
|
}
|
|
9228
9683
|
|
|
9229
|
-
/** Add an edge
|
|
9684
|
+
/** Add an edge list to the body
|
|
9230
9685
|
* @param {Array<Vector2>} points
|
|
9231
9686
|
* @param {number} [density]
|
|
9232
9687
|
* @param {number} [friction]
|
|
9233
9688
|
* @param {number} [restitution]
|
|
9234
9689
|
* @param {boolean} [isSensor] */
|
|
9235
|
-
|
|
9690
|
+
addEdgeList(points, density, friction, restitution, isSensor)
|
|
9236
9691
|
{
|
|
9237
|
-
|
|
9238
|
-
const
|
|
9239
|
-
for (let i=0; i<points.length; ++i)
|
|
9692
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
9693
|
+
const fixtures = [], edgePoints = [];
|
|
9694
|
+
for (let i=0; i<points.length-1; ++i)
|
|
9240
9695
|
{
|
|
9241
9696
|
const shape = new box2d.instance.b2EdgeShape();
|
|
9242
|
-
shape.set_m_vertex0(box2d.vec2dTo(
|
|
9243
|
-
shape.set_m_vertex1(box2d.vec2dTo(
|
|
9244
|
-
shape.set_m_vertex2(box2d.vec2dTo(
|
|
9245
|
-
shape.set_m_vertex3(box2d.vec2dTo(
|
|
9697
|
+
points[i-1] && shape.set_m_vertex0(box2d.vec2dTo(points[i-1]));
|
|
9698
|
+
points[i+0] && shape.set_m_vertex1(box2d.vec2dTo(points[i+0]));
|
|
9699
|
+
points[i+1] && shape.set_m_vertex2(box2d.vec2dTo(points[i+1]));
|
|
9700
|
+
points[i+2] && shape.set_m_vertex3(box2d.vec2dTo(points[i+2]));
|
|
9246
9701
|
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
9247
9702
|
fixtures.push(f);
|
|
9703
|
+
edgePoints.push(points[i].copy());
|
|
9248
9704
|
}
|
|
9705
|
+
edgePoints.push(points[points.length-1].copy());
|
|
9706
|
+
this.edgeLists.push(edgePoints);
|
|
9249
9707
|
return fixtures;
|
|
9250
9708
|
}
|
|
9251
9709
|
|
|
9252
|
-
/** Add an edge
|
|
9710
|
+
/** Add an edge loop to the body, an edge loop connects the end points
|
|
9253
9711
|
* @param {Array<Vector2>} points
|
|
9254
9712
|
* @param {number} [density]
|
|
9255
9713
|
* @param {number} [friction]
|
|
9256
9714
|
* @param {number} [restitution]
|
|
9257
9715
|
* @param {boolean} [isSensor] */
|
|
9258
|
-
|
|
9716
|
+
addEdgeLoop(points, density, friction, restitution, isSensor)
|
|
9259
9717
|
{
|
|
9260
|
-
|
|
9261
|
-
|
|
9718
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
9719
|
+
const fixtures = [], edgePoints = [];
|
|
9720
|
+
const getPoint = i=> points[mod(i,points.length)];
|
|
9721
|
+
for (let i=0; i<points.length; ++i)
|
|
9262
9722
|
{
|
|
9263
9723
|
const shape = new box2d.instance.b2EdgeShape();
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9724
|
+
shape.set_m_vertex0(box2d.vec2dTo(getPoint(i-1)));
|
|
9725
|
+
shape.set_m_vertex1(box2d.vec2dTo(getPoint(i+0)));
|
|
9726
|
+
shape.set_m_vertex2(box2d.vec2dTo(getPoint(i+1)));
|
|
9727
|
+
shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
|
|
9268
9728
|
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
9269
9729
|
fixtures.push(f);
|
|
9730
|
+
i < points.length && edgePoints.push(points[i].copy());
|
|
9270
9731
|
}
|
|
9732
|
+
this.edgeLoops.push(edgePoints);
|
|
9271
9733
|
return fixtures;
|
|
9272
9734
|
}
|
|
9273
9735
|
|
|
@@ -9302,6 +9764,10 @@ class Box2dObject extends EngineObject
|
|
|
9302
9764
|
* @return {number} */
|
|
9303
9765
|
getBodyType() { return this.body.GetType(); }
|
|
9304
9766
|
|
|
9767
|
+
/** Get the speed of this object
|
|
9768
|
+
* @return {number} */
|
|
9769
|
+
getSpeed() { return this.getLinearVelocity().length(); }
|
|
9770
|
+
|
|
9305
9771
|
///////////////////////////////////////////////////////////////////////////////
|
|
9306
9772
|
// physics set functions
|
|
9307
9773
|
|
|
@@ -9317,33 +9783,40 @@ class Box2dObject extends EngineObject
|
|
|
9317
9783
|
|
|
9318
9784
|
/** Sets the position
|
|
9319
9785
|
* @param {Vector2} pos */
|
|
9320
|
-
setPosition(pos)
|
|
9786
|
+
setPosition(pos)
|
|
9787
|
+
{ this.setTransform(pos, this.body.GetAngle()); }
|
|
9321
9788
|
|
|
9322
9789
|
/** Sets the angle
|
|
9323
9790
|
* @param {number} angle */
|
|
9324
|
-
setAngle(angle)
|
|
9791
|
+
setAngle(angle)
|
|
9792
|
+
{ this.setTransform(box2d.vec2From(this.body.GetPosition()), -angle); }
|
|
9325
9793
|
|
|
9326
9794
|
/** Sets the linear velocity
|
|
9327
9795
|
* @param {Vector2} velocity */
|
|
9328
|
-
setLinearVelocity(velocity)
|
|
9796
|
+
setLinearVelocity(velocity)
|
|
9797
|
+
{ this.body.SetLinearVelocity(box2d.vec2dTo(velocity)); }
|
|
9329
9798
|
|
|
9330
9799
|
/** Sets the angular velocity
|
|
9331
9800
|
* @param {number} angularVelocity */
|
|
9332
|
-
setAngularVelocity(angularVelocity)
|
|
9801
|
+
setAngularVelocity(angularVelocity)
|
|
9802
|
+
{ this.body.SetAngularVelocity(angularVelocity); }
|
|
9333
9803
|
|
|
9334
9804
|
/** Sets the linear damping
|
|
9335
9805
|
* @param {number} damping */
|
|
9336
|
-
setLinearDamping(damping)
|
|
9806
|
+
setLinearDamping(damping)
|
|
9807
|
+
{ this.body.SetLinearDamping(damping); }
|
|
9337
9808
|
|
|
9338
9809
|
/** Sets the angular damping
|
|
9339
9810
|
* @param {number} damping */
|
|
9340
|
-
setAngularDamping(damping)
|
|
9811
|
+
setAngularDamping(damping)
|
|
9812
|
+
{ this.body.SetAngularDamping(damping); }
|
|
9341
9813
|
|
|
9342
9814
|
/** Sets the gravity scale
|
|
9343
9815
|
* @param {number} [scale] */
|
|
9344
|
-
setGravityScale(scale=1)
|
|
9816
|
+
setGravityScale(scale=1)
|
|
9817
|
+
{ this.body.SetGravityScale(this.gravityScale = scale); }
|
|
9345
9818
|
|
|
9346
|
-
/** Should
|
|
9819
|
+
/** Should be like a bullet for continuous collision detection?
|
|
9347
9820
|
* @param {boolean} [isBullet] */
|
|
9348
9821
|
setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
|
|
9349
9822
|
|
|
@@ -9357,11 +9830,13 @@ class Box2dObject extends EngineObject
|
|
|
9357
9830
|
|
|
9358
9831
|
/** Set whether the body is allowed to sleep
|
|
9359
9832
|
* @param {boolean} [isAllowed] */
|
|
9360
|
-
setSleepingAllowed(isAllowed=true)
|
|
9833
|
+
setSleepingAllowed(isAllowed=true)
|
|
9834
|
+
{ this.body.SetSleepingAllowed(isAllowed); }
|
|
9361
9835
|
|
|
9362
9836
|
/** Set whether the body can rotate
|
|
9363
9837
|
* @param {boolean} [isFixed] */
|
|
9364
|
-
setFixedRotation(isFixed=true)
|
|
9838
|
+
setFixedRotation(isFixed=true)
|
|
9839
|
+
{ this.body.SetFixedRotation(isFixed); }
|
|
9365
9840
|
|
|
9366
9841
|
/** Set the center of mass of the body
|
|
9367
9842
|
* @param {Vector2} center */
|
|
@@ -9373,10 +9848,11 @@ class Box2dObject extends EngineObject
|
|
|
9373
9848
|
|
|
9374
9849
|
/** Set the moment of inertia of the body
|
|
9375
9850
|
* @param {number} momentOfInertia */
|
|
9376
|
-
setMomentOfInertia(momentOfInertia)
|
|
9851
|
+
setMomentOfInertia(momentOfInertia)
|
|
9852
|
+
{ this.setMassData(undefined, undefined, momentOfInertia) }
|
|
9377
9853
|
|
|
9378
9854
|
/** Reset the mass, center of mass, and moment */
|
|
9379
|
-
resetMassData()
|
|
9855
|
+
resetMassData() { this.body.ResetMassData(); }
|
|
9380
9856
|
|
|
9381
9857
|
/** Set the mass data of the body
|
|
9382
9858
|
* @param {Vector2} [localCenter]
|
|
@@ -10018,6 +10494,50 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
10018
10494
|
getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
|
|
10019
10495
|
}
|
|
10020
10496
|
|
|
10497
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10498
|
+
/**
|
|
10499
|
+
* Box2D Static Object - Box2d with a static physics body
|
|
10500
|
+
* @extends Box2dObject
|
|
10501
|
+
* @memberof Box2D
|
|
10502
|
+
*/
|
|
10503
|
+
class Box2dStaticObject extends Box2dObject
|
|
10504
|
+
{
|
|
10505
|
+
/** Create a LittleJS object with Box2d physics
|
|
10506
|
+
* @param {Vector2} [pos]
|
|
10507
|
+
* @param {Vector2} [size]
|
|
10508
|
+
* @param {TileInfo} [tileInfo]
|
|
10509
|
+
* @param {number} [angle]
|
|
10510
|
+
* @param {Color} [color]
|
|
10511
|
+
* @param {number} [renderOrder] */
|
|
10512
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
10513
|
+
{
|
|
10514
|
+
const bodyType = box2d.bodyTypeStatic;
|
|
10515
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
10516
|
+
}
|
|
10517
|
+
}
|
|
10518
|
+
|
|
10519
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10520
|
+
/**
|
|
10521
|
+
* Box2D Kiematic Object - Box2d with a kinematic physics body
|
|
10522
|
+
* @extends Box2dObject
|
|
10523
|
+
* @memberof Box2D
|
|
10524
|
+
*/
|
|
10525
|
+
class Box2dKiematicObject extends Box2dObject
|
|
10526
|
+
{
|
|
10527
|
+
/** Create a LittleJS object with Box2d physics
|
|
10528
|
+
* @param {Vector2} [pos]
|
|
10529
|
+
* @param {Vector2} [size]
|
|
10530
|
+
* @param {TileInfo} [tileInfo]
|
|
10531
|
+
* @param {number} [angle]
|
|
10532
|
+
* @param {Color} [color]
|
|
10533
|
+
* @param {number} [renderOrder] */
|
|
10534
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
10535
|
+
{
|
|
10536
|
+
const bodyType = box2d.bodyTypeKinematic;
|
|
10537
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
10538
|
+
}
|
|
10539
|
+
}
|
|
10540
|
+
|
|
10021
10541
|
///////////////////////////////////////////////////////////////////////////////
|
|
10022
10542
|
/**
|
|
10023
10543
|
* Box2D Wheel Joint
|
|
@@ -10380,6 +10900,7 @@ class Box2dPlugin
|
|
|
10380
10900
|
box2d = this;
|
|
10381
10901
|
this.instance = instance;
|
|
10382
10902
|
this.world = new box2d.instance.b2World();
|
|
10903
|
+
this.objects = [];
|
|
10383
10904
|
|
|
10384
10905
|
/** @property {number} - Velocity iterations per update*/
|
|
10385
10906
|
this.velocityIterations = 8;
|
|
@@ -10622,9 +11143,10 @@ class Box2dPlugin
|
|
|
10622
11143
|
|
|
10623
11144
|
/** converts a box2d vec2 pointer to a Vector2
|
|
10624
11145
|
* @param {Object} v */
|
|
10625
|
-
vec2FromPointer(
|
|
11146
|
+
vec2FromPointer(vp)
|
|
10626
11147
|
{
|
|
10627
|
-
|
|
11148
|
+
const v = box2d.instance.wrapPointer(vp, box2d.instance.b2Vec2);
|
|
11149
|
+
return box2d.vec2From(v);
|
|
10628
11150
|
}
|
|
10629
11151
|
|
|
10630
11152
|
/** converts a Vector2 to a box2 vec2
|
|
@@ -10698,8 +11220,23 @@ async function box2dInit()
|
|
|
10698
11220
|
// add the box2d plugin to the engine
|
|
10699
11221
|
function box2dUpdate()
|
|
10700
11222
|
{
|
|
10701
|
-
if (
|
|
10702
|
-
|
|
11223
|
+
if (paused)
|
|
11224
|
+
return;
|
|
11225
|
+
|
|
11226
|
+
box2d.step();
|
|
11227
|
+
|
|
11228
|
+
// remove destroyed objects
|
|
11229
|
+
box2d.objects = box2d.objects.filter(o=>!o.destroyed);
|
|
11230
|
+
|
|
11231
|
+
// copy box2d physics results to engine objects
|
|
11232
|
+
for (const o of box2d.objects)
|
|
11233
|
+
{
|
|
11234
|
+
if (o.body)
|
|
11235
|
+
{
|
|
11236
|
+
o.pos = box2d.vec2From(o.body.GetPosition());
|
|
11237
|
+
o.angle = -o.body.GetAngle();
|
|
11238
|
+
}
|
|
11239
|
+
}
|
|
10703
11240
|
}
|
|
10704
11241
|
function box2dRender()
|
|
10705
11242
|
{
|
|
@@ -10988,6 +11525,7 @@ export
|
|
|
10988
11525
|
gamepadDirectionEmulateStick,
|
|
10989
11526
|
inputWASDEmulateDirection,
|
|
10990
11527
|
touchGamepadEnable,
|
|
11528
|
+
touchGamepadCenterButton,
|
|
10991
11529
|
touchGamepadAnalog,
|
|
10992
11530
|
touchGamepadSize,
|
|
10993
11531
|
touchGamepadAlpha,
|
|
@@ -11031,6 +11569,7 @@ export
|
|
|
11031
11569
|
setGamepadDirectionEmulateStick,
|
|
11032
11570
|
setInputWASDEmulateDirection,
|
|
11033
11571
|
setTouchGamepadEnable,
|
|
11572
|
+
setTouchGamepadCenterButton,
|
|
11034
11573
|
setTouchGamepadAnalog,
|
|
11035
11574
|
setTouchGamepadSize,
|
|
11036
11575
|
setTouchGamepadAlpha,
|
|
@@ -11049,9 +11588,18 @@ export
|
|
|
11049
11588
|
// Utilities
|
|
11050
11589
|
PI,
|
|
11051
11590
|
abs,
|
|
11591
|
+
floor,
|
|
11592
|
+
ceil,
|
|
11593
|
+
round,
|
|
11052
11594
|
min,
|
|
11053
11595
|
max,
|
|
11054
11596
|
sign,
|
|
11597
|
+
hypot,
|
|
11598
|
+
log2,
|
|
11599
|
+
sin,
|
|
11600
|
+
cos,
|
|
11601
|
+
tan,
|
|
11602
|
+
atan2,
|
|
11055
11603
|
mod,
|
|
11056
11604
|
clamp,
|
|
11057
11605
|
percent,
|
|
@@ -11071,6 +11619,7 @@ export
|
|
|
11071
11619
|
// Random
|
|
11072
11620
|
rand,
|
|
11073
11621
|
randInt,
|
|
11622
|
+
randBool,
|
|
11074
11623
|
randSign,
|
|
11075
11624
|
randInCircle,
|
|
11076
11625
|
randVec2,
|
|
@@ -11088,6 +11637,7 @@ export
|
|
|
11088
11637
|
isVector2,
|
|
11089
11638
|
isNumber,
|
|
11090
11639
|
isString,
|
|
11640
|
+
isArray,
|
|
11091
11641
|
|
|
11092
11642
|
// Default Colors
|
|
11093
11643
|
WHITE,
|
|
@@ -11119,6 +11669,8 @@ export
|
|
|
11119
11669
|
drawCount,
|
|
11120
11670
|
screenToWorld,
|
|
11121
11671
|
worldToScreen,
|
|
11672
|
+
screenToWorldDelta,
|
|
11673
|
+
worldToScreenDelta,
|
|
11122
11674
|
drawTile,
|
|
11123
11675
|
drawRect,
|
|
11124
11676
|
drawRectGradient,
|
|
@@ -11186,6 +11738,7 @@ export
|
|
|
11186
11738
|
mouseDelta,
|
|
11187
11739
|
mouseDeltaScreen,
|
|
11188
11740
|
mouseWheel,
|
|
11741
|
+
mouseInWindow,
|
|
11189
11742
|
isUsingGamepad,
|
|
11190
11743
|
inputPreventDefault,
|
|
11191
11744
|
setInputPreventDefault,
|
|
@@ -11274,6 +11827,8 @@ export
|
|
|
11274
11827
|
box2dInit,
|
|
11275
11828
|
Box2dPlugin,
|
|
11276
11829
|
Box2dObject,
|
|
11830
|
+
Box2dStaticObject,
|
|
11831
|
+
Box2dKiematicObject,
|
|
11277
11832
|
Box2dRaycastResult,
|
|
11278
11833
|
Box2dJoint,
|
|
11279
11834
|
Box2dTargetJoint,
|