littlejsengine 1.18.19 → 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 -51
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +228 -51
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +225 -51
- 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/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 */
|
|
@@ -4194,6 +4202,14 @@ class TileInfo
|
|
|
4194
4202
|
return this.offset(new Vector2(x));
|
|
4195
4203
|
}
|
|
4196
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
|
+
|
|
4197
4213
|
/**
|
|
4198
4214
|
* Set this tile to use a full image in a texture info
|
|
4199
4215
|
* @param {TextureInfo} [textureInfo]
|
|
@@ -4207,14 +4223,6 @@ class TileInfo
|
|
|
4207
4223
|
this.bleed = this.padding = 0;
|
|
4208
4224
|
return this;
|
|
4209
4225
|
}
|
|
4210
|
-
|
|
4211
|
-
/**
|
|
4212
|
-
* Returns a tile info for an index using this tile as reference
|
|
4213
|
-
* @param {Vector2|number} [index=0]
|
|
4214
|
-
* @return {TileInfo}
|
|
4215
|
-
*/
|
|
4216
|
-
tile(index)
|
|
4217
|
-
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
4218
4226
|
}
|
|
4219
4227
|
|
|
4220
4228
|
/**
|
|
@@ -5912,7 +5920,10 @@ function inputInit()
|
|
|
5912
5920
|
{
|
|
5913
5921
|
inputData[0][e.code] = (inputData[0][e.code]&2) | 4;
|
|
5914
5922
|
if (inputWASDEmulateDirection)
|
|
5915
|
-
|
|
5923
|
+
{
|
|
5924
|
+
const remap = remapKey(e.code);
|
|
5925
|
+
inputData[0][remap] = (inputData[0][remap]&2) | 4;
|
|
5926
|
+
}
|
|
5916
5927
|
}
|
|
5917
5928
|
function remapKey(k)
|
|
5918
5929
|
{
|
|
@@ -5987,7 +5998,7 @@ function inputInit()
|
|
|
5987
5998
|
document.addEventListener('touchend', (e)=> handleTouch(e), { passive: false });
|
|
5988
5999
|
|
|
5989
6000
|
// handle all touch events the same way
|
|
5990
|
-
let wasTouching;
|
|
6001
|
+
let wasTouching, touchIdentifier;
|
|
5991
6002
|
function handleTouch(e)
|
|
5992
6003
|
{
|
|
5993
6004
|
if (!touchInputEnable) return;
|
|
@@ -6018,10 +6029,11 @@ function inputInit()
|
|
|
6018
6029
|
const pos = vec2(gameTouches[0].clientX, gameTouches[0].clientY);
|
|
6019
6030
|
const mousePosScreenLast = mousePosScreen;
|
|
6020
6031
|
mousePosScreen = mouseEventToScreen(pos);
|
|
6021
|
-
if (wasTouching)
|
|
6032
|
+
if (wasTouching && gameTouches[0].identifier === touchIdentifier)
|
|
6022
6033
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
6023
|
-
else
|
|
6034
|
+
else if (!wasTouching)
|
|
6024
6035
|
inputData[0][button] = 3;
|
|
6036
|
+
touchIdentifier = gameTouches[0].identifier;
|
|
6025
6037
|
}
|
|
6026
6038
|
else if (wasTouching)
|
|
6027
6039
|
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
@@ -7179,7 +7191,7 @@ class SoundInstance
|
|
|
7179
7191
|
* @param {number} [rate] - How quickly to speak
|
|
7180
7192
|
* @param {number} [pitch] - How much to change the pitch by
|
|
7181
7193
|
* @param {string} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
7182
|
-
* @return {SpeechSynthesisUtterance} - The utterance that was spoken
|
|
7194
|
+
* @return {SpeechSynthesisUtterance|undefined} - The utterance that was spoken, or undefined if speech is unavailable
|
|
7183
7195
|
* @memberof Audio */
|
|
7184
7196
|
function speak(text, volume=1, rate=1, pitch=1, language='')
|
|
7185
7197
|
{
|
|
@@ -7232,7 +7244,7 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
7232
7244
|
* @param {number} [pan] - How much to apply stereo panning
|
|
7233
7245
|
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
|
|
7234
7246
|
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
7235
|
-
* @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)
|
|
7236
7248
|
* @param {number} [offset] - Offset in seconds to start playback from
|
|
7237
7249
|
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
7238
7250
|
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
@@ -7474,10 +7486,14 @@ function tileCollisionGetData(pos, solidOnly=true)
|
|
|
7474
7486
|
// check all tile collision layers
|
|
7475
7487
|
for (const layer of tileCollisionLayers)
|
|
7476
7488
|
if (!solidOnly || layer.isSolid)
|
|
7477
|
-
if (pos.arrayCheck(layer.size))
|
|
7478
7489
|
{
|
|
7479
|
-
|
|
7480
|
-
|
|
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
|
+
}
|
|
7481
7497
|
}
|
|
7482
7498
|
return 0;
|
|
7483
7499
|
}
|
|
@@ -7752,7 +7768,7 @@ class TileLayer extends CanvasLayer
|
|
|
7752
7768
|
|
|
7753
7769
|
/** @property {TileInfo} - Default tile info for layer */
|
|
7754
7770
|
this.tileInfo = undefined;
|
|
7755
|
-
/** @property {Array<TileLayerData>} -
|
|
7771
|
+
/** @property {Array<TileLayerData>} - Array of tile data for the layer */
|
|
7756
7772
|
this.data = [];
|
|
7757
7773
|
/** @property {boolean} - Is this layer using a webgl texture? */
|
|
7758
7774
|
this.isUsingWebGL = false;
|
|
@@ -7837,7 +7853,7 @@ class TileLayer extends CanvasLayer
|
|
|
7837
7853
|
|
|
7838
7854
|
const size = this.drawSize || this.size;
|
|
7839
7855
|
const pos = this.pos.add(size.scale(.5));
|
|
7840
|
-
this.draw(pos,
|
|
7856
|
+
this.draw(pos, size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
7841
7857
|
}
|
|
7842
7858
|
|
|
7843
7859
|
/** Called after this layer is redrawn, does nothing by default */
|
|
@@ -7926,7 +7942,7 @@ class TileLayer extends CanvasLayer
|
|
|
7926
7942
|
const d = this.getData(layerPos);
|
|
7927
7943
|
if (!d || !d.tile) return;
|
|
7928
7944
|
|
|
7929
|
-
const tileInfo = this.tileInfo && this.tileInfo.
|
|
7945
|
+
const tileInfo = this.tileInfo && this.tileInfo.index(d.tile);
|
|
7930
7946
|
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
7931
7947
|
}
|
|
7932
7948
|
|
|
@@ -8220,8 +8236,8 @@ class TileCollisionLayer extends TileLayer
|
|
|
8220
8236
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
8221
8237
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
8222
8238
|
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
8223
|
-
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate
|
|
8224
|
-
* .5, 1 // randomness, collide
|
|
8239
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate
|
|
8240
|
+
* .5, 1 // randomness, collide
|
|
8225
8241
|
* );
|
|
8226
8242
|
*/
|
|
8227
8243
|
class ParticleEmitter extends EngineObject
|
|
@@ -8613,13 +8629,12 @@ class Particle
|
|
|
8613
8629
|
const hitLayer = tileCollisionTest(this.pos);
|
|
8614
8630
|
if (!testCollision(oldPos))
|
|
8615
8631
|
{
|
|
8616
|
-
// testCollision already invoked collideCallback with the
|
|
8617
|
-
// correct (this, data, pos) args; no need to re-check here.
|
|
8618
8632
|
// test which side we bounced off (or both if a corner)
|
|
8619
8633
|
const isBlockedX = testCollision(vec2(this.pos.x, oldPos.y));
|
|
8620
8634
|
const isBlockedY = testCollision(vec2(oldPos.x, this.pos.y));
|
|
8621
|
-
|
|
8622
|
-
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;
|
|
8623
8638
|
if (isBlockedX)
|
|
8624
8639
|
{
|
|
8625
8640
|
// move to previous X position and bounce
|
|
@@ -10268,7 +10283,8 @@ class NewgroundsPlugin
|
|
|
10268
10283
|
return;
|
|
10269
10284
|
}
|
|
10270
10285
|
debugMedals && LOG(xmlHttp.responseText);
|
|
10271
|
-
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); }
|
|
10272
10288
|
}
|
|
10273
10289
|
}
|
|
10274
10290
|
/**
|
|
@@ -10287,8 +10303,8 @@ class NewgroundsPlugin
|
|
|
10287
10303
|
let postProcess;
|
|
10288
10304
|
|
|
10289
10305
|
/////////////////////////////////////////////////////////////////////////
|
|
10290
|
-
/**
|
|
10291
|
-
*
|
|
10306
|
+
/**
|
|
10307
|
+
* Post Process Plugin - Applies a full screen shader to the rendered output
|
|
10292
10308
|
* @memberof PostProcess
|
|
10293
10309
|
*/
|
|
10294
10310
|
class PostProcessPlugin
|
|
@@ -10402,8 +10418,9 @@ class PostProcessPlugin
|
|
|
10402
10418
|
workCanvas.height = mainCanvasSize.y;
|
|
10403
10419
|
glCopyToContext(workContext);
|
|
10404
10420
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
10405
|
-
mainCanvas.width |= 0
|
|
10406
|
-
|
|
10421
|
+
mainCanvas.width |= 0; // setting size clears the main canvas
|
|
10422
|
+
|
|
10423
|
+
|
|
10407
10424
|
// copy work canvas to texture
|
|
10408
10425
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
|
|
10409
10426
|
}
|
|
@@ -11508,7 +11525,7 @@ class UISystemPlugin
|
|
|
11508
11525
|
}
|
|
11509
11526
|
|
|
11510
11527
|
/** Get other axis navigation direction from gamepad or keyboard
|
|
11511
|
-
* @return {
|
|
11528
|
+
* @return {number} */
|
|
11512
11529
|
getNavigationOtherDirection()
|
|
11513
11530
|
{
|
|
11514
11531
|
if (uiSystem.navigationDirection === 2)
|
|
@@ -11597,6 +11614,7 @@ class UISystemPlugin
|
|
|
11597
11614
|
uiSystem.navigationDirection = savedNavigationDirection;
|
|
11598
11615
|
inputClear();
|
|
11599
11616
|
}
|
|
11617
|
+
return confirmMenu;
|
|
11600
11618
|
}
|
|
11601
11619
|
}
|
|
11602
11620
|
|
|
@@ -12030,7 +12048,7 @@ class UITextInput extends UIObject
|
|
|
12030
12048
|
this.onClick();
|
|
12031
12049
|
}
|
|
12032
12050
|
|
|
12033
|
-
/** Stop editing the text
|
|
12051
|
+
/** Stop editing the text */
|
|
12034
12052
|
stopEditing()
|
|
12035
12053
|
{
|
|
12036
12054
|
if (!this.isKeyInputObject())
|
|
@@ -12193,7 +12211,7 @@ class UICheckbox extends UIObject
|
|
|
12193
12211
|
ASSERT(isStringLike(text), 'ui checkbox must be a string');
|
|
12194
12212
|
ASSERT(isColor(color), 'ui checkbox color must be a color');
|
|
12195
12213
|
|
|
12196
|
-
/** @property {boolean} -
|
|
12214
|
+
/** @property {boolean} - Is the checkbox currently checked? */
|
|
12197
12215
|
this.checked = checked;
|
|
12198
12216
|
// set properties
|
|
12199
12217
|
this.text = text;
|
|
@@ -12958,7 +12976,7 @@ class Box2dObject extends EngineObject
|
|
|
12958
12976
|
shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
|
|
12959
12977
|
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
12960
12978
|
fixtures.push(f);
|
|
12961
|
-
|
|
12979
|
+
edgePoints.push(points[i].copy());
|
|
12962
12980
|
}
|
|
12963
12981
|
this.edgeLoops.push(edgePoints);
|
|
12964
12982
|
return fixtures;
|
|
@@ -13024,7 +13042,7 @@ class Box2dObject extends EngineObject
|
|
|
13024
13042
|
/** Sets the position
|
|
13025
13043
|
* @param {Vector2} pos */
|
|
13026
13044
|
setPosition(pos)
|
|
13027
|
-
{ this.setTransform(pos, this.body.GetAngle()); }
|
|
13045
|
+
{ this.setTransform(pos, -this.body.GetAngle()); }
|
|
13028
13046
|
|
|
13029
13047
|
/** Sets the angle
|
|
13030
13048
|
* @param {number} angle */
|
|
@@ -13121,6 +13139,7 @@ class Box2dObject extends EngineObject
|
|
|
13121
13139
|
filter.set_categoryBits(categoryBits);
|
|
13122
13140
|
filter.set_maskBits(0xffff & ~ignoreCategoryBits);
|
|
13123
13141
|
filter.set_groupIndex(groupIndex);
|
|
13142
|
+
fixture.SetFilterData(filter); // applies and refilters contacts
|
|
13124
13143
|
});
|
|
13125
13144
|
}
|
|
13126
13145
|
|
|
@@ -13656,7 +13675,7 @@ class Box2dRevoluteJoint extends Box2dJoint
|
|
|
13656
13675
|
jointDef.set_bodyB(objectB.body);
|
|
13657
13676
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
13658
13677
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
13659
|
-
jointDef.set_referenceAngle(
|
|
13678
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
13660
13679
|
jointDef.set_collideConnected(collide);
|
|
13661
13680
|
super(jointDef);
|
|
13662
13681
|
}
|
|
@@ -13803,14 +13822,14 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
13803
13822
|
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
13804
13823
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
13805
13824
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
13806
|
-
const localAxisA =
|
|
13825
|
+
const localAxisA = objectA.worldToLocalVector(worldAxis);
|
|
13807
13826
|
const jointDef = new box2d.instance.b2PrismaticJointDef();
|
|
13808
13827
|
jointDef.set_bodyA(objectA.body);
|
|
13809
13828
|
jointDef.set_bodyB(objectB.body);
|
|
13810
13829
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
13811
13830
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
13812
13831
|
jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
|
|
13813
|
-
jointDef.set_referenceAngle(
|
|
13832
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
13814
13833
|
jointDef.set_collideConnected(collide);
|
|
13815
13834
|
super(jointDef);
|
|
13816
13835
|
}
|
|
@@ -13913,7 +13932,7 @@ class Box2dWheelJoint extends Box2dJoint
|
|
|
13913
13932
|
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
13914
13933
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
13915
13934
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
13916
|
-
const localAxisA =
|
|
13935
|
+
const localAxisA = objectA.worldToLocalVector(worldAxis);
|
|
13917
13936
|
const jointDef = new box2d.instance.b2WheelJointDef();
|
|
13918
13937
|
jointDef.set_bodyA(objectA.body);
|
|
13919
13938
|
jointDef.set_bodyB(objectB.body);
|
|
@@ -14013,7 +14032,7 @@ class Box2dWeldJoint extends Box2dJoint
|
|
|
14013
14032
|
jointDef.set_bodyB(objectB.body);
|
|
14014
14033
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
14015
14034
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
14016
|
-
jointDef.set_referenceAngle(
|
|
14035
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
14017
14036
|
jointDef.set_collideConnected(collide);
|
|
14018
14037
|
super(jointDef);
|
|
14019
14038
|
}
|
|
@@ -14460,7 +14479,7 @@ class Box2dPlugin
|
|
|
14460
14479
|
* @param {number} [lineWidth]
|
|
14461
14480
|
* @param {boolean} [useWebGL=glEnable]
|
|
14462
14481
|
* @param {CanvasRenderingContext2D} [context] */
|
|
14463
|
-
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1,
|
|
14482
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
|
|
14464
14483
|
{
|
|
14465
14484
|
const shape = box2d.castShapeObject(fixture.GetShape());
|
|
14466
14485
|
switch (shape.GetType())
|
|
@@ -14470,20 +14489,20 @@ class Box2dPlugin
|
|
|
14470
14489
|
let points = [];
|
|
14471
14490
|
for (let i=shape.GetVertexCount(); i--;)
|
|
14472
14491
|
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
14473
|
-
drawPoly(points, color, lineWidth, lineColor, pos, angle,
|
|
14492
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, false, context);
|
|
14474
14493
|
break;
|
|
14475
14494
|
}
|
|
14476
14495
|
case box2d.instance.b2Shape.e_circle:
|
|
14477
14496
|
{
|
|
14478
14497
|
const radius = shape.get_m_radius();
|
|
14479
|
-
drawCircle(pos, radius*2, color, lineWidth, lineColor,
|
|
14498
|
+
drawCircle(pos, radius*2, color, lineWidth, lineColor, useWebGL, false, context);
|
|
14480
14499
|
break;
|
|
14481
14500
|
}
|
|
14482
14501
|
case box2d.instance.b2Shape.e_edge:
|
|
14483
14502
|
{
|
|
14484
14503
|
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
14485
14504
|
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
14486
|
-
drawLine(v1, v2, lineWidth, lineColor, pos, angle,
|
|
14505
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebGL, false, context);
|
|
14487
14506
|
break;
|
|
14488
14507
|
}
|
|
14489
14508
|
}
|
|
@@ -15314,7 +15333,11 @@ function tweenProperty(target, propertyPath, start, end, duration = 1, options =
|
|
|
15314
15333
|
const callback = (value) =>
|
|
15315
15334
|
{
|
|
15316
15335
|
let obj = target;
|
|
15317
|
-
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
|
+
}
|
|
15318
15341
|
obj[lastKey] = value;
|
|
15319
15342
|
};
|
|
15320
15343
|
return new Tween(callback, start, end, duration, options);
|
|
@@ -16197,6 +16220,160 @@ class PathFinder
|
|
|
16197
16220
|
}
|
|
16198
16221
|
}
|
|
16199
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
|
+
|
|
16200
16377
|
|
|
16201
16378
|
/**
|
|
16202
16379
|
* LittleJS Module Export
|
|
@@ -16683,4 +16860,9 @@ export
|
|
|
16683
16860
|
// Path Finding
|
|
16684
16861
|
PathFinder,
|
|
16685
16862
|
PathFinderNode,
|
|
16863
|
+
|
|
16864
|
+
// Three.js
|
|
16865
|
+
threeJS,
|
|
16866
|
+
ThreeJSPlugin,
|
|
16867
|
+
ThreeJSObject,
|
|
16686
16868
|
}
|