littlejsengine 1.18.18 → 1.18.21
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/README.md +9 -1
- package/dist/littlejs.d.ts +84 -19
- package/dist/littlejs.esm.js +233 -53
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +228 -53
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +225 -53
- package/package.json +1 -1
- package/plugins/box2d.js +12 -11
- package/plugins/newgrounds.js +2 -1
- package/plugins/pluginExport.js +5 -0
- package/plugins/postProcess.js +5 -4
- package/plugins/threejs.js +155 -0
- package/plugins/tweenSystem.js +5 -1
- package/plugins/uiSystem.js +4 -3
- package/src/engine.js +6 -2
- package/src/engineAudio.js +2 -2
- package/src/engineBuild.mjs +3 -2
- package/src/engineDebug.js +3 -0
- package/src/engineDraw.js +8 -8
- package/src/engineInput.js +8 -4
- package/src/engineMath.js +2 -2
- package/src/engineObject.js +0 -2
- package/src/engineParticles.js +5 -6
- package/src/engineSettings.js +1 -0
- package/src/engineTileLayer.js +10 -6
- package/src/engineUtilities.js +1 -1
package/dist/littlejs.esm.js
CHANGED
|
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
|
|
|
35
35
|
* @type {string}
|
|
36
36
|
* @default
|
|
37
37
|
* @memberof Engine */
|
|
38
|
-
const engineVersion = '1.18.
|
|
38
|
+
const engineVersion = '1.18.21';
|
|
39
39
|
|
|
40
40
|
/** Frames per second to update
|
|
41
41
|
* @type {number}
|
|
@@ -474,6 +474,11 @@ function engineObjectsUpdate()
|
|
|
474
474
|
// get list of solid objects for physics optimization
|
|
475
475
|
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
476
476
|
|
|
477
|
+
// update physics before object update
|
|
478
|
+
for (const o of engineObjects)
|
|
479
|
+
if (!o.parent && !o.destroyed)
|
|
480
|
+
o.updatePhysics();
|
|
481
|
+
|
|
477
482
|
// recursive object update
|
|
478
483
|
function updateChildObject(o)
|
|
479
484
|
{
|
|
@@ -489,7 +494,6 @@ function engineObjectsUpdate()
|
|
|
489
494
|
|
|
490
495
|
// update top level objects
|
|
491
496
|
o.update();
|
|
492
|
-
o.updatePhysics();
|
|
493
497
|
for (const child of o.children)
|
|
494
498
|
updateChildObject(child);
|
|
495
499
|
o.updateTransforms();
|
|
@@ -1237,6 +1241,7 @@ function debugVideoCaptureStart()
|
|
|
1237
1241
|
{
|
|
1238
1242
|
LOG('Video capture not supported in this browser!');
|
|
1239
1243
|
silentAudioSource?.stop();
|
|
1244
|
+
audioStreamDestination && audioMasterGain.disconnect(audioStreamDestination);
|
|
1240
1245
|
return;
|
|
1241
1246
|
}
|
|
1242
1247
|
|
|
@@ -1265,6 +1270,8 @@ function debugVideoCaptureStop()
|
|
|
1265
1270
|
debugVideoCapture.silentAudioSource?.stop();
|
|
1266
1271
|
debugVideoCapture.mediaRecorder?.stop();
|
|
1267
1272
|
debugVideoCapture.videoTrack?.stop();
|
|
1273
|
+
if (debugVideoCapture.audioStreamDestination)
|
|
1274
|
+
audioMasterGain.disconnect(debugVideoCapture.audioStreamDestination);
|
|
1268
1275
|
debugVideoCapture = undefined;
|
|
1269
1276
|
}
|
|
1270
1277
|
|
|
@@ -1503,7 +1510,7 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
|
1503
1510
|
* @param {number} value
|
|
1504
1511
|
* @return {boolean}
|
|
1505
1512
|
* @memberof Math */
|
|
1506
|
-
function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
1513
|
+
function isPowerOfTwo(value) { return value > 0 && !(value & (value - 1)); }
|
|
1507
1514
|
|
|
1508
1515
|
/** Returns the nearest power of two not less than the value
|
|
1509
1516
|
* @param {number} value
|
|
@@ -1580,7 +1587,7 @@ function isIntersecting(start, end, pos, size)
|
|
|
1580
1587
|
* @memberof Math */
|
|
1581
1588
|
function oscillate(frequency=1, amplitude=1, t=time, offset=0, type=0)
|
|
1582
1589
|
{
|
|
1583
|
-
const phase = (offset + t*frequency
|
|
1590
|
+
const phase = mod(offset + t*frequency, 1);
|
|
1584
1591
|
let value;
|
|
1585
1592
|
|
|
1586
1593
|
if (type === 1) // triangle
|
|
@@ -2516,7 +2523,7 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
|
2516
2523
|
class Timer
|
|
2517
2524
|
{
|
|
2518
2525
|
/** Create a timer object set time passed in
|
|
2519
|
-
* @param {number} [timeLeft] - How much time left before the timer
|
|
2526
|
+
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds (undefined = unset)
|
|
2520
2527
|
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
2521
2528
|
constructor(timeLeft, useRealTime=false)
|
|
2522
2529
|
{
|
|
@@ -3115,6 +3122,7 @@ let vibrateEnable = true;
|
|
|
3115
3122
|
let soundEnable = true;
|
|
3116
3123
|
|
|
3117
3124
|
/** Volume scale to apply to all sound, music and speech
|
|
3125
|
+
* Use setSoundVolume to also update the audio master gain immediately
|
|
3118
3126
|
* @type {number}
|
|
3119
3127
|
* @default
|
|
3120
3128
|
* @memberof Settings */
|
|
@@ -3669,8 +3677,6 @@ class EngineObject
|
|
|
3669
3677
|
|
|
3670
3678
|
// notify objects of collision and check if should be resolved
|
|
3671
3679
|
const collide1 = this.collideWithObject(o);
|
|
3672
|
-
// callback may have destroyed us; stop resolving against more objects
|
|
3673
|
-
if (this.destroyed) return;
|
|
3674
3680
|
const collide2 = o.collideWithObject(this);
|
|
3675
3681
|
if (!collide1 || !collide2) continue;
|
|
3676
3682
|
|
|
@@ -4196,6 +4202,14 @@ class TileInfo
|
|
|
4196
4202
|
return this.offset(new Vector2(x));
|
|
4197
4203
|
}
|
|
4198
4204
|
|
|
4205
|
+
/**
|
|
4206
|
+
* Returns a tile info for an index using this tile as reference
|
|
4207
|
+
* @param {Vector2|number} [index=0]
|
|
4208
|
+
* @return {TileInfo}
|
|
4209
|
+
*/
|
|
4210
|
+
index(index)
|
|
4211
|
+
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
4212
|
+
|
|
4199
4213
|
/**
|
|
4200
4214
|
* Set this tile to use a full image in a texture info
|
|
4201
4215
|
* @param {TextureInfo} [textureInfo]
|
|
@@ -4209,14 +4223,6 @@ class TileInfo
|
|
|
4209
4223
|
this.bleed = this.padding = 0;
|
|
4210
4224
|
return this;
|
|
4211
4225
|
}
|
|
4212
|
-
|
|
4213
|
-
/**
|
|
4214
|
-
* Returns a tile info for an index using this tile as reference
|
|
4215
|
-
* @param {Vector2|number} [index=0]
|
|
4216
|
-
* @return {TileInfo}
|
|
4217
|
-
*/
|
|
4218
|
-
tile(index)
|
|
4219
|
-
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
4220
4226
|
}
|
|
4221
4227
|
|
|
4222
4228
|
/**
|
|
@@ -5914,7 +5920,10 @@ function inputInit()
|
|
|
5914
5920
|
{
|
|
5915
5921
|
inputData[0][e.code] = (inputData[0][e.code]&2) | 4;
|
|
5916
5922
|
if (inputWASDEmulateDirection)
|
|
5917
|
-
|
|
5923
|
+
{
|
|
5924
|
+
const remap = remapKey(e.code);
|
|
5925
|
+
inputData[0][remap] = (inputData[0][remap]&2) | 4;
|
|
5926
|
+
}
|
|
5918
5927
|
}
|
|
5919
5928
|
function remapKey(k)
|
|
5920
5929
|
{
|
|
@@ -5989,7 +5998,7 @@ function inputInit()
|
|
|
5989
5998
|
document.addEventListener('touchend', (e)=> handleTouch(e), { passive: false });
|
|
5990
5999
|
|
|
5991
6000
|
// handle all touch events the same way
|
|
5992
|
-
let wasTouching;
|
|
6001
|
+
let wasTouching, touchIdentifier;
|
|
5993
6002
|
function handleTouch(e)
|
|
5994
6003
|
{
|
|
5995
6004
|
if (!touchInputEnable) return;
|
|
@@ -6020,10 +6029,11 @@ function inputInit()
|
|
|
6020
6029
|
const pos = vec2(gameTouches[0].clientX, gameTouches[0].clientY);
|
|
6021
6030
|
const mousePosScreenLast = mousePosScreen;
|
|
6022
6031
|
mousePosScreen = mouseEventToScreen(pos);
|
|
6023
|
-
if (wasTouching)
|
|
6032
|
+
if (wasTouching && gameTouches[0].identifier === touchIdentifier)
|
|
6024
6033
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
6025
|
-
else
|
|
6034
|
+
else if (!wasTouching)
|
|
6026
6035
|
inputData[0][button] = 3;
|
|
6036
|
+
touchIdentifier = gameTouches[0].identifier;
|
|
6027
6037
|
}
|
|
6028
6038
|
else if (wasTouching)
|
|
6029
6039
|
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
@@ -7181,7 +7191,7 @@ class SoundInstance
|
|
|
7181
7191
|
* @param {number} [rate] - How quickly to speak
|
|
7182
7192
|
* @param {number} [pitch] - How much to change the pitch by
|
|
7183
7193
|
* @param {string} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
7184
|
-
* @return {SpeechSynthesisUtterance} - The utterance that was spoken
|
|
7194
|
+
* @return {SpeechSynthesisUtterance|undefined} - The utterance that was spoken, or undefined if speech is unavailable
|
|
7185
7195
|
* @memberof Audio */
|
|
7186
7196
|
function speak(text, volume=1, rate=1, pitch=1, language='')
|
|
7187
7197
|
{
|
|
@@ -7234,7 +7244,7 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
7234
7244
|
* @param {number} [pan] - How much to apply stereo panning
|
|
7235
7245
|
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
|
|
7236
7246
|
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
7237
|
-
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
7247
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing (disconnected when the sound ends)
|
|
7238
7248
|
* @param {number} [offset] - Offset in seconds to start playback from
|
|
7239
7249
|
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
7240
7250
|
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
@@ -7476,10 +7486,14 @@ function tileCollisionGetData(pos, solidOnly=true)
|
|
|
7476
7486
|
// check all tile collision layers
|
|
7477
7487
|
for (const layer of tileCollisionLayers)
|
|
7478
7488
|
if (!solidOnly || layer.isSolid)
|
|
7479
|
-
if (pos.arrayCheck(layer.size))
|
|
7480
7489
|
{
|
|
7481
|
-
|
|
7482
|
-
|
|
7490
|
+
// convert world pos to layer local space
|
|
7491
|
+
const layerPos = pos.subtract(layer.pos);
|
|
7492
|
+
if (layerPos.arrayCheck(layer.size))
|
|
7493
|
+
{
|
|
7494
|
+
const data = layer.getCollisionData(layerPos);
|
|
7495
|
+
if (data) return data;
|
|
7496
|
+
}
|
|
7483
7497
|
}
|
|
7484
7498
|
return 0;
|
|
7485
7499
|
}
|
|
@@ -7754,7 +7768,7 @@ class TileLayer extends CanvasLayer
|
|
|
7754
7768
|
|
|
7755
7769
|
/** @property {TileInfo} - Default tile info for layer */
|
|
7756
7770
|
this.tileInfo = undefined;
|
|
7757
|
-
/** @property {Array<TileLayerData>} -
|
|
7771
|
+
/** @property {Array<TileLayerData>} - Array of tile data for the layer */
|
|
7758
7772
|
this.data = [];
|
|
7759
7773
|
/** @property {boolean} - Is this layer using a webgl texture? */
|
|
7760
7774
|
this.isUsingWebGL = false;
|
|
@@ -7839,7 +7853,7 @@ class TileLayer extends CanvasLayer
|
|
|
7839
7853
|
|
|
7840
7854
|
const size = this.drawSize || this.size;
|
|
7841
7855
|
const pos = this.pos.add(size.scale(.5));
|
|
7842
|
-
this.draw(pos,
|
|
7856
|
+
this.draw(pos, size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
7843
7857
|
}
|
|
7844
7858
|
|
|
7845
7859
|
/** Called after this layer is redrawn, does nothing by default */
|
|
@@ -7928,7 +7942,7 @@ class TileLayer extends CanvasLayer
|
|
|
7928
7942
|
const d = this.getData(layerPos);
|
|
7929
7943
|
if (!d || !d.tile) return;
|
|
7930
7944
|
|
|
7931
|
-
const tileInfo = this.tileInfo && this.tileInfo.
|
|
7945
|
+
const tileInfo = this.tileInfo && this.tileInfo.index(d.tile);
|
|
7932
7946
|
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
7933
7947
|
}
|
|
7934
7948
|
|
|
@@ -8222,8 +8236,8 @@ class TileCollisionLayer extends TileLayer
|
|
|
8222
8236
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
8223
8237
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
8224
8238
|
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
8225
|
-
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate
|
|
8226
|
-
* .5, 1 // randomness, collide
|
|
8239
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate
|
|
8240
|
+
* .5, 1 // randomness, collide
|
|
8227
8241
|
* );
|
|
8228
8242
|
*/
|
|
8229
8243
|
class ParticleEmitter extends EngineObject
|
|
@@ -8615,13 +8629,12 @@ class Particle
|
|
|
8615
8629
|
const hitLayer = tileCollisionTest(this.pos);
|
|
8616
8630
|
if (!testCollision(oldPos))
|
|
8617
8631
|
{
|
|
8618
|
-
// testCollision already invoked collideCallback with the
|
|
8619
|
-
// correct (this, data, pos) args; no need to re-check here.
|
|
8620
8632
|
// test which side we bounced off (or both if a corner)
|
|
8621
8633
|
const isBlockedX = testCollision(vec2(this.pos.x, oldPos.y));
|
|
8622
8634
|
const isBlockedY = testCollision(vec2(oldPos.x, this.pos.y));
|
|
8623
|
-
|
|
8624
|
-
const
|
|
8635
|
+
// collide callback may hit where the layer test does not, so hitLayer can be undefined
|
|
8636
|
+
const hitRestitution = hitLayer ? max(restitution, hitLayer.restitution) : restitution;
|
|
8637
|
+
const hitFriction = hitLayer ? max(friction, hitLayer.friction) : friction;
|
|
8625
8638
|
if (isBlockedX)
|
|
8626
8639
|
{
|
|
8627
8640
|
// move to previous X position and bounce
|
|
@@ -10270,7 +10283,8 @@ class NewgroundsPlugin
|
|
|
10270
10283
|
return;
|
|
10271
10284
|
}
|
|
10272
10285
|
debugMedals && LOG(xmlHttp.responseText);
|
|
10273
|
-
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
10286
|
+
try { return xmlHttp.responseText && JSON.parse(xmlHttp.responseText); }
|
|
10287
|
+
catch(e) { debugMedals && LOG('newgrounds response is not valid JSON', e); }
|
|
10274
10288
|
}
|
|
10275
10289
|
}
|
|
10276
10290
|
/**
|
|
@@ -10289,8 +10303,8 @@ class NewgroundsPlugin
|
|
|
10289
10303
|
let postProcess;
|
|
10290
10304
|
|
|
10291
10305
|
/////////////////////////////////////////////////////////////////////////
|
|
10292
|
-
/**
|
|
10293
|
-
*
|
|
10306
|
+
/**
|
|
10307
|
+
* Post Process Plugin - Applies a full screen shader to the rendered output
|
|
10294
10308
|
* @memberof PostProcess
|
|
10295
10309
|
*/
|
|
10296
10310
|
class PostProcessPlugin
|
|
@@ -10404,8 +10418,9 @@ class PostProcessPlugin
|
|
|
10404
10418
|
workCanvas.height = mainCanvasSize.y;
|
|
10405
10419
|
glCopyToContext(workContext);
|
|
10406
10420
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
10407
|
-
mainCanvas.width |= 0
|
|
10408
|
-
|
|
10421
|
+
mainCanvas.width |= 0; // setting size clears the main canvas
|
|
10422
|
+
|
|
10423
|
+
|
|
10409
10424
|
// copy work canvas to texture
|
|
10410
10425
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
|
|
10411
10426
|
}
|
|
@@ -11510,7 +11525,7 @@ class UISystemPlugin
|
|
|
11510
11525
|
}
|
|
11511
11526
|
|
|
11512
11527
|
/** Get other axis navigation direction from gamepad or keyboard
|
|
11513
|
-
* @return {
|
|
11528
|
+
* @return {number} */
|
|
11514
11529
|
getNavigationOtherDirection()
|
|
11515
11530
|
{
|
|
11516
11531
|
if (uiSystem.navigationDirection === 2)
|
|
@@ -11599,6 +11614,7 @@ class UISystemPlugin
|
|
|
11599
11614
|
uiSystem.navigationDirection = savedNavigationDirection;
|
|
11600
11615
|
inputClear();
|
|
11601
11616
|
}
|
|
11617
|
+
return confirmMenu;
|
|
11602
11618
|
}
|
|
11603
11619
|
}
|
|
11604
11620
|
|
|
@@ -12032,7 +12048,7 @@ class UITextInput extends UIObject
|
|
|
12032
12048
|
this.onClick();
|
|
12033
12049
|
}
|
|
12034
12050
|
|
|
12035
|
-
/** Stop editing the text
|
|
12051
|
+
/** Stop editing the text */
|
|
12036
12052
|
stopEditing()
|
|
12037
12053
|
{
|
|
12038
12054
|
if (!this.isKeyInputObject())
|
|
@@ -12195,7 +12211,7 @@ class UICheckbox extends UIObject
|
|
|
12195
12211
|
ASSERT(isStringLike(text), 'ui checkbox must be a string');
|
|
12196
12212
|
ASSERT(isColor(color), 'ui checkbox color must be a color');
|
|
12197
12213
|
|
|
12198
|
-
/** @property {boolean} -
|
|
12214
|
+
/** @property {boolean} - Is the checkbox currently checked? */
|
|
12199
12215
|
this.checked = checked;
|
|
12200
12216
|
// set properties
|
|
12201
12217
|
this.text = text;
|
|
@@ -12960,7 +12976,7 @@ class Box2dObject extends EngineObject
|
|
|
12960
12976
|
shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
|
|
12961
12977
|
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
12962
12978
|
fixtures.push(f);
|
|
12963
|
-
|
|
12979
|
+
edgePoints.push(points[i].copy());
|
|
12964
12980
|
}
|
|
12965
12981
|
this.edgeLoops.push(edgePoints);
|
|
12966
12982
|
return fixtures;
|
|
@@ -13026,7 +13042,7 @@ class Box2dObject extends EngineObject
|
|
|
13026
13042
|
/** Sets the position
|
|
13027
13043
|
* @param {Vector2} pos */
|
|
13028
13044
|
setPosition(pos)
|
|
13029
|
-
{ this.setTransform(pos, this.body.GetAngle()); }
|
|
13045
|
+
{ this.setTransform(pos, -this.body.GetAngle()); }
|
|
13030
13046
|
|
|
13031
13047
|
/** Sets the angle
|
|
13032
13048
|
* @param {number} angle */
|
|
@@ -13123,6 +13139,7 @@ class Box2dObject extends EngineObject
|
|
|
13123
13139
|
filter.set_categoryBits(categoryBits);
|
|
13124
13140
|
filter.set_maskBits(0xffff & ~ignoreCategoryBits);
|
|
13125
13141
|
filter.set_groupIndex(groupIndex);
|
|
13142
|
+
fixture.SetFilterData(filter); // applies and refilters contacts
|
|
13126
13143
|
});
|
|
13127
13144
|
}
|
|
13128
13145
|
|
|
@@ -13658,7 +13675,7 @@ class Box2dRevoluteJoint extends Box2dJoint
|
|
|
13658
13675
|
jointDef.set_bodyB(objectB.body);
|
|
13659
13676
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
13660
13677
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
13661
|
-
jointDef.set_referenceAngle(
|
|
13678
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
13662
13679
|
jointDef.set_collideConnected(collide);
|
|
13663
13680
|
super(jointDef);
|
|
13664
13681
|
}
|
|
@@ -13805,14 +13822,14 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
13805
13822
|
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
13806
13823
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
13807
13824
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
13808
|
-
const localAxisA =
|
|
13825
|
+
const localAxisA = objectA.worldToLocalVector(worldAxis);
|
|
13809
13826
|
const jointDef = new box2d.instance.b2PrismaticJointDef();
|
|
13810
13827
|
jointDef.set_bodyA(objectA.body);
|
|
13811
13828
|
jointDef.set_bodyB(objectB.body);
|
|
13812
13829
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
13813
13830
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
13814
13831
|
jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
|
|
13815
|
-
jointDef.set_referenceAngle(
|
|
13832
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
13816
13833
|
jointDef.set_collideConnected(collide);
|
|
13817
13834
|
super(jointDef);
|
|
13818
13835
|
}
|
|
@@ -13915,7 +13932,7 @@ class Box2dWheelJoint extends Box2dJoint
|
|
|
13915
13932
|
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
13916
13933
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
13917
13934
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
13918
|
-
const localAxisA =
|
|
13935
|
+
const localAxisA = objectA.worldToLocalVector(worldAxis);
|
|
13919
13936
|
const jointDef = new box2d.instance.b2WheelJointDef();
|
|
13920
13937
|
jointDef.set_bodyA(objectA.body);
|
|
13921
13938
|
jointDef.set_bodyB(objectB.body);
|
|
@@ -14015,7 +14032,7 @@ class Box2dWeldJoint extends Box2dJoint
|
|
|
14015
14032
|
jointDef.set_bodyB(objectB.body);
|
|
14016
14033
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
14017
14034
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
14018
|
-
jointDef.set_referenceAngle(
|
|
14035
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
14019
14036
|
jointDef.set_collideConnected(collide);
|
|
14020
14037
|
super(jointDef);
|
|
14021
14038
|
}
|
|
@@ -14462,7 +14479,7 @@ class Box2dPlugin
|
|
|
14462
14479
|
* @param {number} [lineWidth]
|
|
14463
14480
|
* @param {boolean} [useWebGL=glEnable]
|
|
14464
14481
|
* @param {CanvasRenderingContext2D} [context] */
|
|
14465
|
-
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1,
|
|
14482
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
|
|
14466
14483
|
{
|
|
14467
14484
|
const shape = box2d.castShapeObject(fixture.GetShape());
|
|
14468
14485
|
switch (shape.GetType())
|
|
@@ -14472,20 +14489,20 @@ class Box2dPlugin
|
|
|
14472
14489
|
let points = [];
|
|
14473
14490
|
for (let i=shape.GetVertexCount(); i--;)
|
|
14474
14491
|
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
14475
|
-
drawPoly(points, color, lineWidth, lineColor, pos, angle,
|
|
14492
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, false, context);
|
|
14476
14493
|
break;
|
|
14477
14494
|
}
|
|
14478
14495
|
case box2d.instance.b2Shape.e_circle:
|
|
14479
14496
|
{
|
|
14480
14497
|
const radius = shape.get_m_radius();
|
|
14481
|
-
drawCircle(pos, radius*2, color, lineWidth, lineColor,
|
|
14498
|
+
drawCircle(pos, radius*2, color, lineWidth, lineColor, useWebGL, false, context);
|
|
14482
14499
|
break;
|
|
14483
14500
|
}
|
|
14484
14501
|
case box2d.instance.b2Shape.e_edge:
|
|
14485
14502
|
{
|
|
14486
14503
|
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
14487
14504
|
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
14488
|
-
drawLine(v1, v2, lineWidth, lineColor, pos, angle,
|
|
14505
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebGL, false, context);
|
|
14489
14506
|
break;
|
|
14490
14507
|
}
|
|
14491
14508
|
}
|
|
@@ -15316,7 +15333,11 @@ function tweenProperty(target, propertyPath, start, end, duration = 1, options =
|
|
|
15316
15333
|
const callback = (value) =>
|
|
15317
15334
|
{
|
|
15318
15335
|
let obj = target;
|
|
15319
|
-
for (const k of parts)
|
|
15336
|
+
for (const k of parts)
|
|
15337
|
+
{
|
|
15338
|
+
obj = obj[k];
|
|
15339
|
+
ASSERT(obj != null, 'tweenProperty path does not resolve: ' + propertyPath);
|
|
15340
|
+
}
|
|
15320
15341
|
obj[lastKey] = value;
|
|
15321
15342
|
};
|
|
15322
15343
|
return new Tween(callback, start, end, duration, options);
|
|
@@ -16199,6 +16220,160 @@ class PathFinder
|
|
|
16199
16220
|
}
|
|
16200
16221
|
}
|
|
16201
16222
|
|
|
16223
|
+
/**
|
|
16224
|
+
* LittleJS Three.js Plugin
|
|
16225
|
+
* - Renders a three.js scene on a canvas behind the LittleJS canvases
|
|
16226
|
+
* - The three.js module is passed in by the user, nothing is bundled
|
|
16227
|
+
* - Keep canvasClearColor transparent so the 3D scene shows through
|
|
16228
|
+
* - Aligned camera mode locks the 3D camera to the LittleJS 2D camera
|
|
16229
|
+
* - ThreeJSObject lets LittleJS physics drive a three.js mesh
|
|
16230
|
+
* - Call new ThreeJSPlugin(THREE) in gameInit to set up
|
|
16231
|
+
* @namespace ThreeJS
|
|
16232
|
+
*/
|
|
16233
|
+
|
|
16234
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
16235
|
+
|
|
16236
|
+
/** Global ThreeJS plugin object
|
|
16237
|
+
* @type {ThreeJSPlugin}
|
|
16238
|
+
* @memberof ThreeJS */
|
|
16239
|
+
let threeJS;
|
|
16240
|
+
|
|
16241
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
16242
|
+
/**
|
|
16243
|
+
* ThreeJS Plugin - Renders a three.js scene behind the LittleJS canvas
|
|
16244
|
+
* @example
|
|
16245
|
+
* // in gameInit, with three.js loaded by the user
|
|
16246
|
+
* new ThreeJSPlugin(THREE);
|
|
16247
|
+
* threeJS.scene.add(new THREE.AmbientLight);
|
|
16248
|
+
* @memberof ThreeJS
|
|
16249
|
+
*/
|
|
16250
|
+
class ThreeJSPlugin
|
|
16251
|
+
{
|
|
16252
|
+
/** Set up the three.js rendering layer, call in gameInit
|
|
16253
|
+
* @param {Object} THREE - The three.js module, supplied by the user
|
|
16254
|
+
* @param {number} [cameraFOV] - Vertical field of view in degrees */
|
|
16255
|
+
constructor(THREE, cameraFOV=60)
|
|
16256
|
+
{
|
|
16257
|
+
ASSERT(!threeJS, 'ThreeJS plugin already initialized');
|
|
16258
|
+
threeJS = this;
|
|
16259
|
+
if (headlessMode) return;
|
|
16260
|
+
ASSERT(mainCanvas, 'ThreeJS plugin must be created after engineInit, call in gameInit');
|
|
16261
|
+
ASSERT(THREE && THREE.WebGLRenderer, 'three.js module must be passed in');
|
|
16262
|
+
|
|
16263
|
+
/** @property {Object} - The three.js module passed into the constructor */
|
|
16264
|
+
this.THREE = THREE;
|
|
16265
|
+
/** @property {Object} - The three.js renderer */
|
|
16266
|
+
this.renderer = new THREE.WebGLRenderer({antialias: true});
|
|
16267
|
+
/** @property {Object} - The three.js scene, add lights and meshes here */
|
|
16268
|
+
this.scene = new THREE.Scene();
|
|
16269
|
+
/** @property {Object} - The three.js perspective camera */
|
|
16270
|
+
this.camera = new THREE.PerspectiveCamera(cameraFOV, 1, .1, 1e3);
|
|
16271
|
+
/** @property {boolean} - Lock the camera to the LittleJS 2D camera so the z=0 plane matches world space */
|
|
16272
|
+
this.cameraAlign2D = true;
|
|
16273
|
+
|
|
16274
|
+
// insert the canvas below the engine canvases and match the layout
|
|
16275
|
+
const threeCanvas = this.renderer.domElement;
|
|
16276
|
+
const rootElement = mainCanvas.parentElement;
|
|
16277
|
+
rootElement.insertBefore(threeCanvas, rootElement.firstChild);
|
|
16278
|
+
threeCanvas.style.cssText = mainCanvas.style.cssText;
|
|
16279
|
+
|
|
16280
|
+
// render automatically each frame after the engine renders
|
|
16281
|
+
engineAddPlugin(undefined, ()=> this.render());
|
|
16282
|
+
}
|
|
16283
|
+
|
|
16284
|
+
/** Position the camera so the z=0 plane exactly matches LittleJS world space,
|
|
16285
|
+
* called automatically when cameraAlign2D is set */
|
|
16286
|
+
alignCamera2D()
|
|
16287
|
+
{
|
|
16288
|
+
const halfHeight = mainCanvasSize.y / 2 / cameraScale; // half visible height in world units
|
|
16289
|
+
const distance = halfHeight / tan(this.camera.fov/2 * PI/180);
|
|
16290
|
+
this.camera.position.set(cameraPos.x, cameraPos.y, distance);
|
|
16291
|
+
// reset all axes in case a free camera was used, littlejs angles are clockwise
|
|
16292
|
+
this.camera.rotation.set(0, 0, -cameraAngle);
|
|
16293
|
+
}
|
|
16294
|
+
|
|
16295
|
+
/** Sync the canvas layout and render the scene, called automatically each frame */
|
|
16296
|
+
render()
|
|
16297
|
+
{
|
|
16298
|
+
if (!this.renderer) return; // headless mode
|
|
16299
|
+
|
|
16300
|
+
// keep renderer size and css in sync with the LittleJS canvas
|
|
16301
|
+
const threeCanvas = this.renderer.domElement;
|
|
16302
|
+
if (threeCanvas.width != mainCanvasSize.x || threeCanvas.height != mainCanvasSize.y)
|
|
16303
|
+
{
|
|
16304
|
+
this.renderer.setSize(mainCanvasSize.x, mainCanvasSize.y, false);
|
|
16305
|
+
this.camera.aspect = mainCanvasSize.x / mainCanvasSize.y;
|
|
16306
|
+
this.camera.updateProjectionMatrix();
|
|
16307
|
+
}
|
|
16308
|
+
if (threeCanvas.style.cssText != mainCanvas.style.cssText)
|
|
16309
|
+
threeCanvas.style.cssText = mainCanvas.style.cssText;
|
|
16310
|
+
|
|
16311
|
+
if (this.cameraAlign2D)
|
|
16312
|
+
this.alignCamera2D();
|
|
16313
|
+
this.renderer.render(this.scene, this.camera);
|
|
16314
|
+
}
|
|
16315
|
+
}
|
|
16316
|
+
|
|
16317
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
16318
|
+
/**
|
|
16319
|
+
* ThreeJS Object - EngineObject that drives a three.js mesh
|
|
16320
|
+
* - LittleJS physics moves the object and the mesh follows automatically
|
|
16321
|
+
* - Destroying the object removes the mesh from the scene
|
|
16322
|
+
* @extends EngineObject
|
|
16323
|
+
* @memberof ThreeJS
|
|
16324
|
+
*/
|
|
16325
|
+
class ThreeJSObject extends EngineObject
|
|
16326
|
+
{
|
|
16327
|
+
/** Create an engine object that drives a three.js mesh
|
|
16328
|
+
* @param {Vector2} [pos] - World space position
|
|
16329
|
+
* @param {Vector2} [size] - World space size
|
|
16330
|
+
* @param {Object} [mesh] - The three.js object3d to drive
|
|
16331
|
+
* @param {number} [z] - Mesh height above the 2D plane */
|
|
16332
|
+
constructor(pos, size, mesh, z=0)
|
|
16333
|
+
{
|
|
16334
|
+
super(pos, size);
|
|
16335
|
+
ASSERT(threeJS, 'ThreeJS plugin must be initialized first');
|
|
16336
|
+
|
|
16337
|
+
/** @property {Object} - The three.js object3d this object drives */
|
|
16338
|
+
this.mesh = mesh;
|
|
16339
|
+
/** @property {number} - Mesh height above the 2D plane */
|
|
16340
|
+
this.z = z;
|
|
16341
|
+
if (mesh)
|
|
16342
|
+
{
|
|
16343
|
+
threeJS.scene.add(mesh);
|
|
16344
|
+
this.syncMesh();
|
|
16345
|
+
}
|
|
16346
|
+
}
|
|
16347
|
+
|
|
16348
|
+
/** Update the object and sync the mesh to its transform */
|
|
16349
|
+
update()
|
|
16350
|
+
{
|
|
16351
|
+
super.update();
|
|
16352
|
+
this.syncMesh();
|
|
16353
|
+
}
|
|
16354
|
+
|
|
16355
|
+
/** Copy this object's transform to the mesh */
|
|
16356
|
+
syncMesh()
|
|
16357
|
+
{
|
|
16358
|
+
if (!this.mesh) return;
|
|
16359
|
+
this.mesh.position.set(this.pos.x, this.pos.y, this.z);
|
|
16360
|
+
this.mesh.rotation.z = -this.angle; // littlejs angles are clockwise
|
|
16361
|
+
}
|
|
16362
|
+
|
|
16363
|
+
/** The mesh is this object's visual, the default 2D rendering is skipped */
|
|
16364
|
+
render() {}
|
|
16365
|
+
|
|
16366
|
+
/** Destroy this object and remove its mesh from the scene
|
|
16367
|
+
* @param {boolean} [immediate] */
|
|
16368
|
+
destroy(immediate)
|
|
16369
|
+
{
|
|
16370
|
+
if (this.destroyed) return;
|
|
16371
|
+
// note: frequently destroyed objects should also dispose geometry and material
|
|
16372
|
+
this.mesh && threeJS.scene.remove(this.mesh);
|
|
16373
|
+
super.destroy(immediate);
|
|
16374
|
+
}
|
|
16375
|
+
}
|
|
16376
|
+
|
|
16202
16377
|
|
|
16203
16378
|
/**
|
|
16204
16379
|
* LittleJS Module Export
|
|
@@ -16685,4 +16860,9 @@ export
|
|
|
16685
16860
|
// Path Finding
|
|
16686
16861
|
PathFinder,
|
|
16687
16862
|
PathFinderNode,
|
|
16863
|
+
|
|
16864
|
+
// Three.js
|
|
16865
|
+
threeJS,
|
|
16866
|
+
ThreeJSPlugin,
|
|
16867
|
+
ThreeJSObject,
|
|
16688
16868
|
}
|