littlejsengine 1.11.13 → 1.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/littlejs.d.ts +1418 -109
- package/dist/littlejs.esm.js +3495 -581
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +3258 -399
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +3147 -379
- package/examples/box2d/game.js +73 -45
- package/examples/box2d/gameObjects.js +96 -92
- package/examples/box2d/index.html +2 -7
- package/examples/box2d/scenes.js +82 -92
- package/examples/breakout/game.js +62 -30
- 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 +25 -19
- package/examples/htmlMenu/index.html +5 -7
- package/examples/index.html +2 -3
- 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 +71 -48
- 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 +58 -42
- package/examples/puzzle/index.html +1 -3
- package/examples/shorts/base.html +2 -2
- package/examples/shorts/particles.js +1 -1
- package/examples/shorts/platformer.js +1 -1
- package/examples/shorts/tileLayer.js +5 -6
- package/examples/shorts/tiles.png +0 -0
- package/examples/starter/build.js +4 -4
- package/examples/starter/game.js +9 -12
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +44 -43
- 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 +33 -36
- package/examples/uiSystem/index.html +1 -5
- package/package.json +3 -3
- package/plugins/box2d.js +1556 -640
- package/plugins/{Box2D_v2.3.1_min.wasm.js → box2d.wasm.js} +1 -1
- package/plugins/newgrounds.js +7 -8
- package/plugins/pluginExport.js +50 -0
- package/plugins/postProcess.js +94 -93
- package/plugins/uiSystem.js +145 -161
- package/plugins/zzfxm.js +163 -0
- package/reference.md +29 -27
- package/src/engine.js +15 -20
- package/src/engineAudio.js +74 -204
- package/src/engineBuild.js +29 -4
- package/src/engineDebug.js +105 -9
- package/src/engineDraw.js +27 -14
- package/src/engineExport.js +12 -8
- package/src/engineInput.js +3 -1
- package/src/engineObject.js +18 -14
- package/src/engineParticles.js +6 -1
- package/src/engineRelease.js +6 -1
- package/src/engineSettings.js +8 -8
- package/src/engineTileLayer.js +179 -96
- package/src/engineUtilities.js +5 -11
- package/src/engineWebGL.js +19 -7
- package/examples/starter/build/index.html +0 -2
- package/examples/starter/build/index.js +0 -1
- package/examples/starter/build/tiles.png +0 -0
- package/examples/starter/game.zip +0 -0
- 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/plugins/{Box2D_v2.3.1_min.wasm.wasm → box2d.wasm.wasm} +0 -0
package/dist/littlejs.esm.js
CHANGED
|
@@ -77,8 +77,10 @@ function ASSERT(assert, output)
|
|
|
77
77
|
* @memberof Debug */
|
|
78
78
|
function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
|
|
79
79
|
{
|
|
80
|
+
if (typeof size == 'number')
|
|
81
|
+
size = vec2(size); // allow passing in floats
|
|
80
82
|
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
81
|
-
debugPrimitives.push({pos, size
|
|
83
|
+
debugPrimitives.push({pos, size, color, time:new Timer(time), angle, fill});
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
/** Draw a debug poly in world space
|
|
@@ -202,17 +204,17 @@ function debugSaveDataURL(dataURL, filename)
|
|
|
202
204
|
* @memberof Debug */
|
|
203
205
|
function debugShowErrors()
|
|
204
206
|
{
|
|
205
|
-
onunhandledrejection = (event)=>showError(event.reason);
|
|
206
|
-
onerror = (event, source, lineno, colno)=>
|
|
207
|
-
showError(`${event}\n${source}\nLn ${lineno}, Col ${colno}`);
|
|
208
|
-
|
|
209
207
|
const showError = (message)=>
|
|
210
208
|
{
|
|
211
209
|
// replace entire page with error message
|
|
212
210
|
document.body.style.display = '';
|
|
213
211
|
document.body.style.backgroundColor = '#111';
|
|
214
|
-
document.body.innerHTML = `<pre style=color:#f00;font-size:50px>` + message;
|
|
212
|
+
document.body.innerHTML = `<pre style=color:#f00;font-size:50px;white-space:pre-wrap>` + message;
|
|
215
213
|
}
|
|
214
|
+
onunhandledrejection = (event)=>
|
|
215
|
+
showError(event.reason.stack || event.reason);
|
|
216
|
+
onerror = (message, source, lineno, colno)=>
|
|
217
|
+
showError(`${message}\n${source}\nLn ${lineno}, Col ${colno}`);
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -245,11 +247,16 @@ function debugUpdate()
|
|
|
245
247
|
debugRaycast = !debugRaycast;
|
|
246
248
|
if (keyWasPressed('Digit5'))
|
|
247
249
|
debugScreenshot();
|
|
250
|
+
if (keyWasPressed('Digit6'))
|
|
251
|
+
debugVideoCaptureIsActive() ? debugVideoCaptureStop() : debugVideoCaptureStart();
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
254
|
|
|
251
255
|
function debugRender()
|
|
252
256
|
{
|
|
257
|
+
if (debugVideoCaptureIsActive())
|
|
258
|
+
return; // don't show debug info when capturing video
|
|
259
|
+
|
|
253
260
|
glCopyToContext(mainContext);
|
|
254
261
|
|
|
255
262
|
if (debugTakeScreenshot)
|
|
@@ -327,11 +334,9 @@ function debugRender()
|
|
|
327
334
|
}
|
|
328
335
|
}
|
|
329
336
|
|
|
330
|
-
if (
|
|
337
|
+
if (tileCollisionLayers.length) // show floored tile pick if there is tile collision
|
|
331
338
|
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(0,0,1,.5), 0, false);
|
|
332
339
|
mainContext = saveContext;
|
|
333
|
-
|
|
334
|
-
//glCopyToContext(mainContext = saveContext);
|
|
335
340
|
}
|
|
336
341
|
|
|
337
342
|
{
|
|
@@ -444,6 +449,7 @@ function debugRender()
|
|
|
444
449
|
overlayContext.fillText('4: Debug Raycasts', x, y += h);
|
|
445
450
|
overlayContext.fillStyle = '#fff';
|
|
446
451
|
overlayContext.fillText('5: Save Screenshot', x, y += h);
|
|
452
|
+
overlayContext.fillText('6: Capture Video', x, y += h);
|
|
447
453
|
|
|
448
454
|
let keysPressed = '';
|
|
449
455
|
for(const i in inputData[0])
|
|
@@ -472,6 +478,96 @@ function debugRender()
|
|
|
472
478
|
|
|
473
479
|
overlayContext.restore();
|
|
474
480
|
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
484
|
+
// video capture - records video and audio at 60 fps using MediaRecorder API
|
|
485
|
+
|
|
486
|
+
// internal variables used to capture video
|
|
487
|
+
let debugVideoCapture, debugVideoCaptureTrack, debugVideoCaptureIcon, debugVideoCaptureTimer;
|
|
488
|
+
|
|
489
|
+
/** Check if video capture is active
|
|
490
|
+
* @memberof Debug */
|
|
491
|
+
function debugVideoCaptureIsActive() { return !!debugVideoCapture; }
|
|
492
|
+
|
|
493
|
+
/** Start capturing video
|
|
494
|
+
* @memberof Debug */
|
|
495
|
+
function debugVideoCaptureStart()
|
|
496
|
+
{
|
|
497
|
+
if (debugVideoCaptureIsActive())
|
|
498
|
+
return; // already recording
|
|
499
|
+
|
|
500
|
+
// captureStream passing in 0 to only capture when requestFrame() is called
|
|
501
|
+
const stream = mainCanvas.captureStream(0);
|
|
502
|
+
const chunks = [];
|
|
503
|
+
debugVideoCaptureTrack = stream.getVideoTracks()[0];
|
|
504
|
+
if (debugVideoCaptureTrack.applyConstraints)
|
|
505
|
+
debugVideoCaptureTrack.applyConstraints({frameRate:frameRate}); // force 60 fps
|
|
506
|
+
debugVideoCapture = new MediaRecorder(stream, {mimeType:'video/webm;codecs=vp8'});
|
|
507
|
+
debugVideoCapture.ondataavailable = (e)=> chunks.push(e.data);
|
|
508
|
+
debugVideoCapture.onstop = ()=>
|
|
509
|
+
{
|
|
510
|
+
const blob = new Blob(chunks, {type: 'video/webm'});
|
|
511
|
+
const url = URL.createObjectURL(blob);
|
|
512
|
+
downloadLink.download = 'capture.webm';
|
|
513
|
+
downloadLink.href = url;
|
|
514
|
+
downloadLink.click();
|
|
515
|
+
URL.revokeObjectURL(url);
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
if (audioMasterGain)
|
|
519
|
+
{
|
|
520
|
+
// connect to audio master gain node
|
|
521
|
+
const audioStreamDestination = audioContext.createMediaStreamDestination();
|
|
522
|
+
audioMasterGain.connect(audioStreamDestination);
|
|
523
|
+
for (const track of audioStreamDestination.stream.getAudioTracks())
|
|
524
|
+
stream.addTrack(track); // add audio tracks to capture stream
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// start recording
|
|
528
|
+
console.log('Video capture started.');
|
|
529
|
+
debugVideoCapture.start();
|
|
530
|
+
debugVideoCaptureTimer = new Timer(0);
|
|
531
|
+
|
|
532
|
+
if (!debugVideoCaptureIcon)
|
|
533
|
+
{
|
|
534
|
+
// create recording icon to show it is capturing video
|
|
535
|
+
debugVideoCaptureIcon = document.createElement('div');
|
|
536
|
+
debugVideoCaptureIcon.style.position = 'absolute';
|
|
537
|
+
debugVideoCaptureIcon.style.padding = '9px';
|
|
538
|
+
debugVideoCaptureIcon.style.color = '#f00';
|
|
539
|
+
debugVideoCaptureIcon.style.font = '50px monospace';
|
|
540
|
+
document.body.appendChild(debugVideoCaptureIcon);
|
|
541
|
+
}
|
|
542
|
+
// show recording icon
|
|
543
|
+
debugVideoCaptureIcon.textContent = '';
|
|
544
|
+
debugVideoCaptureIcon.style.display = '';
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** Stop capturing video and save to disk
|
|
548
|
+
* @memberof Debug */
|
|
549
|
+
function debugVideoCaptureStop()
|
|
550
|
+
{
|
|
551
|
+
if (!debugVideoCaptureIsActive())
|
|
552
|
+
return; // not recording
|
|
553
|
+
|
|
554
|
+
// stop recording
|
|
555
|
+
console.log(`Video capture ended. ${debugVideoCaptureTimer.get().toFixed(2)} seconds recorded.`);
|
|
556
|
+
debugVideoCapture.stop();
|
|
557
|
+
debugVideoCapture = 0;
|
|
558
|
+
debugVideoCaptureIcon.style.display = 'none';
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// update video capture, called automatically by engine
|
|
562
|
+
function debugVideoCaptureUpdate()
|
|
563
|
+
{
|
|
564
|
+
if (!debugVideoCaptureIsActive())
|
|
565
|
+
return; // not recording
|
|
566
|
+
|
|
567
|
+
// save the video frame
|
|
568
|
+
combineCanvases();
|
|
569
|
+
debugVideoCaptureTrack.requestFrame();
|
|
570
|
+
debugVideoCaptureIcon.textContent = '● REC ' + formatTime(debugVideoCaptureTimer);
|
|
475
571
|
}
|
|
476
572
|
/**
|
|
477
573
|
* LittleJS Utility Classes and Functions
|
|
@@ -566,7 +662,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
566
662
|
* @returns {number}
|
|
567
663
|
* @memberof Utilities */
|
|
568
664
|
function lerpWrap(percent, valueA, valueB, wrapSize=1)
|
|
569
|
-
{ return
|
|
665
|
+
{ return valueA + clamp(percent) * distanceWrap(valueB, valueA, wrapSize); }
|
|
570
666
|
|
|
571
667
|
/** Returns signed wrapped distance between the two angles passed in
|
|
572
668
|
* @param {number} angleA
|
|
@@ -764,23 +860,17 @@ class RandomGenerator
|
|
|
764
860
|
///////////////////////////////////////////////////////////////////////////////
|
|
765
861
|
|
|
766
862
|
/**
|
|
767
|
-
* Create a 2d vector, can take
|
|
768
|
-
* @param {
|
|
769
|
-
* @param {number} [y]
|
|
863
|
+
* Create a 2d vector, can take 1 or 2 scalar values
|
|
864
|
+
* @param {number} [x]
|
|
865
|
+
* @param {number} [y] - if y is undefined, x is used for both
|
|
770
866
|
* @return {Vector2}
|
|
771
867
|
* @example
|
|
772
868
|
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
773
|
-
* let b = vec2(a); // copy a into b
|
|
774
869
|
* a = vec2(5); // set a to (5, 5)
|
|
775
870
|
* b = vec2(); // set b to (0, 0)
|
|
776
871
|
* @memberof Utilities
|
|
777
872
|
*/
|
|
778
|
-
function vec2(x=0, y)
|
|
779
|
-
{
|
|
780
|
-
return typeof x == 'number' ?
|
|
781
|
-
new Vector2(x, y == undefined? x : y) :
|
|
782
|
-
new Vector2(x.x, x.y);
|
|
783
|
-
}
|
|
873
|
+
function vec2(x=0, y) { return new Vector2(x, y == undefined? x : y); }
|
|
784
874
|
|
|
785
875
|
/**
|
|
786
876
|
* Check if object is a valid Vector2
|
|
@@ -1438,7 +1528,7 @@ let canvasMaxSize = vec2(1920, 1080);
|
|
|
1438
1528
|
* @memberof Settings */
|
|
1439
1529
|
let canvasFixedSize = vec2();
|
|
1440
1530
|
|
|
1441
|
-
/** Use nearest neighbor scaling
|
|
1531
|
+
/** Use nearest neighbor canvas scaling for more pixelated look
|
|
1442
1532
|
* - Must be set before startup to take effect
|
|
1443
1533
|
* - If enabled sets css image-rendering:pixelated
|
|
1444
1534
|
* @type {boolean}
|
|
@@ -1548,11 +1638,11 @@ let objectDefaultFriction = .8;
|
|
|
1548
1638
|
* @memberof Settings */
|
|
1549
1639
|
let objectMaxSpeed = 1;
|
|
1550
1640
|
|
|
1551
|
-
/** How much gravity to apply to objects
|
|
1552
|
-
* @type {
|
|
1641
|
+
/** How much gravity to apply to objects, negative Y is down
|
|
1642
|
+
* @type {Vector2}
|
|
1553
1643
|
* @default
|
|
1554
1644
|
* @memberof Settings */
|
|
1555
|
-
let gravity =
|
|
1645
|
+
let gravity = vec2();
|
|
1556
1646
|
|
|
1557
1647
|
/** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters)
|
|
1558
1648
|
* @type {number}
|
|
@@ -1778,8 +1868,8 @@ function setObjectDefaultFriction(friction) { objectDefaultFriction = friction;
|
|
|
1778
1868
|
* @memberof Settings */
|
|
1779
1869
|
function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
|
|
1780
1870
|
|
|
1781
|
-
/** Set how much gravity to apply to objects
|
|
1782
|
-
* @param {
|
|
1871
|
+
/** Set how much gravity to apply to objects
|
|
1872
|
+
* @param {Vector2} newGravity
|
|
1783
1873
|
* @memberof Settings */
|
|
1784
1874
|
function setGravity(newGravity) { gravity = newGravity; }
|
|
1785
1875
|
|
|
@@ -1844,8 +1934,8 @@ function setSoundEnable(enable) { soundEnable = enable; }
|
|
|
1844
1934
|
function setSoundVolume(volume)
|
|
1845
1935
|
{
|
|
1846
1936
|
soundVolume = volume;
|
|
1847
|
-
if (soundEnable && !headlessMode &&
|
|
1848
|
-
|
|
1937
|
+
if (soundEnable && !headlessMode && audioMasterGain)
|
|
1938
|
+
audioMasterGain.gain.value = volume; // update gain immediately
|
|
1849
1939
|
}
|
|
1850
1940
|
|
|
1851
1941
|
/** Set default range where sound no longer plays
|
|
@@ -2047,7 +2137,10 @@ class EngineObject
|
|
|
2047
2137
|
this.velocity.x *= this.damping;
|
|
2048
2138
|
this.velocity.y *= this.damping;
|
|
2049
2139
|
if (this.mass) // don't apply gravity to static objects
|
|
2050
|
-
|
|
2140
|
+
{
|
|
2141
|
+
this.velocity.x += gravity.x * this.gravityScale;
|
|
2142
|
+
this.velocity.y += gravity.y * this.gravityScale;
|
|
2143
|
+
}
|
|
2051
2144
|
this.pos.x += this.velocity.x;
|
|
2052
2145
|
this.pos.y += this.velocity.y;
|
|
2053
2146
|
this.angle += this.angleVelocity *= this.angleDamping;
|
|
@@ -2062,9 +2155,9 @@ class EngineObject
|
|
|
2062
2155
|
if (this.groundObject)
|
|
2063
2156
|
{
|
|
2064
2157
|
// apply friction in local space of ground object
|
|
2065
|
-
const
|
|
2066
|
-
|
|
2067
|
-
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) *
|
|
2158
|
+
const friction = max(this.friction, this.groundObject.friction);
|
|
2159
|
+
const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
|
|
2160
|
+
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
|
|
2068
2161
|
this.groundObject = undefined;
|
|
2069
2162
|
//debugOverlay && debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
|
|
2070
2163
|
}
|
|
@@ -2106,7 +2199,7 @@ class EngineObject
|
|
|
2106
2199
|
|
|
2107
2200
|
// check for collision
|
|
2108
2201
|
const sizeBoth = this.size.add(o.size);
|
|
2109
|
-
const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
|
|
2202
|
+
const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity.y; // prefer to push up if small delta
|
|
2110
2203
|
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
2111
2204
|
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
2112
2205
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
@@ -2168,19 +2261,21 @@ class EngineObject
|
|
|
2168
2261
|
if (this.collideTiles)
|
|
2169
2262
|
{
|
|
2170
2263
|
// check collision against tiles
|
|
2171
|
-
|
|
2264
|
+
const hitLayer = tileCollisionTest(this.pos, this.size, this)
|
|
2265
|
+
if (hitLayer)
|
|
2172
2266
|
{
|
|
2173
2267
|
// if already was stuck in collision, don't do anything
|
|
2174
2268
|
// this should not happen unless something starts in collision
|
|
2175
2269
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
2176
2270
|
{
|
|
2177
2271
|
// test which side we bounced off (or both if a corner)
|
|
2178
|
-
const
|
|
2179
|
-
const
|
|
2180
|
-
if (
|
|
2272
|
+
const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
2273
|
+
const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
2274
|
+
if (blockedLayerY || !blockedLayerX)
|
|
2181
2275
|
{
|
|
2182
2276
|
// bounce velocity
|
|
2183
|
-
|
|
2277
|
+
const elasticity = max(this.elasticity, hitLayer.elasticity);
|
|
2278
|
+
this.velocity.y *= -elasticity;
|
|
2184
2279
|
|
|
2185
2280
|
if (wasMovingDown)
|
|
2186
2281
|
{
|
|
@@ -2189,9 +2284,8 @@ class EngineObject
|
|
|
2189
2284
|
const epsilon = .0001;
|
|
2190
2285
|
this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
|
|
2191
2286
|
|
|
2192
|
-
// set ground object
|
|
2193
|
-
|
|
2194
|
-
this.groundObject = this;
|
|
2287
|
+
// set ground object for tile collision
|
|
2288
|
+
this.groundObject = hitLayer;
|
|
2195
2289
|
}
|
|
2196
2290
|
else
|
|
2197
2291
|
{
|
|
@@ -2200,7 +2294,7 @@ class EngineObject
|
|
|
2200
2294
|
this.groundObject = undefined;
|
|
2201
2295
|
}
|
|
2202
2296
|
}
|
|
2203
|
-
if (
|
|
2297
|
+
if (blockedLayerX)
|
|
2204
2298
|
{
|
|
2205
2299
|
// move to previous position and bounce
|
|
2206
2300
|
this.pos.x = oldPos.x;
|
|
@@ -2511,6 +2605,8 @@ class TextureInfo
|
|
|
2511
2605
|
this.image = image;
|
|
2512
2606
|
/** @property {Vector2} - size of the image */
|
|
2513
2607
|
this.size = vec2(image.width, image.height);
|
|
2608
|
+
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
2609
|
+
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
2514
2610
|
/** @property {WebGLTexture} - webgl texture */
|
|
2515
2611
|
this.glTexture = glEnable && glCreateTexture(image);
|
|
2516
2612
|
}
|
|
@@ -2556,19 +2652,19 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
|
2556
2652
|
* @param {Color} [color=(1,1,1,1)] - Color to modulate with
|
|
2557
2653
|
* @param {number} [angle] - Angle to rotate by
|
|
2558
2654
|
* @param {boolean} [mirror] - If true image is flipped along the Y axis
|
|
2559
|
-
* @param {Color} [additiveColor
|
|
2655
|
+
* @param {Color} [additiveColor] - Additive color to be applied if any
|
|
2560
2656
|
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
2561
2657
|
* @param {boolean} [screenSpace=false] - If true the pos and size are in screen space
|
|
2562
2658
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
2563
2659
|
* @memberof Draw */
|
|
2564
2660
|
function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
2565
|
-
angle=0, mirror, additiveColor
|
|
2661
|
+
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
2566
2662
|
{
|
|
2567
2663
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
2568
2664
|
ASSERT(typeof tileInfo !== 'number' || !tileInfo,
|
|
2569
2665
|
'this is an old style calls, to fix replace it with tile(tileIndex, tileSize)');
|
|
2570
2666
|
ASSERT(isVector2(pos) && isVector2(size));
|
|
2571
|
-
ASSERT(isColor(color) && isColor(additiveColor));
|
|
2667
|
+
ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)));
|
|
2572
2668
|
|
|
2573
2669
|
const textureInfo = tileInfo && tileInfo.getTextureInfo();
|
|
2574
2670
|
if (useWebGL)
|
|
@@ -2579,21 +2675,30 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
2579
2675
|
pos = screenToWorld(pos);
|
|
2580
2676
|
size = size.scale(1/cameraScale);
|
|
2581
2677
|
}
|
|
2582
|
-
|
|
2583
2678
|
if (textureInfo)
|
|
2584
2679
|
{
|
|
2585
2680
|
// calculate uvs and render
|
|
2586
|
-
const sizeInverse =
|
|
2681
|
+
const sizeInverse = textureInfo.sizeInverse;
|
|
2587
2682
|
const x = tileInfo.pos.x * sizeInverse.x;
|
|
2588
2683
|
const y = tileInfo.pos.y * sizeInverse.y;
|
|
2589
2684
|
const w = tileInfo.size.x * sizeInverse.x;
|
|
2590
2685
|
const h = tileInfo.size.y * sizeInverse.y;
|
|
2591
|
-
const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
|
|
2592
2686
|
glSetTexture(textureInfo.glTexture);
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2687
|
+
if (tileFixBleedScale)
|
|
2688
|
+
{
|
|
2689
|
+
const tileImageFixBleedX = sizeInverse.x*tileFixBleedScale;
|
|
2690
|
+
const tileImageFixBleedY = sizeInverse.y*tileFixBleedScale;
|
|
2691
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2692
|
+
x + tileImageFixBleedX, y + tileImageFixBleedY,
|
|
2693
|
+
x - tileImageFixBleedX + w, y - tileImageFixBleedY + h,
|
|
2694
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
2695
|
+
}
|
|
2696
|
+
else
|
|
2697
|
+
{
|
|
2698
|
+
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2699
|
+
x, y, x + w, y + h,
|
|
2700
|
+
color.rgbaInt(), additiveColor && additiveColor.rgbaInt());
|
|
2701
|
+
}
|
|
2597
2702
|
}
|
|
2598
2703
|
else
|
|
2599
2704
|
{
|
|
@@ -2805,16 +2910,15 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
2805
2910
|
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, maxWidth=undefined, context=overlayContext)
|
|
2806
2911
|
{
|
|
2807
2912
|
context.fillStyle = color.toString();
|
|
2808
|
-
context.lineWidth = lineWidth;
|
|
2809
2913
|
context.strokeStyle = lineColor.toString();
|
|
2914
|
+
context.lineWidth = lineWidth;
|
|
2810
2915
|
context.textAlign = textAlign;
|
|
2811
2916
|
context.font = size + 'px '+ font;
|
|
2812
2917
|
context.textBaseline = 'middle';
|
|
2813
2918
|
context.lineJoin = 'round';
|
|
2814
2919
|
|
|
2815
|
-
pos = pos.copy();
|
|
2816
|
-
|
|
2817
2920
|
const lines = (text+'').split('\n');
|
|
2921
|
+
pos = pos.copy();
|
|
2818
2922
|
pos.y -= (lines.length-1) * size/2; // center text vertically
|
|
2819
2923
|
lines.forEach(line=>
|
|
2820
2924
|
{
|
|
@@ -2884,7 +2988,10 @@ class FontImage
|
|
|
2884
2988
|
{
|
|
2885
2989
|
// load default font image
|
|
2886
2990
|
if (!engineFontImage)
|
|
2887
|
-
|
|
2991
|
+
{
|
|
2992
|
+
engineFontImage = new Image;
|
|
2993
|
+
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
2994
|
+
}
|
|
2888
2995
|
|
|
2889
2996
|
this.image = image || engineFontImage;
|
|
2890
2997
|
this.tileSize = tileSize;
|
|
@@ -3287,7 +3394,9 @@ function gamepadsUpdate()
|
|
|
3287
3394
|
const button = gamepad.buttons[j];
|
|
3288
3395
|
const wasDown = gamepadIsDown(j,i);
|
|
3289
3396
|
data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
3290
|
-
|
|
3397
|
+
if (!button.value || button.value > .9) // must be a full press
|
|
3398
|
+
if (!i && button.pressed)
|
|
3399
|
+
isUsingGamepad = true;
|
|
3291
3400
|
}
|
|
3292
3401
|
|
|
3293
3402
|
if (gamepadDirectionEmulateStick)
|
|
@@ -3515,16 +3624,15 @@ let audioContext = new AudioContext;
|
|
|
3515
3624
|
/** Master gain node for all audio to pass through
|
|
3516
3625
|
* @type {GainNode}
|
|
3517
3626
|
* @memberof Audio */
|
|
3518
|
-
let
|
|
3627
|
+
let audioMasterGain;
|
|
3519
3628
|
|
|
3520
3629
|
function audioInit()
|
|
3521
3630
|
{
|
|
3522
3631
|
if (!soundEnable || headlessMode) return;
|
|
3523
3632
|
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
audioGainNode.gain.value = soundVolume; // set starting value
|
|
3633
|
+
audioMasterGain = audioContext.createGain();
|
|
3634
|
+
audioMasterGain.connect(audioContext.destination);
|
|
3635
|
+
audioMasterGain.gain.value = soundVolume; // set starting value
|
|
3528
3636
|
}
|
|
3529
3637
|
|
|
3530
3638
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3653,6 +3761,8 @@ class Sound
|
|
|
3653
3761
|
isLoading() { return !this.sampleChannels; }
|
|
3654
3762
|
}
|
|
3655
3763
|
|
|
3764
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3765
|
+
|
|
3656
3766
|
/**
|
|
3657
3767
|
* Sound Wave Object - Stores a wave sound for later use and can be played positionally
|
|
3658
3768
|
* - this can be used to play wave, mp3, and ogg files
|
|
@@ -3704,59 +3814,7 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
3704
3814
|
return new SoundWave(filename,0,0,0, s=>s.play(undefined, volume, 1, 1, loop));
|
|
3705
3815
|
}
|
|
3706
3816
|
|
|
3707
|
-
|
|
3708
|
-
* Music Object - Stores a zzfx music track for later use
|
|
3709
|
-
*
|
|
3710
|
-
* <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
|
|
3711
|
-
* @example
|
|
3712
|
-
* // create some music
|
|
3713
|
-
* const music_example = new Music(
|
|
3714
|
-
* [
|
|
3715
|
-
* [ // instruments
|
|
3716
|
-
* [,0,400] // simple note
|
|
3717
|
-
* ],
|
|
3718
|
-
* [ // patterns
|
|
3719
|
-
* [ // pattern 1
|
|
3720
|
-
* [ // channel 0
|
|
3721
|
-
* 0, -1, // instrument 0, left speaker
|
|
3722
|
-
* 1, 0, 9, 1 // channel notes
|
|
3723
|
-
* ],
|
|
3724
|
-
* [ // channel 1
|
|
3725
|
-
* 0, 1, // instrument 0, right speaker
|
|
3726
|
-
* 0, 12, 17, -1 // channel notes
|
|
3727
|
-
* ]
|
|
3728
|
-
* ],
|
|
3729
|
-
* ],
|
|
3730
|
-
* [0, 0, 0, 0], // sequence, play pattern 0 four times
|
|
3731
|
-
* 90 // BPM
|
|
3732
|
-
* ]);
|
|
3733
|
-
*
|
|
3734
|
-
* // play the music
|
|
3735
|
-
* music_example.play();
|
|
3736
|
-
*/
|
|
3737
|
-
class Music extends Sound
|
|
3738
|
-
{
|
|
3739
|
-
/** Create a music object and cache the zzfx music samples for later use
|
|
3740
|
-
* @param {[Array, Array, Array, number]} zzfxMusic - Array of zzfx music parameters
|
|
3741
|
-
*/
|
|
3742
|
-
constructor(zzfxMusic)
|
|
3743
|
-
{
|
|
3744
|
-
super(undefined);
|
|
3745
|
-
|
|
3746
|
-
if (!soundEnable || headlessMode) return;
|
|
3747
|
-
this.randomness = 0;
|
|
3748
|
-
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
3749
|
-
this.sampleRate = zzfxR;
|
|
3750
|
-
}
|
|
3751
|
-
|
|
3752
|
-
/** Play the music
|
|
3753
|
-
* @param {number} [volume=1] - How much to scale volume by
|
|
3754
|
-
* @param {boolean} [loop] - True if the music should loop
|
|
3755
|
-
* @return {AudioBufferSourceNode} - The audio source node
|
|
3756
|
-
*/
|
|
3757
|
-
playMusic(volume, loop=false)
|
|
3758
|
-
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
3759
|
-
}
|
|
3817
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3760
3818
|
|
|
3761
3819
|
/** Speak text with passed in settings
|
|
3762
3820
|
* @param {string} text - The text to speak
|
|
@@ -3828,7 +3886,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3828
3886
|
// create and connect gain node
|
|
3829
3887
|
gainNode = gainNode || audioContext.createGain();
|
|
3830
3888
|
gainNode.gain.value = volume;
|
|
3831
|
-
gainNode.connect(
|
|
3889
|
+
gainNode.connect(audioMasterGain);
|
|
3832
3890
|
|
|
3833
3891
|
// connect source to stereo panner and gain
|
|
3834
3892
|
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
|
|
@@ -3848,7 +3906,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3848
3906
|
}
|
|
3849
3907
|
|
|
3850
3908
|
///////////////////////////////////////////////////////////////////////////////
|
|
3851
|
-
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.
|
|
3909
|
+
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.2 by Frank Force
|
|
3852
3910
|
|
|
3853
3911
|
/** Generate and play a ZzFX sound
|
|
3854
3912
|
*
|
|
@@ -3890,21 +3948,45 @@ const zzfxR = 44100;
|
|
|
3890
3948
|
*/
|
|
3891
3949
|
function zzfxG
|
|
3892
3950
|
(
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3951
|
+
volume = 1,
|
|
3952
|
+
randomness = .05,
|
|
3953
|
+
frequency = 220,
|
|
3954
|
+
attack = 0,
|
|
3955
|
+
sustain = 0,
|
|
3956
|
+
release = .1,
|
|
3957
|
+
shape = 0,
|
|
3958
|
+
shapeCurve = 1,
|
|
3959
|
+
slide = 0,
|
|
3960
|
+
deltaSlide = 0,
|
|
3961
|
+
pitchJump = 0,
|
|
3962
|
+
pitchJumpTime = 0,
|
|
3963
|
+
repeatTime = 0,
|
|
3964
|
+
noise = 0,
|
|
3965
|
+
modulation = 0,
|
|
3966
|
+
bitCrush = 0,
|
|
3967
|
+
delay = 0,
|
|
3968
|
+
sustainVolume = 1,
|
|
3969
|
+
decay = 0,
|
|
3970
|
+
tremolo = 0,
|
|
3971
|
+
filter = 0
|
|
3898
3972
|
)
|
|
3899
3973
|
{
|
|
3900
|
-
// LJS Note: ZZFX modded so randomness is handled by Sound class
|
|
3901
|
-
|
|
3902
3974
|
// init parameters
|
|
3903
|
-
let
|
|
3975
|
+
let sampleRate = zzfxR,
|
|
3976
|
+
PI2 = PI*2,
|
|
3904
3977
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
3905
3978
|
startFrequency = frequency *=
|
|
3906
|
-
|
|
3907
|
-
|
|
3979
|
+
(1 + rand(randomness,-randomness)) * PI2 / sampleRate,
|
|
3980
|
+
modOffset = 0, // modulation offset
|
|
3981
|
+
repeat = 0, // repeat offset
|
|
3982
|
+
crush = 0, // bit crush offset
|
|
3983
|
+
jump = 1, // pitch jump timer
|
|
3984
|
+
length, // sample length
|
|
3985
|
+
b = [], // sample buffer
|
|
3986
|
+
t = 0, // sample time
|
|
3987
|
+
i = 0, // sample index
|
|
3988
|
+
s = 0, // sample value
|
|
3989
|
+
f, // wave frequency
|
|
3908
3990
|
|
|
3909
3991
|
// biquad LP/HP filter
|
|
3910
3992
|
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
@@ -3914,35 +3996,37 @@ function zzfxG
|
|
|
3914
3996
|
b1 = -(sign(filter) + cos) / a0, b2 = b0,
|
|
3915
3997
|
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
3916
3998
|
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3999
|
+
// scale by sample rate
|
|
4000
|
+
const minAttack = 9; // prevent pop if attack is 0
|
|
4001
|
+
attack = attack * sampleRate || minAttack;
|
|
4002
|
+
decay *= sampleRate;
|
|
4003
|
+
sustain *= sampleRate;
|
|
4004
|
+
release *= sampleRate;
|
|
4005
|
+
delay *= sampleRate;
|
|
4006
|
+
deltaSlide *= 500 * PI2 / sampleRate**3;
|
|
4007
|
+
modulation *= PI2 / sampleRate;
|
|
4008
|
+
pitchJump *= PI2 / sampleRate;
|
|
4009
|
+
pitchJumpTime *= sampleRate;
|
|
4010
|
+
repeatTime = repeatTime * sampleRate | 0;
|
|
3928
4011
|
|
|
3929
4012
|
// generate waveform
|
|
3930
4013
|
for(length = attack + decay + sustain + release + delay | 0;
|
|
3931
|
-
i < length; b[i++] = s * volume)
|
|
4014
|
+
i < length; b[i++] = s * volume) // sample
|
|
3932
4015
|
{
|
|
3933
|
-
if (!(++
|
|
4016
|
+
if (!(++crush%(bitCrush*100|0))) // bit crush
|
|
3934
4017
|
{
|
|
3935
|
-
s = shape? shape>1? shape>2? shape>3?
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
1-
|
|
3940
|
-
Math.
|
|
4018
|
+
s = shape? shape>1? shape>2? shape>3? shape>4? // wave shape
|
|
4019
|
+
(t/PI2%1 < shapeCurve/2? 1 : -1) : // 5 square duty
|
|
4020
|
+
Math.sin(t**3) : // 4 noise
|
|
4021
|
+
Math.max(Math.min(Math.tan(t),1),-1): // 3 tan
|
|
4022
|
+
1-(2*t/PI2%2+2)%2: // 2 saw
|
|
4023
|
+
1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
|
|
4024
|
+
Math.sin(t); // 0 sin
|
|
3941
4025
|
|
|
3942
4026
|
s = (repeatTime ?
|
|
3943
4027
|
1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
|
|
3944
4028
|
: 1) *
|
|
3945
|
-
sign(s)*
|
|
4029
|
+
(shape>4?s:sign(s)*abs(s)**shapeCurve) * // shape curve
|
|
3946
4030
|
(i < attack ? i/attack : // attack
|
|
3947
4031
|
i < attack + decay ? // decay
|
|
3948
4032
|
1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
|
|
@@ -3957,209 +4041,77 @@ function zzfxG
|
|
|
3957
4041
|
(i<length-delay? 1 : (length-i)/delay) * // release delay
|
|
3958
4042
|
b[i-delay|0]/2/volume) : s; // sample delay
|
|
3959
4043
|
|
|
3960
|
-
if (filter)
|
|
4044
|
+
if (filter) // apply filter
|
|
3961
4045
|
s = y1 = b2*x2 + b1*(x2=x1) + b0*(x1=s) - a2*y2 - a1*(y2=y1);
|
|
3962
4046
|
}
|
|
3963
4047
|
|
|
3964
4048
|
f = (frequency += slide += deltaSlide) *// frequency
|
|
3965
|
-
Math.cos(modulation*
|
|
4049
|
+
Math.cos(modulation*modOffset++); // modulation
|
|
3966
4050
|
t += f + f*noise*Math.sin(i**5); // noise
|
|
3967
4051
|
|
|
3968
|
-
if (
|
|
4052
|
+
if (jump && ++jump > pitchJumpTime) // pitch jump
|
|
3969
4053
|
{
|
|
3970
4054
|
frequency += pitchJump; // apply pitch jump
|
|
3971
4055
|
startFrequency += pitchJump; // also apply to start
|
|
3972
|
-
|
|
4056
|
+
jump = 0; // stop pitch jump time
|
|
3973
4057
|
}
|
|
3974
4058
|
|
|
3975
|
-
if (repeatTime && !(++
|
|
4059
|
+
if (repeatTime && !(++repeat % repeatTime)) // repeat
|
|
3976
4060
|
{
|
|
3977
|
-
frequency = startFrequency;
|
|
3978
|
-
slide = startSlide;
|
|
3979
|
-
|
|
4061
|
+
frequency = startFrequency; // reset frequency
|
|
4062
|
+
slide = startSlide; // reset slide
|
|
4063
|
+
jump ||= 1; // reset pitch jump time
|
|
3980
4064
|
}
|
|
3981
4065
|
}
|
|
3982
4066
|
|
|
3983
|
-
return b;
|
|
4067
|
+
return b; // return sample buffer
|
|
3984
4068
|
}
|
|
3985
|
-
|
|
3986
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
3987
|
-
// ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
|
|
3988
|
-
|
|
3989
|
-
/** Generate samples for a ZzFM song with given parameters
|
|
3990
|
-
* @param {Array} instruments - Array of ZzFX sound parameters
|
|
3991
|
-
* @param {Array} patterns - Array of pattern data
|
|
3992
|
-
* @param {Array} sequence - Array of pattern indexes
|
|
3993
|
-
* @param {number} [BPM] - Playback speed of the song in BPM
|
|
3994
|
-
* @return {Array} - Left and right channel sample data
|
|
3995
|
-
* @memberof Audio */
|
|
3996
|
-
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
3997
|
-
{
|
|
3998
|
-
let i, j, k;
|
|
3999
|
-
let instrumentParameters;
|
|
4000
|
-
let note;
|
|
4001
|
-
let sample;
|
|
4002
|
-
let patternChannel;
|
|
4003
|
-
let notFirstBeat;
|
|
4004
|
-
let stop;
|
|
4005
|
-
let instrument;
|
|
4006
|
-
let attenuation;
|
|
4007
|
-
let outSampleOffset;
|
|
4008
|
-
let isSequenceEnd;
|
|
4009
|
-
let sampleOffset = 0;
|
|
4010
|
-
let nextSampleOffset;
|
|
4011
|
-
let sampleBuffer = [];
|
|
4012
|
-
let leftChannelBuffer = [];
|
|
4013
|
-
let rightChannelBuffer = [];
|
|
4014
|
-
let channelIndex = 0;
|
|
4015
|
-
let panning = 0;
|
|
4016
|
-
let hasMore = 1;
|
|
4017
|
-
let sampleCache = {};
|
|
4018
|
-
let beatLength = zzfxR / BPM * 60 >> 2;
|
|
4019
|
-
|
|
4020
|
-
// for each channel in order until there are no more
|
|
4021
|
-
for (; hasMore; channelIndex++) {
|
|
4022
|
-
|
|
4023
|
-
// reset current values
|
|
4024
|
-
sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
|
|
4025
|
-
|
|
4026
|
-
// for each pattern in sequence
|
|
4027
|
-
sequence.forEach((patternIndex, sequenceIndex) => {
|
|
4028
|
-
// get pattern for current channel, use empty 1 note pattern if none found
|
|
4029
|
-
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
4030
|
-
|
|
4031
|
-
// check if there are more channels
|
|
4032
|
-
hasMore |= patterns[patternIndex][channelIndex]&&1;
|
|
4033
|
-
|
|
4034
|
-
// get next offset, use the length of first channel
|
|
4035
|
-
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
|
|
4036
|
-
// for each beat in pattern, plus one extra if end of sequence
|
|
4037
|
-
isSequenceEnd = sequenceIndex == sequence.length - 1;
|
|
4038
|
-
for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
|
|
4039
|
-
|
|
4040
|
-
// <channel-note>
|
|
4041
|
-
note = patternChannel[i];
|
|
4042
|
-
|
|
4043
|
-
// stop if end, different instrument or new note
|
|
4044
|
-
stop = i == patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
4045
|
-
instrument != (patternChannel[0] || 0) || note | 0;
|
|
4046
|
-
|
|
4047
|
-
// fill buffer with samples for previous beat, most cpu intensive part
|
|
4048
|
-
for (j = 0; j < beatLength && notFirstBeat;
|
|
4049
|
-
|
|
4050
|
-
// fade off attenuation at end of beat if stopping note, prevents clicking
|
|
4051
|
-
j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
|
|
4052
|
-
) {
|
|
4053
|
-
// copy sample to stereo buffers with panning
|
|
4054
|
-
sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
|
|
4055
|
-
leftChannelBuffer[k] = (leftChannelBuffer[k] || 0) - sample * panning + sample;
|
|
4056
|
-
rightChannelBuffer[k] = (rightChannelBuffer[k++] || 0) + sample * panning + sample;
|
|
4057
|
-
}
|
|
4058
|
-
|
|
4059
|
-
// set up for next note
|
|
4060
|
-
if (note) {
|
|
4061
|
-
// set attenuation
|
|
4062
|
-
attenuation = note % 1;
|
|
4063
|
-
panning = patternChannel[1] || 0;
|
|
4064
|
-
if (note |= 0) {
|
|
4065
|
-
// get cached sample
|
|
4066
|
-
sampleBuffer = sampleCache[
|
|
4067
|
-
[
|
|
4068
|
-
instrument = patternChannel[sampleOffset = 0] || 0,
|
|
4069
|
-
note
|
|
4070
|
-
]
|
|
4071
|
-
] = sampleCache[[instrument, note]] || (
|
|
4072
|
-
// add sample to cache
|
|
4073
|
-
instrumentParameters = [...instruments[instrument]],
|
|
4074
|
-
instrumentParameters[2] *= 2 ** ((note - 12) / 12),
|
|
4075
|
-
|
|
4076
|
-
// allow negative values to stop notes
|
|
4077
|
-
note > 0 ? zzfxG(...instrumentParameters) : []
|
|
4078
|
-
);
|
|
4079
|
-
}
|
|
4080
|
-
}
|
|
4081
|
-
}
|
|
4082
|
-
|
|
4083
|
-
// update the sample offset
|
|
4084
|
-
outSampleOffset = nextSampleOffset;
|
|
4085
|
-
});
|
|
4086
|
-
}
|
|
4087
|
-
|
|
4088
|
-
return [leftChannelBuffer, rightChannelBuffer];
|
|
4089
|
-
}
|
|
4069
|
+
|
|
4090
4070
|
/**
|
|
4091
4071
|
* LittleJS Tile Layer System
|
|
4092
4072
|
* - Caches arrays of tiles to off screen canvas for fast rendering
|
|
4093
4073
|
* - Unlimited numbers of layers, allocates canvases as needed
|
|
4094
|
-
* - Interfaces with EngineObject for collision
|
|
4095
|
-
* - Collision layer is separate from visible layers
|
|
4096
|
-
* - It is recommended to have a visible layer that matches the collision
|
|
4097
4074
|
* - Tile layers can be drawn to using their context with canvas2d
|
|
4098
4075
|
* - Drawn directly to the main canvas without using WebGL
|
|
4076
|
+
* - Tile layers can also have collision with EngineObjects
|
|
4099
4077
|
* @namespace TileCollision
|
|
4100
4078
|
*/
|
|
4101
4079
|
|
|
4102
4080
|
|
|
4103
4081
|
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
* @memberof TileCollision */
|
|
4107
|
-
let tileCollision = [];
|
|
4108
|
-
|
|
4109
|
-
/** Size of the tile collision layer 2d grid
|
|
4110
|
-
* @type {Vector2}
|
|
4111
|
-
* @memberof TileCollision */
|
|
4112
|
-
let tileCollisionSize = vec2();
|
|
4113
|
-
|
|
4114
|
-
/** Clear and initialize tile collision
|
|
4115
|
-
* @param {Vector2} size - width and height of tile collision 2d grid
|
|
4116
|
-
* @memberof TileCollision */
|
|
4117
|
-
function initTileCollision(size)
|
|
4118
|
-
{
|
|
4119
|
-
tileCollisionSize = size;
|
|
4120
|
-
tileCollision = [];
|
|
4121
|
-
for (let i=tileCollision.length = tileCollisionSize.area(); i--;)
|
|
4122
|
-
tileCollision[i] = 0;
|
|
4123
|
-
}
|
|
4082
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4083
|
+
// Tile Layer System
|
|
4124
4084
|
|
|
4125
|
-
/**
|
|
4126
|
-
* @
|
|
4127
|
-
* @param {number} [data]
|
|
4085
|
+
/** Keep track of all tile layers with collision
|
|
4086
|
+
* @type {Array<TileCollisionLayer>}
|
|
4128
4087
|
* @memberof TileCollision */
|
|
4129
|
-
|
|
4130
|
-
{
|
|
4131
|
-
pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
|
|
4132
|
-
}
|
|
4088
|
+
let tileCollisionLayers = [];
|
|
4133
4089
|
|
|
4134
4090
|
/** Get tile collision data for a given cell in the grid
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4091
|
+
* @param {Vector2} pos
|
|
4092
|
+
* @return {number}
|
|
4093
|
+
* @memberof TileCollision */
|
|
4138
4094
|
function getTileCollisionData(pos)
|
|
4139
4095
|
{
|
|
4140
|
-
|
|
4096
|
+
// check all tile collision layers
|
|
4097
|
+
for (const layer of tileCollisionLayers)
|
|
4098
|
+
if (pos.arrayCheck(layer.size))
|
|
4099
|
+
return layer.getCollisionData(pos);
|
|
4100
|
+
return 0;
|
|
4141
4101
|
}
|
|
4142
4102
|
|
|
4143
|
-
/** Check if
|
|
4103
|
+
/** Check if a tile layer collides with another object
|
|
4144
4104
|
* @param {Vector2} pos
|
|
4145
4105
|
* @param {Vector2} [size=(0,0)]
|
|
4146
4106
|
* @param {EngineObject} [object]
|
|
4147
|
-
* @return {
|
|
4107
|
+
* @return {TileCollisionLayer}
|
|
4148
4108
|
* @memberof TileCollision */
|
|
4149
4109
|
function tileCollisionTest(pos, size=vec2(), object)
|
|
4150
4110
|
{
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
for (let y = minY; y < maxY; ++y)
|
|
4156
|
-
for (let x = minX; x < maxX; ++x)
|
|
4157
|
-
{
|
|
4158
|
-
const tileData = tileCollision[y*tileCollisionSize.x+x];
|
|
4159
|
-
if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
|
|
4160
|
-
return true;
|
|
4161
|
-
}
|
|
4162
|
-
return false;
|
|
4111
|
+
// check all tile collision layers
|
|
4112
|
+
for (const layer of tileCollisionLayers)
|
|
4113
|
+
if (layer.collisionTest(pos, size, object))
|
|
4114
|
+
return layer;
|
|
4163
4115
|
}
|
|
4164
4116
|
|
|
4165
4117
|
/** Return the center of first tile hit, undefined if nothing was hit.
|
|
@@ -4171,49 +4123,17 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
4171
4123
|
* @memberof TileCollision */
|
|
4172
4124
|
function tileCollisionRaycast(posStart, posEnd, object)
|
|
4173
4125
|
{
|
|
4174
|
-
//
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
const flooredPosStart = posStart.floor();
|
|
4181
|
-
|
|
4182
|
-
// setup iteration variables
|
|
4183
|
-
let pos = flooredPosStart;
|
|
4184
|
-
let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
|
|
4185
|
-
let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
|
|
4186
|
-
|
|
4187
|
-
while (true)
|
|
4188
|
-
{
|
|
4189
|
-
// check for tile collision
|
|
4190
|
-
const tileData = getTileCollisionData(pos);
|
|
4191
|
-
if (tileData && (!object || object.collideWithTile(tileData, pos)))
|
|
4192
|
-
{
|
|
4193
|
-
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
4194
|
-
debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
|
|
4195
|
-
return pos.add(vec2(.5));
|
|
4196
|
-
}
|
|
4197
|
-
|
|
4198
|
-
// check if past the end
|
|
4199
|
-
if (xi > totalLength && yi > totalLength)
|
|
4200
|
-
break;
|
|
4201
|
-
|
|
4202
|
-
// get coordinates of the next tile to check
|
|
4203
|
-
if (xi > yi)
|
|
4204
|
-
pos.y += sign(delta.y), yi += unit.y;
|
|
4205
|
-
else
|
|
4206
|
-
pos.x += sign(delta.x), xi += unit.x;
|
|
4126
|
+
// check all tile collision layers
|
|
4127
|
+
for (const layer of tileCollisionLayers)
|
|
4128
|
+
{
|
|
4129
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, object)
|
|
4130
|
+
if (hitPos)
|
|
4131
|
+
return hitPos;
|
|
4207
4132
|
}
|
|
4208
|
-
|
|
4209
|
-
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
4210
4133
|
}
|
|
4211
4134
|
|
|
4212
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
4213
|
-
// Tile Layer Rendering System
|
|
4214
|
-
|
|
4215
4135
|
/**
|
|
4216
|
-
* Tile layer data object stores info about how to
|
|
4136
|
+
* Tile layer data object stores info about how to draw a tile
|
|
4217
4137
|
* @example
|
|
4218
4138
|
* // create tile layer data with tile index 0 and random orientation and color
|
|
4219
4139
|
* const tileIndex = 0;
|
|
@@ -4245,28 +4165,27 @@ class TileLayerData
|
|
|
4245
4165
|
clear() { this.tile = this.direction = 0; this.mirror = false; this.color = new Color; }
|
|
4246
4166
|
}
|
|
4247
4167
|
|
|
4168
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4248
4169
|
/**
|
|
4249
4170
|
* Tile Layer - cached rendering system for tile layers
|
|
4250
4171
|
* - Each Tile layer is rendered to an off screen canvas
|
|
4251
4172
|
* - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
4252
4173
|
* - Some devices like mobile phones are limited to 4k texture resolution
|
|
4253
|
-
* -
|
|
4174
|
+
* - For with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
4254
4175
|
* @extends EngineObject
|
|
4255
4176
|
* @example
|
|
4256
|
-
*
|
|
4257
|
-
* initTileCollision(vec2(200,100));
|
|
4258
|
-
* const tileLayer = new TileLayer();
|
|
4177
|
+
* const tileLayer = new TileLayer(vec2(), vec2(200,100));
|
|
4259
4178
|
*/
|
|
4260
4179
|
class TileLayer extends EngineObject
|
|
4261
4180
|
{
|
|
4262
4181
|
/** Create a tile layer object
|
|
4263
|
-
* @param {Vector2} [position=(0,0)]
|
|
4264
|
-
* @param {Vector2} [size=
|
|
4265
|
-
* @param {TileInfo} [tileInfo]
|
|
4266
|
-
* @param {Vector2} [scale=(1,1)]
|
|
4267
|
-
* @param {number} [renderOrder]
|
|
4182
|
+
* @param {Vector2} [position=(0,0)] - World space position
|
|
4183
|
+
* @param {Vector2} [size=(1,1)] - World space size
|
|
4184
|
+
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
4185
|
+
* @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
|
|
4186
|
+
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
4268
4187
|
*/
|
|
4269
|
-
constructor(position, size
|
|
4188
|
+
constructor(position, size, tileInfo=tile(), scale=vec2(1), renderOrder=0)
|
|
4270
4189
|
{
|
|
4271
4190
|
super(position, size, tileInfo, 0, undefined, renderOrder);
|
|
4272
4191
|
|
|
@@ -4278,6 +4197,10 @@ class TileLayer extends EngineObject
|
|
|
4278
4197
|
this.scale = scale;
|
|
4279
4198
|
/** @property {boolean} - If true this layer will render to overlay canvas and appear above all objects */
|
|
4280
4199
|
this.isOverlay = false;
|
|
4200
|
+
// set no friction by default, applied friction is max of both objects
|
|
4201
|
+
this.friction = 0;
|
|
4202
|
+
// set no elasticity by default, applied elasticity is max of both objects
|
|
4203
|
+
this.elasticity = 0;
|
|
4281
4204
|
|
|
4282
4205
|
// init tile data
|
|
4283
4206
|
this.data = [];
|
|
@@ -4474,23 +4397,163 @@ class TileLayer extends EngineObject
|
|
|
4474
4397
|
* @param {number} [angle=0] */
|
|
4475
4398
|
drawRect(pos, size, color, angle)
|
|
4476
4399
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
4477
|
-
}
|
|
4478
|
-
|
|
4479
|
-
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4402
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4403
|
+
/**
|
|
4404
|
+
* Tile Collision Layer - a tile layer with collision
|
|
4405
|
+
* - adds collision data and functions to TileLayer
|
|
4406
|
+
* - there can be multiple tile collision layers
|
|
4407
|
+
* - tile collison layers should not overlap each other
|
|
4408
|
+
* @extends TileLayer
|
|
4480
4409
|
*/
|
|
4410
|
+
class TileCollisionLayer extends TileLayer
|
|
4411
|
+
{
|
|
4412
|
+
/** Create a tile layer object
|
|
4413
|
+
* @param {Vector2} [position=(0,0)] - World space position
|
|
4414
|
+
* @param {Vector2} [size=(0,0)] - World space size
|
|
4415
|
+
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
4416
|
+
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
4417
|
+
*/
|
|
4418
|
+
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
4419
|
+
{
|
|
4420
|
+
const scale = vec2(1); // collision layers are not scaled
|
|
4421
|
+
super(position, size.floor(), tileInfo, scale, renderOrder);
|
|
4481
4422
|
|
|
4423
|
+
/** @property {Array<number>} - The tile collision grid */
|
|
4424
|
+
this.collisionData = [];
|
|
4425
|
+
this.initCollision(this.size);
|
|
4482
4426
|
|
|
4427
|
+
// keep track of all collision layers
|
|
4428
|
+
tileCollisionLayers.push(this);
|
|
4429
|
+
}
|
|
4483
4430
|
|
|
4484
|
-
/**
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4431
|
+
/** Destroy this collision layer */
|
|
4432
|
+
destroy()
|
|
4433
|
+
{
|
|
4434
|
+
if (this.destroyed)
|
|
4435
|
+
return;
|
|
4436
|
+
|
|
4437
|
+
// remove from collision layers array and destroy
|
|
4438
|
+
const index = tileCollisionLayers.indexOf(this);
|
|
4439
|
+
ASSERT(index >= 0, 'tile collision layer not found in array');
|
|
4440
|
+
tileCollisionLayers.splice(index, 1);
|
|
4441
|
+
super.destroy();
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
/** Clear and initialize tile collision to new size
|
|
4445
|
+
* @param {Vector2} size - width and height of tile collision 2d grid */
|
|
4446
|
+
initCollision(size)
|
|
4447
|
+
{
|
|
4448
|
+
this.size = size.floor();
|
|
4449
|
+
this.collisionData = [];
|
|
4450
|
+
this.collisionData.length = size.area();
|
|
4451
|
+
this.collisionData.fill(0);
|
|
4452
|
+
}
|
|
4453
|
+
|
|
4454
|
+
/** Set tile collision data for a given cell in the grid
|
|
4455
|
+
* @param {Vector2} pos
|
|
4456
|
+
* @param {number} [data] */
|
|
4457
|
+
setCollisionData(pos, data=1)
|
|
4458
|
+
{
|
|
4459
|
+
const i = (pos.y|0)*this.size.x + pos.x|0;
|
|
4460
|
+
pos.arrayCheck(this.size) && (this.collisionData[i] = data);
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
/** Get tile collision data for a given cell in the grid
|
|
4464
|
+
* @param {Vector2} pos
|
|
4465
|
+
* @return {number} */
|
|
4466
|
+
getCollisionData(pos)
|
|
4467
|
+
{
|
|
4468
|
+
const i = (pos.y|0)*this.size.x + pos.x|0;
|
|
4469
|
+
return pos.arrayCheck(this.size) ? this.collisionData[i] : 0;
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
/** Check if collision with another object should occur
|
|
4473
|
+
* @param {Vector2} pos
|
|
4474
|
+
* @param {Vector2} [size=(0,0)]
|
|
4475
|
+
* @param {EngineObject} [object]
|
|
4476
|
+
* @return {boolean} */
|
|
4477
|
+
collisionTest(pos, size=vec2(), object)
|
|
4478
|
+
{
|
|
4479
|
+
const minX = max(pos.x - size.x/2|0, 0);
|
|
4480
|
+
const minY = max(pos.y - size.y/2|0, 0);
|
|
4481
|
+
const maxX = min(pos.x + size.x/2, this.size.x);
|
|
4482
|
+
const maxY = min(pos.y + size.y/2, this.size.y);
|
|
4483
|
+
for (let y = minY; y < maxY; ++y)
|
|
4484
|
+
for (let x = minX; x < maxX; ++x)
|
|
4485
|
+
{
|
|
4486
|
+
// check if the object should collide with this tile
|
|
4487
|
+
const tileData = this.collisionData[y*this.size.x+x];
|
|
4488
|
+
if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
|
|
4489
|
+
return true;
|
|
4490
|
+
}
|
|
4491
|
+
return false;
|
|
4492
|
+
}
|
|
4493
|
+
|
|
4494
|
+
/** Return the center of first tile hit, undefined if nothing was hit.
|
|
4495
|
+
* This does not return the exact intersection, but the center of the tile hit.
|
|
4496
|
+
* @param {Vector2} posStart
|
|
4497
|
+
* @param {Vector2} posEnd
|
|
4498
|
+
* @param {EngineObject} [object]
|
|
4499
|
+
* @return {Vector2} */
|
|
4500
|
+
collisionRaycast(posStart, posEnd, object)
|
|
4501
|
+
{
|
|
4502
|
+
// test if a ray collides with tiles from start to end
|
|
4503
|
+
// todo: a way to get the exact hit point, it must still be inside the hit tile
|
|
4504
|
+
const delta = posEnd.subtract(posStart);
|
|
4505
|
+
const totalLength = delta.length();
|
|
4506
|
+
const normalizedDelta = delta.normalize();
|
|
4507
|
+
const unit = vec2(abs(1/normalizedDelta.x), abs(1/normalizedDelta.y));
|
|
4508
|
+
const flooredPosStart = posStart.floor();
|
|
4509
|
+
|
|
4510
|
+
// setup iteration variables
|
|
4511
|
+
let pos = flooredPosStart;
|
|
4512
|
+
let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
|
|
4513
|
+
let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
|
|
4514
|
+
|
|
4515
|
+
// use line drawing algorithm to test for collisions
|
|
4516
|
+
while (true)
|
|
4517
|
+
{
|
|
4518
|
+
// check for tile collision
|
|
4519
|
+
const tileData = this.getCollisionData(pos);
|
|
4520
|
+
if (tileData && (!object || object.collideWithTile(tileData, pos)))
|
|
4521
|
+
{
|
|
4522
|
+
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
4523
|
+
debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
|
|
4524
|
+
return pos.add(vec2(.5));
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
// check if past the end
|
|
4528
|
+
if (xi > totalLength && yi > totalLength)
|
|
4529
|
+
break;
|
|
4530
|
+
|
|
4531
|
+
// get coordinates of next tile to check
|
|
4532
|
+
if (xi > yi)
|
|
4533
|
+
pos.y += sign(delta.y), yi += unit.y;
|
|
4534
|
+
else
|
|
4535
|
+
pos.x += sign(delta.x), xi += unit.x;
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4538
|
+
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
/**
|
|
4542
|
+
* LittleJS Particle System
|
|
4543
|
+
*/
|
|
4544
|
+
|
|
4545
|
+
|
|
4546
|
+
|
|
4547
|
+
/**
|
|
4548
|
+
* Particle Emitter - Spawns particles with the given settings
|
|
4549
|
+
* @extends EngineObject
|
|
4550
|
+
* @example
|
|
4551
|
+
* // create a particle emitter
|
|
4552
|
+
* let pos = vec2(2,3);
|
|
4553
|
+
* let particleEmitter = new ParticleEmitter
|
|
4554
|
+
* (
|
|
4555
|
+
* pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emitCone
|
|
4556
|
+
* tile(0, 16), // tileInfo
|
|
4494
4557
|
* rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
|
|
4495
4558
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
4496
4559
|
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
@@ -4643,7 +4706,12 @@ class ParticleEmitter extends EngineObject
|
|
|
4643
4706
|
else
|
|
4644
4707
|
this.destroy();
|
|
4645
4708
|
|
|
4646
|
-
|
|
4709
|
+
if (debugParticles)
|
|
4710
|
+
{
|
|
4711
|
+
// show emitter bounds
|
|
4712
|
+
const emitSize = typeof this.emitSize === 'number' ? vec2(this.emitSize) : this.emitSize;
|
|
4713
|
+
debugRect(this.pos, emitSize, '#0f0', 0, this.angle);
|
|
4714
|
+
}
|
|
4647
4715
|
}
|
|
4648
4716
|
|
|
4649
4717
|
/** Spawn one particle
|
|
@@ -5105,12 +5173,12 @@ function glPreRender()
|
|
|
5105
5173
|
|
|
5106
5174
|
// set vertex attributes
|
|
5107
5175
|
let offset = glAdditive = glBatchAdditive = 0;
|
|
5108
|
-
|
|
5176
|
+
const initVertexAttribArray = (name, type, typeSize, size)=>
|
|
5109
5177
|
{
|
|
5110
5178
|
const location = glContext.getAttribLocation(glShader, name);
|
|
5111
5179
|
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
5112
5180
|
const divisor = typeSize && 1; // only if not geometry
|
|
5113
|
-
const normalize = typeSize==1; // only if color
|
|
5181
|
+
const normalize = typeSize == 1; // only if color
|
|
5114
5182
|
glContext.enableVertexAttribArray(location);
|
|
5115
5183
|
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
5116
5184
|
glContext.vertexAttribDivisor(location, divisor);
|
|
@@ -5209,14 +5277,14 @@ function glCreateTexture(image)
|
|
|
5209
5277
|
const texture = glContext.createTexture();
|
|
5210
5278
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
5211
5279
|
if (image && image.width)
|
|
5212
|
-
|
|
5280
|
+
glSetTextureData(texture, image);
|
|
5213
5281
|
else
|
|
5214
5282
|
{
|
|
5215
5283
|
// create a white texture
|
|
5216
5284
|
const whitePixel = new Uint8Array([255, 255, 255, 255]);
|
|
5217
5285
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
|
|
5218
5286
|
}
|
|
5219
|
-
|
|
5287
|
+
|
|
5220
5288
|
// use point filtering for pixelated rendering
|
|
5221
5289
|
const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
|
|
5222
5290
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
|
|
@@ -5224,6 +5292,18 @@ function glCreateTexture(image)
|
|
|
5224
5292
|
return texture;
|
|
5225
5293
|
}
|
|
5226
5294
|
|
|
5295
|
+
/** Set WebGL texture data from an image
|
|
5296
|
+
* @param {WebGLTexture} texture
|
|
5297
|
+
* @param {HTMLImageElement} image
|
|
5298
|
+
* @memberof WebGL */
|
|
5299
|
+
function glSetTextureData(texture, image)
|
|
5300
|
+
{
|
|
5301
|
+
// build the texture
|
|
5302
|
+
ASSERT(!!image && image.width > 0, 'Invalid image data.');
|
|
5303
|
+
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
5304
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
5305
|
+
}
|
|
5306
|
+
|
|
5227
5307
|
/** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
|
|
5228
5308
|
* @memberof WebGL */
|
|
5229
5309
|
function glFlush()
|
|
@@ -5277,10 +5357,10 @@ function glSetAntialias(antialias=true)
|
|
|
5277
5357
|
* @param {Number} uv0Y
|
|
5278
5358
|
* @param {Number} uv1X
|
|
5279
5359
|
* @param {Number} uv1Y
|
|
5280
|
-
* @param {Number} rgba
|
|
5281
|
-
* @param {Number} [rgbaAdditive=0]
|
|
5360
|
+
* @param {Number} [rgba=-1] - white is -1
|
|
5361
|
+
* @param {Number} [rgbaAdditive=0] - black is 0
|
|
5282
5362
|
* @memberof WebGL */
|
|
5283
|
-
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
5363
|
+
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=-1, rgbaAdditive=0)
|
|
5284
5364
|
{
|
|
5285
5365
|
ASSERT(typeof rgba == 'number' && typeof rgbaAdditive == 'number', 'invalid color');
|
|
5286
5366
|
|
|
@@ -5333,7 +5413,7 @@ const engineName = 'LittleJS';
|
|
|
5333
5413
|
* @type {string}
|
|
5334
5414
|
* @default
|
|
5335
5415
|
* @memberof Engine */
|
|
5336
|
-
const engineVersion = '1.
|
|
5416
|
+
const engineVersion = '1.12.4';
|
|
5337
5417
|
|
|
5338
5418
|
/** Frames per second to update
|
|
5339
5419
|
* @type {number}
|
|
@@ -5421,16 +5501,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5421
5501
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
5422
5502
|
|
|
5423
5503
|
// allow passing in empty functions
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
gameUpdatePost = ()=>{};
|
|
5430
|
-
if (!gameRender)
|
|
5431
|
-
gameRender = ()=>{};
|
|
5432
|
-
if (!gameRenderPost)
|
|
5433
|
-
gameRenderPost = ()=>{};
|
|
5504
|
+
gameInit ||= ()=>{};
|
|
5505
|
+
gameUpdate ||= ()=>{};
|
|
5506
|
+
gameUpdatePost ||= ()=>{};
|
|
5507
|
+
gameRender ||= ()=>{};
|
|
5508
|
+
gameRenderPost ||= ()=>{};
|
|
5434
5509
|
|
|
5435
5510
|
// Called automatically by engine to setup render system
|
|
5436
5511
|
function enginePreRender()
|
|
@@ -5457,11 +5532,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5457
5532
|
const debugSpeedUp = debug && keyIsDown('Equal'); // +
|
|
5458
5533
|
const debugSpeedDown = debug && keyIsDown('Minus'); // -
|
|
5459
5534
|
if (debug) // +/- to speed/slow time
|
|
5460
|
-
frameTimeDeltaMS *= debugSpeedUp ?
|
|
5535
|
+
frameTimeDeltaMS *= debugSpeedUp ? 10 : debugSpeedDown ? .1 : 1;
|
|
5461
5536
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
5462
5537
|
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
5463
5538
|
if (!debugSpeedUp)
|
|
5464
|
-
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp
|
|
5539
|
+
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
|
|
5540
|
+
if (debug && debugVideoCaptureIsActive())
|
|
5541
|
+
frameTimeBufferMS = 0; // disable time smoothing when capturing video
|
|
5465
5542
|
|
|
5466
5543
|
updateCanvas();
|
|
5467
5544
|
|
|
@@ -5540,6 +5617,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5540
5617
|
}
|
|
5541
5618
|
}
|
|
5542
5619
|
|
|
5620
|
+
debugVideoCaptureUpdate();
|
|
5543
5621
|
requestAnimationFrame(engineUpdate);
|
|
5544
5622
|
}
|
|
5545
5623
|
|
|
@@ -5587,11 +5665,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5587
5665
|
|
|
5588
5666
|
// setup html
|
|
5589
5667
|
const styleRoot =
|
|
5590
|
-
'margin:0;
|
|
5591
|
-
'width:100vw;height:100vh;' + // fill the window
|
|
5592
|
-
'display:flex;' + // use flexbox
|
|
5593
|
-
'align-items:center;' + // horizontal center
|
|
5594
|
-
'justify-content:center;' + // vertical center
|
|
5668
|
+
'margin:0;' + // fill the window
|
|
5595
5669
|
'background:#000;' + // set background color
|
|
5596
5670
|
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
5597
5671
|
'user-select:none;' + // prevent hold to select
|
|
@@ -5614,7 +5688,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5614
5688
|
overlayContext = overlayCanvas.getContext('2d');
|
|
5615
5689
|
|
|
5616
5690
|
// set canvas style
|
|
5617
|
-
const styleCanvas = 'position:absolute'
|
|
5691
|
+
const styleCanvas = 'position:absolute;'+ // allow canvases to overlap
|
|
5692
|
+
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center on screen
|
|
5618
5693
|
mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
5619
5694
|
if (glCanvas)
|
|
5620
5695
|
glCanvas.style.cssText = styleCanvas;
|
|
@@ -5625,12 +5700,12 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5625
5700
|
new Promise(resolve =>
|
|
5626
5701
|
{
|
|
5627
5702
|
const image = new Image;
|
|
5628
|
-
image.crossOrigin = 'anonymous';
|
|
5629
5703
|
image.onerror = image.onload = ()=>
|
|
5630
5704
|
{
|
|
5631
5705
|
textureInfos[textureIndex] = new TextureInfo(image);
|
|
5632
5706
|
resolve();
|
|
5633
5707
|
}
|
|
5708
|
+
image.crossOrigin = 'anonymous';
|
|
5634
5709
|
image.src = src;
|
|
5635
5710
|
})
|
|
5636
5711
|
);
|
|
@@ -5932,192 +6007,2979 @@ function drawEngineSplashScreen(t)
|
|
|
5932
6007
|
x.restore();
|
|
5933
6008
|
}
|
|
5934
6009
|
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
*
|
|
5938
|
-
* -
|
|
6010
|
+
/**
|
|
6011
|
+
* LittleJS Newgrounds API
|
|
6012
|
+
* - NewgroundsMedal extends Medal with Newgrounds API functionality
|
|
6013
|
+
* - Call new NewgroundsPlugin() to setup Newgrounds
|
|
6014
|
+
* - Uses CryptoJS for encryption if optional cipher is provided
|
|
6015
|
+
* - Keeps connection alive and logs views
|
|
6016
|
+
* - Functions to interact with scoreboards
|
|
6017
|
+
* - Functions to unlock medals
|
|
5939
6018
|
*/
|
|
5940
6019
|
|
|
5941
|
-
export
|
|
5942
|
-
{
|
|
5943
|
-
// Engine
|
|
5944
|
-
engineName,
|
|
5945
|
-
engineVersion,
|
|
5946
|
-
frameRate,
|
|
5947
|
-
timeDelta,
|
|
5948
|
-
engineObjects,
|
|
5949
|
-
frame,
|
|
5950
|
-
time,
|
|
5951
|
-
timeReal,
|
|
5952
|
-
paused,
|
|
5953
|
-
setPaused,
|
|
5954
|
-
engineInit,
|
|
5955
|
-
engineObjectsUpdate,
|
|
5956
|
-
engineObjectsDestroy,
|
|
5957
|
-
engineObjectsCollect,
|
|
5958
|
-
engineObjectsCallback,
|
|
5959
|
-
engineObjectsRaycast,
|
|
5960
|
-
engineAddPlugin,
|
|
5961
6020
|
|
|
5962
|
-
// Globals
|
|
5963
|
-
debug,
|
|
5964
|
-
debugOverlay,
|
|
5965
|
-
showWatermark,
|
|
5966
6021
|
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
debugCircle,
|
|
5972
|
-
debugPoint,
|
|
5973
|
-
debugLine,
|
|
5974
|
-
debugOverlap,
|
|
5975
|
-
debugText,
|
|
5976
|
-
debugClear,
|
|
5977
|
-
debugScreenshot,
|
|
5978
|
-
debugSaveCanvas,
|
|
5979
|
-
debugSaveText,
|
|
5980
|
-
debugSaveDataURL,
|
|
6022
|
+
/** Global Newgrounds object
|
|
6023
|
+
* @type {NewgroundsPlugin}
|
|
6024
|
+
* @memberof Medal */
|
|
6025
|
+
let newgrounds;
|
|
5981
6026
|
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
objectDefaultElasticity,
|
|
5999
|
-
objectDefaultFriction,
|
|
6000
|
-
objectMaxSpeed,
|
|
6001
|
-
gravity,
|
|
6002
|
-
particleEmitRateScale,
|
|
6003
|
-
glEnable,
|
|
6004
|
-
glOverlay,
|
|
6005
|
-
gamepadsEnable,
|
|
6006
|
-
gamepadDirectionEmulateStick,
|
|
6007
|
-
inputWASDEmulateDirection,
|
|
6008
|
-
touchGamepadEnable,
|
|
6009
|
-
touchGamepadAnalog,
|
|
6010
|
-
touchGamepadSize,
|
|
6011
|
-
touchGamepadAlpha,
|
|
6012
|
-
vibrateEnable,
|
|
6013
|
-
soundEnable,
|
|
6014
|
-
soundVolume,
|
|
6015
|
-
soundDefaultRange,
|
|
6016
|
-
soundDefaultTaper,
|
|
6017
|
-
medalDisplayTime,
|
|
6018
|
-
medalDisplaySlideTime,
|
|
6019
|
-
medalDisplaySize,
|
|
6027
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6028
|
+
/**
|
|
6029
|
+
* Newgrounds medal auto unlocks in newgrounds API
|
|
6030
|
+
* @extends Medal
|
|
6031
|
+
*/
|
|
6032
|
+
class NewgroundsMedal extends Medal
|
|
6033
|
+
{
|
|
6034
|
+
/** Create a newgrounds medal object and adds it to the list of medals
|
|
6035
|
+
* @param {Number} id - The unique identifier of the medal
|
|
6036
|
+
* @param {String} name - Name of the medal
|
|
6037
|
+
* @param {String} [description] - Description of the medal
|
|
6038
|
+
* @param {String} [icon] - Icon for the medal
|
|
6039
|
+
* @param {String} [src] - Image location for the medal
|
|
6040
|
+
*/
|
|
6041
|
+
constructor(id, name, description, icon, src)
|
|
6042
|
+
{ super(id, name, description, icon, src); }
|
|
6020
6043
|
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
setFontDefault,
|
|
6029
|
-
setShowSplashScreen,
|
|
6030
|
-
setHeadlessMode,
|
|
6031
|
-
setGlEnable,
|
|
6032
|
-
setGlOverlay,
|
|
6033
|
-
setTileSizeDefault,
|
|
6034
|
-
setTileFixBleedScale,
|
|
6035
|
-
setEnablePhysicsSolver,
|
|
6036
|
-
setObjectDefaultMass,
|
|
6037
|
-
setObjectDefaultDamping,
|
|
6038
|
-
setObjectDefaultAngleDamping,
|
|
6039
|
-
setObjectDefaultElasticity,
|
|
6040
|
-
setObjectDefaultFriction,
|
|
6041
|
-
setObjectMaxSpeed,
|
|
6042
|
-
setGravity,
|
|
6043
|
-
setParticleEmitRateScale,
|
|
6044
|
-
setTouchInputEnable,
|
|
6045
|
-
setGamepadsEnable,
|
|
6046
|
-
setGamepadDirectionEmulateStick,
|
|
6047
|
-
setInputWASDEmulateDirection,
|
|
6048
|
-
setTouchGamepadEnable,
|
|
6049
|
-
setTouchGamepadAnalog,
|
|
6050
|
-
setTouchGamepadSize,
|
|
6051
|
-
setTouchGamepadAlpha,
|
|
6052
|
-
setVibrateEnable,
|
|
6053
|
-
setSoundEnable,
|
|
6054
|
-
setSoundVolume,
|
|
6055
|
-
setSoundDefaultRange,
|
|
6056
|
-
setSoundDefaultTaper,
|
|
6057
|
-
setMedalDisplayTime,
|
|
6058
|
-
setMedalDisplaySlideTime,
|
|
6059
|
-
setMedalDisplaySize,
|
|
6060
|
-
setMedalsPreventUnlock,
|
|
6061
|
-
setShowWatermark,
|
|
6062
|
-
setDebugKey,
|
|
6044
|
+
/** Unlocks a medal if not already unlocked */
|
|
6045
|
+
unlock()
|
|
6046
|
+
{
|
|
6047
|
+
super.unlock();
|
|
6048
|
+
newgrounds && newgrounds.unlockMedal(this.id);
|
|
6049
|
+
}
|
|
6050
|
+
}
|
|
6063
6051
|
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
formatTime,
|
|
6052
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6053
|
+
/**
|
|
6054
|
+
* Newgrounds API object
|
|
6055
|
+
*/
|
|
6056
|
+
class NewgroundsPlugin
|
|
6057
|
+
{
|
|
6058
|
+
/** Create the global newgrounds object
|
|
6059
|
+
* @param {string} app_id - The newgrounds App ID
|
|
6060
|
+
* @param {string} [cipher] - The encryption Key (AES-128/Base64)
|
|
6061
|
+
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
|
|
6062
|
+
* @example
|
|
6063
|
+
* // create the newgrounds object, replace the app id with your own
|
|
6064
|
+
* const app_id = 'your_app_id_here';
|
|
6065
|
+
* new NewgroundsPlugin(app_id);
|
|
6066
|
+
*/
|
|
6067
|
+
constructor(app_id, cipher, cryptoJS)
|
|
6068
|
+
{
|
|
6069
|
+
ASSERT(!newgrounds, 'there can only be one newgrounds object');
|
|
6070
|
+
ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
|
|
6084
6071
|
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
randVector,
|
|
6091
|
-
randColor,
|
|
6072
|
+
newgrounds = this; // set global newgrounds object
|
|
6073
|
+
this.app_id = app_id;
|
|
6074
|
+
this.cipher = cipher;
|
|
6075
|
+
this.cryptoJS = cryptoJS;
|
|
6076
|
+
this.host = location ? location.hostname : '';
|
|
6092
6077
|
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
Color,
|
|
6097
|
-
Timer,
|
|
6098
|
-
vec2,
|
|
6099
|
-
rgb,
|
|
6100
|
-
hsl,
|
|
6101
|
-
isColor,
|
|
6078
|
+
// get session id from url search params
|
|
6079
|
+
const url = new URL(location.href);
|
|
6080
|
+
this.session_id = url.searchParams.get('ngio_session_id');
|
|
6102
6081
|
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
BLACK,
|
|
6106
|
-
GRAY,
|
|
6107
|
-
RED,
|
|
6108
|
-
ORANGE,
|
|
6109
|
-
YELLOW,
|
|
6110
|
-
GREEN,
|
|
6111
|
-
CYAN,
|
|
6112
|
-
BLUE,
|
|
6113
|
-
PURPLE,
|
|
6114
|
-
MAGENTA,
|
|
6082
|
+
if (!this.session_id)
|
|
6083
|
+
return; // only use newgrounds when logged in
|
|
6115
6084
|
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6085
|
+
// get medals
|
|
6086
|
+
const medalsResult = this.call('Medal.getList');
|
|
6087
|
+
this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
|
|
6088
|
+
debugMedals && console.log(this.medals);
|
|
6089
|
+
for (const newgroundsMedal of this.medals)
|
|
6090
|
+
{
|
|
6091
|
+
const medal = medals[newgroundsMedal['id']];
|
|
6092
|
+
if (medal)
|
|
6093
|
+
{
|
|
6094
|
+
// copy newgrounds medal data
|
|
6095
|
+
medal.image = new Image;
|
|
6096
|
+
medal.image.src = newgroundsMedal['icon'];
|
|
6097
|
+
medal.name = newgroundsMedal['name'];
|
|
6098
|
+
medal.description = newgroundsMedal['description'];
|
|
6099
|
+
medal.unlocked = newgroundsMedal['unlocked'];
|
|
6100
|
+
medal.difficulty = newgroundsMedal['difficulty'];
|
|
6101
|
+
medal.value = newgroundsMedal['value'];
|
|
6102
|
+
|
|
6103
|
+
if (medal.value) // add value to description
|
|
6104
|
+
medal.description = medal.description + ` (${ medal.value })`;
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
|
|
6108
|
+
// get scoreboards
|
|
6109
|
+
const scoreboardResult = this.call('ScoreBoard.getBoards');
|
|
6110
|
+
this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
|
|
6111
|
+
debugMedals && console.log(this.scoreboards);
|
|
6112
|
+
|
|
6113
|
+
// keep the session alive with a ping every minute
|
|
6114
|
+
const keepAliveMS = 60 * 1e3;
|
|
6115
|
+
setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
|
|
6116
|
+
}
|
|
6117
|
+
|
|
6118
|
+
/** Send message to unlock a medal by id
|
|
6119
|
+
* @param {number} id - The medal id */
|
|
6120
|
+
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
|
|
6121
|
+
|
|
6122
|
+
/** Send message to post score
|
|
6123
|
+
* @param {number} id - The scoreboard id
|
|
6124
|
+
* @param {number} value - The score value */
|
|
6125
|
+
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
|
|
6126
|
+
|
|
6127
|
+
/** Get scores from a scoreboard
|
|
6128
|
+
* @param {number} id - The scoreboard id
|
|
6129
|
+
* @param {string} [user] - A user's id or name
|
|
6130
|
+
* @param {number} [social] - If true, only social scores will be loaded
|
|
6131
|
+
* @param {number} [skip] - Number of scores to skip before start
|
|
6132
|
+
* @param {number} [limit] - Number of scores to include in the list
|
|
6133
|
+
* @return {Object} - The response JSON object
|
|
6134
|
+
*/
|
|
6135
|
+
getScores(id, user, social=0, skip=0, limit=10)
|
|
6136
|
+
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
6137
|
+
|
|
6138
|
+
/** Send message to log a view */
|
|
6139
|
+
logView() { return this.call('App.logView', {'host':this.host}, true); }
|
|
6140
|
+
|
|
6141
|
+
/** Send a message to call a component of the Newgrounds API
|
|
6142
|
+
* @param {string} component - Name of the component
|
|
6143
|
+
* @param {Object} [parameters] - Parameters to use for call
|
|
6144
|
+
* @param {boolean} [async] - If true, don't wait for response before continuing
|
|
6145
|
+
* @return {Object} - The response JSON object
|
|
6146
|
+
*/
|
|
6147
|
+
call(component, parameters, async=false)
|
|
6148
|
+
{
|
|
6149
|
+
const call = {'component':component, 'parameters':parameters};
|
|
6150
|
+
if (this.cipher)
|
|
6151
|
+
{
|
|
6152
|
+
// encrypt using AES-128 Base64 with cryptoJS
|
|
6153
|
+
const cryptoJS = this.cryptoJS;
|
|
6154
|
+
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
6155
|
+
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
6156
|
+
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
6157
|
+
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
6158
|
+
call['parameters'] = 0;
|
|
6159
|
+
}
|
|
6160
|
+
|
|
6161
|
+
// build the input object
|
|
6162
|
+
const input =
|
|
6163
|
+
{
|
|
6164
|
+
'app_id': this.app_id,
|
|
6165
|
+
'session_id': this.session_id,
|
|
6166
|
+
'call': call
|
|
6167
|
+
};
|
|
6168
|
+
|
|
6169
|
+
// build post data
|
|
6170
|
+
const formData = new FormData();
|
|
6171
|
+
formData.append('input', JSON.stringify(input));
|
|
6172
|
+
|
|
6173
|
+
// send post data
|
|
6174
|
+
const xmlHttp = new XMLHttpRequest();
|
|
6175
|
+
const url = 'https://newgrounds.io/gateway_v3.php';
|
|
6176
|
+
xmlHttp.open('POST', url, !debugMedals && async);
|
|
6177
|
+
try { xmlHttp.send(formData); }
|
|
6178
|
+
catch(e)
|
|
6179
|
+
{
|
|
6180
|
+
debugMedals && console.log('newgrounds call failed', e);
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
6183
|
+
debugMedals && console.log(xmlHttp.responseText);
|
|
6184
|
+
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6187
|
+
|
|
6188
|
+
/**
|
|
6189
|
+
* LittleJS Post Processing Plugin
|
|
6190
|
+
* - Supports shadertoy style post processing shaders
|
|
6191
|
+
* - call new new PostProcessPlugin() to setup post processing
|
|
6192
|
+
* - can be enabled to pass other canvases through a final shader
|
|
6193
|
+
*/
|
|
6194
|
+
|
|
6195
|
+
|
|
6196
|
+
|
|
6197
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6198
|
+
|
|
6199
|
+
/** Global Post Process plugin object
|
|
6200
|
+
* @type {PostProcessPlugin} */
|
|
6201
|
+
let postProcess;
|
|
6202
|
+
|
|
6203
|
+
/////////////////////////////////////////////////////////////////////////
|
|
6204
|
+
/**
|
|
6205
|
+
* UI System Global Object
|
|
6206
|
+
*/
|
|
6207
|
+
class PostProcessPlugin
|
|
6208
|
+
{
|
|
6209
|
+
/** Create global post processing shader
|
|
6210
|
+
* @param {string} shaderCode
|
|
6211
|
+
* @param {boolean} [includeOverlay]
|
|
6212
|
+
* @example
|
|
6213
|
+
* // create the post process plugin object
|
|
6214
|
+
* new PostProcessPlugin(shaderCode);
|
|
6215
|
+
*/
|
|
6216
|
+
constructor(shaderCode, includeOverlay=false)
|
|
6217
|
+
{
|
|
6218
|
+
ASSERT(!postProcess, 'Post process already initialized');
|
|
6219
|
+
postProcess = this;
|
|
6220
|
+
|
|
6221
|
+
if (headlessMode) return;
|
|
6222
|
+
if (!shaderCode) // default shader pass through
|
|
6223
|
+
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
6224
|
+
|
|
6225
|
+
/** @property {WebGLProgram} - Shader for post processing */
|
|
6226
|
+
this.shader = glCreateProgram(
|
|
6227
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
6228
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
6229
|
+
'in vec2 p;'+ // position
|
|
6230
|
+
'void main(){'+ // shader entry point
|
|
6231
|
+
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
6232
|
+
'}' // end of shader
|
|
6233
|
+
,
|
|
6234
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
6235
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
6236
|
+
'uniform sampler2D iChannel0;'+ // input texture
|
|
6237
|
+
'uniform vec3 iResolution;'+ // size of output texture
|
|
6238
|
+
'uniform float iTime;'+ // time
|
|
6239
|
+
'out vec4 c;'+ // out color
|
|
6240
|
+
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
6241
|
+
'void main(){'+ // shader entry point
|
|
6242
|
+
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
6243
|
+
'c.a=1.;'+ // always use full alpha
|
|
6244
|
+
'}' // end of shader
|
|
6245
|
+
);
|
|
6246
|
+
|
|
6247
|
+
/** @property {WebGLTexture} - Texture for post processing */
|
|
6248
|
+
this.texture = glCreateTexture();
|
|
6249
|
+
|
|
6250
|
+
/** @property {boolean} - Should overlay canvas be included in post processing */
|
|
6251
|
+
this.includeOverlay = includeOverlay;
|
|
6252
|
+
|
|
6253
|
+
// Render the post processing shader, called automatically by the engine
|
|
6254
|
+
engineAddPlugin(undefined, postProcessRender);
|
|
6255
|
+
function postProcessRender()
|
|
6256
|
+
{
|
|
6257
|
+
if (headlessMode) return;
|
|
6258
|
+
|
|
6259
|
+
// prepare to render post process shader
|
|
6260
|
+
if (glEnable)
|
|
6261
|
+
{
|
|
6262
|
+
glFlush(); // clear out the buffer
|
|
6263
|
+
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
6264
|
+
}
|
|
6265
|
+
else
|
|
6266
|
+
{
|
|
6267
|
+
// set the viewport
|
|
6268
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
6269
|
+
}
|
|
6270
|
+
|
|
6271
|
+
if (postProcess.includeOverlay)
|
|
6272
|
+
{
|
|
6273
|
+
// copy overlay canvas so it will be included in post processing
|
|
6274
|
+
mainContext.drawImage(overlayCanvas, 0, 0);
|
|
6275
|
+
overlayCanvas.width |= 0;
|
|
6276
|
+
}
|
|
6277
|
+
|
|
6278
|
+
// setup shader program to draw one triangle
|
|
6279
|
+
glContext.useProgram(postProcess.shader);
|
|
6280
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
6281
|
+
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, 1);
|
|
6282
|
+
glContext.disable(glContext.BLEND);
|
|
6283
|
+
|
|
6284
|
+
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
6285
|
+
glContext.activeTexture(glContext.TEXTURE0);
|
|
6286
|
+
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
6287
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, mainCanvas);
|
|
6288
|
+
|
|
6289
|
+
// set vertex position attribute
|
|
6290
|
+
const vertexByteStride = 8;
|
|
6291
|
+
const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
|
|
6292
|
+
glContext.enableVertexAttribArray(pLocation);
|
|
6293
|
+
glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
|
|
6294
|
+
|
|
6295
|
+
// set uniforms and draw
|
|
6296
|
+
const uniformLocation = (name)=>glContext.getUniformLocation(postProcess.shader, name);
|
|
6297
|
+
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
6298
|
+
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
6299
|
+
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
6300
|
+
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
|
|
6301
|
+
}
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
|
|
6305
|
+
/**
|
|
6306
|
+
* LittleJS ZzFXM Plugin
|
|
6307
|
+
*/
|
|
6308
|
+
|
|
6309
|
+
|
|
6310
|
+
|
|
6311
|
+
/**
|
|
6312
|
+
* Music Object - Stores a zzfx music track for later use
|
|
6313
|
+
*
|
|
6314
|
+
* <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
|
|
6315
|
+
* @example
|
|
6316
|
+
* // create some music
|
|
6317
|
+
* const music_example = new Music(
|
|
6318
|
+
* [
|
|
6319
|
+
* [ // instruments
|
|
6320
|
+
* [,0,400] // simple note
|
|
6321
|
+
* ],
|
|
6322
|
+
* [ // patterns
|
|
6323
|
+
* [ // pattern 1
|
|
6324
|
+
* [ // channel 0
|
|
6325
|
+
* 0, -1, // instrument 0, left speaker
|
|
6326
|
+
* 1, 0, 9, 1 // channel notes
|
|
6327
|
+
* ],
|
|
6328
|
+
* [ // channel 1
|
|
6329
|
+
* 0, 1, // instrument 0, right speaker
|
|
6330
|
+
* 0, 12, 17, -1 // channel notes
|
|
6331
|
+
* ]
|
|
6332
|
+
* ],
|
|
6333
|
+
* ],
|
|
6334
|
+
* [0, 0, 0, 0], // sequence, play pattern 0 four times
|
|
6335
|
+
* 90 // BPM
|
|
6336
|
+
* ]);
|
|
6337
|
+
*
|
|
6338
|
+
* // play the music
|
|
6339
|
+
* music_example.play();
|
|
6340
|
+
*/
|
|
6341
|
+
class ZzFXMusic extends Sound
|
|
6342
|
+
{
|
|
6343
|
+
/** Create a music object and cache the zzfx music samples for later use
|
|
6344
|
+
* @param {[Array, Array, Array, number]} zzfxMusic - Array of zzfx music parameters
|
|
6345
|
+
*/
|
|
6346
|
+
constructor(zzfxMusic)
|
|
6347
|
+
{
|
|
6348
|
+
super(undefined);
|
|
6349
|
+
|
|
6350
|
+
if (!soundEnable || headlessMode) return;
|
|
6351
|
+
this.randomness = 0;
|
|
6352
|
+
this.sampleChannels = zzfxM(...zzfxMusic);
|
|
6353
|
+
this.sampleRate = zzfxR;
|
|
6354
|
+
}
|
|
6355
|
+
|
|
6356
|
+
/** Play the music
|
|
6357
|
+
* @param {number} [volume=1] - How much to scale volume by
|
|
6358
|
+
* @param {boolean} [loop] - True if the music should loop
|
|
6359
|
+
* @return {AudioBufferSourceNode} - The audio source node
|
|
6360
|
+
*/
|
|
6361
|
+
playMusic(volume, loop=false)
|
|
6362
|
+
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
6363
|
+
}
|
|
6364
|
+
|
|
6365
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6366
|
+
// ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
|
|
6367
|
+
|
|
6368
|
+
/** Generate samples for a ZzFM song with given parameters
|
|
6369
|
+
* @param {Array} instruments - Array of ZzFX sound parameters
|
|
6370
|
+
* @param {Array} patterns - Array of pattern data
|
|
6371
|
+
* @param {Array} sequence - Array of pattern indexes
|
|
6372
|
+
* @param {number} [BPM] - Playback speed of the song in BPM
|
|
6373
|
+
* @return {Array} - Left and right channel sample data */
|
|
6374
|
+
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
6375
|
+
{
|
|
6376
|
+
let i, j, k;
|
|
6377
|
+
let instrumentParameters;
|
|
6378
|
+
let note;
|
|
6379
|
+
let sample;
|
|
6380
|
+
let patternChannel;
|
|
6381
|
+
let notFirstBeat;
|
|
6382
|
+
let stop;
|
|
6383
|
+
let instrument;
|
|
6384
|
+
let attenuation;
|
|
6385
|
+
let outSampleOffset;
|
|
6386
|
+
let isSequenceEnd;
|
|
6387
|
+
let sampleOffset = 0;
|
|
6388
|
+
let nextSampleOffset;
|
|
6389
|
+
let sampleBuffer = [];
|
|
6390
|
+
let leftChannelBuffer = [];
|
|
6391
|
+
let rightChannelBuffer = [];
|
|
6392
|
+
let channelIndex = 0;
|
|
6393
|
+
let panning = 0;
|
|
6394
|
+
let hasMore = 1;
|
|
6395
|
+
let sampleCache = {};
|
|
6396
|
+
let beatLength = zzfxR / BPM * 60 >> 2;
|
|
6397
|
+
|
|
6398
|
+
// for each channel in order until there are no more
|
|
6399
|
+
for (; hasMore; channelIndex++) {
|
|
6400
|
+
|
|
6401
|
+
// reset current values
|
|
6402
|
+
sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
|
|
6403
|
+
|
|
6404
|
+
// for each pattern in sequence
|
|
6405
|
+
sequence.forEach((patternIndex, sequenceIndex) => {
|
|
6406
|
+
// get pattern for current channel, use empty 1 note pattern if none found
|
|
6407
|
+
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
6408
|
+
|
|
6409
|
+
// check if there are more channels
|
|
6410
|
+
hasMore |= patterns[patternIndex][channelIndex]&&1;
|
|
6411
|
+
|
|
6412
|
+
// get next offset, use the length of first channel
|
|
6413
|
+
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
|
|
6414
|
+
// for each beat in pattern, plus one extra if end of sequence
|
|
6415
|
+
isSequenceEnd = sequenceIndex == sequence.length - 1;
|
|
6416
|
+
for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
|
|
6417
|
+
|
|
6418
|
+
// <channel-note>
|
|
6419
|
+
note = patternChannel[i];
|
|
6420
|
+
|
|
6421
|
+
// stop if end, different instrument or new note
|
|
6422
|
+
stop = i == patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
6423
|
+
instrument != (patternChannel[0] || 0) || note | 0;
|
|
6424
|
+
|
|
6425
|
+
// fill buffer with samples for previous beat, most cpu intensive part
|
|
6426
|
+
for (j = 0; j < beatLength && notFirstBeat;
|
|
6427
|
+
|
|
6428
|
+
// fade off attenuation at end of beat if stopping note, prevents clicking
|
|
6429
|
+
j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
|
|
6430
|
+
) {
|
|
6431
|
+
// copy sample to stereo buffers with panning
|
|
6432
|
+
sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
|
|
6433
|
+
leftChannelBuffer[k] = (leftChannelBuffer[k] || 0) - sample * panning + sample;
|
|
6434
|
+
rightChannelBuffer[k] = (rightChannelBuffer[k++] || 0) + sample * panning + sample;
|
|
6435
|
+
}
|
|
6436
|
+
|
|
6437
|
+
// set up for next note
|
|
6438
|
+
if (note) {
|
|
6439
|
+
// set attenuation
|
|
6440
|
+
attenuation = note % 1;
|
|
6441
|
+
panning = patternChannel[1] || 0;
|
|
6442
|
+
if (note |= 0) {
|
|
6443
|
+
// get cached sample
|
|
6444
|
+
sampleBuffer = sampleCache[
|
|
6445
|
+
[
|
|
6446
|
+
instrument = patternChannel[sampleOffset = 0] || 0,
|
|
6447
|
+
note
|
|
6448
|
+
]
|
|
6449
|
+
] = sampleCache[[instrument, note]] || (
|
|
6450
|
+
// add sample to cache
|
|
6451
|
+
instrumentParameters = [...instruments[instrument]],
|
|
6452
|
+
instrumentParameters[2] = (instrumentParameters[2] || 220) * 2**(note / 12 - 1),
|
|
6453
|
+
|
|
6454
|
+
// allow negative values to stop notes
|
|
6455
|
+
note > 0 ? zzfxG(...instrumentParameters) : []
|
|
6456
|
+
);
|
|
6457
|
+
}
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
|
|
6461
|
+
// update the sample offset
|
|
6462
|
+
outSampleOffset = nextSampleOffset;
|
|
6463
|
+
});
|
|
6464
|
+
}
|
|
6465
|
+
|
|
6466
|
+
return [leftChannelBuffer, rightChannelBuffer];
|
|
6467
|
+
}
|
|
6468
|
+
|
|
6469
|
+
/**
|
|
6470
|
+
* LittleJS User Interface Plugin
|
|
6471
|
+
* - call new UISystemPlugin() to setup the UI system
|
|
6472
|
+
* - Nested Menus
|
|
6473
|
+
* - Text
|
|
6474
|
+
* - Buttons
|
|
6475
|
+
* - Checkboxes
|
|
6476
|
+
* - Images
|
|
6477
|
+
*/
|
|
6478
|
+
|
|
6479
|
+
|
|
6480
|
+
|
|
6481
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6482
|
+
|
|
6483
|
+
/** Global UI system plugin object
|
|
6484
|
+
* @type {UISystemPlugin} */
|
|
6485
|
+
let uiSystem;
|
|
6486
|
+
|
|
6487
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6488
|
+
/**
|
|
6489
|
+
* UI System Global Object
|
|
6490
|
+
*/
|
|
6491
|
+
class UISystemPlugin
|
|
6492
|
+
{
|
|
6493
|
+
/** Create the global UI system object
|
|
6494
|
+
* @param {CanvasRenderingContext2D} [context]
|
|
6495
|
+
* @example
|
|
6496
|
+
* // create the ui plugin object
|
|
6497
|
+
* new UISystemPlugin;
|
|
6498
|
+
*/
|
|
6499
|
+
constructor(context=overlayContext)
|
|
6500
|
+
{
|
|
6501
|
+
ASSERT(!uiSystem, 'UI system already initialized');
|
|
6502
|
+
uiSystem = this;
|
|
6503
|
+
|
|
6504
|
+
/** @property {Color} - Default fill color for UI elements */
|
|
6505
|
+
this.defaultColor = WHITE;
|
|
6506
|
+
/** @property {Color} - Default outline color for UI elements */
|
|
6507
|
+
this.defaultLineColor = BLACK;
|
|
6508
|
+
/** @property {Color} - Default text color for UI elements */
|
|
6509
|
+
this.defaultTextColor = BLACK;
|
|
6510
|
+
/** @property {Color} - Default button color for UI elements */
|
|
6511
|
+
this.defaultButtonColor = hsl(0,0,.5);
|
|
6512
|
+
/** @property {Color} - Default hover color for UI elements */
|
|
6513
|
+
this.defaultHoverColor = hsl(0,0,.7);
|
|
6514
|
+
/** @property {number} - Default line width for UI elements */
|
|
6515
|
+
this.defaultLineWidth = 4;
|
|
6516
|
+
/** @property {string} - Default font for UI elements */
|
|
6517
|
+
this.defaultFont = 'arial';
|
|
6518
|
+
/** @property {Array<UIObject>} - List of all UI elements */
|
|
6519
|
+
this.uiObjects = [];
|
|
6520
|
+
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - Context to render UI elements to */
|
|
6521
|
+
this.uiContext = context;
|
|
6522
|
+
|
|
6523
|
+
engineAddPlugin(uiUpdate, uiRender);
|
|
6524
|
+
|
|
6525
|
+
// setup recursive update and render
|
|
6526
|
+
function uiUpdate()
|
|
6527
|
+
{
|
|
6528
|
+
function updateObject(o)
|
|
6529
|
+
{
|
|
6530
|
+
if (!o.visible)
|
|
6531
|
+
return;
|
|
6532
|
+
if (o.parent)
|
|
6533
|
+
o.pos = o.localPos.add(o.parent.pos);
|
|
6534
|
+
o.update();
|
|
6535
|
+
for(const c of o.children)
|
|
6536
|
+
updateObject(c);
|
|
6537
|
+
}
|
|
6538
|
+
uiSystem.uiObjects.forEach(o=> o.parent || updateObject(o));
|
|
6539
|
+
}
|
|
6540
|
+
function uiRender()
|
|
6541
|
+
{
|
|
6542
|
+
function renderObject(o)
|
|
6543
|
+
{
|
|
6544
|
+
if (!o.visible)
|
|
6545
|
+
return;
|
|
6546
|
+
if (o.parent)
|
|
6547
|
+
o.pos = o.localPos.add(o.parent.pos);
|
|
6548
|
+
o.render();
|
|
6549
|
+
for(const c of o.children)
|
|
6550
|
+
renderObject(c);
|
|
6551
|
+
}
|
|
6552
|
+
uiSystem.uiObjects.forEach(o=> o.parent || renderObject(o));
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
|
|
6556
|
+
/** Draw a rectangle to the UI context
|
|
6557
|
+
* @param {Vector2} pos
|
|
6558
|
+
* @param {Vector2} size
|
|
6559
|
+
* @param {Color} [color=uiSystem.defaultColor]
|
|
6560
|
+
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
6561
|
+
* @param {Color} [lineColor=uiSystem.defaultLineColor] */
|
|
6562
|
+
drawRect(pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor)
|
|
6563
|
+
{
|
|
6564
|
+
uiSystem.uiContext.fillStyle = color.toString();
|
|
6565
|
+
uiSystem.uiContext.beginPath();
|
|
6566
|
+
uiSystem.uiContext.rect(pos.x-size.x/2, pos.y-size.y/2, size.x, size.y);
|
|
6567
|
+
uiSystem.uiContext.fill();
|
|
6568
|
+
if (lineWidth)
|
|
6569
|
+
{
|
|
6570
|
+
uiSystem.uiContext.strokeStyle = lineColor.toString();
|
|
6571
|
+
uiSystem.uiContext.lineWidth = lineWidth;
|
|
6572
|
+
uiSystem.uiContext.stroke();
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
6575
|
+
|
|
6576
|
+
/** Draw a line to the UI context
|
|
6577
|
+
* @param {Vector2} posA
|
|
6578
|
+
* @param {Vector2} posB
|
|
6579
|
+
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
6580
|
+
* @param {Color} [lineColor=uiSystem.defaultLineColor] */
|
|
6581
|
+
drawLine(posA, posB, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor)
|
|
6582
|
+
{
|
|
6583
|
+
uiSystem.uiContext.strokeStyle = lineColor.toString();
|
|
6584
|
+
uiSystem.uiContext.lineWidth = lineWidth;
|
|
6585
|
+
uiSystem.uiContext.beginPath();
|
|
6586
|
+
uiSystem.uiContext.lineTo(posA.x, posA.y);
|
|
6587
|
+
uiSystem.uiContext.lineTo(posB.x, posB.y);
|
|
6588
|
+
uiSystem.uiContext.stroke();
|
|
6589
|
+
}
|
|
6590
|
+
|
|
6591
|
+
/** Draw a tile to the UI context
|
|
6592
|
+
* @param {Vector2} pos
|
|
6593
|
+
* @param {Vector2} size
|
|
6594
|
+
* @param {TileInfo} tileInfo
|
|
6595
|
+
* @param {Color} [color=uiSystem.defaultColor]
|
|
6596
|
+
* @param {number} [angle]
|
|
6597
|
+
* @param {boolean} [mirror] */
|
|
6598
|
+
drawTile(pos, size, tileInfo, color=uiSystem.defaultColor, angle=0, mirror=false)
|
|
6599
|
+
{
|
|
6600
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, BLACK, false, true, uiSystem.uiContext);
|
|
6601
|
+
}
|
|
6602
|
+
|
|
6603
|
+
/** Draw text to the UI context
|
|
6604
|
+
* @param {string} text
|
|
6605
|
+
* @param {Vector2} pos
|
|
6606
|
+
* @param {Vector2} size
|
|
6607
|
+
* @param {Color} [color=uiSystem.defaultColor]
|
|
6608
|
+
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
6609
|
+
* @param {Color} [lineColor=uiSystem.defaultLineColor]
|
|
6610
|
+
* @param {string} [align]
|
|
6611
|
+
* @param {string} [font=uiSystem.defaultFont] */
|
|
6612
|
+
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont)
|
|
6613
|
+
{
|
|
6614
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, size.x, uiSystem.uiContext);
|
|
6615
|
+
}
|
|
6616
|
+
}
|
|
6617
|
+
|
|
6618
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6619
|
+
/**
|
|
6620
|
+
* UI Object - Base level object for all UI elements
|
|
6621
|
+
*/
|
|
6622
|
+
class UIObject
|
|
6623
|
+
{
|
|
6624
|
+
/** Create a UIObject
|
|
6625
|
+
* @param {Vector2} [pos=(0,0)]
|
|
6626
|
+
* @param {Vector2} [size=(1,1)]
|
|
6627
|
+
*/
|
|
6628
|
+
constructor(pos=vec2(), size=vec2())
|
|
6629
|
+
{
|
|
6630
|
+
/** @property {Vector2} - Local position of the object */
|
|
6631
|
+
this.localPos = pos.copy();
|
|
6632
|
+
/** @property {Vector2} - Screen space position of the object */
|
|
6633
|
+
this.pos = pos.copy();
|
|
6634
|
+
/** @property {Vector2} - Screen space size of the object */
|
|
6635
|
+
this.size = size.copy();
|
|
6636
|
+
/** @property {Color} */
|
|
6637
|
+
this.color = uiSystem.defaultColor;
|
|
6638
|
+
/** @property {Color} */
|
|
6639
|
+
this.lineColor = uiSystem.defaultLineColor;
|
|
6640
|
+
/** @property {Color} */
|
|
6641
|
+
this.textColor = uiSystem.defaultTextColor;
|
|
6642
|
+
/** @property {Color} */
|
|
6643
|
+
this.hoverColor = uiSystem.defaultHoverColor;
|
|
6644
|
+
/** @property {number} */
|
|
6645
|
+
this.lineWidth = uiSystem.defaultLineWidth;
|
|
6646
|
+
/** @property {string} */
|
|
6647
|
+
this.font = uiSystem.defaultFont;
|
|
6648
|
+
/** @property {boolean} */
|
|
6649
|
+
this.visible = true;
|
|
6650
|
+
/** @property {Array<UIObject>} */
|
|
6651
|
+
this.children = [];
|
|
6652
|
+
/** @property {UIObject} */
|
|
6653
|
+
this.parent = undefined;
|
|
6654
|
+
uiSystem.uiObjects.push(this);
|
|
6655
|
+
}
|
|
6656
|
+
|
|
6657
|
+
/** Add a child UIObject to this object
|
|
6658
|
+
* @param {UIObject} child
|
|
6659
|
+
*/
|
|
6660
|
+
addChild(child)
|
|
6661
|
+
{
|
|
6662
|
+
ASSERT(!child.parent && !this.children.includes(child));
|
|
6663
|
+
this.children.push(child);
|
|
6664
|
+
child.parent = this;
|
|
6665
|
+
}
|
|
6666
|
+
|
|
6667
|
+
/** Remove a child UIObject from this object
|
|
6668
|
+
* @param {UIObject} child
|
|
6669
|
+
*/
|
|
6670
|
+
removeChild(child)
|
|
6671
|
+
{
|
|
6672
|
+
ASSERT(child.parent == this && this.children.includes(child));
|
|
6673
|
+
this.children.splice(this.children.indexOf(child), 1);
|
|
6674
|
+
child.parent = undefined;
|
|
6675
|
+
}
|
|
6676
|
+
|
|
6677
|
+
/** Update the object, called automatically by plugin once each frame */
|
|
6678
|
+
update()
|
|
6679
|
+
{
|
|
6680
|
+
// track mouse input
|
|
6681
|
+
const mouseWasOver = this.mouseIsOver;
|
|
6682
|
+
const mouseDown = mouseIsDown(0);
|
|
6683
|
+
if (!mouseDown || isTouchDevice)
|
|
6684
|
+
{
|
|
6685
|
+
this.mouseIsOver = isOverlapping(this.pos, this.size, mousePosScreen);
|
|
6686
|
+
if (!mouseDown && isTouchDevice)
|
|
6687
|
+
this.mouseIsOver = false;
|
|
6688
|
+
if (this.mouseIsOver && !mouseWasOver)
|
|
6689
|
+
this.onEnter();
|
|
6690
|
+
if (!this.mouseIsOver && mouseWasOver)
|
|
6691
|
+
this.onLeave();
|
|
6692
|
+
}
|
|
6693
|
+
if (mouseWasPressed(0) && this.mouseIsOver)
|
|
6694
|
+
{
|
|
6695
|
+
this.mouseIsHeld = true;
|
|
6696
|
+
this.onPress();
|
|
6697
|
+
if (isTouchDevice)
|
|
6698
|
+
this.mouseIsOver = false;
|
|
6699
|
+
}
|
|
6700
|
+
else if (this.mouseIsHeld && !mouseDown)
|
|
6701
|
+
{
|
|
6702
|
+
this.mouseIsHeld = false;
|
|
6703
|
+
this.onRelease();
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6706
|
+
|
|
6707
|
+
/** Render the object, called automatically by plugin once each frame */
|
|
6708
|
+
render()
|
|
6709
|
+
{
|
|
6710
|
+
if (this.size.x && this.size.y)
|
|
6711
|
+
uiSystem.drawRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor);
|
|
6712
|
+
}
|
|
6713
|
+
|
|
6714
|
+
/** Called when the mouse enters the object */
|
|
6715
|
+
onEnter() {}
|
|
6716
|
+
|
|
6717
|
+
/** Called when the mouse leaves the object */
|
|
6718
|
+
onLeave() {}
|
|
6719
|
+
|
|
6720
|
+
/** Called when the mouse is pressed while over the object */
|
|
6721
|
+
onPress() {}
|
|
6722
|
+
|
|
6723
|
+
/** Called when the mouse is released while over the object */
|
|
6724
|
+
onRelease() {}
|
|
6725
|
+
|
|
6726
|
+
/** Called when the state of this object changes */
|
|
6727
|
+
onChange() {}
|
|
6728
|
+
}
|
|
6729
|
+
|
|
6730
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6731
|
+
/**
|
|
6732
|
+
* UIText - A UI object that displays text
|
|
6733
|
+
* @extends UIObject
|
|
6734
|
+
*/
|
|
6735
|
+
class UIText extends UIObject
|
|
6736
|
+
{
|
|
6737
|
+
/** Create a UIText object
|
|
6738
|
+
* @param {Vector2} [pos]
|
|
6739
|
+
* @param {Vector2} [size]
|
|
6740
|
+
* @param {string} [text]
|
|
6741
|
+
* @param {string} [align]
|
|
6742
|
+
* @param {string} [font=uiSystem.defaultFont]
|
|
6743
|
+
*/
|
|
6744
|
+
constructor(pos, size, text='', align='center', font=uiSystem.defaultFont)
|
|
6745
|
+
{
|
|
6746
|
+
super(pos, size);
|
|
6747
|
+
|
|
6748
|
+
/** @property {string} */
|
|
6749
|
+
this.text = text;
|
|
6750
|
+
/** @property {string} */
|
|
6751
|
+
this.align = align;
|
|
6752
|
+
|
|
6753
|
+
this.font = font; // set font
|
|
6754
|
+
this.lineWidth = 0; // set text to not be outlined by default
|
|
6755
|
+
}
|
|
6756
|
+
render()
|
|
6757
|
+
{
|
|
6758
|
+
uiSystem.drawText(this.text, this.pos, this.size, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
|
|
6759
|
+
}
|
|
6760
|
+
}
|
|
6761
|
+
|
|
6762
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6763
|
+
/**
|
|
6764
|
+
* UITile - A UI object that displays a tile image
|
|
6765
|
+
* @extends UIObject
|
|
6766
|
+
*/
|
|
6767
|
+
class UITile extends UIObject
|
|
6768
|
+
{
|
|
6769
|
+
/** Create a UITile object
|
|
6770
|
+
* @param {Vector2} [pos]
|
|
6771
|
+
* @param {Vector2} [size]
|
|
6772
|
+
* @param {TileInfo} [tileInfo]
|
|
6773
|
+
* @param {Color} [color=WHITE]
|
|
6774
|
+
* @param {number} [angle]
|
|
6775
|
+
* @param {boolean} [mirror]
|
|
6776
|
+
*/
|
|
6777
|
+
constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false)
|
|
6778
|
+
{
|
|
6779
|
+
super(pos, size);
|
|
6780
|
+
|
|
6781
|
+
/** @property {TileInfo} - Tile image to use */
|
|
6782
|
+
this.tileInfo = tileInfo;
|
|
6783
|
+
/** @property {number} - Angle to rotate in radians */
|
|
6784
|
+
this.angle = angle;
|
|
6785
|
+
/** @property {boolean} - Should it be mirrored? */
|
|
6786
|
+
this.mirror = mirror;
|
|
6787
|
+
this.color = color;
|
|
6788
|
+
}
|
|
6789
|
+
render()
|
|
6790
|
+
{
|
|
6791
|
+
uiSystem.drawTile(this.pos, this.size, this.tileInfo, this.color, this.angle, this.mirror);
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
|
|
6795
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6796
|
+
/**
|
|
6797
|
+
* UIButton - A UI object that acts as a button
|
|
6798
|
+
* @extends UIObject
|
|
6799
|
+
*/
|
|
6800
|
+
class UIButton extends UIObject
|
|
6801
|
+
{
|
|
6802
|
+
/** Create a UIButton object
|
|
6803
|
+
* @param {Vector2} [pos]
|
|
6804
|
+
* @param {Vector2} [size]
|
|
6805
|
+
* @param {string} [text]
|
|
6806
|
+
* @param {Color} [color=uiSystem.defaultButtonColor]
|
|
6807
|
+
*/
|
|
6808
|
+
constructor(pos, size, text='', color=uiSystem.defaultButtonColor)
|
|
6809
|
+
{
|
|
6810
|
+
super(pos, size);
|
|
6811
|
+
|
|
6812
|
+
/** @property {string} */
|
|
6813
|
+
this.text = text;
|
|
6814
|
+
this.color = color;
|
|
6815
|
+
}
|
|
6816
|
+
render()
|
|
6817
|
+
{
|
|
6818
|
+
const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
|
|
6819
|
+
const color = this.mouseIsOver? this.hoverColor : this.color;
|
|
6820
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor);
|
|
6821
|
+
const textSize = vec2(this.size.x, this.size.y*.8);
|
|
6822
|
+
uiSystem.drawText(this.text, this.pos, textSize,
|
|
6823
|
+
this.textColor, 0, undefined, this.align, this.font);
|
|
6824
|
+
}
|
|
6825
|
+
}
|
|
6826
|
+
|
|
6827
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6828
|
+
/**
|
|
6829
|
+
* UICheckbox - A UI object that acts as a checkbox
|
|
6830
|
+
* @extends UIObject
|
|
6831
|
+
*/
|
|
6832
|
+
class UICheckbox extends UIObject
|
|
6833
|
+
{
|
|
6834
|
+
/** Create a UICheckbox object
|
|
6835
|
+
* @param {Vector2} [pos]
|
|
6836
|
+
* @param {Vector2} [size]
|
|
6837
|
+
* @param {boolean} [checked]
|
|
6838
|
+
*/
|
|
6839
|
+
constructor(pos, size, checked=false)
|
|
6840
|
+
{
|
|
6841
|
+
super(pos, size);
|
|
6842
|
+
|
|
6843
|
+
/** @property {boolean} */
|
|
6844
|
+
this.checked = checked;
|
|
6845
|
+
}
|
|
6846
|
+
onPress()
|
|
6847
|
+
{
|
|
6848
|
+
this.checked = !this.checked;
|
|
6849
|
+
this.onChange();
|
|
6850
|
+
}
|
|
6851
|
+
render()
|
|
6852
|
+
{
|
|
6853
|
+
const color = this.mouseIsOver? this.hoverColor : this.color;
|
|
6854
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, this.lineColor);
|
|
6855
|
+
if (this.checked)
|
|
6856
|
+
{
|
|
6857
|
+
// draw an X if checked
|
|
6858
|
+
uiSystem.drawLine(this.pos.add(this.size.multiply(vec2(-.5,-.5))), this.pos.add(this.size.multiply(vec2(.5,.5))), this.lineWidth, this.lineColor);
|
|
6859
|
+
uiSystem.drawLine(this.pos.add(this.size.multiply(vec2(-.5,.5))), this.pos.add(this.size.multiply(vec2(.5,-.5))), this.lineWidth, this.lineColor);
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
|
|
6864
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6865
|
+
/**
|
|
6866
|
+
* UIScrollbar - A UI object that acts as a scrollbar
|
|
6867
|
+
* @extends UIObject
|
|
6868
|
+
*/
|
|
6869
|
+
class UIScrollbar extends UIObject
|
|
6870
|
+
{
|
|
6871
|
+
/** Create a UIScrollbar object
|
|
6872
|
+
* @param {Vector2} [pos]
|
|
6873
|
+
* @param {Vector2} [size]
|
|
6874
|
+
* @param {number} [value]
|
|
6875
|
+
* @param {string} [text]
|
|
6876
|
+
* @param {Color} [color=uiSystem.defaultButtonColor]
|
|
6877
|
+
* @param {Color} [handleColor=WHITE]
|
|
6878
|
+
*/
|
|
6879
|
+
constructor(pos, size, value=.5, text='', color=uiSystem.defaultButtonColor, handleColor=WHITE)
|
|
6880
|
+
{
|
|
6881
|
+
super(pos, size);
|
|
6882
|
+
|
|
6883
|
+
/** @property {number} */
|
|
6884
|
+
this.value = value;
|
|
6885
|
+
/** @property {string} */
|
|
6886
|
+
this.text = text;
|
|
6887
|
+
this.color = color;
|
|
6888
|
+
this.handleColor = handleColor;
|
|
6889
|
+
}
|
|
6890
|
+
update()
|
|
6891
|
+
{
|
|
6892
|
+
super.update();
|
|
6893
|
+
if (this.mouseIsHeld)
|
|
6894
|
+
{
|
|
6895
|
+
const handleSize = vec2(this.size.y);
|
|
6896
|
+
const handleWidth = this.size.x - handleSize.x;
|
|
6897
|
+
const p1 = this.pos.x - handleWidth/2;
|
|
6898
|
+
const p2 = this.pos.x + handleWidth/2;
|
|
6899
|
+
const oldValue = this.value;
|
|
6900
|
+
this.value = percent(mousePosScreen.x, p1, p2);
|
|
6901
|
+
this.value == oldValue || this.onChange();
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
6904
|
+
render()
|
|
6905
|
+
{
|
|
6906
|
+
const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
|
|
6907
|
+
const color = this.mouseIsOver? this.hoverColor : this.color;
|
|
6908
|
+
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor);
|
|
6909
|
+
|
|
6910
|
+
const handleSize = vec2(this.size.y);
|
|
6911
|
+
const handleWidth = this.size.x - handleSize.x;
|
|
6912
|
+
const p1 = this.pos.x - handleWidth/2;
|
|
6913
|
+
const p2 = this.pos.x + handleWidth/2;
|
|
6914
|
+
const handlePos = vec2(lerp(this.value, p1, p2), this.pos.y);
|
|
6915
|
+
const barColor = this.mouseIsHeld ? this.color : this.handleColor;
|
|
6916
|
+
uiSystem.drawRect(handlePos, handleSize, barColor, this.lineWidth, this.lineColor);
|
|
6917
|
+
|
|
6918
|
+
const textSize = vec2(this.size.x, this.size.y*.8);
|
|
6919
|
+
uiSystem.drawText(this.text, this.pos, textSize,
|
|
6920
|
+
this.textColor, 0, undefined, this.align, this.font);
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
|
|
6924
|
+
/**
|
|
6925
|
+
* LittleJS Box2D Physics Plugin
|
|
6926
|
+
* - Box2dObject extends EngineObject with Box2D physics
|
|
6927
|
+
* - Call box2dEngineInit() to start instead of normal engineInit()
|
|
6928
|
+
* - You will also need to include box2d.wasm.js
|
|
6929
|
+
* - Uses box2d.js super fast web assembly port of Box2D
|
|
6930
|
+
* - More info: https://github.com/kripken/box2d.js
|
|
6931
|
+
* - Fully wraps everything in Box2d
|
|
6932
|
+
* - Functions to create polygon, circle, and edge shapes
|
|
6933
|
+
* - Raycasting and querying
|
|
6934
|
+
* - Joint creation
|
|
6935
|
+
* - Contact begin and end callbacks
|
|
6936
|
+
* - Debug physics drawing
|
|
6937
|
+
*/
|
|
6938
|
+
|
|
6939
|
+
|
|
6940
|
+
|
|
6941
|
+
/** Global Box2d Plugin object
|
|
6942
|
+
* @type {Box2dPlugin} */
|
|
6943
|
+
let box2d;
|
|
6944
|
+
|
|
6945
|
+
/** Enable Box2D debug drawing
|
|
6946
|
+
* @type {boolean}
|
|
6947
|
+
* @default */
|
|
6948
|
+
let box2dDebug = false;
|
|
6949
|
+
|
|
6950
|
+
/** Enable Box2D debug drawing
|
|
6951
|
+
* @param {boolean} enable */
|
|
6952
|
+
function box2dSetDebug(enable) { box2dDebug = enable; }
|
|
6953
|
+
|
|
6954
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6955
|
+
/**
|
|
6956
|
+
* Box2D Object - extend with your own custom physics objects
|
|
6957
|
+
* - A LittleJS object with Box2D physics
|
|
6958
|
+
* - Each object has a Box2D body which can have multiple fixtures and joints
|
|
6959
|
+
* - Provides interface for Box2D body and fixture functions
|
|
6960
|
+
* @extends EngineObject
|
|
6961
|
+
*/
|
|
6962
|
+
class Box2dObject extends EngineObject
|
|
6963
|
+
{
|
|
6964
|
+
/** Create a LittleJS object with Box2d physics
|
|
6965
|
+
* @param {Vector2} [pos]
|
|
6966
|
+
* @param {Vector2} [size]
|
|
6967
|
+
* @param {TileInfo} [tileInfo]
|
|
6968
|
+
* @param {number} [angle]
|
|
6969
|
+
* @param {Color} [color]
|
|
6970
|
+
* @param {number} [bodyType]
|
|
6971
|
+
* @param {number} [renderOrder] */
|
|
6972
|
+
constructor(pos=vec2(), size, tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
|
|
6973
|
+
{
|
|
6974
|
+
super(pos, size, tileInfo, angle, color, renderOrder);
|
|
6975
|
+
|
|
6976
|
+
// create physics body
|
|
6977
|
+
const bodyDef = new box2d.instance.b2BodyDef();
|
|
6978
|
+
bodyDef.set_type(bodyType);
|
|
6979
|
+
bodyDef.set_position(box2d.vec2dTo(pos));
|
|
6980
|
+
bodyDef.set_angle(-angle);
|
|
6981
|
+
this.body = box2d.world.CreateBody(bodyDef);
|
|
6982
|
+
this.body.object = this;
|
|
6983
|
+
this.outlineColor = BLACK;
|
|
6984
|
+
}
|
|
6985
|
+
|
|
6986
|
+
/** Destroy this object and it's physics body */
|
|
6987
|
+
destroy()
|
|
6988
|
+
{
|
|
6989
|
+
// destroy physics body, fixtures, and joints
|
|
6990
|
+
this.body && box2d.world.DestroyBody(this.body);
|
|
6991
|
+
this.body = 0;
|
|
6992
|
+
super.destroy();
|
|
6993
|
+
}
|
|
6994
|
+
|
|
6995
|
+
/** Copy box2d update sim data */
|
|
6996
|
+
update()
|
|
6997
|
+
{
|
|
6998
|
+
// use box2d physics update
|
|
6999
|
+
this.pos = box2d.vec2From(this.body.GetPosition());
|
|
7000
|
+
this.angle = -this.body.GetAngle();
|
|
7001
|
+
}
|
|
7002
|
+
|
|
7003
|
+
/** Render the object, uses box2d drawing if no tile info exists */
|
|
7004
|
+
render()
|
|
7005
|
+
{
|
|
7006
|
+
// use default render or draw fixtures
|
|
7007
|
+
if (this.tileInfo)
|
|
7008
|
+
super.render();
|
|
7009
|
+
else
|
|
7010
|
+
this.drawFixtures(this.color, this.outlineColor, this.lineWidth, mainContext);
|
|
7011
|
+
}
|
|
7012
|
+
|
|
7013
|
+
/** Render debug info */
|
|
7014
|
+
renderDebugInfo()
|
|
7015
|
+
{
|
|
7016
|
+
const isAsleep = !this.getIsAwake();
|
|
7017
|
+
const isStatic = this.getBodyType() == box2d.bodyTypeStatic;
|
|
7018
|
+
const color = rgb(isAsleep?1:0, isAsleep?1:0, isStatic?1:0, .5);
|
|
7019
|
+
this.drawFixtures(color);
|
|
7020
|
+
}
|
|
7021
|
+
|
|
7022
|
+
/** Draws all this object's fixtures
|
|
7023
|
+
* @param {Color} [color]
|
|
7024
|
+
* @param {Color} [outlineColor]
|
|
7025
|
+
* @param {number} [lineWidth]
|
|
7026
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
7027
|
+
drawFixtures(color=WHITE, outlineColor, lineWidth=.1, context)
|
|
7028
|
+
{
|
|
7029
|
+
this.getFixtureList().forEach(fixture=>
|
|
7030
|
+
box2d.drawFixture(fixture, this.pos, this.angle, color, outlineColor, lineWidth, context));
|
|
7031
|
+
}
|
|
7032
|
+
|
|
7033
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7034
|
+
// physics contact callbacks
|
|
7035
|
+
|
|
7036
|
+
/** Called when a contact begins
|
|
7037
|
+
* @param {Box2dObject} otherObject */
|
|
7038
|
+
beginContact(otherObject) {}
|
|
7039
|
+
|
|
7040
|
+
/** Called when a contact ends
|
|
7041
|
+
* @param {Box2dObject} otherObject */
|
|
7042
|
+
endContact(otherObject) {}
|
|
7043
|
+
|
|
7044
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7045
|
+
// physics fixtures and shapes
|
|
7046
|
+
|
|
7047
|
+
/** Add a shape fixture to the body
|
|
7048
|
+
* @param {Object} shape
|
|
7049
|
+
* @param {number} [density]
|
|
7050
|
+
* @param {number} [friction]
|
|
7051
|
+
* @param {number} [restitution]
|
|
7052
|
+
* @param {boolean} [isSensor] */
|
|
7053
|
+
addShape(shape, density=1, friction=.2, restitution=0, isSensor=false)
|
|
7054
|
+
{
|
|
7055
|
+
const fd = new box2d.instance.b2FixtureDef();
|
|
7056
|
+
fd.set_shape(shape);
|
|
7057
|
+
fd.set_density(density);
|
|
7058
|
+
fd.set_friction(friction);
|
|
7059
|
+
fd.set_restitution(restitution);
|
|
7060
|
+
fd.set_isSensor(isSensor);
|
|
7061
|
+
return this.body.CreateFixture(fd);
|
|
7062
|
+
}
|
|
7063
|
+
|
|
7064
|
+
/** Add a box shape to the body
|
|
7065
|
+
* @param {Vector2} [size]
|
|
7066
|
+
* @param {Vector2} [offset]
|
|
7067
|
+
* @param {number} [angle]
|
|
7068
|
+
* @param {number} [density]
|
|
7069
|
+
* @param {number} [friction]
|
|
7070
|
+
* @param {number} [restitution]
|
|
7071
|
+
* @param {boolean} [isSensor] */
|
|
7072
|
+
addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
|
|
7073
|
+
{
|
|
7074
|
+
const shape = new box2d.instance.b2PolygonShape();
|
|
7075
|
+
shape.SetAsBox(size.x/2, size.y/2, box2d.vec2dTo(offset), angle);
|
|
7076
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
7077
|
+
}
|
|
7078
|
+
|
|
7079
|
+
/** Add a polygon shape to the body
|
|
7080
|
+
* @param {Array<Vector2>} points
|
|
7081
|
+
* @param {number} [density]
|
|
7082
|
+
* @param {number} [friction]
|
|
7083
|
+
* @param {number} [restitution]
|
|
7084
|
+
* @param {boolean} [isSensor] */
|
|
7085
|
+
addPoly(points, density, friction, restitution, isSensor)
|
|
7086
|
+
{
|
|
7087
|
+
function box2dCreatePolygonShape(points)
|
|
7088
|
+
{
|
|
7089
|
+
function box2dCreatePointList(points)
|
|
7090
|
+
{
|
|
7091
|
+
const buffer = box2d.instance._malloc(points.length * 8);
|
|
7092
|
+
for (let i=0, offset=0; i<points.length; ++i)
|
|
7093
|
+
{
|
|
7094
|
+
box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].x;
|
|
7095
|
+
offset += 4;
|
|
7096
|
+
box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].y;
|
|
7097
|
+
offset += 4;
|
|
7098
|
+
}
|
|
7099
|
+
return box2d.instance.wrapPointer(buffer, box2d.instance.b2Vec2);
|
|
7100
|
+
}
|
|
7101
|
+
|
|
7102
|
+
ASSERT(3 <= points.length && points.length <= 8);
|
|
7103
|
+
const shape = new box2d.instance.b2PolygonShape();
|
|
7104
|
+
const box2dPoints = box2dCreatePointList(points);
|
|
7105
|
+
shape.Set(box2dPoints, points.length);
|
|
7106
|
+
return shape;
|
|
7107
|
+
}
|
|
7108
|
+
|
|
7109
|
+
const shape = box2dCreatePolygonShape(points);
|
|
7110
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
7111
|
+
}
|
|
7112
|
+
|
|
7113
|
+
/** Add a regular polygon shape to the body
|
|
7114
|
+
* @param {number} [diameter]
|
|
7115
|
+
* @param {number} [sides]
|
|
7116
|
+
* @param {number} [density]
|
|
7117
|
+
* @param {number} [friction]
|
|
7118
|
+
* @param {number} [restitution]
|
|
7119
|
+
* @param {boolean} [isSensor] */
|
|
7120
|
+
addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
|
|
7121
|
+
{
|
|
7122
|
+
const points = [];
|
|
7123
|
+
const radius = diameter/2;
|
|
7124
|
+
for (let i=sides; i--;)
|
|
7125
|
+
points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2));
|
|
7126
|
+
return this.addPoly(points, density, friction, restitution, isSensor);
|
|
7127
|
+
}
|
|
7128
|
+
|
|
7129
|
+
/** Add a random polygon shape to the body
|
|
7130
|
+
* @param {number} [diameter]
|
|
7131
|
+
* @param {number} [density]
|
|
7132
|
+
* @param {number} [friction]
|
|
7133
|
+
* @param {number} [restitution]
|
|
7134
|
+
* @param {boolean} [isSensor] */
|
|
7135
|
+
addRandomPoly(diameter=1, density, friction, restitution, isSensor)
|
|
7136
|
+
{
|
|
7137
|
+
const sides = randInt(3, 9);
|
|
7138
|
+
const points = [];
|
|
7139
|
+
const radius = diameter/2;
|
|
7140
|
+
for (let i=sides; i--;)
|
|
7141
|
+
points.push(vec2(rand(radius/2,radius*1.5),0).rotate(i/sides*PI*2));
|
|
7142
|
+
return this.addPoly(points, density, friction, restitution, isSensor);
|
|
7143
|
+
}
|
|
7144
|
+
|
|
7145
|
+
/** Add a circle shape to the body
|
|
7146
|
+
* @param {number} [diameter]
|
|
7147
|
+
* @param {Vector2} [offset]
|
|
7148
|
+
* @param {number} [density]
|
|
7149
|
+
* @param {number} [friction]
|
|
7150
|
+
* @param {number} [restitution]
|
|
7151
|
+
* @param {boolean} [isSensor] */
|
|
7152
|
+
addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
|
|
7153
|
+
{
|
|
7154
|
+
const shape = new box2d.instance.b2CircleShape();
|
|
7155
|
+
shape.set_m_p(box2d.vec2dTo(offset));
|
|
7156
|
+
shape.set_m_radius(diameter/2);
|
|
7157
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
7158
|
+
}
|
|
7159
|
+
|
|
7160
|
+
/** Add an edge shape to the body
|
|
7161
|
+
* @param {Vector2} point1
|
|
7162
|
+
* @param {Vector2} point2
|
|
7163
|
+
* @param {number} [density]
|
|
7164
|
+
* @param {number} [friction]
|
|
7165
|
+
* @param {number} [restitution]
|
|
7166
|
+
* @param {boolean} [isSensor] */
|
|
7167
|
+
addEdge(point1, point2, density, friction, restitution, isSensor)
|
|
7168
|
+
{
|
|
7169
|
+
const shape = new box2d.instance.b2EdgeShape();
|
|
7170
|
+
shape.Set(box2d.vec2dTo(point1), box2d.vec2dTo(point2));
|
|
7171
|
+
return this.addShape(shape, density, friction, restitution, isSensor);
|
|
7172
|
+
}
|
|
7173
|
+
|
|
7174
|
+
/** Add an edge loop to the body, an edge loop connects the end points
|
|
7175
|
+
* @param {Array<Vector2>} points
|
|
7176
|
+
* @param {number} [density]
|
|
7177
|
+
* @param {number} [friction]
|
|
7178
|
+
* @param {number} [restitution]
|
|
7179
|
+
* @param {boolean} [isSensor] */
|
|
7180
|
+
addEdgeLoop(points, density, friction, restitution, isSensor)
|
|
7181
|
+
{
|
|
7182
|
+
const fixtures = [];
|
|
7183
|
+
const getPoint = i=> points[mod(i,points.length)];
|
|
7184
|
+
for (let i=0; i<points.length; ++i)
|
|
7185
|
+
{
|
|
7186
|
+
const shape = new box2d.instance.b2EdgeShape();
|
|
7187
|
+
shape.set_m_vertex0(box2d.vec2dTo(getPoint(i-1)));
|
|
7188
|
+
shape.set_m_vertex1(box2d.vec2dTo(getPoint(i+0)));
|
|
7189
|
+
shape.set_m_vertex2(box2d.vec2dTo(getPoint(i+1)));
|
|
7190
|
+
shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
|
|
7191
|
+
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
7192
|
+
fixtures.push(f);
|
|
7193
|
+
}
|
|
7194
|
+
return fixtures;
|
|
7195
|
+
}
|
|
7196
|
+
|
|
7197
|
+
/** Add an edge list to the body
|
|
7198
|
+
* @param {Array<Vector2>} points
|
|
7199
|
+
* @param {number} [density]
|
|
7200
|
+
* @param {number} [friction]
|
|
7201
|
+
* @param {number} [restitution]
|
|
7202
|
+
* @param {boolean} [isSensor] */
|
|
7203
|
+
addEdgeList(points, density, friction, restitution, isSensor)
|
|
7204
|
+
{
|
|
7205
|
+
const fixtures = [];
|
|
7206
|
+
for (let i=0; i<points.length-1; ++i)
|
|
7207
|
+
{
|
|
7208
|
+
const shape = new box2d.instance.b2EdgeShape();
|
|
7209
|
+
points[i-1] && shape.set_m_vertex0(box2d.vec2dTo(points[i-1]));
|
|
7210
|
+
points[i+0] && shape.set_m_vertex1(box2d.vec2dTo(points[i+0]));
|
|
7211
|
+
points[i+1] && shape.set_m_vertex2(box2d.vec2dTo(points[i+1]));
|
|
7212
|
+
points[i+2] && shape.set_m_vertex3(box2d.vec2dTo(points[i+2]));
|
|
7213
|
+
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
7214
|
+
fixtures.push(f);
|
|
7215
|
+
}
|
|
7216
|
+
return fixtures;
|
|
7217
|
+
}
|
|
7218
|
+
|
|
7219
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7220
|
+
// physics get functions
|
|
7221
|
+
|
|
7222
|
+
/** Gets the center of mass
|
|
7223
|
+
* @return {Vector2} */
|
|
7224
|
+
getCenterOfMass() { return box2d.vec2From(this.body.GetWorldCenter()); }
|
|
7225
|
+
|
|
7226
|
+
/** Gets the linear velocity
|
|
7227
|
+
* @return {Vector2} */
|
|
7228
|
+
getLinearVelocity() { return box2d.vec2From(this.body.GetLinearVelocity()); }
|
|
7229
|
+
|
|
7230
|
+
/** Gets the angular velocity
|
|
7231
|
+
* @return {Vector2} */
|
|
7232
|
+
getAngularVelocity() { return this.body.GetAngularVelocity(); }
|
|
7233
|
+
|
|
7234
|
+
/** Gets the mass
|
|
7235
|
+
* @return {number} */
|
|
7236
|
+
getMass() { return this.body.GetMass(); }
|
|
7237
|
+
|
|
7238
|
+
/** Gets the rotational inertia
|
|
7239
|
+
* @return {number} */
|
|
7240
|
+
getInertia() { return this.body.GetInertia(); }
|
|
7241
|
+
|
|
7242
|
+
/** Check if this object is awake
|
|
7243
|
+
* @return {boolean} */
|
|
7244
|
+
getIsAwake() { return this.body.IsAwake(); }
|
|
7245
|
+
|
|
7246
|
+
/** Gets the physics body type
|
|
7247
|
+
* @return {number} */
|
|
7248
|
+
getBodyType() { return this.body.GetType(); }
|
|
7249
|
+
|
|
7250
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7251
|
+
// physics set functions
|
|
7252
|
+
|
|
7253
|
+
/** Sets the position and angle
|
|
7254
|
+
* @param {Vector2} pos
|
|
7255
|
+
* @param {number} angle */
|
|
7256
|
+
setTransform(pos, angle)
|
|
7257
|
+
{
|
|
7258
|
+
this.pos = pos;
|
|
7259
|
+
this.angle = angle;
|
|
7260
|
+
this.body.SetTransform(box2d.vec2dTo(pos), angle);
|
|
7261
|
+
}
|
|
7262
|
+
|
|
7263
|
+
/** Sets the position
|
|
7264
|
+
* @param {Vector2} pos */
|
|
7265
|
+
setPosition(pos) { this.setTransform(pos, this.body.GetAngle()); }
|
|
7266
|
+
|
|
7267
|
+
/** Sets the angle
|
|
7268
|
+
* @param {number} angle */
|
|
7269
|
+
setAngle(angle) { this.setTransform(box2d.vec2From(this.body.GetPosition()), -angle); }
|
|
7270
|
+
|
|
7271
|
+
/** Sets the linear velocity
|
|
7272
|
+
* @param {Vector2} velocity */
|
|
7273
|
+
setLinearVelocity(velocity) { this.body.SetLinearVelocity(box2d.vec2dTo(velocity)); }
|
|
7274
|
+
|
|
7275
|
+
/** Sets the angular velocity
|
|
7276
|
+
* @param {number} angularVelocity */
|
|
7277
|
+
setAngularVelocity(angularVelocity) { this.body.SetAngularVelocity(angularVelocity); }
|
|
7278
|
+
|
|
7279
|
+
/** Sets the linear damping
|
|
7280
|
+
* @param {number} damping */
|
|
7281
|
+
setLinearDamping(damping) { this.body.SetLinearDamping(damping); }
|
|
7282
|
+
|
|
7283
|
+
/** Sets the angular damping
|
|
7284
|
+
* @param {number} damping */
|
|
7285
|
+
setAngularDamping(damping) { this.body.SetAngularDamping(damping); }
|
|
7286
|
+
|
|
7287
|
+
/** Sets the gravity scale
|
|
7288
|
+
* @param {number} [scale] */
|
|
7289
|
+
setGravityScale(scale=1) { this.body.SetGravityScale(this.gravityScale = scale); }
|
|
7290
|
+
|
|
7291
|
+
/** Should this body be treated like a bullet for continuous collision detection?
|
|
7292
|
+
* @param {boolean} [isBullet] */
|
|
7293
|
+
setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
|
|
7294
|
+
|
|
7295
|
+
/** Set the sleep state of the body
|
|
7296
|
+
* @param {boolean} [isAwake] */
|
|
7297
|
+
setAwake(isAwake=true) { this.body.SetAwake(isAwake); }
|
|
7298
|
+
|
|
7299
|
+
/** Set the physics body type
|
|
7300
|
+
* @param {number} type */
|
|
7301
|
+
setBodyType(type) { this.body.SetType(type); }
|
|
7302
|
+
|
|
7303
|
+
/** Set whether the body is allowed to sleep
|
|
7304
|
+
* @param {boolean} [isAllowed] */
|
|
7305
|
+
setSleepingAllowed(isAllowed=true) { this.body.SetSleepingAllowed(isAllowed); }
|
|
7306
|
+
|
|
7307
|
+
/** Set whether the body can rotate
|
|
7308
|
+
* @param {boolean} [isFixed] */
|
|
7309
|
+
setFixedRotation(isFixed=true) { this.body.SetFixedRotation(isFixed); }
|
|
7310
|
+
|
|
7311
|
+
/** Set the center of mass of the body
|
|
7312
|
+
* @param {Vector2} center */
|
|
7313
|
+
setCenterOfMass(center) { this.setMassData(center) }
|
|
7314
|
+
|
|
7315
|
+
/** Set the mass of the body
|
|
7316
|
+
* @param {number} mass */
|
|
7317
|
+
setMass(mass) { this.setMassData(undefined, mass) }
|
|
7318
|
+
|
|
7319
|
+
/** Set the moment of inertia of the body
|
|
7320
|
+
* @param {number} momentOfInertia */
|
|
7321
|
+
setMomentOfInertia(momentOfInertia) { this.setMassData(undefined, undefined, momentOfInertia) }
|
|
7322
|
+
|
|
7323
|
+
/** Reset the mass, center of mass, and moment */
|
|
7324
|
+
resetMassData() { this.body.ResetMassData(); }
|
|
7325
|
+
|
|
7326
|
+
/** Set the mass data of the body
|
|
7327
|
+
* @param {Vector2} [localCenter]
|
|
7328
|
+
* @param {number} [mass]
|
|
7329
|
+
* @param {number} [momentOfInertia] */
|
|
7330
|
+
setMassData(localCenter, mass, momentOfInertia)
|
|
7331
|
+
{
|
|
7332
|
+
const data = new box2d.instance.b2MassData();
|
|
7333
|
+
this.body.GetMassData(data);
|
|
7334
|
+
localCenter && data.set_center(box2d.vec2dTo(localCenter));
|
|
7335
|
+
mass && data.set_mass(mass);
|
|
7336
|
+
momentOfInertia && data.set_I(momentOfInertia);
|
|
7337
|
+
this.body.SetMassData(data);
|
|
7338
|
+
}
|
|
7339
|
+
|
|
7340
|
+
/** Set the collision filter data for this body
|
|
7341
|
+
* @param {number} [categoryBits]
|
|
7342
|
+
* @param {number} [ignoreCategoryBits]
|
|
7343
|
+
* @param {number} [groupIndex] */
|
|
7344
|
+
setFilterData(categoryBits=0, ignoreCategoryBits=0, groupIndex=0)
|
|
7345
|
+
{
|
|
7346
|
+
this.getFixtureList().forEach(fixture=>
|
|
7347
|
+
{
|
|
7348
|
+
const filter = fixture.GetFilterData();
|
|
7349
|
+
filter.set_categoryBits(categoryBits);
|
|
7350
|
+
filter.set_maskBits(0xffff & ~ignoreCategoryBits);
|
|
7351
|
+
filter.set_groupIndex(groupIndex);
|
|
7352
|
+
});
|
|
7353
|
+
}
|
|
7354
|
+
|
|
7355
|
+
/** Set if this body is a sensor
|
|
7356
|
+
* @param {boolean} [isSensor] */
|
|
7357
|
+
setSensor(isSensor=true)
|
|
7358
|
+
{ this.getFixtureList().forEach(f=>f.SetSensor(isSensor)); }
|
|
7359
|
+
|
|
7360
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7361
|
+
// physics force and torque functions
|
|
7362
|
+
|
|
7363
|
+
/** Apply force to this object
|
|
7364
|
+
* @param {Vector2} force
|
|
7365
|
+
* @param {Vector2} [pos] */
|
|
7366
|
+
applyForce(force, pos)
|
|
7367
|
+
{
|
|
7368
|
+
pos ||= this.getCenterOfMass();
|
|
7369
|
+
this.setAwake();
|
|
7370
|
+
this.body.ApplyForce(box2d.vec2dTo(force), box2d.vec2dTo(pos));
|
|
7371
|
+
}
|
|
7372
|
+
|
|
7373
|
+
/** Apply acceleration to this object
|
|
7374
|
+
* @param {Vector2} acceleration
|
|
7375
|
+
* @param {Vector2} [pos] */
|
|
7376
|
+
applyAcceleration(acceleration, pos)
|
|
7377
|
+
{
|
|
7378
|
+
pos ||= this.getCenterOfMass();
|
|
7379
|
+
this.setAwake();
|
|
7380
|
+
this.body.ApplyLinearImpulse(box2d.vec2dTo(acceleration), box2d.vec2dTo(pos));
|
|
7381
|
+
}
|
|
7382
|
+
|
|
7383
|
+
/** Apply torque to this object
|
|
7384
|
+
* @param {number} torque */
|
|
7385
|
+
applyTorque(torque)
|
|
7386
|
+
{
|
|
7387
|
+
this.setAwake();
|
|
7388
|
+
this.body.ApplyTorque(torque);
|
|
7389
|
+
}
|
|
7390
|
+
|
|
7391
|
+
/** Apply angular acceleration to this object
|
|
7392
|
+
* @param {number} acceleration */
|
|
7393
|
+
applyAngularAcceleration(acceleration)
|
|
7394
|
+
{
|
|
7395
|
+
this.setAwake();
|
|
7396
|
+
this.body.ApplyAngularImpulse(acceleration);
|
|
7397
|
+
}
|
|
7398
|
+
|
|
7399
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7400
|
+
// lists of fixtures and joints
|
|
7401
|
+
|
|
7402
|
+
/** Check if this object has any fixtures
|
|
7403
|
+
* @return {boolean} */
|
|
7404
|
+
hasFixtures() { return !box2d.isNull(this.body.GetFixtureList()); }
|
|
7405
|
+
|
|
7406
|
+
/** Get list of fixtures for this object
|
|
7407
|
+
* @return {Array<Object>} */
|
|
7408
|
+
getFixtureList()
|
|
7409
|
+
{
|
|
7410
|
+
const fixtures = [];
|
|
7411
|
+
for (let fixture=this.body.GetFixtureList(); !box2d.isNull(fixture); )
|
|
7412
|
+
{
|
|
7413
|
+
fixtures.push(fixture);
|
|
7414
|
+
fixture = fixture.GetNext();
|
|
7415
|
+
}
|
|
7416
|
+
return fixtures;
|
|
7417
|
+
}
|
|
7418
|
+
|
|
7419
|
+
/** Check if this object has any joints
|
|
7420
|
+
* @return {boolean} */
|
|
7421
|
+
hasJoints() { return !box2d.isNull(this.body.GetJointList()); }
|
|
7422
|
+
|
|
7423
|
+
/** Get list of joints for this object
|
|
7424
|
+
* @return {Array<Object>} */
|
|
7425
|
+
getJointList()
|
|
7426
|
+
{
|
|
7427
|
+
const joints = [];
|
|
7428
|
+
for (let joint=this.body.GetJointList(); !box2d.isNull(joint); )
|
|
7429
|
+
{
|
|
7430
|
+
joints.push(joint);
|
|
7431
|
+
joint = joint.get_next();
|
|
7432
|
+
}
|
|
7433
|
+
return joints;
|
|
7434
|
+
}
|
|
7435
|
+
}
|
|
7436
|
+
|
|
7437
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7438
|
+
/**
|
|
7439
|
+
* Box2D Raycast Result
|
|
7440
|
+
* - Holds results from a box2d raycast queries
|
|
7441
|
+
* - Automatically created by box2d raycast functions
|
|
7442
|
+
*/
|
|
7443
|
+
class Box2dRaycastResult
|
|
7444
|
+
{
|
|
7445
|
+
/** Create a raycast result
|
|
7446
|
+
* @param {Object} fixture
|
|
7447
|
+
* @param {Vector2} point
|
|
7448
|
+
* @param {Vector2} normal
|
|
7449
|
+
* @param {number} fraction */
|
|
7450
|
+
constructor(fixture, point, normal, fraction)
|
|
7451
|
+
{
|
|
7452
|
+
/** @property {Box2dObject} - The box2d object */
|
|
7453
|
+
this.object = fixture.GetBody().object;
|
|
7454
|
+
/** @property {Object} - The fixture that was hit */
|
|
7455
|
+
this.fixture = fixture;
|
|
7456
|
+
/** @property {Vector2} - The hit point */
|
|
7457
|
+
this.point = point;
|
|
7458
|
+
/** @property {Vector2} - The hit normal */
|
|
7459
|
+
this.normal = normal;
|
|
7460
|
+
/** @property {number} - Distance fraction at the point of intersection */
|
|
7461
|
+
this.fraction = fraction;
|
|
7462
|
+
}
|
|
7463
|
+
}
|
|
7464
|
+
|
|
7465
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7466
|
+
/**
|
|
7467
|
+
* Box2D Joint
|
|
7468
|
+
* - Base class for Box2D joints
|
|
7469
|
+
* - A joint is used to connect objects together
|
|
7470
|
+
*/
|
|
7471
|
+
class Box2dJoint
|
|
7472
|
+
{
|
|
7473
|
+
/** Create a box2d joint, the base class is not intended to be used directly
|
|
7474
|
+
* @param {Object} jointDef */
|
|
7475
|
+
constructor(jointDef)
|
|
7476
|
+
{
|
|
7477
|
+
this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
|
|
7478
|
+
}
|
|
7479
|
+
|
|
7480
|
+
/** Destroy this joint */
|
|
7481
|
+
destroy() { box2d.world.DestroyJoint(this.box2dJoint); this.box2dJoint = 0; }
|
|
7482
|
+
|
|
7483
|
+
/** Get the first object attached to this joint
|
|
7484
|
+
* @return {Box2dObject} */
|
|
7485
|
+
getObjectA() { return this.box2dJoint.GetBodyA().object; }
|
|
7486
|
+
|
|
7487
|
+
/** Get the second object attached to this joint
|
|
7488
|
+
* @return {Box2dObject} */
|
|
7489
|
+
getObjectB() { return this.box2dJoint.GetBodyB().object; }
|
|
7490
|
+
|
|
7491
|
+
/** Get the first anchor for this joint in world coordinates
|
|
7492
|
+
* @return {Vector2} */
|
|
7493
|
+
getAnchorA() { return box2d.vec2From(this.box2dJoint.GetAnchorA());}
|
|
7494
|
+
|
|
7495
|
+
/** Get the second anchor for this joint in world coordinates
|
|
7496
|
+
* @return {Vector2} */
|
|
7497
|
+
getAnchorB() { return box2d.vec2From(this.box2dJoint.GetAnchorB());}
|
|
7498
|
+
|
|
7499
|
+
/** Get the reaction force on bodyB at the joint anchor given a time step
|
|
7500
|
+
* @param {number} time
|
|
7501
|
+
* @return {Vector2} */
|
|
7502
|
+
getReactionForce(time) { return box2d.vec2From(this.box2dJoint.GetReactionForce(1/time));}
|
|
7503
|
+
|
|
7504
|
+
/** Get the reaction torque on bodyB in N*m given a time step
|
|
7505
|
+
* @param {number} time
|
|
7506
|
+
* @return {number} */
|
|
7507
|
+
getReactionTorque(time) { return this.box2dJoint.GetReactionTorque(1/time);}
|
|
7508
|
+
|
|
7509
|
+
/** Check if the connected bodies should collide
|
|
7510
|
+
* @return {boolean} */
|
|
7511
|
+
getCollideConnected() { return this.box2dJoint.getCollideConnected();}
|
|
7512
|
+
|
|
7513
|
+
/** Check if either connected body is active
|
|
7514
|
+
* @return {boolean} */
|
|
7515
|
+
isActive() { return this.box2dJoint.IsActive();}
|
|
7516
|
+
}
|
|
7517
|
+
|
|
7518
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7519
|
+
/**
|
|
7520
|
+
* Box2D Target Joint, also known as a mouse joint
|
|
7521
|
+
* - Used to make a point on a object track a specific world point target
|
|
7522
|
+
* - This a soft constraint with a max force
|
|
7523
|
+
* - This allows the constraint to stretch and without applying huge forces
|
|
7524
|
+
* @extends Box2dJoint
|
|
7525
|
+
*/
|
|
7526
|
+
class Box2dTargetJoint extends Box2dJoint
|
|
7527
|
+
{
|
|
7528
|
+
/** Create a target joint
|
|
7529
|
+
* @param {Box2dObject} object
|
|
7530
|
+
* @param {Box2dObject} fixedObject
|
|
7531
|
+
* @param {Vector2} worldPos */
|
|
7532
|
+
constructor(object, fixedObject, worldPos)
|
|
7533
|
+
{
|
|
7534
|
+
object.setAwake();
|
|
7535
|
+
const jointDef = new box2d.instance.b2MouseJointDef();
|
|
7536
|
+
jointDef.set_bodyA(fixedObject.body);
|
|
7537
|
+
jointDef.set_bodyB(object.body);
|
|
7538
|
+
jointDef.set_target(box2d.vec2dTo(worldPos));
|
|
7539
|
+
jointDef.set_maxForce(2e3 * object.getMass());
|
|
7540
|
+
super(jointDef);
|
|
7541
|
+
}
|
|
7542
|
+
|
|
7543
|
+
/** Set the target point in world coordinates
|
|
7544
|
+
* @param {Vector2} pos */
|
|
7545
|
+
setTarget(pos) { this.box2dJoint.SetTarget(box2d.vec2dTo(pos)); }
|
|
7546
|
+
|
|
7547
|
+
/** Get the target point in world coordinates
|
|
7548
|
+
* @return {Vector2} */
|
|
7549
|
+
getTarget(){ return box2d.vec2From(this.box2dJoint.GetTarget()); }
|
|
7550
|
+
|
|
7551
|
+
/** Sets the maximum force in Newtons
|
|
7552
|
+
* @param {number} force */
|
|
7553
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
|
|
7554
|
+
|
|
7555
|
+
/** Gets the maximum force in Newtons
|
|
7556
|
+
* @return {number} */
|
|
7557
|
+
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
|
|
7558
|
+
|
|
7559
|
+
/** Sets the joint frequency in Hertz
|
|
7560
|
+
* @param {number} hz */
|
|
7561
|
+
setFrequency(hz) { this.box2dJoint.SetFrequency(hz); }
|
|
7562
|
+
|
|
7563
|
+
/** Gets the joint frequency in Hertz
|
|
7564
|
+
* @return {number} */
|
|
7565
|
+
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
7566
|
+
}
|
|
7567
|
+
|
|
7568
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7569
|
+
/**
|
|
7570
|
+
* Box2D Distance Joint
|
|
7571
|
+
* - Constrains two points on two objects to remain at a fixed distance
|
|
7572
|
+
* - You can view this as a massless, rigid rod
|
|
7573
|
+
* @extends Box2dJoint
|
|
7574
|
+
*/
|
|
7575
|
+
class Box2dDistanceJoint extends Box2dJoint
|
|
7576
|
+
{
|
|
7577
|
+
/** Create a distance joint
|
|
7578
|
+
* @param {Box2dObject} objectA
|
|
7579
|
+
* @param {Box2dObject} objectB
|
|
7580
|
+
* @param {Vector2} anchorA
|
|
7581
|
+
* @param {Vector2} anchorB
|
|
7582
|
+
* @param {boolean} [collide] */
|
|
7583
|
+
constructor(objectA, objectB, anchorA, anchorB, collide=false)
|
|
7584
|
+
{
|
|
7585
|
+
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
|
|
7586
|
+
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7587
|
+
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
7588
|
+
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
7589
|
+
const jointDef = new box2d.instance.b2DistanceJointDef();
|
|
7590
|
+
jointDef.set_bodyA(objectA.body);
|
|
7591
|
+
jointDef.set_bodyB(objectB.body);
|
|
7592
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7593
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7594
|
+
jointDef.set_length(anchorA.distance(anchorB));
|
|
7595
|
+
jointDef.set_collideConnected(collide);
|
|
7596
|
+
super(jointDef);
|
|
7597
|
+
}
|
|
7598
|
+
|
|
7599
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7600
|
+
* @return {Vector2} */
|
|
7601
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7602
|
+
|
|
7603
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7604
|
+
* @return {Vector2} */
|
|
7605
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7606
|
+
|
|
7607
|
+
/** Set the length of the joint
|
|
7608
|
+
* @param {number} length */
|
|
7609
|
+
setLength(length) { this.box2dJoint.SetLength(length); }
|
|
7610
|
+
|
|
7611
|
+
/** Get the length of the joint
|
|
7612
|
+
* @return {number} */
|
|
7613
|
+
getLength() { return this.box2dJoint.GetLength(); }
|
|
7614
|
+
|
|
7615
|
+
/** Set the frequency in Hertz
|
|
7616
|
+
* @param {number} hz */
|
|
7617
|
+
setFrequency(hz) { this.box2dJoint.SetFrequency(hz); }
|
|
7618
|
+
|
|
7619
|
+
/** Get the frequency in Hertz
|
|
7620
|
+
* @return {number} */
|
|
7621
|
+
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
7622
|
+
|
|
7623
|
+
/** Set the damping ratio
|
|
7624
|
+
* @param {number} ratio */
|
|
7625
|
+
setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); }
|
|
7626
|
+
|
|
7627
|
+
/** Get the damping ratio
|
|
7628
|
+
* @return {number} */
|
|
7629
|
+
getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
|
|
7630
|
+
}
|
|
7631
|
+
|
|
7632
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7633
|
+
/**
|
|
7634
|
+
* Box2D Pin Joint
|
|
7635
|
+
* - Pins two objects together at a point
|
|
7636
|
+
* @extends Box2dDistanceJoint
|
|
7637
|
+
*/
|
|
7638
|
+
class Box2dPinJoint extends Box2dDistanceJoint
|
|
7639
|
+
{
|
|
7640
|
+
/** Create a pin joint
|
|
7641
|
+
* @param {Box2dObject} objectA
|
|
7642
|
+
* @param {Box2dObject} objectB
|
|
7643
|
+
* @param {Vector2} [pos]
|
|
7644
|
+
* @param {boolean} [collide] */
|
|
7645
|
+
constructor(objectA, objectB, pos=objectA.pos, collide=false)
|
|
7646
|
+
{
|
|
7647
|
+
super(objectA, objectB, undefined, pos, collide);
|
|
7648
|
+
}
|
|
7649
|
+
}
|
|
7650
|
+
|
|
7651
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7652
|
+
/**
|
|
7653
|
+
* Box2D Rope Joint
|
|
7654
|
+
* - Enforces a maximum distance between two points on two objects
|
|
7655
|
+
* @extends Box2dJoint
|
|
7656
|
+
*/
|
|
7657
|
+
class Box2dRopeJoint extends Box2dJoint
|
|
7658
|
+
{
|
|
7659
|
+
/** Create a rope joint
|
|
7660
|
+
* @param {Box2dObject} objectA
|
|
7661
|
+
* @param {Box2dObject} objectB
|
|
7662
|
+
* @param {Vector2} anchorA
|
|
7663
|
+
* @param {Vector2} anchorB
|
|
7664
|
+
* @param {number} extraLength
|
|
7665
|
+
* @param {boolean} [collide] */
|
|
7666
|
+
constructor(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false)
|
|
7667
|
+
{
|
|
7668
|
+
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
|
|
7669
|
+
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7670
|
+
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
7671
|
+
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
7672
|
+
const jointDef = new box2d.instance.b2RopeJointDef();
|
|
7673
|
+
jointDef.set_bodyA(objectA.body);
|
|
7674
|
+
jointDef.set_bodyB(objectB.body);
|
|
7675
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7676
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7677
|
+
jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength);
|
|
7678
|
+
jointDef.set_collideConnected(collide);
|
|
7679
|
+
super(jointDef);
|
|
7680
|
+
}
|
|
7681
|
+
|
|
7682
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7683
|
+
* @return {Vector2} */
|
|
7684
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7685
|
+
|
|
7686
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7687
|
+
* @return {Vector2} */
|
|
7688
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7689
|
+
|
|
7690
|
+
/** Set the max length of the joint
|
|
7691
|
+
* @param {number} length */
|
|
7692
|
+
setMaxLength(length) { this.box2dJoint.SetMaxLength(length); }
|
|
7693
|
+
|
|
7694
|
+
/** Get the max length of the joint
|
|
7695
|
+
* @return {number} */
|
|
7696
|
+
getMaxLength() { return this.box2dJoint.GetMaxLength(); }
|
|
7697
|
+
}
|
|
7698
|
+
|
|
7699
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7700
|
+
/**
|
|
7701
|
+
* Box2D Revolute Joint
|
|
7702
|
+
* - Constrains two objects to share a point while they are free to rotate around the point
|
|
7703
|
+
* - The relative rotation about the shared point is the joint angle
|
|
7704
|
+
* - You can limit the relative rotation with a joint limit
|
|
7705
|
+
* - You can use a motor to drive the relative rotation about the shared point
|
|
7706
|
+
* - A maximum motor torque is provided so that infinite forces are not generated
|
|
7707
|
+
* @extends Box2dJoint
|
|
7708
|
+
*/
|
|
7709
|
+
class Box2dRevoluteJoint extends Box2dJoint
|
|
7710
|
+
{
|
|
7711
|
+
/** Create a revolute joint
|
|
7712
|
+
* @param {Box2dObject} objectA
|
|
7713
|
+
* @param {Box2dObject} objectB
|
|
7714
|
+
* @param {Vector2} anchor
|
|
7715
|
+
* @param {boolean} [collide] */
|
|
7716
|
+
constructor(objectA, objectB, anchor, collide=false)
|
|
7717
|
+
{
|
|
7718
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7719
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
7720
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
7721
|
+
const jointDef = new box2d.instance.b2RevoluteJointDef();
|
|
7722
|
+
jointDef.set_bodyA(objectA.body);
|
|
7723
|
+
jointDef.set_bodyB(objectB.body);
|
|
7724
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7725
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7726
|
+
jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
|
|
7727
|
+
jointDef.set_collideConnected(collide);
|
|
7728
|
+
super(jointDef);
|
|
7729
|
+
}
|
|
7730
|
+
|
|
7731
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7732
|
+
* @return {Vector2} */
|
|
7733
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7734
|
+
|
|
7735
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7736
|
+
* @return {Vector2} */
|
|
7737
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7738
|
+
|
|
7739
|
+
/** Get the reference angle, objectB angle minus objectA angle in the reference state
|
|
7740
|
+
* @return {number} */
|
|
7741
|
+
getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
|
|
7742
|
+
|
|
7743
|
+
/** Get the current joint angle
|
|
7744
|
+
* @return {number} */
|
|
7745
|
+
getJointAngle() { return this.box2dJoint.GetJointAngle(); }
|
|
7746
|
+
|
|
7747
|
+
/** Get the current joint angle speed in radians per second
|
|
7748
|
+
* @return {number} */
|
|
7749
|
+
getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
|
|
7750
|
+
|
|
7751
|
+
/** Is the joint limit enabled?
|
|
7752
|
+
* @return {boolean} */
|
|
7753
|
+
isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
|
|
7754
|
+
|
|
7755
|
+
/** Enable/disable the joint limit
|
|
7756
|
+
* @param {boolean} [enable] */
|
|
7757
|
+
enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
|
|
7758
|
+
|
|
7759
|
+
/** Get the lower joint limit
|
|
7760
|
+
* @return {number} */
|
|
7761
|
+
getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
|
|
7762
|
+
|
|
7763
|
+
/** Get the upper joint limit
|
|
7764
|
+
* @return {number} */
|
|
7765
|
+
getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
|
|
7766
|
+
|
|
7767
|
+
/** Set the joint limits
|
|
7768
|
+
* @param {number} min
|
|
7769
|
+
* @param {number} max */
|
|
7770
|
+
setLimits(min, max) { return this.box2dJoint.SetLimits(min, max); }
|
|
7771
|
+
|
|
7772
|
+
/** Is the joint motor enabled?
|
|
7773
|
+
* @return {boolean} */
|
|
7774
|
+
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
|
|
7775
|
+
|
|
7776
|
+
/** Enable/disable the joint motor
|
|
7777
|
+
* @param {boolean} [enable] */
|
|
7778
|
+
enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
|
|
7779
|
+
|
|
7780
|
+
/** Set the motor speed
|
|
7781
|
+
* @param {number} speed */
|
|
7782
|
+
setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
|
|
7783
|
+
|
|
7784
|
+
/** Get the motor speed
|
|
7785
|
+
* @return {number} */
|
|
7786
|
+
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
|
|
7787
|
+
|
|
7788
|
+
/** Set the motor torque
|
|
7789
|
+
* @param {number} torque */
|
|
7790
|
+
setMaxMotorTorque(torque) { return this.box2dJoint.SetMaxMotorTorque(torque); }
|
|
7791
|
+
|
|
7792
|
+
/** Get the max motor torque
|
|
7793
|
+
* @return {number} */
|
|
7794
|
+
getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
|
|
7795
|
+
|
|
7796
|
+
/** Get the motor torque given a time step
|
|
7797
|
+
* @param {number} time
|
|
7798
|
+
* @return {number} */
|
|
7799
|
+
getMotorTorque(time) { return this.box2dJoint.GetMotorTorque(1/time); }
|
|
7800
|
+
}
|
|
7801
|
+
|
|
7802
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7803
|
+
/**
|
|
7804
|
+
* Box2D Gear Joint
|
|
7805
|
+
* - A gear joint is used to connect two joints together
|
|
7806
|
+
* - Either joint can be a revolute or prismatic joint
|
|
7807
|
+
* - You specify a gear ratio to bind the motions together
|
|
7808
|
+
* @extends Box2dJoint
|
|
7809
|
+
*/
|
|
7810
|
+
class Box2dGearJoint extends Box2dJoint
|
|
7811
|
+
{
|
|
7812
|
+
/** Create a gear joint
|
|
7813
|
+
* @param {Box2dObject} objectA
|
|
7814
|
+
* @param {Box2dObject} objectB
|
|
7815
|
+
* @param {Box2dJoint} joint1
|
|
7816
|
+
* @param {Box2dJoint} joint2
|
|
7817
|
+
* @param {ratio} [ratio] */
|
|
7818
|
+
constructor(objectA, objectB, joint1, joint2, ratio=1)
|
|
7819
|
+
{
|
|
7820
|
+
const jointDef = new box2d.instance.b2GearJointDef();
|
|
7821
|
+
jointDef.set_bodyA(objectA.body);
|
|
7822
|
+
jointDef.set_bodyB(objectB.body);
|
|
7823
|
+
jointDef.set_joint1(joint1.box2dJoint);
|
|
7824
|
+
jointDef.set_joint2(joint2.box2dJoint);
|
|
7825
|
+
jointDef.set_ratio(ratio);
|
|
7826
|
+
super(jointDef);
|
|
7827
|
+
|
|
7828
|
+
this.joint1 = joint1;
|
|
7829
|
+
this.joint2 = joint2;
|
|
7830
|
+
}
|
|
7831
|
+
|
|
7832
|
+
/** Get the first joint
|
|
7833
|
+
* @return {Box2dJoint} */
|
|
7834
|
+
getJoint1() { return this.joint1; }
|
|
7835
|
+
|
|
7836
|
+
/** Get the second joint
|
|
7837
|
+
* @return {Box2dJoint} */
|
|
7838
|
+
getJoint2() { return this.joint2; }
|
|
7839
|
+
|
|
7840
|
+
/** Set the gear ratio
|
|
7841
|
+
* @param {number} ratio */
|
|
7842
|
+
setRatio(ratio) { return this.box2dJoint.SetRatio(ratio); }
|
|
7843
|
+
|
|
7844
|
+
/** Get the gear ratio
|
|
7845
|
+
* @return {number} */
|
|
7846
|
+
getRatio() { return this.box2dJoint.GetRatio(); }
|
|
7847
|
+
}
|
|
7848
|
+
|
|
7849
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7850
|
+
/**
|
|
7851
|
+
* Box2D Prismatic Joint
|
|
7852
|
+
* - Provides one degree of freedom: translation along an axis fixed in objectA
|
|
7853
|
+
* - Relative rotation is prevented
|
|
7854
|
+
* - You can use a joint limit to restrict the range of motion
|
|
7855
|
+
* - You can use a joint motor to drive the motion or to model joint friction
|
|
7856
|
+
* @extends Box2dJoint
|
|
7857
|
+
*/
|
|
7858
|
+
class Box2dPrismaticJoint extends Box2dJoint
|
|
7859
|
+
{
|
|
7860
|
+
/** Create a prismatic joint
|
|
7861
|
+
* @param {Box2dObject} objectA
|
|
7862
|
+
* @param {Box2dObject} objectB
|
|
7863
|
+
* @param {Vector2} anchor
|
|
7864
|
+
* @param {Vector2} worldAxis
|
|
7865
|
+
* @param {boolean} [collide] */
|
|
7866
|
+
constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
|
|
7867
|
+
{
|
|
7868
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7869
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
7870
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
7871
|
+
const localAxisA = objectB.worldToLocalVector(worldAxis);
|
|
7872
|
+
const jointDef = new box2d.instance.b2PrismaticJointDef();
|
|
7873
|
+
jointDef.set_bodyA(objectA.body);
|
|
7874
|
+
jointDef.set_bodyB(objectB.body);
|
|
7875
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7876
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7877
|
+
jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
|
|
7878
|
+
jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
|
|
7879
|
+
jointDef.set_collideConnected(collide);
|
|
7880
|
+
super(jointDef);
|
|
7881
|
+
}
|
|
7882
|
+
|
|
7883
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7884
|
+
* @return {Vector2} */
|
|
7885
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7886
|
+
|
|
7887
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7888
|
+
* @return {Vector2} */
|
|
7889
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7890
|
+
|
|
7891
|
+
/** Get the local joint axis relative to bodyA
|
|
7892
|
+
* @return {Vector2} */
|
|
7893
|
+
getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
|
|
7894
|
+
|
|
7895
|
+
/** Get the reference angle
|
|
7896
|
+
* @return {number} */
|
|
7897
|
+
getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
|
|
7898
|
+
|
|
7899
|
+
/** Get the current joint translation
|
|
7900
|
+
* @return {number} */
|
|
7901
|
+
getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
|
|
7902
|
+
|
|
7903
|
+
/** Get the current joint translation speed
|
|
7904
|
+
* @return {number} */
|
|
7905
|
+
getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
|
|
7906
|
+
|
|
7907
|
+
/** Is the joint limit enabled?
|
|
7908
|
+
* @return {boolean} */
|
|
7909
|
+
isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
|
|
7910
|
+
|
|
7911
|
+
/** Enable/disable the joint limit
|
|
7912
|
+
* @param {boolean} [enable] */
|
|
7913
|
+
enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
|
|
7914
|
+
|
|
7915
|
+
/** Get the lower joint limit
|
|
7916
|
+
* @return {number} */
|
|
7917
|
+
getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
|
|
7918
|
+
|
|
7919
|
+
/** Get the upper joint limit
|
|
7920
|
+
* @return {number} */
|
|
7921
|
+
getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
|
|
7922
|
+
|
|
7923
|
+
/** Set the joint limits
|
|
7924
|
+
* @param {number} min
|
|
7925
|
+
* @param {number} max */
|
|
7926
|
+
setLimits(min, max) { return this.box2dJoint.SetLimits(min, max); }
|
|
7927
|
+
|
|
7928
|
+
/** Is the motor enabled?
|
|
7929
|
+
* @return {boolean} */
|
|
7930
|
+
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
|
|
7931
|
+
|
|
7932
|
+
/** Enable/disable the joint motor
|
|
7933
|
+
* @param {boolean} [enable] */
|
|
7934
|
+
enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
|
|
7935
|
+
|
|
7936
|
+
/** Set the motor speed
|
|
7937
|
+
* @param {number} speed */
|
|
7938
|
+
setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
|
|
7939
|
+
|
|
7940
|
+
/** Get the motor speed
|
|
7941
|
+
* @return {number} */
|
|
7942
|
+
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
|
|
7943
|
+
|
|
7944
|
+
/** Set the maximum motor force
|
|
7945
|
+
* @param {number} force */
|
|
7946
|
+
setMaxMotorForce(force) { return this.box2dJoint.SetMaxMotorForce(force); }
|
|
7947
|
+
|
|
7948
|
+
/** Get the maximum motor force
|
|
7949
|
+
* @return {number} */
|
|
7950
|
+
getMaxMotorForce() { return this.box2dJoint.GetMaxMotorForce(); }
|
|
7951
|
+
|
|
7952
|
+
/** Get the motor force given a time step
|
|
7953
|
+
* @param {number} time
|
|
7954
|
+
* @return {number} */
|
|
7955
|
+
getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
|
|
7956
|
+
}
|
|
7957
|
+
|
|
7958
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7959
|
+
/**
|
|
7960
|
+
* Box2D Wheel Joint
|
|
7961
|
+
* - Provides two degrees of freedom: translation along an axis fixed in objectA and rotation
|
|
7962
|
+
* - You can use a joint limit to restrict the range of motion
|
|
7963
|
+
* - You can use a joint motor to drive the motion or to model joint friction
|
|
7964
|
+
* - This joint is designed for vehicle suspensions
|
|
7965
|
+
* @extends Box2dJoint
|
|
7966
|
+
*/
|
|
7967
|
+
class Box2dWheelJoint extends Box2dJoint
|
|
7968
|
+
{
|
|
7969
|
+
/** Create a wheel joint
|
|
7970
|
+
* @param {Box2dObject} objectA
|
|
7971
|
+
* @param {Box2dObject} objectB
|
|
7972
|
+
* @param {Vector2} anchor
|
|
7973
|
+
* @param {Vector2} worldAxis
|
|
7974
|
+
* @param {boolean} [collide] */
|
|
7975
|
+
constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
|
|
7976
|
+
{
|
|
7977
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
7978
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
7979
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
7980
|
+
const localAxisA = objectB.worldToLocalVector(worldAxis);
|
|
7981
|
+
const jointDef = new box2d.instance.b2WheelJointDef();
|
|
7982
|
+
jointDef.set_bodyA(objectA.body);
|
|
7983
|
+
jointDef.set_bodyB(objectB.body);
|
|
7984
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
7985
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
7986
|
+
jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
|
|
7987
|
+
jointDef.set_collideConnected(collide);
|
|
7988
|
+
super(jointDef);
|
|
7989
|
+
}
|
|
7990
|
+
|
|
7991
|
+
/** Get the local anchor point relative to objectA's origin
|
|
7992
|
+
* @return {Vector2} */
|
|
7993
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
7994
|
+
|
|
7995
|
+
/** Get the local anchor point relative to objectB's origin
|
|
7996
|
+
* @return {Vector2} */
|
|
7997
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
7998
|
+
|
|
7999
|
+
/** Get the local joint axis relative to bodyA
|
|
8000
|
+
* @return {Vector2} */
|
|
8001
|
+
getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
|
|
8002
|
+
|
|
8003
|
+
/** Get the current joint translation
|
|
8004
|
+
* @return {number} */
|
|
8005
|
+
getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
|
|
8006
|
+
|
|
8007
|
+
/** Get the current joint translation speed
|
|
8008
|
+
* @return {number} */
|
|
8009
|
+
getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
|
|
8010
|
+
|
|
8011
|
+
/** Is the joint motor enabled?
|
|
8012
|
+
* @return {boolean} */
|
|
8013
|
+
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
|
|
8014
|
+
|
|
8015
|
+
/** Enable/disable the joint motor
|
|
8016
|
+
* @param {boolean} [enable] */
|
|
8017
|
+
enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
|
|
8018
|
+
|
|
8019
|
+
/** Set the motor speed
|
|
8020
|
+
* @param {number} speed */
|
|
8021
|
+
setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
|
|
8022
|
+
|
|
8023
|
+
/** Get the motor speed
|
|
8024
|
+
* @return {number} */
|
|
8025
|
+
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
|
|
8026
|
+
|
|
8027
|
+
/** Set the maximum motor torque
|
|
8028
|
+
* @param {number} torque */
|
|
8029
|
+
setMaxMotorTorque(torque) { return this.box2dJoint.SetMaxMotorTorque(torque); }
|
|
8030
|
+
|
|
8031
|
+
/** Get the max motor torque
|
|
8032
|
+
* @return {number} */
|
|
8033
|
+
getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
|
|
8034
|
+
|
|
8035
|
+
/** Get the motor torque for a time step
|
|
8036
|
+
* @return {number} */
|
|
8037
|
+
getMotorTorque(time) { return this.box2dJoint.GetMotorTorque(1/time); }
|
|
8038
|
+
|
|
8039
|
+
/** Set the spring frequency in Hertz
|
|
8040
|
+
* @param {number} hz */
|
|
8041
|
+
setSpringFrequencyHz(hz) { return this.box2dJoint.SetSpringFrequencyHz(hz); }
|
|
8042
|
+
|
|
8043
|
+
/** Get the spring frequency in Hertz
|
|
8044
|
+
* @return {number} */
|
|
8045
|
+
getSpringFrequencyHz() { return this.box2dJoint.GetSpringFrequencyHz(); }
|
|
8046
|
+
|
|
8047
|
+
/** Set the spring damping ratio
|
|
8048
|
+
* @param {number} ratio */
|
|
8049
|
+
setSpringDampingRatio(ratio) { return this.box2dJoint.SetSpringDampingRatio(ratio); }
|
|
8050
|
+
|
|
8051
|
+
/** Get the spring damping ratio
|
|
8052
|
+
* @return {number} */
|
|
8053
|
+
getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
|
|
8054
|
+
}
|
|
8055
|
+
|
|
8056
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8057
|
+
/**
|
|
8058
|
+
* Box2D Weld Joint
|
|
8059
|
+
* - Glues two objects together
|
|
8060
|
+
* @extends Box2dJoint
|
|
8061
|
+
*/
|
|
8062
|
+
class Box2dWeldJoint extends Box2dJoint
|
|
8063
|
+
{
|
|
8064
|
+
/** Create a weld joint
|
|
8065
|
+
* @param {Box2dObject} objectA
|
|
8066
|
+
* @param {Box2dObject} objectB
|
|
8067
|
+
* @param {Vector2} anchor
|
|
8068
|
+
* @param {boolean} [collide] */
|
|
8069
|
+
constructor(objectA, objectB, anchor, collide=false)
|
|
8070
|
+
{
|
|
8071
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
8072
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
8073
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
8074
|
+
const jointDef = new box2d.instance.b2WeldJointDef();
|
|
8075
|
+
jointDef.set_bodyA(objectA.body);
|
|
8076
|
+
jointDef.set_bodyB(objectB.body);
|
|
8077
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
8078
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
8079
|
+
jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
|
|
8080
|
+
jointDef.set_collideConnected(collide);
|
|
8081
|
+
super(jointDef);
|
|
8082
|
+
}
|
|
8083
|
+
|
|
8084
|
+
/** Get the local anchor point relative to objectA's origin
|
|
8085
|
+
* @return {Vector2} */
|
|
8086
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
8087
|
+
|
|
8088
|
+
/** Get the local anchor point relative to objectB's origin
|
|
8089
|
+
* @return {Vector2} */
|
|
8090
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
8091
|
+
|
|
8092
|
+
/** Get the reference angle
|
|
8093
|
+
* @return {number} */
|
|
8094
|
+
getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
|
|
8095
|
+
|
|
8096
|
+
/** Set the frequency in Hertz
|
|
8097
|
+
* @param {number} hz */
|
|
8098
|
+
setFrequency(hz) { return this.box2dJoint.SetFrequency(hz); }
|
|
8099
|
+
|
|
8100
|
+
/** Get the frequency in Hertz
|
|
8101
|
+
* @return {number} */
|
|
8102
|
+
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
8103
|
+
|
|
8104
|
+
/** Set the damping ratio
|
|
8105
|
+
* @param {number} ratio */
|
|
8106
|
+
setSpringDampingRatio(ratio) { return this.box2dJoint.SetSpringDampingRatio(ratio); }
|
|
8107
|
+
|
|
8108
|
+
/** Get the damping ratio
|
|
8109
|
+
* @return {number} */
|
|
8110
|
+
getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
|
|
8111
|
+
}
|
|
8112
|
+
|
|
8113
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8114
|
+
/**
|
|
8115
|
+
* Box2D Friction Joint
|
|
8116
|
+
* - Used to apply top-down friction
|
|
8117
|
+
* - Provides 2D translational friction and angular friction
|
|
8118
|
+
* @extends Box2dJoint
|
|
8119
|
+
*/
|
|
8120
|
+
class Box2dFrictionJoint extends Box2dJoint
|
|
8121
|
+
{
|
|
8122
|
+
/** Create a friction joint
|
|
8123
|
+
* @param {Box2dObject} objectA
|
|
8124
|
+
* @param {Box2dObject} objectB
|
|
8125
|
+
* @param {Vector2} anchor
|
|
8126
|
+
* @param {boolean} [collide] */
|
|
8127
|
+
constructor(objectA, objectB, anchor, collide=false)
|
|
8128
|
+
{
|
|
8129
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
8130
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
8131
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
8132
|
+
const jointDef = new box2d.instance.b2FrictionJointDef();
|
|
8133
|
+
jointDef.set_bodyA(objectA.body);
|
|
8134
|
+
jointDef.set_bodyB(objectB.body);
|
|
8135
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
8136
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
8137
|
+
jointDef.set_collideConnected(collide);
|
|
8138
|
+
super(jointDef);
|
|
8139
|
+
}
|
|
8140
|
+
|
|
8141
|
+
/** Get the local anchor point relative to objectA's origin
|
|
8142
|
+
* @return {Vector2} */
|
|
8143
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
8144
|
+
|
|
8145
|
+
/** Get the local anchor point relative to objectB's origin
|
|
8146
|
+
* @return {Vector2} */
|
|
8147
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
8148
|
+
|
|
8149
|
+
/** Set the maximum friction force
|
|
8150
|
+
* @param {number} force */
|
|
8151
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
|
|
8152
|
+
|
|
8153
|
+
/** Get the maximum friction force
|
|
8154
|
+
* @return {number} */
|
|
8155
|
+
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
|
|
8156
|
+
|
|
8157
|
+
/** Set the maximum friction torque
|
|
8158
|
+
* @param {number} torque */
|
|
8159
|
+
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(torque); }
|
|
8160
|
+
|
|
8161
|
+
/** Get the maximum friction torque
|
|
8162
|
+
* @return {number} */
|
|
8163
|
+
getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
|
|
8164
|
+
}
|
|
8165
|
+
|
|
8166
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8167
|
+
/**
|
|
8168
|
+
* Box2D Pulley Joint
|
|
8169
|
+
* - Connects to two objects and two fixed ground points
|
|
8170
|
+
* - The pulley supports a ratio such that: length1 + ratio * length2 <= constant
|
|
8171
|
+
* - The force transmitted is scaled by the ratio
|
|
8172
|
+
* @extends Box2dJoint
|
|
8173
|
+
*/
|
|
8174
|
+
class Box2dPulleyJoint extends Box2dJoint
|
|
8175
|
+
{
|
|
8176
|
+
/** Create a pulley joint
|
|
8177
|
+
* @param {Box2dObject} objectA
|
|
8178
|
+
* @param {Box2dObject} objectB
|
|
8179
|
+
* @param {Vector2} groundAnchorA
|
|
8180
|
+
* @param {Vector2} groundAnchorB
|
|
8181
|
+
* @param {Vector2} anchorA
|
|
8182
|
+
* @param {Vector2} anchorB
|
|
8183
|
+
* @param {number} [ratio]
|
|
8184
|
+
* @param {boolean} [collide] */
|
|
8185
|
+
constructor(objectA, objectB, groundAnchorA, groundAnchorB, anchorA, anchorB, ratio=1, collide=false)
|
|
8186
|
+
{
|
|
8187
|
+
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
|
|
8188
|
+
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
|
|
8189
|
+
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
8190
|
+
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
8191
|
+
const jointDef = new box2d.instance.b2PulleyJointDef();
|
|
8192
|
+
jointDef.set_bodyA(objectA.body);
|
|
8193
|
+
jointDef.set_bodyB(objectB.body);
|
|
8194
|
+
jointDef.set_groundAnchorA(box2d.vec2dTo(groundAnchorA));
|
|
8195
|
+
jointDef.set_groundAnchorB(box2d.vec2dTo(groundAnchorB));
|
|
8196
|
+
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
8197
|
+
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
8198
|
+
jointDef.set_ratio(ratio);
|
|
8199
|
+
jointDef.set_lengthA(groundAnchorA.distance(anchorA));
|
|
8200
|
+
jointDef.set_lengthB(groundAnchorB.distance(anchorB));
|
|
8201
|
+
jointDef.set_collideConnected(collide);
|
|
8202
|
+
super(jointDef);
|
|
8203
|
+
}
|
|
8204
|
+
|
|
8205
|
+
/** Get the first ground anchor
|
|
8206
|
+
* @return {Vector2} */
|
|
8207
|
+
getGroundAnchorA() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorA()); }
|
|
8208
|
+
|
|
8209
|
+
/** Get the second ground anchor
|
|
8210
|
+
* @return {Vector2} */
|
|
8211
|
+
getGroundAnchorB() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorB()); }
|
|
8212
|
+
|
|
8213
|
+
/** Get the current length of the segment attached to objectA
|
|
8214
|
+
* @return {number} */
|
|
8215
|
+
getLengthA() { return this.box2dJoint.GetLengthA(); }
|
|
8216
|
+
|
|
8217
|
+
/** Get the current length of the segment attached to objectB
|
|
8218
|
+
* @return {number} */
|
|
8219
|
+
getLengthB(){ return this.box2dJoint.GetLengthB(); }
|
|
8220
|
+
|
|
8221
|
+
/** Get the pulley ratio
|
|
8222
|
+
* @return {number} */
|
|
8223
|
+
getRatio() { return this.box2dJoint.GetRatio(); }
|
|
8224
|
+
|
|
8225
|
+
/** Get the current length of the segment attached to objectA
|
|
8226
|
+
* @return {number} */
|
|
8227
|
+
getCurrentLengthA() { return this.box2dJoint.GetCurrentLengthA(); }
|
|
8228
|
+
|
|
8229
|
+
/** Get the current length of the segment attached to objectB
|
|
8230
|
+
* @return {number} */
|
|
8231
|
+
getCurrentLengthB() { return this.box2dJoint.GetCurrentLengthB(); }
|
|
8232
|
+
}
|
|
8233
|
+
|
|
8234
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8235
|
+
/**
|
|
8236
|
+
* Box2D Motor Joint
|
|
8237
|
+
* - Controls the relative motion between two objects
|
|
8238
|
+
* - Typical usage is to control the movement of a object with respect to the ground
|
|
8239
|
+
* @extends Box2dJoint
|
|
8240
|
+
*/
|
|
8241
|
+
class Box2dMotorJoint extends Box2dJoint
|
|
8242
|
+
{
|
|
8243
|
+
/** Create a motor joint
|
|
8244
|
+
* @param {Box2dObject} objectA
|
|
8245
|
+
* @param {Box2dObject} objectB */
|
|
8246
|
+
constructor(objectA, objectB)
|
|
8247
|
+
{
|
|
8248
|
+
const linearOffset = objectA.worldToLocal(box2d.vec2From(objectB.body.GetPosition()));
|
|
8249
|
+
const angularOffset = objectB.body.GetAngle() - objectA.body.GetAngle();
|
|
8250
|
+
const jointDef = new box2d.instance.b2MotorJointDef();
|
|
8251
|
+
jointDef.set_bodyA(objectA.body);
|
|
8252
|
+
jointDef.set_bodyB(objectB.body);
|
|
8253
|
+
jointDef.set_linearOffset(box2d.vec2dTo(linearOffset));
|
|
8254
|
+
jointDef.set_angularOffset(angularOffset);
|
|
8255
|
+
super(jointDef);
|
|
8256
|
+
}
|
|
8257
|
+
|
|
8258
|
+
/** Set the target linear offset, in frame A, in meters.
|
|
8259
|
+
* @param {Vector2} offset */
|
|
8260
|
+
setLinearOffset(offset) { this.box2dJoint.SetLinearOffset(box2d.vec2dTo(offset)); }
|
|
8261
|
+
|
|
8262
|
+
/** Get the target linear offset, in frame A, in meters.
|
|
8263
|
+
* @return {Vector2} */
|
|
8264
|
+
getLinearOffset() { return box2d.vec2From(this.box2dJoint.GetLinearOffset()); }
|
|
8265
|
+
|
|
8266
|
+
/** Set the target angular offset
|
|
8267
|
+
* @param {number} offset */
|
|
8268
|
+
setAngularOffset(offset) { this.box2dJoint.SetAngularOffset(offset); }
|
|
8269
|
+
|
|
8270
|
+
/** Get the target angular offset
|
|
8271
|
+
* @return {number} */
|
|
8272
|
+
getAngularOffset() { return this.box2dJoint.GetAngularOffset(); }
|
|
8273
|
+
|
|
8274
|
+
/** Set the maximum friction force
|
|
8275
|
+
* @param {number} force */
|
|
8276
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
|
|
8277
|
+
|
|
8278
|
+
/** Get the maximum friction force
|
|
8279
|
+
* @return {number} */
|
|
8280
|
+
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
|
|
8281
|
+
|
|
8282
|
+
/** Set the maximum torque
|
|
8283
|
+
* @param {number} torque */
|
|
8284
|
+
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(torque); }
|
|
8285
|
+
|
|
8286
|
+
/** Get the maximum torque
|
|
8287
|
+
* @return {number} */
|
|
8288
|
+
getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
|
|
8289
|
+
|
|
8290
|
+
/** Set the position correction factor in the range [0,1]
|
|
8291
|
+
* @param {number} factor */
|
|
8292
|
+
setCorrectionFactor(factor) { this.box2dJoint.SetCorrectionFactor(factor); }
|
|
8293
|
+
|
|
8294
|
+
/** Get the position correction factor in the range [0,1]
|
|
8295
|
+
* @return {number} */
|
|
8296
|
+
getCorrectionFactor() { return this.box2dJoint.GetCorrectionFactor(); }
|
|
8297
|
+
}
|
|
8298
|
+
|
|
8299
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8300
|
+
/**
|
|
8301
|
+
* Box2D Global Object
|
|
8302
|
+
* - Wraps Box2d world and provides global functions
|
|
8303
|
+
*/
|
|
8304
|
+
class Box2dPlugin
|
|
8305
|
+
{
|
|
8306
|
+
/** Create the global UI system object
|
|
8307
|
+
* @param {Object} instance */
|
|
8308
|
+
constructor(instance)
|
|
8309
|
+
{
|
|
8310
|
+
ASSERT(!box2d, 'Box2D already initialized');
|
|
8311
|
+
box2d = this;
|
|
8312
|
+
this.instance = instance;
|
|
8313
|
+
this.world = new box2d.instance.b2World();
|
|
8314
|
+
|
|
8315
|
+
/** @property {number} - Velocity iterations per update*/
|
|
8316
|
+
this.velocityIterations = 8;
|
|
8317
|
+
/** @property {number} - Position iterations per update*/
|
|
8318
|
+
this.positionIterations = 3;
|
|
8319
|
+
/** @property {number} - Static, zero mass, zero velocity, may be manually moved */
|
|
8320
|
+
this.bodyTypeStatic = instance.b2_staticBody;
|
|
8321
|
+
/** @property {number} - Kinematic, zero mass, non-zero velocity set by user, moved by solver */
|
|
8322
|
+
this.bodyTypeKinematic = instance.b2_kinematicBody;
|
|
8323
|
+
/** @property {number} - Dynamic, positive mass, non-zero velocity determined by forces, moved by solver */
|
|
8324
|
+
this.bodyTypeDynamic = instance.b2_dynamicBody;
|
|
8325
|
+
|
|
8326
|
+
// setup contact listener
|
|
8327
|
+
const listener = new box2d.instance.JSContactListener();
|
|
8328
|
+
listener.BeginContact = function(contactPtr)
|
|
8329
|
+
{
|
|
8330
|
+
const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
|
|
8331
|
+
const fixtureA = contact.GetFixtureA();
|
|
8332
|
+
const fixtureB = contact.GetFixtureB();
|
|
8333
|
+
const objectA = fixtureA.GetBody().object;
|
|
8334
|
+
const objectB = fixtureB.GetBody().object;
|
|
8335
|
+
objectA.beginContact(objectB);
|
|
8336
|
+
objectB.beginContact(objectA);
|
|
8337
|
+
}
|
|
8338
|
+
listener.EndContact = function(contactPtr)
|
|
8339
|
+
{
|
|
8340
|
+
const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
|
|
8341
|
+
const fixtureA = contact.GetFixtureA();
|
|
8342
|
+
const fixtureB = contact.GetFixtureB();
|
|
8343
|
+
const objectA = fixtureA.GetBody().object;
|
|
8344
|
+
const objectB = fixtureB.GetBody().object;
|
|
8345
|
+
objectA.endContact(objectB);
|
|
8346
|
+
objectB.endContact(objectA);
|
|
8347
|
+
};
|
|
8348
|
+
listener.PreSolve = function() {};
|
|
8349
|
+
listener.PostSolve = function() {};
|
|
8350
|
+
box2d.world.SetContactListener(listener);
|
|
8351
|
+
}
|
|
8352
|
+
|
|
8353
|
+
/** Step the physics world simulation
|
|
8354
|
+
* @param {number} [frames] */
|
|
8355
|
+
step(frames=1)
|
|
8356
|
+
{
|
|
8357
|
+
box2d.world.SetGravity(box2d.vec2dTo(gravity));
|
|
8358
|
+
for (let i=frames; i--;)
|
|
8359
|
+
box2d.world.Step(timeDelta, this.velocityIterations, this.positionIterations);
|
|
8360
|
+
}
|
|
8361
|
+
|
|
8362
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8363
|
+
// raycasting and querying
|
|
8364
|
+
|
|
8365
|
+
/** raycast and return a list of all the results
|
|
8366
|
+
* @param {Vector2} start
|
|
8367
|
+
* @param {Vector2} end */
|
|
8368
|
+
raycastAll(start, end)
|
|
8369
|
+
{
|
|
8370
|
+
const raycastCallback = new box2d.instance.JSRayCastCallback();
|
|
8371
|
+
raycastCallback.ReportFixture = function(fixturePointer, point, normal, fraction)
|
|
8372
|
+
{
|
|
8373
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
8374
|
+
point = box2d.vec2FromPointer(point);
|
|
8375
|
+
normal = box2d.vec2FromPointer(normal);
|
|
8376
|
+
raycastResults.push(new Box2dRaycastResult(fixture, point, normal, fraction));
|
|
8377
|
+
return 1; // continue getting results
|
|
8378
|
+
};
|
|
8379
|
+
|
|
8380
|
+
const raycastResults = [];
|
|
8381
|
+
box2d.world.RayCast(raycastCallback, box2d.vec2dTo(start), box2d.vec2dTo(end));
|
|
8382
|
+
debugRaycast && debugLine(start, end, raycastResults.length ? '#f00' : '#00f', .02);
|
|
8383
|
+
return raycastResults;
|
|
8384
|
+
}
|
|
8385
|
+
|
|
8386
|
+
/** raycast and return the first result
|
|
8387
|
+
* @param {Vector2} start
|
|
8388
|
+
* @param {Vector2} end */
|
|
8389
|
+
raycast(start, end)
|
|
8390
|
+
{
|
|
8391
|
+
const raycastResults = box2d.raycastAll(start, end);
|
|
8392
|
+
if (!raycastResults.length)
|
|
8393
|
+
return undefined;
|
|
8394
|
+
return raycastResults.reduce((a,b)=>a.fraction < b.fraction ? a : b);
|
|
8395
|
+
}
|
|
8396
|
+
|
|
8397
|
+
/** box aabb cast and return all the objects
|
|
8398
|
+
* @param {Vector2} pos
|
|
8399
|
+
* @param {Vector2} size */
|
|
8400
|
+
boxCastAll(pos, size)
|
|
8401
|
+
{
|
|
8402
|
+
const queryCallback = new box2d.instance.JSQueryCallback();
|
|
8403
|
+
queryCallback.ReportFixture = function(fixturePointer)
|
|
8404
|
+
{
|
|
8405
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
8406
|
+
const o = fixture.GetBody().object;
|
|
8407
|
+
if (!queryObjects.includes(o))
|
|
8408
|
+
queryObjects.push(o); // add if not already in list
|
|
8409
|
+
return true; // continue getting results
|
|
8410
|
+
};
|
|
8411
|
+
|
|
8412
|
+
const aabb = new box2d.instance.b2AABB();
|
|
8413
|
+
aabb.set_lowerBound(box2d.vec2dTo(pos.subtract(size.scale(.5))));
|
|
8414
|
+
aabb.set_upperBound(box2d.vec2dTo(pos.add(size.scale(.5))));
|
|
8415
|
+
|
|
8416
|
+
let queryObjects = [];
|
|
8417
|
+
box2d.world.QueryAABB(queryCallback, aabb);
|
|
8418
|
+
debugRaycast && debugRect(pos, size, queryObjects.length ? '#f00' : '#00f', .02);
|
|
8419
|
+
return queryObjects;
|
|
8420
|
+
}
|
|
8421
|
+
|
|
8422
|
+
/** box aabb cast and return the first object
|
|
8423
|
+
* @param {Vector2} pos
|
|
8424
|
+
* @param {Vector2} size */
|
|
8425
|
+
boxCast(pos, size)
|
|
8426
|
+
{
|
|
8427
|
+
const queryCallback = new box2d.instance.JSQueryCallback();
|
|
8428
|
+
queryCallback.ReportFixture = function(fixturePointer)
|
|
8429
|
+
{
|
|
8430
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
8431
|
+
queryObject = fixture.GetBody().object;
|
|
8432
|
+
return false; // stop getting results
|
|
8433
|
+
};
|
|
8434
|
+
|
|
8435
|
+
const aabb = new box2d.instance.b2AABB();
|
|
8436
|
+
aabb.set_lowerBound(box2d.vec2dTo(pos.subtract(size.scale(.5))));
|
|
8437
|
+
aabb.set_upperBound(box2d.vec2dTo(pos.add(size.scale(.5))));
|
|
8438
|
+
|
|
8439
|
+
let queryObject;
|
|
8440
|
+
box2d.world.QueryAABB(queryCallback, aabb);
|
|
8441
|
+
debugRaycast && debugRect(pos, size, queryObject ? '#f00' : '#00f', .02);
|
|
8442
|
+
return queryObject;
|
|
8443
|
+
}
|
|
8444
|
+
|
|
8445
|
+
/** circle cast and return all the objects
|
|
8446
|
+
* @param {Vector2} pos
|
|
8447
|
+
* @param {number} diameter */
|
|
8448
|
+
circleCastAll(pos, diameter)
|
|
8449
|
+
{
|
|
8450
|
+
const radius2 = (diameter/2)**2;
|
|
8451
|
+
const results = box2d.boxCastAll(pos, vec2(diameter));
|
|
8452
|
+
return results.filter(o=>o.pos.distanceSquared(pos) < radius2);
|
|
8453
|
+
}
|
|
8454
|
+
|
|
8455
|
+
/** circle cast and return the first object
|
|
8456
|
+
* @param {Vector2} pos
|
|
8457
|
+
* @param {number} diameter */
|
|
8458
|
+
circleCast(pos, diameter)
|
|
8459
|
+
{
|
|
8460
|
+
const radius2 = (diameter/2)**2;
|
|
8461
|
+
let results = box2d.boxCastAll(pos, vec2(diameter));
|
|
8462
|
+
|
|
8463
|
+
let bestResult, bestDistance2;
|
|
8464
|
+
for (const result of results)
|
|
8465
|
+
{
|
|
8466
|
+
const distance2 = result.pos.distanceSquared(pos);
|
|
8467
|
+
if (distance2 < radius2 && (!bestResult || distance2 < bestDistance2))
|
|
8468
|
+
{
|
|
8469
|
+
bestResult = result;
|
|
8470
|
+
bestDistance2 = distance2;
|
|
8471
|
+
}
|
|
8472
|
+
}
|
|
8473
|
+
return bestResult;
|
|
8474
|
+
}
|
|
8475
|
+
|
|
8476
|
+
/** point cast and return the first object
|
|
8477
|
+
* @param {Vector2} pos
|
|
8478
|
+
* @param {boolean} dynamicOnly */
|
|
8479
|
+
pointCast(pos, dynamicOnly=true)
|
|
8480
|
+
{
|
|
8481
|
+
const queryCallback = new box2d.instance.JSQueryCallback();
|
|
8482
|
+
queryCallback.ReportFixture = function(fixturePointer)
|
|
8483
|
+
{
|
|
8484
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
8485
|
+
if (dynamicOnly && fixture.GetBody().GetType() != box2d.instance.b2_dynamicBody)
|
|
8486
|
+
return true; // continue getting results
|
|
8487
|
+
if (!fixture.TestPoint(box2d.vec2dTo(pos)))
|
|
8488
|
+
return true; // continue getting results
|
|
8489
|
+
queryObject = fixture.GetBody().object;
|
|
8490
|
+
return false; // stop getting results
|
|
8491
|
+
};
|
|
8492
|
+
|
|
8493
|
+
const aabb = new box2d.instance.b2AABB();
|
|
8494
|
+
aabb.set_lowerBound(box2d.vec2dTo(pos));
|
|
8495
|
+
aabb.set_upperBound(box2d.vec2dTo(pos));
|
|
8496
|
+
|
|
8497
|
+
let queryObject;
|
|
8498
|
+
box2d.world.QueryAABB(queryCallback, aabb);
|
|
8499
|
+
debugRaycast && debugRect(pos, vec2(), queryObject ? '#f00' : '#00f', .02);
|
|
8500
|
+
return queryObject;
|
|
8501
|
+
}
|
|
8502
|
+
|
|
8503
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8504
|
+
// drawing
|
|
8505
|
+
|
|
8506
|
+
/** draws a fixture
|
|
8507
|
+
* @param {Object} fixture
|
|
8508
|
+
* @param {Vector2} pos
|
|
8509
|
+
* @param {number} angle
|
|
8510
|
+
* @param {Color} [color]
|
|
8511
|
+
* @param {Color} [outlineColor]
|
|
8512
|
+
* @param {number} [lineWidth]
|
|
8513
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
8514
|
+
drawFixture(fixture, pos, angle, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
|
|
8515
|
+
{
|
|
8516
|
+
const shape = box2d.castObjectType(fixture.GetShape());
|
|
8517
|
+
switch (shape.GetType())
|
|
8518
|
+
{
|
|
8519
|
+
case box2d.instance.b2Shape.e_polygon:
|
|
8520
|
+
{
|
|
8521
|
+
let points = [];
|
|
8522
|
+
for (let i=shape.GetVertexCount(); i--;)
|
|
8523
|
+
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
8524
|
+
box2d.drawPoly(pos, angle, points, color, outlineColor, lineWidth, context);
|
|
8525
|
+
break;
|
|
8526
|
+
}
|
|
8527
|
+
case box2d.instance.b2Shape.e_circle:
|
|
8528
|
+
{
|
|
8529
|
+
const radius = shape.get_m_radius();
|
|
8530
|
+
box2d.drawCircle(pos, radius, color, outlineColor, lineWidth, context);
|
|
8531
|
+
break;
|
|
8532
|
+
}
|
|
8533
|
+
case box2d.instance.b2Shape.e_edge:
|
|
8534
|
+
{
|
|
8535
|
+
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
8536
|
+
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
8537
|
+
box2d.drawLine(pos, angle, v1, v2, color, lineWidth, context);
|
|
8538
|
+
break;
|
|
8539
|
+
}
|
|
8540
|
+
}
|
|
8541
|
+
}
|
|
8542
|
+
|
|
8543
|
+
/** draws a circle
|
|
8544
|
+
* @param {Vector2} pos
|
|
8545
|
+
* @param {number} radius
|
|
8546
|
+
* @param {Color} [color]
|
|
8547
|
+
* @param {Color} [outlineColor]
|
|
8548
|
+
* @param {number} [lineWidth]
|
|
8549
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
8550
|
+
drawCircle(pos, radius, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
|
|
8551
|
+
{
|
|
8552
|
+
drawCanvas2D(pos, vec2(1), 0, 0, context=>
|
|
8553
|
+
{
|
|
8554
|
+
context.beginPath();
|
|
8555
|
+
context.arc(0, 0, radius, 0, 9);
|
|
8556
|
+
box2d.drawFillStroke(color, outlineColor, lineWidth, context);
|
|
8557
|
+
}, 0, context);
|
|
8558
|
+
}
|
|
8559
|
+
|
|
8560
|
+
/** draws a polygon
|
|
8561
|
+
* @param {Vector2} pos
|
|
8562
|
+
* @param {number} angle
|
|
8563
|
+
* @param {Array<Vector2>} points
|
|
8564
|
+
* @param {Color} [color]
|
|
8565
|
+
* @param {Color} [outlineColor]
|
|
8566
|
+
* @param {number} [lineWidth]
|
|
8567
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
8568
|
+
drawPoly(pos, angle, points, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
|
|
8569
|
+
{
|
|
8570
|
+
drawCanvas2D(pos, vec2(1), angle, 0, context=>
|
|
8571
|
+
{
|
|
8572
|
+
context.beginPath();
|
|
8573
|
+
points.forEach(p=>context.lineTo(p.x, p.y));
|
|
8574
|
+
context.closePath();
|
|
8575
|
+
box2d.drawFillStroke(color, outlineColor, lineWidth, context);
|
|
8576
|
+
}, 0, context);
|
|
8577
|
+
}
|
|
8578
|
+
|
|
8579
|
+
/** draws a line
|
|
8580
|
+
* @param {Vector2} pos
|
|
8581
|
+
* @param {number} angle
|
|
8582
|
+
* @param {Vector2} posA
|
|
8583
|
+
* @param {Vector2} posB
|
|
8584
|
+
* @param {Color} [color]
|
|
8585
|
+
* @param {number} [lineWidth]
|
|
8586
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
8587
|
+
drawLine(pos, angle, posA, posB, color=WHITE, lineWidth=.1, context=mainContext)
|
|
8588
|
+
{
|
|
8589
|
+
drawCanvas2D(pos, vec2(1), angle, 0, context=>
|
|
8590
|
+
{
|
|
8591
|
+
context.beginPath();
|
|
8592
|
+
context.lineTo(posA.x, posA.y);
|
|
8593
|
+
context.lineTo(posB.x, posB.y);
|
|
8594
|
+
box2d.drawFillStroke(0, color, lineWidth, context);
|
|
8595
|
+
}, 0, context);
|
|
8596
|
+
}
|
|
8597
|
+
|
|
8598
|
+
/** performs a fill or stroke as a helper to the other draw functions
|
|
8599
|
+
* @param {Color} [color]
|
|
8600
|
+
* @param {Color} [outlineColor]
|
|
8601
|
+
* @param {number} [lineWidth]
|
|
8602
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
8603
|
+
drawFillStroke(color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
|
|
8604
|
+
{
|
|
8605
|
+
if (color)
|
|
8606
|
+
{
|
|
8607
|
+
context.fillStyle = color.toString();
|
|
8608
|
+
context.fill();
|
|
8609
|
+
}
|
|
8610
|
+
if (outlineColor && lineWidth)
|
|
8611
|
+
{
|
|
8612
|
+
context.lineWidth = lineWidth;
|
|
8613
|
+
context.lineJoin = context.lineCap = 'round';
|
|
8614
|
+
context.strokeStyle = outlineColor.toString();
|
|
8615
|
+
context.stroke();
|
|
8616
|
+
}
|
|
8617
|
+
}
|
|
8618
|
+
|
|
8619
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8620
|
+
// helper functions
|
|
8621
|
+
|
|
8622
|
+
/** converts a box2d vec2 to a Vector2
|
|
8623
|
+
* @param {Object} v */
|
|
8624
|
+
vec2From(v)
|
|
8625
|
+
{
|
|
8626
|
+
ASSERT(v instanceof box2d.instance.b2Vec2);
|
|
8627
|
+
return new Vector2(v.get_x(), v.get_y());
|
|
8628
|
+
}
|
|
8629
|
+
|
|
8630
|
+
/** converts a box2d vec2 pointer to a Vector2
|
|
8631
|
+
* @param {Object} v */
|
|
8632
|
+
vec2FromPointer(v)
|
|
8633
|
+
{
|
|
8634
|
+
return box2d.vec2From(box2d.instance.wrapPointer(v, box2d.instance.b2Vec2));
|
|
8635
|
+
}
|
|
8636
|
+
|
|
8637
|
+
/** converts a Vector2 to a box2 vec2
|
|
8638
|
+
* @param {Vector2} v */
|
|
8639
|
+
vec2dTo(v)
|
|
8640
|
+
{
|
|
8641
|
+
ASSERT(v instanceof Vector2);
|
|
8642
|
+
return new box2d.instance.b2Vec2(v.x, v.y);
|
|
8643
|
+
}
|
|
8644
|
+
|
|
8645
|
+
/** checks if a box2d object is null
|
|
8646
|
+
* @param {Object} o */
|
|
8647
|
+
isNull(o) { return !box2d.instance.getPointer(o); }
|
|
8648
|
+
|
|
8649
|
+
/** casts a box2d object to its correct type
|
|
8650
|
+
* @param {Object} o */
|
|
8651
|
+
castObjectType(o)
|
|
8652
|
+
{
|
|
8653
|
+
switch (o.GetType())
|
|
8654
|
+
{
|
|
8655
|
+
case box2d.instance.b2Shape.e_circle:
|
|
8656
|
+
return box2d.instance.castObject(o, box2d.instance.b2CircleShape);
|
|
8657
|
+
case box2d.instance.b2Shape.e_edge:
|
|
8658
|
+
return box2d.instance.castObject(o, box2d.instance.b2EdgeShape);
|
|
8659
|
+
case box2d.instance.b2Shape.e_polygon:
|
|
8660
|
+
return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
|
|
8661
|
+
case box2d.instance.b2Shape.e_chain:
|
|
8662
|
+
return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
|
|
8663
|
+
case box2d.instance.e_revoluteJoint:
|
|
8664
|
+
return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
|
|
8665
|
+
case box2d.instance.e_prismaticJoint:
|
|
8666
|
+
return box2d.instance.castObject(o, box2d.instance.b2PrismaticJoint);
|
|
8667
|
+
case box2d.instance.e_distanceJoint:
|
|
8668
|
+
return box2d.instance.castObject(o, box2d.instance.b2DistanceJoint);
|
|
8669
|
+
case box2d.instance.e_pulleyJoint:
|
|
8670
|
+
return box2d.instance.castObject(o, box2d.instance.b2PulleyJoint);
|
|
8671
|
+
case box2d.instance.e_mouseJoint:
|
|
8672
|
+
return box2d.instance.castObject(o, box2d.instance.b2MouseJoint);
|
|
8673
|
+
case box2d.instance.e_gearJoint:
|
|
8674
|
+
return box2d.instance.castObject(o, box2d.instance.b2GearJoint);
|
|
8675
|
+
case box2d.instance.e_wheelJoint:
|
|
8676
|
+
return box2d.instance.castObject(o, box2d.instance.b2WheelJoint);
|
|
8677
|
+
case box2d.instance.e_weldJoint:
|
|
8678
|
+
return box2d.instance.castObject(o, box2d.instance.b2WeldJoint);
|
|
8679
|
+
case box2d.instance.e_frictionJoint:
|
|
8680
|
+
return box2d.instance.castObject(o, box2d.instance.b2FrictionJoint);
|
|
8681
|
+
case box2d.instance.e_ropeJoint:
|
|
8682
|
+
return box2d.instance.castObject(o, box2d.instance.b2RopeJoint);
|
|
8683
|
+
case box2d.instance.e_motorJoint:
|
|
8684
|
+
return box2d.instance.castObject(o, box2d.instance.b2MotorJoint);
|
|
8685
|
+
}
|
|
8686
|
+
|
|
8687
|
+
ASSERT(false, 'Unknown box2d object type');
|
|
8688
|
+
}
|
|
8689
|
+
}
|
|
8690
|
+
|
|
8691
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8692
|
+
/** Box2d Init - Startup LittleJS engine with your callback functions
|
|
8693
|
+
* @param {Function|function():Promise} gameInit - Called once after the engine starts up
|
|
8694
|
+
* @param {Function} gameUpdate - Called every frame before objects are updated
|
|
8695
|
+
* @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused
|
|
8696
|
+
* @param {Function} gameRender - Called before objects are rendered, for drawing the background
|
|
8697
|
+
* @param {Function} gameRenderPost - Called after objects are rendered, useful for drawing UI
|
|
8698
|
+
* @param {Array<string>} [imageSources=[]] - List of images to load
|
|
8699
|
+
* @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default */
|
|
8700
|
+
function box2dEngineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources, rootElement)
|
|
8701
|
+
{
|
|
8702
|
+
Box2D().then(box2dInstance=>
|
|
8703
|
+
{
|
|
8704
|
+
// create box2d object
|
|
8705
|
+
new Box2dPlugin(box2dInstance);
|
|
8706
|
+
setupDebugDraw();
|
|
8707
|
+
|
|
8708
|
+
// start littlejs
|
|
8709
|
+
engineAddPlugin(box2dUpdate, box2dRender);
|
|
8710
|
+
engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources, rootElement);
|
|
8711
|
+
});
|
|
8712
|
+
|
|
8713
|
+
// hook up box2d plugin to update and render
|
|
8714
|
+
function box2dUpdate()
|
|
8715
|
+
{
|
|
8716
|
+
if (!paused)
|
|
8717
|
+
box2d.step();
|
|
8718
|
+
}
|
|
8719
|
+
function box2dRender()
|
|
8720
|
+
{
|
|
8721
|
+
if (box2dDebug || debugPhysics && debugOverlay)
|
|
8722
|
+
box2d.world.DrawDebugData();
|
|
8723
|
+
}
|
|
8724
|
+
|
|
8725
|
+
// box2d debug drawing
|
|
8726
|
+
function setupDebugDraw()
|
|
8727
|
+
{
|
|
8728
|
+
// setup debug draw
|
|
8729
|
+
const debugDraw = new box2d.instance.JSDraw();
|
|
8730
|
+
const box2dColor = (c)=> new Color(c.get_r(), c.get_g(), c.get_b());
|
|
8731
|
+
const box2dColorPointer = (c)=>
|
|
8732
|
+
box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
|
|
8733
|
+
const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
|
|
8734
|
+
const getPointsList = (vertices, vertexCount) =>
|
|
8735
|
+
{
|
|
8736
|
+
const points = [];
|
|
8737
|
+
for (let i=vertexCount; i--;)
|
|
8738
|
+
points.push(box2d.vec2FromPointer(vertices+i*8));
|
|
8739
|
+
return points;
|
|
8740
|
+
}
|
|
8741
|
+
debugDraw.DrawSegment = function(point1, point2, color)
|
|
8742
|
+
{
|
|
8743
|
+
color = getDebugColor(color);
|
|
8744
|
+
point1 = box2d.vec2FromPointer(point1);
|
|
8745
|
+
point2 = box2d.vec2FromPointer(point2);
|
|
8746
|
+
box2d.drawLine(vec2(), 0, point1, point2, color, undefined, overlayContext);
|
|
8747
|
+
};
|
|
8748
|
+
debugDraw.DrawPolygon = function(vertices, vertexCount, color)
|
|
8749
|
+
{
|
|
8750
|
+
color = getDebugColor(color);
|
|
8751
|
+
const points = getPointsList(vertices, vertexCount);
|
|
8752
|
+
box2d.drawPoly(vec2(), 0, points, undefined, color, undefined, overlayContext);
|
|
8753
|
+
};
|
|
8754
|
+
debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
|
|
8755
|
+
{
|
|
8756
|
+
color = getDebugColor(color);
|
|
8757
|
+
const points = getPointsList(vertices, vertexCount);
|
|
8758
|
+
box2d.drawPoly(vec2(), 0, points, color, color, undefined, overlayContext);
|
|
8759
|
+
};
|
|
8760
|
+
debugDraw.DrawCircle = function(center, radius, color)
|
|
8761
|
+
{
|
|
8762
|
+
color = getDebugColor(color);
|
|
8763
|
+
center = box2d.vec2FromPointer(center);
|
|
8764
|
+
box2d.drawCircle(center, radius, undefined, color, undefined, overlayContext);
|
|
8765
|
+
};
|
|
8766
|
+
debugDraw.DrawSolidCircle = function(center, radius, axis, color)
|
|
8767
|
+
{
|
|
8768
|
+
color = getDebugColor(color);
|
|
8769
|
+
center = box2d.vec2FromPointer(center);
|
|
8770
|
+
axis = box2d.vec2FromPointer(axis).scale(radius);
|
|
8771
|
+
box2d.drawCircle(center, radius, color, color, undefined, overlayContext);
|
|
8772
|
+
box2d.drawLine(center, 0, vec2(), axis, color, undefined, overlayContext);
|
|
8773
|
+
};
|
|
8774
|
+
debugDraw.DrawTransform = function(transform)
|
|
8775
|
+
{
|
|
8776
|
+
transform = box2d.instance.wrapPointer(transform, box2d.instance.b2Transform);
|
|
8777
|
+
const pos = vec2(transform.get_p());
|
|
8778
|
+
const angle = -transform.get_q().GetAngle();
|
|
8779
|
+
const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
|
|
8780
|
+
const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
|
|
8781
|
+
box2d.drawLine(pos, angle, vec2(), p1, c1, undefined, overlayContext);
|
|
8782
|
+
box2d.drawLine(pos, angle, vec2(), p2, c2, undefined, overlayContext);
|
|
8783
|
+
}
|
|
8784
|
+
|
|
8785
|
+
debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
|
|
8786
|
+
debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);
|
|
8787
|
+
//debugDraw.AppendFlags(box2d.instance.b2Draw.e_aabbBit);
|
|
8788
|
+
//debugDraw.AppendFlags(box2d.instance.b2Draw.e_pairBit);
|
|
8789
|
+
//debugDraw.AppendFlags(box2d.instance.b2Draw.e_centerOfMassBit);
|
|
8790
|
+
box2d.world.SetDebugDraw(debugDraw);
|
|
8791
|
+
}
|
|
8792
|
+
}
|
|
8793
|
+
|
|
8794
|
+
|
|
8795
|
+
/**
|
|
8796
|
+
* LittleJS Module Export
|
|
8797
|
+
*/
|
|
8798
|
+
|
|
8799
|
+
export
|
|
8800
|
+
{
|
|
8801
|
+
// Engine
|
|
8802
|
+
engineName,
|
|
8803
|
+
engineVersion,
|
|
8804
|
+
frameRate,
|
|
8805
|
+
timeDelta,
|
|
8806
|
+
engineObjects,
|
|
8807
|
+
frame,
|
|
8808
|
+
time,
|
|
8809
|
+
timeReal,
|
|
8810
|
+
paused,
|
|
8811
|
+
setPaused,
|
|
8812
|
+
engineInit,
|
|
8813
|
+
engineObjectsUpdate,
|
|
8814
|
+
engineObjectsDestroy,
|
|
8815
|
+
engineObjectsCollect,
|
|
8816
|
+
engineObjectsCallback,
|
|
8817
|
+
engineObjectsRaycast,
|
|
8818
|
+
engineAddPlugin,
|
|
8819
|
+
|
|
8820
|
+
// Globals
|
|
8821
|
+
debug,
|
|
8822
|
+
debugOverlay,
|
|
8823
|
+
showWatermark,
|
|
8824
|
+
|
|
8825
|
+
// Debug
|
|
8826
|
+
ASSERT,
|
|
8827
|
+
debugRect,
|
|
8828
|
+
debugPoly,
|
|
8829
|
+
debugCircle,
|
|
8830
|
+
debugPoint,
|
|
8831
|
+
debugLine,
|
|
8832
|
+
debugOverlap,
|
|
8833
|
+
debugText,
|
|
8834
|
+
debugClear,
|
|
8835
|
+
debugScreenshot,
|
|
8836
|
+
debugSaveCanvas,
|
|
8837
|
+
debugSaveText,
|
|
8838
|
+
debugSaveDataURL,
|
|
8839
|
+
debugShowErrors,
|
|
8840
|
+
debugVideoCaptureIsActive,
|
|
8841
|
+
debugVideoCaptureStart,
|
|
8842
|
+
debugVideoCaptureStop,
|
|
8843
|
+
|
|
8844
|
+
// Settings
|
|
8845
|
+
cameraPos,
|
|
8846
|
+
cameraScale,
|
|
8847
|
+
canvasMaxSize,
|
|
8848
|
+
canvasFixedSize,
|
|
8849
|
+
canvasPixelated,
|
|
8850
|
+
tilesPixelated,
|
|
8851
|
+
fontDefault,
|
|
8852
|
+
showSplashScreen,
|
|
8853
|
+
headlessMode,
|
|
8854
|
+
tileSizeDefault,
|
|
8855
|
+
tileFixBleedScale,
|
|
8856
|
+
enablePhysicsSolver,
|
|
8857
|
+
objectDefaultMass,
|
|
8858
|
+
objectDefaultDamping,
|
|
8859
|
+
objectDefaultAngleDamping,
|
|
8860
|
+
objectDefaultElasticity,
|
|
8861
|
+
objectDefaultFriction,
|
|
8862
|
+
objectMaxSpeed,
|
|
8863
|
+
gravity,
|
|
8864
|
+
particleEmitRateScale,
|
|
8865
|
+
glEnable,
|
|
8866
|
+
glOverlay,
|
|
8867
|
+
gamepadsEnable,
|
|
8868
|
+
gamepadDirectionEmulateStick,
|
|
8869
|
+
inputWASDEmulateDirection,
|
|
8870
|
+
touchGamepadEnable,
|
|
8871
|
+
touchGamepadAnalog,
|
|
8872
|
+
touchGamepadSize,
|
|
8873
|
+
touchGamepadAlpha,
|
|
8874
|
+
vibrateEnable,
|
|
8875
|
+
soundEnable,
|
|
8876
|
+
soundVolume,
|
|
8877
|
+
soundDefaultRange,
|
|
8878
|
+
soundDefaultTaper,
|
|
8879
|
+
medalDisplayTime,
|
|
8880
|
+
medalDisplaySlideTime,
|
|
8881
|
+
medalDisplaySize,
|
|
8882
|
+
|
|
8883
|
+
// Setters for globals
|
|
8884
|
+
setCameraPos,
|
|
8885
|
+
setCameraScale,
|
|
8886
|
+
setCanvasMaxSize,
|
|
8887
|
+
setCanvasFixedSize,
|
|
8888
|
+
setCanvasPixelated,
|
|
8889
|
+
setTilesPixelated,
|
|
8890
|
+
setFontDefault,
|
|
8891
|
+
setShowSplashScreen,
|
|
8892
|
+
setHeadlessMode,
|
|
8893
|
+
setGlEnable,
|
|
8894
|
+
setGlOverlay,
|
|
8895
|
+
setTileSizeDefault,
|
|
8896
|
+
setTileFixBleedScale,
|
|
8897
|
+
setEnablePhysicsSolver,
|
|
8898
|
+
setObjectDefaultMass,
|
|
8899
|
+
setObjectDefaultDamping,
|
|
8900
|
+
setObjectDefaultAngleDamping,
|
|
8901
|
+
setObjectDefaultElasticity,
|
|
8902
|
+
setObjectDefaultFriction,
|
|
8903
|
+
setObjectMaxSpeed,
|
|
8904
|
+
setGravity,
|
|
8905
|
+
setParticleEmitRateScale,
|
|
8906
|
+
setTouchInputEnable,
|
|
8907
|
+
setGamepadsEnable,
|
|
8908
|
+
setGamepadDirectionEmulateStick,
|
|
8909
|
+
setInputWASDEmulateDirection,
|
|
8910
|
+
setTouchGamepadEnable,
|
|
8911
|
+
setTouchGamepadAnalog,
|
|
8912
|
+
setTouchGamepadSize,
|
|
8913
|
+
setTouchGamepadAlpha,
|
|
8914
|
+
setVibrateEnable,
|
|
8915
|
+
setSoundEnable,
|
|
8916
|
+
setSoundVolume,
|
|
8917
|
+
setSoundDefaultRange,
|
|
8918
|
+
setSoundDefaultTaper,
|
|
8919
|
+
setMedalDisplayTime,
|
|
8920
|
+
setMedalDisplaySlideTime,
|
|
8921
|
+
setMedalDisplaySize,
|
|
8922
|
+
setMedalsPreventUnlock,
|
|
8923
|
+
setShowWatermark,
|
|
8924
|
+
setDebugKey,
|
|
8925
|
+
|
|
8926
|
+
// Utilities
|
|
8927
|
+
PI,
|
|
8928
|
+
abs,
|
|
8929
|
+
min,
|
|
8930
|
+
max,
|
|
8931
|
+
sign,
|
|
8932
|
+
mod,
|
|
8933
|
+
clamp,
|
|
8934
|
+
percent,
|
|
8935
|
+
distanceWrap,
|
|
8936
|
+
lerpWrap,
|
|
8937
|
+
distanceAngle,
|
|
8938
|
+
lerpAngle,
|
|
8939
|
+
lerp,
|
|
8940
|
+
smoothStep,
|
|
8941
|
+
nearestPowerOfTwo,
|
|
8942
|
+
isOverlapping,
|
|
8943
|
+
isIntersecting,
|
|
8944
|
+
wave,
|
|
8945
|
+
formatTime,
|
|
8946
|
+
|
|
8947
|
+
// Random
|
|
8948
|
+
rand,
|
|
8949
|
+
randInt,
|
|
8950
|
+
randSign,
|
|
8951
|
+
randInCircle,
|
|
8952
|
+
randVector,
|
|
8953
|
+
randColor,
|
|
8954
|
+
|
|
8955
|
+
// Utility Classes
|
|
8956
|
+
RandomGenerator,
|
|
8957
|
+
Vector2,
|
|
8958
|
+
Color,
|
|
8959
|
+
Timer,
|
|
8960
|
+
vec2,
|
|
8961
|
+
rgb,
|
|
8962
|
+
hsl,
|
|
8963
|
+
isColor,
|
|
8964
|
+
|
|
8965
|
+
// Default Colors
|
|
8966
|
+
WHITE,
|
|
8967
|
+
BLACK,
|
|
8968
|
+
GRAY,
|
|
8969
|
+
RED,
|
|
8970
|
+
ORANGE,
|
|
8971
|
+
YELLOW,
|
|
8972
|
+
GREEN,
|
|
8973
|
+
CYAN,
|
|
8974
|
+
BLUE,
|
|
8975
|
+
PURPLE,
|
|
8976
|
+
MAGENTA,
|
|
8977
|
+
|
|
8978
|
+
// Draw
|
|
8979
|
+
textureInfos,
|
|
8980
|
+
tile,
|
|
8981
|
+
TileInfo,
|
|
8982
|
+
TextureInfo,
|
|
6121
8983
|
mainCanvas,
|
|
6122
8984
|
mainContext,
|
|
6123
8985
|
overlayCanvas,
|
|
@@ -6151,6 +9013,7 @@ export
|
|
|
6151
9013
|
glCopyToContext,
|
|
6152
9014
|
glCreateProgram,
|
|
6153
9015
|
glCreateTexture,
|
|
9016
|
+
glSetTextureData,
|
|
6154
9017
|
glDraw,
|
|
6155
9018
|
glFlush,
|
|
6156
9019
|
glSetTexture,
|
|
@@ -6180,7 +9043,8 @@ export
|
|
|
6180
9043
|
mousePosScreen,
|
|
6181
9044
|
mouseWheel,
|
|
6182
9045
|
isUsingGamepad,
|
|
6183
|
-
|
|
9046
|
+
inputPreventDefault,
|
|
9047
|
+
setInputPreventDefault,
|
|
6184
9048
|
gamepadIsDown,
|
|
6185
9049
|
gamepadWasPressed,
|
|
6186
9050
|
gamepadWasReleased,
|
|
@@ -6193,28 +9057,27 @@ export
|
|
|
6193
9057
|
// Audio
|
|
6194
9058
|
Sound,
|
|
6195
9059
|
SoundWave,
|
|
6196
|
-
Music,
|
|
6197
9060
|
playAudioFile,
|
|
6198
9061
|
speak,
|
|
6199
9062
|
speakStop,
|
|
6200
9063
|
getNoteFrequency,
|
|
6201
|
-
audioContext,
|
|
6202
9064
|
playSamples,
|
|
6203
9065
|
zzfx,
|
|
9066
|
+
zzfxG,
|
|
9067
|
+
zzfxR,
|
|
9068
|
+
audioContext,
|
|
6204
9069
|
|
|
6205
9070
|
// Base Object
|
|
6206
9071
|
EngineObject,
|
|
6207
9072
|
|
|
6208
9073
|
// Tiles
|
|
6209
|
-
|
|
6210
|
-
tileCollisionSize,
|
|
6211
|
-
initTileCollision,
|
|
6212
|
-
setTileCollisionData,
|
|
9074
|
+
tileCollisionLayers,
|
|
6213
9075
|
getTileCollisionData,
|
|
6214
9076
|
tileCollisionTest,
|
|
6215
9077
|
tileCollisionRaycast,
|
|
6216
9078
|
TileLayerData,
|
|
6217
9079
|
TileLayer,
|
|
9080
|
+
TileCollisionLayer,
|
|
6218
9081
|
|
|
6219
9082
|
// Particles
|
|
6220
9083
|
ParticleEmitter,
|
|
@@ -6227,3 +9090,54 @@ export
|
|
|
6227
9090
|
Medal,
|
|
6228
9091
|
};
|
|
6229
9092
|
|
|
9093
|
+
/**
|
|
9094
|
+
* LittleJS Module Plugins Export
|
|
9095
|
+
*/
|
|
9096
|
+
|
|
9097
|
+
export
|
|
9098
|
+
{
|
|
9099
|
+
// Newgrounds
|
|
9100
|
+
newgrounds,
|
|
9101
|
+
NewgroundsPlugin,
|
|
9102
|
+
NewgroundsMedal,
|
|
9103
|
+
|
|
9104
|
+
// Post Process
|
|
9105
|
+
postProcess,
|
|
9106
|
+
PostProcessPlugin,
|
|
9107
|
+
|
|
9108
|
+
// ZzFXMusic
|
|
9109
|
+
ZzFXMusic,
|
|
9110
|
+
|
|
9111
|
+
// UI System
|
|
9112
|
+
uiSystem,
|
|
9113
|
+
UISystemPlugin,
|
|
9114
|
+
UIObject,
|
|
9115
|
+
UIText,
|
|
9116
|
+
UITile,
|
|
9117
|
+
UIButton,
|
|
9118
|
+
UICheckbox,
|
|
9119
|
+
UIScrollbar,
|
|
9120
|
+
|
|
9121
|
+
// Box2D Physics
|
|
9122
|
+
box2d,
|
|
9123
|
+
box2dDebug,
|
|
9124
|
+
box2dSetDebug,
|
|
9125
|
+
box2dEngineInit,
|
|
9126
|
+
Box2dPlugin,
|
|
9127
|
+
Box2dObject,
|
|
9128
|
+
Box2dRaycastResult,
|
|
9129
|
+
Box2dJoint,
|
|
9130
|
+
Box2dTargetJoint,
|
|
9131
|
+
Box2dDistanceJoint,
|
|
9132
|
+
Box2dPinJoint,
|
|
9133
|
+
Box2dRopeJoint,
|
|
9134
|
+
Box2dRevoluteJoint,
|
|
9135
|
+
Box2dGearJoint,
|
|
9136
|
+
Box2dPrismaticJoint,
|
|
9137
|
+
Box2dWheelJoint,
|
|
9138
|
+
Box2dWeldJoint,
|
|
9139
|
+
Box2dFrictionJoint,
|
|
9140
|
+
Box2dPulleyJoint,
|
|
9141
|
+
Box2dMotorJoint,
|
|
9142
|
+
};
|
|
9143
|
+
|