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.release.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();
|
|
@@ -815,7 +819,7 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
|
815
819
|
* @param {number} value
|
|
816
820
|
* @return {boolean}
|
|
817
821
|
* @memberof Math */
|
|
818
|
-
function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
822
|
+
function isPowerOfTwo(value) { return value > 0 && !(value & (value - 1)); }
|
|
819
823
|
|
|
820
824
|
/** Returns the nearest power of two not less than the value
|
|
821
825
|
* @param {number} value
|
|
@@ -892,7 +896,7 @@ function isIntersecting(start, end, pos, size)
|
|
|
892
896
|
* @memberof Math */
|
|
893
897
|
function oscillate(frequency=1, amplitude=1, t=time, offset=0, type=0)
|
|
894
898
|
{
|
|
895
|
-
const phase = (offset + t*frequency
|
|
899
|
+
const phase = mod(offset + t*frequency, 1);
|
|
896
900
|
let value;
|
|
897
901
|
|
|
898
902
|
if (type === 1) // triangle
|
|
@@ -1828,7 +1832,7 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
|
1828
1832
|
class Timer
|
|
1829
1833
|
{
|
|
1830
1834
|
/** Create a timer object set time passed in
|
|
1831
|
-
* @param {number} [timeLeft] - How much time left before the timer
|
|
1835
|
+
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds (undefined = unset)
|
|
1832
1836
|
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
1833
1837
|
constructor(timeLeft, useRealTime=false)
|
|
1834
1838
|
{
|
|
@@ -2427,6 +2431,7 @@ let vibrateEnable = true;
|
|
|
2427
2431
|
let soundEnable = true;
|
|
2428
2432
|
|
|
2429
2433
|
/** Volume scale to apply to all sound, music and speech
|
|
2434
|
+
* Use setSoundVolume to also update the audio master gain immediately
|
|
2430
2435
|
* @type {number}
|
|
2431
2436
|
* @default
|
|
2432
2437
|
* @memberof Settings */
|
|
@@ -3506,6 +3511,14 @@ class TileInfo
|
|
|
3506
3511
|
return this.offset(new Vector2(x));
|
|
3507
3512
|
}
|
|
3508
3513
|
|
|
3514
|
+
/**
|
|
3515
|
+
* Returns a tile info for an index using this tile as reference
|
|
3516
|
+
* @param {Vector2|number} [index=0]
|
|
3517
|
+
* @return {TileInfo}
|
|
3518
|
+
*/
|
|
3519
|
+
index(index)
|
|
3520
|
+
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3521
|
+
|
|
3509
3522
|
/**
|
|
3510
3523
|
* Set this tile to use a full image in a texture info
|
|
3511
3524
|
* @param {TextureInfo} [textureInfo]
|
|
@@ -3519,14 +3532,6 @@ class TileInfo
|
|
|
3519
3532
|
this.bleed = this.padding = 0;
|
|
3520
3533
|
return this;
|
|
3521
3534
|
}
|
|
3522
|
-
|
|
3523
|
-
/**
|
|
3524
|
-
* Returns a tile info for an index using this tile as reference
|
|
3525
|
-
* @param {Vector2|number} [index=0]
|
|
3526
|
-
* @return {TileInfo}
|
|
3527
|
-
*/
|
|
3528
|
-
tile(index)
|
|
3529
|
-
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
3530
3535
|
}
|
|
3531
3536
|
|
|
3532
3537
|
/**
|
|
@@ -5224,7 +5229,10 @@ function inputInit()
|
|
|
5224
5229
|
{
|
|
5225
5230
|
inputData[0][e.code] = (inputData[0][e.code]&2) | 4;
|
|
5226
5231
|
if (inputWASDEmulateDirection)
|
|
5227
|
-
|
|
5232
|
+
{
|
|
5233
|
+
const remap = remapKey(e.code);
|
|
5234
|
+
inputData[0][remap] = (inputData[0][remap]&2) | 4;
|
|
5235
|
+
}
|
|
5228
5236
|
}
|
|
5229
5237
|
function remapKey(k)
|
|
5230
5238
|
{
|
|
@@ -5299,7 +5307,7 @@ function inputInit()
|
|
|
5299
5307
|
document.addEventListener('touchend', (e)=> handleTouch(e), { passive: false });
|
|
5300
5308
|
|
|
5301
5309
|
// handle all touch events the same way
|
|
5302
|
-
let wasTouching;
|
|
5310
|
+
let wasTouching, touchIdentifier;
|
|
5303
5311
|
function handleTouch(e)
|
|
5304
5312
|
{
|
|
5305
5313
|
if (!touchInputEnable) return;
|
|
@@ -5330,10 +5338,11 @@ function inputInit()
|
|
|
5330
5338
|
const pos = vec2(gameTouches[0].clientX, gameTouches[0].clientY);
|
|
5331
5339
|
const mousePosScreenLast = mousePosScreen;
|
|
5332
5340
|
mousePosScreen = mouseEventToScreen(pos);
|
|
5333
|
-
if (wasTouching)
|
|
5341
|
+
if (wasTouching && gameTouches[0].identifier === touchIdentifier)
|
|
5334
5342
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
5335
|
-
else
|
|
5343
|
+
else if (!wasTouching)
|
|
5336
5344
|
inputData[0][button] = 3;
|
|
5345
|
+
touchIdentifier = gameTouches[0].identifier;
|
|
5337
5346
|
}
|
|
5338
5347
|
else if (wasTouching)
|
|
5339
5348
|
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
@@ -6491,7 +6500,7 @@ class SoundInstance
|
|
|
6491
6500
|
* @param {number} [rate] - How quickly to speak
|
|
6492
6501
|
* @param {number} [pitch] - How much to change the pitch by
|
|
6493
6502
|
* @param {string} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
6494
|
-
* @return {SpeechSynthesisUtterance} - The utterance that was spoken
|
|
6503
|
+
* @return {SpeechSynthesisUtterance|undefined} - The utterance that was spoken, or undefined if speech is unavailable
|
|
6495
6504
|
* @memberof Audio */
|
|
6496
6505
|
function speak(text, volume=1, rate=1, pitch=1, language='')
|
|
6497
6506
|
{
|
|
@@ -6544,7 +6553,7 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
6544
6553
|
* @param {number} [pan] - How much to apply stereo panning
|
|
6545
6554
|
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
|
|
6546
6555
|
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
6547
|
-
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
6556
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing (disconnected when the sound ends)
|
|
6548
6557
|
* @param {number} [offset] - Offset in seconds to start playback from
|
|
6549
6558
|
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
6550
6559
|
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
@@ -6786,10 +6795,14 @@ function tileCollisionGetData(pos, solidOnly=true)
|
|
|
6786
6795
|
// check all tile collision layers
|
|
6787
6796
|
for (const layer of tileCollisionLayers)
|
|
6788
6797
|
if (!solidOnly || layer.isSolid)
|
|
6789
|
-
if (pos.arrayCheck(layer.size))
|
|
6790
6798
|
{
|
|
6791
|
-
|
|
6792
|
-
|
|
6799
|
+
// convert world pos to layer local space
|
|
6800
|
+
const layerPos = pos.subtract(layer.pos);
|
|
6801
|
+
if (layerPos.arrayCheck(layer.size))
|
|
6802
|
+
{
|
|
6803
|
+
const data = layer.getCollisionData(layerPos);
|
|
6804
|
+
if (data) return data;
|
|
6805
|
+
}
|
|
6793
6806
|
}
|
|
6794
6807
|
return 0;
|
|
6795
6808
|
}
|
|
@@ -7064,7 +7077,7 @@ class TileLayer extends CanvasLayer
|
|
|
7064
7077
|
|
|
7065
7078
|
/** @property {TileInfo} - Default tile info for layer */
|
|
7066
7079
|
this.tileInfo = undefined;
|
|
7067
|
-
/** @property {Array<TileLayerData>} -
|
|
7080
|
+
/** @property {Array<TileLayerData>} - Array of tile data for the layer */
|
|
7068
7081
|
this.data = [];
|
|
7069
7082
|
/** @property {boolean} - Is this layer using a webgl texture? */
|
|
7070
7083
|
this.isUsingWebGL = false;
|
|
@@ -7149,7 +7162,7 @@ class TileLayer extends CanvasLayer
|
|
|
7149
7162
|
|
|
7150
7163
|
const size = this.drawSize || this.size;
|
|
7151
7164
|
const pos = this.pos.add(size.scale(.5));
|
|
7152
|
-
this.draw(pos,
|
|
7165
|
+
this.draw(pos, size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
7153
7166
|
}
|
|
7154
7167
|
|
|
7155
7168
|
/** Called after this layer is redrawn, does nothing by default */
|
|
@@ -7238,7 +7251,7 @@ class TileLayer extends CanvasLayer
|
|
|
7238
7251
|
const d = this.getData(layerPos);
|
|
7239
7252
|
if (!d || !d.tile) return;
|
|
7240
7253
|
|
|
7241
|
-
const tileInfo = this.tileInfo && this.tileInfo.
|
|
7254
|
+
const tileInfo = this.tileInfo && this.tileInfo.index(d.tile);
|
|
7242
7255
|
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
7243
7256
|
}
|
|
7244
7257
|
|
|
@@ -7532,8 +7545,8 @@ class TileCollisionLayer extends TileLayer
|
|
|
7532
7545
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
7533
7546
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
7534
7547
|
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
7535
|
-
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate
|
|
7536
|
-
* .5, 1 // randomness, collide
|
|
7548
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate
|
|
7549
|
+
* .5, 1 // randomness, collide
|
|
7537
7550
|
* );
|
|
7538
7551
|
*/
|
|
7539
7552
|
class ParticleEmitter extends EngineObject
|
|
@@ -7925,13 +7938,12 @@ class Particle
|
|
|
7925
7938
|
const hitLayer = tileCollisionTest(this.pos);
|
|
7926
7939
|
if (!testCollision(oldPos))
|
|
7927
7940
|
{
|
|
7928
|
-
// testCollision already invoked collideCallback with the
|
|
7929
|
-
// correct (this, data, pos) args; no need to re-check here.
|
|
7930
7941
|
// test which side we bounced off (or both if a corner)
|
|
7931
7942
|
const isBlockedX = testCollision(vec2(this.pos.x, oldPos.y));
|
|
7932
7943
|
const isBlockedY = testCollision(vec2(oldPos.x, this.pos.y));
|
|
7933
|
-
|
|
7934
|
-
const
|
|
7944
|
+
// collide callback may hit where the layer test does not, so hitLayer can be undefined
|
|
7945
|
+
const hitRestitution = hitLayer ? max(restitution, hitLayer.restitution) : restitution;
|
|
7946
|
+
const hitFriction = hitLayer ? max(friction, hitLayer.friction) : friction;
|
|
7935
7947
|
if (isBlockedX)
|
|
7936
7948
|
{
|
|
7937
7949
|
// move to previous X position and bounce
|
|
@@ -9580,7 +9592,8 @@ class NewgroundsPlugin
|
|
|
9580
9592
|
return;
|
|
9581
9593
|
}
|
|
9582
9594
|
debugMedals && LOG(xmlHttp.responseText);
|
|
9583
|
-
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
9595
|
+
try { return xmlHttp.responseText && JSON.parse(xmlHttp.responseText); }
|
|
9596
|
+
catch(e) { debugMedals && LOG('newgrounds response is not valid JSON', e); }
|
|
9584
9597
|
}
|
|
9585
9598
|
}
|
|
9586
9599
|
/**
|
|
@@ -9599,8 +9612,8 @@ class NewgroundsPlugin
|
|
|
9599
9612
|
let postProcess;
|
|
9600
9613
|
|
|
9601
9614
|
/////////////////////////////////////////////////////////////////////////
|
|
9602
|
-
/**
|
|
9603
|
-
*
|
|
9615
|
+
/**
|
|
9616
|
+
* Post Process Plugin - Applies a full screen shader to the rendered output
|
|
9604
9617
|
* @memberof PostProcess
|
|
9605
9618
|
*/
|
|
9606
9619
|
class PostProcessPlugin
|
|
@@ -9714,8 +9727,9 @@ class PostProcessPlugin
|
|
|
9714
9727
|
workCanvas.height = mainCanvasSize.y;
|
|
9715
9728
|
glCopyToContext(workContext);
|
|
9716
9729
|
workContext.drawImage(mainCanvas, 0, 0);
|
|
9717
|
-
mainCanvas.width |= 0
|
|
9718
|
-
|
|
9730
|
+
mainCanvas.width |= 0; // setting size clears the main canvas
|
|
9731
|
+
|
|
9732
|
+
|
|
9719
9733
|
// copy work canvas to texture
|
|
9720
9734
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
|
|
9721
9735
|
}
|
|
@@ -10820,7 +10834,7 @@ class UISystemPlugin
|
|
|
10820
10834
|
}
|
|
10821
10835
|
|
|
10822
10836
|
/** Get other axis navigation direction from gamepad or keyboard
|
|
10823
|
-
* @return {
|
|
10837
|
+
* @return {number} */
|
|
10824
10838
|
getNavigationOtherDirection()
|
|
10825
10839
|
{
|
|
10826
10840
|
if (uiSystem.navigationDirection === 2)
|
|
@@ -10909,6 +10923,7 @@ class UISystemPlugin
|
|
|
10909
10923
|
uiSystem.navigationDirection = savedNavigationDirection;
|
|
10910
10924
|
inputClear();
|
|
10911
10925
|
}
|
|
10926
|
+
return confirmMenu;
|
|
10912
10927
|
}
|
|
10913
10928
|
}
|
|
10914
10929
|
|
|
@@ -11342,7 +11357,7 @@ class UITextInput extends UIObject
|
|
|
11342
11357
|
this.onClick();
|
|
11343
11358
|
}
|
|
11344
11359
|
|
|
11345
|
-
/** Stop editing the text
|
|
11360
|
+
/** Stop editing the text */
|
|
11346
11361
|
stopEditing()
|
|
11347
11362
|
{
|
|
11348
11363
|
if (!this.isKeyInputObject())
|
|
@@ -11505,7 +11520,7 @@ class UICheckbox extends UIObject
|
|
|
11505
11520
|
ASSERT(isStringLike(text), 'ui checkbox must be a string');
|
|
11506
11521
|
ASSERT(isColor(color), 'ui checkbox color must be a color');
|
|
11507
11522
|
|
|
11508
|
-
/** @property {boolean} -
|
|
11523
|
+
/** @property {boolean} - Is the checkbox currently checked? */
|
|
11509
11524
|
this.checked = checked;
|
|
11510
11525
|
// set properties
|
|
11511
11526
|
this.text = text;
|
|
@@ -12270,7 +12285,7 @@ class Box2dObject extends EngineObject
|
|
|
12270
12285
|
shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
|
|
12271
12286
|
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
12272
12287
|
fixtures.push(f);
|
|
12273
|
-
|
|
12288
|
+
edgePoints.push(points[i].copy());
|
|
12274
12289
|
}
|
|
12275
12290
|
this.edgeLoops.push(edgePoints);
|
|
12276
12291
|
return fixtures;
|
|
@@ -12336,7 +12351,7 @@ class Box2dObject extends EngineObject
|
|
|
12336
12351
|
/** Sets the position
|
|
12337
12352
|
* @param {Vector2} pos */
|
|
12338
12353
|
setPosition(pos)
|
|
12339
|
-
{ this.setTransform(pos, this.body.GetAngle()); }
|
|
12354
|
+
{ this.setTransform(pos, -this.body.GetAngle()); }
|
|
12340
12355
|
|
|
12341
12356
|
/** Sets the angle
|
|
12342
12357
|
* @param {number} angle */
|
|
@@ -12433,6 +12448,7 @@ class Box2dObject extends EngineObject
|
|
|
12433
12448
|
filter.set_categoryBits(categoryBits);
|
|
12434
12449
|
filter.set_maskBits(0xffff & ~ignoreCategoryBits);
|
|
12435
12450
|
filter.set_groupIndex(groupIndex);
|
|
12451
|
+
fixture.SetFilterData(filter); // applies and refilters contacts
|
|
12436
12452
|
});
|
|
12437
12453
|
}
|
|
12438
12454
|
|
|
@@ -12968,7 +12984,7 @@ class Box2dRevoluteJoint extends Box2dJoint
|
|
|
12968
12984
|
jointDef.set_bodyB(objectB.body);
|
|
12969
12985
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
12970
12986
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
12971
|
-
jointDef.set_referenceAngle(
|
|
12987
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
12972
12988
|
jointDef.set_collideConnected(collide);
|
|
12973
12989
|
super(jointDef);
|
|
12974
12990
|
}
|
|
@@ -13115,14 +13131,14 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
13115
13131
|
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
13116
13132
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
13117
13133
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
13118
|
-
const localAxisA =
|
|
13134
|
+
const localAxisA = objectA.worldToLocalVector(worldAxis);
|
|
13119
13135
|
const jointDef = new box2d.instance.b2PrismaticJointDef();
|
|
13120
13136
|
jointDef.set_bodyA(objectA.body);
|
|
13121
13137
|
jointDef.set_bodyB(objectB.body);
|
|
13122
13138
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
13123
13139
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
13124
13140
|
jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
|
|
13125
|
-
jointDef.set_referenceAngle(
|
|
13141
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
13126
13142
|
jointDef.set_collideConnected(collide);
|
|
13127
13143
|
super(jointDef);
|
|
13128
13144
|
}
|
|
@@ -13225,7 +13241,7 @@ class Box2dWheelJoint extends Box2dJoint
|
|
|
13225
13241
|
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
13226
13242
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
13227
13243
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
13228
|
-
const localAxisA =
|
|
13244
|
+
const localAxisA = objectA.worldToLocalVector(worldAxis);
|
|
13229
13245
|
const jointDef = new box2d.instance.b2WheelJointDef();
|
|
13230
13246
|
jointDef.set_bodyA(objectA.body);
|
|
13231
13247
|
jointDef.set_bodyB(objectB.body);
|
|
@@ -13325,7 +13341,7 @@ class Box2dWeldJoint extends Box2dJoint
|
|
|
13325
13341
|
jointDef.set_bodyB(objectB.body);
|
|
13326
13342
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
13327
13343
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
13328
|
-
jointDef.set_referenceAngle(
|
|
13344
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
13329
13345
|
jointDef.set_collideConnected(collide);
|
|
13330
13346
|
super(jointDef);
|
|
13331
13347
|
}
|
|
@@ -13772,7 +13788,7 @@ class Box2dPlugin
|
|
|
13772
13788
|
* @param {number} [lineWidth]
|
|
13773
13789
|
* @param {boolean} [useWebGL=glEnable]
|
|
13774
13790
|
* @param {CanvasRenderingContext2D} [context] */
|
|
13775
|
-
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1,
|
|
13791
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
|
|
13776
13792
|
{
|
|
13777
13793
|
const shape = box2d.castShapeObject(fixture.GetShape());
|
|
13778
13794
|
switch (shape.GetType())
|
|
@@ -13782,20 +13798,20 @@ class Box2dPlugin
|
|
|
13782
13798
|
let points = [];
|
|
13783
13799
|
for (let i=shape.GetVertexCount(); i--;)
|
|
13784
13800
|
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
13785
|
-
drawPoly(points, color, lineWidth, lineColor, pos, angle,
|
|
13801
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, false, context);
|
|
13786
13802
|
break;
|
|
13787
13803
|
}
|
|
13788
13804
|
case box2d.instance.b2Shape.e_circle:
|
|
13789
13805
|
{
|
|
13790
13806
|
const radius = shape.get_m_radius();
|
|
13791
|
-
drawCircle(pos, radius*2, color, lineWidth, lineColor,
|
|
13807
|
+
drawCircle(pos, radius*2, color, lineWidth, lineColor, useWebGL, false, context);
|
|
13792
13808
|
break;
|
|
13793
13809
|
}
|
|
13794
13810
|
case box2d.instance.b2Shape.e_edge:
|
|
13795
13811
|
{
|
|
13796
13812
|
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
13797
13813
|
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
13798
|
-
drawLine(v1, v2, lineWidth, lineColor, pos, angle,
|
|
13814
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebGL, false, context);
|
|
13799
13815
|
break;
|
|
13800
13816
|
}
|
|
13801
13817
|
}
|
|
@@ -14626,7 +14642,11 @@ function tweenProperty(target, propertyPath, start, end, duration = 1, options =
|
|
|
14626
14642
|
const callback = (value) =>
|
|
14627
14643
|
{
|
|
14628
14644
|
let obj = target;
|
|
14629
|
-
for (const k of parts)
|
|
14645
|
+
for (const k of parts)
|
|
14646
|
+
{
|
|
14647
|
+
obj = obj[k];
|
|
14648
|
+
ASSERT(obj != null, 'tweenProperty path does not resolve: ' + propertyPath);
|
|
14649
|
+
}
|
|
14630
14650
|
obj[lastKey] = value;
|
|
14631
14651
|
};
|
|
14632
14652
|
return new Tween(callback, start, end, duration, options);
|
|
@@ -15509,3 +15529,157 @@ class PathFinder
|
|
|
15509
15529
|
}
|
|
15510
15530
|
}
|
|
15511
15531
|
|
|
15532
|
+
/**
|
|
15533
|
+
* LittleJS Three.js Plugin
|
|
15534
|
+
* - Renders a three.js scene on a canvas behind the LittleJS canvases
|
|
15535
|
+
* - The three.js module is passed in by the user, nothing is bundled
|
|
15536
|
+
* - Keep canvasClearColor transparent so the 3D scene shows through
|
|
15537
|
+
* - Aligned camera mode locks the 3D camera to the LittleJS 2D camera
|
|
15538
|
+
* - ThreeJSObject lets LittleJS physics drive a three.js mesh
|
|
15539
|
+
* - Call new ThreeJSPlugin(THREE) in gameInit to set up
|
|
15540
|
+
* @namespace ThreeJS
|
|
15541
|
+
*/
|
|
15542
|
+
|
|
15543
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
15544
|
+
|
|
15545
|
+
/** Global ThreeJS plugin object
|
|
15546
|
+
* @type {ThreeJSPlugin}
|
|
15547
|
+
* @memberof ThreeJS */
|
|
15548
|
+
let threeJS;
|
|
15549
|
+
|
|
15550
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
15551
|
+
/**
|
|
15552
|
+
* ThreeJS Plugin - Renders a three.js scene behind the LittleJS canvas
|
|
15553
|
+
* @example
|
|
15554
|
+
* // in gameInit, with three.js loaded by the user
|
|
15555
|
+
* new ThreeJSPlugin(THREE);
|
|
15556
|
+
* threeJS.scene.add(new THREE.AmbientLight);
|
|
15557
|
+
* @memberof ThreeJS
|
|
15558
|
+
*/
|
|
15559
|
+
class ThreeJSPlugin
|
|
15560
|
+
{
|
|
15561
|
+
/** Set up the three.js rendering layer, call in gameInit
|
|
15562
|
+
* @param {Object} THREE - The three.js module, supplied by the user
|
|
15563
|
+
* @param {number} [cameraFOV] - Vertical field of view in degrees */
|
|
15564
|
+
constructor(THREE, cameraFOV=60)
|
|
15565
|
+
{
|
|
15566
|
+
ASSERT(!threeJS, 'ThreeJS plugin already initialized');
|
|
15567
|
+
threeJS = this;
|
|
15568
|
+
if (headlessMode) return;
|
|
15569
|
+
ASSERT(mainCanvas, 'ThreeJS plugin must be created after engineInit, call in gameInit');
|
|
15570
|
+
ASSERT(THREE && THREE.WebGLRenderer, 'three.js module must be passed in');
|
|
15571
|
+
|
|
15572
|
+
/** @property {Object} - The three.js module passed into the constructor */
|
|
15573
|
+
this.THREE = THREE;
|
|
15574
|
+
/** @property {Object} - The three.js renderer */
|
|
15575
|
+
this.renderer = new THREE.WebGLRenderer({antialias: true});
|
|
15576
|
+
/** @property {Object} - The three.js scene, add lights and meshes here */
|
|
15577
|
+
this.scene = new THREE.Scene();
|
|
15578
|
+
/** @property {Object} - The three.js perspective camera */
|
|
15579
|
+
this.camera = new THREE.PerspectiveCamera(cameraFOV, 1, .1, 1e3);
|
|
15580
|
+
/** @property {boolean} - Lock the camera to the LittleJS 2D camera so the z=0 plane matches world space */
|
|
15581
|
+
this.cameraAlign2D = true;
|
|
15582
|
+
|
|
15583
|
+
// insert the canvas below the engine canvases and match the layout
|
|
15584
|
+
const threeCanvas = this.renderer.domElement;
|
|
15585
|
+
const rootElement = mainCanvas.parentElement;
|
|
15586
|
+
rootElement.insertBefore(threeCanvas, rootElement.firstChild);
|
|
15587
|
+
threeCanvas.style.cssText = mainCanvas.style.cssText;
|
|
15588
|
+
|
|
15589
|
+
// render automatically each frame after the engine renders
|
|
15590
|
+
engineAddPlugin(undefined, ()=> this.render());
|
|
15591
|
+
}
|
|
15592
|
+
|
|
15593
|
+
/** Position the camera so the z=0 plane exactly matches LittleJS world space,
|
|
15594
|
+
* called automatically when cameraAlign2D is set */
|
|
15595
|
+
alignCamera2D()
|
|
15596
|
+
{
|
|
15597
|
+
const halfHeight = mainCanvasSize.y / 2 / cameraScale; // half visible height in world units
|
|
15598
|
+
const distance = halfHeight / tan(this.camera.fov/2 * PI/180);
|
|
15599
|
+
this.camera.position.set(cameraPos.x, cameraPos.y, distance);
|
|
15600
|
+
// reset all axes in case a free camera was used, littlejs angles are clockwise
|
|
15601
|
+
this.camera.rotation.set(0, 0, -cameraAngle);
|
|
15602
|
+
}
|
|
15603
|
+
|
|
15604
|
+
/** Sync the canvas layout and render the scene, called automatically each frame */
|
|
15605
|
+
render()
|
|
15606
|
+
{
|
|
15607
|
+
if (!this.renderer) return; // headless mode
|
|
15608
|
+
|
|
15609
|
+
// keep renderer size and css in sync with the LittleJS canvas
|
|
15610
|
+
const threeCanvas = this.renderer.domElement;
|
|
15611
|
+
if (threeCanvas.width != mainCanvasSize.x || threeCanvas.height != mainCanvasSize.y)
|
|
15612
|
+
{
|
|
15613
|
+
this.renderer.setSize(mainCanvasSize.x, mainCanvasSize.y, false);
|
|
15614
|
+
this.camera.aspect = mainCanvasSize.x / mainCanvasSize.y;
|
|
15615
|
+
this.camera.updateProjectionMatrix();
|
|
15616
|
+
}
|
|
15617
|
+
if (threeCanvas.style.cssText != mainCanvas.style.cssText)
|
|
15618
|
+
threeCanvas.style.cssText = mainCanvas.style.cssText;
|
|
15619
|
+
|
|
15620
|
+
if (this.cameraAlign2D)
|
|
15621
|
+
this.alignCamera2D();
|
|
15622
|
+
this.renderer.render(this.scene, this.camera);
|
|
15623
|
+
}
|
|
15624
|
+
}
|
|
15625
|
+
|
|
15626
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
15627
|
+
/**
|
|
15628
|
+
* ThreeJS Object - EngineObject that drives a three.js mesh
|
|
15629
|
+
* - LittleJS physics moves the object and the mesh follows automatically
|
|
15630
|
+
* - Destroying the object removes the mesh from the scene
|
|
15631
|
+
* @extends EngineObject
|
|
15632
|
+
* @memberof ThreeJS
|
|
15633
|
+
*/
|
|
15634
|
+
class ThreeJSObject extends EngineObject
|
|
15635
|
+
{
|
|
15636
|
+
/** Create an engine object that drives a three.js mesh
|
|
15637
|
+
* @param {Vector2} [pos] - World space position
|
|
15638
|
+
* @param {Vector2} [size] - World space size
|
|
15639
|
+
* @param {Object} [mesh] - The three.js object3d to drive
|
|
15640
|
+
* @param {number} [z] - Mesh height above the 2D plane */
|
|
15641
|
+
constructor(pos, size, mesh, z=0)
|
|
15642
|
+
{
|
|
15643
|
+
super(pos, size);
|
|
15644
|
+
ASSERT(threeJS, 'ThreeJS plugin must be initialized first');
|
|
15645
|
+
|
|
15646
|
+
/** @property {Object} - The three.js object3d this object drives */
|
|
15647
|
+
this.mesh = mesh;
|
|
15648
|
+
/** @property {number} - Mesh height above the 2D plane */
|
|
15649
|
+
this.z = z;
|
|
15650
|
+
if (mesh)
|
|
15651
|
+
{
|
|
15652
|
+
threeJS.scene.add(mesh);
|
|
15653
|
+
this.syncMesh();
|
|
15654
|
+
}
|
|
15655
|
+
}
|
|
15656
|
+
|
|
15657
|
+
/** Update the object and sync the mesh to its transform */
|
|
15658
|
+
update()
|
|
15659
|
+
{
|
|
15660
|
+
super.update();
|
|
15661
|
+
this.syncMesh();
|
|
15662
|
+
}
|
|
15663
|
+
|
|
15664
|
+
/** Copy this object's transform to the mesh */
|
|
15665
|
+
syncMesh()
|
|
15666
|
+
{
|
|
15667
|
+
if (!this.mesh) return;
|
|
15668
|
+
this.mesh.position.set(this.pos.x, this.pos.y, this.z);
|
|
15669
|
+
this.mesh.rotation.z = -this.angle; // littlejs angles are clockwise
|
|
15670
|
+
}
|
|
15671
|
+
|
|
15672
|
+
/** The mesh is this object's visual, the default 2D rendering is skipped */
|
|
15673
|
+
render() {}
|
|
15674
|
+
|
|
15675
|
+
/** Destroy this object and remove its mesh from the scene
|
|
15676
|
+
* @param {boolean} [immediate] */
|
|
15677
|
+
destroy(immediate)
|
|
15678
|
+
{
|
|
15679
|
+
if (this.destroyed) return;
|
|
15680
|
+
// note: frequently destroyed objects should also dispose geometry and material
|
|
15681
|
+
this.mesh && threeJS.scene.remove(this.mesh);
|
|
15682
|
+
super.destroy(immediate);
|
|
15683
|
+
}
|
|
15684
|
+
}
|
|
15685
|
+
|
package/package.json
CHANGED
package/plugins/box2d.js
CHANGED
|
@@ -345,7 +345,7 @@ class Box2dObject extends EngineObject
|
|
|
345
345
|
shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
|
|
346
346
|
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
347
347
|
fixtures.push(f);
|
|
348
|
-
|
|
348
|
+
edgePoints.push(points[i].copy());
|
|
349
349
|
}
|
|
350
350
|
this.edgeLoops.push(edgePoints);
|
|
351
351
|
return fixtures;
|
|
@@ -411,7 +411,7 @@ class Box2dObject extends EngineObject
|
|
|
411
411
|
/** Sets the position
|
|
412
412
|
* @param {Vector2} pos */
|
|
413
413
|
setPosition(pos)
|
|
414
|
-
{ this.setTransform(pos, this.body.GetAngle()); }
|
|
414
|
+
{ this.setTransform(pos, -this.body.GetAngle()); }
|
|
415
415
|
|
|
416
416
|
/** Sets the angle
|
|
417
417
|
* @param {number} angle */
|
|
@@ -508,6 +508,7 @@ class Box2dObject extends EngineObject
|
|
|
508
508
|
filter.set_categoryBits(categoryBits);
|
|
509
509
|
filter.set_maskBits(0xffff & ~ignoreCategoryBits);
|
|
510
510
|
filter.set_groupIndex(groupIndex);
|
|
511
|
+
fixture.SetFilterData(filter); // applies and refilters contacts
|
|
511
512
|
});
|
|
512
513
|
}
|
|
513
514
|
|
|
@@ -1043,7 +1044,7 @@ class Box2dRevoluteJoint extends Box2dJoint
|
|
|
1043
1044
|
jointDef.set_bodyB(objectB.body);
|
|
1044
1045
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
1045
1046
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
1046
|
-
jointDef.set_referenceAngle(
|
|
1047
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
1047
1048
|
jointDef.set_collideConnected(collide);
|
|
1048
1049
|
super(jointDef);
|
|
1049
1050
|
}
|
|
@@ -1190,14 +1191,14 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
1190
1191
|
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
1191
1192
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
1192
1193
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
1193
|
-
const localAxisA =
|
|
1194
|
+
const localAxisA = objectA.worldToLocalVector(worldAxis);
|
|
1194
1195
|
const jointDef = new box2d.instance.b2PrismaticJointDef();
|
|
1195
1196
|
jointDef.set_bodyA(objectA.body);
|
|
1196
1197
|
jointDef.set_bodyB(objectB.body);
|
|
1197
1198
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
1198
1199
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
1199
1200
|
jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
|
|
1200
|
-
jointDef.set_referenceAngle(
|
|
1201
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
1201
1202
|
jointDef.set_collideConnected(collide);
|
|
1202
1203
|
super(jointDef);
|
|
1203
1204
|
}
|
|
@@ -1300,7 +1301,7 @@ class Box2dWheelJoint extends Box2dJoint
|
|
|
1300
1301
|
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
1301
1302
|
const localAnchorA = objectA.worldToLocal(anchor);
|
|
1302
1303
|
const localAnchorB = objectB.worldToLocal(anchor);
|
|
1303
|
-
const localAxisA =
|
|
1304
|
+
const localAxisA = objectA.worldToLocalVector(worldAxis);
|
|
1304
1305
|
const jointDef = new box2d.instance.b2WheelJointDef();
|
|
1305
1306
|
jointDef.set_bodyA(objectA.body);
|
|
1306
1307
|
jointDef.set_bodyB(objectB.body);
|
|
@@ -1400,7 +1401,7 @@ class Box2dWeldJoint extends Box2dJoint
|
|
|
1400
1401
|
jointDef.set_bodyB(objectB.body);
|
|
1401
1402
|
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
|
|
1402
1403
|
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
|
|
1403
|
-
jointDef.set_referenceAngle(
|
|
1404
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
1404
1405
|
jointDef.set_collideConnected(collide);
|
|
1405
1406
|
super(jointDef);
|
|
1406
1407
|
}
|
|
@@ -1847,7 +1848,7 @@ class Box2dPlugin
|
|
|
1847
1848
|
* @param {number} [lineWidth]
|
|
1848
1849
|
* @param {boolean} [useWebGL=glEnable]
|
|
1849
1850
|
* @param {CanvasRenderingContext2D} [context] */
|
|
1850
|
-
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1,
|
|
1851
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
|
|
1851
1852
|
{
|
|
1852
1853
|
const shape = box2d.castShapeObject(fixture.GetShape());
|
|
1853
1854
|
switch (shape.GetType())
|
|
@@ -1857,20 +1858,20 @@ class Box2dPlugin
|
|
|
1857
1858
|
let points = [];
|
|
1858
1859
|
for (let i=shape.GetVertexCount(); i--;)
|
|
1859
1860
|
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
1860
|
-
drawPoly(points, color, lineWidth, lineColor, pos, angle,
|
|
1861
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, false, context);
|
|
1861
1862
|
break;
|
|
1862
1863
|
}
|
|
1863
1864
|
case box2d.instance.b2Shape.e_circle:
|
|
1864
1865
|
{
|
|
1865
1866
|
const radius = shape.get_m_radius();
|
|
1866
|
-
drawCircle(pos, radius*2, color, lineWidth, lineColor,
|
|
1867
|
+
drawCircle(pos, radius*2, color, lineWidth, lineColor, useWebGL, false, context);
|
|
1867
1868
|
break;
|
|
1868
1869
|
}
|
|
1869
1870
|
case box2d.instance.b2Shape.e_edge:
|
|
1870
1871
|
{
|
|
1871
1872
|
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
1872
1873
|
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
1873
|
-
drawLine(v1, v2, lineWidth, lineColor, pos, angle,
|
|
1874
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebGL, false, context);
|
|
1874
1875
|
break;
|
|
1875
1876
|
}
|
|
1876
1877
|
}
|
package/plugins/newgrounds.js
CHANGED
|
@@ -193,6 +193,7 @@ class NewgroundsPlugin
|
|
|
193
193
|
return;
|
|
194
194
|
}
|
|
195
195
|
debugMedals && LOG(xmlHttp.responseText);
|
|
196
|
-
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
196
|
+
try { return xmlHttp.responseText && JSON.parse(xmlHttp.responseText); }
|
|
197
|
+
catch(e) { debugMedals && LOG('newgrounds response is not valid JSON', e); }
|
|
197
198
|
}
|
|
198
199
|
}
|