emeraldengine 2.2.1 → 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 +2198 -968
- package/dist/types/index.d.ts +75 -32
- package/dist/types/src/Animator.d.ts +50 -0
- package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
- package/dist/types/src/Camera.d.ts +122 -0
- package/dist/types/src/CameraController.d.ts +107 -0
- package/dist/types/src/CanvasText.d.ts +91 -0
- package/dist/types/src/CollisionLayers.d.ts +58 -0
- package/dist/types/{Color.d.ts → src/Color.d.ts} +6 -6
- package/dist/types/src/Coroutine.d.ts +111 -0
- package/dist/types/src/DebugOverlay.d.ts +86 -0
- package/dist/types/src/Drawable.d.ts +271 -0
- package/dist/types/src/Easing.d.ts +22 -0
- package/dist/types/src/Emerald.d.ts +420 -0
- package/dist/types/src/EmeraldDB.d.ts +159 -0
- package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
- package/dist/types/src/GLUtils.d.ts +4 -0
- package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
- package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +75 -23
- package/dist/types/src/Interpolator.d.ts +66 -0
- package/dist/types/src/Material.d.ts +87 -0
- package/dist/types/src/MathUtils.d.ts +81 -0
- package/dist/types/src/ParticleEmitter.d.ts +131 -0
- package/dist/types/src/Physics.d.ts +337 -0
- package/dist/types/src/Pool.d.ts +53 -0
- package/dist/types/src/PostEffects.d.ts +68 -0
- package/dist/types/src/PostProcessor.d.ts +124 -0
- package/dist/types/src/RenderTarget.d.ts +56 -0
- package/dist/types/src/Scene.d.ts +62 -0
- package/dist/types/src/ScreenEffects.d.ts +111 -0
- package/dist/types/src/Serializer.d.ts +86 -0
- package/dist/types/src/Shaders.d.ts +2 -0
- package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
- package/dist/types/src/SpatialGrid.d.ts +50 -0
- package/dist/types/src/SpriteBatch.d.ts +89 -0
- package/dist/types/src/StateMachine.d.ts +59 -0
- package/dist/types/src/Storage.d.ts +89 -0
- package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
- package/dist/types/src/TextureAtlas.d.ts +55 -0
- package/dist/types/src/Tilemap.d.ts +91 -0
- package/dist/types/src/Time.d.ts +53 -0
- package/dist/types/src/Timer.d.ts +54 -0
- package/dist/types/src/Transform.d.ts +94 -0
- package/dist/types/src/Tween.d.ts +81 -0
- package/dist/types/src/UI.d.ts +121 -0
- package/dist/types/src/components/Behaviour.d.ts +71 -0
- package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
- package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
- package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
- package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
- package/dist/types/src/components/Collider.d.ts +102 -0
- package/dist/types/src/components/GameObject.d.ts +135 -0
- package/dist/types/src/components/PolygonCollider.d.ts +33 -0
- package/dist/types/src/components/RigidBody.d.ts +456 -0
- package/dist/types/src/importers/Aseprite.d.ts +79 -0
- package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
- package/dist/types/src/importers/TiledMap.d.ts +62 -0
- package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
- package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
- package/dist/types/src/managers/AssetManager.d.ts +116 -0
- package/dist/types/src/managers/AudioManager.d.ts +259 -0
- package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
- package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
- package/dist/types/src/managers/GLManager.d.ts +84 -0
- package/dist/types/src/managers/GLState.d.ts +37 -0
- package/dist/types/src/managers/Gamepad.d.ts +102 -0
- package/dist/types/src/managers/IDManager.d.ts +31 -0
- package/dist/types/src/managers/InputManager.d.ts +381 -0
- package/dist/types/src/managers/NetworkManager.d.ts +93 -0
- package/dist/types/src/managers/RenderStats.d.ts +34 -0
- package/dist/types/src/managers/SceneManager.d.ts +44 -0
- package/dist/types/src/managers/ShaderManager.d.ts +55 -0
- package/dist/types/src/managers/TextureManager.d.ts +102 -0
- package/dist/types/src/particlesystem/Particle.d.ts +64 -0
- package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
- package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
- 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 +78 -0
- package/package.json +73 -60
- package/src/Animator.js +95 -0
- package/src/BitmapText.js +6 -5
- package/src/Camera.js +183 -0
- package/src/CameraController.js +192 -0
- package/src/CanvasText.js +281 -0
- package/src/CollisionLayers.js +86 -0
- package/src/Color.js +26 -18
- package/src/Coroutine.js +259 -0
- package/src/DebugOverlay.js +246 -0
- package/src/Drawable.js +842 -582
- package/src/Easing.js +57 -0
- package/src/Emerald.js +1150 -459
- package/src/EmeraldDB.js +328 -0
- package/src/FPSCounter.js +43 -43
- package/src/GLUtils.js +60 -67
- package/src/Instance.js +41 -5
- package/src/InstancedTexture.js +308 -127
- package/src/Interpolator.js +124 -0
- package/src/Material.js +202 -0
- package/src/MathUtils.js +134 -0
- package/src/ParticleEmitter.js +284 -0
- package/src/Physics.js +431 -40
- package/src/Pool.js +85 -0
- package/src/PostEffects.js +296 -0
- package/src/PostProcessor.js +304 -0
- package/src/RenderTarget.js +134 -0
- package/src/Scene.js +115 -83
- package/src/ScreenEffects.js +266 -0
- package/src/Serializer.js +131 -0
- package/src/Shaders.js +150 -165
- package/src/Shapes.js +118 -129
- package/src/SpatialGrid.js +111 -0
- package/src/SpriteBatch.js +299 -0
- package/src/StateMachine.js +82 -0
- package/src/Storage.js +175 -47
- package/src/Texture.js +58 -67
- package/src/TextureAtlas.js +96 -0
- package/src/Tilemap.js +274 -0
- package/src/Time.js +51 -6
- package/src/Timer.js +99 -0
- package/src/Transform.js +100 -7
- package/src/Tween.js +160 -0
- package/src/UI.js +394 -0
- package/src/components/Behaviour.js +90 -0
- package/src/components/BoxCollider.js +29 -12
- package/src/components/BoxColliderDebug.js +3 -4
- package/src/components/CircleCollider.js +26 -12
- package/src/components/CircleColliderDebug.js +25 -24
- package/src/components/Collider.js +150 -34
- package/src/components/GameObject.js +130 -21
- package/src/components/PolygonCollider.js +55 -0
- package/src/components/RigidBody.js +556 -8
- package/src/importers/Aseprite.js +142 -0
- package/src/importers/ForgeLevel.js +581 -0
- package/src/importers/TiledMap.js +158 -0
- package/src/lights/DirectionalLight.js +6 -15
- package/src/lights/PointLight.js +4 -4
- package/src/managers/AssetManager.js +239 -0
- package/src/managers/AudioManager.js +565 -146
- package/src/managers/EventManager.js +488 -477
- package/src/managers/GLManager.js +57 -0
- package/src/managers/GLState.js +70 -0
- package/src/managers/Gamepad.js +126 -0
- package/src/managers/IDManager.js +24 -2
- package/src/managers/InputManager.js +905 -0
- package/src/managers/NetworkManager.js +178 -0
- package/src/managers/RenderStats.js +34 -0
- package/src/managers/SceneManager.js +30 -0
- package/src/managers/ShaderManager.js +0 -2
- package/src/managers/TextureManager.js +218 -0
- package/src/particlesystem/Particle.js +82 -7
- package/src/particlesystem/ParticleSettings.js +21 -3
- package/src/particlesystem/Particles.js +80 -31
- 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
- package/dist/types/Drawable.d.ts +0 -157
- package/dist/types/Emerald.d.ts +0 -73
- package/dist/types/GLUtils.d.ts +0 -6
- package/dist/types/Physics.d.ts +0 -139
- package/dist/types/Scene.d.ts +0 -39
- package/dist/types/Shaders.d.ts +0 -4
- package/dist/types/Storage.d.ts +0 -46
- package/dist/types/Time.d.ts +0 -22
- package/dist/types/Transform.d.ts +0 -41
- package/dist/types/components/BoxColliderDebug.d.ts +0 -19
- package/dist/types/components/CircleColliderDebug.d.ts +0 -19
- package/dist/types/components/Collider.d.ts +0 -53
- package/dist/types/components/GameObject.d.ts +0 -72
- package/dist/types/components/RigidBody.d.ts +0 -104
- package/dist/types/managers/AudioManager.d.ts +0 -60
- package/dist/types/managers/GLManager.d.ts +0 -47
- package/dist/types/managers/IDManager.d.ts +0 -21
- package/dist/types/managers/SceneManager.d.ts +0 -22
- package/dist/types/particlesystem/Particle.d.ts +0 -42
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
import { Vec2 } from "./Math2D.js";
|
|
2
|
+
import Settings from "./Settings.js";
|
|
3
|
+
import AABB from "./AABB.js";
|
|
4
|
+
import { BroadPhase } from "./BroadPhase.js";
|
|
5
|
+
import { Body } from "./Body.js";
|
|
6
|
+
import { BodyType } from "./BodyType.js";
|
|
7
|
+
import { ContactManager, shouldCollide } from "./Contact.js";
|
|
8
|
+
import Island from "./Island.js";
|
|
9
|
+
import { DistanceProxy } from "./Distance.js";
|
|
10
|
+
import { timeOfImpact, TOIState } from "./TimeOfImpact.js";
|
|
11
|
+
import { JointType } from "./Joint.js";
|
|
12
|
+
import DistanceJoint from "./DistanceJoint.js";
|
|
13
|
+
import RevoluteJoint from "./RevoluteJoint.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @function removeFrom
|
|
17
|
+
* @description Splices the first occurrence of `item` out of `array`.
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
function removeFrom(array, item) {
|
|
21
|
+
const index = array.indexOf(item);
|
|
22
|
+
if (index >= 0) array.splice(index, 1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const EVENTS = [
|
|
26
|
+
"begin-contact",
|
|
27
|
+
"end-contact",
|
|
28
|
+
"pre-solve",
|
|
29
|
+
"post-solve",
|
|
30
|
+
"remove-body",
|
|
31
|
+
"remove-fixture",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @class World
|
|
36
|
+
* @description The physics world: it owns the bodies, finds which ones touch,
|
|
37
|
+
* and advances them all by one time step. This is Emerald's own rigid-body
|
|
38
|
+
* engine: broadphase, narrowphase, an impulse solver and continuous collision
|
|
39
|
+
* detection, with no external physics dependency.
|
|
40
|
+
*
|
|
41
|
+
* Games normally reach it through {@link Physics}, which adds the pixel/meter
|
|
42
|
+
* conversion, a fixed-timestep accumulator and collision routing to
|
|
43
|
+
* {@link Behaviour} components.
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} [def] - `{ gravity, allowSleep, warmStarting,
|
|
46
|
+
* continuousPhysics, velocityThreshold, velocityIterations,
|
|
47
|
+
* positionIterations }`
|
|
48
|
+
*/
|
|
49
|
+
class World {
|
|
50
|
+
constructor(def = {}) {
|
|
51
|
+
this.gravity = new Vec2(def.gravity || { x: 0, y: 0 });
|
|
52
|
+
this.broadPhase = new BroadPhase();
|
|
53
|
+
this.contactManager = new ContactManager(this);
|
|
54
|
+
|
|
55
|
+
/** Head of the body list; walk it with {@link Body#getNext}. @private */
|
|
56
|
+
this.bodyList = null;
|
|
57
|
+
this.bodyCount = 0;
|
|
58
|
+
|
|
59
|
+
/** Head of the joint list. @private */
|
|
60
|
+
this.jointList = null;
|
|
61
|
+
this.jointCount = 0;
|
|
62
|
+
|
|
63
|
+
this.allowSleep = def.allowSleep !== false;
|
|
64
|
+
this.warmStarting = def.warmStarting !== false;
|
|
65
|
+
this.continuousPhysics = def.continuousPhysics !== false;
|
|
66
|
+
this.autoClearForces = def.autoClearForces !== false;
|
|
67
|
+
|
|
68
|
+
this.velocityThreshold =
|
|
69
|
+
def.velocityThreshold ?? Settings.velocityThreshold;
|
|
70
|
+
this.velocityIterations =
|
|
71
|
+
def.velocityIterations ?? Settings.velocityIterations;
|
|
72
|
+
this.positionIterations =
|
|
73
|
+
def.positionIterations ?? Settings.positionIterations;
|
|
74
|
+
|
|
75
|
+
/** Optional `(fixtureA, fixtureB) => boolean` veto on new contacts. */
|
|
76
|
+
this.contactFilter = null;
|
|
77
|
+
|
|
78
|
+
/** @private */
|
|
79
|
+
this.newContacts = false;
|
|
80
|
+
/** @private */
|
|
81
|
+
this.locked = false;
|
|
82
|
+
/** @private */
|
|
83
|
+
this.island = new Island();
|
|
84
|
+
/** @private */
|
|
85
|
+
this.listeners = Object.create(null);
|
|
86
|
+
for (const name of EVENTS) this.listeners[name] = [];
|
|
87
|
+
|
|
88
|
+
/** @private */
|
|
89
|
+
this._toiProxyA = new DistanceProxy();
|
|
90
|
+
/** @private */
|
|
91
|
+
this._toiProxyB = new DistanceProxy();
|
|
92
|
+
/** What the last time-of-impact search hit. @private */
|
|
93
|
+
this._toiHitBody = null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @method on
|
|
98
|
+
* @description Subscribes to a world event. Supported events:
|
|
99
|
+
* `begin-contact`, `end-contact`, `pre-solve`, `post-solve`, `remove-body`
|
|
100
|
+
* and `remove-fixture`.
|
|
101
|
+
* @param {string} name - Event name
|
|
102
|
+
* @param {Function} listener - Called with the contact (or body/fixture)
|
|
103
|
+
* @returns {World} - this
|
|
104
|
+
*/
|
|
105
|
+
on(name, listener) {
|
|
106
|
+
if (!this.listeners[name]) this.listeners[name] = [];
|
|
107
|
+
if (typeof listener === "function") this.listeners[name].push(listener);
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @method off
|
|
113
|
+
* @description Unsubscribes a listener.
|
|
114
|
+
* @param {string} name
|
|
115
|
+
* @param {Function} listener
|
|
116
|
+
* @returns {World} - this
|
|
117
|
+
*/
|
|
118
|
+
off(name, listener) {
|
|
119
|
+
const list = this.listeners[name];
|
|
120
|
+
if (!list) return this;
|
|
121
|
+
const index = list.indexOf(listener);
|
|
122
|
+
if (index >= 0) list.splice(index, 1);
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @method dispatch
|
|
128
|
+
* @description Fires a world event. Listener errors are contained so one bad
|
|
129
|
+
* callback can't halt the simulation mid-step.
|
|
130
|
+
* @param {string} name
|
|
131
|
+
* @param {...*} args
|
|
132
|
+
*/
|
|
133
|
+
dispatch(name, ...args) {
|
|
134
|
+
const list = this.listeners[name];
|
|
135
|
+
if (!list || list.length === 0) return;
|
|
136
|
+
for (const listener of list) {
|
|
137
|
+
try {
|
|
138
|
+
listener(...args);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(`[Physics] ${name} listener threw:`, error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @method createBody
|
|
147
|
+
* @description Creates a body in this world.
|
|
148
|
+
* @param {Object} [def] - See {@link Body}
|
|
149
|
+
* @returns {Body}
|
|
150
|
+
*/
|
|
151
|
+
createBody(def = {}) {
|
|
152
|
+
if (this.locked) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
"[Physics] Cannot create a body while the world is stepping."
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const body = new Body(this, def);
|
|
158
|
+
body.prev = null;
|
|
159
|
+
body.next = this.bodyList;
|
|
160
|
+
if (this.bodyList) this.bodyList.prev = body;
|
|
161
|
+
this.bodyList = body;
|
|
162
|
+
this.bodyCount++;
|
|
163
|
+
return body;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @method destroyBody
|
|
168
|
+
* @description Removes a body, its fixtures and all its contacts.
|
|
169
|
+
* @param {Body} body
|
|
170
|
+
*/
|
|
171
|
+
destroyBody(body) {
|
|
172
|
+
if (!body || body.world !== this) return;
|
|
173
|
+
if (this.locked) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"[Physics] Cannot destroy a body while the world is stepping."
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (let i = body.joints.length - 1; i >= 0; i--) {
|
|
180
|
+
this.destroyJoint(body.joints[i]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (let i = body.contacts.length - 1; i >= 0; i--) {
|
|
184
|
+
this.contactManager.destroy(body.contacts[i]);
|
|
185
|
+
}
|
|
186
|
+
body.contacts.length = 0;
|
|
187
|
+
|
|
188
|
+
let fixture = body.fixtureList;
|
|
189
|
+
while (fixture) {
|
|
190
|
+
const next = fixture.next;
|
|
191
|
+
this.dispatch("remove-fixture", fixture);
|
|
192
|
+
if (body.active) fixture.destroyProxies(this.broadPhase);
|
|
193
|
+
fixture.body = null;
|
|
194
|
+
fixture.next = null;
|
|
195
|
+
fixture = next;
|
|
196
|
+
}
|
|
197
|
+
body.fixtureList = null;
|
|
198
|
+
body.fixtureCount = 0;
|
|
199
|
+
|
|
200
|
+
this.dispatch("remove-body", body);
|
|
201
|
+
|
|
202
|
+
if (body.prev) body.prev.next = body.next;
|
|
203
|
+
if (body.next) body.next.prev = body.prev;
|
|
204
|
+
if (body === this.bodyList) this.bodyList = body.next;
|
|
205
|
+
body.prev = null;
|
|
206
|
+
body.next = null;
|
|
207
|
+
body.world = null;
|
|
208
|
+
this.bodyCount--;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @method getBodyList
|
|
213
|
+
* @description Returns the first body; walk the rest with
|
|
214
|
+
* {@link Body#getNext}.
|
|
215
|
+
* @returns {Body|null}
|
|
216
|
+
*/
|
|
217
|
+
getBodyList() {
|
|
218
|
+
return this.bodyList;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @method getBodyCount
|
|
223
|
+
* @description Number of bodies in the world.
|
|
224
|
+
* @returns {number}
|
|
225
|
+
*/
|
|
226
|
+
getBodyCount() {
|
|
227
|
+
return this.bodyCount;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @method createJoint
|
|
232
|
+
* @description Creates a constraint between two bodies, a
|
|
233
|
+
* {@link DistanceJoint} or a {@link RevoluteJoint}, chosen by `def.type`
|
|
234
|
+
* (see {@link JointType}). Wakes both bodies, since a joint pulling on a
|
|
235
|
+
* sleeping body needs it simulating again immediately.
|
|
236
|
+
* @param {Object} def - `{ type, bodyA, bodyB, ... }`, forwarded to the
|
|
237
|
+
* chosen joint's constructor
|
|
238
|
+
* @returns {Joint}
|
|
239
|
+
*/
|
|
240
|
+
createJoint(def) {
|
|
241
|
+
if (this.locked) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
"[Physics] Cannot create a joint while the world is stepping."
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const joint =
|
|
248
|
+
def.type === JointType.REVOLUTE
|
|
249
|
+
? new RevoluteJoint(def)
|
|
250
|
+
: new DistanceJoint(def);
|
|
251
|
+
|
|
252
|
+
joint.prev = null;
|
|
253
|
+
joint.next = this.jointList;
|
|
254
|
+
if (this.jointList) this.jointList.prev = joint;
|
|
255
|
+
this.jointList = joint;
|
|
256
|
+
this.jointCount++;
|
|
257
|
+
|
|
258
|
+
joint.bodyA.joints.push(joint);
|
|
259
|
+
joint.bodyB.joints.push(joint);
|
|
260
|
+
joint.bodyA.setAwake(true);
|
|
261
|
+
joint.bodyB.setAwake(true);
|
|
262
|
+
|
|
263
|
+
if (!joint.collideConnected) {
|
|
264
|
+
for (let i = joint.bodyA.contacts.length - 1; i >= 0; i--) {
|
|
265
|
+
const contact = joint.bodyA.contacts[i];
|
|
266
|
+
const other =
|
|
267
|
+
contact.fixtureA.body === joint.bodyA
|
|
268
|
+
? contact.fixtureB.body
|
|
269
|
+
: contact.fixtureA.body;
|
|
270
|
+
if (other === joint.bodyB) this.contactManager.destroy(contact);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return joint;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* @method destroyJoint
|
|
279
|
+
* @description Removes a joint. Safe to call as part of destroying one of
|
|
280
|
+
* its bodies (see {@link World#destroyBody}).
|
|
281
|
+
* @param {Joint} joint
|
|
282
|
+
*/
|
|
283
|
+
destroyJoint(joint) {
|
|
284
|
+
if (!joint) return;
|
|
285
|
+
if (this.locked) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
"[Physics] Cannot destroy a joint while the world is stepping."
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
joint.bodyA.setAwake(true);
|
|
292
|
+
joint.bodyB.setAwake(true);
|
|
293
|
+
removeFrom(joint.bodyA.joints, joint);
|
|
294
|
+
removeFrom(joint.bodyB.joints, joint);
|
|
295
|
+
|
|
296
|
+
if (joint.prev) joint.prev.next = joint.next;
|
|
297
|
+
if (joint.next) joint.next.prev = joint.prev;
|
|
298
|
+
if (joint === this.jointList) this.jointList = joint.next;
|
|
299
|
+
joint.prev = null;
|
|
300
|
+
joint.next = null;
|
|
301
|
+
this.jointCount--;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @method getJointList
|
|
306
|
+
* @description Returns the first joint; walk the rest with `joint.next`.
|
|
307
|
+
* @returns {Joint|null}
|
|
308
|
+
*/
|
|
309
|
+
getJointList() {
|
|
310
|
+
return this.jointList;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @method getJointCount
|
|
315
|
+
* @returns {number}
|
|
316
|
+
*/
|
|
317
|
+
getJointCount() {
|
|
318
|
+
return this.jointCount;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* @method getContactList
|
|
323
|
+
* @description Returns every live contact (touching or merely close enough
|
|
324
|
+
* for their bounding boxes to overlap).
|
|
325
|
+
* @returns {Array<Contact>}
|
|
326
|
+
*/
|
|
327
|
+
getContactList() {
|
|
328
|
+
return this.contactManager.contacts;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* @method getContactCount
|
|
333
|
+
* @description Number of live contacts.
|
|
334
|
+
* @returns {number}
|
|
335
|
+
*/
|
|
336
|
+
getContactCount() {
|
|
337
|
+
return this.contactManager.contacts.length;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* @method setGravity
|
|
342
|
+
* @description Sets world gravity in physics units per second squared.
|
|
343
|
+
* @param {Object} gravity - `{ x, y }`
|
|
344
|
+
*/
|
|
345
|
+
setGravity(gravity) {
|
|
346
|
+
this.gravity.set(gravity.x, gravity.y);
|
|
347
|
+
for (let body = this.bodyList; body; body = body.next) {
|
|
348
|
+
if (body.type === BodyType.DYNAMIC) body.setAwake(true);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @method getGravity
|
|
354
|
+
* @description Returns world gravity.
|
|
355
|
+
* @returns {Vec2}
|
|
356
|
+
*/
|
|
357
|
+
getGravity() {
|
|
358
|
+
return this.gravity;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* @method isLocked
|
|
363
|
+
* @description True while a step is in progress, when the body/fixture graph
|
|
364
|
+
* must not be modified.
|
|
365
|
+
* @returns {boolean}
|
|
366
|
+
*/
|
|
367
|
+
isLocked() {
|
|
368
|
+
return this.locked;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @method setAllowSleeping
|
|
373
|
+
* @description Enables or disables sleeping world-wide.
|
|
374
|
+
* @param {boolean} flag
|
|
375
|
+
*/
|
|
376
|
+
setAllowSleeping(flag) {
|
|
377
|
+
if (flag === this.allowSleep) return;
|
|
378
|
+
this.allowSleep = !!flag;
|
|
379
|
+
if (!this.allowSleep) {
|
|
380
|
+
for (let body = this.bodyList; body; body = body.next)
|
|
381
|
+
body.setAwake(true);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @method step
|
|
387
|
+
* @description Advances the simulation by `dt` seconds: refresh contacts,
|
|
388
|
+
* solve the islands, then sweep any continuous (bullet) bodies.
|
|
389
|
+
* @param {number} dt - Time step in seconds
|
|
390
|
+
* @param {number} [velocityIterations] - Solver velocity iterations
|
|
391
|
+
* @param {number} [positionIterations] - Solver position iterations
|
|
392
|
+
*/
|
|
393
|
+
step(
|
|
394
|
+
dt,
|
|
395
|
+
velocityIterations = this.velocityIterations,
|
|
396
|
+
positionIterations = this.positionIterations
|
|
397
|
+
) {
|
|
398
|
+
if (!Number.isFinite(dt) || dt < 0) return;
|
|
399
|
+
|
|
400
|
+
if (this.newContacts) {
|
|
401
|
+
this.contactManager.findNewContacts();
|
|
402
|
+
this.newContacts = false;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
this.locked = true;
|
|
406
|
+
|
|
407
|
+
const step = {
|
|
408
|
+
dt,
|
|
409
|
+
invDt: dt > 0 ? 1 / dt : 0,
|
|
410
|
+
velocityIterations,
|
|
411
|
+
positionIterations,
|
|
412
|
+
warmStarting: this.warmStarting,
|
|
413
|
+
velocityThreshold: this.velocityThreshold,
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
this.contactManager.collide();
|
|
417
|
+
|
|
418
|
+
if (dt > 0) {
|
|
419
|
+
this.solve(step);
|
|
420
|
+
if (this.continuousPhysics) this.solveContinuous();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this.locked = false;
|
|
424
|
+
|
|
425
|
+
if (this.autoClearForces) this.clearForces();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* @method solve
|
|
430
|
+
* @description Groups the awake bodies into islands of things that touch and
|
|
431
|
+
* solves each island on its own.
|
|
432
|
+
* @param {Object} step
|
|
433
|
+
* @private
|
|
434
|
+
*/
|
|
435
|
+
solve(step) {
|
|
436
|
+
for (let body = this.bodyList; body; body = body.next) {
|
|
437
|
+
body.islandFlag = false;
|
|
438
|
+
body.sweep.alpha0 = 0;
|
|
439
|
+
}
|
|
440
|
+
for (const contact of this.contactManager.contacts) {
|
|
441
|
+
contact.islandFlag = false;
|
|
442
|
+
}
|
|
443
|
+
for (let joint = this.jointList; joint; joint = joint.next) {
|
|
444
|
+
joint.islandFlag = false;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const island = this.island;
|
|
448
|
+
const stack = [];
|
|
449
|
+
|
|
450
|
+
for (let seed = this.bodyList; seed; seed = seed.next) {
|
|
451
|
+
if (seed.islandFlag) continue;
|
|
452
|
+
if (!seed.awake || !seed.active) continue;
|
|
453
|
+
if (seed.type === BodyType.STATIC) continue;
|
|
454
|
+
|
|
455
|
+
island.clear();
|
|
456
|
+
stack.length = 0;
|
|
457
|
+
stack.push(seed);
|
|
458
|
+
seed.islandFlag = true;
|
|
459
|
+
|
|
460
|
+
while (stack.length > 0) {
|
|
461
|
+
const body = stack.pop();
|
|
462
|
+
island.addBody(body);
|
|
463
|
+
body.awake = true;
|
|
464
|
+
|
|
465
|
+
if (body.type === BodyType.STATIC) continue;
|
|
466
|
+
|
|
467
|
+
for (const contact of body.contacts) {
|
|
468
|
+
if (contact.islandFlag) continue;
|
|
469
|
+
if (!contact.touching || !contact.enabled) continue;
|
|
470
|
+
if (contact.fixtureA.sensor || contact.fixtureB.sensor) continue;
|
|
471
|
+
|
|
472
|
+
island.addContact(contact);
|
|
473
|
+
contact.islandFlag = true;
|
|
474
|
+
|
|
475
|
+
const other =
|
|
476
|
+
contact.fixtureA.body === body
|
|
477
|
+
? contact.fixtureB.body
|
|
478
|
+
: contact.fixtureA.body;
|
|
479
|
+
if (other.islandFlag) continue;
|
|
480
|
+
stack.push(other);
|
|
481
|
+
other.islandFlag = true;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
for (const joint of body.joints) {
|
|
485
|
+
if (joint.islandFlag) continue;
|
|
486
|
+
const other = joint.bodyA === body ? joint.bodyB : joint.bodyA;
|
|
487
|
+
if (!other.active) continue;
|
|
488
|
+
|
|
489
|
+
island.addJoint(joint);
|
|
490
|
+
joint.islandFlag = true;
|
|
491
|
+
|
|
492
|
+
if (other.islandFlag) continue;
|
|
493
|
+
stack.push(other);
|
|
494
|
+
other.islandFlag = true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
island.solve(step, this.gravity, this.allowSleep);
|
|
499
|
+
|
|
500
|
+
for (const body of island.bodies) {
|
|
501
|
+
if (body.type === BodyType.STATIC) body.islandFlag = false;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
for (let body = this.bodyList; body; body = body.next) {
|
|
506
|
+
if (!body.islandFlag) continue;
|
|
507
|
+
if (body.type === BodyType.STATIC) continue;
|
|
508
|
+
body.synchronizeFixtures();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
this.contactManager.findNewContacts();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* @method solveContinuous
|
|
516
|
+
* @description Sweeps every bullet body against the static and kinematic
|
|
517
|
+
* geometry it passed through this step, and rewinds it to the first impact.
|
|
518
|
+
* Without this, a body moving further than its own size in one step would
|
|
519
|
+
* simply appear on the far side of a thin wall.
|
|
520
|
+
* @private
|
|
521
|
+
*/
|
|
522
|
+
solveContinuous() {
|
|
523
|
+
for (let body = this.bodyList; body; body = body.next) {
|
|
524
|
+
if (!body.bullet) continue;
|
|
525
|
+
if (body.type !== BodyType.DYNAMIC) continue;
|
|
526
|
+
if (!body.awake || !body.active) continue;
|
|
527
|
+
|
|
528
|
+
const dx = body.sweep.c.x - body.sweep.c0.x;
|
|
529
|
+
const dy = body.sweep.c.y - body.sweep.c0.y;
|
|
530
|
+
const travelSq = dx * dx + dy * dy;
|
|
531
|
+
if (travelSq === 0) continue;
|
|
532
|
+
|
|
533
|
+
const alpha = this._findTimeOfImpact(body);
|
|
534
|
+
if (alpha === null) continue;
|
|
535
|
+
|
|
536
|
+
body.advance(alpha);
|
|
537
|
+
this._absorbImpactVelocity(body, dx, dy);
|
|
538
|
+
body.synchronizeFixtures();
|
|
539
|
+
this.newContacts = true;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (this.newContacts) {
|
|
543
|
+
this.contactManager.findNewContacts();
|
|
544
|
+
this.newContacts = false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* @method _absorbImpactVelocity
|
|
550
|
+
* @description Takes the excess approach speed out of a body that a rewind
|
|
551
|
+
* stopped short of a *moving* obstacle, leaving it travelling with whatever
|
|
552
|
+
* it landed on.
|
|
553
|
+
*
|
|
554
|
+
* The rewind puts the body where the two first touched, but the obstacle
|
|
555
|
+
* carries on to the end of the step, so the body is left hovering by up to
|
|
556
|
+
* one step of the obstacle's travel, too far apart for the discrete solver
|
|
557
|
+
* to see a contact next step. Keeping the body's original velocity as well
|
|
558
|
+
* means it arrives even faster the following step, is rewound again, and
|
|
559
|
+
* never lands: a rider on a descending platform would fall for ever without
|
|
560
|
+
* ever standing on it. Dropping the approach speed to the obstacle's own
|
|
561
|
+
* closes that gap within a step or two, which is where the discrete solver
|
|
562
|
+
* takes over and the two stay in contact.
|
|
563
|
+
*
|
|
564
|
+
* Static obstacles are left alone: they do not run away from the body, so
|
|
565
|
+
* the overlap the rewind aims for survives into the next step and the
|
|
566
|
+
* discrete solver already handles them.
|
|
567
|
+
*
|
|
568
|
+
* @param {Body} body - The body that was rewound
|
|
569
|
+
* @param {number} dx - How far it swept this step, before the rewind
|
|
570
|
+
* @param {number} dy
|
|
571
|
+
* @private
|
|
572
|
+
*/
|
|
573
|
+
_absorbImpactVelocity(body, dx, dy) {
|
|
574
|
+
const other = this._toiHitBody;
|
|
575
|
+
if (!other || other.type === BodyType.STATIC) return;
|
|
576
|
+
|
|
577
|
+
const travel = Math.sqrt(dx * dx + dy * dy);
|
|
578
|
+
if (travel === 0) return;
|
|
579
|
+
|
|
580
|
+
const nx = dx / travel;
|
|
581
|
+
const ny = dy / travel;
|
|
582
|
+
const v = body.linearVelocity;
|
|
583
|
+
const w = other.linearVelocity;
|
|
584
|
+
const approach = (v.x - w.x) * nx + (v.y - w.y) * ny;
|
|
585
|
+
if (approach <= 0) return;
|
|
586
|
+
|
|
587
|
+
v.x -= approach * nx;
|
|
588
|
+
v.y -= approach * ny;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* @method _findTimeOfImpact
|
|
593
|
+
* @description Earliest fraction of this step at which `body` first touches
|
|
594
|
+
* non-dynamic geometry, or null when it hits nothing. The body that was hit
|
|
595
|
+
* is left in `_toiHitBody`.
|
|
596
|
+
* @param {Body} body
|
|
597
|
+
* @returns {number|null}
|
|
598
|
+
* @private
|
|
599
|
+
*/
|
|
600
|
+
_findTimeOfImpact(body) {
|
|
601
|
+
let minAlpha = 1;
|
|
602
|
+
let hit = false;
|
|
603
|
+
this._toiHitBody = null;
|
|
604
|
+
const proxyA = this._toiProxyA;
|
|
605
|
+
const proxyB = this._toiProxyB;
|
|
606
|
+
|
|
607
|
+
for (let fixture = body.fixtureList; fixture; fixture = fixture.next) {
|
|
608
|
+
if (fixture.sensor) continue;
|
|
609
|
+
proxyA.set(fixture.shape);
|
|
610
|
+
|
|
611
|
+
const sweptAABB = fixture.getAABB();
|
|
612
|
+
|
|
613
|
+
this.broadPhase.query(sweptAABB, (proxyId) => {
|
|
614
|
+
const other = this.broadPhase.getUserData(proxyId).fixture;
|
|
615
|
+
const otherBody = other.body;
|
|
616
|
+
if (otherBody === body) return true;
|
|
617
|
+
if (otherBody.type === BodyType.DYNAMIC) return true;
|
|
618
|
+
if (other.sensor) return true;
|
|
619
|
+
if (!otherBody.active) return true;
|
|
620
|
+
if (!shouldCollide(fixture, other)) return true;
|
|
621
|
+
if (this.contactFilter && !this.contactFilter(fixture, other)) {
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
proxyB.set(other.shape);
|
|
626
|
+
const result = timeOfImpact({
|
|
627
|
+
proxyA,
|
|
628
|
+
proxyB,
|
|
629
|
+
sweepA: body.sweep,
|
|
630
|
+
sweepB: otherBody.sweep,
|
|
631
|
+
tMax: minAlpha,
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
if (
|
|
635
|
+
(result.state === TOIState.TOUCHING ||
|
|
636
|
+
result.state === TOIState.OVERLAPPED) &&
|
|
637
|
+
result.t < minAlpha
|
|
638
|
+
) {
|
|
639
|
+
minAlpha = result.t;
|
|
640
|
+
hit = true;
|
|
641
|
+
this._toiHitBody = otherBody;
|
|
642
|
+
}
|
|
643
|
+
return true;
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (!hit || minAlpha <= 0 || minAlpha >= 1) return null;
|
|
648
|
+
return minAlpha;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* @method clearForces
|
|
653
|
+
* @description Zeroes the accumulated forces and torques on every body.
|
|
654
|
+
* Called automatically at the end of each step.
|
|
655
|
+
*/
|
|
656
|
+
clearForces() {
|
|
657
|
+
for (let body = this.bodyList; body; body = body.next) {
|
|
658
|
+
body.force.setZero();
|
|
659
|
+
body.torque = 0;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* @method rayCast
|
|
665
|
+
* @description Casts a ray from `p1` to `p2`, calling
|
|
666
|
+
* `callback(fixture, point, normal, fraction)` for every fixture hit.
|
|
667
|
+
*
|
|
668
|
+
* The callback controls what happens next, exactly like a filter: return the
|
|
669
|
+
* `fraction` to clip the ray there and keep looking for closer hits (the
|
|
670
|
+
* usual "closest hit" behaviour), `0` to stop immediately, `1` to keep the
|
|
671
|
+
* full ray and collect every hit, or `-1` to ignore this fixture entirely.
|
|
672
|
+
*
|
|
673
|
+
* @param {Object} p1 - Ray start `{ x, y }` in physics units
|
|
674
|
+
* @param {Object} p2 - Ray end `{ x, y }` in physics units
|
|
675
|
+
* @param {Function} callback
|
|
676
|
+
*/
|
|
677
|
+
rayCast(p1, p2, callback) {
|
|
678
|
+
const input = {
|
|
679
|
+
p1: new Vec2(p1),
|
|
680
|
+
p2: new Vec2(p2),
|
|
681
|
+
maxFraction: 1,
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
this.broadPhase.rayCast(input, (subInput, proxyId) => {
|
|
685
|
+
const proxy = this.broadPhase.getUserData(proxyId);
|
|
686
|
+
const fixture = proxy.fixture;
|
|
687
|
+
const output = { fraction: 0, normal: new Vec2() };
|
|
688
|
+
const hit = fixture.rayCast(output, subInput);
|
|
689
|
+
if (!hit) return subInput.maxFraction;
|
|
690
|
+
|
|
691
|
+
const fraction = output.fraction;
|
|
692
|
+
const point = new Vec2(
|
|
693
|
+
(1 - fraction) * input.p1.x + fraction * input.p2.x,
|
|
694
|
+
(1 - fraction) * input.p1.y + fraction * input.p2.y
|
|
695
|
+
);
|
|
696
|
+
return callback(fixture, point, output.normal, fraction);
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* @method queryAABB
|
|
702
|
+
* @description Calls `callback(fixture)` for every fixture whose bounding box
|
|
703
|
+
* overlaps `aabb`. Returning false from the callback ends the query.
|
|
704
|
+
* @param {AABB} aabb
|
|
705
|
+
* @param {Function} callback
|
|
706
|
+
*/
|
|
707
|
+
queryAABB(aabb, callback) {
|
|
708
|
+
this.broadPhase.query(aabb, (proxyId) => {
|
|
709
|
+
const fixture = this.broadPhase.getUserData(proxyId).fixture;
|
|
710
|
+
return callback(fixture);
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* @method queryPoint
|
|
716
|
+
* @description Calls `callback(fixture)` for every fixture containing a
|
|
717
|
+
* world-space point.
|
|
718
|
+
* @param {Object} point - `{ x, y }` in physics units
|
|
719
|
+
* @param {Function} callback
|
|
720
|
+
*/
|
|
721
|
+
queryPoint(point, callback) {
|
|
722
|
+
const aabb = new AABB(point.x, point.y, point.x, point.y);
|
|
723
|
+
this.broadPhase.query(aabb, (proxyId) => {
|
|
724
|
+
const fixture = this.broadPhase.getUserData(proxyId).fixture;
|
|
725
|
+
if (!fixture.testPoint(point)) return true;
|
|
726
|
+
return callback(fixture);
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
export default World;
|