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.
- package/README.md +1498 -1659
- package/dist/types/index.d.ts +4 -1
- package/dist/types/src/Animator.d.ts +1 -1
- package/dist/types/src/CollisionLayers.d.ts +2 -2
- package/dist/types/src/Color.d.ts +1 -0
- package/dist/types/src/Drawable.d.ts +1 -1
- package/dist/types/src/EmeraldDB.d.ts +2 -2
- package/dist/types/src/InstancedTexture.d.ts +17 -2
- package/dist/types/src/Material.d.ts +2 -2
- package/dist/types/src/MathUtils.d.ts +2 -1
- package/dist/types/src/ParticleEmitter.d.ts +1 -1
- package/dist/types/src/Physics.d.ts +148 -18
- package/dist/types/src/Scene.d.ts +1 -1
- package/dist/types/src/Shaders.d.ts +2 -2
- package/dist/types/src/Tilemap.d.ts +1 -1
- package/dist/types/src/UI.d.ts +1 -1
- package/dist/types/src/components/Behaviour.d.ts +2 -2
- package/dist/types/src/components/Collider.d.ts +7 -1
- package/dist/types/src/components/GameObject.d.ts +2 -2
- package/dist/types/src/components/PolygonCollider.d.ts +33 -0
- package/dist/types/src/components/RigidBody.d.ts +281 -8
- package/dist/types/src/importers/Aseprite.d.ts +2 -2
- package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
- package/dist/types/src/importers/TiledMap.d.ts +1 -1
- package/dist/types/src/managers/EventManager.d.ts +1 -1
- package/dist/types/src/managers/Gamepad.d.ts +102 -0
- package/dist/types/src/managers/InputManager.d.ts +93 -2
- package/dist/types/src/managers/NetworkManager.d.ts +2 -2
- package/dist/types/src/managers/RenderStats.d.ts +1 -1
- package/dist/types/src/managers/TextureManager.d.ts +1 -1
- package/dist/types/src/physics/AABB.d.ts +92 -0
- package/dist/types/src/physics/Body.d.ts +435 -0
- package/dist/types/src/physics/BodyType.d.ts +6 -0
- package/dist/types/src/physics/BroadPhase.d.ts +210 -0
- package/dist/types/src/physics/Collision.d.ts +102 -0
- package/dist/types/src/physics/Contact.d.ts +206 -0
- package/dist/types/src/physics/ContactSolver.d.ts +108 -0
- package/dist/types/src/physics/Distance.d.ts +54 -0
- package/dist/types/src/physics/DistanceJoint.d.ts +90 -0
- package/dist/types/src/physics/Fixture.d.ts +221 -0
- package/dist/types/src/physics/Island.d.ts +52 -0
- package/dist/types/src/physics/Joint.d.ts +59 -0
- package/dist/types/src/physics/Math2D.d.ts +371 -0
- package/dist/types/src/physics/RevoluteJoint.d.ts +119 -0
- package/dist/types/src/physics/Settings.d.ts +22 -0
- package/dist/types/src/physics/Shapes.d.ts +207 -0
- package/dist/types/src/physics/TimeOfImpact.d.ts +22 -0
- package/dist/types/src/physics/World.d.ts +274 -0
- package/dist/types/src/physics/index.d.ts +34 -0
- package/index.js +6 -0
- package/package.json +2 -3
- package/src/Animator.js +1 -1
- package/src/CollisionLayers.js +3 -3
- package/src/Color.js +8 -0
- package/src/Drawable.js +1 -1
- package/src/Emerald.js +1 -1
- package/src/EmeraldDB.js +2 -2
- package/src/InstancedTexture.js +57 -9
- package/src/Material.js +2 -2
- package/src/MathUtils.js +2 -1
- package/src/ParticleEmitter.js +1 -1
- package/src/Physics.js +270 -60
- package/src/Scene.js +1 -1
- package/src/Shaders.js +20 -20
- package/src/Tilemap.js +1 -1
- package/src/UI.js +1 -1
- package/src/components/Behaviour.js +2 -2
- package/src/components/BoxCollider.js +7 -9
- package/src/components/BoxColliderDebug.js +3 -4
- package/src/components/CircleCollider.js +7 -9
- package/src/components/CircleColliderDebug.js +3 -2
- package/src/components/Collider.js +13 -3
- package/src/components/GameObject.js +2 -2
- package/src/components/PolygonCollider.js +55 -0
- package/src/components/RigidBody.js +441 -14
- package/src/importers/Aseprite.js +2 -2
- package/src/importers/ForgeLevel.js +581 -0
- package/src/importers/TiledMap.js +1 -1
- package/src/managers/EventManager.js +1 -1
- package/src/managers/Gamepad.js +126 -0
- package/src/managers/InputManager.js +129 -3
- package/src/managers/NetworkManager.js +2 -2
- package/src/managers/RenderStats.js +1 -1
- package/src/managers/TextureManager.js +1 -1
- package/src/physics/AABB.js +207 -0
- package/src/physics/Body.js +862 -0
- package/src/physics/BodyType.js +16 -0
- package/src/physics/BroadPhase.js +641 -0
- package/src/physics/Collision.js +534 -0
- package/src/physics/Contact.js +500 -0
- package/src/physics/ContactSolver.js +526 -0
- package/src/physics/Distance.js +403 -0
- package/src/physics/DistanceJoint.js +227 -0
- package/src/physics/Fixture.js +346 -0
- package/src/physics/Island.js +203 -0
- package/src/physics/Joint.js +78 -0
- package/src/physics/Math2D.js +573 -0
- package/src/physics/RevoluteJoint.js +278 -0
- package/src/physics/Settings.js +78 -0
- package/src/physics/Shapes.js +549 -0
- package/src/physics/TimeOfImpact.js +87 -0
- package/src/physics/World.js +731 -0
- 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;
|