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,346 @@
|
|
|
1
|
+
import AABB from "./AABB.js";
|
|
2
|
+
import { Vec2 } from "./Math2D.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @class FixtureProxy
|
|
6
|
+
* @description The link between a fixture and its entry in the broadphase tree.
|
|
7
|
+
* @private
|
|
8
|
+
*/
|
|
9
|
+
class FixtureProxy {
|
|
10
|
+
constructor(fixture, childIndex) {
|
|
11
|
+
this.aabb = new AABB();
|
|
12
|
+
this.fixture = fixture;
|
|
13
|
+
this.childIndex = childIndex;
|
|
14
|
+
this.proxyId = -1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @class Fixture
|
|
20
|
+
* @description A shape bound to a body, together with the material properties
|
|
21
|
+
* that decide how contacts behave: density (which feeds the body's mass),
|
|
22
|
+
* friction, restitution (bounciness), the sensor flag, and the collision
|
|
23
|
+
* filter. A body can carry any number of fixtures.
|
|
24
|
+
*
|
|
25
|
+
* Fixtures are created through {@link Body#createFixture}, never directly.
|
|
26
|
+
*/
|
|
27
|
+
class Fixture {
|
|
28
|
+
constructor(body, shape, def = {}) {
|
|
29
|
+
this.body = body;
|
|
30
|
+
this.shape = shape;
|
|
31
|
+
this.density = def.density ?? 0;
|
|
32
|
+
this.friction = def.friction ?? 0.2;
|
|
33
|
+
this.restitution = def.restitution ?? 0;
|
|
34
|
+
this.sensor = !!def.isSensor;
|
|
35
|
+
this.userData = def.userData ?? null;
|
|
36
|
+
this.filter = {
|
|
37
|
+
categoryBits: def.filterCategoryBits ?? def.categoryBits ?? 0x0001,
|
|
38
|
+
maskBits: def.filterMaskBits ?? def.maskBits ?? 0xffff,
|
|
39
|
+
groupIndex: def.filterGroupIndex ?? def.groupIndex ?? 0,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** @private */
|
|
43
|
+
this.proxies = [new FixtureProxy(this, 0)];
|
|
44
|
+
/** @private */
|
|
45
|
+
this.next = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @method getType
|
|
50
|
+
* @description Returns the underlying shape type ("circle" or "polygon").
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
getType() {
|
|
54
|
+
return this.shape.type;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @method getShape
|
|
59
|
+
* @description Returns the fixture's shape.
|
|
60
|
+
* @returns {Shape}
|
|
61
|
+
*/
|
|
62
|
+
getShape() {
|
|
63
|
+
return this.shape;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @method getBody
|
|
68
|
+
* @description Returns the body this fixture is attached to.
|
|
69
|
+
* @returns {Body}
|
|
70
|
+
*/
|
|
71
|
+
getBody() {
|
|
72
|
+
return this.body;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @method getNext
|
|
77
|
+
* @description Returns the next fixture on the same body, or null.
|
|
78
|
+
* @returns {Fixture|null}
|
|
79
|
+
*/
|
|
80
|
+
getNext() {
|
|
81
|
+
return this.next;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @method setUserData
|
|
86
|
+
* @description Attaches arbitrary data to the fixture.
|
|
87
|
+
* @param {*} data
|
|
88
|
+
*/
|
|
89
|
+
setUserData(data) {
|
|
90
|
+
this.userData = data;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @method getUserData
|
|
95
|
+
* @description Returns the attached data.
|
|
96
|
+
* @returns {*}
|
|
97
|
+
*/
|
|
98
|
+
getUserData() {
|
|
99
|
+
return this.userData;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @method setSensor
|
|
104
|
+
* @description A sensor still reports contacts but never generates a
|
|
105
|
+
* collision response: the classic trigger volume.
|
|
106
|
+
* @param {boolean} value
|
|
107
|
+
*/
|
|
108
|
+
setSensor(value) {
|
|
109
|
+
if (value !== this.sensor) {
|
|
110
|
+
this.body.setAwake(true);
|
|
111
|
+
this.sensor = !!value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @method isSensor
|
|
117
|
+
* @description Whether this fixture is a sensor.
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
isSensor() {
|
|
121
|
+
return this.sensor;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @method setFilterData
|
|
126
|
+
* @description Replaces the collision filter. Two fixtures collide only when
|
|
127
|
+
* each one's category bit appears in the other's mask (or they share a
|
|
128
|
+
* positive group index).
|
|
129
|
+
* @param {Object} filter - `{ categoryBits, maskBits, groupIndex }`
|
|
130
|
+
*/
|
|
131
|
+
setFilterData(filter = {}) {
|
|
132
|
+
this.filter = {
|
|
133
|
+
categoryBits: filter.categoryBits ?? this.filter.categoryBits,
|
|
134
|
+
maskBits: filter.maskBits ?? this.filter.maskBits,
|
|
135
|
+
groupIndex: filter.groupIndex ?? this.filter.groupIndex,
|
|
136
|
+
};
|
|
137
|
+
this.refilter();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @method getFilterData
|
|
142
|
+
* @description Returns the current collision filter.
|
|
143
|
+
* @returns {Object}
|
|
144
|
+
*/
|
|
145
|
+
getFilterData() {
|
|
146
|
+
return this.filter;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @method getFilterCategoryBits
|
|
151
|
+
* @description Returns the category bits.
|
|
152
|
+
* @returns {number}
|
|
153
|
+
*/
|
|
154
|
+
getFilterCategoryBits() {
|
|
155
|
+
return this.filter.categoryBits;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @method getFilterMaskBits
|
|
160
|
+
* @description Returns the mask bits.
|
|
161
|
+
* @returns {number}
|
|
162
|
+
*/
|
|
163
|
+
getFilterMaskBits() {
|
|
164
|
+
return this.filter.maskBits;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @method getFilterGroupIndex
|
|
169
|
+
* @description Returns the group index.
|
|
170
|
+
* @returns {number}
|
|
171
|
+
*/
|
|
172
|
+
getFilterGroupIndex() {
|
|
173
|
+
return this.filter.groupIndex;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @method refilter
|
|
178
|
+
* @description Re-evaluates existing contacts after a filter change, so a
|
|
179
|
+
* fixture that just stopped colliding with a layer separates immediately
|
|
180
|
+
* instead of on its next move.
|
|
181
|
+
*/
|
|
182
|
+
refilter() {
|
|
183
|
+
const world = this.body.world;
|
|
184
|
+
if (!world) return;
|
|
185
|
+
|
|
186
|
+
for (const contact of this.body.contacts) {
|
|
187
|
+
if (contact.fixtureA === this || contact.fixtureB === this) {
|
|
188
|
+
contact.flagForFiltering();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (const proxy of this.proxies) {
|
|
193
|
+
if (proxy.proxyId !== -1) world.broadPhase.touchProxy(proxy.proxyId);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @method setDensity
|
|
199
|
+
* @description Sets the density. Call {@link Body#resetMassData} afterwards
|
|
200
|
+
* for it to take effect on the body's mass.
|
|
201
|
+
* @param {number} density
|
|
202
|
+
*/
|
|
203
|
+
setDensity(density) {
|
|
204
|
+
this.density = density;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @method getDensity
|
|
209
|
+
* @description Returns the density.
|
|
210
|
+
* @returns {number}
|
|
211
|
+
*/
|
|
212
|
+
getDensity() {
|
|
213
|
+
return this.density;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @method setFriction
|
|
218
|
+
* @description Sets the friction coefficient (0 = ice).
|
|
219
|
+
* @param {number} friction
|
|
220
|
+
*/
|
|
221
|
+
setFriction(friction) {
|
|
222
|
+
this.friction = friction;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @method getFriction
|
|
227
|
+
* @description Returns the friction coefficient.
|
|
228
|
+
* @returns {number}
|
|
229
|
+
*/
|
|
230
|
+
getFriction() {
|
|
231
|
+
return this.friction;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @method setRestitution
|
|
236
|
+
* @description Sets the bounciness (0 = no bounce, 1 = perfectly elastic).
|
|
237
|
+
* @param {number} restitution
|
|
238
|
+
*/
|
|
239
|
+
setRestitution(restitution) {
|
|
240
|
+
this.restitution = restitution;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @method getRestitution
|
|
245
|
+
* @description Returns the bounciness.
|
|
246
|
+
* @returns {number}
|
|
247
|
+
*/
|
|
248
|
+
getRestitution() {
|
|
249
|
+
return this.restitution;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @method testPoint
|
|
254
|
+
* @description True when a world-space point is inside this fixture.
|
|
255
|
+
* @param {Object} p - `{ x, y }` in physics units
|
|
256
|
+
* @returns {boolean}
|
|
257
|
+
*/
|
|
258
|
+
testPoint(p) {
|
|
259
|
+
return this.shape.testPoint(this.body.xf, p);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @method rayCast
|
|
264
|
+
* @description Casts a ray against this fixture's shape.
|
|
265
|
+
* @param {Object} output - Written as `{ fraction, normal }` on a hit
|
|
266
|
+
* @param {Object} input - `{ p1, p2, maxFraction }`
|
|
267
|
+
* @returns {boolean}
|
|
268
|
+
*/
|
|
269
|
+
rayCast(output, input) {
|
|
270
|
+
return this.shape.rayCast(output, input, this.body.xf);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @method getMassData
|
|
275
|
+
* @description Computes this fixture's contribution to the body's mass.
|
|
276
|
+
* @param {Object} [massData]
|
|
277
|
+
* @returns {Object} - `{ mass, center, I }`
|
|
278
|
+
*/
|
|
279
|
+
getMassData(massData = { mass: 0, center: new Vec2(), I: 0 }) {
|
|
280
|
+
this.shape.computeMass(massData, this.density);
|
|
281
|
+
return massData;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @method getAABB
|
|
286
|
+
* @description Returns the fixture's current world AABB.
|
|
287
|
+
* @param {number} [childIndex=0]
|
|
288
|
+
* @returns {AABB}
|
|
289
|
+
*/
|
|
290
|
+
getAABB(childIndex = 0) {
|
|
291
|
+
return this.proxies[childIndex].aabb;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @method createProxies
|
|
296
|
+
* @description Registers this fixture with the broadphase.
|
|
297
|
+
* @param {BroadPhase} broadPhase
|
|
298
|
+
* @param {Transform2} xf
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
createProxies(broadPhase, xf) {
|
|
302
|
+
for (const proxy of this.proxies) {
|
|
303
|
+
this.shape.computeAABB(proxy.aabb, xf);
|
|
304
|
+
proxy.proxyId = broadPhase.createProxy(proxy.aabb, proxy);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* @method destroyProxies
|
|
310
|
+
* @description Removes this fixture from the broadphase.
|
|
311
|
+
* @param {BroadPhase} broadPhase
|
|
312
|
+
* @private
|
|
313
|
+
*/
|
|
314
|
+
destroyProxies(broadPhase) {
|
|
315
|
+
for (const proxy of this.proxies) {
|
|
316
|
+
if (proxy.proxyId !== -1) {
|
|
317
|
+
broadPhase.destroyProxy(proxy.proxyId);
|
|
318
|
+
proxy.proxyId = -1;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* @method synchronize
|
|
325
|
+
* @description Re-fits the broadphase proxies after the body moved. The AABB
|
|
326
|
+
* spans both the old and the new transform so a fast body's proxy still
|
|
327
|
+
* covers the ground it swept over.
|
|
328
|
+
* @param {BroadPhase} broadPhase
|
|
329
|
+
* @param {Transform2} xf1
|
|
330
|
+
* @param {Transform2} xf2
|
|
331
|
+
* @private
|
|
332
|
+
*/
|
|
333
|
+
synchronize(broadPhase, xf1, xf2) {
|
|
334
|
+
for (const proxy of this.proxies) {
|
|
335
|
+
const aabb1 = new AABB();
|
|
336
|
+
const aabb2 = new AABB();
|
|
337
|
+
this.shape.computeAABB(aabb1, xf1);
|
|
338
|
+
this.shape.computeAABB(aabb2, xf2);
|
|
339
|
+
proxy.aabb.combine(aabb1, aabb2);
|
|
340
|
+
const displacement = new Vec2(xf2.p.x - xf1.p.x, xf2.p.y - xf1.p.y);
|
|
341
|
+
broadPhase.moveProxy(proxy.proxyId, proxy.aabb, displacement);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export { Fixture, FixtureProxy };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Vec2 } from "./Math2D.js";
|
|
2
|
+
import Settings from "./Settings.js";
|
|
3
|
+
import ContactSolver from "./ContactSolver.js";
|
|
4
|
+
import { BodyType } from "./BodyType.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @class Island
|
|
8
|
+
* @description A connected group of bodies, everything reachable through
|
|
9
|
+
* touching contacts, solved together. Islands are what make sleeping work:
|
|
10
|
+
* a pile of crates only goes to sleep once *every* body in it has settled, so
|
|
11
|
+
* one crate still rolling keeps the whole pile simulated.
|
|
12
|
+
*/
|
|
13
|
+
class Island {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.bodies = [];
|
|
16
|
+
this.contacts = [];
|
|
17
|
+
this.joints = [];
|
|
18
|
+
/** @private */
|
|
19
|
+
this.positions = [];
|
|
20
|
+
/** @private */
|
|
21
|
+
this.velocities = [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @method clear
|
|
26
|
+
* @description Empties the island for reuse on the next seed body.
|
|
27
|
+
*/
|
|
28
|
+
clear() {
|
|
29
|
+
this.bodies.length = 0;
|
|
30
|
+
this.contacts.length = 0;
|
|
31
|
+
this.joints.length = 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @method addBody
|
|
36
|
+
* @description Adds a body and records its index for the solver.
|
|
37
|
+
* @param {Body} body
|
|
38
|
+
*/
|
|
39
|
+
addBody(body) {
|
|
40
|
+
body.islandIndex = this.bodies.length;
|
|
41
|
+
this.bodies.push(body);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @method addContact
|
|
46
|
+
* @description Adds a touching contact to be solved with this island.
|
|
47
|
+
* @param {Contact} contact
|
|
48
|
+
*/
|
|
49
|
+
addContact(contact) {
|
|
50
|
+
this.contacts.push(contact);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @method addJoint
|
|
55
|
+
* @description Adds a joint to be solved with this island.
|
|
56
|
+
* @param {Joint} joint
|
|
57
|
+
*/
|
|
58
|
+
addJoint(joint) {
|
|
59
|
+
this.joints.push(joint);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @method solve
|
|
64
|
+
* @description Advances the island by one step: integrate velocities, solve
|
|
65
|
+
* contacts, integrate positions, fix leftover overlap, then decide whether
|
|
66
|
+
* the island can go to sleep.
|
|
67
|
+
* @param {Object} step - `{ dt, velocityIterations, positionIterations,
|
|
68
|
+
* warmStarting, velocityThreshold }`
|
|
69
|
+
* @param {Vec2} gravity - World gravity in physics units
|
|
70
|
+
* @param {boolean} allowSleep - Whether sleeping is enabled world-wide
|
|
71
|
+
*/
|
|
72
|
+
solve(step, gravity, allowSleep) {
|
|
73
|
+
const h = step.dt;
|
|
74
|
+
|
|
75
|
+
this.positions.length = 0;
|
|
76
|
+
this.velocities.length = 0;
|
|
77
|
+
|
|
78
|
+
for (const body of this.bodies) {
|
|
79
|
+
const c = body.sweep.c.clone();
|
|
80
|
+
const a = body.sweep.a;
|
|
81
|
+
const v = body.linearVelocity.clone();
|
|
82
|
+
let w = body.angularVelocity;
|
|
83
|
+
|
|
84
|
+
body.sweep.c0.copy(body.sweep.c);
|
|
85
|
+
body.sweep.a0 = body.sweep.a;
|
|
86
|
+
|
|
87
|
+
if (body.type === BodyType.DYNAMIC) {
|
|
88
|
+
v.x +=
|
|
89
|
+
h * (body.gravityScale * gravity.x + body.invMass * body.force.x);
|
|
90
|
+
v.y +=
|
|
91
|
+
h * (body.gravityScale * gravity.y + body.invMass * body.force.y);
|
|
92
|
+
w += h * body.invI * body.torque;
|
|
93
|
+
|
|
94
|
+
const linearScale = 1 / (1 + h * body.linearDamping);
|
|
95
|
+
v.x *= linearScale;
|
|
96
|
+
v.y *= linearScale;
|
|
97
|
+
w *= 1 / (1 + h * body.angularDamping);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.positions.push({ c, a });
|
|
101
|
+
this.velocities.push({ v, w });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const solver = new ContactSolver({
|
|
105
|
+
contacts: this.contacts,
|
|
106
|
+
positions: this.positions,
|
|
107
|
+
velocities: this.velocities,
|
|
108
|
+
dt: h,
|
|
109
|
+
velocityThreshold: step.velocityThreshold,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
for (const joint of this.joints) {
|
|
113
|
+
joint.initVelocityConstraints(step, this.positions, this.velocities);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
solver.initializeVelocityConstraints();
|
|
117
|
+
if (step.warmStarting) solver.warmStart();
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < step.velocityIterations; i++) {
|
|
120
|
+
for (const joint of this.joints) {
|
|
121
|
+
joint.solveVelocityConstraints(step, this.velocities);
|
|
122
|
+
}
|
|
123
|
+
solver.solveVelocityConstraints();
|
|
124
|
+
}
|
|
125
|
+
solver.storeImpulses();
|
|
126
|
+
|
|
127
|
+
for (let i = 0; i < this.bodies.length; i++) {
|
|
128
|
+
const pos = this.positions[i];
|
|
129
|
+
const vel = this.velocities[i];
|
|
130
|
+
|
|
131
|
+
let translationX = h * vel.v.x;
|
|
132
|
+
let translationY = h * vel.v.y;
|
|
133
|
+
const translationSq =
|
|
134
|
+
translationX * translationX + translationY * translationY;
|
|
135
|
+
if (translationSq > Settings.maxTranslation * Settings.maxTranslation) {
|
|
136
|
+
const ratio = Settings.maxTranslation / Math.sqrt(translationSq);
|
|
137
|
+
vel.v.x *= ratio;
|
|
138
|
+
vel.v.y *= ratio;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const rotation = h * vel.w;
|
|
142
|
+
if (rotation * rotation > Settings.maxRotation * Settings.maxRotation) {
|
|
143
|
+
vel.w *= Settings.maxRotation / Math.abs(rotation);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
pos.c.x += h * vel.v.x;
|
|
147
|
+
pos.c.y += h * vel.v.y;
|
|
148
|
+
pos.a += h * vel.w;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let positionSolved = false;
|
|
152
|
+
for (let i = 0; i < step.positionIterations; i++) {
|
|
153
|
+
let jointsOkay = true;
|
|
154
|
+
for (const joint of this.joints) {
|
|
155
|
+
jointsOkay =
|
|
156
|
+
joint.solvePositionConstraints(this.positions) && jointsOkay;
|
|
157
|
+
}
|
|
158
|
+
const contactsOkay = solver.solvePositionConstraints();
|
|
159
|
+
if (jointsOkay && contactsOkay) {
|
|
160
|
+
positionSolved = true;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < this.bodies.length; i++) {
|
|
166
|
+
const body = this.bodies[i];
|
|
167
|
+
body.sweep.c.copy(this.positions[i].c);
|
|
168
|
+
body.sweep.a = this.positions[i].a;
|
|
169
|
+
body.linearVelocity.copy(this.velocities[i].v);
|
|
170
|
+
body.angularVelocity = this.velocities[i].w;
|
|
171
|
+
body.synchronizeTransform();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!allowSleep) return;
|
|
175
|
+
|
|
176
|
+
let minSleepTime = Number.MAX_VALUE;
|
|
177
|
+
const linTolSqr =
|
|
178
|
+
Settings.linearSleepTolerance * Settings.linearSleepTolerance;
|
|
179
|
+
const angTolSqr =
|
|
180
|
+
Settings.angularSleepTolerance * Settings.angularSleepTolerance;
|
|
181
|
+
|
|
182
|
+
for (const body of this.bodies) {
|
|
183
|
+
if (body.type === BodyType.STATIC) continue;
|
|
184
|
+
if (
|
|
185
|
+
!body.allowSleep ||
|
|
186
|
+
body.angularVelocity * body.angularVelocity > angTolSqr ||
|
|
187
|
+
Vec2.dot(body.linearVelocity, body.linearVelocity) > linTolSqr
|
|
188
|
+
) {
|
|
189
|
+
body.sleepTime = 0;
|
|
190
|
+
minSleepTime = 0;
|
|
191
|
+
} else {
|
|
192
|
+
body.sleepTime += h;
|
|
193
|
+
minSleepTime = Math.min(minSleepTime, body.sleepTime);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (minSleepTime >= Settings.timeToSleep && positionSolved) {
|
|
198
|
+
for (const body of this.bodies) body.setAwake(false);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export default Island;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Identifies a concrete {@link Joint} subclass.
|
|
3
|
+
*/
|
|
4
|
+
const JointType = Object.freeze({
|
|
5
|
+
DISTANCE: "distance",
|
|
6
|
+
REVOLUTE: "revolute",
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @class Joint
|
|
11
|
+
* @description Base class for a constraint between two bodies. A joint is
|
|
12
|
+
* solved by the same island that solves contacts (see {@link Island}), right
|
|
13
|
+
* alongside them, so a body linked only by a joint (no touching fixtures)
|
|
14
|
+
* still wakes, sleeps and moves together with whatever it is jointed to.
|
|
15
|
+
*
|
|
16
|
+
* Concrete joints ({@link DistanceJoint}, {@link RevoluteJoint}) implement
|
|
17
|
+
* `initVelocityConstraints`, `solveVelocityConstraints` and
|
|
18
|
+
* `solvePositionConstraints`, matching {@link ContactSolver}'s per-step
|
|
19
|
+
* lifecycle.
|
|
20
|
+
* @param {Object} def - `{ type, bodyA, bodyB, collideConnected, userData }`
|
|
21
|
+
*/
|
|
22
|
+
class Joint {
|
|
23
|
+
constructor(def) {
|
|
24
|
+
this.type = def.type;
|
|
25
|
+
this.bodyA = def.bodyA;
|
|
26
|
+
this.bodyB = def.bodyB;
|
|
27
|
+
this.collideConnected = def.collideConnected ?? false;
|
|
28
|
+
this.userData = def.userData ?? null;
|
|
29
|
+
/** @private */
|
|
30
|
+
this.islandFlag = false;
|
|
31
|
+
/** @private */
|
|
32
|
+
this.prev = null;
|
|
33
|
+
/** @private */
|
|
34
|
+
this.next = null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @method getType
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
getType() {
|
|
42
|
+
return this.type;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @method getBodyA
|
|
47
|
+
* @returns {Body}
|
|
48
|
+
*/
|
|
49
|
+
getBodyA() {
|
|
50
|
+
return this.bodyA;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @method getBodyB
|
|
55
|
+
* @returns {Body}
|
|
56
|
+
*/
|
|
57
|
+
getBodyB() {
|
|
58
|
+
return this.bodyB;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @method getUserData
|
|
63
|
+
* @returns {*}
|
|
64
|
+
*/
|
|
65
|
+
getUserData() {
|
|
66
|
+
return this.userData;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @method setUserData
|
|
71
|
+
* @param {*} data
|
|
72
|
+
*/
|
|
73
|
+
setUserData(data) {
|
|
74
|
+
this.userData = data;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { Joint, JointType };
|