emeraldengine 3.0.0 → 3.1.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.
Files changed (103) hide show
  1. package/README.md +1498 -1659
  2. package/dist/types/index.d.ts +4 -1
  3. package/dist/types/src/Animator.d.ts +1 -1
  4. package/dist/types/src/CollisionLayers.d.ts +2 -2
  5. package/dist/types/src/Color.d.ts +1 -0
  6. package/dist/types/src/Drawable.d.ts +1 -1
  7. package/dist/types/src/EmeraldDB.d.ts +2 -2
  8. package/dist/types/src/InstancedTexture.d.ts +17 -2
  9. package/dist/types/src/Material.d.ts +2 -2
  10. package/dist/types/src/MathUtils.d.ts +2 -1
  11. package/dist/types/src/ParticleEmitter.d.ts +1 -1
  12. package/dist/types/src/Physics.d.ts +148 -18
  13. package/dist/types/src/Scene.d.ts +1 -1
  14. package/dist/types/src/Shaders.d.ts +2 -2
  15. package/dist/types/src/Tilemap.d.ts +1 -1
  16. package/dist/types/src/UI.d.ts +1 -1
  17. package/dist/types/src/components/Behaviour.d.ts +2 -2
  18. package/dist/types/src/components/Collider.d.ts +7 -1
  19. package/dist/types/src/components/GameObject.d.ts +2 -2
  20. package/dist/types/src/components/PolygonCollider.d.ts +33 -0
  21. package/dist/types/src/components/RigidBody.d.ts +281 -8
  22. package/dist/types/src/importers/Aseprite.d.ts +2 -2
  23. package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
  24. package/dist/types/src/importers/TiledMap.d.ts +1 -1
  25. package/dist/types/src/managers/EventManager.d.ts +1 -1
  26. package/dist/types/src/managers/Gamepad.d.ts +102 -0
  27. package/dist/types/src/managers/InputManager.d.ts +93 -2
  28. package/dist/types/src/managers/NetworkManager.d.ts +2 -2
  29. package/dist/types/src/managers/RenderStats.d.ts +1 -1
  30. package/dist/types/src/managers/TextureManager.d.ts +1 -1
  31. package/dist/types/src/physics/AABB.d.ts +92 -0
  32. package/dist/types/src/physics/Body.d.ts +435 -0
  33. package/dist/types/src/physics/BodyType.d.ts +6 -0
  34. package/dist/types/src/physics/BroadPhase.d.ts +210 -0
  35. package/dist/types/src/physics/Collision.d.ts +102 -0
  36. package/dist/types/src/physics/Contact.d.ts +206 -0
  37. package/dist/types/src/physics/ContactSolver.d.ts +108 -0
  38. package/dist/types/src/physics/Distance.d.ts +54 -0
  39. package/dist/types/src/physics/DistanceJoint.d.ts +90 -0
  40. package/dist/types/src/physics/Fixture.d.ts +221 -0
  41. package/dist/types/src/physics/Island.d.ts +52 -0
  42. package/dist/types/src/physics/Joint.d.ts +59 -0
  43. package/dist/types/src/physics/Math2D.d.ts +371 -0
  44. package/dist/types/src/physics/RevoluteJoint.d.ts +119 -0
  45. package/dist/types/src/physics/Settings.d.ts +22 -0
  46. package/dist/types/src/physics/Shapes.d.ts +207 -0
  47. package/dist/types/src/physics/TimeOfImpact.d.ts +22 -0
  48. package/dist/types/src/physics/World.d.ts +274 -0
  49. package/dist/types/src/physics/index.d.ts +34 -0
  50. package/index.js +6 -0
  51. package/package.json +2 -3
  52. package/src/Animator.js +1 -1
  53. package/src/CollisionLayers.js +3 -3
  54. package/src/Color.js +8 -0
  55. package/src/Drawable.js +1 -1
  56. package/src/Emerald.js +1 -1
  57. package/src/EmeraldDB.js +2 -2
  58. package/src/InstancedTexture.js +57 -9
  59. package/src/Material.js +2 -2
  60. package/src/MathUtils.js +2 -1
  61. package/src/ParticleEmitter.js +1 -1
  62. package/src/Physics.js +270 -60
  63. package/src/Scene.js +1 -1
  64. package/src/Shaders.js +20 -20
  65. package/src/Tilemap.js +1 -1
  66. package/src/UI.js +1 -1
  67. package/src/components/Behaviour.js +2 -2
  68. package/src/components/BoxCollider.js +7 -9
  69. package/src/components/BoxColliderDebug.js +3 -4
  70. package/src/components/CircleCollider.js +7 -9
  71. package/src/components/CircleColliderDebug.js +3 -2
  72. package/src/components/Collider.js +13 -3
  73. package/src/components/GameObject.js +2 -2
  74. package/src/components/PolygonCollider.js +55 -0
  75. package/src/components/RigidBody.js +441 -14
  76. package/src/importers/Aseprite.js +2 -2
  77. package/src/importers/ForgeLevel.js +581 -0
  78. package/src/importers/TiledMap.js +1 -1
  79. package/src/managers/EventManager.js +1 -1
  80. package/src/managers/Gamepad.js +126 -0
  81. package/src/managers/InputManager.js +129 -3
  82. package/src/managers/NetworkManager.js +2 -2
  83. package/src/managers/RenderStats.js +1 -1
  84. package/src/managers/TextureManager.js +1 -1
  85. package/src/physics/AABB.js +207 -0
  86. package/src/physics/Body.js +862 -0
  87. package/src/physics/BodyType.js +16 -0
  88. package/src/physics/BroadPhase.js +641 -0
  89. package/src/physics/Collision.js +534 -0
  90. package/src/physics/Contact.js +500 -0
  91. package/src/physics/ContactSolver.js +526 -0
  92. package/src/physics/Distance.js +403 -0
  93. package/src/physics/DistanceJoint.js +227 -0
  94. package/src/physics/Fixture.js +346 -0
  95. package/src/physics/Island.js +203 -0
  96. package/src/physics/Joint.js +78 -0
  97. package/src/physics/Math2D.js +573 -0
  98. package/src/physics/RevoluteJoint.js +278 -0
  99. package/src/physics/Settings.js +78 -0
  100. package/src/physics/Shapes.js +549 -0
  101. package/src/physics/TimeOfImpact.js +87 -0
  102. package/src/physics/World.js +731 -0
  103. package/src/physics/index.js +79 -0
