littlejsengine 1.25.0 → 1.26.0
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/CHANGELOG.md +87 -0
- package/FAQ.md +49 -11
- package/README.md +6 -2
- package/REFERENCE.md +120 -18
- package/dist/littlejs.d.ts +464 -99
- package/dist/littlejs.esm.js +2853 -704
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +2844 -701
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +2658 -512
- package/package.json +4 -4
- package/plugins/box2d.js +110 -34
- package/plugins/gltf.js +74 -22
- package/plugins/lightSystem.js +410 -28
- package/plugins/math3d.js +4 -2
- package/plugins/medalSystem.js +19 -7
- package/plugins/newgrounds.js +4 -2
- package/plugins/parallax.js +1 -0
- package/plugins/particleEffects.js +44 -10
- package/plugins/pathFinder.js +9 -11
- package/plugins/pluginExport.js +9 -0
- package/plugins/postProcess.js +4 -2
- package/plugins/render3d.js +485 -2385
- package/plugins/render3dDebug.js +5 -8
- package/plugins/render3dEditor.js +81 -6
- package/plugins/render3dExtras.js +103 -31
- package/plugins/render3dLevel.js +53 -5
- package/plugins/render3dMesh.js +1158 -0
- package/plugins/render3dObject.js +1451 -0
- package/plugins/render3dVoxels.js +15 -3
- package/plugins/textureSheet.js +9 -8
- package/plugins/threejs.js +1 -1
- package/plugins/tweenSystem.js +76 -51
- package/plugins/uiSystem.js +37 -8
- package/plugins/wavedash.js +229 -0
- package/src/engine.js +184 -36
- package/src/engineAudio.js +37 -21
- package/src/engineBuild.mjs +6 -1
- package/src/engineDebug.js +22 -162
- package/src/engineDraw.js +139 -34
- package/src/engineEditor.js +79 -18
- package/src/engineExport.js +0 -3
- package/src/engineInput.js +27 -18
- package/src/engineMath.js +35 -14
- package/src/engineObject.js +60 -41
- package/src/engineParticles.js +58 -11
- package/src/engineRelease.js +1 -5
- package/src/engineSettings.js +48 -9
- package/src/engineTileLayer.js +11 -4
- package/src/engineUtilities.js +41 -9
- package/src/engineWebGL.js +37 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "littlejsengine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"description": "LittleJS - Tiny and Fast HTML5 Game Engine",
|
|
5
5
|
"main": "dist/littlejs.esm.js",
|
|
6
6
|
"types": "dist/littlejs.d.ts",
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"default": "./dist/littlejs.esm.js"
|
|
12
12
|
},
|
|
13
13
|
"./dist/*": "./dist/*",
|
|
14
|
+
"./plugins/*": "./plugins/*",
|
|
15
|
+
"./src/*": "./src/*",
|
|
14
16
|
"./package.json": "./package.json"
|
|
15
17
|
},
|
|
16
18
|
"repository": {
|
|
@@ -52,12 +54,10 @@
|
|
|
52
54
|
"test:browser": "node test/browser/run.mjs"
|
|
53
55
|
},
|
|
54
56
|
"engines": {
|
|
55
|
-
"node": "
|
|
57
|
+
"node": ">=22.12.0"
|
|
56
58
|
},
|
|
57
59
|
"devDependencies": {
|
|
58
60
|
"bestzip": "^2.2.1",
|
|
59
|
-
"electron": "^38.2.0",
|
|
60
|
-
"electron-packager": "^17.1.2",
|
|
61
61
|
"google-closure-compiler": "~20230502.0.0",
|
|
62
62
|
"jsdoc": "~4.0.2",
|
|
63
63
|
"roadroller": "~2.1.0",
|
package/plugins/box2d.js
CHANGED
|
@@ -35,7 +35,7 @@ let box2dDebug = false;
|
|
|
35
35
|
let box2dTempVectors;
|
|
36
36
|
function box2dTemp(v, slot=0)
|
|
37
37
|
{
|
|
38
|
-
ASSERT(isVector2(v));
|
|
38
|
+
ASSERT(isVector2(v), 'Box2D: expected a vec2', v);
|
|
39
39
|
const temp = (box2dTempVectors ||= [new box2d.instance.b2Vec2(), new box2d.instance.b2Vec2()])[slot];
|
|
40
40
|
temp.Set(v.x, v.y);
|
|
41
41
|
return temp;
|
|
@@ -88,6 +88,9 @@ function box2dWakeWithContacts(body)
|
|
|
88
88
|
edge.get_other().SetAwake(true);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// a number Box2D can take: it stops for good on NaN or Infinity, and a release build has no asserts to catch one first
|
|
92
|
+
const box2dFinite = (value, otherwise)=> isFinite(value) ? value : otherwise;
|
|
93
|
+
|
|
91
94
|
// wake both bodies of a joint whose length, spring, ratio or strength changed, a sleeping body would stay where it was
|
|
92
95
|
function box2dWakeJoint(joint)
|
|
93
96
|
{
|
|
@@ -147,6 +150,64 @@ function box2dDestroyGears(joint)
|
|
|
147
150
|
* @memberof Box2D */
|
|
148
151
|
function box2dSetDebug(enable) { box2dDebug = enable; }
|
|
149
152
|
|
|
153
|
+
// the EngineObject physics fields a Box2D body does not read, each with what moves the body instead; a debug build
|
|
154
|
+
// warns once a field when a game sets one, which did nothing and said nothing
|
|
155
|
+
const box2dUnusedFields = {
|
|
156
|
+
velocity: 'setLinearVelocity', angleVelocity: 'setAngularVelocity', damping: 'setLinearDamping',
|
|
157
|
+
angleDamping: 'setAngularDamping', mass: 'setMass or the density of its shapes',
|
|
158
|
+
friction: 'the friction of its shapes, given to addBox and the rest',
|
|
159
|
+
restitution: 'the restitution of its shapes, given to addBox and the rest'};
|
|
160
|
+
const box2dUnusedWarned = new Set, box2dUnusedStarts = new WeakMap;
|
|
161
|
+
|
|
162
|
+
// the values of those fields as numbers, a vector as its two, then gravityScale, kept for the object
|
|
163
|
+
function box2dUnusedFieldValues(o)
|
|
164
|
+
{
|
|
165
|
+
const values = [];
|
|
166
|
+
for (const name in box2dUnusedFields)
|
|
167
|
+
{
|
|
168
|
+
const value = o[name];
|
|
169
|
+
value instanceof Vector2 ? values.push(value.x, value.y) : values.push(value);
|
|
170
|
+
}
|
|
171
|
+
values.push(o.gravityScale);
|
|
172
|
+
return values;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// warn once a field
|
|
176
|
+
function box2dUnusedWarn(name, use)
|
|
177
|
+
{
|
|
178
|
+
if (box2dUnusedWarned.has(name)) return;
|
|
179
|
+
box2dUnusedWarned.add(name);
|
|
180
|
+
console.warn('Box2dObject.' + name + ' does nothing on a Box2D body, use ' + use);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// warn once a field of one set on a Box2dObject, and of gravityScale set on the field, not with setGravityScale;
|
|
184
|
+
// compared in place, as it runs for every body every step of a debug build
|
|
185
|
+
function box2dCheckUnusedFields(o)
|
|
186
|
+
{
|
|
187
|
+
const start = box2dUnusedStarts.get(o);
|
|
188
|
+
if (!start) return;
|
|
189
|
+
let i = 0;
|
|
190
|
+
for (const name in box2dUnusedFields)
|
|
191
|
+
{
|
|
192
|
+
const value = o[name];
|
|
193
|
+
let changed;
|
|
194
|
+
if (value instanceof Vector2)
|
|
195
|
+
{
|
|
196
|
+
changed = value.x !== start[i] || value.y !== start[i + 1];
|
|
197
|
+
i += 2;
|
|
198
|
+
}
|
|
199
|
+
else
|
|
200
|
+
changed = value !== start[i++];
|
|
201
|
+
changed && box2dUnusedWarn(name, box2dUnusedFields[name]);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// the body's own scale is a call into Box2D, asked only when the field changed since it was last asked,
|
|
205
|
+
// since setGravityScale sets both
|
|
206
|
+
if (o.gravityScale === start[i]) return;
|
|
207
|
+
start[i] = o.gravityScale;
|
|
208
|
+
Math.fround(o.gravityScale) === o.body.GetGravityScale() || box2dUnusedWarn('gravityScale', 'setGravityScale');
|
|
209
|
+
}
|
|
210
|
+
|
|
150
211
|
///////////////////////////////////////////////////////////////////////////////
|
|
151
212
|
/**
|
|
152
213
|
* Box2D Object - extend with your own custom physics objects
|
|
@@ -154,6 +215,10 @@ function box2dSetDebug(enable) { box2dDebug = enable; }
|
|
|
154
215
|
* - Provides interface for Box2D body and fixture functions
|
|
155
216
|
* - Each object can have multiple fixtures and joints
|
|
156
217
|
* - Angular values are clockwise like angle: angular velocity, torque, joint angles, limits and motor speeds
|
|
218
|
+
* - Box2D moves the body, so the EngineObject physics fields do nothing on it: velocity, angleVelocity, damping,
|
|
219
|
+
* angleDamping, mass, friction, restitution and gravityScale; use setLinearVelocity, setAngularVelocity,
|
|
220
|
+
* setLinearDamping, setAngularDamping, setMass, the friction and restitution of its shapes and setGravityScale.
|
|
221
|
+
* A debug build warns once when one of them is set
|
|
157
222
|
* @extends EngineObject
|
|
158
223
|
* @memberof Box2D
|
|
159
224
|
*/
|
|
@@ -196,6 +261,9 @@ class Box2dObject extends EngineObject
|
|
|
196
261
|
|
|
197
262
|
this.body.object = this; // link body to this object
|
|
198
263
|
box2d.objects.push(this); // keep track of all box2d objects
|
|
264
|
+
|
|
265
|
+
// a debug build notes the engine physics fields Box2D does not read, to warn when one is set
|
|
266
|
+
debug && box2dUnusedStarts.set(this, box2dUnusedFieldValues(this));
|
|
199
267
|
}
|
|
200
268
|
|
|
201
269
|
/** Destroy this object and its physics body
|
|
@@ -244,7 +312,7 @@ class Box2dObject extends EngineObject
|
|
|
244
312
|
this.drawFixtures(this.color, this.lineColor, this.lineWidth);
|
|
245
313
|
}
|
|
246
314
|
|
|
247
|
-
/**
|
|
315
|
+
/** Draw its fixtures for the debug view: yellow asleep, blue static, both when both */
|
|
248
316
|
renderDebugInfo()
|
|
249
317
|
{
|
|
250
318
|
const isAsleep = !this.getIsAwake();
|
|
@@ -253,7 +321,7 @@ class Box2dObject extends EngineObject
|
|
|
253
321
|
this.drawFixtures(color);
|
|
254
322
|
}
|
|
255
323
|
|
|
256
|
-
/**
|
|
324
|
+
/** Draw all this object's fixtures
|
|
257
325
|
* @param {Color} [color]
|
|
258
326
|
* @param {Color} [lineColor]
|
|
259
327
|
* @param {number} [lineWidth]
|
|
@@ -343,8 +411,8 @@ class Box2dObject extends EngineObject
|
|
|
343
411
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
344
412
|
|
|
345
413
|
// Box2D stops for good on a box with almost no area, like addPoly no fixture is made from one
|
|
346
|
-
ASSERT(size.x * size.y > 1e-6, 'box is too small for Box2D');
|
|
347
|
-
if (!(size.x * size.y > 1e-6)) return;
|
|
414
|
+
ASSERT(size.x * size.y > 1e-6 && isFinite(size.x * size.y), 'box is too small or too big for Box2D');
|
|
415
|
+
if (!(size.x * size.y > 1e-6) || !isFinite(size.x * size.y)) return;
|
|
348
416
|
|
|
349
417
|
const shape = new box2d.instance.b2PolygonShape();
|
|
350
418
|
shape.SetAsBox(size.x/2, size.y/2, box2dTemp(offset), -angle);
|
|
@@ -449,12 +517,13 @@ class Box2dObject extends EngineObject
|
|
|
449
517
|
* @param {number} [friction]
|
|
450
518
|
* @param {number} [restitution]
|
|
451
519
|
* @param {boolean} [isSensor]
|
|
452
|
-
* @return {Object} - The fixture made, Box2D's own */
|
|
520
|
+
* @return {Object|undefined} - The fixture made, Box2D's own, undefined for a circle too big to make */
|
|
453
521
|
addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
|
|
454
522
|
{
|
|
455
|
-
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
|
|
523
|
+
ASSERT(isNumber(diameter) && diameter>0 && isFinite(diameter), 'diameter must be a positive number');
|
|
456
524
|
ASSERT(isVector2(offset), 'offset must be a Vector2');
|
|
457
|
-
|
|
525
|
+
if (!isFinite(diameter)) return; // Box2D stops for good on it, no fixture is made
|
|
526
|
+
|
|
458
527
|
const shape = new box2d.instance.b2CircleShape();
|
|
459
528
|
shape.set_m_p(box2dTemp(offset));
|
|
460
529
|
shape.set_m_radius(diameter/2);
|
|
@@ -685,12 +754,14 @@ class Box2dObject extends EngineObject
|
|
|
685
754
|
setAngularVelocity(angularVelocity)
|
|
686
755
|
{ this.body.SetAngularVelocity(-angularVelocity); }
|
|
687
756
|
|
|
688
|
-
/** Sets the linear damping
|
|
757
|
+
/** Sets the linear damping, Box2D's: a rate, 0 none and larger slows it faster, with no upper limit, where the
|
|
758
|
+
* damping of an EngineObject is the fraction it keeps each frame and does nothing on a Box2dObject
|
|
689
759
|
* @param {number} damping */
|
|
690
760
|
setLinearDamping(damping)
|
|
691
761
|
{ this.body.SetLinearDamping(damping); }
|
|
692
762
|
|
|
693
|
-
/** Sets the angular damping
|
|
763
|
+
/** Sets the angular damping, Box2D's: a rate, 0 none and larger slows its turning faster, with no upper limit;
|
|
764
|
+
* angleDamping does nothing on a Box2dObject
|
|
694
765
|
* @param {number} damping */
|
|
695
766
|
setAngularDamping(damping)
|
|
696
767
|
{ this.body.SetAngularDamping(damping); }
|
|
@@ -767,7 +838,7 @@ class Box2dObject extends EngineObject
|
|
|
767
838
|
const f = Math.fround;
|
|
768
839
|
const oldInertia = f(data.get_I() - box2dCenterInertia(oldMass, center.get_x(), center.get_y()));
|
|
769
840
|
const inertia = momentOfInertia ?? oldInertia;
|
|
770
|
-
mass
|
|
841
|
+
mass = box2dFinite(mass ?? oldMass, oldMass);
|
|
771
842
|
const offset = box2dCenterInertia(mass > 0 ? mass : 1, cx, cy); // a mass of 0 or less is 1 to Box2D
|
|
772
843
|
const I = f(inertia + offset);
|
|
773
844
|
data.set_mass(mass);
|
|
@@ -1088,13 +1159,15 @@ class Box2dJoint
|
|
|
1088
1159
|
const bodyA = jointDef.get_bodyA(), bodyB = jointDef.get_bodyB();
|
|
1089
1160
|
const bothLive = !box2d.isNull(bodyA) && !box2d.isNull(bodyB);
|
|
1090
1161
|
ASSERT(bothLive, 'a joint needs two objects that are not destroyed');
|
|
1091
|
-
|
|
1162
|
+
const twoObjects = !bothLive || box2d.instance.getPointer(bodyA) !== box2d.instance.getPointer(bodyB);
|
|
1163
|
+
ASSERT(twoObjects, 'a joint needs two different objects');
|
|
1092
1164
|
|
|
1093
1165
|
/** @property {Object} - The Box2d joint, 0 once it is destroyed, as it is when either object is */
|
|
1094
1166
|
this.box2dJoint = 0;
|
|
1095
|
-
if (!bothLive)
|
|
1167
|
+
if (!bothLive || !twoObjects)
|
|
1096
1168
|
{
|
|
1097
|
-
// one of its objects is gone, so it is made destroyed, as it would be had it gone after
|
|
1169
|
+
// one of its objects is gone, so it is made destroyed, as it would be had it gone after; a joint of an
|
|
1170
|
+
// object to itself, which Box2D stops for good on, is made destroyed too in a release build
|
|
1098
1171
|
box2d.instance.destroy(jointDef);
|
|
1099
1172
|
return;
|
|
1100
1173
|
}
|
|
@@ -1206,7 +1279,7 @@ class Box2dTargetJoint extends Box2dJoint
|
|
|
1206
1279
|
|
|
1207
1280
|
/** Sets the joint frequency in Hertz, above 0, Box2D stops for good on 0
|
|
1208
1281
|
* @param {number} hz */
|
|
1209
|
-
setFrequency(hz) { this.box2dJoint.SetFrequency(max(hz, 1e-3)); box2dWakeJoint(this.box2dJoint); }
|
|
1282
|
+
setFrequency(hz) { this.box2dJoint.SetFrequency(max(box2dFinite(hz, 0), 1e-3)); box2dWakeJoint(this.box2dJoint); }
|
|
1210
1283
|
|
|
1211
1284
|
/** Gets the joint frequency in Hertz
|
|
1212
1285
|
* @return {number} */
|
|
@@ -1269,11 +1342,11 @@ class Box2dDistanceJoint extends Box2dJoint
|
|
|
1269
1342
|
* @return {number} */
|
|
1270
1343
|
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
1271
1344
|
|
|
1272
|
-
/** Set the
|
|
1345
|
+
/** Set how much the spring is damped, 0 for none and 1 to stop it bouncing
|
|
1273
1346
|
* @param {number} ratio */
|
|
1274
1347
|
setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); box2dWakeJoint(this.box2dJoint); }
|
|
1275
1348
|
|
|
1276
|
-
/** Get the
|
|
1349
|
+
/** Get how much the spring is damped, 0 for none and 1 to stop it bouncing
|
|
1277
1350
|
* @return {number} */
|
|
1278
1351
|
getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
|
|
1279
1352
|
}
|
|
@@ -1402,6 +1475,7 @@ class Box2dRevoluteJoint extends Box2dJoint
|
|
|
1402
1475
|
setLimits(min, max)
|
|
1403
1476
|
{
|
|
1404
1477
|
ASSERT(min <= max, 'the lower limit must not be above the upper one');
|
|
1478
|
+
min = box2dFinite(min, 0), max = box2dFinite(max, 0); // or on NaN, which passes the order check
|
|
1405
1479
|
if (min > max) [min, max] = [max, min]; // Box2D stops on them reversed
|
|
1406
1480
|
this.box2dJoint.SetLimits(-max, -min);
|
|
1407
1481
|
}
|
|
@@ -1426,7 +1500,7 @@ class Box2dRevoluteJoint extends Box2dJoint
|
|
|
1426
1500
|
* @param {number} torque */
|
|
1427
1501
|
setMaxMotorTorque(torque) { this.box2dJoint.SetMaxMotorTorque(torque); }
|
|
1428
1502
|
|
|
1429
|
-
/** Get the
|
|
1503
|
+
/** Get the most torque the motor can apply, a magnitude
|
|
1430
1504
|
* @return {number} */
|
|
1431
1505
|
getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
|
|
1432
1506
|
|
|
@@ -1604,6 +1678,7 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
1604
1678
|
setLimits(min, max)
|
|
1605
1679
|
{
|
|
1606
1680
|
ASSERT(min <= max, 'the lower limit must not be above the upper one');
|
|
1681
|
+
min = box2dFinite(min, 0), max = box2dFinite(max, 0); // or on NaN, which passes the order check
|
|
1607
1682
|
if (min > max) [min, max] = [max, min]; // Box2D stops on them reversed
|
|
1608
1683
|
this.box2dJoint.SetLimits(min, max);
|
|
1609
1684
|
}
|
|
@@ -1616,11 +1691,11 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
1616
1691
|
* @param {boolean} [enable] */
|
|
1617
1692
|
enableMotor(enable=true) { this.box2dJoint.EnableMotor(enable); }
|
|
1618
1693
|
|
|
1619
|
-
/** Set the motor
|
|
1694
|
+
/** Set the speed the motor drives the bodies apart along the axis, in meters per second
|
|
1620
1695
|
* @param {number} speed */
|
|
1621
1696
|
setMotorSpeed(speed) { this.box2dJoint.SetMotorSpeed(speed); }
|
|
1622
1697
|
|
|
1623
|
-
/** Get the motor
|
|
1698
|
+
/** Get the speed the motor drives the bodies apart along the axis, in meters per second
|
|
1624
1699
|
* @return {number} */
|
|
1625
1700
|
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
|
|
1626
1701
|
|
|
@@ -1712,7 +1787,7 @@ class Box2dWheelJoint extends Box2dJoint
|
|
|
1712
1787
|
* @param {number} torque */
|
|
1713
1788
|
setMaxMotorTorque(torque) { this.box2dJoint.SetMaxMotorTorque(torque); }
|
|
1714
1789
|
|
|
1715
|
-
/** Get the
|
|
1790
|
+
/** Get the most torque the wheel's motor can apply, a magnitude
|
|
1716
1791
|
* @return {number} */
|
|
1717
1792
|
getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
|
|
1718
1793
|
|
|
@@ -1729,11 +1804,11 @@ class Box2dWheelJoint extends Box2dJoint
|
|
|
1729
1804
|
* @return {number} */
|
|
1730
1805
|
getSpringFrequencyHz() { return this.box2dJoint.GetSpringFrequencyHz(); }
|
|
1731
1806
|
|
|
1732
|
-
/** Set the spring
|
|
1807
|
+
/** Set how much the suspension spring is damped, 0 for none and 1 to stop it bouncing
|
|
1733
1808
|
* @param {number} ratio */
|
|
1734
1809
|
setSpringDampingRatio(ratio) { this.box2dJoint.SetSpringDampingRatio(ratio); box2dWakeJoint(this.box2dJoint); }
|
|
1735
1810
|
|
|
1736
|
-
/** Get the spring
|
|
1811
|
+
/** Get how much the suspension spring is damped, 0 for none and 1 to stop it bouncing
|
|
1737
1812
|
* @return {number} */
|
|
1738
1813
|
getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
|
|
1739
1814
|
}
|
|
@@ -1791,11 +1866,11 @@ class Box2dWeldJoint extends Box2dJoint
|
|
|
1791
1866
|
* @return {number} */
|
|
1792
1867
|
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
1793
1868
|
|
|
1794
|
-
/** Set the
|
|
1869
|
+
/** Set how much the weld's spring is damped, 0 for none and 1 to stop it bouncing
|
|
1795
1870
|
* @param {number} ratio */
|
|
1796
1871
|
setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); box2dWakeJoint(this.box2dJoint); }
|
|
1797
1872
|
|
|
1798
|
-
/** Get the
|
|
1873
|
+
/** Get how much the weld's spring is damped, 0 for none and 1 to stop it bouncing
|
|
1799
1874
|
* @return {number} */
|
|
1800
1875
|
getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
|
|
1801
1876
|
}
|
|
@@ -1839,7 +1914,7 @@ class Box2dFrictionJoint extends Box2dJoint
|
|
|
1839
1914
|
|
|
1840
1915
|
/** Set the maximum friction force
|
|
1841
1916
|
* @param {number} force */
|
|
1842
|
-
setMaxForce(force) { this.box2dJoint.SetMaxForce(max(force, 0)); } // Box2D stops on a negative one
|
|
1917
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(max(box2dFinite(force, 0), 0)); } // Box2D stops on a negative one
|
|
1843
1918
|
|
|
1844
1919
|
/** Get the maximum friction force
|
|
1845
1920
|
* @return {number} */
|
|
@@ -1847,7 +1922,7 @@ class Box2dFrictionJoint extends Box2dJoint
|
|
|
1847
1922
|
|
|
1848
1923
|
/** Set the maximum friction torque
|
|
1849
1924
|
* @param {number} torque */
|
|
1850
|
-
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(max(torque, 0)); } // Box2D stops on a negative one
|
|
1925
|
+
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(max(box2dFinite(torque, 0), 0)); } // Box2D stops on a negative one
|
|
1851
1926
|
|
|
1852
1927
|
/** Get the maximum friction torque
|
|
1853
1928
|
* @return {number} */
|
|
@@ -1888,7 +1963,7 @@ class Box2dPulleyJoint extends Box2dJoint
|
|
|
1888
1963
|
jointDef.set_localAnchorA(box2dTemp(localAnchorA));
|
|
1889
1964
|
jointDef.set_localAnchorB(box2dTemp(localAnchorB));
|
|
1890
1965
|
ASSERT(ratio, 'a pulley ratio can not be 0');
|
|
1891
|
-
jointDef.set_ratio(ratio);
|
|
1966
|
+
jointDef.set_ratio(box2dFinite(ratio, 1) || 1); // Box2D stops for good on 0
|
|
1892
1967
|
jointDef.set_lengthA(groundAnchorA.distance(anchorA));
|
|
1893
1968
|
jointDef.set_lengthB(groundAnchorB.distance(anchorB));
|
|
1894
1969
|
jointDef.set_collideConnected(collide);
|
|
@@ -1949,11 +2024,11 @@ class Box2dMotorJoint extends Box2dJoint
|
|
|
1949
2024
|
super(jointDef);
|
|
1950
2025
|
}
|
|
1951
2026
|
|
|
1952
|
-
/** Set the target linear offset, in frame A, in meters
|
|
2027
|
+
/** Set the target linear offset, in frame A, in meters
|
|
1953
2028
|
* @param {Vector2} offset */
|
|
1954
2029
|
setLinearOffset(offset) { this.box2dJoint.SetLinearOffset(box2dTemp(offset)); }
|
|
1955
2030
|
|
|
1956
|
-
/** Get the target linear offset, in frame A, in meters
|
|
2031
|
+
/** Get the target linear offset, in frame A, in meters
|
|
1957
2032
|
* @return {Vector2} */
|
|
1958
2033
|
getLinearOffset() { return box2d.vec2From(this.box2dJoint.GetLinearOffset()); }
|
|
1959
2034
|
|
|
@@ -1967,7 +2042,7 @@ class Box2dMotorJoint extends Box2dJoint
|
|
|
1967
2042
|
|
|
1968
2043
|
/** Set the maximum force
|
|
1969
2044
|
* @param {number} force */
|
|
1970
|
-
setMaxForce(force) { this.box2dJoint.SetMaxForce(max(force, 0)); box2dWakeJoint(this.box2dJoint); } // Box2D stops on a negative one
|
|
2045
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(max(box2dFinite(force, 0), 0)); box2dWakeJoint(this.box2dJoint); } // Box2D stops on a negative one
|
|
1971
2046
|
|
|
1972
2047
|
/** Get the maximum force
|
|
1973
2048
|
* @return {number} */
|
|
@@ -1975,7 +2050,7 @@ class Box2dMotorJoint extends Box2dJoint
|
|
|
1975
2050
|
|
|
1976
2051
|
/** Set the maximum torque
|
|
1977
2052
|
* @param {number} torque */
|
|
1978
|
-
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(max(torque, 0)); box2dWakeJoint(this.box2dJoint); } // Box2D stops on a negative one
|
|
2053
|
+
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(max(box2dFinite(torque, 0), 0)); box2dWakeJoint(this.box2dJoint); } // Box2D stops on a negative one
|
|
1979
2054
|
|
|
1980
2055
|
/** Get the maximum torque
|
|
1981
2056
|
* @return {number} */
|
|
@@ -2329,7 +2404,7 @@ class Box2dPlugin
|
|
|
2329
2404
|
* @return {Vector2} */
|
|
2330
2405
|
vec2From(v)
|
|
2331
2406
|
{
|
|
2332
|
-
ASSERT(v instanceof box2d.instance.b2Vec2);
|
|
2407
|
+
ASSERT(v instanceof box2d.instance.b2Vec2, 'vec2From: expected a b2Vec2', v);
|
|
2333
2408
|
return new Vector2(v.get_x(), v.get_y());
|
|
2334
2409
|
}
|
|
2335
2410
|
|
|
@@ -2348,7 +2423,7 @@ class Box2dPlugin
|
|
|
2348
2423
|
* @return {Object} - A Box2D vector, its b2Vec2 */
|
|
2349
2424
|
vec2dTo(v)
|
|
2350
2425
|
{
|
|
2351
|
-
ASSERT(isVector2(v));
|
|
2426
|
+
ASSERT(isVector2(v), 'vec2dTo: expected a vec2', v);
|
|
2352
2427
|
return new box2d.instance.b2Vec2(v.x, v.y);
|
|
2353
2428
|
}
|
|
2354
2429
|
|
|
@@ -2445,6 +2520,7 @@ async function box2dInit()
|
|
|
2445
2520
|
{
|
|
2446
2521
|
if (o.body)
|
|
2447
2522
|
{
|
|
2523
|
+
debug && box2dCheckUnusedFields(o);
|
|
2448
2524
|
// moved in place, as the engine moves an EngineObject's pos, so what holds it follows the body;
|
|
2449
2525
|
// box2d uses reverse angle
|
|
2450
2526
|
const p = o.body.GetPosition();
|
package/plugins/gltf.js
CHANGED
|
@@ -73,6 +73,9 @@ class GLTFPart
|
|
|
73
73
|
this.emissiveMap = undefined;
|
|
74
74
|
/** @property {Color} - The emissiveFactor, which multiplies the emissive map */
|
|
75
75
|
this.emissiveMapColor = WHITE;
|
|
76
|
+
/** @property {number} - The material's roughnessFactor, 0 a mirror to 1 matte, 1 when it has none as the
|
|
77
|
+
* format says; the object createObject makes takes it as its roughness, which sets its shininess */
|
|
78
|
+
this.roughness = 1;
|
|
76
79
|
/** @property {GLTFSkin|undefined} - For a skinned mesh, what bends it: mesh is its resting pose, and the
|
|
77
80
|
* object createObject makes bends a copy of its own to each pose
|
|
78
81
|
* @type {GLTFSkin|undefined} */
|
|
@@ -134,6 +137,8 @@ class GLTFModel
|
|
|
134
137
|
/** @property {Array<GLTFAnimation>} - The animations, play one through createObject's GLTFObject
|
|
135
138
|
* @type {Array<GLTFAnimation>} */
|
|
136
139
|
this.animations = animations;
|
|
140
|
+
/** @type {{nodes: Array<Object>, parents: Array<number|undefined>, restInverse: Array<Matrix4>,
|
|
141
|
+
* restPose: Array<Matrix4|undefined>}|undefined} */
|
|
137
142
|
this.nodeTree = nodeTree; // each node's parent and resting place, for animation
|
|
138
143
|
this.modelMatrix = new Matrix4; // what center, fit and transform did to the parts, animation works through it
|
|
139
144
|
/** @property {Mesh} - Every part combined, each tinted with its material color; the texture is textureInfo,
|
|
@@ -303,6 +308,7 @@ class GLTFObject extends EngineObject3D
|
|
|
303
308
|
o.emissive = part.unlit ? 1 : 0;
|
|
304
309
|
o.normalMap = part.normalMap, o.normalScale = part.normalScale;
|
|
305
310
|
o.emissiveMap = part.emissiveMap, o.emissiveMapColor = part.emissiveMapColor.copy(); // its own, as color is
|
|
311
|
+
o.roughness = part.roughness;
|
|
306
312
|
const rest = !part.skin && model.nodeTree?.restPose?.[part.node];
|
|
307
313
|
if (rest)
|
|
308
314
|
{
|
|
@@ -313,6 +319,7 @@ class GLTFObject extends EngineObject3D
|
|
|
313
319
|
o.rotation3D = m.getRotation();
|
|
314
320
|
o.scale3D = m.getScale();
|
|
315
321
|
}
|
|
322
|
+
render3DShareSettings(o, this); // what is set on the model, the part's own material otherwise
|
|
316
323
|
this.addChild(o);
|
|
317
324
|
this.parts.push(o);
|
|
318
325
|
}
|
|
@@ -450,9 +457,9 @@ class GLTFObject extends EngineObject3D
|
|
|
450
457
|
* @memberof GLTF */
|
|
451
458
|
async function loadGLTF(url)
|
|
452
459
|
{
|
|
453
|
-
const response = await
|
|
460
|
+
const response = await loadFetch(url, 'loadGLTF');
|
|
454
461
|
if (!response.ok)
|
|
455
|
-
throw new Error(
|
|
462
|
+
throw new Error(`loadGLTF: could not load ${url}, ${response.status} ${response.statusText}`);
|
|
456
463
|
// the files beside it are beside where it came from, after any redirect; a blob or data url has nothing
|
|
457
464
|
// beside it, and a model in one is whole, or parsed with parseGLTF and a base of its own
|
|
458
465
|
const from = response.url || url;
|
|
@@ -477,7 +484,7 @@ async function loadGLTF(url)
|
|
|
477
484
|
* is fetched then
|
|
478
485
|
* - A file the model needs that is not found is named in the error, and an image that can not be read is named in
|
|
479
486
|
* a warning and left out
|
|
480
|
-
* @param {ArrayBuffer|Object
|
|
487
|
+
* @param {ArrayBuffer|ArrayBufferView|Object<string, any>|string} data - GLB bytes, or the glTF JSON as bytes, text or an object
|
|
481
488
|
* @param {string} [baseUrl] - Where the .bin and image files are, with its trailing slash; loadGLTF passes the file's folder
|
|
482
489
|
* @param {Map<string, Blob>} [files] - The files it refers to, by their paths, in place of fetching them
|
|
483
490
|
* @return {Promise<GLTFModel>}
|
|
@@ -489,7 +496,10 @@ async function loadGLTF(url)
|
|
|
489
496
|
* @memberof GLTF */
|
|
490
497
|
async function parseGLTF(data, baseUrl='', files)
|
|
491
498
|
{
|
|
492
|
-
|
|
499
|
+
// bytes as a typed array, a Node Buffer or a view into a bigger buffer, read as an ArrayBuffer of just them
|
|
500
|
+
if (ArrayBuffer.isView(data))
|
|
501
|
+
data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
502
|
+
let json = /** @type {any} */ (data), glbBuffer; // bytes, text or the parsed glTF, an object once parsed
|
|
493
503
|
if (data instanceof ArrayBuffer)
|
|
494
504
|
{
|
|
495
505
|
const view = new DataView(data);
|
|
@@ -784,39 +794,67 @@ function gltfSkinMeshCopy(mesh)
|
|
|
784
794
|
|
|
785
795
|
// bend a skinned mesh to a pose: each vertex its stored place moved by its four joints, weighted, and its normal by
|
|
786
796
|
// their normal matrices, each joint's matrix modelMatrix * its place now * its inverse bind; written into the
|
|
787
|
-
// mesh's own vectors, so a pose makes nothing for each vertex
|
|
797
|
+
// mesh's own vectors, so a pose makes nothing for each vertex, and once an upload has laid the mesh out for the GPU
|
|
798
|
+
// into that data too, with the radius and box the upload would measure, so it sends the data as it is
|
|
788
799
|
function gltfSkinApply(skin, worldOf, modelMatrix, mesh)
|
|
789
800
|
{
|
|
790
|
-
|
|
791
|
-
|
|
801
|
+
// each joint's point matrix, 12 numbers, then its normal matrix, 9, in one array the skin keeps
|
|
802
|
+
const jointCount = skin.joints.length, M = skin.jointMatrices ||= new Float32Array(jointCount * 21);
|
|
803
|
+
const m = gltfSkinScratch[0], n = gltfSkinScratch[1];
|
|
804
|
+
for (let j = 0; j < jointCount; ++j)
|
|
792
805
|
{
|
|
793
|
-
|
|
794
|
-
|
|
806
|
+
m.m.set(modelMatrix.m);
|
|
807
|
+
m.multiply(worldOf(skin.joints[j])).multiply(skin.inverseBind[j]);
|
|
808
|
+
n.m.set(m.m);
|
|
809
|
+
n.invert().transpose();
|
|
810
|
+
const a = m.m, b = n.m, o = j * 21;
|
|
811
|
+
M[o] = a[0], M[o+1] = a[1], M[o+2] = a[2], M[o+3] = a[4], M[o+4] = a[5], M[o+5] = a[6];
|
|
812
|
+
M[o+6] = a[8], M[o+7] = a[9], M[o+8] = a[10], M[o+9] = a[12], M[o+10] = a[13], M[o+11] = a[14];
|
|
813
|
+
M[o+12] = b[0], M[o+13] = b[1], M[o+14] = b[2], M[o+15] = b[4], M[o+16] = b[5], M[o+17] = b[6];
|
|
814
|
+
M[o+18] = b[8], M[o+19] = b[9], M[o+20] = b[10];
|
|
795
815
|
}
|
|
796
816
|
const {vertexJoints, vertexWeights, bindPoints, bindNormals} = skin, points = mesh.points, normals = mesh.normals;
|
|
797
|
-
|
|
817
|
+
// an indexed mesh's GPU vertex j is its point j, as upload lays it out
|
|
818
|
+
const layout = mesh.vertexLayout, count = points.length;
|
|
819
|
+
const floats = layout && mesh.indices && layout.pointCount === count ? new Float32Array(layout.data) : undefined;
|
|
820
|
+
let r = 0, x0 = Infinity, y0 = Infinity, z0 = Infinity, x1 = -Infinity, y1 = -Infinity, z1 = -Infinity;
|
|
821
|
+
for (let v = 0; v < count; ++v)
|
|
798
822
|
{
|
|
799
|
-
const px = bindPoints[
|
|
800
|
-
const qx = bindNormals[
|
|
823
|
+
const v3 = v*3, v4 = v*4, px = bindPoints[v3], py = bindPoints[v3+1], pz = bindPoints[v3+2];
|
|
824
|
+
const qx = bindNormals[v3], qy = bindNormals[v3+1], qz = bindNormals[v3+2];
|
|
801
825
|
let x = 0, y = 0, z = 0, nx = 0, ny = 0, nz = 0;
|
|
802
826
|
for (let k = 0; k < 4; ++k)
|
|
803
827
|
{
|
|
804
|
-
const w = vertexWeights[
|
|
828
|
+
const w = vertexWeights[v4+k];
|
|
805
829
|
if (!w) continue;
|
|
806
|
-
const
|
|
807
|
-
x += w * (
|
|
808
|
-
y += w * (
|
|
809
|
-
z += w * (
|
|
810
|
-
nx += w * (
|
|
811
|
-
ny += w * (
|
|
812
|
-
nz += w * (
|
|
830
|
+
const o = vertexJoints[v4+k] * 21;
|
|
831
|
+
x += w * (M[o]*px + M[o+3]*py + M[o+6]*pz + M[o+9]);
|
|
832
|
+
y += w * (M[o+1]*px + M[o+4]*py + M[o+7]*pz + M[o+10]);
|
|
833
|
+
z += w * (M[o+2]*px + M[o+5]*py + M[o+8]*pz + M[o+11]);
|
|
834
|
+
nx += w * (M[o+12]*qx + M[o+15]*qy + M[o+18]*qz);
|
|
835
|
+
ny += w * (M[o+13]*qx + M[o+16]*qy + M[o+19]*qz);
|
|
836
|
+
nz += w * (M[o+14]*qx + M[o+17]*qy + M[o+20]*qz);
|
|
813
837
|
}
|
|
814
838
|
const p = points[v], normal = normals[v], l = (nx*nx + ny*ny + nz*nz) ** .5 || 1;
|
|
815
839
|
p.x = x, p.y = y, p.z = z;
|
|
816
840
|
normal.x = nx / l, normal.y = ny / l, normal.z = nz / l;
|
|
841
|
+
if (!floats) continue;
|
|
842
|
+
const at = v * RENDER3D_VERTEX_FLOATS;
|
|
843
|
+
floats[at] = x, floats[at+1] = y, floats[at+2] = z;
|
|
844
|
+
floats[at+3] = normal.x, floats[at+4] = normal.y, floats[at+5] = normal.z;
|
|
845
|
+
r = max(r, x*x + y*y + z*z);
|
|
846
|
+
x0 = min(x0, x), y0 = min(y0, y), z0 = min(z0, z);
|
|
847
|
+
x1 = max(x1, x), y1 = max(y1, y), z1 = max(z1, z);
|
|
848
|
+
}
|
|
849
|
+
if (floats)
|
|
850
|
+
{
|
|
851
|
+
mesh.radius = r ** .5;
|
|
852
|
+
mesh.bounds = count ? {min: vec3(x0, y0, z0), max: vec3(x1, y1, z1)} : {min: vec3(), max: vec3()};
|
|
853
|
+
mesh.vertexDataPacked = true;
|
|
817
854
|
}
|
|
818
855
|
mesh.dirty = true;
|
|
819
856
|
}
|
|
857
|
+
const gltfSkinScratch = [new Matrix4, new Matrix4]; // a joint's matrix and its normal matrix, as it is worked out
|
|
820
858
|
|
|
821
859
|
// a node's translation, rotation and scale as arrays to animate, copies so the file's stay as they rest
|
|
822
860
|
function gltfNodeTRS(node)
|
|
@@ -977,7 +1015,7 @@ function gltfFetch(uri, baseUrl, files)
|
|
|
977
1015
|
const page = typeof location !== 'undefined' && location.href;
|
|
978
1016
|
const base = page ? new URL(baseUrl, page) : /^[a-z][a-z0-9+.-]*:/i.test(baseUrl) ? baseUrl : undefined;
|
|
979
1017
|
const url = base ? new URL(uri, base).href : baseUrl + uri;
|
|
980
|
-
return
|
|
1018
|
+
return loadFetch(url, 'glTF').then(r=>
|
|
981
1019
|
{
|
|
982
1020
|
if (!r.ok) throw new Error('glTF needs ' + uri + ', not found at ' + url);
|
|
983
1021
|
return r;
|
|
@@ -1022,6 +1060,9 @@ function gltfAccessor(json, buffers, index)
|
|
|
1022
1060
|
if (view)
|
|
1023
1061
|
{
|
|
1024
1062
|
const buffer = buffers[view.buffer], offset = (view.byteOffset || 0) + (a.byteOffset || 0);
|
|
1063
|
+
// the format keeps values on their size, a typed array can not read them off it
|
|
1064
|
+
if (offset % size || stride % size)
|
|
1065
|
+
throw new Error('glTF accessor ' + index + ' is not aligned to its ' + size + ' byte values');
|
|
1025
1066
|
if (stride === components * size)
|
|
1026
1067
|
out.set(new Type(buffer, offset, a.count * components)); // packed, one view over all of it
|
|
1027
1068
|
else
|
|
@@ -1036,6 +1077,13 @@ function gltfAccessor(json, buffers, index)
|
|
|
1036
1077
|
const indexView = json.bufferViews?.[indices.bufferView], valueView = json.bufferViews?.[values.bufferView];
|
|
1037
1078
|
if (!IndexType || !indexView || !valueView)
|
|
1038
1079
|
throw new Error('glTF sparse accessor is missing its indices or values');
|
|
1080
|
+
// its indices and values in their buffers and on their sizes, before typed arrays are made over them
|
|
1081
|
+
const indexAt = (indexView.byteOffset || 0) + (indices.byteOffset || 0);
|
|
1082
|
+
const valueAt = (valueView.byteOffset || 0) + (values.byteOffset || 0);
|
|
1083
|
+
if (indexAt % IndexType.BYTES_PER_ELEMENT || valueAt % size ||
|
|
1084
|
+
!(indexAt + sparse.count * IndexType.BYTES_PER_ELEMENT <= (buffers[indexView.buffer]?.byteLength ?? 0)) ||
|
|
1085
|
+
!(valueAt + sparse.count * components * size <= (buffers[valueView.buffer]?.byteLength ?? 0)))
|
|
1086
|
+
throw new Error('glTF sparse accessor ' + index + ' is not aligned or reaches past its buffer');
|
|
1039
1087
|
const at = new IndexType(buffers[indexView.buffer], (indexView.byteOffset || 0) + (indices.byteOffset || 0), sparse.count);
|
|
1040
1088
|
const data = new Type(buffers[valueView.buffer], (valueView.byteOffset || 0) + (values.byteOffset || 0), sparse.count * components);
|
|
1041
1089
|
for (let i = 0; i < sparse.count; ++i)
|
|
@@ -1067,7 +1115,10 @@ function gltfPart(json, buffers, textures, primitive, matrix, name)
|
|
|
1067
1115
|
};
|
|
1068
1116
|
const points = read(attributes.POSITION, (d, k)=> vec3(d[k], d[k+1], d[k+2]));
|
|
1069
1117
|
const normals = attributes.NORMAL !== undefined ? read(attributes.NORMAL, (d, k)=> vec3(d[k], d[k+1], d[k+2])) : undefined;
|
|
1070
|
-
// the uv set the base color texture names, moved by its KHR_texture_transform once here, offset + rotation * scale
|
|
1118
|
+
// the uv set the base color texture names, moved by its KHR_texture_transform once here, offset + rotation * scale,
|
|
1119
|
+
// the rotation turning (u, v) to (cos u + sin v, cos v - sin u), counter-clockwise in glTF's uv space with v down,
|
|
1120
|
+
// as the extension's text says and as Khronos's sample renderer and three.js turn it, which models are made to
|
|
1121
|
+
// look right in; the GLSL sample in the extension's readme, read column major, turns the other way
|
|
1071
1122
|
const material = json.materials?.[primitive.material] || {}, pbr = material.pbrMetallicRoughness || {};
|
|
1072
1123
|
const textureRef = pbr.baseColorTexture, uvTransform = textureRef?.extensions?.KHR_texture_transform;
|
|
1073
1124
|
const uvAccessor = attributes['TEXCOORD_' + (uvTransform?.texCoord ?? textureRef?.texCoord ?? 0)];
|
|
@@ -1108,6 +1159,7 @@ function gltfPart(json, buffers, textures, primitive, matrix, name)
|
|
|
1108
1159
|
const normalRef = material.normalTexture, emissiveRef = material.emissiveTexture;
|
|
1109
1160
|
part.normalMap = normalRef && textures[normalRef.index];
|
|
1110
1161
|
part.normalScale = normalRef?.scale ?? 1;
|
|
1162
|
+
part.roughness = pbr.roughnessFactor ?? 1; // a roughness map is not read
|
|
1111
1163
|
const [er, eg, eb] = material.emissiveFactor || [0, 0, 0];
|
|
1112
1164
|
if (er || eg || eb)
|
|
1113
1165
|
{
|