littlejsengine 1.11.17 → 1.12.6
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/box2d.wasm.js +630 -0
- package/dist/box2d.wasm.wasm +0 -0
- package/dist/littlejs.d.ts +158 -81
- package/dist/littlejs.esm.js +882 -793
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +3048 -175
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +3047 -172
- package/examples/box2d/game.js +82 -56
- package/examples/box2d/gameObjects.js +114 -107
- package/examples/box2d/index.html +2 -7
- package/examples/box2d/scenes.js +72 -78
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +63 -28
- package/examples/breakout/gameObjects.js +45 -47
- package/examples/breakout/index.html +1 -5
- package/examples/breakoutTutorial/game.js +36 -29
- package/examples/breakoutTutorial/index.html +1 -3
- package/examples/empty/game.js +5 -2
- package/examples/empty/index.html +1 -3
- package/examples/htmlMenu/game.js +24 -15
- package/examples/htmlMenu/index.html +5 -7
- package/examples/index.html +24 -17
- package/examples/module/game.js +32 -34
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +27 -22
- package/examples/platformer/game.js +62 -45
- package/examples/platformer/gameCharacter.js +42 -34
- package/examples/platformer/gameEffects.js +82 -75
- package/examples/platformer/gameLevel.js +67 -38
- package/examples/platformer/{gameLevelData.js → gameLevelData.json} +1 -11
- package/examples/platformer/gameObjects.js +70 -64
- package/examples/platformer/gamePlayer.js +12 -7
- package/examples/platformer/index.html +1 -9
- package/examples/puzzle/game.js +59 -40
- package/examples/puzzle/index.html +1 -3
- package/examples/shorts/base.html +3 -2
- package/examples/shorts/box2d.js +46 -0
- package/examples/shorts/box2dCar.js +49 -0
- package/examples/shorts/particles.js +1 -1
- package/examples/shorts/platformer.js +1 -1
- package/examples/shorts/postProcess.js +45 -0
- package/examples/shorts/tileLayer.js +5 -6
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/uiSystem.js +39 -0
- package/examples/starter/game.js +9 -10
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +44 -44
- package/examples/typescript/build.js +17 -18
- package/examples/typescript/game.js +32 -34
- package/examples/typescript/game.ts +32 -34
- package/examples/typescript/index.html +1 -1
- package/examples/uiSystem/game.js +34 -31
- package/examples/uiSystem/index.html +1 -5
- package/package.json +1 -1
- package/plugins/box2d.js +28 -30
- package/plugins/pluginExport.js +2 -3
- package/plugins/uiSystem.js +9 -3
- package/reference.md +29 -27
- package/src/engine.js +10 -11
- package/src/engineAudio.js +24 -15
- package/src/engineBuild.js +25 -8
- package/src/engineDebug.js +1 -3
- package/src/engineExport.js +3 -6
- package/src/engineInput.js +6 -4
- package/src/engineObject.js +18 -14
- package/src/engineSettings.js +6 -6
- package/src/engineTileLayer.js +179 -96
- package/src/engineUtilities.js +16 -0
- package/src/engineWebGL.js +13 -1
- package/examples/typescript/build/dist/littlejs.esm.js +0 -4834
- package/examples/typescript/build/examples/typescript/build.js +0 -24
- package/examples/typescript/build/examples/typescript/game.js +0 -102
package/dist/littlejs.release.js
CHANGED
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
* - Debug functionality is disabled to reduce size and increase performance
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
12
|
let showWatermark = 0;
|
|
15
13
|
let debugKey = '';
|
|
16
14
|
const debug = 0;
|
|
@@ -53,8 +51,6 @@ function debugVideoCaptureUpdate(){}
|
|
|
53
51
|
* @namespace Utilities
|
|
54
52
|
*/
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
54
|
/** A shortcut to get Math.PI
|
|
59
55
|
* @type {number}
|
|
60
56
|
* @default Math.PI
|
|
@@ -234,6 +230,16 @@ function wave(frequency=1, amplitude=1, t=time)
|
|
|
234
230
|
* @memberof Utilities */
|
|
235
231
|
function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
|
|
236
232
|
|
|
233
|
+
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
234
|
+
* @param {string} url - URL of JSON file
|
|
235
|
+
* @return {Promise<object>}
|
|
236
|
+
* @memberof Utilities */
|
|
237
|
+
async function fetchJSON(url)
|
|
238
|
+
{
|
|
239
|
+
const response = await fetch(url);
|
|
240
|
+
return response.json();
|
|
241
|
+
}
|
|
242
|
+
|
|
237
243
|
///////////////////////////////////////////////////////////////////////////////
|
|
238
244
|
|
|
239
245
|
/** Random global functions
|
|
@@ -329,6 +335,12 @@ class RandomGenerator
|
|
|
329
335
|
/** Randomly returns either -1 or 1 deterministically
|
|
330
336
|
* @return {number} */
|
|
331
337
|
sign() { return this.float() > .5 ? 1 : -1; }
|
|
338
|
+
|
|
339
|
+
/** Returns a seeded random value between the two values passed in with a random sign
|
|
340
|
+
* @param {number} [valueA]
|
|
341
|
+
* @param {number} [valueB]
|
|
342
|
+
* @return {number} */
|
|
343
|
+
floatSign(valueA=1, valueB=0) { return this.float(valueA, valueB) * this.sign(); }
|
|
332
344
|
}
|
|
333
345
|
|
|
334
346
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -969,8 +981,6 @@ class Timer
|
|
|
969
981
|
* @namespace Settings
|
|
970
982
|
*/
|
|
971
983
|
|
|
972
|
-
|
|
973
|
-
|
|
974
984
|
///////////////////////////////////////////////////////////////////////////////
|
|
975
985
|
// Camera settings
|
|
976
986
|
|
|
@@ -1002,7 +1012,7 @@ let canvasMaxSize = vec2(1920, 1080);
|
|
|
1002
1012
|
* @memberof Settings */
|
|
1003
1013
|
let canvasFixedSize = vec2();
|
|
1004
1014
|
|
|
1005
|
-
/** Use nearest neighbor scaling
|
|
1015
|
+
/** Use nearest neighbor canvas scaling for more pixelated look
|
|
1006
1016
|
* - Must be set before startup to take effect
|
|
1007
1017
|
* - If enabled sets css image-rendering:pixelated
|
|
1008
1018
|
* @type {boolean}
|
|
@@ -1112,11 +1122,11 @@ let objectDefaultFriction = .8;
|
|
|
1112
1122
|
* @memberof Settings */
|
|
1113
1123
|
let objectMaxSpeed = 1;
|
|
1114
1124
|
|
|
1115
|
-
/** How much gravity to apply to objects
|
|
1116
|
-
* @type {
|
|
1125
|
+
/** How much gravity to apply to objects, negative Y is down
|
|
1126
|
+
* @type {Vector2}
|
|
1117
1127
|
* @default
|
|
1118
1128
|
* @memberof Settings */
|
|
1119
|
-
let gravity =
|
|
1129
|
+
let gravity = vec2();
|
|
1120
1130
|
|
|
1121
1131
|
/** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters)
|
|
1122
1132
|
* @type {number}
|
|
@@ -1342,8 +1352,8 @@ function setObjectDefaultFriction(friction) { objectDefaultFriction = friction;
|
|
|
1342
1352
|
* @memberof Settings */
|
|
1343
1353
|
function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
|
|
1344
1354
|
|
|
1345
|
-
/** Set how much gravity to apply to objects
|
|
1346
|
-
* @param {
|
|
1355
|
+
/** Set how much gravity to apply to objects
|
|
1356
|
+
* @param {Vector2} newGravity
|
|
1347
1357
|
* @memberof Settings */
|
|
1348
1358
|
function setGravity(newGravity) { gravity = newGravity; }
|
|
1349
1359
|
|
|
@@ -1455,8 +1465,6 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
1455
1465
|
* LittleJS Object System
|
|
1456
1466
|
*/
|
|
1457
1467
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
1468
|
/**
|
|
1461
1469
|
* LittleJS Object Base Object Class
|
|
1462
1470
|
* - Top level object class used by the engine
|
|
@@ -1611,7 +1619,10 @@ class EngineObject
|
|
|
1611
1619
|
this.velocity.x *= this.damping;
|
|
1612
1620
|
this.velocity.y *= this.damping;
|
|
1613
1621
|
if (this.mass) // don't apply gravity to static objects
|
|
1614
|
-
|
|
1622
|
+
{
|
|
1623
|
+
this.velocity.x += gravity.x * this.gravityScale;
|
|
1624
|
+
this.velocity.y += gravity.y * this.gravityScale;
|
|
1625
|
+
}
|
|
1615
1626
|
this.pos.x += this.velocity.x;
|
|
1616
1627
|
this.pos.y += this.velocity.y;
|
|
1617
1628
|
this.angle += this.angleVelocity *= this.angleDamping;
|
|
@@ -1626,9 +1637,9 @@ class EngineObject
|
|
|
1626
1637
|
if (this.groundObject)
|
|
1627
1638
|
{
|
|
1628
1639
|
// apply friction in local space of ground object
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1631
|
-
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) *
|
|
1640
|
+
const friction = max(this.friction, this.groundObject.friction);
|
|
1641
|
+
const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
|
|
1642
|
+
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
|
|
1632
1643
|
this.groundObject = undefined;
|
|
1633
1644
|
//debugOverlay && debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
|
|
1634
1645
|
}
|
|
@@ -1670,7 +1681,7 @@ class EngineObject
|
|
|
1670
1681
|
|
|
1671
1682
|
// check for collision
|
|
1672
1683
|
const sizeBoth = this.size.add(o.size);
|
|
1673
|
-
const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
|
|
1684
|
+
const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity.y; // prefer to push up if small delta
|
|
1674
1685
|
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
1675
1686
|
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
1676
1687
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
@@ -1732,19 +1743,21 @@ class EngineObject
|
|
|
1732
1743
|
if (this.collideTiles)
|
|
1733
1744
|
{
|
|
1734
1745
|
// check collision against tiles
|
|
1735
|
-
|
|
1746
|
+
const hitLayer = tileCollisionTest(this.pos, this.size, this)
|
|
1747
|
+
if (hitLayer)
|
|
1736
1748
|
{
|
|
1737
1749
|
// if already was stuck in collision, don't do anything
|
|
1738
1750
|
// this should not happen unless something starts in collision
|
|
1739
1751
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
1740
1752
|
{
|
|
1741
1753
|
// test which side we bounced off (or both if a corner)
|
|
1742
|
-
const
|
|
1743
|
-
const
|
|
1744
|
-
if (
|
|
1754
|
+
const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
1755
|
+
const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
1756
|
+
if (blockedLayerY || !blockedLayerX)
|
|
1745
1757
|
{
|
|
1746
1758
|
// bounce velocity
|
|
1747
|
-
|
|
1759
|
+
const elasticity = max(this.elasticity, hitLayer.elasticity);
|
|
1760
|
+
this.velocity.y *= -elasticity;
|
|
1748
1761
|
|
|
1749
1762
|
if (wasMovingDown)
|
|
1750
1763
|
{
|
|
@@ -1753,9 +1766,8 @@ class EngineObject
|
|
|
1753
1766
|
const epsilon = .0001;
|
|
1754
1767
|
this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
|
|
1755
1768
|
|
|
1756
|
-
// set ground object
|
|
1757
|
-
|
|
1758
|
-
this.groundObject = this;
|
|
1769
|
+
// set ground object for tile collision
|
|
1770
|
+
this.groundObject = hitLayer;
|
|
1759
1771
|
}
|
|
1760
1772
|
else
|
|
1761
1773
|
{
|
|
@@ -1764,7 +1776,7 @@ class EngineObject
|
|
|
1764
1776
|
this.groundObject = undefined;
|
|
1765
1777
|
}
|
|
1766
1778
|
}
|
|
1767
|
-
if (
|
|
1779
|
+
if (blockedLayerX)
|
|
1768
1780
|
{
|
|
1769
1781
|
// move to previous position and bounce
|
|
1770
1782
|
this.pos.x = oldPos.x;
|
|
@@ -1935,8 +1947,6 @@ class EngineObject
|
|
|
1935
1947
|
* @namespace Draw
|
|
1936
1948
|
*/
|
|
1937
1949
|
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
1950
|
/** The primary 2D canvas visible to the user
|
|
1941
1951
|
* @type {HTMLCanvasElement}
|
|
1942
1952
|
* @memberof Draw */
|
|
@@ -2558,8 +2568,6 @@ function setCursor(cursorStyle = 'auto')
|
|
|
2558
2568
|
* @namespace Input
|
|
2559
2569
|
*/
|
|
2560
2570
|
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
2571
|
/** Returns true if device key is down
|
|
2564
2572
|
* @param {string|number} key
|
|
2565
2573
|
* @param {number} [device]
|
|
@@ -2611,21 +2619,21 @@ function clearInput() { inputData = [[]]; touchGamepadButtons = []; }
|
|
|
2611
2619
|
* @param {number} button
|
|
2612
2620
|
* @return {boolean}
|
|
2613
2621
|
* @memberof Input */
|
|
2614
|
-
|
|
2622
|
+
function mouseIsDown(button) { return keyIsDown(button); }
|
|
2615
2623
|
|
|
2616
2624
|
/** Returns true if mouse button was pressed
|
|
2617
2625
|
* @function
|
|
2618
2626
|
* @param {number} button
|
|
2619
2627
|
* @return {boolean}
|
|
2620
2628
|
* @memberof Input */
|
|
2621
|
-
|
|
2629
|
+
function mouseWasPressed(button) { return keyWasPressed(button); }
|
|
2622
2630
|
|
|
2623
2631
|
/** Returns true if mouse button was released
|
|
2624
2632
|
* @function
|
|
2625
2633
|
* @param {number} button
|
|
2626
2634
|
* @return {boolean}
|
|
2627
2635
|
* @memberof Input */
|
|
2628
|
-
|
|
2636
|
+
function mouseWasReleased(button) { return keyWasReleased(button); }
|
|
2629
2637
|
|
|
2630
2638
|
/** Mouse pos in world space
|
|
2631
2639
|
* @type {Vector2}
|
|
@@ -2864,7 +2872,9 @@ function gamepadsUpdate()
|
|
|
2864
2872
|
const button = gamepad.buttons[j];
|
|
2865
2873
|
const wasDown = gamepadIsDown(j,i);
|
|
2866
2874
|
data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
2867
|
-
|
|
2875
|
+
if (!button.value || button.value > .9) // must be a full press
|
|
2876
|
+
if (!i && button.pressed)
|
|
2877
|
+
isUsingGamepad = true;
|
|
2868
2878
|
}
|
|
2869
2879
|
|
|
2870
2880
|
if (gamepadDirectionEmulateStick)
|
|
@@ -3082,8 +3092,6 @@ function touchGamepadRender()
|
|
|
3082
3092
|
* @namespace Audio
|
|
3083
3093
|
*/
|
|
3084
3094
|
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
3095
|
/** Audio context used by the engine
|
|
3088
3096
|
* @type {AudioContext}
|
|
3089
3097
|
* @memberof Audio */
|
|
@@ -3120,17 +3128,17 @@ class Sound
|
|
|
3120
3128
|
{
|
|
3121
3129
|
/** Create a sound object and cache the zzfx samples for later use
|
|
3122
3130
|
* @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
|
|
3123
|
-
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
3131
|
+
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
3124
3132
|
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
3125
3133
|
*/
|
|
3126
3134
|
constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
|
|
3127
3135
|
{
|
|
3128
3136
|
if (!soundEnable || headlessMode) return;
|
|
3129
3137
|
|
|
3130
|
-
/** @property {number} - World space max range of sound
|
|
3138
|
+
/** @property {number} - World space max range of sound */
|
|
3131
3139
|
this.range = range;
|
|
3132
3140
|
|
|
3133
|
-
/** @property {number} - At what percentage of range should it start tapering
|
|
3141
|
+
/** @property {number} - At what percentage of range should it start tapering */
|
|
3134
3142
|
this.taper = taper;
|
|
3135
3143
|
|
|
3136
3144
|
/** @property {number} - How much to randomize frequency each time sound plays */
|
|
@@ -3246,8 +3254,8 @@ class SoundWave extends Sound
|
|
|
3246
3254
|
/** Create a sound object and cache the wave file for later use
|
|
3247
3255
|
* @param {string} filename - Filename of audio file to load
|
|
3248
3256
|
* @param {number} [randomness] - How much to randomize frequency each time sound plays
|
|
3249
|
-
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
3250
|
-
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
3257
|
+
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
3258
|
+
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
3251
3259
|
* @param {Function} [onloadCallback] - callback function to call when sound is loaded
|
|
3252
3260
|
*/
|
|
3253
3261
|
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
@@ -3255,17 +3263,26 @@ class SoundWave extends Sound
|
|
|
3255
3263
|
super(undefined, range, taper);
|
|
3256
3264
|
if (!soundEnable || headlessMode) return;
|
|
3257
3265
|
|
|
3266
|
+
/** @property {Function} - callback function to call when sound is loaded */
|
|
3267
|
+
this.onloadCallback = onloadCallback;
|
|
3258
3268
|
this.randomness = randomness;
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
+
this.loadSound(filename);
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
/** Loads a sound from a URL and decodes it into sample data. Must be used with await!
|
|
3273
|
+
* @param {string} filename
|
|
3274
|
+
* @return {Promise<void>} */
|
|
3275
|
+
async loadSound(filename)
|
|
3276
|
+
{
|
|
3277
|
+
const response = await fetch(filename);
|
|
3278
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
3279
|
+
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
3280
|
+
this.sampleChannels = [];
|
|
3281
|
+
for (let i = audioBuffer.numberOfChannels; i--;)
|
|
3282
|
+
this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
|
|
3283
|
+
this.sampleRate = audioBuffer.sampleRate;
|
|
3284
|
+
if (this.onloadCallback)
|
|
3285
|
+
this.onloadCallback();
|
|
3269
3286
|
}
|
|
3270
3287
|
}
|
|
3271
3288
|
|
|
@@ -3539,75 +3556,45 @@ function zzfxG
|
|
|
3539
3556
|
* LittleJS Tile Layer System
|
|
3540
3557
|
* - Caches arrays of tiles to off screen canvas for fast rendering
|
|
3541
3558
|
* - Unlimited numbers of layers, allocates canvases as needed
|
|
3542
|
-
* - Interfaces with EngineObject for collision
|
|
3543
|
-
* - Collision layer is separate from visible layers
|
|
3544
|
-
* - It is recommended to have a visible layer that matches the collision
|
|
3545
3559
|
* - Tile layers can be drawn to using their context with canvas2d
|
|
3546
3560
|
* - Drawn directly to the main canvas without using WebGL
|
|
3561
|
+
* - Tile layers can also have collision with EngineObjects
|
|
3547
3562
|
* @namespace TileCollision
|
|
3548
3563
|
*/
|
|
3549
3564
|
|
|
3565
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3566
|
+
// Tile Layer System
|
|
3550
3567
|
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
* @type {Array<number>}
|
|
3554
|
-
* @memberof TileCollision */
|
|
3555
|
-
let tileCollision = [];
|
|
3556
|
-
|
|
3557
|
-
/** Size of the tile collision layer 2d grid
|
|
3558
|
-
* @type {Vector2}
|
|
3559
|
-
* @memberof TileCollision */
|
|
3560
|
-
let tileCollisionSize = vec2();
|
|
3561
|
-
|
|
3562
|
-
/** Clear and initialize tile collision
|
|
3563
|
-
* @param {Vector2} size - width and height of tile collision 2d grid
|
|
3564
|
-
* @memberof TileCollision */
|
|
3565
|
-
function initTileCollision(size)
|
|
3566
|
-
{
|
|
3567
|
-
tileCollisionSize = size;
|
|
3568
|
-
tileCollision = [];
|
|
3569
|
-
for (let i=tileCollision.length = tileCollisionSize.area(); i--;)
|
|
3570
|
-
tileCollision[i] = 0;
|
|
3571
|
-
}
|
|
3572
|
-
|
|
3573
|
-
/** Set tile collision data for a given cell in the grid
|
|
3574
|
-
* @param {Vector2} pos
|
|
3575
|
-
* @param {number} [data]
|
|
3568
|
+
/** Keep track of all tile layers with collision
|
|
3569
|
+
* @type {Array<TileCollisionLayer>}
|
|
3576
3570
|
* @memberof TileCollision */
|
|
3577
|
-
|
|
3578
|
-
{
|
|
3579
|
-
pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
|
|
3580
|
-
}
|
|
3571
|
+
let tileCollisionLayers = [];
|
|
3581
3572
|
|
|
3582
3573
|
/** Get tile collision data for a given cell in the grid
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3574
|
+
* @param {Vector2} pos
|
|
3575
|
+
* @return {number}
|
|
3576
|
+
* @memberof TileCollision */
|
|
3586
3577
|
function getTileCollisionData(pos)
|
|
3587
3578
|
{
|
|
3588
|
-
|
|
3579
|
+
// check all tile collision layers
|
|
3580
|
+
for (const layer of tileCollisionLayers)
|
|
3581
|
+
if (pos.arrayCheck(layer.size))
|
|
3582
|
+
return layer.getCollisionData(pos);
|
|
3583
|
+
return 0;
|
|
3589
3584
|
}
|
|
3590
3585
|
|
|
3591
|
-
/** Check if
|
|
3586
|
+
/** Check if a tile layer collides with another object
|
|
3592
3587
|
* @param {Vector2} pos
|
|
3593
3588
|
* @param {Vector2} [size=(0,0)]
|
|
3594
3589
|
* @param {EngineObject} [object]
|
|
3595
|
-
* @return {
|
|
3590
|
+
* @return {TileCollisionLayer}
|
|
3596
3591
|
* @memberof TileCollision */
|
|
3597
3592
|
function tileCollisionTest(pos, size=vec2(), object)
|
|
3598
3593
|
{
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
for (let y = minY; y < maxY; ++y)
|
|
3604
|
-
for (let x = minX; x < maxX; ++x)
|
|
3605
|
-
{
|
|
3606
|
-
const tileData = tileCollision[y*tileCollisionSize.x+x];
|
|
3607
|
-
if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
|
|
3608
|
-
return true;
|
|
3609
|
-
}
|
|
3610
|
-
return false;
|
|
3594
|
+
// check all tile collision layers
|
|
3595
|
+
for (const layer of tileCollisionLayers)
|
|
3596
|
+
if (layer.collisionTest(pos, size, object))
|
|
3597
|
+
return layer;
|
|
3611
3598
|
}
|
|
3612
3599
|
|
|
3613
3600
|
/** Return the center of first tile hit, undefined if nothing was hit.
|
|
@@ -3619,49 +3606,17 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
3619
3606
|
* @memberof TileCollision */
|
|
3620
3607
|
function tileCollisionRaycast(posStart, posEnd, object)
|
|
3621
3608
|
{
|
|
3622
|
-
//
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
const flooredPosStart = posStart.floor();
|
|
3629
|
-
|
|
3630
|
-
// setup iteration variables
|
|
3631
|
-
let pos = flooredPosStart;
|
|
3632
|
-
let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
|
|
3633
|
-
let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
|
|
3634
|
-
|
|
3635
|
-
while (true)
|
|
3636
|
-
{
|
|
3637
|
-
// check for tile collision
|
|
3638
|
-
const tileData = getTileCollisionData(pos);
|
|
3639
|
-
if (tileData && (!object || object.collideWithTile(tileData, pos)))
|
|
3640
|
-
{
|
|
3641
|
-
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
3642
|
-
debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
|
|
3643
|
-
return pos.add(vec2(.5));
|
|
3644
|
-
}
|
|
3645
|
-
|
|
3646
|
-
// check if past the end
|
|
3647
|
-
if (xi > totalLength && yi > totalLength)
|
|
3648
|
-
break;
|
|
3649
|
-
|
|
3650
|
-
// get coordinates of the next tile to check
|
|
3651
|
-
if (xi > yi)
|
|
3652
|
-
pos.y += sign(delta.y), yi += unit.y;
|
|
3653
|
-
else
|
|
3654
|
-
pos.x += sign(delta.x), xi += unit.x;
|
|
3609
|
+
// check all tile collision layers
|
|
3610
|
+
for (const layer of tileCollisionLayers)
|
|
3611
|
+
{
|
|
3612
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, object)
|
|
3613
|
+
if (hitPos)
|
|
3614
|
+
return hitPos;
|
|
3655
3615
|
}
|
|
3656
|
-
|
|
3657
|
-
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
3658
3616
|
}
|
|
3659
3617
|
|
|
3660
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
3661
|
-
// Tile Layer Rendering System
|
|
3662
|
-
|
|
3663
3618
|
/**
|
|
3664
|
-
* Tile layer data object stores info about how to
|
|
3619
|
+
* Tile layer data object stores info about how to draw a tile
|
|
3665
3620
|
* @example
|
|
3666
3621
|
* // create tile layer data with tile index 0 and random orientation and color
|
|
3667
3622
|
* const tileIndex = 0;
|
|
@@ -3693,28 +3648,27 @@ class TileLayerData
|
|
|
3693
3648
|
clear() { this.tile = this.direction = 0; this.mirror = false; this.color = new Color; }
|
|
3694
3649
|
}
|
|
3695
3650
|
|
|
3651
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3696
3652
|
/**
|
|
3697
3653
|
* Tile Layer - cached rendering system for tile layers
|
|
3698
3654
|
* - Each Tile layer is rendered to an off screen canvas
|
|
3699
3655
|
* - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
3700
3656
|
* - Some devices like mobile phones are limited to 4k texture resolution
|
|
3701
|
-
* -
|
|
3657
|
+
* - For with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
3702
3658
|
* @extends EngineObject
|
|
3703
3659
|
* @example
|
|
3704
|
-
*
|
|
3705
|
-
* initTileCollision(vec2(200,100));
|
|
3706
|
-
* const tileLayer = new TileLayer();
|
|
3660
|
+
* const tileLayer = new TileLayer(vec2(), vec2(200,100));
|
|
3707
3661
|
*/
|
|
3708
3662
|
class TileLayer extends EngineObject
|
|
3709
3663
|
{
|
|
3710
3664
|
/** Create a tile layer object
|
|
3711
|
-
* @param {Vector2} [position=(0,0)]
|
|
3712
|
-
* @param {Vector2} [size=
|
|
3713
|
-
* @param {TileInfo} [tileInfo]
|
|
3714
|
-
* @param {Vector2} [scale=(1,1)]
|
|
3715
|
-
* @param {number} [renderOrder]
|
|
3665
|
+
* @param {Vector2} [position=(0,0)] - World space position
|
|
3666
|
+
* @param {Vector2} [size=(1,1)] - World space size
|
|
3667
|
+
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
3668
|
+
* @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
|
|
3669
|
+
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
3716
3670
|
*/
|
|
3717
|
-
constructor(position, size
|
|
3671
|
+
constructor(position, size, tileInfo=tile(), scale=vec2(1), renderOrder=0)
|
|
3718
3672
|
{
|
|
3719
3673
|
super(position, size, tileInfo, 0, undefined, renderOrder);
|
|
3720
3674
|
|
|
@@ -3726,6 +3680,10 @@ class TileLayer extends EngineObject
|
|
|
3726
3680
|
this.scale = scale;
|
|
3727
3681
|
/** @property {boolean} - If true this layer will render to overlay canvas and appear above all objects */
|
|
3728
3682
|
this.isOverlay = false;
|
|
3683
|
+
// set no friction by default, applied friction is max of both objects
|
|
3684
|
+
this.friction = 0;
|
|
3685
|
+
// set no elasticity by default, applied elasticity is max of both objects
|
|
3686
|
+
this.elasticity = 0;
|
|
3729
3687
|
|
|
3730
3688
|
// init tile data
|
|
3731
3689
|
this.data = [];
|
|
@@ -3922,13 +3880,151 @@ class TileLayer extends EngineObject
|
|
|
3922
3880
|
* @param {number} [angle=0] */
|
|
3923
3881
|
drawRect(pos, size, color, angle)
|
|
3924
3882
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3886
|
+
/**
|
|
3887
|
+
* Tile Collision Layer - a tile layer with collision
|
|
3888
|
+
* - adds collision data and functions to TileLayer
|
|
3889
|
+
* - there can be multiple tile collision layers
|
|
3890
|
+
* - tile collison layers should not overlap each other
|
|
3891
|
+
* @extends TileLayer
|
|
3892
|
+
*/
|
|
3893
|
+
class TileCollisionLayer extends TileLayer
|
|
3894
|
+
{
|
|
3895
|
+
/** Create a tile layer object
|
|
3896
|
+
* @param {Vector2} [position=(0,0)] - World space position
|
|
3897
|
+
* @param {Vector2} [size=(0,0)] - World space size
|
|
3898
|
+
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
3899
|
+
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
3900
|
+
*/
|
|
3901
|
+
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
3902
|
+
{
|
|
3903
|
+
const scale = vec2(1); // collision layers are not scaled
|
|
3904
|
+
super(position, size.floor(), tileInfo, scale, renderOrder);
|
|
3905
|
+
|
|
3906
|
+
/** @property {Array<number>} - The tile collision grid */
|
|
3907
|
+
this.collisionData = [];
|
|
3908
|
+
this.initCollision(this.size);
|
|
3909
|
+
|
|
3910
|
+
// keep track of all collision layers
|
|
3911
|
+
tileCollisionLayers.push(this);
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
/** Destroy this collision layer */
|
|
3915
|
+
destroy()
|
|
3916
|
+
{
|
|
3917
|
+
if (this.destroyed)
|
|
3918
|
+
return;
|
|
3919
|
+
|
|
3920
|
+
// remove from collision layers array and destroy
|
|
3921
|
+
const index = tileCollisionLayers.indexOf(this);
|
|
3922
|
+
ASSERT(index >= 0, 'tile collision layer not found in array');
|
|
3923
|
+
tileCollisionLayers.splice(index, 1);
|
|
3924
|
+
super.destroy();
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3927
|
+
/** Clear and initialize tile collision to new size
|
|
3928
|
+
* @param {Vector2} size - width and height of tile collision 2d grid */
|
|
3929
|
+
initCollision(size)
|
|
3930
|
+
{
|
|
3931
|
+
this.size = size.floor();
|
|
3932
|
+
this.collisionData = [];
|
|
3933
|
+
this.collisionData.length = size.area();
|
|
3934
|
+
this.collisionData.fill(0);
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
/** Set tile collision data for a given cell in the grid
|
|
3938
|
+
* @param {Vector2} pos
|
|
3939
|
+
* @param {number} [data] */
|
|
3940
|
+
setCollisionData(pos, data=1)
|
|
3941
|
+
{
|
|
3942
|
+
const i = (pos.y|0)*this.size.x + pos.x|0;
|
|
3943
|
+
pos.arrayCheck(this.size) && (this.collisionData[i] = data);
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
/** Get tile collision data for a given cell in the grid
|
|
3947
|
+
* @param {Vector2} pos
|
|
3948
|
+
* @return {number} */
|
|
3949
|
+
getCollisionData(pos)
|
|
3950
|
+
{
|
|
3951
|
+
const i = (pos.y|0)*this.size.x + pos.x|0;
|
|
3952
|
+
return pos.arrayCheck(this.size) ? this.collisionData[i] : 0;
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
/** Check if collision with another object should occur
|
|
3956
|
+
* @param {Vector2} pos
|
|
3957
|
+
* @param {Vector2} [size=(0,0)]
|
|
3958
|
+
* @param {EngineObject} [object]
|
|
3959
|
+
* @return {boolean} */
|
|
3960
|
+
collisionTest(pos, size=vec2(), object)
|
|
3961
|
+
{
|
|
3962
|
+
const minX = max(pos.x - size.x/2|0, 0);
|
|
3963
|
+
const minY = max(pos.y - size.y/2|0, 0);
|
|
3964
|
+
const maxX = min(pos.x + size.x/2, this.size.x);
|
|
3965
|
+
const maxY = min(pos.y + size.y/2, this.size.y);
|
|
3966
|
+
for (let y = minY; y < maxY; ++y)
|
|
3967
|
+
for (let x = minX; x < maxX; ++x)
|
|
3968
|
+
{
|
|
3969
|
+
// check if the object should collide with this tile
|
|
3970
|
+
const tileData = this.collisionData[y*this.size.x+x];
|
|
3971
|
+
if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
|
|
3972
|
+
return true;
|
|
3973
|
+
}
|
|
3974
|
+
return false;
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
/** Return the center of first tile hit, undefined if nothing was hit.
|
|
3978
|
+
* This does not return the exact intersection, but the center of the tile hit.
|
|
3979
|
+
* @param {Vector2} posStart
|
|
3980
|
+
* @param {Vector2} posEnd
|
|
3981
|
+
* @param {EngineObject} [object]
|
|
3982
|
+
* @return {Vector2} */
|
|
3983
|
+
collisionRaycast(posStart, posEnd, object)
|
|
3984
|
+
{
|
|
3985
|
+
// test if a ray collides with tiles from start to end
|
|
3986
|
+
// todo: a way to get the exact hit point, it must still be inside the hit tile
|
|
3987
|
+
const delta = posEnd.subtract(posStart);
|
|
3988
|
+
const totalLength = delta.length();
|
|
3989
|
+
const normalizedDelta = delta.normalize();
|
|
3990
|
+
const unit = vec2(abs(1/normalizedDelta.x), abs(1/normalizedDelta.y));
|
|
3991
|
+
const flooredPosStart = posStart.floor();
|
|
3992
|
+
|
|
3993
|
+
// setup iteration variables
|
|
3994
|
+
let pos = flooredPosStart;
|
|
3995
|
+
let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
|
|
3996
|
+
let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
|
|
3997
|
+
|
|
3998
|
+
// use line drawing algorithm to test for collisions
|
|
3999
|
+
while (true)
|
|
4000
|
+
{
|
|
4001
|
+
// check for tile collision
|
|
4002
|
+
const tileData = this.getCollisionData(pos);
|
|
4003
|
+
if (tileData && (!object || object.collideWithTile(tileData, pos)))
|
|
4004
|
+
{
|
|
4005
|
+
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
4006
|
+
debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
|
|
4007
|
+
return pos.add(vec2(.5));
|
|
4008
|
+
}
|
|
4009
|
+
|
|
4010
|
+
// check if past the end
|
|
4011
|
+
if (xi > totalLength && yi > totalLength)
|
|
4012
|
+
break;
|
|
4013
|
+
|
|
4014
|
+
// get coordinates of next tile to check
|
|
4015
|
+
if (xi > yi)
|
|
4016
|
+
pos.y += sign(delta.y), yi += unit.y;
|
|
4017
|
+
else
|
|
4018
|
+
pos.x += sign(delta.x), xi += unit.x;
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
4022
|
+
}
|
|
3925
4023
|
}
|
|
3926
4024
|
/**
|
|
3927
4025
|
* LittleJS Particle System
|
|
3928
4026
|
*/
|
|
3929
4027
|
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
4028
|
/**
|
|
3933
4029
|
* Particle Emitter - Spawns particles with the given settings
|
|
3934
4030
|
* @extends EngineObject
|
|
@@ -4273,8 +4369,6 @@ class Particle extends EngineObject
|
|
|
4273
4369
|
* @namespace Medals
|
|
4274
4370
|
*/
|
|
4275
4371
|
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
4372
|
/** List of all medals
|
|
4279
4373
|
* @type {Object}
|
|
4280
4374
|
* @memberof Medals */
|
|
@@ -4462,8 +4556,6 @@ class Medal
|
|
|
4462
4556
|
* @namespace WebGL
|
|
4463
4557
|
*/
|
|
4464
4558
|
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
4559
|
/** The WebGL canvas which appears above the main canvas and below the overlay canvas
|
|
4468
4560
|
* @type {HTMLCanvasElement}
|
|
4469
4561
|
* @memberof WebGL */
|
|
@@ -4662,7 +4754,19 @@ function glCreateTexture(image)
|
|
|
4662
4754
|
const texture = glContext.createTexture();
|
|
4663
4755
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
4664
4756
|
if (image && image.width)
|
|
4757
|
+
{
|
|
4665
4758
|
glSetTextureData(texture, image);
|
|
4759
|
+
|
|
4760
|
+
const isPowerOfTwo = (value)=> !(value & (value - 1));
|
|
4761
|
+
if (!tilesPixelated && isPowerOfTwo(image.width) && isPowerOfTwo(image.height))
|
|
4762
|
+
{
|
|
4763
|
+
// use mipmap filtering
|
|
4764
|
+
glContext.generateMipmap(glContext.TEXTURE_2D);
|
|
4765
|
+
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, glContext.LINEAR_MIPMAP_LINEAR);
|
|
4766
|
+
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, glContext.LINEAR);
|
|
4767
|
+
return texture;
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4666
4770
|
else
|
|
4667
4771
|
{
|
|
4668
4772
|
// create a white texture
|
|
@@ -4670,7 +4774,7 @@ function glCreateTexture(image)
|
|
|
4670
4774
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
|
|
4671
4775
|
}
|
|
4672
4776
|
|
|
4673
|
-
//
|
|
4777
|
+
// set texture filtering
|
|
4674
4778
|
const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
|
|
4675
4779
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
|
|
4676
4780
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, filter);
|
|
@@ -4786,8 +4890,6 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=-1, rgba
|
|
|
4786
4890
|
* @namespace Engine
|
|
4787
4891
|
*/
|
|
4788
4892
|
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
4893
|
/** Name of engine
|
|
4792
4894
|
* @type {string}
|
|
4793
4895
|
* @default
|
|
@@ -4798,7 +4900,7 @@ const engineName = 'LittleJS';
|
|
|
4798
4900
|
* @type {string}
|
|
4799
4901
|
* @default
|
|
4800
4902
|
* @memberof Engine */
|
|
4801
|
-
const engineVersion = '1.
|
|
4903
|
+
const engineVersion = '1.12.6';
|
|
4802
4904
|
|
|
4803
4905
|
/** Frames per second to update
|
|
4804
4906
|
* @type {number}
|
|
@@ -4880,7 +4982,7 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
4880
4982
|
* @param {Array<string>} [imageSources=[]] - List of images to load
|
|
4881
4983
|
* @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
|
|
4882
4984
|
* @memberof Engine */
|
|
4883
|
-
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
4985
|
+
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
4884
4986
|
{
|
|
4885
4987
|
ASSERT(!mainContext, 'engine already initialized');
|
|
4886
4988
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
@@ -5037,16 +5139,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5037
5139
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
5038
5140
|
}
|
|
5039
5141
|
|
|
5040
|
-
|
|
5142
|
+
// wait for gameInit to load
|
|
5143
|
+
async function startEngine()
|
|
5041
5144
|
{
|
|
5042
|
-
|
|
5145
|
+
await gameInit();
|
|
5146
|
+
engineUpdate();
|
|
5043
5147
|
}
|
|
5044
|
-
|
|
5045
5148
|
if (headlessMode)
|
|
5046
|
-
|
|
5047
|
-
startEngine();
|
|
5048
|
-
return;
|
|
5049
|
-
}
|
|
5149
|
+
return startEngine();
|
|
5050
5150
|
|
|
5051
5151
|
// setup html
|
|
5052
5152
|
const styleRoot =
|
|
@@ -5122,8 +5222,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5122
5222
|
}));
|
|
5123
5223
|
}
|
|
5124
5224
|
|
|
5125
|
-
//
|
|
5126
|
-
Promise.all(promises)
|
|
5225
|
+
// wait for all the promises to finish
|
|
5226
|
+
await Promise.all(promises);
|
|
5227
|
+
return startEngine();
|
|
5127
5228
|
}
|
|
5128
5229
|
|
|
5129
5230
|
/** Update each engine object, remove destroyed objects, and update time
|
|
@@ -5392,3 +5493,2777 @@ function drawEngineSplashScreen(t)
|
|
|
5392
5493
|
x.restore();
|
|
5393
5494
|
}
|
|
5394
5495
|
|
|
5496
|
+
/**
|
|
5497
|
+
* LittleJS Newgrounds API
|
|
5498
|
+
* - NewgroundsMedal extends Medal with Newgrounds API functionality
|
|
5499
|
+
* - Call new NewgroundsPlugin() to setup Newgrounds
|
|
5500
|
+
* - Uses CryptoJS for encryption if optional cipher is provided
|
|
5501
|
+
* - Keeps connection alive and logs views
|
|
5502
|
+
* - Functions to interact with scoreboards
|
|
5503
|
+
* - Functions to unlock medals
|
|
5504
|
+
*/
|
|
5505
|
+
|
|
5506
|
+
/** Global Newgrounds object
|
|
5507
|
+
* @type {NewgroundsPlugin}
|
|
5508
|
+
* @memberof Medal */
|
|
5509
|
+
let newgrounds;
|
|
5510
|
+
|
|
5511
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5512
|
+
/**
|
|
5513
|
+
* Newgrounds medal auto unlocks in newgrounds API
|
|
5514
|
+
* @extends Medal
|
|
5515
|
+
*/
|
|
5516
|
+
class NewgroundsMedal extends Medal
|
|
5517
|
+
{
|
|
5518
|
+
/** Create a newgrounds medal object and adds it to the list of medals
|
|
5519
|
+
* @param {Number} id - The unique identifier of the medal
|
|
5520
|
+
* @param {String} name - Name of the medal
|
|
5521
|
+
* @param {String} [description] - Description of the medal
|
|
5522
|
+
* @param {String} [icon] - Icon for the medal
|
|
5523
|
+
* @param {String} [src] - Image location for the medal
|
|
5524
|
+
*/
|
|
5525
|
+
constructor(id, name, description, icon, src)
|
|
5526
|
+
{ super(id, name, description, icon, src); }
|
|
5527
|
+
|
|
5528
|
+
/** Unlocks a medal if not already unlocked */
|
|
5529
|
+
unlock()
|
|
5530
|
+
{
|
|
5531
|
+
super.unlock();
|
|
5532
|
+
newgrounds && newgrounds.unlockMedal(this.id);
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
|
|
5536
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5537
|
+
/**
|
|
5538
|
+
* Newgrounds API object
|
|
5539
|
+
*/
|
|
5540
|
+
class NewgroundsPlugin
|
|
5541
|
+
{
|
|
5542
|
+
/** Create the global newgrounds object
|
|
5543
|
+
* @param {string} app_id - The newgrounds App ID
|
|
5544
|
+
* @param {string} [cipher] - The encryption Key (AES-128/Base64)
|
|
5545
|
+
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
|
|
5546
|
+
* @example
|
|
5547
|
+
* // create the newgrounds object, replace the app id with your own
|
|
5548
|
+
* const app_id = 'your_app_id_here';
|
|
5549
|
+
* new NewgroundsPlugin(app_id);
|
|
5550
|
+
*/
|
|
5551
|
+
constructor(app_id, cipher, cryptoJS)
|
|
5552
|
+
{
|
|
5553
|
+
ASSERT(!newgrounds, 'there can only be one newgrounds object');
|
|
5554
|
+
ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
|
|
5555
|
+
|
|
5556
|
+
newgrounds = this; // set global newgrounds object
|
|
5557
|
+
this.app_id = app_id;
|
|
5558
|
+
this.cipher = cipher;
|
|
5559
|
+
this.cryptoJS = cryptoJS;
|
|
5560
|
+
this.host = location ? location.hostname : '';
|
|
5561
|
+
|
|
5562
|
+
// get session id from url search params
|
|
5563
|
+
const url = new URL(location.href);
|
|
5564
|
+
this.session_id = url.searchParams.get('ngio_session_id');
|
|
5565
|
+
|
|
5566
|
+
if (!this.session_id)
|
|
5567
|
+
return; // only use newgrounds when logged in
|
|
5568
|
+
|
|
5569
|
+
// get medals
|
|
5570
|
+
const medalsResult = this.call('Medal.getList');
|
|
5571
|
+
this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
|
|
5572
|
+
debugMedals && console.log(this.medals);
|
|
5573
|
+
for (const newgroundsMedal of this.medals)
|
|
5574
|
+
{
|
|
5575
|
+
const medal = medals[newgroundsMedal['id']];
|
|
5576
|
+
if (medal)
|
|
5577
|
+
{
|
|
5578
|
+
// copy newgrounds medal data
|
|
5579
|
+
medal.image = new Image;
|
|
5580
|
+
medal.image.src = newgroundsMedal['icon'];
|
|
5581
|
+
medal.name = newgroundsMedal['name'];
|
|
5582
|
+
medal.description = newgroundsMedal['description'];
|
|
5583
|
+
medal.unlocked = newgroundsMedal['unlocked'];
|
|
5584
|
+
medal.difficulty = newgroundsMedal['difficulty'];
|
|
5585
|
+
medal.value = newgroundsMedal['value'];
|
|
5586
|
+
|
|
5587
|
+
if (medal.value) // add value to description
|
|
5588
|
+
medal.description = medal.description + ` (${ medal.value })`;
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
|
|
5592
|
+
// get scoreboards
|
|
5593
|
+
const scoreboardResult = this.call('ScoreBoard.getBoards');
|
|
5594
|
+
this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
|
|
5595
|
+
debugMedals && console.log(this.scoreboards);
|
|
5596
|
+
|
|
5597
|
+
// keep the session alive with a ping every minute
|
|
5598
|
+
const keepAliveMS = 60 * 1e3;
|
|
5599
|
+
setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
|
|
5600
|
+
}
|
|
5601
|
+
|
|
5602
|
+
/** Send message to unlock a medal by id
|
|
5603
|
+
* @param {number} id - The medal id */
|
|
5604
|
+
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
|
|
5605
|
+
|
|
5606
|
+
/** Send message to post score
|
|
5607
|
+
* @param {number} id - The scoreboard id
|
|
5608
|
+
* @param {number} value - The score value */
|
|
5609
|
+
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
|
|
5610
|
+
|
|
5611
|
+
/** Get scores from a scoreboard
|
|
5612
|
+
* @param {number} id - The scoreboard id
|
|
5613
|
+
* @param {string} [user] - A user's id or name
|
|
5614
|
+
* @param {number} [social] - If true, only social scores will be loaded
|
|
5615
|
+
* @param {number} [skip] - Number of scores to skip before start
|
|
5616
|
+
* @param {number} [limit] - Number of scores to include in the list
|
|
5617
|
+
* @return {Object} - The response JSON object
|
|
5618
|
+
*/
|
|
5619
|
+
getScores(id, user, social=0, skip=0, limit=10)
|
|
5620
|
+
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
5621
|
+
|
|
5622
|
+
/** Send message to log a view */
|
|
5623
|
+
logView() { return this.call('App.logView', {'host':this.host}, true); }
|
|
5624
|
+
|
|
5625
|
+
/** Send a message to call a component of the Newgrounds API
|
|
5626
|
+
* @param {string} component - Name of the component
|
|
5627
|
+
* @param {Object} [parameters] - Parameters to use for call
|
|
5628
|
+
* @param {boolean} [async] - If true, don't wait for response before continuing
|
|
5629
|
+
* @return {Object} - The response JSON object
|
|
5630
|
+
*/
|
|
5631
|
+
call(component, parameters, async=false)
|
|
5632
|
+
{
|
|
5633
|
+
const call = {'component':component, 'parameters':parameters};
|
|
5634
|
+
if (this.cipher)
|
|
5635
|
+
{
|
|
5636
|
+
// encrypt using AES-128 Base64 with cryptoJS
|
|
5637
|
+
const cryptoJS = this.cryptoJS;
|
|
5638
|
+
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
5639
|
+
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
5640
|
+
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
5641
|
+
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
5642
|
+
call['parameters'] = 0;
|
|
5643
|
+
}
|
|
5644
|
+
|
|
5645
|
+
// build the input object
|
|
5646
|
+
const input =
|
|
5647
|
+
{
|
|
5648
|
+
'app_id': this.app_id,
|
|
5649
|
+
'session_id': this.session_id,
|
|
5650
|
+
'call': call
|
|
5651
|
+
};
|
|
5652
|
+
|
|
5653
|
+
// build post data
|
|
5654
|
+
const formData = new FormData();
|
|
5655
|
+
formData.append('input', JSON.stringify(input));
|
|
5656
|
+
|
|
5657
|
+
// send post data
|
|
5658
|
+
const xmlHttp = new XMLHttpRequest();
|
|
5659
|
+
const url = 'https://newgrounds.io/gateway_v3.php';
|
|
5660
|
+
xmlHttp.open('POST', url, !debugMedals && async);
|
|
5661
|
+
try { xmlHttp.send(formData); }
|
|
5662
|
+
catch(e)
|
|
5663
|
+
{
|
|
5664
|
+
debugMedals && console.log('newgrounds call failed', e);
|
|
5665
|
+
return;
|
|
5666
|
+
}
|
|
5667
|
+
debugMedals && console.log(xmlHttp.responseText);
|
|
5668
|
+
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
5669
|
+
}
|
|
5670
|
+
}
|
|
5671
|
+
|
|
5672
|
+
/**
|
|
5673
|
+
* LittleJS Post Processing Plugin
|
|
5674
|
+
* - Supports shadertoy style post processing shaders
|
|
5675
|
+
* - call new new PostProcessPlugin() to setup post processing
|
|
5676
|
+
* - can be enabled to pass other canvases through a final shader
|
|
5677
|
+
*/
|
|
5678
|
+
|
|
5679
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5680
|
+
|
|
5681
|
+
/** Global Post Process plugin object
|
|
5682
|
+
* @type {PostProcessPlugin} */
|
|
5683
|
+
let postProcess;
|
|
5684
|
+
|
|
5685
|
+
/////////////////////////////////////////////////////////////////////////
|
|
5686
|
+
/**
|
|
5687
|
+
* UI System Global Object
|
|
5688
|
+
*/
|
|
5689
|
+
class PostProcessPlugin
|
|
5690
|
+
{
|
|
5691
|
+
/** Create global post processing shader
|
|
5692
|
+
* @param {string} shaderCode
|
|
5693
|
+
* @param {boolean} [includeOverlay]
|
|
5694
|
+
* @example
|
|
5695
|
+
* // create the post process plugin object
|
|
5696
|
+
* new PostProcessPlugin(shaderCode);
|
|
5697
|
+
*/
|
|
5698
|
+
constructor(shaderCode, includeOverlay=false)
|
|
5699
|
+
{
|
|
5700
|
+
ASSERT(!postProcess, 'Post process already initialized');
|
|
5701
|
+
postProcess = this;
|
|
5702
|
+
|
|
5703
|
+
if (headlessMode) return;
|
|
5704
|
+
if (!shaderCode) // default shader pass through
|
|
5705
|
+
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
5706
|
+
|
|
5707
|
+
/** @property {WebGLProgram} - Shader for post processing */
|
|
5708
|
+
this.shader = glCreateProgram(
|
|
5709
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
5710
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
5711
|
+
'in vec2 p;'+ // position
|
|
5712
|
+
'void main(){'+ // shader entry point
|
|
5713
|
+
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
5714
|
+
'}' // end of shader
|
|
5715
|
+
,
|
|
5716
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
5717
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
5718
|
+
'uniform sampler2D iChannel0;'+ // input texture
|
|
5719
|
+
'uniform vec3 iResolution;'+ // size of output texture
|
|
5720
|
+
'uniform float iTime;'+ // time
|
|
5721
|
+
'out vec4 c;'+ // out color
|
|
5722
|
+
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
5723
|
+
'void main(){'+ // shader entry point
|
|
5724
|
+
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
5725
|
+
'c.a=1.;'+ // always use full alpha
|
|
5726
|
+
'}' // end of shader
|
|
5727
|
+
);
|
|
5728
|
+
|
|
5729
|
+
/** @property {WebGLTexture} - Texture for post processing */
|
|
5730
|
+
this.texture = glCreateTexture();
|
|
5731
|
+
|
|
5732
|
+
/** @property {boolean} - Should overlay canvas be included in post processing */
|
|
5733
|
+
this.includeOverlay = includeOverlay;
|
|
5734
|
+
|
|
5735
|
+
// Render the post processing shader, called automatically by the engine
|
|
5736
|
+
engineAddPlugin(undefined, postProcessRender);
|
|
5737
|
+
function postProcessRender()
|
|
5738
|
+
{
|
|
5739
|
+
if (headlessMode) return;
|
|
5740
|
+
|
|
5741
|
+
// prepare to render post process shader
|
|
5742
|
+
if (glEnable)
|
|
5743
|
+
{
|
|
5744
|
+
glFlush(); // clear out the buffer
|
|
5745
|
+
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
5746
|
+
}
|
|
5747
|
+
else
|
|
5748
|
+
{
|
|
5749
|
+
// set the viewport
|
|
5750
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
5751
|
+
}
|
|
5752
|
+
|
|
5753
|
+
if (postProcess.includeOverlay)
|
|
5754
|
+
{
|
|
5755
|
+
// copy overlay canvas so it will be included in post processing
|
|
5756
|
+
mainContext.drawImage(overlayCanvas, 0, 0);
|
|
5757
|
+
overlayCanvas.width |= 0;
|
|
5758
|
+
}
|
|
5759
|
+
|
|
5760
|
+
// setup shader program to draw one triangle
|
|
5761
|
+
glContext.useProgram(postProcess.shader);
|
|
5762
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
5763
|
+
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, 1);
|
|
5764
|
+
glContext.disable(glContext.BLEND);
|
|
5765
|
+
|
|
5766
|
+
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
5767
|
+
glContext.activeTexture(glContext.TEXTURE0);
|
|
5768
|
+
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
5769
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, mainCanvas);
|
|
5770
|
+
|
|
5771
|
+
// set vertex position attribute
|
|
5772
|
+
const vertexByteStride = 8;
|
|
5773
|
+
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
5774
|
+
glContext.enableVertexAttribArray(pLocation);
|
|
5775
|
+
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
5776
|
+
|
|
5777
|
+
// set uniforms and draw
|
|
5778
|
+
const uniformLocation = (name)=>glContext.getUniformLocation(postProcess.shader, name);
|
|
5779
|
+
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
5780
|
+
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
5781
|
+
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
5782
|
+
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
|
|
5783
|
+
}
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
|
|
5787
|
+
/**
|
|
5788
|
+
* LittleJS ZzFXM Plugin
|
|
5789
|
+
*/
|
|
5790
|
+
|
|
5791
|
+
/**
|
|
5792
|
+
* Music Object - Stores a zzfx music track for later use
|
|
5793
|
+
*
|
|
5794
|
+
* <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
|
|
5795
|
+
* @example
|
|
5796
|
+
* // create some music
|
|
5797
|
+
* const music_example = new Music(
|
|
5798
|
+
* [
|
|
5799
|
+
* [ // instruments
|
|
5800
|
+
* [,0,400] // simple note
|
|
5801
|
+
* ],
|
|
5802
|
+
* [ // patterns
|
|
5803
|
+
* [ // pattern 1
|
|
5804
|
+
* [ // channel 0
|
|
5805
|
+
* 0, -1, // instrument 0, left speaker
|
|
5806
|
+
* 1, 0, 9, 1 // channel notes
|
|
5807
|
+
* ],
|
|
5808
|
+
* [ // channel 1
|
|
5809
|
+
* 0, 1, // instrument 0, right speaker
|
|
5810
|
+
* 0, 12, 17, -1 // channel notes
|
|
5811
|
+
* ]
|
|
5812
|
+
* ],
|
|
5813
|
+
* ],
|
|
5814
|
+
* [0, 0, 0, 0], // sequence, play pattern 0 four times
|
|
5815
|
+
* 90 // BPM
|
|
5816
|
+
* ]);
|
|
5817
|
+
*
|
|
5818
|
+
* // play the music
|
|
5819
|
+
* music_example.play();
|
|
5820
|
+
*/
|
|
5821
|
+
class ZzFXMusic extends Sound
|
|
5822
|
+
{
|
|
5823
|
+
/** Create a music object and cache the zzfx music samples for later use
|
|
5824
|
+
* @param {[Array, Array, Array, number]} zzfxMusic - Array of zzfx music parameters
|
|
5825
|
+
*/
|
|
5826
|
+
constructor(zzfxMusic)
|
|
5827
|
+
{
|
|
5828
|
+
super(undefined);
|
|
5829
|
+
|
|
5830
|
+
if (!soundEnable || headlessMode) return;
|
|
5831
|
+
this.randomness = 0;
|
|
5832
|
+
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
5833
|
+
this.sampleRate = zzfxR;
|
|
5834
|
+
}
|
|
5835
|
+
|
|
5836
|
+
/** Play the music
|
|
5837
|
+
* @param {number} [volume=1] - How much to scale volume by
|
|
5838
|
+
* @param {boolean} [loop] - True if the music should loop
|
|
5839
|
+
* @return {AudioBufferSourceNode} - The audio source node
|
|
5840
|
+
*/
|
|
5841
|
+
playMusic(volume, loop=false)
|
|
5842
|
+
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
5843
|
+
}
|
|
5844
|
+
|
|
5845
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5846
|
+
// ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
|
|
5847
|
+
|
|
5848
|
+
/** Generate samples for a ZzFM song with given parameters
|
|
5849
|
+
* @param {Array} instruments - Array of ZzFX sound parameters
|
|
5850
|
+
* @param {Array} patterns - Array of pattern data
|
|
5851
|
+
* @param {Array} sequence - Array of pattern indexes
|
|
5852
|
+
* @param {number} [BPM] - Playback speed of the song in BPM
|
|
5853
|
+
* @return {Array} - Left and right channel sample data */
|
|
5854
|
+
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
5855
|
+
{
|
|
5856
|
+
let i, j, k;
|
|
5857
|
+
let instrumentParameters;
|
|
5858
|
+
let note;
|
|
5859
|
+
let sample;
|
|
5860
|
+
let patternChannel;
|
|
5861
|
+
let notFirstBeat;
|
|
5862
|
+
let stop;
|
|
5863
|
+
let instrument;
|
|
5864
|
+
let attenuation;
|
|
5865
|
+
let outSampleOffset;
|
|
5866
|
+
let isSequenceEnd;
|
|
5867
|
+
let sampleOffset = 0;
|
|
5868
|
+
let nextSampleOffset;
|
|
5869
|
+
let sampleBuffer = [];
|
|
5870
|
+
let leftChannelBuffer = [];
|
|
5871
|
+
let rightChannelBuffer = [];
|
|
5872
|
+
let channelIndex = 0;
|
|
5873
|
+
let panning = 0;
|
|
5874
|
+
let hasMore = 1;
|
|
5875
|
+
let sampleCache = {};
|
|
5876
|
+
let beatLength = zzfxR / BPM * 60 >> 2;
|
|
5877
|
+
|
|
5878
|
+
// for each channel in order until there are no more
|
|
5879
|
+
for (; hasMore; channelIndex++) {
|
|
5880
|
+
|
|
5881
|
+
// reset current values
|
|
5882
|
+
sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
|
|
5883
|
+
|
|
5884
|
+
// for each pattern in sequence
|
|
5885
|
+
sequence.forEach((patternIndex, sequenceIndex) => {
|
|
5886
|
+
// get pattern for current channel, use empty 1 note pattern if none found
|
|
5887
|
+
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
5888
|
+
|
|
5889
|
+
// check if there are more channels
|
|
5890
|
+
hasMore |= patterns[patternIndex][channelIndex]&&1;
|
|
5891
|
+
|
|
5892
|
+
// get next offset, use the length of first channel
|
|
5893
|
+
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
|
|
5894
|
+
// for each beat in pattern, plus one extra if end of sequence
|
|
5895
|
+
isSequenceEnd = sequenceIndex == sequence.length - 1;
|
|
5896
|
+
for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
|
|
5897
|
+
|
|
5898
|
+
// <channel-note>
|
|
5899
|
+
note = patternChannel[i];
|
|
5900
|
+
|
|
5901
|
+
// stop if end, different instrument or new note
|
|
5902
|
+
stop = i == patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
5903
|
+
instrument != (patternChannel[0] || 0) || note | 0;
|
|
5904
|
+
|
|
5905
|
+
// fill buffer with samples for previous beat, most cpu intensive part
|
|
5906
|
+
for (j = 0; j < beatLength && notFirstBeat;
|
|
5907
|
+
|
|
5908
|
+
// fade off attenuation at end of beat if stopping note, prevents clicking
|
|
5909
|
+
j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
|
|
5910
|
+
) {
|
|
5911
|
+
// copy sample to stereo buffers with panning
|
|
5912
|
+
sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
|
|
5913
|
+
leftChannelBuffer[k] = (leftChannelBuffer[k] || 0) - sample * panning + sample;
|
|
5914
|
+
rightChannelBuffer[k] = (rightChannelBuffer[k++] || 0) + sample * panning + sample;
|
|
5915
|
+
}
|
|
5916
|
+
|
|
5917
|
+
// set up for next note
|
|
5918
|
+
if (note) {
|
|
5919
|
+
// set attenuation
|
|
5920
|
+
attenuation = note % 1;
|
|
5921
|
+
panning = patternChannel[1] || 0;
|
|
5922
|
+
if (note |= 0) {
|
|
5923
|
+
// get cached sample
|
|
5924
|
+
sampleBuffer = sampleCache[
|
|
5925
|
+
[
|
|
5926
|
+
instrument = patternChannel[sampleOffset = 0] || 0,
|
|
5927
|
+
note
|
|
5928
|
+
]
|
|
5929
|
+
] = sampleCache[[instrument, note]] || (
|
|
5930
|
+
// add sample to cache
|
|
5931
|
+
instrumentParameters = [...instruments[instrument]],
|
|
5932
|
+
instrumentParameters[2] = (instrumentParameters[2] || 220) * 2**(note / 12 - 1),
|
|
5933
|
+
|
|
5934
|
+
// allow negative values to stop notes
|
|
5935
|
+
note > 0 ? zzfxG(...instrumentParameters) : []
|
|
5936
|
+
);
|
|
5937
|
+
}
|
|
5938
|
+
}
|
|
5939
|
+
}
|
|
5940
|
+
|
|
5941
|
+
// update the sample offset
|
|
5942
|
+
outSampleOffset = nextSampleOffset;
|
|
5943
|
+
});
|
|
5944
|
+
}
|
|
5945
|
+
|
|
5946
|
+
return [leftChannelBuffer, rightChannelBuffer];
|
|
5947
|
+
}
|
|
5948
|
+
|
|
5949
|
+
/**
|
|
5950
|
+
* LittleJS User Interface Plugin
|
|
5951
|
+
* - call new UISystemPlugin() to setup the UI system
|
|
5952
|
+
* - Nested Menus
|
|
5953
|
+
* - Text
|
|
5954
|
+
* - Buttons
|
|
5955
|
+
* - Checkboxes
|
|
5956
|
+
* - Images
|
|
5957
|
+
*/
|
|
5958
|
+
|
|
5959
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5960
|
+
|
|
5961
|
+
/** Global UI system plugin object
|
|
5962
|
+
* @type {UISystemPlugin} */
|
|
5963
|
+
let uiSystem;
|
|
5964
|
+
|
|
5965
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5966
|
+
/**
|
|
5967
|
+
* UI System Global Object
|
|
5968
|
+
*/
|
|
5969
|
+
class UISystemPlugin
|
|
5970
|
+
{
|
|
5971
|
+
/** Create the global UI system object
|
|
5972
|
+
* @param {CanvasRenderingContext2D} [context]
|
|
5973
|
+
* @example
|
|
5974
|
+
* // create the ui plugin object
|
|
5975
|
+
* new UISystemPlugin;
|
|
5976
|
+
*/
|
|
5977
|
+
constructor(context=overlayContext)
|
|
5978
|
+
{
|
|
5979
|
+
ASSERT(!uiSystem, 'UI system already initialized');
|
|
5980
|
+
uiSystem = this;
|
|
5981
|
+
|
|
5982
|
+
/** @property {Color} - Default fill color for UI elements */
|
|
5983
|
+
this.defaultColor = WHITE;
|
|
5984
|
+
/** @property {Color} - Default outline color for UI elements */
|
|
5985
|
+
this.defaultLineColor = BLACK;
|
|
5986
|
+
/** @property {Color} - Default text color for UI elements */
|
|
5987
|
+
this.defaultTextColor = BLACK;
|
|
5988
|
+
/** @property {Color} - Default button color for UI elements */
|
|
5989
|
+
this.defaultButtonColor = hsl(0,0,.5);
|
|
5990
|
+
/** @property {Color} - Default hover color for UI elements */
|
|
5991
|
+
this.defaultHoverColor = hsl(0,0,.7);
|
|
5992
|
+
/** @property {number} - Default line width for UI elements */
|
|
5993
|
+
this.defaultLineWidth = 4;
|
|
5994
|
+
/** @property {string} - Default font for UI elements */
|
|
5995
|
+
this.defaultFont = 'arial';
|
|
5996
|
+
/** @property {Array<UIObject>} - List of all UI elements */
|
|
5997
|
+
this.uiObjects = [];
|
|
5998
|
+
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - Context to render UI elements to */
|
|
5999
|
+
this.uiContext = context;
|
|
6000
|
+
|
|
6001
|
+
engineAddPlugin(uiUpdate, uiRender);
|
|
6002
|
+
|
|
6003
|
+
// setup recursive update and render
|
|
6004
|
+
function uiUpdate()
|
|
6005
|
+
{
|
|
6006
|
+
function updateObject(o)
|
|
6007
|
+
{
|
|
6008
|
+
if (!o.visible)
|
|
6009
|
+
return;
|
|
6010
|
+
if (o.parent)
|
|
6011
|
+
o.pos = o.localPos.add(o.parent.pos);
|
|
6012
|
+
o.update();
|
|
6013
|
+
for(const c of o.children)
|
|
6014
|
+
updateObject(c);
|
|
6015
|
+
}
|
|
6016
|
+
uiSystem.uiObjects.forEach(o=> o.parent || updateObject(o));
|
|
6017
|
+
}
|
|
6018
|
+
function uiRender()
|
|
6019
|
+
{
|
|
6020
|
+
function renderObject(o)
|
|
6021
|
+
{
|
|
6022
|
+
if (!o.visible)
|
|
6023
|
+
return;
|
|
6024
|
+
if (o.parent)
|
|
6025
|
+
o.pos = o.localPos.add(o.parent.pos);
|
|
6026
|
+
o.render();
|
|
6027
|
+
for(const c of o.children)
|
|
6028
|
+
renderObject(c);
|
|
6029
|
+
}
|
|
6030
|
+
uiSystem.uiObjects.forEach(o=> o.parent || renderObject(o));
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
|
|
6034
|
+
/** Draw a rectangle to the UI context
|
|
6035
|
+
* @param {Vector2} pos
|
|
6036
|
+
* @param {Vector2} size
|
|
6037
|
+
* @param {Color} [color=uiSystem.defaultColor]
|
|
6038
|
+
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
6039
|
+
* @param {Color} [lineColor=uiSystem.defaultLineColor] */
|
|
6040
|
+
drawRect(pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor)
|
|
6041
|
+
{
|
|
6042
|
+
uiSystem.uiContext.fillStyle = color.toString();
|
|
6043
|
+
uiSystem.uiContext.beginPath();
|
|
6044
|
+
uiSystem.uiContext.rect(pos.x-size.x/2, pos.y-size.y/2, size.x, size.y);
|
|
6045
|
+
uiSystem.uiContext.fill();
|
|
6046
|
+
if (lineWidth)
|
|
6047
|
+
{
|
|
6048
|
+
uiSystem.uiContext.strokeStyle = lineColor.toString();
|
|
6049
|
+
uiSystem.uiContext.lineWidth = lineWidth;
|
|
6050
|
+
uiSystem.uiContext.stroke();
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
|
|
6054
|
+
/** Draw a line to the UI context
|
|
6055
|
+
* @param {Vector2} posA
|
|
6056
|
+
* @param {Vector2} posB
|
|
6057
|
+
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
6058
|
+
* @param {Color} [lineColor=uiSystem.defaultLineColor] */
|
|
6059
|
+
drawLine(posA, posB, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor)
|
|
6060
|
+
{
|
|
6061
|
+
uiSystem.uiContext.strokeStyle = lineColor.toString();
|
|
6062
|
+
uiSystem.uiContext.lineWidth = lineWidth;
|
|
6063
|
+
uiSystem.uiContext.beginPath();
|
|
6064
|
+
uiSystem.uiContext.lineTo(posA.x, posA.y);
|
|
6065
|
+
uiSystem.uiContext.lineTo(posB.x, posB.y);
|
|
6066
|
+
uiSystem.uiContext.stroke();
|
|
6067
|
+
}
|
|
6068
|
+
|
|
6069
|
+
/** Draw a tile to the UI context
|
|
6070
|
+
* @param {Vector2} pos
|
|
6071
|
+
* @param {Vector2} size
|
|
6072
|
+
* @param {TileInfo} tileInfo
|
|
6073
|
+
* @param {Color} [color=uiSystem.defaultColor]
|
|
6074
|
+
* @param {number} [angle]
|
|
6075
|
+
* @param {boolean} [mirror] */
|
|
6076
|
+
drawTile(pos, size, tileInfo, color=uiSystem.defaultColor, angle=0, mirror=false)
|
|
6077
|
+
{
|
|
6078
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, BLACK, false, true, uiSystem.uiContext);
|
|
6079
|
+
}
|
|
6080
|
+
|
|
6081
|
+
/** Draw text to the UI context
|
|
6082
|
+
* @param {string} text
|
|
6083
|
+
* @param {Vector2} pos
|
|
6084
|
+
* @param {Vector2} size
|
|
6085
|
+
* @param {Color} [color=uiSystem.defaultColor]
|
|
6086
|
+
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
6087
|
+
* @param {Color} [lineColor=uiSystem.defaultLineColor]
|
|
6088
|
+
* @param {string} [align]
|
|
6089
|
+
* @param {string} [font=uiSystem.defaultFont] */
|
|
6090
|
+
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont)
|
|
6091
|
+
{
|
|
6092
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, size.x, uiSystem.uiContext);
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
|
|
6096
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6097
|
+
/**
|
|
6098
|
+
* UI Object - Base level object for all UI elements
|
|
6099
|
+
*/
|
|
6100
|
+
class UIObject
|
|
6101
|
+
{
|
|
6102
|
+
/** Create a UIObject
|
|
6103
|
+
* @param {Vector2} [pos=(0,0)]
|
|
6104
|
+
* @param {Vector2} [size=(1,1)]
|
|
6105
|
+
*/
|
|
6106
|
+
constructor(pos=vec2(), size=vec2())
|
|
6107
|
+
{
|
|
6108
|
+
/** @property {Vector2} - Local position of the object */
|
|
6109
|
+
this.localPos = pos.copy();
|
|
6110
|
+
/** @property {Vector2} - Screen space position of the object */
|
|
6111
|
+
this.pos = pos.copy();
|
|
6112
|
+
/** @property {Vector2} - Screen space size of the object */
|
|
6113
|
+
this.size = size.copy();
|
|
6114
|
+
/** @property {Color} */
|
|
6115
|
+
this.color = uiSystem.defaultColor;
|
|
6116
|
+
/** @property {Color} */
|
|
6117
|
+
this.lineColor = uiSystem.defaultLineColor;
|
|
6118
|
+
/** @property {Color} */
|
|
6119
|
+
this.textColor = uiSystem.defaultTextColor;
|
|
6120
|
+
/** @property {Color} */
|
|
6121
|
+
this.hoverColor = uiSystem.defaultHoverColor;
|
|
6122
|
+
/** @property {number} */
|
|
6123
|
+
this.lineWidth = uiSystem.defaultLineWidth;
|
|
6124
|
+
/** @property {string} */
|
|
6125
|
+
this.font = uiSystem.defaultFont;
|
|
6126
|
+
/** @property {number} - override for text height */
|
|
6127
|
+
this.textHeight = undefined;
|
|
6128
|
+
/** @property {boolean} */
|
|
6129
|
+
this.visible = true;
|
|
6130
|
+
/** @property {Array<UIObject>} */
|
|
6131
|
+
this.children = [];
|
|
6132
|
+
/** @property {UIObject} */
|
|
6133
|
+
this.parent = undefined;
|
|
6134
|
+
uiSystem.uiObjects.push(this);
|
|
6135
|
+
}
|
|
6136
|
+
|
|
6137
|
+
/** Add a child UIObject to this object
|
|
6138
|
+
* @param {UIObject} child
|
|
6139
|
+
*/
|
|
6140
|
+
addChild(child)
|
|
6141
|
+
{
|
|
6142
|
+
ASSERT(!child.parent && !this.children.includes(child));
|
|
6143
|
+
this.children.push(child);
|
|
6144
|
+
child.parent = this;
|
|
6145
|
+
}
|
|
6146
|
+
|
|
6147
|
+
/** Remove a child UIObject from this object
|
|
6148
|
+
* @param {UIObject} child
|
|
6149
|
+
*/
|
|
6150
|
+
removeChild(child)
|
|
6151
|
+
{
|
|
6152
|
+
ASSERT(child.parent == this && this.children.includes(child));
|
|
6153
|
+
this.children.splice(this.children.indexOf(child), 1);
|
|
6154
|
+
child.parent = undefined;
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6157
|
+
/** Update the object, called automatically by plugin once each frame */
|
|
6158
|
+
update()
|
|
6159
|
+
{
|
|
6160
|
+
// track mouse input
|
|
6161
|
+
const mouseWasOver = this.mouseIsOver;
|
|
6162
|
+
const mouseDown = mouseIsDown(0);
|
|
6163
|
+
if (!mouseDown || isTouchDevice)
|
|
6164
|
+
{
|
|
6165
|
+
this.mouseIsOver = isOverlapping(this.pos, this.size, mousePosScreen);
|
|
6166
|
+
if (!mouseDown && isTouchDevice)
|
|
6167
|
+
this.mouseIsOver = false;
|
|
6168
|
+
if (this.mouseIsOver && !mouseWasOver)
|
|
6169
|
+
this.onEnter();
|
|
6170
|
+
if (!this.mouseIsOver && mouseWasOver)
|
|
6171
|
+
this.onLeave();
|
|
6172
|
+
}
|
|
6173
|
+
if (mouseWasPressed(0) && this.mouseIsOver)
|
|
6174
|
+
{
|
|
6175
|
+
this.mouseIsHeld = true;
|
|
6176
|
+
this.onPress();
|
|
6177
|
+
if (isTouchDevice)
|
|
6178
|
+
this.mouseIsOver = false;
|
|
6179
|
+
}
|
|
6180
|
+
else if (this.mouseIsHeld && !mouseDown)
|
|
6181
|
+
{
|
|
6182
|
+
this.mouseIsHeld = false;
|
|
6183
|
+
this.onRelease();
|
|
6184
|
+
}
|
|
6185
|
+
}
|
|
6186
|
+
|
|
6187
|
+
/** Render the object, called automatically by plugin once each frame */
|
|
6188
|
+
render()
|
|
6189
|
+
{
|
|
6190
|
+
if (this.size.x && this.size.y)
|
|
6191
|
+
uiSystem.drawRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor);
|
|
6192
|
+
}
|
|
6193
|
+
|
|
6194
|
+
/** Called when the mouse enters the object */
|
|
6195
|
+
onEnter() {}
|
|
6196
|
+
|
|
6197
|
+
/** Called when the mouse leaves the object */
|
|
6198
|
+
onLeave() {}
|
|
6199
|
+
|
|
6200
|
+
/** Called when the mouse is pressed while over the object */
|
|
6201
|
+
onPress() {}
|
|
6202
|
+
|
|
6203
|
+
/** Called when the mouse is released while over the object */
|
|
6204
|
+
onRelease() {}
|
|
6205
|
+
|
|
6206
|
+
/** Called when the state of this object changes */
|
|
6207
|
+
onChange() {}
|
|
6208
|
+
}
|
|
6209
|
+
|
|
6210
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6211
|
+
/**
|
|
6212
|
+
* UIText - A UI object that displays text
|
|
6213
|
+
* @extends UIObject
|
|
6214
|
+
*/
|
|
6215
|
+
class UIText extends UIObject
|
|
6216
|
+
{
|
|
6217
|
+
/** Create a UIText object
|
|
6218
|
+
* @param {Vector2} [pos]
|
|
6219
|
+
* @param {Vector2} [size]
|
|
6220
|
+
* @param {string} [text]
|
|
6221
|
+
* @param {string} [align]
|
|
6222
|
+
* @param {string} [font=uiSystem.defaultFont]
|
|
6223
|
+
*/
|
|
6224
|
+
constructor(pos, size, text='', align='center', font=uiSystem.defaultFont)
|
|
6225
|
+
{
|
|
6226
|
+
super(pos, size);
|
|
6227
|
+
|
|
6228
|
+
/** @property {string} */
|
|
6229
|
+
this.text = text;
|
|
6230
|
+
/** @property {string} */
|
|
6231
|
+
this.align = align;
|
|
6232
|
+
|
|
6233
|
+
this.font = font; // set font
|
|
6234
|
+
this.lineWidth = 0; // set text to not be outlined by default
|
|
6235
|
+
}
|
|
6236
|
+
render()
|
|
6237
|
+
{
|
|
6238
|
+
const textSize = vec2(this.size.x, this.textHeight || this.size.y);
|
|
6239
|
+
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
|
|
6243
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6244
|
+
/**
|
|
6245
|
+
* UITile - A UI object that displays a tile image
|
|
6246
|
+
* @extends UIObject
|
|
6247
|
+
*/
|
|
6248
|
+
class UITile extends UIObject
|
|
6249
|
+
{
|
|
6250
|
+
/** Create a UITile object
|
|
6251
|
+
* @param {Vector2} [pos]
|
|
6252
|
+
* @param {Vector2} [size]
|
|
6253
|
+
* @param {TileInfo} [tileInfo]
|
|
6254
|
+
* @param {Color} [color=WHITE]
|
|
6255
|
+
* @param {number} [angle]
|
|
6256
|
+
* @param {boolean} [mirror]
|
|
6257
|
+
*/
|
|
6258
|
+
constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false)
|
|
6259
|
+
{
|
|
6260
|
+
super(pos, size);
|
|
6261
|
+
|
|
6262
|
+
/** @property {TileInfo} - Tile image to use */
|
|
6263
|
+
this.tileInfo = tileInfo;
|
|
6264
|
+
/** @property {number} - Angle to rotate in radians */
|
|
6265
|
+
this.angle = angle;
|
|
6266
|
+
/** @property {boolean} - Should it be mirrored? */
|
|
6267
|
+
this.mirror = mirror;
|
|
6268
|
+
this.color = color;
|
|
6269
|
+
}
|
|
6270
|
+
render()
|
|
6271
|
+
{
|
|
6272
|
+
uiSystem.drawTile(this.pos, this.size, this.tileInfo, this.color, this.angle, this.mirror);
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
|
|
6276
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6277
|
+
/**
|
|
6278
|
+
* UIButton - A UI object that acts as a button
|
|
6279
|
+
* @extends UIObject
|
|
6280
|
+
*/
|
|
6281
|
+
class UIButton extends UIObject
|
|
6282
|
+
{
|
|
6283
|
+
/** Create a UIButton object
|
|
6284
|
+
* @param {Vector2} [pos]
|
|
6285
|
+
* @param {Vector2} [size]
|
|
6286
|
+
* @param {string} [text]
|
|
6287
|
+
* @param {Color} [color=uiSystem.defaultButtonColor]
|
|
6288
|
+
*/
|
|
6289
|
+
constructor(pos, size, text='', color=uiSystem.defaultButtonColor)
|
|
6290
|
+
{
|
|
6291
|
+
super(pos, size);
|
|
6292
|
+
|
|
6293
|
+
/** @property {string} */
|
|
6294
|
+
this.text = text;
|
|
6295
|
+
this.color = color;
|
|
6296
|
+
}
|
|
6297
|
+
render()
|
|
6298
|
+
{
|
|
6299
|
+
const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
|
|
6300
|
+
const color = this.mouseIsOver? this.hoverColor : this.color;
|
|
6301
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor);
|
|
6302
|
+
|
|
6303
|
+
const textScale = .8; // scale text to fit in button
|
|
6304
|
+
const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
|
|
6305
|
+
uiSystem.drawText(this.text, this.pos, textSize,
|
|
6306
|
+
this.textColor, 0, undefined, this.align, this.font);
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
|
|
6310
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6311
|
+
/**
|
|
6312
|
+
* UICheckbox - A UI object that acts as a checkbox
|
|
6313
|
+
* @extends UIObject
|
|
6314
|
+
*/
|
|
6315
|
+
class UICheckbox extends UIObject
|
|
6316
|
+
{
|
|
6317
|
+
/** Create a UICheckbox object
|
|
6318
|
+
* @param {Vector2} [pos]
|
|
6319
|
+
* @param {Vector2} [size]
|
|
6320
|
+
* @param {boolean} [checked]
|
|
6321
|
+
*/
|
|
6322
|
+
constructor(pos, size, checked=false)
|
|
6323
|
+
{
|
|
6324
|
+
super(pos, size);
|
|
6325
|
+
|
|
6326
|
+
/** @property {boolean} */
|
|
6327
|
+
this.checked = checked;
|
|
6328
|
+
}
|
|
6329
|
+
onPress()
|
|
6330
|
+
{
|
|
6331
|
+
this.checked = !this.checked;
|
|
6332
|
+
this.onChange();
|
|
6333
|
+
}
|
|
6334
|
+
render()
|
|
6335
|
+
{
|
|
6336
|
+
const color = this.mouseIsOver? this.hoverColor : this.color;
|
|
6337
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, this.lineColor);
|
|
6338
|
+
if (this.checked)
|
|
6339
|
+
{
|
|
6340
|
+
// draw an X if checked
|
|
6341
|
+
uiSystem.drawLine(this.pos.add(this.size.multiply(vec2(-.5,-.5))), this.pos.add(this.size.multiply(vec2(.5,.5))), this.lineWidth, this.lineColor);
|
|
6342
|
+
uiSystem.drawLine(this.pos.add(this.size.multiply(vec2(-.5,.5))), this.pos.add(this.size.multiply(vec2(.5,-.5))), this.lineWidth, this.lineColor);
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
}
|
|
6346
|
+
|
|
6347
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6348
|
+
/**
|
|
6349
|
+
* UIScrollbar - A UI object that acts as a scrollbar
|
|
6350
|
+
* @extends UIObject
|
|
6351
|
+
*/
|
|
6352
|
+
class UIScrollbar extends UIObject
|
|
6353
|
+
{
|
|
6354
|
+
/** Create a UIScrollbar object
|
|
6355
|
+
* @param {Vector2} [pos]
|
|
6356
|
+
* @param {Vector2} [size]
|
|
6357
|
+
* @param {number} [value]
|
|
6358
|
+
* @param {string} [text]
|
|
6359
|
+
* @param {Color} [color=uiSystem.defaultButtonColor]
|
|
6360
|
+
* @param {Color} [handleColor=WHITE]
|
|
6361
|
+
*/
|
|
6362
|
+
constructor(pos, size, value=.5, text='', color=uiSystem.defaultButtonColor, handleColor=WHITE)
|
|
6363
|
+
{
|
|
6364
|
+
super(pos, size);
|
|
6365
|
+
|
|
6366
|
+
/** @property {number} */
|
|
6367
|
+
this.value = value;
|
|
6368
|
+
/** @property {string} */
|
|
6369
|
+
this.text = text;
|
|
6370
|
+
this.color = color;
|
|
6371
|
+
this.handleColor = handleColor;
|
|
6372
|
+
}
|
|
6373
|
+
update()
|
|
6374
|
+
{
|
|
6375
|
+
super.update();
|
|
6376
|
+
if (this.mouseIsHeld)
|
|
6377
|
+
{
|
|
6378
|
+
const handleSize = vec2(this.size.y);
|
|
6379
|
+
const handleWidth = this.size.x - handleSize.x;
|
|
6380
|
+
const p1 = this.pos.x - handleWidth/2;
|
|
6381
|
+
const p2 = this.pos.x + handleWidth/2;
|
|
6382
|
+
const oldValue = this.value;
|
|
6383
|
+
this.value = percent(mousePosScreen.x, p1, p2);
|
|
6384
|
+
this.value == oldValue || this.onChange();
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6387
|
+
render()
|
|
6388
|
+
{
|
|
6389
|
+
const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
|
|
6390
|
+
const color = this.mouseIsOver? this.hoverColor : this.color;
|
|
6391
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor);
|
|
6392
|
+
|
|
6393
|
+
const handleSize = vec2(this.size.y);
|
|
6394
|
+
const handleWidth = this.size.x - handleSize.x;
|
|
6395
|
+
const p1 = this.pos.x - handleWidth/2;
|
|
6396
|
+
const p2 = this.pos.x + handleWidth/2;
|
|
6397
|
+
const handlePos = vec2(lerp(this.value, p1, p2), this.pos.y);
|
|
6398
|
+
const barColor = this.mouseIsHeld ? this.color : this.handleColor;
|
|
6399
|
+
uiSystem.drawRect(handlePos, handleSize, barColor, this.lineWidth, this.lineColor);
|
|
6400
|
+
|
|
6401
|
+
const textScale = .8; // scale text to fit in scrollbar
|
|
6402
|
+
const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
|
|
6403
|
+
uiSystem.drawText(this.text, this.pos, textSize,
|
|
6404
|
+
this.textColor, 0, undefined, this.align, this.font);
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6407
|
+
|
|
6408
|
+
/**
|
|
6409
|
+
* LittleJS Box2D Physics Plugin
|
|
6410
|
+
* - Box2dObject extends EngineObject with Box2D physics
|
|
6411
|
+
* - Call box2dInit() before engineInit() to enable
|
|
6412
|
+
* - You will also need to include box2d.wasm.js
|
|
6413
|
+
* - Uses a super fast web assembly port of Box2D
|
|
6414
|
+
* - More info: https://github.com/kripken/box2d.js
|
|
6415
|
+
* - Functions to create polygon, circle, and edge shapes
|
|
6416
|
+
* - Contact begin and end callbacks
|
|
6417
|
+
* - Wraps b2Vec2 type to/from Vector2
|
|
6418
|
+
* - Raycasting and querying
|
|
6419
|
+
* - Every type of joint
|
|
6420
|
+
* - Debug physics drawing
|
|
6421
|
+
* @namespace Box2D
|
|
6422
|
+
*/
|
|
6423
|
+
|
|
6424
|
+
/** Global Box2d Plugin object
|
|
6425
|
+
* @type {Box2dPlugin}
|
|
6426
|
+
* @memberof Box2D */
|
|
6427
|
+
let box2d;
|
|
6428
|
+
|
|
6429
|
+
/** Enable Box2D debug drawing
|
|
6430
|
+
* @type {boolean}
|
|
6431
|
+
* @default
|
|
6432
|
+
* @memberof Box2D */
|
|
6433
|
+
let box2dDebug = false;
|
|
6434
|
+
|
|
6435
|
+
/** Enable Box2D debug drawing
|
|
6436
|
+
* @param {boolean} enable
|
|
6437
|
+
* @memberof Box2D */
|
|
6438
|
+
function box2dSetDebug(enable) { box2dDebug = enable; }
|
|
6439
|
+
|
|
6440
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6441
|
+
/**
|
|
6442
|
+
* Box2D Object - extend with your own custom physics objects
|
|
6443
|
+
* - A LittleJS object with Box2D physics
|
|
6444
|
+
* - Each object has a Box2D body which can have multiple fixtures and joints
|
|
6445
|
+
* - Provides interface for Box2D body and fixture functions
|
|
6446
|
+
* @extends EngineObject
|
|
6447
|
+
*/
|
|
6448
|
+
class Box2dObject extends EngineObject
|
|
6449
|
+
{
|
|
6450
|
+
/** Create a LittleJS object with Box2d physics
|
|
6451
|
+
* @param {Vector2} [pos]
|
|
6452
|
+
* @param {Vector2} [size]
|
|
6453
|
+
* @param {TileInfo} [tileInfo]
|
|
6454
|
+
* @param {number} [angle]
|
|
6455
|
+
* @param {Color} [color]
|
|
6456
|
+
* @param {number} [bodyType]
|
|
6457
|
+
* @param {number} [renderOrder] */
|
|
6458
|
+
constructor(pos=vec2(), size, tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
|
|
6459
|
+
{
|
|
6460
|
+
super(pos, size, tileInfo, angle, color, renderOrder);
|
|
6461
|
+
|
|
6462
|
+
// create physics body
|
|
6463
|
+
const bodyDef = new box2d.instance.b2BodyDef();
|
|
6464
|
+
bodyDef.set_type(bodyType);
|
|
6465
|
+
bodyDef.set_position(box2d.vec2dTo(pos));
|
|
6466
|
+
bodyDef.set_angle(-angle);
|
|
6467
|
+
this.body = box2d.world.CreateBody(bodyDef);
|
|
6468
|
+
this.body.object = this;
|
|
6469
|
+
this.outlineColor = BLACK;
|
|
6470
|
+
}
|
|
6471
|
+
|
|
6472
|
+
/** Destroy this object and it's physics body */
|
|
6473
|
+
destroy()
|
|
6474
|
+
{
|
|
6475
|
+
// destroy physics body, fixtures, and joints
|
|
6476
|
+
this.body && box2d.world.DestroyBody(this.body);
|
|
6477
|
+
this.body = 0;
|
|
6478
|
+
super.destroy();
|
|
6479
|
+
}
|
|
6480
|
+
|
|
6481
|
+
/** Copy box2d update sim data */
|
|
6482
|
+
update()
|
|
6483
|
+
{
|
|
6484
|
+
// use box2d physics update
|
|
6485
|
+
this.pos = box2d.vec2From(this.body.GetPosition());
|
|
6486
|
+
this.angle = -this.body.GetAngle();
|
|
6487
|
+
}
|
|
6488
|
+
|
|
6489
|
+
/** Render the object, uses box2d drawing if no tile info exists */
|
|
6490
|
+
render()
|
|
6491
|
+
{
|
|
6492
|
+
// use default render or draw fixtures
|
|
6493
|
+
if (this.tileInfo)
|
|
6494
|
+
super.render();
|
|
6495
|
+
else
|
|
6496
|
+
this.drawFixtures(this.color, this.outlineColor, this.lineWidth, mainContext);
|
|
6497
|
+
}
|
|
6498
|
+
|
|
6499
|
+
/** Render debug info */
|
|
6500
|
+
renderDebugInfo()
|
|
6501
|
+
{
|
|
6502
|
+
const isAsleep = !this.getIsAwake();
|
|
6503
|
+
const isStatic = this.getBodyType() == box2d.bodyTypeStatic;
|
|
6504
|
+
const color = rgb(isAsleep?1:0, isAsleep?1:0, isStatic?1:0, .5);
|
|
6505
|
+
this.drawFixtures(color);
|
|
6506
|
+
}
|
|
6507
|
+
|
|
6508
|
+
/** Draws all this object's fixtures
|
|
6509
|
+
* @param {Color} [color]
|
|
6510
|
+
* @param {Color} [outlineColor]
|
|
6511
|
+
* @param {number} [lineWidth]
|
|
6512
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
6513
|
+
drawFixtures(color=WHITE, outlineColor, lineWidth=.1, context)
|
|
6514
|
+
{
|
|
6515
|
+
this.getFixtureList().forEach(fixture=>
|
|
6516
|
+
box2d.drawFixture(fixture, this.pos, this.angle, color, outlineColor, lineWidth, context));
|
|
6517
|
+
}
|
|
6518
|
+
|
|
6519
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6520
|
+
// physics contact callbacks
|
|
6521
|
+
|
|
6522
|
+
/** Called when a contact begins
|
|
6523
|
+
* @param {Box2dObject} otherObject */
|
|
6524
|
+
beginContact(otherObject) {}
|
|
6525
|
+
|
|
6526
|
+
/** Called when a contact ends
|
|
6527
|
+
* @param {Box2dObject} otherObject */
|
|
6528
|
+
endContact(otherObject) {}
|
|
6529
|
+
|
|
6530
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6531
|
+
// physics fixtures and shapes
|
|
6532
|
+
|
|
6533
|
+
/** Add a shape fixture to the body
|
|
6534
|
+
* @param {Object} shape
|
|
6535
|
+
* @param {number} [density]
|
|
6536
|
+
* @param {number} [friction]
|
|
6537
|
+
* @param {number} [restitution]
|
|
6538
|
+
* @param {boolean} [isSensor] */
|
|
6539
|
+
addShape(shape, density=1, friction=1, restitution=0, isSensor=false)
|
|
6540
|
+
{
|
|
6541
|
+
const fd = new box2d.instance.b2FixtureDef();
|
|
6542
|
+
fd.set_shape(shape);
|
|
6543
|
+
fd.set_density(density);
|
|
6544
|
+
fd.set_friction(friction);
|
|
6545
|
+
fd.set_restitution(restitution);
|
|
6546
|
+
fd.set_isSensor(isSensor);
|
|
6547
|
+
return this.body.CreateFixture(fd);
|
|
6548
|
+
}
|
|
6549
|
+
|
|
6550
|
+
/** Add a box shape to the body
|
|
6551
|
+
* @param {Vector2} [size]
|
|
6552
|
+
* @param {Vector2} [offset]
|
|
6553
|
+
* @param {number} [angle]
|
|
6554
|
+
* @param {number} [density]
|
|
6555
|
+
* @param {number} [friction]
|
|
6556
|
+
* @param {number} [restitution]
|
|
6557
|
+
* @param {boolean} [isSensor] */
|
|
6558
|
+
addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
|
|
6559
|
+
{
|
|
6560
|
+
const shape = new box2d.instance.b2PolygonShape();
|
|
6561
|
+
shape.SetAsBox(size.x/2, size.y/2, box2d.vec2dTo(offset), angle);
|
|
6562
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
6563
|
+
}
|
|
6564
|
+
|
|
6565
|
+
/** Add a polygon shape to the body
|
|
6566
|
+
* @param {Array<Vector2>} points
|
|
6567
|
+
* @param {number} [density]
|
|
6568
|
+
* @param {number} [friction]
|
|
6569
|
+
* @param {number} [restitution]
|
|
6570
|
+
* @param {boolean} [isSensor] */
|
|
6571
|
+
addPoly(points, density, friction, restitution, isSensor)
|
|
6572
|
+
{
|
|
6573
|
+
function box2dCreatePolygonShape(points)
|
|
6574
|
+
{
|
|
6575
|
+
function box2dCreatePointList(points)
|
|
6576
|
+
{
|
|
6577
|
+
const buffer = box2d.instance._malloc(points.length * 8);
|
|
6578
|
+
for (let i=0, offset=0; i<points.length; ++i)
|
|
6579
|
+
{
|
|
6580
|
+
box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].x;
|
|
6581
|
+
offset += 4;
|
|
6582
|
+
box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].y;
|
|
6583
|
+
offset += 4;
|
|
6584
|
+
}
|
|
6585
|
+
return box2d.instance.wrapPointer(buffer, box2d.instance.b2Vec2);
|
|
6586
|
+
}
|
|
6587
|
+
|
|
6588
|
+
ASSERT(3 <= points.length && points.length <= 8);
|
|
6589
|
+
const shape = new box2d.instance.b2PolygonShape();
|
|
6590
|
+
const box2dPoints = box2dCreatePointList(points);
|
|
6591
|
+
shape.Set(box2dPoints, points.length);
|
|
6592
|
+
return shape;
|
|
6593
|
+
}
|
|
6594
|
+
|
|
6595
|
+
const shape = box2dCreatePolygonShape(points);
|
|
6596
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
6597
|
+
}
|
|
6598
|
+
|
|
6599
|
+
/** Add a regular polygon shape to the body
|
|
6600
|
+
* @param {number} [diameter]
|
|
6601
|
+
* @param {number} [sides]
|
|
6602
|
+
* @param {number} [density]
|
|
6603
|
+
* @param {number} [friction]
|
|
6604
|
+
* @param {number} [restitution]
|
|
6605
|
+
* @param {boolean} [isSensor] */
|
|
6606
|
+
addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
|
|
6607
|
+
{
|
|
6608
|
+
const points = [];
|
|
6609
|
+
const radius = diameter/2;
|
|
6610
|
+
for (let i=sides; i--;)
|
|
6611
|
+
points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2));
|
|
6612
|
+
return this.addPoly(points, density, friction, restitution, isSensor);
|
|
6613
|
+
}
|
|
6614
|
+
|
|
6615
|
+
/** Add a random polygon shape to the body
|
|
6616
|
+
* @param {number} [diameter]
|
|
6617
|
+
* @param {number} [density]
|
|
6618
|
+
* @param {number} [friction]
|
|
6619
|
+
* @param {number} [restitution]
|
|
6620
|
+
* @param {boolean} [isSensor] */
|
|
6621
|
+
addRandomPoly(diameter=1, density, friction, restitution, isSensor)
|
|
6622
|
+
{
|
|
6623
|
+
const sides = randInt(3, 9);
|
|
6624
|
+
const points = [];
|
|
6625
|
+
const radius = diameter/2;
|
|
6626
|
+
for (let i=sides; i--;)
|
|
6627
|
+
points.push(vec2(rand(radius/2,radius*1.5),0).rotate(i/sides*PI*2));
|
|
6628
|
+
return this.addPoly(points, density, friction, restitution, isSensor);
|
|
6629
|
+
}
|
|
6630
|
+
|
|
6631
|
+
/** Add a circle shape to the body
|
|
6632
|
+
* @param {number} [diameter]
|
|
6633
|
+
* @param {Vector2} [offset]
|
|
6634
|
+
* @param {number} [density]
|
|
6635
|
+
* @param {number} [friction]
|
|
6636
|
+
* @param {number} [restitution]
|
|
6637
|
+
* @param {boolean} [isSensor] */
|
|
6638
|
+
addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
|
|
6639
|
+
{
|
|
6640
|
+
const shape = new box2d.instance.b2CircleShape();
|
|
6641
|
+
shape.set_m_p(box2d.vec2dTo(offset));
|
|
6642
|
+
shape.set_m_radius(diameter/2);
|
|
6643
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
6644
|
+
}
|
|
6645
|
+
|
|
6646
|
+
/** Add an edge shape to the body
|
|
6647
|
+
* @param {Vector2} point1
|
|
6648
|
+
* @param {Vector2} point2
|
|
6649
|
+
* @param {number} [density]
|
|
6650
|
+
* @param {number} [friction]
|
|
6651
|
+
* @param {number} [restitution]
|
|
6652
|
+
* @param {boolean} [isSensor] */
|
|
6653
|
+
addEdge(point1, point2, density, friction, restitution, isSensor)
|
|
6654
|
+
{
|
|
6655
|
+
const shape = new box2d.instance.b2EdgeShape();
|
|
6656
|
+
shape.Set(box2d.vec2dTo(point1), box2d.vec2dTo(point2));
|
|
6657
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
6658
|
+
}
|
|
6659
|
+
|
|
6660
|
+
/** Add an edge loop to the body, an edge loop connects the end points
|
|
6661
|
+
* @param {Array<Vector2>} points
|
|
6662
|
+
* @param {number} [density]
|
|
6663
|
+
* @param {number} [friction]
|
|
6664
|
+
* @param {number} [restitution]
|
|
6665
|
+
* @param {boolean} [isSensor] */
|
|
6666
|
+
addEdgeLoop(points, density, friction, restitution, isSensor)
|
|
6667
|
+
{
|
|
6668
|
+
const fixtures = [];
|
|
6669
|
+
const getPoint = i=> points[mod(i,points.length)];
|
|
6670
|
+
for (let i=0; i<points.length; ++i)
|
|
6671
|
+
{
|
|
6672
|
+
const shape = new box2d.instance.b2EdgeShape();
|
|
6673
|
+
shape.set_m_vertex0(box2d.vec2dTo(getPoint(i-1)));
|
|
6674
|
+
shape.set_m_vertex1(box2d.vec2dTo(getPoint(i+0)));
|
|
6675
|
+
shape.set_m_vertex2(box2d.vec2dTo(getPoint(i+1)));
|
|
6676
|
+
shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
|
|
6677
|
+
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
6678
|
+
fixtures.push(f);
|
|
6679
|
+
}
|
|
6680
|
+
return fixtures;
|
|
6681
|
+
}
|
|
6682
|
+
|
|
6683
|
+
/** Add an edge list to the body
|
|
6684
|
+
* @param {Array<Vector2>} points
|
|
6685
|
+
* @param {number} [density]
|
|
6686
|
+
* @param {number} [friction]
|
|
6687
|
+
* @param {number} [restitution]
|
|
6688
|
+
* @param {boolean} [isSensor] */
|
|
6689
|
+
addEdgeList(points, density, friction, restitution, isSensor)
|
|
6690
|
+
{
|
|
6691
|
+
const fixtures = [];
|
|
6692
|
+
for (let i=0; i<points.length-1; ++i)
|
|
6693
|
+
{
|
|
6694
|
+
const shape = new box2d.instance.b2EdgeShape();
|
|
6695
|
+
points[i-1] && shape.set_m_vertex0(box2d.vec2dTo(points[i-1]));
|
|
6696
|
+
points[i+0] && shape.set_m_vertex1(box2d.vec2dTo(points[i+0]));
|
|
6697
|
+
points[i+1] && shape.set_m_vertex2(box2d.vec2dTo(points[i+1]));
|
|
6698
|
+
points[i+2] && shape.set_m_vertex3(box2d.vec2dTo(points[i+2]));
|
|
6699
|
+
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
6700
|
+
fixtures.push(f);
|
|
6701
|
+
}
|
|
6702
|
+
return fixtures;
|
|
6703
|
+
}
|
|
6704
|
+
|
|
6705
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6706
|
+
// physics get functions
|
|
6707
|
+
|
|
6708
|
+
/** Gets the center of mass
|
|
6709
|
+
* @return {Vector2} */
|
|
6710
|
+
getCenterOfMass() { return box2d.vec2From(this.body.GetWorldCenter()); }
|
|
6711
|
+
|
|
6712
|
+
/** Gets the linear velocity
|
|
6713
|
+
* @return {Vector2} */
|
|
6714
|
+
getLinearVelocity() { return box2d.vec2From(this.body.GetLinearVelocity()); }
|
|
6715
|
+
|
|
6716
|
+
/** Gets the angular velocity
|
|
6717
|
+
* @return {Vector2} */
|
|
6718
|
+
getAngularVelocity() { return this.body.GetAngularVelocity(); }
|
|
6719
|
+
|
|
6720
|
+
/** Gets the mass
|
|
6721
|
+
* @return {number} */
|
|
6722
|
+
getMass() { return this.body.GetMass(); }
|
|
6723
|
+
|
|
6724
|
+
/** Gets the rotational inertia
|
|
6725
|
+
* @return {number} */
|
|
6726
|
+
getInertia() { return this.body.GetInertia(); }
|
|
6727
|
+
|
|
6728
|
+
/** Check if this object is awake
|
|
6729
|
+
* @return {boolean} */
|
|
6730
|
+
getIsAwake() { return this.body.IsAwake(); }
|
|
6731
|
+
|
|
6732
|
+
/** Gets the physics body type
|
|
6733
|
+
* @return {number} */
|
|
6734
|
+
getBodyType() { return this.body.GetType(); }
|
|
6735
|
+
|
|
6736
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6737
|
+
// physics set functions
|
|
6738
|
+
|
|
6739
|
+
/** Sets the position and angle
|
|
6740
|
+
* @param {Vector2} pos
|
|
6741
|
+
* @param {number} angle */
|
|
6742
|
+
setTransform(pos, angle)
|
|
6743
|
+
{
|
|
6744
|
+
this.pos = pos;
|
|
6745
|
+
this.angle = angle;
|
|
6746
|
+
this.body.SetTransform(box2d.vec2dTo(pos), angle);
|
|
6747
|
+
}
|
|
6748
|
+
|
|
6749
|
+
/** Sets the position
|
|
6750
|
+
* @param {Vector2} pos */
|
|
6751
|
+
setPosition(pos) { this.setTransform(pos, this.body.GetAngle()); }
|
|
6752
|
+
|
|
6753
|
+
/** Sets the angle
|
|
6754
|
+
* @param {number} angle */
|
|
6755
|
+
setAngle(angle) { this.setTransform(box2d.vec2From(this.body.GetPosition()), -angle); }
|
|
6756
|
+
|
|
6757
|
+
/** Sets the linear velocity
|
|
6758
|
+
* @param {Vector2} velocity */
|
|
6759
|
+
setLinearVelocity(velocity) { this.body.SetLinearVelocity(box2d.vec2dTo(velocity)); }
|
|
6760
|
+
|
|
6761
|
+
/** Sets the angular velocity
|
|
6762
|
+
* @param {number} angularVelocity */
|
|
6763
|
+
setAngularVelocity(angularVelocity) { this.body.SetAngularVelocity(angularVelocity); }
|
|
6764
|
+
|
|
6765
|
+
/** Sets the linear damping
|
|
6766
|
+
* @param {number} damping */
|
|
6767
|
+
setLinearDamping(damping) { this.body.SetLinearDamping(damping); }
|
|
6768
|
+
|
|
6769
|
+
/** Sets the angular damping
|
|
6770
|
+
* @param {number} damping */
|
|
6771
|
+
setAngularDamping(damping) { this.body.SetAngularDamping(damping); }
|
|
6772
|
+
|
|
6773
|
+
/** Sets the gravity scale
|
|
6774
|
+
* @param {number} [scale] */
|
|
6775
|
+
setGravityScale(scale=1) { this.body.SetGravityScale(this.gravityScale = scale); }
|
|
6776
|
+
|
|
6777
|
+
/** Should this body be treated like a bullet for continuous collision detection?
|
|
6778
|
+
* @param {boolean} [isBullet] */
|
|
6779
|
+
setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
|
|
6780
|
+
|
|
6781
|
+
/** Set the sleep state of the body
|
|
6782
|
+
* @param {boolean} [isAwake] */
|
|
6783
|
+
setAwake(isAwake=true) { this.body.SetAwake(isAwake); }
|
|
6784
|
+
|
|
6785
|
+
/** Set the physics body type
|
|
6786
|
+
* @param {number} type */
|
|
6787
|
+
setBodyType(type) { this.body.SetType(type); }
|
|
6788
|
+
|
|
6789
|
+
/** Set whether the body is allowed to sleep
|
|
6790
|
+
* @param {boolean} [isAllowed] */
|
|
6791
|
+
setSleepingAllowed(isAllowed=true) { this.body.SetSleepingAllowed(isAllowed); }
|
|
6792
|
+
|
|
6793
|
+
/** Set whether the body can rotate
|
|
6794
|
+
* @param {boolean} [isFixed] */
|
|
6795
|
+
setFixedRotation(isFixed=true) { this.body.SetFixedRotation(isFixed); }
|
|
6796
|
+
|
|
6797
|
+
/** Set the center of mass of the body
|
|
6798
|
+
* @param {Vector2} center */
|
|
6799
|
+
setCenterOfMass(center) { this.setMassData(center) }
|
|
6800
|
+
|
|
6801
|
+
/** Set the mass of the body
|
|
6802
|
+
* @param {number} mass */
|
|
6803
|
+
setMass(mass) { this.setMassData(undefined, mass) }
|
|
6804
|
+
|
|
6805
|
+
/** Set the moment of inertia of the body
|
|
6806
|
+
* @param {number} momentOfInertia */
|
|
6807
|
+
setMomentOfInertia(momentOfInertia) { this.setMassData(undefined, undefined, momentOfInertia) }
|
|
6808
|
+
|
|
6809
|
+
/** Reset the mass, center of mass, and moment */
|
|
6810
|
+
resetMassData() { this.body.ResetMassData(); }
|
|
6811
|
+
|
|
6812
|
+
/** Set the mass data of the body
|
|
6813
|
+
* @param {Vector2} [localCenter]
|
|
6814
|
+
* @param {number} [mass]
|
|
6815
|
+
* @param {number} [momentOfInertia] */
|
|
6816
|
+
setMassData(localCenter, mass, momentOfInertia)
|
|
6817
|
+
{
|
|
6818
|
+
const data = new box2d.instance.b2MassData();
|
|
6819
|
+
this.body.GetMassData(data);
|
|
6820
|
+
localCenter && data.set_center(box2d.vec2dTo(localCenter));
|
|
6821
|
+
mass && data.set_mass(mass);
|
|
6822
|
+
momentOfInertia && data.set_I(momentOfInertia);
|
|
6823
|
+
this.body.SetMassData(data);
|
|
6824
|
+
}
|
|
6825
|
+
|
|
6826
|
+
/** Set the collision filter data for this body
|
|
6827
|
+
* @param {number} [categoryBits]
|
|
6828
|
+
* @param {number} [ignoreCategoryBits]
|
|
6829
|
+
* @param {number} [groupIndex] */
|
|
6830
|
+
setFilterData(categoryBits=0, ignoreCategoryBits=0, groupIndex=0)
|
|
6831
|
+
{
|
|
6832
|
+
this.getFixtureList().forEach(fixture=>
|
|
6833
|
+
{
|
|
6834
|
+
const filter = fixture.GetFilterData();
|
|
6835
|
+
filter.set_categoryBits(categoryBits);
|
|
6836
|
+
filter.set_maskBits(0xffff & ~ignoreCategoryBits);
|
|
6837
|
+
filter.set_groupIndex(groupIndex);
|
|
6838
|
+
});
|
|
6839
|
+
}
|
|
6840
|
+
|
|
6841
|
+
/** Set if this body is a sensor
|
|
6842
|
+
* @param {boolean} [isSensor] */
|
|
6843
|
+
setSensor(isSensor=true)
|
|
6844
|
+
{ this.getFixtureList().forEach(f=>f.SetSensor(isSensor)); }
|
|
6845
|
+
|
|
6846
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6847
|
+
// physics force and torque functions
|
|
6848
|
+
|
|
6849
|
+
/** Apply force to this object
|
|
6850
|
+
* @param {Vector2} force
|
|
6851
|
+
* @param {Vector2} [pos] */
|
|
6852
|
+
applyForce(force, pos)
|
|
6853
|
+
{
|
|
6854
|
+
pos ||= this.getCenterOfMass();
|
|
6855
|
+
this.setAwake();
|
|
6856
|
+
this.body.ApplyForce(box2d.vec2dTo(force), box2d.vec2dTo(pos));
|
|
6857
|
+
}
|
|
6858
|
+
|
|
6859
|
+
/** Apply acceleration to this object
|
|
6860
|
+
* @param {Vector2} acceleration
|
|
6861
|
+
* @param {Vector2} [pos] */
|
|
6862
|
+
applyAcceleration(acceleration, pos)
|
|
6863
|
+
{
|
|
6864
|
+
pos ||= this.getCenterOfMass();
|
|
6865
|
+
this.setAwake();
|
|
6866
|
+
this.body.ApplyLinearImpulse(box2d.vec2dTo(acceleration), box2d.vec2dTo(pos));
|
|
6867
|
+
}
|
|
6868
|
+
|
|
6869
|
+
/** Apply torque to this object
|
|
6870
|
+
* @param {number} torque */
|
|
6871
|
+
applyTorque(torque)
|
|
6872
|
+
{
|
|
6873
|
+
this.setAwake();
|
|
6874
|
+
this.body.ApplyTorque(torque);
|
|
6875
|
+
}
|
|
6876
|
+
|
|
6877
|
+
/** Apply angular acceleration to this object
|
|
6878
|
+
* @param {number} acceleration */
|
|
6879
|
+
applyAngularAcceleration(acceleration)
|
|
6880
|
+
{
|
|
6881
|
+
this.setAwake();
|
|
6882
|
+
this.body.ApplyAngularImpulse(acceleration);
|
|
6883
|
+
}
|
|
6884
|
+
|
|
6885
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6886
|
+
// lists of fixtures and joints
|
|
6887
|
+
|
|
6888
|
+
/** Check if this object has any fixtures
|
|
6889
|
+
* @return {boolean} */
|
|
6890
|
+
hasFixtures() { return !box2d.isNull(this.body.GetFixtureList()); }
|
|
6891
|
+
|
|
6892
|
+
/** Get list of fixtures for this object
|
|
6893
|
+
* @return {Array<Object>} */
|
|
6894
|
+
getFixtureList()
|
|
6895
|
+
{
|
|
6896
|
+
const fixtures = [];
|
|
6897
|
+
for (let fixture=this.body.GetFixtureList(); !box2d.isNull(fixture); )
|
|
6898
|
+
{
|
|
6899
|
+
fixtures.push(fixture);
|
|
6900
|
+
fixture = fixture.GetNext();
|
|
6901
|
+
}
|
|
6902
|
+
return fixtures;
|
|
6903
|
+
}
|
|
6904
|
+
|
|
6905
|
+
/** Check if this object has any joints
|
|
6906
|
+
* @return {boolean} */
|
|
6907
|
+
hasJoints() { return !box2d.isNull(this.body.GetJointList()); }
|
|
6908
|
+
|
|
6909
|
+
/** Get list of joints for this object
|
|
6910
|
+
* @return {Array<Object>} */
|
|
6911
|
+
getJointList()
|
|
6912
|
+
{
|
|
6913
|
+
const joints = [];
|
|
6914
|
+
for (let joint=this.body.GetJointList(); !box2d.isNull(joint); )
|
|
6915
|
+
{
|
|
6916
|
+
joints.push(joint);
|
|
6917
|
+
joint = joint.get_next();
|
|
6918
|
+
}
|
|
6919
|
+
return joints;
|
|
6920
|
+
}
|
|
6921
|
+
}
|
|
6922
|
+
|
|
6923
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6924
|
+
/**
|
|
6925
|
+
* Box2D Raycast Result
|
|
6926
|
+
* - Holds results from a box2d raycast queries
|
|
6927
|
+
* - Automatically created by box2d raycast functions
|
|
6928
|
+
*/
|
|
6929
|
+
class Box2dRaycastResult
|
|
6930
|
+
{
|
|
6931
|
+
/** Create a raycast result
|
|
6932
|
+
* @param {Object} fixture
|
|
6933
|
+
* @param {Vector2} point
|
|
6934
|
+
* @param {Vector2} normal
|
|
6935
|
+
* @param {number} fraction */
|
|
6936
|
+
constructor(fixture, point, normal, fraction)
|
|
6937
|
+
{
|
|
6938
|
+
/** @property {Box2dObject} - The box2d object */
|
|
6939
|
+
this.object = fixture.GetBody().object;
|
|
6940
|
+
/** @property {Object} - The fixture that was hit */
|
|
6941
|
+
this.fixture = fixture;
|
|
6942
|
+
/** @property {Vector2} - The hit point */
|
|
6943
|
+
this.point = point;
|
|
6944
|
+
/** @property {Vector2} - The hit normal */
|
|
6945
|
+
this.normal = normal;
|
|
6946
|
+
/** @property {number} - Distance fraction at the point of intersection */
|
|
6947
|
+
this.fraction = fraction;
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
|
|
6951
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6952
|
+
/**
|
|
6953
|
+
* Box2D Joint
|
|
6954
|
+
* - Base class for Box2D joints
|
|
6955
|
+
* - A joint is used to connect objects together
|
|
6956
|
+
*/
|
|
6957
|
+
class Box2dJoint
|
|
6958
|
+
{
|
|
6959
|
+
/** Create a box2d joint, the base class is not intended to be used directly
|
|
6960
|
+
* @param {Object} jointDef */
|
|
6961
|
+
constructor(jointDef)
|
|
6962
|
+
{
|
|
6963
|
+
this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
|
|
6964
|
+
}
|
|
6965
|
+
|
|
6966
|
+
/** Destroy this joint */
|
|
6967
|
+
destroy() { box2d.world.DestroyJoint(this.box2dJoint); this.box2dJoint = 0; }
|
|
6968
|
+
|
|
6969
|
+
/** Get the first object attached to this joint
|
|
6970
|
+
* @return {Box2dObject} */
|
|
6971
|
+
getObjectA() { return this.box2dJoint.GetBodyA().object; }
|
|
6972
|
+
|
|
6973
|
+
/** Get the second object attached to this joint
|
|
6974
|
+
* @return {Box2dObject} */
|
|
6975
|
+
getObjectB() { return this.box2dJoint.GetBodyB().object; }
|
|
6976
|
+
|
|
6977
|
+
/** Get the first anchor for this joint in world coordinates
|
|
6978
|
+
* @return {Vector2} */
|
|
6979
|
+
getAnchorA() { return box2d.vec2From(this.box2dJoint.GetAnchorA());}
|
|
6980
|
+
|
|
6981
|
+
/** Get the second anchor for this joint in world coordinates
|
|
6982
|
+
* @return {Vector2} */
|
|
6983
|
+
getAnchorB() { return box2d.vec2From(this.box2dJoint.GetAnchorB());}
|
|
6984
|
+
|
|
6985
|
+
/** Get the reaction force on bodyB at the joint anchor given a time step
|
|
6986
|
+
* @param {number} time
|
|
6987
|
+
* @return {Vector2} */
|
|
6988
|
+
getReactionForce(time) { return box2d.vec2From(this.box2dJoint.GetReactionForce(1/time));}
|
|
6989
|
+
|
|
6990
|
+
/** Get the reaction torque on bodyB in N*m given a time step
|
|
6991
|
+
* @param {number} time
|
|
6992
|
+
* @return {number} */
|
|
6993
|
+
getReactionTorque(time) { return this.box2dJoint.GetReactionTorque(1/time);}
|
|
6994
|
+
|
|
6995
|
+
/** Check if the connected bodies should collide
|
|
6996
|
+
* @return {boolean} */
|
|
6997
|
+
getCollideConnected() { return this.box2dJoint.getCollideConnected();}
|
|
6998
|
+
|
|
6999
|
+
/** Check if either connected body is active
|
|
7000
|
+
* @return {boolean} */
|
|
7001
|
+
isActive() { return this.box2dJoint.IsActive();}
|
|
7002
|
+
}
|
|
7003
|
+
|
|
7004
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7005
|
+
/**
|
|
7006
|
+
* Box2D Target Joint, also known as a mouse joint
|
|
7007
|
+
* - Used to make a point on a object track a specific world point target
|
|
7008
|
+
* - This a soft constraint with a max force
|
|
7009
|
+
* - This allows the constraint to stretch and without applying huge forces
|
|
7010
|
+
* @extends Box2dJoint
|
|
7011
|
+
*/
|
|
7012
|
+
class Box2dTargetJoint extends Box2dJoint
|
|
7013
|
+
{
|
|
7014
|
+
/** Create a target joint
|
|
7015
|
+
* @param {Box2dObject} object
|
|
7016
|
+
* @param {Box2dObject} fixedObject
|
|
7017
|
+
* @param {Vector2} worldPos */
|
|
7018
|
+
constructor(object, fixedObject, worldPos)
|
|
7019
|
+
{
|
|
7020
|
+
object.setAwake();
|
|
7021
|
+
const jointDef = new box2d.instance.b2MouseJointDef();
|
|
7022
|
+
jointDef.set_bodyA(fixedObject.body);
|
|
7023
|
+
jointDef.set_bodyB(object.body);
|
|
7024
|
+
jointDef.set_target(box2d.vec2dTo(worldPos));
|
|
7025
|
+
jointDef.set_maxForce(2e3 * object.getMass());
|
|
7026
|
+
super(jointDef);
|
|
7027
|
+
}
|
|
7028
|
+
|
|
7029
|
+
/** Set the target point in world coordinates
|
|
7030
|
+
* @param {Vector2} pos */
|
|
7031
|
+
setTarget(pos) { this.box2dJoint.SetTarget(box2d.vec2dTo(pos)); }
|
|
7032
|
+
|
|
7033
|
+
/** Get the target point in world coordinates
|
|
7034
|
+
* @return {Vector2} */
|
|
7035
|
+
getTarget(){ return box2d.vec2From(this.box2dJoint.GetTarget()); }
|
|
7036
|
+
|
|
7037
|
+
/** Sets the maximum force in Newtons
|
|
7038
|
+
* @param {number} force */
|
|
7039
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
|
|
7040
|
+
|
|
7041
|
+
/** Gets the maximum force in Newtons
|
|
7042
|
+
* @return {number} */
|
|
7043
|
+
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
|
|
7044
|
+
|
|
7045
|
+
/** Sets the joint frequency in Hertz
|
|
7046
|
+
* @param {number} hz */
|
|
7047
|
+
setFrequency(hz) { this.box2dJoint.SetFrequency(hz); }
|
|
7048
|
+
|
|
7049
|
+
/** Gets the joint frequency in Hertz
|
|
7050
|
+
* @return {number} */
|
|
7051
|
+
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
7052
|
+
}
|
|
7053
|
+
|
|
7054
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7055
|
+
/**
|
|
7056
|
+
* Box2D Distance Joint
|
|
7057
|
+
* - Constrains two points on two objects to remain at a fixed distance
|
|
7058
|
+
* - You can view this as a massless, rigid rod
|
|
7059
|
+
* @extends Box2dJoint
|
|
7060
|
+
*/
|
|
7061
|
+
class Box2dDistanceJoint extends Box2dJoint
|
|
7062
|
+
{
|
|
7063
|
+
/** Create a distance joint
|
|
7064
|
+
* @param {Box2dObject} objectA
|
|
7065
|
+
* @param {Box2dObject} objectB
|
|
7066
|
+
* @param {Vector2} anchorA
|
|
7067
|
+
* @param {Vector2} anchorB
|
|
7068
|
+
* @param {boolean} [collide] */
|
|
7069
|
+
constructor(objectA, objectB, anchorA, anchorB, collide=false)
|
|
7070
|
+
{
|
|
7071
|
+
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
|
|
7072
|
+
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7073
|
+
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
7074
|
+
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
7075
|
+
const jointDef = new box2d.instance.b2DistanceJointDef();
|
|
7076
|
+
jointDef.set_bodyA(objectA.body);
|
|
7077
|
+
jointDef.set_bodyB(objectB.body);
|
|
7078
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7079
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7080
|
+
jointDef.set_length(anchorA.distance(anchorB));
|
|
7081
|
+
jointDef.set_collideConnected(collide);
|
|
7082
|
+
super(jointDef);
|
|
7083
|
+
}
|
|
7084
|
+
|
|
7085
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7086
|
+
* @return {Vector2} */
|
|
7087
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7088
|
+
|
|
7089
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7090
|
+
* @return {Vector2} */
|
|
7091
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7092
|
+
|
|
7093
|
+
/** Set the length of the joint
|
|
7094
|
+
* @param {number} length */
|
|
7095
|
+
setLength(length) { this.box2dJoint.SetLength(length); }
|
|
7096
|
+
|
|
7097
|
+
/** Get the length of the joint
|
|
7098
|
+
* @return {number} */
|
|
7099
|
+
getLength() { return this.box2dJoint.GetLength(); }
|
|
7100
|
+
|
|
7101
|
+
/** Set the frequency in Hertz
|
|
7102
|
+
* @param {number} hz */
|
|
7103
|
+
setFrequency(hz) { this.box2dJoint.SetFrequency(hz); }
|
|
7104
|
+
|
|
7105
|
+
/** Get the frequency in Hertz
|
|
7106
|
+
* @return {number} */
|
|
7107
|
+
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
7108
|
+
|
|
7109
|
+
/** Set the damping ratio
|
|
7110
|
+
* @param {number} ratio */
|
|
7111
|
+
setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); }
|
|
7112
|
+
|
|
7113
|
+
/** Get the damping ratio
|
|
7114
|
+
* @return {number} */
|
|
7115
|
+
getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
|
|
7116
|
+
}
|
|
7117
|
+
|
|
7118
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7119
|
+
/**
|
|
7120
|
+
* Box2D Pin Joint
|
|
7121
|
+
* - Pins two objects together at a point
|
|
7122
|
+
* @extends Box2dDistanceJoint
|
|
7123
|
+
*/
|
|
7124
|
+
class Box2dPinJoint extends Box2dDistanceJoint
|
|
7125
|
+
{
|
|
7126
|
+
/** Create a pin joint
|
|
7127
|
+
* @param {Box2dObject} objectA
|
|
7128
|
+
* @param {Box2dObject} objectB
|
|
7129
|
+
* @param {Vector2} [pos]
|
|
7130
|
+
* @param {boolean} [collide] */
|
|
7131
|
+
constructor(objectA, objectB, pos=objectA.pos, collide=false)
|
|
7132
|
+
{
|
|
7133
|
+
super(objectA, objectB, undefined, pos, collide);
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
|
|
7137
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7138
|
+
/**
|
|
7139
|
+
* Box2D Rope Joint
|
|
7140
|
+
* - Enforces a maximum distance between two points on two objects
|
|
7141
|
+
* @extends Box2dJoint
|
|
7142
|
+
*/
|
|
7143
|
+
class Box2dRopeJoint extends Box2dJoint
|
|
7144
|
+
{
|
|
7145
|
+
/** Create a rope joint
|
|
7146
|
+
* @param {Box2dObject} objectA
|
|
7147
|
+
* @param {Box2dObject} objectB
|
|
7148
|
+
* @param {Vector2} anchorA
|
|
7149
|
+
* @param {Vector2} anchorB
|
|
7150
|
+
* @param {number} extraLength
|
|
7151
|
+
* @param {boolean} [collide] */
|
|
7152
|
+
constructor(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false)
|
|
7153
|
+
{
|
|
7154
|
+
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
|
|
7155
|
+
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7156
|
+
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
7157
|
+
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
7158
|
+
const jointDef = new box2d.instance.b2RopeJointDef();
|
|
7159
|
+
jointDef.set_bodyA(objectA.body);
|
|
7160
|
+
jointDef.set_bodyB(objectB.body);
|
|
7161
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7162
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7163
|
+
jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength);
|
|
7164
|
+
jointDef.set_collideConnected(collide);
|
|
7165
|
+
super(jointDef);
|
|
7166
|
+
}
|
|
7167
|
+
|
|
7168
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7169
|
+
* @return {Vector2} */
|
|
7170
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7171
|
+
|
|
7172
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7173
|
+
* @return {Vector2} */
|
|
7174
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7175
|
+
|
|
7176
|
+
/** Set the max length of the joint
|
|
7177
|
+
* @param {number} length */
|
|
7178
|
+
setMaxLength(length) { this.box2dJoint.SetMaxLength(length); }
|
|
7179
|
+
|
|
7180
|
+
/** Get the max length of the joint
|
|
7181
|
+
* @return {number} */
|
|
7182
|
+
getMaxLength() { return this.box2dJoint.GetMaxLength(); }
|
|
7183
|
+
}
|
|
7184
|
+
|
|
7185
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7186
|
+
/**
|
|
7187
|
+
* Box2D Revolute Joint
|
|
7188
|
+
* - Constrains two objects to share a point while they are free to rotate around the point
|
|
7189
|
+
* - The relative rotation about the shared point is the joint angle
|
|
7190
|
+
* - You can limit the relative rotation with a joint limit
|
|
7191
|
+
* - You can use a motor to drive the relative rotation about the shared point
|
|
7192
|
+
* - A maximum motor torque is provided so that infinite forces are not generated
|
|
7193
|
+
* @extends Box2dJoint
|
|
7194
|
+
*/
|
|
7195
|
+
class Box2dRevoluteJoint extends Box2dJoint
|
|
7196
|
+
{
|
|
7197
|
+
/** Create a revolute joint
|
|
7198
|
+
* @param {Box2dObject} objectA
|
|
7199
|
+
* @param {Box2dObject} objectB
|
|
7200
|
+
* @param {Vector2} anchor
|
|
7201
|
+
* @param {boolean} [collide] */
|
|
7202
|
+
constructor(objectA, objectB, anchor, collide=false)
|
|
7203
|
+
{
|
|
7204
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7205
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
7206
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
7207
|
+
const jointDef = new box2d.instance.b2RevoluteJointDef();
|
|
7208
|
+
jointDef.set_bodyA(objectA.body);
|
|
7209
|
+
jointDef.set_bodyB(objectB.body);
|
|
7210
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7211
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7212
|
+
jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
|
|
7213
|
+
jointDef.set_collideConnected(collide);
|
|
7214
|
+
super(jointDef);
|
|
7215
|
+
}
|
|
7216
|
+
|
|
7217
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7218
|
+
* @return {Vector2} */
|
|
7219
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7220
|
+
|
|
7221
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7222
|
+
* @return {Vector2} */
|
|
7223
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7224
|
+
|
|
7225
|
+
/** Get the reference angle, objectB angle minus objectA angle in the reference state
|
|
7226
|
+
* @return {number} */
|
|
7227
|
+
getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
|
|
7228
|
+
|
|
7229
|
+
/** Get the current joint angle
|
|
7230
|
+
* @return {number} */
|
|
7231
|
+
getJointAngle() { return this.box2dJoint.GetJointAngle(); }
|
|
7232
|
+
|
|
7233
|
+
/** Get the current joint angle speed in radians per second
|
|
7234
|
+
* @return {number} */
|
|
7235
|
+
getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
|
|
7236
|
+
|
|
7237
|
+
/** Is the joint limit enabled?
|
|
7238
|
+
* @return {boolean} */
|
|
7239
|
+
isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
|
|
7240
|
+
|
|
7241
|
+
/** Enable/disable the joint limit
|
|
7242
|
+
* @param {boolean} [enable] */
|
|
7243
|
+
enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
|
|
7244
|
+
|
|
7245
|
+
/** Get the lower joint limit
|
|
7246
|
+
* @return {number} */
|
|
7247
|
+
getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
|
|
7248
|
+
|
|
7249
|
+
/** Get the upper joint limit
|
|
7250
|
+
* @return {number} */
|
|
7251
|
+
getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
|
|
7252
|
+
|
|
7253
|
+
/** Set the joint limits
|
|
7254
|
+
* @param {number} min
|
|
7255
|
+
* @param {number} max */
|
|
7256
|
+
setLimits(min, max) { return this.box2dJoint.SetLimits(min, max); }
|
|
7257
|
+
|
|
7258
|
+
/** Is the joint motor enabled?
|
|
7259
|
+
* @return {boolean} */
|
|
7260
|
+
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
|
|
7261
|
+
|
|
7262
|
+
/** Enable/disable the joint motor
|
|
7263
|
+
* @param {boolean} [enable] */
|
|
7264
|
+
enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
|
|
7265
|
+
|
|
7266
|
+
/** Set the motor speed
|
|
7267
|
+
* @param {number} speed */
|
|
7268
|
+
setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
|
|
7269
|
+
|
|
7270
|
+
/** Get the motor speed
|
|
7271
|
+
* @return {number} */
|
|
7272
|
+
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
|
|
7273
|
+
|
|
7274
|
+
/** Set the motor torque
|
|
7275
|
+
* @param {number} torque */
|
|
7276
|
+
setMaxMotorTorque(torque) { return this.box2dJoint.SetMaxMotorTorque(torque); }
|
|
7277
|
+
|
|
7278
|
+
/** Get the max motor torque
|
|
7279
|
+
* @return {number} */
|
|
7280
|
+
getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
|
|
7281
|
+
|
|
7282
|
+
/** Get the motor torque given a time step
|
|
7283
|
+
* @param {number} time
|
|
7284
|
+
* @return {number} */
|
|
7285
|
+
getMotorTorque(time) { return this.box2dJoint.GetMotorTorque(1/time); }
|
|
7286
|
+
}
|
|
7287
|
+
|
|
7288
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7289
|
+
/**
|
|
7290
|
+
* Box2D Gear Joint
|
|
7291
|
+
* - A gear joint is used to connect two joints together
|
|
7292
|
+
* - Either joint can be a revolute or prismatic joint
|
|
7293
|
+
* - You specify a gear ratio to bind the motions together
|
|
7294
|
+
* @extends Box2dJoint
|
|
7295
|
+
*/
|
|
7296
|
+
class Box2dGearJoint extends Box2dJoint
|
|
7297
|
+
{
|
|
7298
|
+
/** Create a gear joint
|
|
7299
|
+
* @param {Box2dObject} objectA
|
|
7300
|
+
* @param {Box2dObject} objectB
|
|
7301
|
+
* @param {Box2dJoint} joint1
|
|
7302
|
+
* @param {Box2dJoint} joint2
|
|
7303
|
+
* @param {ratio} [ratio] */
|
|
7304
|
+
constructor(objectA, objectB, joint1, joint2, ratio=1)
|
|
7305
|
+
{
|
|
7306
|
+
const jointDef = new box2d.instance.b2GearJointDef();
|
|
7307
|
+
jointDef.set_bodyA(objectA.body);
|
|
7308
|
+
jointDef.set_bodyB(objectB.body);
|
|
7309
|
+
jointDef.set_joint1(joint1.box2dJoint);
|
|
7310
|
+
jointDef.set_joint2(joint2.box2dJoint);
|
|
7311
|
+
jointDef.set_ratio(ratio);
|
|
7312
|
+
super(jointDef);
|
|
7313
|
+
|
|
7314
|
+
this.joint1 = joint1;
|
|
7315
|
+
this.joint2 = joint2;
|
|
7316
|
+
}
|
|
7317
|
+
|
|
7318
|
+
/** Get the first joint
|
|
7319
|
+
* @return {Box2dJoint} */
|
|
7320
|
+
getJoint1() { return this.joint1; }
|
|
7321
|
+
|
|
7322
|
+
/** Get the second joint
|
|
7323
|
+
* @return {Box2dJoint} */
|
|
7324
|
+
getJoint2() { return this.joint2; }
|
|
7325
|
+
|
|
7326
|
+
/** Set the gear ratio
|
|
7327
|
+
* @param {number} ratio */
|
|
7328
|
+
setRatio(ratio) { return this.box2dJoint.SetRatio(ratio); }
|
|
7329
|
+
|
|
7330
|
+
/** Get the gear ratio
|
|
7331
|
+
* @return {number} */
|
|
7332
|
+
getRatio() { return this.box2dJoint.GetRatio(); }
|
|
7333
|
+
}
|
|
7334
|
+
|
|
7335
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7336
|
+
/**
|
|
7337
|
+
* Box2D Prismatic Joint
|
|
7338
|
+
* - Provides one degree of freedom: translation along an axis fixed in objectA
|
|
7339
|
+
* - Relative rotation is prevented
|
|
7340
|
+
* - You can use a joint limit to restrict the range of motion
|
|
7341
|
+
* - You can use a joint motor to drive the motion or to model joint friction
|
|
7342
|
+
* @extends Box2dJoint
|
|
7343
|
+
*/
|
|
7344
|
+
class Box2dPrismaticJoint extends Box2dJoint
|
|
7345
|
+
{
|
|
7346
|
+
/** Create a prismatic joint
|
|
7347
|
+
* @param {Box2dObject} objectA
|
|
7348
|
+
* @param {Box2dObject} objectB
|
|
7349
|
+
* @param {Vector2} anchor
|
|
7350
|
+
* @param {Vector2} worldAxis
|
|
7351
|
+
* @param {boolean} [collide] */
|
|
7352
|
+
constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
|
|
7353
|
+
{
|
|
7354
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7355
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
7356
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
7357
|
+
const localAxisA = objectB.worldToLocalVector(worldAxis);
|
|
7358
|
+
const jointDef = new box2d.instance.b2PrismaticJointDef();
|
|
7359
|
+
jointDef.set_bodyA(objectA.body);
|
|
7360
|
+
jointDef.set_bodyB(objectB.body);
|
|
7361
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7362
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7363
|
+
jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
|
|
7364
|
+
jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
|
|
7365
|
+
jointDef.set_collideConnected(collide);
|
|
7366
|
+
super(jointDef);
|
|
7367
|
+
}
|
|
7368
|
+
|
|
7369
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7370
|
+
* @return {Vector2} */
|
|
7371
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7372
|
+
|
|
7373
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7374
|
+
* @return {Vector2} */
|
|
7375
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7376
|
+
|
|
7377
|
+
/** Get the local joint axis relative to bodyA
|
|
7378
|
+
* @return {Vector2} */
|
|
7379
|
+
getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
|
|
7380
|
+
|
|
7381
|
+
/** Get the reference angle
|
|
7382
|
+
* @return {number} */
|
|
7383
|
+
getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
|
|
7384
|
+
|
|
7385
|
+
/** Get the current joint translation
|
|
7386
|
+
* @return {number} */
|
|
7387
|
+
getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
|
|
7388
|
+
|
|
7389
|
+
/** Get the current joint translation speed
|
|
7390
|
+
* @return {number} */
|
|
7391
|
+
getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
|
|
7392
|
+
|
|
7393
|
+
/** Is the joint limit enabled?
|
|
7394
|
+
* @return {boolean} */
|
|
7395
|
+
isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
|
|
7396
|
+
|
|
7397
|
+
/** Enable/disable the joint limit
|
|
7398
|
+
* @param {boolean} [enable] */
|
|
7399
|
+
enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
|
|
7400
|
+
|
|
7401
|
+
/** Get the lower joint limit
|
|
7402
|
+
* @return {number} */
|
|
7403
|
+
getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
|
|
7404
|
+
|
|
7405
|
+
/** Get the upper joint limit
|
|
7406
|
+
* @return {number} */
|
|
7407
|
+
getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
|
|
7408
|
+
|
|
7409
|
+
/** Set the joint limits
|
|
7410
|
+
* @param {number} min
|
|
7411
|
+
* @param {number} max */
|
|
7412
|
+
setLimits(min, max) { return this.box2dJoint.SetLimits(min, max); }
|
|
7413
|
+
|
|
7414
|
+
/** Is the motor enabled?
|
|
7415
|
+
* @return {boolean} */
|
|
7416
|
+
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
|
|
7417
|
+
|
|
7418
|
+
/** Enable/disable the joint motor
|
|
7419
|
+
* @param {boolean} [enable] */
|
|
7420
|
+
enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
|
|
7421
|
+
|
|
7422
|
+
/** Set the motor speed
|
|
7423
|
+
* @param {number} speed */
|
|
7424
|
+
setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
|
|
7425
|
+
|
|
7426
|
+
/** Get the motor speed
|
|
7427
|
+
* @return {number} */
|
|
7428
|
+
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
|
|
7429
|
+
|
|
7430
|
+
/** Set the maximum motor force
|
|
7431
|
+
* @param {number} force */
|
|
7432
|
+
setMaxMotorForce(force) { return this.box2dJoint.SetMaxMotorForce(force); }
|
|
7433
|
+
|
|
7434
|
+
/** Get the maximum motor force
|
|
7435
|
+
* @return {number} */
|
|
7436
|
+
getMaxMotorForce() { return this.box2dJoint.GetMaxMotorForce(); }
|
|
7437
|
+
|
|
7438
|
+
/** Get the motor force given a time step
|
|
7439
|
+
* @param {number} time
|
|
7440
|
+
* @return {number} */
|
|
7441
|
+
getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
|
|
7442
|
+
}
|
|
7443
|
+
|
|
7444
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7445
|
+
/**
|
|
7446
|
+
* Box2D Wheel Joint
|
|
7447
|
+
* - Provides two degrees of freedom: translation along an axis fixed in objectA and rotation
|
|
7448
|
+
* - You can use a joint limit to restrict the range of motion
|
|
7449
|
+
* - You can use a joint motor to drive the motion or to model joint friction
|
|
7450
|
+
* - This joint is designed for vehicle suspensions
|
|
7451
|
+
* @extends Box2dJoint
|
|
7452
|
+
*/
|
|
7453
|
+
class Box2dWheelJoint extends Box2dJoint
|
|
7454
|
+
{
|
|
7455
|
+
/** Create a wheel joint
|
|
7456
|
+
* @param {Box2dObject} objectA
|
|
7457
|
+
* @param {Box2dObject} objectB
|
|
7458
|
+
* @param {Vector2} anchor
|
|
7459
|
+
* @param {Vector2} worldAxis
|
|
7460
|
+
* @param {boolean} [collide] */
|
|
7461
|
+
constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
|
|
7462
|
+
{
|
|
7463
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7464
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
7465
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
7466
|
+
const localAxisA = objectB.worldToLocalVector(worldAxis);
|
|
7467
|
+
const jointDef = new box2d.instance.b2WheelJointDef();
|
|
7468
|
+
jointDef.set_bodyA(objectA.body);
|
|
7469
|
+
jointDef.set_bodyB(objectB.body);
|
|
7470
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7471
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7472
|
+
jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
|
|
7473
|
+
jointDef.set_collideConnected(collide);
|
|
7474
|
+
super(jointDef);
|
|
7475
|
+
}
|
|
7476
|
+
|
|
7477
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7478
|
+
* @return {Vector2} */
|
|
7479
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7480
|
+
|
|
7481
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7482
|
+
* @return {Vector2} */
|
|
7483
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7484
|
+
|
|
7485
|
+
/** Get the local joint axis relative to bodyA
|
|
7486
|
+
* @return {Vector2} */
|
|
7487
|
+
getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
|
|
7488
|
+
|
|
7489
|
+
/** Get the current joint translation
|
|
7490
|
+
* @return {number} */
|
|
7491
|
+
getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
|
|
7492
|
+
|
|
7493
|
+
/** Get the current joint translation speed
|
|
7494
|
+
* @return {number} */
|
|
7495
|
+
getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
|
|
7496
|
+
|
|
7497
|
+
/** Is the joint motor enabled?
|
|
7498
|
+
* @return {boolean} */
|
|
7499
|
+
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
|
|
7500
|
+
|
|
7501
|
+
/** Enable/disable the joint motor
|
|
7502
|
+
* @param {boolean} [enable] */
|
|
7503
|
+
enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
|
|
7504
|
+
|
|
7505
|
+
/** Set the motor speed
|
|
7506
|
+
* @param {number} speed */
|
|
7507
|
+
setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
|
|
7508
|
+
|
|
7509
|
+
/** Get the motor speed
|
|
7510
|
+
* @return {number} */
|
|
7511
|
+
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
|
|
7512
|
+
|
|
7513
|
+
/** Set the maximum motor torque
|
|
7514
|
+
* @param {number} torque */
|
|
7515
|
+
setMaxMotorTorque(torque) { return this.box2dJoint.SetMaxMotorTorque(torque); }
|
|
7516
|
+
|
|
7517
|
+
/** Get the max motor torque
|
|
7518
|
+
* @return {number} */
|
|
7519
|
+
getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
|
|
7520
|
+
|
|
7521
|
+
/** Get the motor torque for a time step
|
|
7522
|
+
* @return {number} */
|
|
7523
|
+
getMotorTorque(time) { return this.box2dJoint.GetMotorTorque(1/time); }
|
|
7524
|
+
|
|
7525
|
+
/** Set the spring frequency in Hertz
|
|
7526
|
+
* @param {number} hz */
|
|
7527
|
+
setSpringFrequencyHz(hz) { return this.box2dJoint.SetSpringFrequencyHz(hz); }
|
|
7528
|
+
|
|
7529
|
+
/** Get the spring frequency in Hertz
|
|
7530
|
+
* @return {number} */
|
|
7531
|
+
getSpringFrequencyHz() { return this.box2dJoint.GetSpringFrequencyHz(); }
|
|
7532
|
+
|
|
7533
|
+
/** Set the spring damping ratio
|
|
7534
|
+
* @param {number} ratio */
|
|
7535
|
+
setSpringDampingRatio(ratio) { return this.box2dJoint.SetSpringDampingRatio(ratio); }
|
|
7536
|
+
|
|
7537
|
+
/** Get the spring damping ratio
|
|
7538
|
+
* @return {number} */
|
|
7539
|
+
getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
|
|
7540
|
+
}
|
|
7541
|
+
|
|
7542
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7543
|
+
/**
|
|
7544
|
+
* Box2D Weld Joint
|
|
7545
|
+
* - Glues two objects together
|
|
7546
|
+
* @extends Box2dJoint
|
|
7547
|
+
*/
|
|
7548
|
+
class Box2dWeldJoint extends Box2dJoint
|
|
7549
|
+
{
|
|
7550
|
+
/** Create a weld joint
|
|
7551
|
+
* @param {Box2dObject} objectA
|
|
7552
|
+
* @param {Box2dObject} objectB
|
|
7553
|
+
* @param {Vector2} anchor
|
|
7554
|
+
* @param {boolean} [collide] */
|
|
7555
|
+
constructor(objectA, objectB, anchor, collide=false)
|
|
7556
|
+
{
|
|
7557
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7558
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
7559
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
7560
|
+
const jointDef = new box2d.instance.b2WeldJointDef();
|
|
7561
|
+
jointDef.set_bodyA(objectA.body);
|
|
7562
|
+
jointDef.set_bodyB(objectB.body);
|
|
7563
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7564
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7565
|
+
jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
|
|
7566
|
+
jointDef.set_collideConnected(collide);
|
|
7567
|
+
super(jointDef);
|
|
7568
|
+
}
|
|
7569
|
+
|
|
7570
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7571
|
+
* @return {Vector2} */
|
|
7572
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7573
|
+
|
|
7574
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7575
|
+
* @return {Vector2} */
|
|
7576
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7577
|
+
|
|
7578
|
+
/** Get the reference angle
|
|
7579
|
+
* @return {number} */
|
|
7580
|
+
getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
|
|
7581
|
+
|
|
7582
|
+
/** Set the frequency in Hertz
|
|
7583
|
+
* @param {number} hz */
|
|
7584
|
+
setFrequency(hz) { return this.box2dJoint.SetFrequency(hz); }
|
|
7585
|
+
|
|
7586
|
+
/** Get the frequency in Hertz
|
|
7587
|
+
* @return {number} */
|
|
7588
|
+
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
7589
|
+
|
|
7590
|
+
/** Set the damping ratio
|
|
7591
|
+
* @param {number} ratio */
|
|
7592
|
+
setSpringDampingRatio(ratio) { return this.box2dJoint.SetSpringDampingRatio(ratio); }
|
|
7593
|
+
|
|
7594
|
+
/** Get the damping ratio
|
|
7595
|
+
* @return {number} */
|
|
7596
|
+
getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
|
|
7597
|
+
}
|
|
7598
|
+
|
|
7599
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7600
|
+
/**
|
|
7601
|
+
* Box2D Friction Joint
|
|
7602
|
+
* - Used to apply top-down friction
|
|
7603
|
+
* - Provides 2D translational friction and angular friction
|
|
7604
|
+
* @extends Box2dJoint
|
|
7605
|
+
*/
|
|
7606
|
+
class Box2dFrictionJoint extends Box2dJoint
|
|
7607
|
+
{
|
|
7608
|
+
/** Create a friction joint
|
|
7609
|
+
* @param {Box2dObject} objectA
|
|
7610
|
+
* @param {Box2dObject} objectB
|
|
7611
|
+
* @param {Vector2} anchor
|
|
7612
|
+
* @param {boolean} [collide] */
|
|
7613
|
+
constructor(objectA, objectB, anchor, collide=false)
|
|
7614
|
+
{
|
|
7615
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7616
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
7617
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
7618
|
+
const jointDef = new box2d.instance.b2FrictionJointDef();
|
|
7619
|
+
jointDef.set_bodyA(objectA.body);
|
|
7620
|
+
jointDef.set_bodyB(objectB.body);
|
|
7621
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7622
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7623
|
+
jointDef.set_collideConnected(collide);
|
|
7624
|
+
super(jointDef);
|
|
7625
|
+
}
|
|
7626
|
+
|
|
7627
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7628
|
+
* @return {Vector2} */
|
|
7629
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7630
|
+
|
|
7631
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7632
|
+
* @return {Vector2} */
|
|
7633
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7634
|
+
|
|
7635
|
+
/** Set the maximum friction force
|
|
7636
|
+
* @param {number} force */
|
|
7637
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
|
|
7638
|
+
|
|
7639
|
+
/** Get the maximum friction force
|
|
7640
|
+
* @return {number} */
|
|
7641
|
+
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
|
|
7642
|
+
|
|
7643
|
+
/** Set the maximum friction torque
|
|
7644
|
+
* @param {number} torque */
|
|
7645
|
+
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(torque); }
|
|
7646
|
+
|
|
7647
|
+
/** Get the maximum friction torque
|
|
7648
|
+
* @return {number} */
|
|
7649
|
+
getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
|
|
7650
|
+
}
|
|
7651
|
+
|
|
7652
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7653
|
+
/**
|
|
7654
|
+
* Box2D Pulley Joint
|
|
7655
|
+
* - Connects to two objects and two fixed ground points
|
|
7656
|
+
* - The pulley supports a ratio such that: length1 + ratio * length2 <= constant
|
|
7657
|
+
* - The force transmitted is scaled by the ratio
|
|
7658
|
+
* @extends Box2dJoint
|
|
7659
|
+
*/
|
|
7660
|
+
class Box2dPulleyJoint extends Box2dJoint
|
|
7661
|
+
{
|
|
7662
|
+
/** Create a pulley joint
|
|
7663
|
+
* @param {Box2dObject} objectA
|
|
7664
|
+
* @param {Box2dObject} objectB
|
|
7665
|
+
* @param {Vector2} groundAnchorA
|
|
7666
|
+
* @param {Vector2} groundAnchorB
|
|
7667
|
+
* @param {Vector2} anchorA
|
|
7668
|
+
* @param {Vector2} anchorB
|
|
7669
|
+
* @param {number} [ratio]
|
|
7670
|
+
* @param {boolean} [collide] */
|
|
7671
|
+
constructor(objectA, objectB, groundAnchorA, groundAnchorB, anchorA, anchorB, ratio=1, collide=false)
|
|
7672
|
+
{
|
|
7673
|
+
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
|
|
7674
|
+
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7675
|
+
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
7676
|
+
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
7677
|
+
const jointDef = new box2d.instance.b2PulleyJointDef();
|
|
7678
|
+
jointDef.set_bodyA(objectA.body);
|
|
7679
|
+
jointDef.set_bodyB(objectB.body);
|
|
7680
|
+
jointDef.set_groundAnchorA(box2d.vec2dTo(groundAnchorA));
|
|
7681
|
+
jointDef.set_groundAnchorB(box2d.vec2dTo(groundAnchorB));
|
|
7682
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7683
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7684
|
+
jointDef.set_ratio(ratio);
|
|
7685
|
+
jointDef.set_lengthA(groundAnchorA.distance(anchorA));
|
|
7686
|
+
jointDef.set_lengthB(groundAnchorB.distance(anchorB));
|
|
7687
|
+
jointDef.set_collideConnected(collide);
|
|
7688
|
+
super(jointDef);
|
|
7689
|
+
}
|
|
7690
|
+
|
|
7691
|
+
/** Get the first ground anchor
|
|
7692
|
+
* @return {Vector2} */
|
|
7693
|
+
getGroundAnchorA() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorA()); }
|
|
7694
|
+
|
|
7695
|
+
/** Get the second ground anchor
|
|
7696
|
+
* @return {Vector2} */
|
|
7697
|
+
getGroundAnchorB() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorB()); }
|
|
7698
|
+
|
|
7699
|
+
/** Get the current length of the segment attached to objectA
|
|
7700
|
+
* @return {number} */
|
|
7701
|
+
getLengthA() { return this.box2dJoint.GetLengthA(); }
|
|
7702
|
+
|
|
7703
|
+
/** Get the current length of the segment attached to objectB
|
|
7704
|
+
* @return {number} */
|
|
7705
|
+
getLengthB(){ return this.box2dJoint.GetLengthB(); }
|
|
7706
|
+
|
|
7707
|
+
/** Get the pulley ratio
|
|
7708
|
+
* @return {number} */
|
|
7709
|
+
getRatio() { return this.box2dJoint.GetRatio(); }
|
|
7710
|
+
|
|
7711
|
+
/** Get the current length of the segment attached to objectA
|
|
7712
|
+
* @return {number} */
|
|
7713
|
+
getCurrentLengthA() { return this.box2dJoint.GetCurrentLengthA(); }
|
|
7714
|
+
|
|
7715
|
+
/** Get the current length of the segment attached to objectB
|
|
7716
|
+
* @return {number} */
|
|
7717
|
+
getCurrentLengthB() { return this.box2dJoint.GetCurrentLengthB(); }
|
|
7718
|
+
}
|
|
7719
|
+
|
|
7720
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7721
|
+
/**
|
|
7722
|
+
* Box2D Motor Joint
|
|
7723
|
+
* - Controls the relative motion between two objects
|
|
7724
|
+
* - Typical usage is to control the movement of a object with respect to the ground
|
|
7725
|
+
* @extends Box2dJoint
|
|
7726
|
+
*/
|
|
7727
|
+
class Box2dMotorJoint extends Box2dJoint
|
|
7728
|
+
{
|
|
7729
|
+
/** Create a motor joint
|
|
7730
|
+
* @param {Box2dObject} objectA
|
|
7731
|
+
* @param {Box2dObject} objectB */
|
|
7732
|
+
constructor(objectA, objectB)
|
|
7733
|
+
{
|
|
7734
|
+
const linearOffset = objectA.worldToLocal(box2d.vec2From(objectB.body.GetPosition()));
|
|
7735
|
+
const angularOffset = objectB.body.GetAngle() - objectA.body.GetAngle();
|
|
7736
|
+
const jointDef = new box2d.instance.b2MotorJointDef();
|
|
7737
|
+
jointDef.set_bodyA(objectA.body);
|
|
7738
|
+
jointDef.set_bodyB(objectB.body);
|
|
7739
|
+
jointDef.set_linearOffset(box2d.vec2dTo(linearOffset));
|
|
7740
|
+
jointDef.set_angularOffset(angularOffset);
|
|
7741
|
+
super(jointDef);
|
|
7742
|
+
}
|
|
7743
|
+
|
|
7744
|
+
/** Set the target linear offset, in frame A, in meters.
|
|
7745
|
+
* @param {Vector2} offset */
|
|
7746
|
+
setLinearOffset(offset) { this.box2dJoint.SetLinearOffset(box2d.vec2dTo(offset)); }
|
|
7747
|
+
|
|
7748
|
+
/** Get the target linear offset, in frame A, in meters.
|
|
7749
|
+
* @return {Vector2} */
|
|
7750
|
+
getLinearOffset() { return box2d.vec2From(this.box2dJoint.GetLinearOffset()); }
|
|
7751
|
+
|
|
7752
|
+
/** Set the target angular offset
|
|
7753
|
+
* @param {number} offset */
|
|
7754
|
+
setAngularOffset(offset) { this.box2dJoint.SetAngularOffset(offset); }
|
|
7755
|
+
|
|
7756
|
+
/** Get the target angular offset
|
|
7757
|
+
* @return {number} */
|
|
7758
|
+
getAngularOffset() { return this.box2dJoint.GetAngularOffset(); }
|
|
7759
|
+
|
|
7760
|
+
/** Set the maximum friction force
|
|
7761
|
+
* @param {number} force */
|
|
7762
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
|
|
7763
|
+
|
|
7764
|
+
/** Get the maximum friction force
|
|
7765
|
+
* @return {number} */
|
|
7766
|
+
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
|
|
7767
|
+
|
|
7768
|
+
/** Set the maximum torque
|
|
7769
|
+
* @param {number} torque */
|
|
7770
|
+
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(torque); }
|
|
7771
|
+
|
|
7772
|
+
/** Get the maximum torque
|
|
7773
|
+
* @return {number} */
|
|
7774
|
+
getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
|
|
7775
|
+
|
|
7776
|
+
/** Set the position correction factor in the range [0,1]
|
|
7777
|
+
* @param {number} factor */
|
|
7778
|
+
setCorrectionFactor(factor) { this.box2dJoint.SetCorrectionFactor(factor); }
|
|
7779
|
+
|
|
7780
|
+
/** Get the position correction factor in the range [0,1]
|
|
7781
|
+
* @return {number} */
|
|
7782
|
+
getCorrectionFactor() { return this.box2dJoint.GetCorrectionFactor(); }
|
|
7783
|
+
}
|
|
7784
|
+
|
|
7785
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7786
|
+
/**
|
|
7787
|
+
* Box2D Global Object
|
|
7788
|
+
* - Wraps Box2d world and provides global functions
|
|
7789
|
+
*/
|
|
7790
|
+
class Box2dPlugin
|
|
7791
|
+
{
|
|
7792
|
+
/** Create the global UI system object
|
|
7793
|
+
* @param {Object} instance */
|
|
7794
|
+
constructor(instance)
|
|
7795
|
+
{
|
|
7796
|
+
ASSERT(!box2d, 'Box2D already initialized');
|
|
7797
|
+
box2d = this;
|
|
7798
|
+
this.instance = instance;
|
|
7799
|
+
this.world = new box2d.instance.b2World();
|
|
7800
|
+
|
|
7801
|
+
/** @property {number} - Velocity iterations per update*/
|
|
7802
|
+
this.velocityIterations = 8;
|
|
7803
|
+
/** @property {number} - Position iterations per update*/
|
|
7804
|
+
this.positionIterations = 3;
|
|
7805
|
+
/** @property {number} - Static, zero mass, zero velocity, may be manually moved */
|
|
7806
|
+
this.bodyTypeStatic = instance.b2_staticBody;
|
|
7807
|
+
/** @property {number} - Kinematic, zero mass, non-zero velocity set by user, moved by solver */
|
|
7808
|
+
this.bodyTypeKinematic = instance.b2_kinematicBody;
|
|
7809
|
+
/** @property {number} - Dynamic, positive mass, non-zero velocity determined by forces, moved by solver */
|
|
7810
|
+
this.bodyTypeDynamic = instance.b2_dynamicBody;
|
|
7811
|
+
|
|
7812
|
+
// setup contact listener
|
|
7813
|
+
const listener = new box2d.instance.JSContactListener();
|
|
7814
|
+
listener.BeginContact = function(contactPtr)
|
|
7815
|
+
{
|
|
7816
|
+
const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
|
|
7817
|
+
const fixtureA = contact.GetFixtureA();
|
|
7818
|
+
const fixtureB = contact.GetFixtureB();
|
|
7819
|
+
const objectA = fixtureA.GetBody().object;
|
|
7820
|
+
const objectB = fixtureB.GetBody().object;
|
|
7821
|
+
objectA.beginContact(objectB);
|
|
7822
|
+
objectB.beginContact(objectA);
|
|
7823
|
+
}
|
|
7824
|
+
listener.EndContact = function(contactPtr)
|
|
7825
|
+
{
|
|
7826
|
+
const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
|
|
7827
|
+
const fixtureA = contact.GetFixtureA();
|
|
7828
|
+
const fixtureB = contact.GetFixtureB();
|
|
7829
|
+
const objectA = fixtureA.GetBody().object;
|
|
7830
|
+
const objectB = fixtureB.GetBody().object;
|
|
7831
|
+
objectA.endContact(objectB);
|
|
7832
|
+
objectB.endContact(objectA);
|
|
7833
|
+
};
|
|
7834
|
+
listener.PreSolve = function() {};
|
|
7835
|
+
listener.PostSolve = function() {};
|
|
7836
|
+
box2d.world.SetContactListener(listener);
|
|
7837
|
+
}
|
|
7838
|
+
|
|
7839
|
+
/** Step the physics world simulation
|
|
7840
|
+
* @param {number} [frames] */
|
|
7841
|
+
step(frames=1)
|
|
7842
|
+
{
|
|
7843
|
+
box2d.world.SetGravity(box2d.vec2dTo(gravity));
|
|
7844
|
+
for (let i=frames; i--;)
|
|
7845
|
+
box2d.world.Step(timeDelta, this.velocityIterations, this.positionIterations);
|
|
7846
|
+
}
|
|
7847
|
+
|
|
7848
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7849
|
+
// raycasting and querying
|
|
7850
|
+
|
|
7851
|
+
/** raycast and return a list of all the results
|
|
7852
|
+
* @param {Vector2} start
|
|
7853
|
+
* @param {Vector2} end */
|
|
7854
|
+
raycastAll(start, end)
|
|
7855
|
+
{
|
|
7856
|
+
const raycastCallback = new box2d.instance.JSRayCastCallback();
|
|
7857
|
+
raycastCallback.ReportFixture = function(fixturePointer, point, normal, fraction)
|
|
7858
|
+
{
|
|
7859
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
7860
|
+
point = box2d.vec2FromPointer(point);
|
|
7861
|
+
normal = box2d.vec2FromPointer(normal);
|
|
7862
|
+
raycastResults.push(new Box2dRaycastResult(fixture, point, normal, fraction));
|
|
7863
|
+
return 1; // continue getting results
|
|
7864
|
+
};
|
|
7865
|
+
|
|
7866
|
+
const raycastResults = [];
|
|
7867
|
+
box2d.world.RayCast(raycastCallback, box2d.vec2dTo(start), box2d.vec2dTo(end));
|
|
7868
|
+
debugRaycast && debugLine(start, end, raycastResults.length ? '#f00' : '#00f', .02);
|
|
7869
|
+
return raycastResults;
|
|
7870
|
+
}
|
|
7871
|
+
|
|
7872
|
+
/** raycast and return the first result
|
|
7873
|
+
* @param {Vector2} start
|
|
7874
|
+
* @param {Vector2} end */
|
|
7875
|
+
raycast(start, end)
|
|
7876
|
+
{
|
|
7877
|
+
const raycastResults = box2d.raycastAll(start, end);
|
|
7878
|
+
if (!raycastResults.length)
|
|
7879
|
+
return undefined;
|
|
7880
|
+
return raycastResults.reduce((a,b)=>a.fraction < b.fraction ? a : b);
|
|
7881
|
+
}
|
|
7882
|
+
|
|
7883
|
+
/** box aabb cast and return all the objects
|
|
7884
|
+
* @param {Vector2} pos
|
|
7885
|
+
* @param {Vector2} size */
|
|
7886
|
+
boxCastAll(pos, size)
|
|
7887
|
+
{
|
|
7888
|
+
const queryCallback = new box2d.instance.JSQueryCallback();
|
|
7889
|
+
queryCallback.ReportFixture = function(fixturePointer)
|
|
7890
|
+
{
|
|
7891
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
7892
|
+
const o = fixture.GetBody().object;
|
|
7893
|
+
if (!queryObjects.includes(o))
|
|
7894
|
+
queryObjects.push(o); // add if not already in list
|
|
7895
|
+
return true; // continue getting results
|
|
7896
|
+
};
|
|
7897
|
+
|
|
7898
|
+
const aabb = new box2d.instance.b2AABB();
|
|
7899
|
+
aabb.set_lowerBound(box2d.vec2dTo(pos.subtract(size.scale(.5))));
|
|
7900
|
+
aabb.set_upperBound(box2d.vec2dTo(pos.add(size.scale(.5))));
|
|
7901
|
+
|
|
7902
|
+
let queryObjects = [];
|
|
7903
|
+
box2d.world.QueryAABB(queryCallback, aabb);
|
|
7904
|
+
debugRaycast && debugRect(pos, size, queryObjects.length ? '#f00' : '#00f', .02);
|
|
7905
|
+
return queryObjects;
|
|
7906
|
+
}
|
|
7907
|
+
|
|
7908
|
+
/** box aabb cast and return the first object
|
|
7909
|
+
* @param {Vector2} pos
|
|
7910
|
+
* @param {Vector2} size */
|
|
7911
|
+
boxCast(pos, size)
|
|
7912
|
+
{
|
|
7913
|
+
const queryCallback = new box2d.instance.JSQueryCallback();
|
|
7914
|
+
queryCallback.ReportFixture = function(fixturePointer)
|
|
7915
|
+
{
|
|
7916
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
7917
|
+
queryObject = fixture.GetBody().object;
|
|
7918
|
+
return false; // stop getting results
|
|
7919
|
+
};
|
|
7920
|
+
|
|
7921
|
+
const aabb = new box2d.instance.b2AABB();
|
|
7922
|
+
aabb.set_lowerBound(box2d.vec2dTo(pos.subtract(size.scale(.5))));
|
|
7923
|
+
aabb.set_upperBound(box2d.vec2dTo(pos.add(size.scale(.5))));
|
|
7924
|
+
|
|
7925
|
+
let queryObject;
|
|
7926
|
+
box2d.world.QueryAABB(queryCallback, aabb);
|
|
7927
|
+
debugRaycast && debugRect(pos, size, queryObject ? '#f00' : '#00f', .02);
|
|
7928
|
+
return queryObject;
|
|
7929
|
+
}
|
|
7930
|
+
|
|
7931
|
+
/** circle cast and return all the objects
|
|
7932
|
+
* @param {Vector2} pos
|
|
7933
|
+
* @param {number} diameter */
|
|
7934
|
+
circleCastAll(pos, diameter)
|
|
7935
|
+
{
|
|
7936
|
+
const radius2 = (diameter/2)**2;
|
|
7937
|
+
const results = box2d.boxCastAll(pos, vec2(diameter));
|
|
7938
|
+
return results.filter(o=>o.pos.distanceSquared(pos) < radius2);
|
|
7939
|
+
}
|
|
7940
|
+
|
|
7941
|
+
/** circle cast and return the first object
|
|
7942
|
+
* @param {Vector2} pos
|
|
7943
|
+
* @param {number} diameter */
|
|
7944
|
+
circleCast(pos, diameter)
|
|
7945
|
+
{
|
|
7946
|
+
const radius2 = (diameter/2)**2;
|
|
7947
|
+
let results = box2d.boxCastAll(pos, vec2(diameter));
|
|
7948
|
+
|
|
7949
|
+
let bestResult, bestDistance2;
|
|
7950
|
+
for (const result of results)
|
|
7951
|
+
{
|
|
7952
|
+
const distance2 = result.pos.distanceSquared(pos);
|
|
7953
|
+
if (distance2 < radius2 && (!bestResult || distance2 < bestDistance2))
|
|
7954
|
+
{
|
|
7955
|
+
bestResult = result;
|
|
7956
|
+
bestDistance2 = distance2;
|
|
7957
|
+
}
|
|
7958
|
+
}
|
|
7959
|
+
return bestResult;
|
|
7960
|
+
}
|
|
7961
|
+
|
|
7962
|
+
/** point cast and return the first object
|
|
7963
|
+
* @param {Vector2} pos
|
|
7964
|
+
* @param {boolean} dynamicOnly */
|
|
7965
|
+
pointCast(pos, dynamicOnly=true)
|
|
7966
|
+
{
|
|
7967
|
+
const queryCallback = new box2d.instance.JSQueryCallback();
|
|
7968
|
+
queryCallback.ReportFixture = function(fixturePointer)
|
|
7969
|
+
{
|
|
7970
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
7971
|
+
if (dynamicOnly && fixture.GetBody().GetType() != box2d.instance.b2_dynamicBody)
|
|
7972
|
+
return true; // continue getting results
|
|
7973
|
+
if (!fixture.TestPoint(box2d.vec2dTo(pos)))
|
|
7974
|
+
return true; // continue getting results
|
|
7975
|
+
queryObject = fixture.GetBody().object;
|
|
7976
|
+
return false; // stop getting results
|
|
7977
|
+
};
|
|
7978
|
+
|
|
7979
|
+
const aabb = new box2d.instance.b2AABB();
|
|
7980
|
+
aabb.set_lowerBound(box2d.vec2dTo(pos));
|
|
7981
|
+
aabb.set_upperBound(box2d.vec2dTo(pos));
|
|
7982
|
+
|
|
7983
|
+
let queryObject;
|
|
7984
|
+
box2d.world.QueryAABB(queryCallback, aabb);
|
|
7985
|
+
debugRaycast && debugRect(pos, vec2(), queryObject ? '#f00' : '#00f', .02);
|
|
7986
|
+
return queryObject;
|
|
7987
|
+
}
|
|
7988
|
+
|
|
7989
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7990
|
+
// drawing
|
|
7991
|
+
|
|
7992
|
+
/** draws a fixture
|
|
7993
|
+
* @param {Object} fixture
|
|
7994
|
+
* @param {Vector2} pos
|
|
7995
|
+
* @param {number} angle
|
|
7996
|
+
* @param {Color} [color]
|
|
7997
|
+
* @param {Color} [outlineColor]
|
|
7998
|
+
* @param {number} [lineWidth]
|
|
7999
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
8000
|
+
drawFixture(fixture, pos, angle, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
|
|
8001
|
+
{
|
|
8002
|
+
const shape = box2d.castObjectType(fixture.GetShape());
|
|
8003
|
+
switch (shape.GetType())
|
|
8004
|
+
{
|
|
8005
|
+
case box2d.instance.b2Shape.e_polygon:
|
|
8006
|
+
{
|
|
8007
|
+
let points = [];
|
|
8008
|
+
for (let i=shape.GetVertexCount(); i--;)
|
|
8009
|
+
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
8010
|
+
box2d.drawPoly(pos, angle, points, color, outlineColor, lineWidth, context);
|
|
8011
|
+
break;
|
|
8012
|
+
}
|
|
8013
|
+
case box2d.instance.b2Shape.e_circle:
|
|
8014
|
+
{
|
|
8015
|
+
const radius = shape.get_m_radius();
|
|
8016
|
+
box2d.drawCircle(pos, radius, color, outlineColor, lineWidth, context);
|
|
8017
|
+
break;
|
|
8018
|
+
}
|
|
8019
|
+
case box2d.instance.b2Shape.e_edge:
|
|
8020
|
+
{
|
|
8021
|
+
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
8022
|
+
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
8023
|
+
box2d.drawLine(pos, angle, v1, v2, color, lineWidth, context);
|
|
8024
|
+
break;
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
}
|
|
8028
|
+
|
|
8029
|
+
/** draws a circle
|
|
8030
|
+
* @param {Vector2} pos
|
|
8031
|
+
* @param {number} radius
|
|
8032
|
+
* @param {Color} [color]
|
|
8033
|
+
* @param {Color} [outlineColor]
|
|
8034
|
+
* @param {number} [lineWidth]
|
|
8035
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
8036
|
+
drawCircle(pos, radius, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
|
|
8037
|
+
{
|
|
8038
|
+
drawCanvas2D(pos, vec2(1), 0, 0, context=>
|
|
8039
|
+
{
|
|
8040
|
+
context.beginPath();
|
|
8041
|
+
context.arc(0, 0, radius, 0, 9);
|
|
8042
|
+
box2d.drawFillStroke(color, outlineColor, lineWidth, context);
|
|
8043
|
+
}, 0, context);
|
|
8044
|
+
}
|
|
8045
|
+
|
|
8046
|
+
/** draws a polygon
|
|
8047
|
+
* @param {Vector2} pos
|
|
8048
|
+
* @param {number} angle
|
|
8049
|
+
* @param {Array<Vector2>} points
|
|
8050
|
+
* @param {Color} [color]
|
|
8051
|
+
* @param {Color} [outlineColor]
|
|
8052
|
+
* @param {number} [lineWidth]
|
|
8053
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
8054
|
+
drawPoly(pos, angle, points, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
|
|
8055
|
+
{
|
|
8056
|
+
drawCanvas2D(pos, vec2(1), angle, 0, context=>
|
|
8057
|
+
{
|
|
8058
|
+
context.beginPath();
|
|
8059
|
+
points.forEach(p=>context.lineTo(p.x, p.y));
|
|
8060
|
+
context.closePath();
|
|
8061
|
+
box2d.drawFillStroke(color, outlineColor, lineWidth, context);
|
|
8062
|
+
}, 0, context);
|
|
8063
|
+
}
|
|
8064
|
+
|
|
8065
|
+
/** draws a line
|
|
8066
|
+
* @param {Vector2} pos
|
|
8067
|
+
* @param {number} angle
|
|
8068
|
+
* @param {Vector2} posA
|
|
8069
|
+
* @param {Vector2} posB
|
|
8070
|
+
* @param {Color} [color]
|
|
8071
|
+
* @param {number} [lineWidth]
|
|
8072
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
8073
|
+
drawLine(pos, angle, posA, posB, color=WHITE, lineWidth=.1, context=mainContext)
|
|
8074
|
+
{
|
|
8075
|
+
drawCanvas2D(pos, vec2(1), angle, 0, context=>
|
|
8076
|
+
{
|
|
8077
|
+
context.beginPath();
|
|
8078
|
+
context.lineTo(posA.x, posA.y);
|
|
8079
|
+
context.lineTo(posB.x, posB.y);
|
|
8080
|
+
box2d.drawFillStroke(0, color, lineWidth, context);
|
|
8081
|
+
}, 0, context);
|
|
8082
|
+
}
|
|
8083
|
+
|
|
8084
|
+
/** performs a fill or stroke as a helper to the other draw functions
|
|
8085
|
+
* @param {Color} [color]
|
|
8086
|
+
* @param {Color} [outlineColor]
|
|
8087
|
+
* @param {number} [lineWidth]
|
|
8088
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
8089
|
+
drawFillStroke(color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
|
|
8090
|
+
{
|
|
8091
|
+
if (color)
|
|
8092
|
+
{
|
|
8093
|
+
context.fillStyle = color.toString();
|
|
8094
|
+
context.fill();
|
|
8095
|
+
}
|
|
8096
|
+
if (outlineColor && lineWidth)
|
|
8097
|
+
{
|
|
8098
|
+
context.lineWidth = lineWidth;
|
|
8099
|
+
context.lineJoin = context.lineCap = 'round';
|
|
8100
|
+
context.strokeStyle = outlineColor.toString();
|
|
8101
|
+
context.stroke();
|
|
8102
|
+
}
|
|
8103
|
+
}
|
|
8104
|
+
|
|
8105
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8106
|
+
// helper functions
|
|
8107
|
+
|
|
8108
|
+
/** converts a box2d vec2 to a Vector2
|
|
8109
|
+
* @param {Object} v */
|
|
8110
|
+
vec2From(v)
|
|
8111
|
+
{
|
|
8112
|
+
ASSERT(v instanceof box2d.instance.b2Vec2);
|
|
8113
|
+
return new Vector2(v.get_x(), v.get_y());
|
|
8114
|
+
}
|
|
8115
|
+
|
|
8116
|
+
/** converts a box2d vec2 pointer to a Vector2
|
|
8117
|
+
* @param {Object} v */
|
|
8118
|
+
vec2FromPointer(v)
|
|
8119
|
+
{
|
|
8120
|
+
return box2d.vec2From(box2d.instance.wrapPointer(v, box2d.instance.b2Vec2));
|
|
8121
|
+
}
|
|
8122
|
+
|
|
8123
|
+
/** converts a Vector2 to a box2 vec2
|
|
8124
|
+
* @param {Vector2} v */
|
|
8125
|
+
vec2dTo(v)
|
|
8126
|
+
{
|
|
8127
|
+
ASSERT(v instanceof Vector2);
|
|
8128
|
+
return new box2d.instance.b2Vec2(v.x, v.y);
|
|
8129
|
+
}
|
|
8130
|
+
|
|
8131
|
+
/** checks if a box2d object is null
|
|
8132
|
+
* @param {Object} o */
|
|
8133
|
+
isNull(o) { return !box2d.instance.getPointer(o); }
|
|
8134
|
+
|
|
8135
|
+
/** casts a box2d object to its correct type
|
|
8136
|
+
* @param {Object} o */
|
|
8137
|
+
castObjectType(o)
|
|
8138
|
+
{
|
|
8139
|
+
switch (o.GetType())
|
|
8140
|
+
{
|
|
8141
|
+
case box2d.instance.b2Shape.e_circle:
|
|
8142
|
+
return box2d.instance.castObject(o, box2d.instance.b2CircleShape);
|
|
8143
|
+
case box2d.instance.b2Shape.e_edge:
|
|
8144
|
+
return box2d.instance.castObject(o, box2d.instance.b2EdgeShape);
|
|
8145
|
+
case box2d.instance.b2Shape.e_polygon:
|
|
8146
|
+
return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
|
|
8147
|
+
case box2d.instance.b2Shape.e_chain:
|
|
8148
|
+
return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
|
|
8149
|
+
case box2d.instance.e_revoluteJoint:
|
|
8150
|
+
return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
|
|
8151
|
+
case box2d.instance.e_prismaticJoint:
|
|
8152
|
+
return box2d.instance.castObject(o, box2d.instance.b2PrismaticJoint);
|
|
8153
|
+
case box2d.instance.e_distanceJoint:
|
|
8154
|
+
return box2d.instance.castObject(o, box2d.instance.b2DistanceJoint);
|
|
8155
|
+
case box2d.instance.e_pulleyJoint:
|
|
8156
|
+
return box2d.instance.castObject(o, box2d.instance.b2PulleyJoint);
|
|
8157
|
+
case box2d.instance.e_mouseJoint:
|
|
8158
|
+
return box2d.instance.castObject(o, box2d.instance.b2MouseJoint);
|
|
8159
|
+
case box2d.instance.e_gearJoint:
|
|
8160
|
+
return box2d.instance.castObject(o, box2d.instance.b2GearJoint);
|
|
8161
|
+
case box2d.instance.e_wheelJoint:
|
|
8162
|
+
return box2d.instance.castObject(o, box2d.instance.b2WheelJoint);
|
|
8163
|
+
case box2d.instance.e_weldJoint:
|
|
8164
|
+
return box2d.instance.castObject(o, box2d.instance.b2WeldJoint);
|
|
8165
|
+
case box2d.instance.e_frictionJoint:
|
|
8166
|
+
return box2d.instance.castObject(o, box2d.instance.b2FrictionJoint);
|
|
8167
|
+
case box2d.instance.e_ropeJoint:
|
|
8168
|
+
return box2d.instance.castObject(o, box2d.instance.b2RopeJoint);
|
|
8169
|
+
case box2d.instance.e_motorJoint:
|
|
8170
|
+
return box2d.instance.castObject(o, box2d.instance.b2MotorJoint);
|
|
8171
|
+
}
|
|
8172
|
+
|
|
8173
|
+
ASSERT(false, 'Unknown box2d object type');
|
|
8174
|
+
}
|
|
8175
|
+
}
|
|
8176
|
+
|
|
8177
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8178
|
+
/** Box2d Init - Call with await before starting LittleJS to init box2d
|
|
8179
|
+
* @return {Promise<Box2dPlugin>}
|
|
8180
|
+
* @memberof Box2D */
|
|
8181
|
+
async function box2dInit()
|
|
8182
|
+
{
|
|
8183
|
+
// load box2d
|
|
8184
|
+
new Box2dPlugin(await Box2D());
|
|
8185
|
+
setupDebugDraw();
|
|
8186
|
+
engineAddPlugin(box2dUpdate, box2dRender);
|
|
8187
|
+
return box2d;
|
|
8188
|
+
|
|
8189
|
+
// add the box2d plugin to the engine
|
|
8190
|
+
function box2dUpdate()
|
|
8191
|
+
{
|
|
8192
|
+
if (!paused)
|
|
8193
|
+
box2d.step();
|
|
8194
|
+
}
|
|
8195
|
+
function box2dRender()
|
|
8196
|
+
{
|
|
8197
|
+
if (box2dDebug || debugPhysics && debugOverlay)
|
|
8198
|
+
box2d.world.DrawDebugData();
|
|
8199
|
+
}
|
|
8200
|
+
|
|
8201
|
+
// box2d debug drawing
|
|
8202
|
+
function setupDebugDraw()
|
|
8203
|
+
{
|
|
8204
|
+
// setup debug draw
|
|
8205
|
+
const debugDraw = new box2d.instance.JSDraw();
|
|
8206
|
+
const box2dColor = (c)=> new Color(c.get_r(), c.get_g(), c.get_b());
|
|
8207
|
+
const box2dColorPointer = (c)=>
|
|
8208
|
+
box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
|
|
8209
|
+
const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
|
|
8210
|
+
const getPointsList = (vertices, vertexCount) =>
|
|
8211
|
+
{
|
|
8212
|
+
const points = [];
|
|
8213
|
+
for (let i=vertexCount; i--;)
|
|
8214
|
+
points.push(box2d.vec2FromPointer(vertices+i*8));
|
|
8215
|
+
return points;
|
|
8216
|
+
}
|
|
8217
|
+
debugDraw.DrawSegment = function(point1, point2, color)
|
|
8218
|
+
{
|
|
8219
|
+
color = getDebugColor(color);
|
|
8220
|
+
point1 = box2d.vec2FromPointer(point1);
|
|
8221
|
+
point2 = box2d.vec2FromPointer(point2);
|
|
8222
|
+
box2d.drawLine(vec2(), 0, point1, point2, color, undefined, overlayContext);
|
|
8223
|
+
};
|
|
8224
|
+
debugDraw.DrawPolygon = function(vertices, vertexCount, color)
|
|
8225
|
+
{
|
|
8226
|
+
color = getDebugColor(color);
|
|
8227
|
+
const points = getPointsList(vertices, vertexCount);
|
|
8228
|
+
box2d.drawPoly(vec2(), 0, points, undefined, color, undefined, overlayContext);
|
|
8229
|
+
};
|
|
8230
|
+
debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
|
|
8231
|
+
{
|
|
8232
|
+
color = getDebugColor(color);
|
|
8233
|
+
const points = getPointsList(vertices, vertexCount);
|
|
8234
|
+
box2d.drawPoly(vec2(), 0, points, color, color, undefined, overlayContext);
|
|
8235
|
+
};
|
|
8236
|
+
debugDraw.DrawCircle = function(center, radius, color)
|
|
8237
|
+
{
|
|
8238
|
+
color = getDebugColor(color);
|
|
8239
|
+
center = box2d.vec2FromPointer(center);
|
|
8240
|
+
box2d.drawCircle(center, radius, undefined, color, undefined, overlayContext);
|
|
8241
|
+
};
|
|
8242
|
+
debugDraw.DrawSolidCircle = function(center, radius, axis, color)
|
|
8243
|
+
{
|
|
8244
|
+
color = getDebugColor(color);
|
|
8245
|
+
center = box2d.vec2FromPointer(center);
|
|
8246
|
+
axis = box2d.vec2FromPointer(axis).scale(radius);
|
|
8247
|
+
box2d.drawCircle(center, radius, color, color, undefined, overlayContext);
|
|
8248
|
+
box2d.drawLine(center, 0, vec2(), axis, color, undefined, overlayContext);
|
|
8249
|
+
};
|
|
8250
|
+
debugDraw.DrawTransform = function(transform)
|
|
8251
|
+
{
|
|
8252
|
+
transform = box2d.instance.wrapPointer(transform, box2d.instance.b2Transform);
|
|
8253
|
+
const pos = vec2(transform.get_p());
|
|
8254
|
+
const angle = -transform.get_q().GetAngle();
|
|
8255
|
+
const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
|
|
8256
|
+
const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
|
|
8257
|
+
box2d.drawLine(pos, angle, vec2(), p1, c1, undefined, overlayContext);
|
|
8258
|
+
box2d.drawLine(pos, angle, vec2(), p2, c2, undefined, overlayContext);
|
|
8259
|
+
}
|
|
8260
|
+
|
|
8261
|
+
debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
|
|
8262
|
+
debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);
|
|
8263
|
+
//debugDraw.AppendFlags(box2d.instance.b2Draw.e_aabbBit);
|
|
8264
|
+
//debugDraw.AppendFlags(box2d.instance.b2Draw.e_pairBit);
|
|
8265
|
+
//debugDraw.AppendFlags(box2d.instance.b2Draw.e_centerOfMassBit);
|
|
8266
|
+
box2d.world.SetDebugDraw(debugDraw);
|
|
8267
|
+
}
|
|
8268
|
+
}
|
|
8269
|
+
|