@@ -0,0 +1,403 @@
1
+ import { Vec2, Transform2, EPSILON } from "./Math2D.js";
2
+ import { ShapeType } from "./Shapes.js";
3
+
4
+ /**
5
+ * @class DistanceProxy
6
+ * @description A convex shape reduced to what GJK needs: a point cloud plus a
7
+ * skin radius. Circles collapse to a single point with a large radius; polygons
8
+ * keep their corners and carry the small polygon skin.
9
+ */
10
+ class DistanceProxy {
11
+ constructor() {
12
+ this.vertices = [];
13
+ this.radius = 0;
14
+ }
15
+
16
+ /**
17
+ * @method set
18
+ * @description Fills this proxy from a shape.
19
+ * @param {Shape} shape - A CircleShape or PolygonShape
20
+ * @returns {DistanceProxy} - this
21
+ */
22
+ set(shape) {
23
+ if (shape.type === ShapeType.CIRCLE) {
24
+ this.vertices = [shape.p];
25
+ } else {
26
+ this.vertices = shape.vertices;
27
+ }
28
+ this.radius = shape.radius;
29
+ return this;
30
+ }
31
+
32
+ /**
33
+ * @method getSupport
34
+ * @description Index of the vertex furthest along a direction.
35
+ * @param {Object} d - Direction in the proxy's local frame
36
+ * @returns {number}
37
+ */
38
+ getSupport(d) {
39
+ let best = 0;
40
+ let bestValue = this.vertices[0].x * d.x + this.vertices[0].y * d.y;
41
+ for (let i = 1; i < this.vertices.length; i++) {
42
+ const value = this.vertices[i].x * d.x + this.vertices[i].y * d.y;
43
+ if (value > bestValue) {
44
+ bestValue = value;
45
+ best = i;
46
+ }
47
+ }
48
+ return best;
49
+ }
50
+
51
+ /**
52
+ * @method getMaxExtent
53
+ * @description Distance from the local origin to the furthest vertex, plus
54
+ * the skin. Continuous collision uses it to bound how fast a rotating body's
55
+ * surface can approach another.
56
+ * @returns {number}
57
+ */
58
+ getMaxExtent() {
59
+ let maxSq = 0;
60
+ for (const v of this.vertices) {
61
+ const sq = v.x * v.x + v.y * v.y;
62
+ if (sq > maxSq) maxSq = sq;
63
+ }
64
+ return Math.sqrt(maxSq) + this.radius;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * @class SimplexVertex
70
+ * @description One vertex of the GJK simplex: a point on the Minkowski
71
+ * difference along with the witness points that produced it.
72
+ * @private
73
+ */
74
+ class SimplexVertex {
75
+ constructor() {
76
+ this.wA = new Vec2();
77
+ this.wB = new Vec2();
78
+ this.w = new Vec2();
79
+ this.a = 0;
80
+ this.indexA = 0;
81
+ this.indexB = 0;
82
+ }
83
+
84
+ copy(other) {
85
+ this.wA.copy(other.wA);
86
+ this.wB.copy(other.wB);
87
+ this.w.copy(other.w);
88
+ this.a = other.a;
89
+ this.indexA = other.indexA;
90
+ this.indexB = other.indexB;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * @class Simplex
96
+ * @description The 1-, 2- or 3-point simplex GJK maintains while walking
97
+ * towards the closest point on the Minkowski difference.
98
+ * @private
99
+ */
100
+ class Simplex {
101
+ constructor() {
102
+ this.v = [new SimplexVertex(), new SimplexVertex(), new SimplexVertex()];
103
+ this.count = 0;
104
+ }
105
+
106
+ getSearchDirection() {
107
+ switch (this.count) {
108
+ case 1:
109
+ return Vec2.neg(this.v[0].w);
110
+ case 2: {
111
+ const e12 = Vec2.sub(this.v[1].w, this.v[0].w);
112
+ const sgn = Vec2.cross(e12, Vec2.neg(this.v[0].w));
113
+ return sgn > 0 ? Vec2.crossSV(1, e12) : Vec2.crossVS(e12, 1);
114
+ }
115
+ default:
116
+ return new Vec2(0, 0);
117
+ }
118
+ }
119
+
120
+ getWitnessPoints(out) {
121
+ switch (this.count) {
122
+ case 1:
123
+ out.pointA.copy(this.v[0].wA);
124
+ out.pointB.copy(this.v[0].wB);
125
+ break;
126
+ case 2:
127
+ out.pointA.set(
128
+ this.v[0].a * this.v[0].wA.x + this.v[1].a * this.v[1].wA.x,
129
+ this.v[0].a * this.v[0].wA.y + this.v[1].a * this.v[1].wA.y
130
+ );
131
+ out.pointB.set(
132
+ this.v[0].a * this.v[0].wB.x + this.v[1].a * this.v[1].wB.x,
133
+ this.v[0].a * this.v[0].wB.y + this.v[1].a * this.v[1].wB.y
134
+ );
135
+ break;
136
+ case 3:
137
+ out.pointA.set(
138
+ this.v[0].a * this.v[0].wA.x +
139
+ this.v[1].a * this.v[1].wA.x +
140
+ this.v[2].a * this.v[2].wA.x,
141
+ this.v[0].a * this.v[0].wA.y +
142
+ this.v[1].a * this.v[1].wA.y +
143
+ this.v[2].a * this.v[2].wA.y
144
+ );
145
+ out.pointB.copy(out.pointA);
146
+ break;
147
+ default:
148
+ break;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * @method solve2
154
+ * @description Closest point on a line segment to the origin, expressed in
155
+ * barycentric coordinates. Drops the vertex that isn't part of the answer.
156
+ * @private
157
+ */
158
+ solve2() {
159
+ const w1 = this.v[0].w;
160
+ const w2 = this.v[1].w;
161
+ const e12 = Vec2.sub(w2, w1);
162
+
163
+ const d12_2 = -Vec2.dot(w1, e12);
164
+ if (d12_2 <= 0) {
165
+ this.v[0].a = 1;
166
+ this.count = 1;
167
+ return;
168
+ }
169
+
170
+ const d12_1 = Vec2.dot(w2, e12);
171
+ if (d12_1 <= 0) {
172
+ this.v[1].a = 1;
173
+ this.count = 1;
174
+ this.v[0].copy(this.v[1]);
175
+ return;
176
+ }
177
+
178
+ const inv = 1 / (d12_1 + d12_2);
179
+ this.v[0].a = d12_1 * inv;
180
+ this.v[1].a = d12_2 * inv;
181
+ this.count = 2;
182
+ }
183
+
184
+ /**
185
+ * @method solve3
186
+ * @description Closest point on a triangle to the origin. Reduces the simplex
187
+ * to the feature (vertex, edge or the whole triangle) that owns it.
188
+ * @private
189
+ */
190
+ solve3() {
191
+ const w1 = this.v[0].w;
192
+ const w2 = this.v[1].w;
193
+ const w3 = this.v[2].w;
194
+
195
+ const e12 = Vec2.sub(w2, w1);
196
+ const w1e12 = Vec2.dot(w1, e12);
197
+ const w2e12 = Vec2.dot(w2, e12);
198
+ const d12_1 = w2e12;
199
+ const d12_2 = -w1e12;
200
+
201
+ const e13 = Vec2.sub(w3, w1);
202
+ const w1e13 = Vec2.dot(w1, e13);
203
+ const w3e13 = Vec2.dot(w3, e13);
204
+ const d13_1 = w3e13;
205
+ const d13_2 = -w1e13;
206
+
207
+ const e23 = Vec2.sub(w3, w2);
208
+ const w2e23 = Vec2.dot(w2, e23);
209
+ const w3e23 = Vec2.dot(w3, e23);
210
+ const d23_1 = w3e23;
211
+ const d23_2 = -w2e23;
212
+
213
+ const n123 = Vec2.cross(e12, e13);
214
+ const d123_1 = n123 * Vec2.cross(w2, w3);
215
+ const d123_2 = n123 * Vec2.cross(w3, w1);
216
+ const d123_3 = n123 * Vec2.cross(w1, w2);
217
+
218
+ if (d12_2 <= 0 && d13_2 <= 0) {
219
+ this.v[0].a = 1;
220
+ this.count = 1;
221
+ return;
222
+ }
223
+
224
+ if (d12_1 > 0 && d12_2 > 0 && d123_3 <= 0) {
225
+ const inv = 1 / (d12_1 + d12_2);
226
+ this.v[0].a = d12_1 * inv;
227
+ this.v[1].a = d12_2 * inv;
228
+ this.count = 2;
229
+ return;
230
+ }
231
+
232
+ if (d13_1 > 0 && d13_2 > 0 && d123_2 <= 0) {
233
+ const inv = 1 / (d13_1 + d13_2);
234
+ this.v[0].a = d13_1 * inv;
235
+ this.v[2].a = d13_2 * inv;
236
+ this.count = 2;
237
+ this.v[1].copy(this.v[2]);
238
+ return;
239
+ }
240
+
241
+ if (d12_1 <= 0 && d23_2 <= 0) {
242
+ this.v[1].a = 1;
243
+ this.count = 1;
244
+ this.v[0].copy(this.v[1]);
245
+ return;
246
+ }
247
+
248
+ if (d13_1 <= 0 && d23_1 <= 0) {
249
+ this.v[2].a = 1;
250
+ this.count = 1;
251
+ this.v[0].copy(this.v[2]);
252
+ return;
253
+ }
254
+
255
+ if (d23_1 > 0 && d23_2 > 0 && d123_1 <= 0) {
256
+ const inv = 1 / (d23_1 + d23_2);
257
+ this.v[1].a = d23_1 * inv;
258
+ this.v[2].a = d23_2 * inv;
259
+ this.count = 2;
260
+ this.v[0].copy(this.v[2]);
261
+ return;
262
+ }
263
+
264
+ const inv = 1 / (d123_1 + d123_2 + d123_3);
265
+ this.v[0].a = d123_1 * inv;
266
+ this.v[1].a = d123_2 * inv;
267
+ this.v[2].a = d123_3 * inv;
268
+ this.count = 3;
269
+ }
270
+ }
271
+
272
+ const simplex = new Simplex();
273
+ const saveA = [0, 0, 0];
274
+ const saveB = [0, 0, 0];
275
+
276
+ /**
277
+ * @function distance
278
+ * @description GJK: computes the distance and the closest points between two
279
+ * convex proxies. This is the primitive continuous collision and overlap tests
280
+ * are built on.
281
+ * @param {Object} output - Written as `{ distance, pointA, pointB, iterations }`
282
+ * @param {Object} input - `{ proxyA, proxyB, transformA, transformB, useRadii }`
283
+ * @returns {Object} - The same output object
284
+ */
285
+ function distance(output, input) {
286
+ const { proxyA, proxyB, transformA, transformB } = input;
287
+
288
+ simplex.count = 1;
289
+ const v0 = simplex.v[0];
290
+ v0.indexA = 0;
291
+ v0.indexB = 0;
292
+ v0.wA.copy(Transform2.mulVec(transformA, proxyA.vertices[0]));
293
+ v0.wB.copy(Transform2.mulVec(transformB, proxyB.vertices[0]));
294
+ v0.w.set(v0.wB.x - v0.wA.x, v0.wB.y - v0.wA.y);
295
+ v0.a = 1;
296
+
297
+ let iter = 0;
298
+ while (iter < 20) {
299
+ const saveCount = simplex.count;
300
+ for (let i = 0; i < saveCount; i++) {
301
+ saveA[i] = simplex.v[i].indexA;
302
+ saveB[i] = simplex.v[i].indexB;
303
+ }
304
+
305
+ if (simplex.count === 2) simplex.solve2();
306
+ else if (simplex.count === 3) simplex.solve3();
307
+
308
+ if (simplex.count === 3) break;
309
+
310
+ const d = simplex.getSearchDirection();
311
+ if (d.lengthSquared() < EPSILON * EPSILON) break;
312
+
313
+ const vertex = simplex.v[simplex.count];
314
+ vertex.indexA = proxyA.getSupport(
315
+ rotateInverse(transformA, d.x * -1, d.y * -1)
316
+ );
317
+ vertex.wA.copy(
318
+ Transform2.mulVec(transformA, proxyA.vertices[vertex.indexA])
319
+ );
320
+ vertex.indexB = proxyB.getSupport(rotateInverse(transformB, d.x, d.y));
321
+ vertex.wB.copy(
322
+ Transform2.mulVec(transformB, proxyB.vertices[vertex.indexB])
323
+ );
324
+ vertex.w.set(vertex.wB.x - vertex.wA.x, vertex.wB.y - vertex.wA.y);
325
+
326
+ iter++;
327
+
328
+ let duplicate = false;
329
+ for (let i = 0; i < saveCount; i++) {
330
+ if (vertex.indexA === saveA[i] && vertex.indexB === saveB[i]) {
331
+ duplicate = true;
332
+ break;
333
+ }
334
+ }
335
+ if (duplicate) break;
336
+
337
+ simplex.count++;
338
+ }
339
+
340
+ if (!output.pointA) output.pointA = new Vec2();
341
+ if (!output.pointB) output.pointB = new Vec2();
342
+ simplex.getWitnessPoints(output);
343
+ output.distance = Vec2.distance(output.pointA, output.pointB);
344
+ output.iterations = iter;
345
+
346
+ if (input.useRadii) {
347
+ const rA = proxyA.radius;
348
+ const rB = proxyB.radius;
349
+ if (output.distance > rA + rB && output.distance > EPSILON) {
350
+ output.distance -= rA + rB;
351
+ const normal = Vec2.sub(output.pointB, output.pointA);
352
+ normal.normalize();
353
+ output.pointA.addMul(rA, normal);
354
+ output.pointB.subMul(rB, normal);
355
+ } else {
356
+ const px = 0.5 * (output.pointA.x + output.pointB.x);
357
+ const py = 0.5 * (output.pointA.y + output.pointB.y);
358
+ output.pointA.set(px, py);
359
+ output.pointB.set(px, py);
360
+ output.distance = 0;
361
+ }
362
+ }
363
+
364
+ return output;
365
+ }
366
+
367
+ /**
368
+ * @function rotateInverse
369
+ * @description Rotates a world direction into a transform's local frame.
370
+ * @private
371
+ */
372
+ function rotateInverse(xf, x, y) {
373
+ return new Vec2(xf.q.c * x + xf.q.s * y, -xf.q.s * x + xf.q.c * y);
374
+ }
375
+
376
+ const overlapProxyA = new DistanceProxy();
377
+ const overlapProxyB = new DistanceProxy();
378
+ const overlapOutput = { distance: 0, pointA: new Vec2(), pointB: new Vec2() };
379
+
380
+ /**
381
+ * @function testOverlap
382
+ * @description True when two shapes overlap under their transforms. Sensors use
383
+ * this instead of building a manifold, since they never need contact points.
384
+ * @param {Shape} shapeA
385
+ * @param {Transform2} xfA
386
+ * @param {Shape} shapeB
387
+ * @param {Transform2} xfB
388
+ * @returns {boolean}
389
+ */
390
+ function testOverlap(shapeA, xfA, shapeB, xfB) {
391
+ overlapProxyA.set(shapeA);
392
+ overlapProxyB.set(shapeB);
393
+ distance(overlapOutput, {
394
+ proxyA: overlapProxyA,
395
+ proxyB: overlapProxyB,
396
+ transformA: xfA,
397
+ transformB: xfB,
398
+ useRadii: true,
399
+ });
400
+ return overlapOutput.distance < 10 * EPSILON;
401
+ }
402
+
403
+ export { DistanceProxy, distance, testOverlap };
@@ -0,0 +1,227 @@
1
+ import { Vec2, Rot } from "./Math2D.js";
2
+ import { Joint, JointType } from "./Joint.js";
3
+ import PhysicsSettings from "./Settings.js";
4
+
5
+ /**
6
+ * @class DistanceJoint
7
+ * @extends Joint
8
+ * @description Holds two anchor points at a fixed distance apart, like a
9
+ * rigid rod. Passing `frequencyHz` turns it into a damped spring pulling the
10
+ * anchors toward that distance instead of holding it exactly, for tethers
11
+ * and camera rigs that should have some give.
12
+ * @param {Object} def - `{ bodyA, bodyB, localAnchorA, localAnchorB, length,
13
+ * frequencyHz, dampingRatio, collideConnected, userData }`
14
+ */
15
+ class DistanceJoint extends Joint {
16
+ constructor(def) {
17
+ super({ ...def, type: JointType.DISTANCE });
18
+ this.localAnchorA = new Vec2(def.localAnchorA || { x: 0, y: 0 });
19
+ this.localAnchorB = new Vec2(def.localAnchorB || { x: 0, y: 0 });
20
+ this.length = def.length ?? 1;
21
+ this.frequencyHz = def.frequencyHz ?? 0;
22
+ this.dampingRatio = def.dampingRatio ?? 0;
23
+
24
+ this.impulse = 0;
25
+ this.gamma = 0;
26
+ this.bias = 0;
27
+ this.u = new Vec2();
28
+ this.rA = new Vec2();
29
+ this.rB = new Vec2();
30
+ this.mass = 0;
31
+ /** @private */
32
+ this.indexA = 0;
33
+ /** @private */
34
+ this.indexB = 0;
35
+ }
36
+
37
+ /**
38
+ * @method getAnchorA
39
+ * @returns {Vec2}
40
+ */
41
+ getAnchorA() {
42
+ return this.bodyA.getWorldPoint(this.localAnchorA);
43
+ }
44
+
45
+ /**
46
+ * @method getAnchorB
47
+ * @returns {Vec2}
48
+ */
49
+ getAnchorB() {
50
+ return this.bodyB.getWorldPoint(this.localAnchorB);
51
+ }
52
+
53
+ /**
54
+ * @method getReactionForce
55
+ * @param {number} invDt
56
+ * @returns {Vec2}
57
+ */
58
+ getReactionForce(invDt) {
59
+ return Vec2.mul(this.impulse * invDt, this.u);
60
+ }
61
+
62
+ /**
63
+ * @method getReactionTorque
64
+ * @returns {number}
65
+ */
66
+ getReactionTorque() {
67
+ return 0;
68
+ }
69
+
70
+ /**
71
+ * @method setLength
72
+ * @param {number} length
73
+ */
74
+ setLength(length) {
75
+ this.length = length;
76
+ }
77
+
78
+ /**
79
+ * @method getLength
80
+ * @returns {number}
81
+ */
82
+ getLength() {
83
+ return this.length;
84
+ }
85
+
86
+ /**
87
+ * @method initVelocityConstraints
88
+ * @description Effective mass at the current anchor separation, plus (for
89
+ * a soft joint) the spring-damper bias for this step.
90
+ * @param {Object} step - `{ dt, warmStarting }`
91
+ * @param {Array<Object>} positions
92
+ * @param {Array<Object>} velocities
93
+ */
94
+ initVelocityConstraints(step, positions, velocities) {
95
+ this.indexA = this.bodyA.islandIndex;
96
+ this.indexB = this.bodyB.islandIndex;
97
+ this.invMassA = this.bodyA.invMass;
98
+ this.invMassB = this.bodyB.invMass;
99
+ this.invIA = this.bodyA.invI;
100
+ this.invIB = this.bodyB.invI;
101
+ const localCenterA = this.bodyA.sweep.localCenter;
102
+ const localCenterB = this.bodyB.sweep.localCenter;
103
+
104
+ const posA = positions[this.indexA];
105
+ const posB = positions[this.indexB];
106
+ const velA = velocities[this.indexA];
107
+ const velB = velocities[this.indexB];
108
+
109
+ const qA = new Rot(posA.a);
110
+ const qB = new Rot(posB.a);
111
+
112
+ this.rA = Rot.mulVec(qA, Vec2.sub(this.localAnchorA, localCenterA));
113
+ this.rB = Rot.mulVec(qB, Vec2.sub(this.localAnchorB, localCenterB));
114
+ this.u = Vec2.sub(Vec2.add(posB.c, this.rB), Vec2.add(posA.c, this.rA));
115
+
116
+ const length = this.u.length();
117
+ if (length > PhysicsSettings.linearSlop) {
118
+ this.u.mul(1 / length);
119
+ } else {
120
+ this.u.set(0, 0);
121
+ }
122
+
123
+ const crA = Vec2.cross(this.rA, this.u);
124
+ const crB = Vec2.cross(this.rB, this.u);
125
+ let invMass =
126
+ this.invMassA +
127
+ this.invIA * crA * crA +
128
+ this.invMassB +
129
+ this.invIB * crB * crB;
130
+ this.mass = invMass !== 0 ? 1 / invMass : 0;
131
+
132
+ if (this.frequencyHz > 0) {
133
+ const C = length - this.length;
134
+ const omega = 2 * Math.PI * this.frequencyHz;
135
+ const d = 2 * this.mass * this.dampingRatio * omega;
136
+ const k = this.mass * omega * omega;
137
+ const h = step.dt;
138
+
139
+ this.gamma = h * (d + h * k);
140
+ this.gamma = this.gamma !== 0 ? 1 / this.gamma : 0;
141
+ this.bias = C * h * k * this.gamma;
142
+
143
+ invMass += this.gamma;
144
+ this.mass = invMass !== 0 ? 1 / invMass : 0;
145
+ } else {
146
+ this.gamma = 0;
147
+ this.bias = 0;
148
+ }
149
+
150
+ if (step.warmStarting) {
151
+ const P = Vec2.mul(this.impulse, this.u);
152
+ velA.v.subMul(this.invMassA, P);
153
+ velA.w -= this.invIA * Vec2.cross(this.rA, P);
154
+ velB.v.addMul(this.invMassB, P);
155
+ velB.w += this.invIB * Vec2.cross(this.rB, P);
156
+ } else {
157
+ this.impulse = 0;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * @method solveVelocityConstraints
163
+ * @param {Object} step - `{ dt }` (unused here, kept for a uniform joint interface)
164
+ * @param {Array<Object>} velocities
165
+ */
166
+ solveVelocityConstraints(step, velocities) {
167
+ const velA = velocities[this.indexA];
168
+ const velB = velocities[this.indexB];
169
+
170
+ const vpA = Vec2.add(velA.v, Vec2.crossSV(velA.w, this.rA));
171
+ const vpB = Vec2.add(velB.v, Vec2.crossSV(velB.w, this.rB));
172
+ const Cdot = Vec2.dot(this.u, Vec2.sub(vpB, vpA));
173
+
174
+ const impulse = -this.mass * (Cdot + this.bias + this.gamma * this.impulse);
175
+ this.impulse += impulse;
176
+
177
+ const P = Vec2.mul(impulse, this.u);
178
+ velA.v.subMul(this.invMassA, P);
179
+ velA.w -= this.invIA * Vec2.cross(this.rA, P);
180
+ velB.v.addMul(this.invMassB, P);
181
+ velB.w += this.invIB * Vec2.cross(this.rB, P);
182
+ }
183
+
184
+ /**
185
+ * @method solvePositionConstraints
186
+ * @description Skipped for a soft (spring) joint: the bias already pulled
187
+ * it toward rest length during the velocity pass, and correcting position
188
+ * on top of that would fight the spring.
189
+ * @param {Array<Object>} positions
190
+ * @returns {boolean}
191
+ */
192
+ solvePositionConstraints(positions) {
193
+ if (this.frequencyHz > 0) return true;
194
+
195
+ const posA = positions[this.indexA];
196
+ const posB = positions[this.indexB];
197
+
198
+ const qA = new Rot(posA.a);
199
+ const qB = new Rot(posB.a);
200
+
201
+ const localCenterA = this.bodyA.sweep.localCenter;
202
+ const localCenterB = this.bodyB.sweep.localCenter;
203
+ const rA = Rot.mulVec(qA, Vec2.sub(this.localAnchorA, localCenterA));
204
+ const rB = Rot.mulVec(qB, Vec2.sub(this.localAnchorB, localCenterB));
205
+ const u = Vec2.sub(Vec2.add(posB.c, rB), Vec2.add(posA.c, rA));
206
+
207
+ const length = u.normalize();
208
+ const C = length - this.length;
209
+
210
+ const clamped = Math.max(
211
+ -PhysicsSettings.maxLinearCorrection,
212
+ Math.min(PhysicsSettings.maxLinearCorrection, C)
213
+ );
214
+
215
+ const impulse = -this.mass * clamped;
216
+ const P = Vec2.mul(impulse, u);
217
+
218
+ posA.c.subMul(this.invMassA, P);
219
+ posA.a -= this.invIA * Vec2.cross(rA, P);
220
+ posB.c.addMul(this.invMassB, P);
221
+ posB.a += this.invIB * Vec2.cross(rB, P);
222
+
223
+ return Math.abs(C) < PhysicsSettings.linearSlop;
224
+ }
225
+ }
226
+
227
+ export default DistanceJoint;