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,862 @@
|
|
|
1
|
+
import { Vec2, Rot, Transform2, Sweep } from "./Math2D.js";
|
|
2
|
+
import { Fixture } from "./Fixture.js";
|
|
3
|
+
import { BodyType } from "./BodyType.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @class Body
|
|
7
|
+
* @description A rigid body: a position, an orientation, and the velocity and
|
|
8
|
+
* mass that go with them. Collision geometry lives on its fixtures, added with
|
|
9
|
+
* {@link Body#createFixture}. Bodies are created through
|
|
10
|
+
* {@link World#createBody}, never with `new`.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is in physics units (meters, radians, seconds). The engine's
|
|
13
|
+
* {@link RigidBody} component is the pixel-space wrapper around this class.
|
|
14
|
+
*
|
|
15
|
+
* @param {World} world - The owning world
|
|
16
|
+
* @param {Object} [def] - `{ type, position, angle, linearVelocity,
|
|
17
|
+
* angularVelocity, linearDamping, angularDamping, fixedRotation, bullet,
|
|
18
|
+
* allowSleep, awake, active, gravityScale, userData }`
|
|
19
|
+
*/
|
|
20
|
+
class Body {
|
|
21
|
+
constructor(world, def = {}) {
|
|
22
|
+
this.world = world;
|
|
23
|
+
this.type = def.type || BodyType.STATIC;
|
|
24
|
+
|
|
25
|
+
this.xf = new Transform2();
|
|
26
|
+
this.xf.set(def.position || new Vec2(0, 0), def.angle || 0);
|
|
27
|
+
|
|
28
|
+
this.sweep = new Sweep();
|
|
29
|
+
this.sweep.localCenter.setZero();
|
|
30
|
+
this.sweep.c0.copy(this.xf.p);
|
|
31
|
+
this.sweep.c.copy(this.xf.p);
|
|
32
|
+
this.sweep.a0 = def.angle || 0;
|
|
33
|
+
this.sweep.a = def.angle || 0;
|
|
34
|
+
this.sweep.alpha0 = 0;
|
|
35
|
+
|
|
36
|
+
this.linearVelocity = new Vec2(def.linearVelocity || { x: 0, y: 0 });
|
|
37
|
+
this.angularVelocity = def.angularVelocity || 0;
|
|
38
|
+
this.force = new Vec2(0, 0);
|
|
39
|
+
this.torque = 0;
|
|
40
|
+
|
|
41
|
+
this.linearDamping = def.linearDamping ?? 0;
|
|
42
|
+
this.angularDamping = def.angularDamping ?? 0;
|
|
43
|
+
this.gravityScale = def.gravityScale ?? 1;
|
|
44
|
+
|
|
45
|
+
this.mass = 0;
|
|
46
|
+
this.invMass = 0;
|
|
47
|
+
this.I = 0;
|
|
48
|
+
this.invI = 0;
|
|
49
|
+
|
|
50
|
+
this.fixedRotation = !!def.fixedRotation;
|
|
51
|
+
this.bullet = !!def.bullet;
|
|
52
|
+
this.allowSleep = def.allowSleep !== false;
|
|
53
|
+
this.awake = def.awake !== false;
|
|
54
|
+
this.active = def.active !== false;
|
|
55
|
+
this.sleepTime = 0;
|
|
56
|
+
|
|
57
|
+
this.userData = def.userData ?? null;
|
|
58
|
+
|
|
59
|
+
/** Head of this body's fixture list. @private */
|
|
60
|
+
this.fixtureList = null;
|
|
61
|
+
/** @private */
|
|
62
|
+
this.fixtureCount = 0;
|
|
63
|
+
/** Contacts this body currently takes part in. @private */
|
|
64
|
+
this.contacts = [];
|
|
65
|
+
/** Joints this body currently takes part in. @private */
|
|
66
|
+
this.joints = [];
|
|
67
|
+
|
|
68
|
+
/** @private */
|
|
69
|
+
this.prev = null;
|
|
70
|
+
/** @private */
|
|
71
|
+
this.next = null;
|
|
72
|
+
/** Scratch flag used while building solver islands. @private */
|
|
73
|
+
this.islandFlag = false;
|
|
74
|
+
/** Index into the current island's body array. @private */
|
|
75
|
+
this.islandIndex = 0;
|
|
76
|
+
|
|
77
|
+
if (this.type === BodyType.STATIC) {
|
|
78
|
+
this.awake = false;
|
|
79
|
+
this.linearVelocity.setZero();
|
|
80
|
+
this.angularVelocity = 0;
|
|
81
|
+
} else if (this.type === BodyType.DYNAMIC) {
|
|
82
|
+
this.mass = 1;
|
|
83
|
+
this.invMass = 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @method createFixture
|
|
89
|
+
* @description Attaches a shape to this body. The second argument may be a
|
|
90
|
+
* plain density number or a definition object.
|
|
91
|
+
* @param {Shape} shape - A CircleShape or PolygonShape
|
|
92
|
+
* @param {Object|number} [def] - `{ density, friction, restitution, isSensor,
|
|
93
|
+
* filterCategoryBits, filterMaskBits, filterGroupIndex, userData }`
|
|
94
|
+
* @returns {Fixture} - The new fixture
|
|
95
|
+
*/
|
|
96
|
+
createFixture(shape, def = {}) {
|
|
97
|
+
const fixtureDef = typeof def === "number" ? { density: def } : def;
|
|
98
|
+
const fixture = new Fixture(this, shape, fixtureDef);
|
|
99
|
+
|
|
100
|
+
if (this.active) {
|
|
101
|
+
fixture.createProxies(this.world.broadPhase, this.xf);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fixture.next = this.fixtureList;
|
|
105
|
+
this.fixtureList = fixture;
|
|
106
|
+
this.fixtureCount++;
|
|
107
|
+
|
|
108
|
+
if (fixture.density > 0) this.resetMassData();
|
|
109
|
+
|
|
110
|
+
this.world.newContacts = true;
|
|
111
|
+
return fixture;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @method destroyFixture
|
|
116
|
+
* @description Removes a fixture and every contact it was part of.
|
|
117
|
+
* @param {Fixture} fixture
|
|
118
|
+
*/
|
|
119
|
+
destroyFixture(fixture) {
|
|
120
|
+
if (!fixture || fixture.body !== this) return;
|
|
121
|
+
|
|
122
|
+
let node = this.fixtureList;
|
|
123
|
+
let prev = null;
|
|
124
|
+
let found = false;
|
|
125
|
+
while (node) {
|
|
126
|
+
if (node === fixture) {
|
|
127
|
+
if (prev) prev.next = node.next;
|
|
128
|
+
else this.fixtureList = node.next;
|
|
129
|
+
found = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
prev = node;
|
|
133
|
+
node = node.next;
|
|
134
|
+
}
|
|
135
|
+
if (!found) return;
|
|
136
|
+
|
|
137
|
+
for (let i = this.contacts.length - 1; i >= 0; i--) {
|
|
138
|
+
const contact = this.contacts[i];
|
|
139
|
+
if (contact.fixtureA === fixture || contact.fixtureB === fixture) {
|
|
140
|
+
this.world.contactManager.destroy(contact);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (this.active) fixture.destroyProxies(this.world.broadPhase);
|
|
145
|
+
|
|
146
|
+
fixture.body = null;
|
|
147
|
+
fixture.next = null;
|
|
148
|
+
this.fixtureCount--;
|
|
149
|
+
this.resetMassData();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @method getFixtureList
|
|
154
|
+
* @description Returns the first fixture; walk the rest with
|
|
155
|
+
* {@link Fixture#getNext}.
|
|
156
|
+
* @returns {Fixture|null}
|
|
157
|
+
*/
|
|
158
|
+
getFixtureList() {
|
|
159
|
+
return this.fixtureList;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @method getNext
|
|
164
|
+
* @description Returns the next body in the world's list.
|
|
165
|
+
* @returns {Body|null}
|
|
166
|
+
*/
|
|
167
|
+
getNext() {
|
|
168
|
+
return this.next;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @method setUserData
|
|
173
|
+
* @description Attaches arbitrary data to the body. The engine stores the
|
|
174
|
+
* owning {@link RigidBody} here so contacts can be traced back to game
|
|
175
|
+
* objects.
|
|
176
|
+
* @param {*} data
|
|
177
|
+
*/
|
|
178
|
+
setUserData(data) {
|
|
179
|
+
this.userData = data;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @method getUserData
|
|
184
|
+
* @description Returns the attached data.
|
|
185
|
+
* @returns {*}
|
|
186
|
+
*/
|
|
187
|
+
getUserData() {
|
|
188
|
+
return this.userData;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @method getWorld
|
|
193
|
+
* @description Returns the owning world.
|
|
194
|
+
* @returns {World}
|
|
195
|
+
*/
|
|
196
|
+
getWorld() {
|
|
197
|
+
return this.world;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @method getType
|
|
202
|
+
* @description Returns "static", "kinematic" or "dynamic".
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
getType() {
|
|
206
|
+
return this.type;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @method setType
|
|
211
|
+
* @description Changes the body type at runtime, resetting velocities and
|
|
212
|
+
* re-deriving mass. Existing contacts are dropped so they rebuild against the
|
|
213
|
+
* new type.
|
|
214
|
+
* @param {string} type - "static", "kinematic" or "dynamic"
|
|
215
|
+
*/
|
|
216
|
+
setType(type) {
|
|
217
|
+
if (this.type === type) return;
|
|
218
|
+
this.type = type;
|
|
219
|
+
|
|
220
|
+
this.resetMassData();
|
|
221
|
+
|
|
222
|
+
if (this.type === BodyType.STATIC) {
|
|
223
|
+
this.linearVelocity.setZero();
|
|
224
|
+
this.angularVelocity = 0;
|
|
225
|
+
this.sweep.a0 = this.sweep.a;
|
|
226
|
+
this.sweep.c0.copy(this.sweep.c);
|
|
227
|
+
this.awake = false;
|
|
228
|
+
this.synchronizeFixtures();
|
|
229
|
+
} else {
|
|
230
|
+
this.setAwake(true);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
this.force.setZero();
|
|
234
|
+
this.torque = 0;
|
|
235
|
+
|
|
236
|
+
for (let i = this.contacts.length - 1; i >= 0; i--) {
|
|
237
|
+
this.world.contactManager.destroy(this.contacts[i]);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (let f = this.fixtureList; f; f = f.next) {
|
|
241
|
+
for (const proxy of f.proxies) {
|
|
242
|
+
if (proxy.proxyId !== -1)
|
|
243
|
+
this.world.broadPhase.touchProxy(proxy.proxyId);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
this.world.newContacts = true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* @method setTransform
|
|
251
|
+
* @description Teleports the body: sets position and angle directly, without
|
|
252
|
+
* any velocity implied by the move.
|
|
253
|
+
*
|
|
254
|
+
* A teleport wakes the body. Contacts are only re-evaluated for bodies that
|
|
255
|
+
* are awake, so a sleeping body dropped into a new spot would otherwise sit
|
|
256
|
+
* there without noticing what it now overlaps: respawning a player onto a
|
|
257
|
+
* pickup, for instance, would silently miss it.
|
|
258
|
+
*
|
|
259
|
+
* @param {Object} position - `{ x, y }` in physics units
|
|
260
|
+
* @param {number} angle - Radians
|
|
261
|
+
*/
|
|
262
|
+
setTransform(position, angle) {
|
|
263
|
+
this.xf.q.set(angle);
|
|
264
|
+
this.xf.p.copy(position);
|
|
265
|
+
|
|
266
|
+
this.sweep.c.copy(Transform2.mulVec(this.xf, this.sweep.localCenter));
|
|
267
|
+
this.sweep.a = angle;
|
|
268
|
+
this.sweep.c0.copy(this.sweep.c);
|
|
269
|
+
this.sweep.a0 = angle;
|
|
270
|
+
|
|
271
|
+
this.setAwake(true);
|
|
272
|
+
this.synchronizeFixtures(this.xf);
|
|
273
|
+
this.world.newContacts = true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @method setPosition
|
|
278
|
+
* @description Moves the body, keeping its angle.
|
|
279
|
+
* @param {Object} position - `{ x, y }` in physics units
|
|
280
|
+
*/
|
|
281
|
+
setPosition(position) {
|
|
282
|
+
this.setTransform(position, this.sweep.a);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @method setAngle
|
|
287
|
+
* @description Rotates the body, keeping its position.
|
|
288
|
+
* @param {number} angle - Radians
|
|
289
|
+
*/
|
|
290
|
+
setAngle(angle) {
|
|
291
|
+
this.setTransform(this.xf.p, angle);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @method getTransform
|
|
296
|
+
* @description Returns the body's transform (live reference).
|
|
297
|
+
* @returns {Transform2}
|
|
298
|
+
*/
|
|
299
|
+
getTransform() {
|
|
300
|
+
return this.xf;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @method getPosition
|
|
305
|
+
* @description Returns the body origin in world space (live reference).
|
|
306
|
+
* @returns {Vec2}
|
|
307
|
+
*/
|
|
308
|
+
getPosition() {
|
|
309
|
+
return this.xf.p;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* @method getAngle
|
|
314
|
+
* @description Returns the body's angle in radians.
|
|
315
|
+
* @returns {number}
|
|
316
|
+
*/
|
|
317
|
+
getAngle() {
|
|
318
|
+
return this.sweep.a;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* @method getWorldCenter
|
|
323
|
+
* @description Returns the center of mass in world space.
|
|
324
|
+
* @returns {Vec2}
|
|
325
|
+
*/
|
|
326
|
+
getWorldCenter() {
|
|
327
|
+
return this.sweep.c;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @method getLocalCenter
|
|
332
|
+
* @description Returns the center of mass in the body's frame.
|
|
333
|
+
* @returns {Vec2}
|
|
334
|
+
*/
|
|
335
|
+
getLocalCenter() {
|
|
336
|
+
return this.sweep.localCenter;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @method getWorldPoint
|
|
341
|
+
* @description Converts a local point to world space.
|
|
342
|
+
* @param {Object} localPoint
|
|
343
|
+
* @returns {Vec2}
|
|
344
|
+
*/
|
|
345
|
+
getWorldPoint(localPoint) {
|
|
346
|
+
return Transform2.mulVec(this.xf, localPoint);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @method getLocalPoint
|
|
351
|
+
* @description Converts a world point to the body's frame.
|
|
352
|
+
* @param {Object} worldPoint
|
|
353
|
+
* @returns {Vec2}
|
|
354
|
+
*/
|
|
355
|
+
getLocalPoint(worldPoint) {
|
|
356
|
+
return Transform2.mulTVec(this.xf, worldPoint);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @method getWorldVector
|
|
361
|
+
* @description Rotates a local direction into world space.
|
|
362
|
+
* @param {Object} localVector
|
|
363
|
+
* @returns {Vec2}
|
|
364
|
+
*/
|
|
365
|
+
getWorldVector(localVector) {
|
|
366
|
+
return Rot.mulVec(this.xf.q, localVector);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* @method getLocalVector
|
|
371
|
+
* @description Rotates a world direction into the body's frame.
|
|
372
|
+
* @param {Object} worldVector
|
|
373
|
+
* @returns {Vec2}
|
|
374
|
+
*/
|
|
375
|
+
getLocalVector(worldVector) {
|
|
376
|
+
return Rot.mulTVec(this.xf.q, worldVector);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* @method setLinearVelocity
|
|
381
|
+
* @description Sets the velocity of the center of mass.
|
|
382
|
+
* @param {Object} velocity - `{ x, y }` in physics units per second
|
|
383
|
+
*/
|
|
384
|
+
setLinearVelocity(velocity) {
|
|
385
|
+
if (this.type === BodyType.STATIC) return;
|
|
386
|
+
if (velocity.x * velocity.x + velocity.y * velocity.y > 0) {
|
|
387
|
+
this.setAwake(true);
|
|
388
|
+
}
|
|
389
|
+
this.linearVelocity.set(velocity.x, velocity.y);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* @method getLinearVelocity
|
|
394
|
+
* @description Returns the velocity of the center of mass (live reference).
|
|
395
|
+
* @returns {Vec2}
|
|
396
|
+
*/
|
|
397
|
+
getLinearVelocity() {
|
|
398
|
+
return this.linearVelocity;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* @method getLinearVelocityFromWorldPoint
|
|
403
|
+
* @description Velocity of the material point of this body that currently
|
|
404
|
+
* coincides with a world point (linear plus the spin contribution).
|
|
405
|
+
* @param {Object} worldPoint
|
|
406
|
+
* @returns {Vec2}
|
|
407
|
+
*/
|
|
408
|
+
getLinearVelocityFromWorldPoint(worldPoint) {
|
|
409
|
+
return Vec2.add(
|
|
410
|
+
this.linearVelocity,
|
|
411
|
+
Vec2.crossSV(this.angularVelocity, Vec2.sub(worldPoint, this.sweep.c))
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* @method setAngularVelocity
|
|
417
|
+
* @description Sets the spin in radians per second.
|
|
418
|
+
* @param {number} omega
|
|
419
|
+
*/
|
|
420
|
+
setAngularVelocity(omega) {
|
|
421
|
+
if (this.type === BodyType.STATIC) return;
|
|
422
|
+
if (omega * omega > 0) this.setAwake(true);
|
|
423
|
+
this.angularVelocity = omega;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* @method getAngularVelocity
|
|
428
|
+
* @description Returns the spin in radians per second.
|
|
429
|
+
* @returns {number}
|
|
430
|
+
*/
|
|
431
|
+
getAngularVelocity() {
|
|
432
|
+
return this.angularVelocity;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* @method applyForce
|
|
437
|
+
* @description Applies a force at a world point. Forces are cleared at the
|
|
438
|
+
* end of every step, so this belongs in your update loop.
|
|
439
|
+
* @param {Object} force
|
|
440
|
+
* @param {Object} [point] - Defaults to the center of mass
|
|
441
|
+
* @param {boolean} [wake=true]
|
|
442
|
+
*/
|
|
443
|
+
applyForce(force, point = null, wake = true) {
|
|
444
|
+
if (this.type !== BodyType.DYNAMIC) return;
|
|
445
|
+
if (wake && !this.awake) this.setAwake(true);
|
|
446
|
+
if (!this.awake) return;
|
|
447
|
+
|
|
448
|
+
this.force.add(force);
|
|
449
|
+
if (point) {
|
|
450
|
+
this.torque += Vec2.cross(Vec2.sub(point, this.sweep.c), force);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* @method applyForceToCenter
|
|
456
|
+
* @description Applies a force at the center of mass (no torque).
|
|
457
|
+
* @param {Object} force
|
|
458
|
+
* @param {boolean} [wake=true]
|
|
459
|
+
*/
|
|
460
|
+
applyForceToCenter(force, wake = true) {
|
|
461
|
+
this.applyForce(force, null, wake);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @method applyTorque
|
|
466
|
+
* @description Applies a torque about the center of mass.
|
|
467
|
+
* @param {number} torque
|
|
468
|
+
* @param {boolean} [wake=true]
|
|
469
|
+
*/
|
|
470
|
+
applyTorque(torque, wake = true) {
|
|
471
|
+
if (this.type !== BodyType.DYNAMIC) return;
|
|
472
|
+
if (wake && !this.awake) this.setAwake(true);
|
|
473
|
+
if (!this.awake) return;
|
|
474
|
+
this.torque += torque;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* @method applyLinearImpulse
|
|
479
|
+
* @description Applies an instantaneous change in momentum at a world point.
|
|
480
|
+
* Unlike a force, an impulse takes effect immediately: this is what a jump
|
|
481
|
+
* or a knockback should use.
|
|
482
|
+
* @param {Object} impulse
|
|
483
|
+
* @param {Object} [point] - Defaults to the center of mass
|
|
484
|
+
* @param {boolean} [wake=true]
|
|
485
|
+
*/
|
|
486
|
+
applyLinearImpulse(impulse, point = null, wake = true) {
|
|
487
|
+
if (this.type !== BodyType.DYNAMIC) return;
|
|
488
|
+
if (wake && !this.awake) this.setAwake(true);
|
|
489
|
+
if (!this.awake) return;
|
|
490
|
+
|
|
491
|
+
this.linearVelocity.addMul(this.invMass, impulse);
|
|
492
|
+
if (point) {
|
|
493
|
+
this.angularVelocity +=
|
|
494
|
+
this.invI * Vec2.cross(Vec2.sub(point, this.sweep.c), impulse);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* @method applyAngularImpulse
|
|
500
|
+
* @description Applies an instantaneous change in angular momentum.
|
|
501
|
+
* @param {number} impulse
|
|
502
|
+
* @param {boolean} [wake=true]
|
|
503
|
+
*/
|
|
504
|
+
applyAngularImpulse(impulse, wake = true) {
|
|
505
|
+
if (this.type !== BodyType.DYNAMIC) return;
|
|
506
|
+
if (wake && !this.awake) this.setAwake(true);
|
|
507
|
+
if (!this.awake) return;
|
|
508
|
+
this.angularVelocity += this.invI * impulse;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* @method getMass
|
|
513
|
+
* @description Returns the body's mass.
|
|
514
|
+
* @returns {number}
|
|
515
|
+
*/
|
|
516
|
+
getMass() {
|
|
517
|
+
return this.mass;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* @method getInertia
|
|
522
|
+
* @description Returns the rotational inertia about the center of mass.
|
|
523
|
+
* @returns {number}
|
|
524
|
+
*/
|
|
525
|
+
getInertia() {
|
|
526
|
+
return this.I;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* @method resetMassData
|
|
531
|
+
* @description Recomputes mass, center of mass and inertia from the
|
|
532
|
+
* fixtures' densities. Called automatically when fixtures change.
|
|
533
|
+
*/
|
|
534
|
+
resetMassData() {
|
|
535
|
+
this.mass = 0;
|
|
536
|
+
this.invMass = 0;
|
|
537
|
+
this.I = 0;
|
|
538
|
+
this.invI = 0;
|
|
539
|
+
this.sweep.localCenter.setZero();
|
|
540
|
+
|
|
541
|
+
if (this.type === BodyType.STATIC || this.type === BodyType.KINEMATIC) {
|
|
542
|
+
this.sweep.c0.copy(this.xf.p);
|
|
543
|
+
this.sweep.c.copy(this.xf.p);
|
|
544
|
+
this.sweep.a0 = this.sweep.a;
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const localCenter = new Vec2(0, 0);
|
|
549
|
+
for (let f = this.fixtureList; f; f = f.next) {
|
|
550
|
+
if (f.density === 0) continue;
|
|
551
|
+
const massData = f.getMassData();
|
|
552
|
+
this.mass += massData.mass;
|
|
553
|
+
localCenter.addMul(massData.mass, massData.center);
|
|
554
|
+
this.I += massData.I;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (this.mass > 0) {
|
|
558
|
+
this.invMass = 1 / this.mass;
|
|
559
|
+
localCenter.mul(this.invMass);
|
|
560
|
+
} else {
|
|
561
|
+
this.mass = 1;
|
|
562
|
+
this.invMass = 1;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (this.I > 0 && !this.fixedRotation) {
|
|
566
|
+
this.I -= this.mass * Vec2.dot(localCenter, localCenter);
|
|
567
|
+
this.invI = 1 / this.I;
|
|
568
|
+
} else {
|
|
569
|
+
this.I = 0;
|
|
570
|
+
this.invI = 0;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const oldCenter = this.sweep.c.clone();
|
|
574
|
+
this.sweep.localCenter.copy(localCenter);
|
|
575
|
+
this.sweep.c.copy(Transform2.mulVec(this.xf, this.sweep.localCenter));
|
|
576
|
+
this.sweep.c0.copy(this.sweep.c);
|
|
577
|
+
|
|
578
|
+
this.linearVelocity.add(
|
|
579
|
+
Vec2.crossSV(this.angularVelocity, Vec2.sub(this.sweep.c, oldCenter))
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* @method setMassData
|
|
585
|
+
* @description Overrides the computed mass properties.
|
|
586
|
+
* @param {Object} massData - `{ mass, center, I }`
|
|
587
|
+
*/
|
|
588
|
+
setMassData(massData) {
|
|
589
|
+
if (this.type !== BodyType.DYNAMIC) return;
|
|
590
|
+
|
|
591
|
+
this.invMass = 0;
|
|
592
|
+
this.I = 0;
|
|
593
|
+
this.invI = 0;
|
|
594
|
+
|
|
595
|
+
this.mass = massData.mass;
|
|
596
|
+
if (this.mass <= 0) this.mass = 1;
|
|
597
|
+
this.invMass = 1 / this.mass;
|
|
598
|
+
|
|
599
|
+
if (massData.I > 0 && !this.fixedRotation) {
|
|
600
|
+
this.I =
|
|
601
|
+
massData.I - this.mass * Vec2.dot(massData.center, massData.center);
|
|
602
|
+
this.invI = 1 / this.I;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const oldCenter = this.sweep.c.clone();
|
|
606
|
+
this.sweep.localCenter.copy(massData.center);
|
|
607
|
+
this.sweep.c.copy(Transform2.mulVec(this.xf, this.sweep.localCenter));
|
|
608
|
+
this.sweep.c0.copy(this.sweep.c);
|
|
609
|
+
this.linearVelocity.add(
|
|
610
|
+
Vec2.crossSV(this.angularVelocity, Vec2.sub(this.sweep.c, oldCenter))
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* @method setAwake
|
|
616
|
+
* @description Wakes the body or puts it to sleep. A sleeping body is skipped
|
|
617
|
+
* by the solver until something touches it, which is what keeps a settled
|
|
618
|
+
* pile of crates free.
|
|
619
|
+
* @param {boolean} flag
|
|
620
|
+
*/
|
|
621
|
+
setAwake(flag) {
|
|
622
|
+
if (this.type === BodyType.STATIC) return;
|
|
623
|
+
if (flag) {
|
|
624
|
+
this.awake = true;
|
|
625
|
+
this.sleepTime = 0;
|
|
626
|
+
} else {
|
|
627
|
+
this.awake = false;
|
|
628
|
+
this.sleepTime = 0;
|
|
629
|
+
this.linearVelocity.setZero();
|
|
630
|
+
this.angularVelocity = 0;
|
|
631
|
+
this.force.setZero();
|
|
632
|
+
this.torque = 0;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* @method isAwake
|
|
638
|
+
* @description Whether the body is currently simulated.
|
|
639
|
+
* @returns {boolean}
|
|
640
|
+
*/
|
|
641
|
+
isAwake() {
|
|
642
|
+
return this.awake;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* @method setSleepingAllowed
|
|
647
|
+
* @description Allows or forbids this body from ever sleeping.
|
|
648
|
+
* @param {boolean} flag
|
|
649
|
+
*/
|
|
650
|
+
setSleepingAllowed(flag) {
|
|
651
|
+
this.allowSleep = !!flag;
|
|
652
|
+
if (!flag) this.setAwake(true);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* @method isSleepingAllowed
|
|
657
|
+
* @description Whether this body may sleep.
|
|
658
|
+
* @returns {boolean}
|
|
659
|
+
*/
|
|
660
|
+
isSleepingAllowed() {
|
|
661
|
+
return this.allowSleep;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* @method setActive
|
|
666
|
+
* @description Adds or removes the body from collision detection without
|
|
667
|
+
* destroying it.
|
|
668
|
+
* @param {boolean} flag
|
|
669
|
+
*/
|
|
670
|
+
setActive(flag) {
|
|
671
|
+
if (flag === this.active) return;
|
|
672
|
+
this.active = !!flag;
|
|
673
|
+
|
|
674
|
+
if (this.active) {
|
|
675
|
+
for (let f = this.fixtureList; f; f = f.next) {
|
|
676
|
+
f.createProxies(this.world.broadPhase, this.xf);
|
|
677
|
+
}
|
|
678
|
+
this.world.newContacts = true;
|
|
679
|
+
} else {
|
|
680
|
+
for (let f = this.fixtureList; f; f = f.next) {
|
|
681
|
+
f.destroyProxies(this.world.broadPhase);
|
|
682
|
+
}
|
|
683
|
+
for (let i = this.contacts.length - 1; i >= 0; i--) {
|
|
684
|
+
this.world.contactManager.destroy(this.contacts[i]);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* @method isActive
|
|
691
|
+
* @description Whether the body takes part in collision detection.
|
|
692
|
+
* @returns {boolean}
|
|
693
|
+
*/
|
|
694
|
+
isActive() {
|
|
695
|
+
return this.active;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* @method setFixedRotation
|
|
700
|
+
* @description Locks or unlocks the body's rotation. Locking is the usual
|
|
701
|
+
* choice for player characters, which should never topple over.
|
|
702
|
+
* @param {boolean} flag
|
|
703
|
+
*/
|
|
704
|
+
setFixedRotation(flag) {
|
|
705
|
+
if (this.fixedRotation === !!flag) return;
|
|
706
|
+
this.fixedRotation = !!flag;
|
|
707
|
+
this.angularVelocity = 0;
|
|
708
|
+
this.resetMassData();
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* @method isFixedRotation
|
|
713
|
+
* @description Whether rotation is locked.
|
|
714
|
+
* @returns {boolean}
|
|
715
|
+
*/
|
|
716
|
+
isFixedRotation() {
|
|
717
|
+
return this.fixedRotation;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* @method setBullet
|
|
722
|
+
* @description Turns continuous collision detection on for this body, so it
|
|
723
|
+
* is swept against static geometry instead of teleporting between steps.
|
|
724
|
+
* @param {boolean} flag
|
|
725
|
+
*/
|
|
726
|
+
setBullet(flag) {
|
|
727
|
+
this.bullet = !!flag;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* @method isBullet
|
|
732
|
+
* @description Whether continuous collision detection is enabled.
|
|
733
|
+
* @returns {boolean}
|
|
734
|
+
*/
|
|
735
|
+
isBullet() {
|
|
736
|
+
return this.bullet;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* @method setLinearDamping
|
|
741
|
+
* @description Sets the drag applied to linear motion each step.
|
|
742
|
+
* @param {number} damping
|
|
743
|
+
*/
|
|
744
|
+
setLinearDamping(damping) {
|
|
745
|
+
this.linearDamping = damping;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* @method getLinearDamping
|
|
750
|
+
* @description Returns the linear damping.
|
|
751
|
+
* @returns {number}
|
|
752
|
+
*/
|
|
753
|
+
getLinearDamping() {
|
|
754
|
+
return this.linearDamping;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* @method setAngularDamping
|
|
759
|
+
* @description Sets the drag applied to rotation each step.
|
|
760
|
+
* @param {number} damping
|
|
761
|
+
*/
|
|
762
|
+
setAngularDamping(damping) {
|
|
763
|
+
this.angularDamping = damping;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* @method getAngularDamping
|
|
768
|
+
* @description Returns the angular damping.
|
|
769
|
+
* @returns {number}
|
|
770
|
+
*/
|
|
771
|
+
getAngularDamping() {
|
|
772
|
+
return this.angularDamping;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* @method setGravityScale
|
|
777
|
+
* @description Scales how strongly gravity pulls on this body (0 disables it,
|
|
778
|
+
* 2 makes it twice as heavy-feeling).
|
|
779
|
+
* @param {number} scale
|
|
780
|
+
*/
|
|
781
|
+
setGravityScale(scale) {
|
|
782
|
+
this.gravityScale = scale;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* @method getGravityScale
|
|
787
|
+
* @description Returns the gravity multiplier.
|
|
788
|
+
* @returns {number}
|
|
789
|
+
*/
|
|
790
|
+
getGravityScale() {
|
|
791
|
+
return this.gravityScale;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* @method getContactList
|
|
796
|
+
* @description Returns the contacts this body is part of.
|
|
797
|
+
* @returns {Array<Contact>}
|
|
798
|
+
*/
|
|
799
|
+
getContactList() {
|
|
800
|
+
return this.contacts;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* @method synchronizeTransform
|
|
805
|
+
* @description Rebuilds the body transform from the sweep's current state.
|
|
806
|
+
* @private
|
|
807
|
+
*/
|
|
808
|
+
synchronizeTransform() {
|
|
809
|
+
this.xf.q.set(this.sweep.a);
|
|
810
|
+
this.xf.p.set(
|
|
811
|
+
this.sweep.c.x -
|
|
812
|
+
(this.xf.q.c * this.sweep.localCenter.x -
|
|
813
|
+
this.xf.q.s * this.sweep.localCenter.y),
|
|
814
|
+
this.sweep.c.y -
|
|
815
|
+
(this.xf.q.s * this.sweep.localCenter.x +
|
|
816
|
+
this.xf.q.c * this.sweep.localCenter.y)
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* @method synchronizeFixtures
|
|
822
|
+
* @description Updates the broadphase proxies to span where the body was at
|
|
823
|
+
* the start of the step and where it is now.
|
|
824
|
+
* @param {Transform2} [xf1] - Optional explicit "before" transform
|
|
825
|
+
* @private
|
|
826
|
+
*/
|
|
827
|
+
synchronizeFixtures(xf1 = null) {
|
|
828
|
+
if (!this.active) return;
|
|
829
|
+
let from = xf1;
|
|
830
|
+
if (!from) {
|
|
831
|
+
from = new Transform2();
|
|
832
|
+
from.q.set(this.sweep.a0);
|
|
833
|
+
from.p.set(
|
|
834
|
+
this.sweep.c0.x -
|
|
835
|
+
(from.q.c * this.sweep.localCenter.x -
|
|
836
|
+
from.q.s * this.sweep.localCenter.y),
|
|
837
|
+
this.sweep.c0.y -
|
|
838
|
+
(from.q.s * this.sweep.localCenter.x +
|
|
839
|
+
from.q.c * this.sweep.localCenter.y)
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
for (let f = this.fixtureList; f; f = f.next) {
|
|
843
|
+
f.synchronize(this.world.broadPhase, from, this.xf);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* @method advance
|
|
849
|
+
* @description Moves the body to a fraction of its sweep, used by continuous
|
|
850
|
+
* collision detection when an impact is found mid-step.
|
|
851
|
+
* @param {number} alpha
|
|
852
|
+
* @private
|
|
853
|
+
*/
|
|
854
|
+
advance(alpha) {
|
|
855
|
+
this.sweep.advance(alpha);
|
|
856
|
+
this.sweep.c.copy(this.sweep.c0);
|
|
857
|
+
this.sweep.a = this.sweep.a0;
|
|
858
|
+
this.synchronizeTransform();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
export { Body, BodyType };
|