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.
Files changed (208) hide show
  1. package/README.md +2198 -968
  2. package/dist/types/index.d.ts +75 -32
  3. package/dist/types/src/Animator.d.ts +50 -0
  4. package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
  5. package/dist/types/src/Camera.d.ts +122 -0
  6. package/dist/types/src/CameraController.d.ts +107 -0
  7. package/dist/types/src/CanvasText.d.ts +91 -0
  8. package/dist/types/src/CollisionLayers.d.ts +58 -0
  9. package/dist/types/{Color.d.ts → src/Color.d.ts} +6 -6
  10. package/dist/types/src/Coroutine.d.ts +111 -0
  11. package/dist/types/src/DebugOverlay.d.ts +86 -0
  12. package/dist/types/src/Drawable.d.ts +271 -0
  13. package/dist/types/src/Easing.d.ts +22 -0
  14. package/dist/types/src/Emerald.d.ts +420 -0
  15. package/dist/types/src/EmeraldDB.d.ts +159 -0
  16. package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
  17. package/dist/types/src/GLUtils.d.ts +4 -0
  18. package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
  19. package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +75 -23
  20. package/dist/types/src/Interpolator.d.ts +66 -0
  21. package/dist/types/src/Material.d.ts +87 -0
  22. package/dist/types/src/MathUtils.d.ts +81 -0
  23. package/dist/types/src/ParticleEmitter.d.ts +131 -0
  24. package/dist/types/src/Physics.d.ts +337 -0
  25. package/dist/types/src/Pool.d.ts +53 -0
  26. package/dist/types/src/PostEffects.d.ts +68 -0
  27. package/dist/types/src/PostProcessor.d.ts +124 -0
  28. package/dist/types/src/RenderTarget.d.ts +56 -0
  29. package/dist/types/src/Scene.d.ts +62 -0
  30. package/dist/types/src/ScreenEffects.d.ts +111 -0
  31. package/dist/types/src/Serializer.d.ts +86 -0
  32. package/dist/types/src/Shaders.d.ts +2 -0
  33. package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
  34. package/dist/types/src/SpatialGrid.d.ts +50 -0
  35. package/dist/types/src/SpriteBatch.d.ts +89 -0
  36. package/dist/types/src/StateMachine.d.ts +59 -0
  37. package/dist/types/src/Storage.d.ts +89 -0
  38. package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
  39. package/dist/types/src/TextureAtlas.d.ts +55 -0
  40. package/dist/types/src/Tilemap.d.ts +91 -0
  41. package/dist/types/src/Time.d.ts +53 -0
  42. package/dist/types/src/Timer.d.ts +54 -0
  43. package/dist/types/src/Transform.d.ts +94 -0
  44. package/dist/types/src/Tween.d.ts +81 -0
  45. package/dist/types/src/UI.d.ts +121 -0
  46. package/dist/types/src/components/Behaviour.d.ts +71 -0
  47. package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
  48. package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
  49. package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
  50. package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
  51. package/dist/types/src/components/Collider.d.ts +102 -0
  52. package/dist/types/src/components/GameObject.d.ts +135 -0
  53. package/dist/types/src/components/PolygonCollider.d.ts +33 -0
  54. package/dist/types/src/components/RigidBody.d.ts +456 -0
  55. package/dist/types/src/importers/Aseprite.d.ts +79 -0
  56. package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
  57. package/dist/types/src/importers/TiledMap.d.ts +62 -0
  58. package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
  59. package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
  60. package/dist/types/src/managers/AssetManager.d.ts +116 -0
  61. package/dist/types/src/managers/AudioManager.d.ts +259 -0
  62. package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
  63. package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
  64. package/dist/types/src/managers/GLManager.d.ts +84 -0
  65. package/dist/types/src/managers/GLState.d.ts +37 -0
  66. package/dist/types/src/managers/Gamepad.d.ts +102 -0
  67. package/dist/types/src/managers/IDManager.d.ts +31 -0
  68. package/dist/types/src/managers/InputManager.d.ts +381 -0
  69. package/dist/types/src/managers/NetworkManager.d.ts +93 -0
  70. package/dist/types/src/managers/RenderStats.d.ts +34 -0
  71. package/dist/types/src/managers/SceneManager.d.ts +44 -0
  72. package/dist/types/src/managers/ShaderManager.d.ts +55 -0
  73. package/dist/types/src/managers/TextureManager.d.ts +102 -0
  74. package/dist/types/src/particlesystem/Particle.d.ts +64 -0
  75. package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
  76. package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
  77. package/dist/types/src/physics/AABB.d.ts +92 -0
  78. package/dist/types/src/physics/Body.d.ts +435 -0
  79. package/dist/types/src/physics/BodyType.d.ts +6 -0
  80. package/dist/types/src/physics/BroadPhase.d.ts +210 -0
  81. package/dist/types/src/physics/Collision.d.ts +102 -0
  82. package/dist/types/src/physics/Contact.d.ts +206 -0
  83. package/dist/types/src/physics/ContactSolver.d.ts +108 -0
  84. package/dist/types/src/physics/Distance.d.ts +54 -0
  85. package/dist/types/src/physics/DistanceJoint.d.ts +90 -0
  86. package/dist/types/src/physics/Fixture.d.ts +221 -0
  87. package/dist/types/src/physics/Island.d.ts +52 -0
  88. package/dist/types/src/physics/Joint.d.ts +59 -0
  89. package/dist/types/src/physics/Math2D.d.ts +371 -0
  90. package/dist/types/src/physics/RevoluteJoint.d.ts +119 -0
  91. package/dist/types/src/physics/Settings.d.ts +22 -0
  92. package/dist/types/src/physics/Shapes.d.ts +207 -0
  93. package/dist/types/src/physics/TimeOfImpact.d.ts +22 -0
  94. package/dist/types/src/physics/World.d.ts +274 -0
  95. package/dist/types/src/physics/index.d.ts +34 -0
  96. package/index.js +78 -0
  97. package/package.json +73 -60
  98. package/src/Animator.js +95 -0
  99. package/src/BitmapText.js +6 -5
  100. package/src/Camera.js +183 -0
  101. package/src/CameraController.js +192 -0
  102. package/src/CanvasText.js +281 -0
  103. package/src/CollisionLayers.js +86 -0
  104. package/src/Color.js +26 -18
  105. package/src/Coroutine.js +259 -0
  106. package/src/DebugOverlay.js +246 -0
  107. package/src/Drawable.js +842 -582
  108. package/src/Easing.js +57 -0
  109. package/src/Emerald.js +1150 -459
  110. package/src/EmeraldDB.js +328 -0
  111. package/src/FPSCounter.js +43 -43
  112. package/src/GLUtils.js +60 -67
  113. package/src/Instance.js +41 -5
  114. package/src/InstancedTexture.js +308 -127
  115. package/src/Interpolator.js +124 -0
  116. package/src/Material.js +202 -0
  117. package/src/MathUtils.js +134 -0
  118. package/src/ParticleEmitter.js +284 -0
  119. package/src/Physics.js +431 -40
  120. package/src/Pool.js +85 -0
  121. package/src/PostEffects.js +296 -0
  122. package/src/PostProcessor.js +304 -0
  123. package/src/RenderTarget.js +134 -0
  124. package/src/Scene.js +115 -83
  125. package/src/ScreenEffects.js +266 -0
  126. package/src/Serializer.js +131 -0
  127. package/src/Shaders.js +150 -165
  128. package/src/Shapes.js +118 -129
  129. package/src/SpatialGrid.js +111 -0
  130. package/src/SpriteBatch.js +299 -0
  131. package/src/StateMachine.js +82 -0
  132. package/src/Storage.js +175 -47
  133. package/src/Texture.js +58 -67
  134. package/src/TextureAtlas.js +96 -0
  135. package/src/Tilemap.js +274 -0
  136. package/src/Time.js +51 -6
  137. package/src/Timer.js +99 -0
  138. package/src/Transform.js +100 -7
  139. package/src/Tween.js +160 -0
  140. package/src/UI.js +394 -0
  141. package/src/components/Behaviour.js +90 -0
  142. package/src/components/BoxCollider.js +29 -12
  143. package/src/components/BoxColliderDebug.js +3 -4
  144. package/src/components/CircleCollider.js +26 -12
  145. package/src/components/CircleColliderDebug.js +25 -24
  146. package/src/components/Collider.js +150 -34
  147. package/src/components/GameObject.js +130 -21
  148. package/src/components/PolygonCollider.js +55 -0
  149. package/src/components/RigidBody.js +556 -8
  150. package/src/importers/Aseprite.js +142 -0
  151. package/src/importers/ForgeLevel.js +581 -0
  152. package/src/importers/TiledMap.js +158 -0
  153. package/src/lights/DirectionalLight.js +6 -15
  154. package/src/lights/PointLight.js +4 -4
  155. package/src/managers/AssetManager.js +239 -0
  156. package/src/managers/AudioManager.js +565 -146
  157. package/src/managers/EventManager.js +488 -477
  158. package/src/managers/GLManager.js +57 -0
  159. package/src/managers/GLState.js +70 -0
  160. package/src/managers/Gamepad.js +126 -0
  161. package/src/managers/IDManager.js +24 -2
  162. package/src/managers/InputManager.js +905 -0
  163. package/src/managers/NetworkManager.js +178 -0
  164. package/src/managers/RenderStats.js +34 -0
  165. package/src/managers/SceneManager.js +30 -0
  166. package/src/managers/ShaderManager.js +0 -2
  167. package/src/managers/TextureManager.js +218 -0
  168. package/src/particlesystem/Particle.js +82 -7
  169. package/src/particlesystem/ParticleSettings.js +21 -3
  170. package/src/particlesystem/Particles.js +80 -31
  171. package/src/physics/AABB.js +207 -0
  172. package/src/physics/Body.js +862 -0
  173. package/src/physics/BodyType.js +16 -0
  174. package/src/physics/BroadPhase.js +641 -0
  175. package/src/physics/Collision.js +534 -0
  176. package/src/physics/Contact.js +500 -0
  177. package/src/physics/ContactSolver.js +526 -0
  178. package/src/physics/Distance.js +403 -0
  179. package/src/physics/DistanceJoint.js +227 -0
  180. package/src/physics/Fixture.js +346 -0
  181. package/src/physics/Island.js +203 -0
  182. package/src/physics/Joint.js +78 -0
  183. package/src/physics/Math2D.js +573 -0
  184. package/src/physics/RevoluteJoint.js +278 -0
  185. package/src/physics/Settings.js +78 -0
  186. package/src/physics/Shapes.js +549 -0
  187. package/src/physics/TimeOfImpact.js +87 -0
  188. package/src/physics/World.js +731 -0
  189. package/src/physics/index.js +79 -0
  190. package/dist/types/Drawable.d.ts +0 -157
  191. package/dist/types/Emerald.d.ts +0 -73
  192. package/dist/types/GLUtils.d.ts +0 -6
  193. package/dist/types/Physics.d.ts +0 -139
  194. package/dist/types/Scene.d.ts +0 -39
  195. package/dist/types/Shaders.d.ts +0 -4
  196. package/dist/types/Storage.d.ts +0 -46
  197. package/dist/types/Time.d.ts +0 -22
  198. package/dist/types/Transform.d.ts +0 -41
  199. package/dist/types/components/BoxColliderDebug.d.ts +0 -19
  200. package/dist/types/components/CircleColliderDebug.d.ts +0 -19
  201. package/dist/types/components/Collider.d.ts +0 -53
  202. package/dist/types/components/GameObject.d.ts +0 -72
  203. package/dist/types/components/RigidBody.d.ts +0 -104
  204. package/dist/types/managers/AudioManager.d.ts +0 -60
  205. package/dist/types/managers/GLManager.d.ts +0 -47
  206. package/dist/types/managers/IDManager.d.ts +0 -21
  207. package/dist/types/managers/SceneManager.d.ts +0 -22
  208. package/dist/types/particlesystem/Particle.d.ts +0 -42
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @enum BodyType
3
+ * @description The three ways a body can take part in the simulation.
4
+ *
5
+ * - `static`: never moves, infinite mass. Ground, walls, platforms.
6
+ * - `kinematic`: moves only when you set its velocity; forces and collisions
7
+ * never push it back. Moving platforms, elevators.
8
+ * - `dynamic`: fully simulated, gravity, impulses and contacts all apply.
9
+ */
10
+ const BodyType = {
11
+ STATIC: "static",
12
+ KINEMATIC: "kinematic",
13
+ DYNAMIC: "dynamic",
14
+ };
15
+
16
+ export { BodyType };
@@ -0,0 +1,641 @@
1
+ import AABB from "./AABB.js";
2
+ import Settings from "./Settings.js";
3
+ import { Vec2 } from "./Math2D.js";
4
+
5
+ const NULL_NODE = -1;
6
+
7
+ /**
8
+ * @class TreeNode
9
+ * @description One node of the dynamic AABB tree. Leaves hold a fixture proxy;
10
+ * internal nodes just bound their two children.
11
+ * @private
12
+ */
13
+ class TreeNode {
14
+ constructor() {
15
+ this.aabb = new AABB();
16
+ this.userData = null;
17
+ this.parent = NULL_NODE;
18
+ this.child1 = NULL_NODE;
19
+ this.child2 = NULL_NODE;
20
+ /** -1 marks a free node, 0 a leaf. */
21
+ this.height = -1;
22
+ this.next = NULL_NODE;
23
+ }
24
+
25
+ isLeaf() {
26
+ return this.child1 === NULL_NODE;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * @class DynamicTree
32
+ * @description A balanced tree of axis-aligned boxes over every fixture in the
33
+ * world. Leaves are stored with a "fat" AABB so a body can jiggle a little
34
+ * without forcing a re-insert, and the tree is rebalanced with rotations on the
35
+ * way back up after each change. Every broadphase query, world raycast and
36
+ * continuous-collision sweep goes through it.
37
+ */
38
+ class DynamicTree {
39
+ constructor() {
40
+ /** @private */
41
+ this.root = NULL_NODE;
42
+ /** @private */
43
+ this.nodes = [];
44
+ /** @private */
45
+ this.freeList = NULL_NODE;
46
+ /** @private */
47
+ this.nodeCount = 0;
48
+ }
49
+
50
+ /**
51
+ * @method allocateNode
52
+ * @description Takes a node from the free list, growing the pool if needed.
53
+ * @returns {number} - The node id
54
+ * @private
55
+ */
56
+ allocateNode() {
57
+ if (this.freeList === NULL_NODE) {
58
+ const node = new TreeNode();
59
+ this.nodes.push(node);
60
+ this.freeList = this.nodes.length - 1;
61
+ node.next = NULL_NODE;
62
+ }
63
+
64
+ const id = this.freeList;
65
+ const node = this.nodes[id];
66
+ this.freeList = node.next;
67
+ node.parent = NULL_NODE;
68
+ node.child1 = NULL_NODE;
69
+ node.child2 = NULL_NODE;
70
+ node.height = 0;
71
+ node.userData = null;
72
+ this.nodeCount++;
73
+ return id;
74
+ }
75
+
76
+ /**
77
+ * @method freeNode
78
+ * @description Returns a node to the free list.
79
+ * @param {number} id
80
+ * @private
81
+ */
82
+ freeNode(id) {
83
+ const node = this.nodes[id];
84
+ node.next = this.freeList;
85
+ node.height = -1;
86
+ node.userData = null;
87
+ this.freeList = id;
88
+ this.nodeCount--;
89
+ }
90
+
91
+ /**
92
+ * @method createProxy
93
+ * @description Adds a fixture proxy to the tree.
94
+ * @param {AABB} aabb - The tight world AABB
95
+ * @param {Object} userData - The FixtureProxy this leaf stands for
96
+ * @returns {number} - The proxy id
97
+ */
98
+ createProxy(aabb, userData) {
99
+ const id = this.allocateNode();
100
+ const node = this.nodes[id];
101
+ node.aabb.copy(aabb).extend(Settings.aabbExtension);
102
+ node.userData = userData;
103
+ node.height = 0;
104
+ this.insertLeaf(id);
105
+ return id;
106
+ }
107
+
108
+ /**
109
+ * @method destroyProxy
110
+ * @description Removes a proxy from the tree.
111
+ * @param {number} id
112
+ */
113
+ destroyProxy(id) {
114
+ this.removeLeaf(id);
115
+ this.freeNode(id);
116
+ }
117
+
118
+ /**
119
+ * @method moveProxy
120
+ * @description Re-fits a proxy whose fixture moved. Returns false (and does
121
+ * nothing) while the new box still fits inside the fat one.
122
+ * @param {number} id
123
+ * @param {AABB} aabb - The new tight AABB
124
+ * @param {Object} displacement - How far the body moved this step
125
+ * @returns {boolean} - Whether the proxy was actually re-inserted
126
+ */
127
+ moveProxy(id, aabb, displacement) {
128
+ const node = this.nodes[id];
129
+ if (node.aabb.contains(aabb)) return false;
130
+
131
+ this.removeLeaf(id);
132
+
133
+ const fat = node.aabb.copy(aabb).extend(Settings.aabbExtension);
134
+ const dx = Settings.aabbMultiplier * displacement.x;
135
+ const dy = Settings.aabbMultiplier * displacement.y;
136
+ if (dx < 0) fat.lowerBound.x += dx;
137
+ else fat.upperBound.x += dx;
138
+ if (dy < 0) fat.lowerBound.y += dy;
139
+ else fat.upperBound.y += dy;
140
+
141
+ this.insertLeaf(id);
142
+ return true;
143
+ }
144
+
145
+ /**
146
+ * @method getFatAABB
147
+ * @description Returns the stored (fattened) AABB of a proxy.
148
+ * @param {number} id
149
+ * @returns {AABB}
150
+ */
151
+ getFatAABB(id) {
152
+ return this.nodes[id].aabb;
153
+ }
154
+
155
+ /**
156
+ * @method getUserData
157
+ * @description Returns the proxy payload of a leaf.
158
+ * @param {number} id
159
+ * @returns {Object}
160
+ */
161
+ getUserData(id) {
162
+ return this.nodes[id].userData;
163
+ }
164
+
165
+ /**
166
+ * @method insertLeaf
167
+ * @description Inserts a leaf, choosing the sibling that grows the tree's
168
+ * total surface area least, then rebalancing on the way back to the root.
169
+ * @param {number} leaf
170
+ * @private
171
+ */
172
+ insertLeaf(leaf) {
173
+ if (this.root === NULL_NODE) {
174
+ this.root = leaf;
175
+ this.nodes[leaf].parent = NULL_NODE;
176
+ return;
177
+ }
178
+
179
+ const leafAABB = this.nodes[leaf].aabb;
180
+ let index = this.root;
181
+ const combined = new AABB();
182
+ const childCombined = new AABB();
183
+
184
+ while (!this.nodes[index].isLeaf()) {
185
+ const node = this.nodes[index];
186
+ const child1 = node.child1;
187
+ const child2 = node.child2;
188
+
189
+ const area = node.aabb.getPerimeter();
190
+ combined.combine(node.aabb, leafAABB);
191
+ const combinedArea = combined.getPerimeter();
192
+
193
+ const cost = 2 * combinedArea;
194
+ const inheritanceCost = 2 * (combinedArea - area);
195
+
196
+ childCombined.combine(leafAABB, this.nodes[child1].aabb);
197
+ let cost1;
198
+ if (this.nodes[child1].isLeaf()) {
199
+ cost1 = childCombined.getPerimeter() + inheritanceCost;
200
+ } else {
201
+ cost1 =
202
+ childCombined.getPerimeter() -
203
+ this.nodes[child1].aabb.getPerimeter() +
204
+ inheritanceCost;
205
+ }
206
+
207
+ childCombined.combine(leafAABB, this.nodes[child2].aabb);
208
+ let cost2;
209
+ if (this.nodes[child2].isLeaf()) {
210
+ cost2 = childCombined.getPerimeter() + inheritanceCost;
211
+ } else {
212
+ cost2 =
213
+ childCombined.getPerimeter() -
214
+ this.nodes[child2].aabb.getPerimeter() +
215
+ inheritanceCost;
216
+ }
217
+
218
+ if (cost < cost1 && cost < cost2) break;
219
+ index = cost1 < cost2 ? child1 : child2;
220
+ }
221
+
222
+ const sibling = index;
223
+ const oldParent = this.nodes[sibling].parent;
224
+ const newParent = this.allocateNode();
225
+ const newParentNode = this.nodes[newParent];
226
+ newParentNode.parent = oldParent;
227
+ newParentNode.userData = null;
228
+ newParentNode.aabb.combine(leafAABB, this.nodes[sibling].aabb);
229
+ newParentNode.height = this.nodes[sibling].height + 1;
230
+ newParentNode.child1 = sibling;
231
+ newParentNode.child2 = leaf;
232
+ this.nodes[sibling].parent = newParent;
233
+ this.nodes[leaf].parent = newParent;
234
+
235
+ if (oldParent !== NULL_NODE) {
236
+ if (this.nodes[oldParent].child1 === sibling) {
237
+ this.nodes[oldParent].child1 = newParent;
238
+ } else {
239
+ this.nodes[oldParent].child2 = newParent;
240
+ }
241
+ } else {
242
+ this.root = newParent;
243
+ }
244
+
245
+ let walk = this.nodes[leaf].parent;
246
+ while (walk !== NULL_NODE) {
247
+ walk = this.balance(walk);
248
+ const node = this.nodes[walk];
249
+ const c1 = this.nodes[node.child1];
250
+ const c2 = this.nodes[node.child2];
251
+ node.height = 1 + Math.max(c1.height, c2.height);
252
+ node.aabb.combine(c1.aabb, c2.aabb);
253
+ walk = node.parent;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * @method removeLeaf
259
+ * @description Unlinks a leaf and collapses its now-single-child parent.
260
+ * @param {number} leaf
261
+ * @private
262
+ */
263
+ removeLeaf(leaf) {
264
+ if (leaf === this.root) {
265
+ this.root = NULL_NODE;
266
+ return;
267
+ }
268
+
269
+ const parent = this.nodes[leaf].parent;
270
+ const grandParent = this.nodes[parent].parent;
271
+ const sibling =
272
+ this.nodes[parent].child1 === leaf
273
+ ? this.nodes[parent].child2
274
+ : this.nodes[parent].child1;
275
+
276
+ if (grandParent !== NULL_NODE) {
277
+ if (this.nodes[grandParent].child1 === parent) {
278
+ this.nodes[grandParent].child1 = sibling;
279
+ } else {
280
+ this.nodes[grandParent].child2 = sibling;
281
+ }
282
+ this.nodes[sibling].parent = grandParent;
283
+ this.freeNode(parent);
284
+
285
+ let index = grandParent;
286
+ while (index !== NULL_NODE) {
287
+ index = this.balance(index);
288
+ const node = this.nodes[index];
289
+ const c1 = this.nodes[node.child1];
290
+ const c2 = this.nodes[node.child2];
291
+ node.aabb.combine(c1.aabb, c2.aabb);
292
+ node.height = 1 + Math.max(c1.height, c2.height);
293
+ index = node.parent;
294
+ }
295
+ } else {
296
+ this.root = sibling;
297
+ this.nodes[sibling].parent = NULL_NODE;
298
+ this.freeNode(parent);
299
+ }
300
+ }
301
+
302
+ /**
303
+ * @method balance
304
+ * @description One AVL-style rotation at `iA` if its subtrees differ in
305
+ * height by more than one. Keeps queries logarithmic.
306
+ * @param {number} iA
307
+ * @returns {number} - The new root of that subtree
308
+ * @private
309
+ */
310
+ balance(iA) {
311
+ const A = this.nodes[iA];
312
+ if (A.isLeaf() || A.height < 2) return iA;
313
+
314
+ const iB = A.child1;
315
+ const iC = A.child2;
316
+ const B = this.nodes[iB];
317
+ const C = this.nodes[iC];
318
+ const balance = C.height - B.height;
319
+
320
+ if (balance > 1) {
321
+ const iF = C.child1;
322
+ const iG = C.child2;
323
+ const F = this.nodes[iF];
324
+ const G = this.nodes[iG];
325
+
326
+ C.child1 = iA;
327
+ C.parent = A.parent;
328
+ A.parent = iC;
329
+
330
+ if (C.parent !== NULL_NODE) {
331
+ if (this.nodes[C.parent].child1 === iA)
332
+ this.nodes[C.parent].child1 = iC;
333
+ else this.nodes[C.parent].child2 = iC;
334
+ } else {
335
+ this.root = iC;
336
+ }
337
+
338
+ if (F.height > G.height) {
339
+ C.child2 = iF;
340
+ A.child2 = iG;
341
+ G.parent = iA;
342
+ A.aabb.combine(B.aabb, G.aabb);
343
+ C.aabb.combine(A.aabb, F.aabb);
344
+ A.height = 1 + Math.max(B.height, G.height);
345
+ C.height = 1 + Math.max(A.height, F.height);
346
+ } else {
347
+ C.child2 = iG;
348
+ A.child2 = iF;
349
+ F.parent = iA;
350
+ A.aabb.combine(B.aabb, F.aabb);
351
+ C.aabb.combine(A.aabb, G.aabb);
352
+ A.height = 1 + Math.max(B.height, F.height);
353
+ C.height = 1 + Math.max(A.height, G.height);
354
+ }
355
+ return iC;
356
+ }
357
+
358
+ if (balance < -1) {
359
+ const iD = B.child1;
360
+ const iE = B.child2;
361
+ const D = this.nodes[iD];
362
+ const E = this.nodes[iE];
363
+
364
+ B.child1 = iA;
365
+ B.parent = A.parent;
366
+ A.parent = iB;
367
+
368
+ if (B.parent !== NULL_NODE) {
369
+ if (this.nodes[B.parent].child1 === iA)
370
+ this.nodes[B.parent].child1 = iB;
371
+ else this.nodes[B.parent].child2 = iB;
372
+ } else {
373
+ this.root = iB;
374
+ }
375
+
376
+ if (D.height > E.height) {
377
+ B.child2 = iD;
378
+ A.child1 = iE;
379
+ E.parent = iA;
380
+ A.aabb.combine(C.aabb, E.aabb);
381
+ B.aabb.combine(A.aabb, D.aabb);
382
+ A.height = 1 + Math.max(C.height, E.height);
383
+ B.height = 1 + Math.max(A.height, D.height);
384
+ } else {
385
+ B.child2 = iE;
386
+ A.child1 = iD;
387
+ D.parent = iA;
388
+ A.aabb.combine(C.aabb, D.aabb);
389
+ B.aabb.combine(A.aabb, E.aabb);
390
+ A.height = 1 + Math.max(C.height, D.height);
391
+ B.height = 1 + Math.max(A.height, E.height);
392
+ }
393
+ return iB;
394
+ }
395
+
396
+ return iA;
397
+ }
398
+
399
+ /**
400
+ * @method query
401
+ * @description Calls `callback(proxyId)` for every leaf whose fat AABB
402
+ * overlaps `aabb`. Returning false from the callback stops the query.
403
+ * @param {AABB} aabb
404
+ * @param {Function} callback
405
+ */
406
+ query(aabb, callback) {
407
+ if (this.root === NULL_NODE) return;
408
+ const stack = [this.root];
409
+ while (stack.length > 0) {
410
+ const id = stack.pop();
411
+ if (id === NULL_NODE) continue;
412
+ const node = this.nodes[id];
413
+ if (!AABB.testOverlap(node.aabb, aabb)) continue;
414
+ if (node.isLeaf()) {
415
+ if (callback(id) === false) return;
416
+ } else {
417
+ stack.push(node.child1, node.child2);
418
+ }
419
+ }
420
+ }
421
+
422
+ /**
423
+ * @method rayCast
424
+ * @description Walks the tree along a ray, calling
425
+ * `callback(subInput, proxyId)`. The callback returns the new max fraction
426
+ * (0 terminates the cast), which lets the traversal shrink as it finds hits.
427
+ * @param {Object} input - `{ p1, p2, maxFraction }`
428
+ * @param {Function} callback
429
+ */
430
+ rayCast(input, callback) {
431
+ if (this.root === NULL_NODE) return;
432
+
433
+ let maxFraction = input.maxFraction;
434
+ const segment = {
435
+ p1: input.p1,
436
+ p2: new Vec2(),
437
+ maxFraction: 1,
438
+ };
439
+
440
+ const stack = [this.root];
441
+ while (stack.length > 0) {
442
+ const id = stack.pop();
443
+ if (id === NULL_NODE) continue;
444
+ const node = this.nodes[id];
445
+
446
+ segment.p2.set(
447
+ input.p1.x + maxFraction * (input.p2.x - input.p1.x),
448
+ input.p1.y + maxFraction * (input.p2.y - input.p1.y)
449
+ );
450
+ if (!node.aabb.rayCast(segment)) continue;
451
+
452
+ if (node.isLeaf()) {
453
+ const value = callback({ p1: input.p1, p2: input.p2, maxFraction }, id);
454
+ if (value === 0) return;
455
+ if (value > 0) maxFraction = value;
456
+ } else {
457
+ stack.push(node.child1, node.child2);
458
+ }
459
+ }
460
+ }
461
+
462
+ /**
463
+ * @method getHeight
464
+ * @description Height of the tree (0 when empty), useful when profiling.
465
+ * @returns {number}
466
+ */
467
+ getHeight() {
468
+ return this.root === NULL_NODE ? 0 : this.nodes[this.root].height;
469
+ }
470
+ }
471
+
472
+ const PAIR_STRIDE = 2097152;
473
+
474
+ /**
475
+ * @class BroadPhase
476
+ * @description Keeps the AABB tree and the list of proxies that moved since the
477
+ * last step, and turns those into the candidate pairs the narrowphase looks at.
478
+ */
479
+ class BroadPhase {
480
+ constructor() {
481
+ /** @private */
482
+ this.tree = new DynamicTree();
483
+ /** @private */
484
+ this.moveBuffer = [];
485
+ /** @private */
486
+ this.proxyCount = 0;
487
+ }
488
+
489
+ /**
490
+ * @method createProxy
491
+ * @description Adds a fixture proxy and queues it for pairing.
492
+ * @param {AABB} aabb
493
+ * @param {Object} userData
494
+ * @returns {number} - Proxy id
495
+ */
496
+ createProxy(aabb, userData) {
497
+ const id = this.tree.createProxy(aabb, userData);
498
+ this.proxyCount++;
499
+ this.bufferMove(id);
500
+ return id;
501
+ }
502
+
503
+ /**
504
+ * @method destroyProxy
505
+ * @description Removes a fixture proxy.
506
+ * @param {number} id
507
+ */
508
+ destroyProxy(id) {
509
+ this.unbufferMove(id);
510
+ this.proxyCount--;
511
+ this.tree.destroyProxy(id);
512
+ }
513
+
514
+ /**
515
+ * @method moveProxy
516
+ * @description Updates a proxy's box, queueing it for re-pairing if the tree
517
+ * actually had to move it.
518
+ * @param {number} id
519
+ * @param {AABB} aabb
520
+ * @param {Object} displacement
521
+ */
522
+ moveProxy(id, aabb, displacement) {
523
+ if (this.tree.moveProxy(id, aabb, displacement)) this.bufferMove(id);
524
+ }
525
+
526
+ /**
527
+ * @method touchProxy
528
+ * @description Forces a proxy to be re-paired next update even though it
529
+ * didn't move (used when a fixture's filter changes).
530
+ * @param {number} id
531
+ */
532
+ touchProxy(id) {
533
+ this.bufferMove(id);
534
+ }
535
+
536
+ /** @private */
537
+ bufferMove(id) {
538
+ this.moveBuffer.push(id);
539
+ }
540
+
541
+ /** @private */
542
+ unbufferMove(id) {
543
+ const index = this.moveBuffer.indexOf(id);
544
+ if (index >= 0) this.moveBuffer[index] = NULL_NODE;
545
+ }
546
+
547
+ /**
548
+ * @method getFatAABB
549
+ * @description Returns a proxy's fattened AABB.
550
+ * @param {number} id
551
+ * @returns {AABB}
552
+ */
553
+ getFatAABB(id) {
554
+ return this.tree.getFatAABB(id);
555
+ }
556
+
557
+ /**
558
+ * @method getUserData
559
+ * @description Returns a proxy's payload.
560
+ * @param {number} id
561
+ * @returns {Object}
562
+ */
563
+ getUserData(id) {
564
+ return this.tree.getUserData(id);
565
+ }
566
+
567
+ /**
568
+ * @method testOverlap
569
+ * @description True when two proxies' fat AABBs overlap.
570
+ * @param {number} idA
571
+ * @param {number} idB
572
+ * @returns {boolean}
573
+ */
574
+ testOverlap(idA, idB) {
575
+ return AABB.testOverlap(
576
+ this.tree.getFatAABB(idA),
577
+ this.tree.getFatAABB(idB)
578
+ );
579
+ }
580
+
581
+ /**
582
+ * @method query
583
+ * @description Forwards an AABB query to the tree.
584
+ * @param {AABB} aabb
585
+ * @param {Function} callback
586
+ */
587
+ query(aabb, callback) {
588
+ this.tree.query(aabb, callback);
589
+ }
590
+
591
+ /**
592
+ * @method rayCast
593
+ * @description Forwards a ray cast to the tree.
594
+ * @param {Object} input
595
+ * @param {Function} callback
596
+ */
597
+ rayCast(input, callback) {
598
+ this.tree.rayCast(input, callback);
599
+ }
600
+
601
+ /**
602
+ * @method updatePairs
603
+ * @description Emits `callback(userDataA, userDataB)` once for every new
604
+ * overlapping pair among the proxies that moved. Pairs are de-duplicated, so
605
+ * two proxies moving towards each other still report once.
606
+ * @param {Function} callback
607
+ */
608
+ updatePairs(callback) {
609
+ if (this.moveBuffer.length === 0) return;
610
+
611
+ const seen = new Set();
612
+ const pairs = [];
613
+
614
+ for (const queryProxy of this.moveBuffer) {
615
+ if (queryProxy === NULL_NODE) continue;
616
+ const fatAABB = this.tree.getFatAABB(queryProxy);
617
+
618
+ this.tree.query(fatAABB, (proxyId) => {
619
+ if (proxyId === queryProxy) return true;
620
+ const a = Math.min(proxyId, queryProxy);
621
+ const b = Math.max(proxyId, queryProxy);
622
+ const key = a * PAIR_STRIDE + b;
623
+ if (seen.has(key)) return true;
624
+ seen.add(key);
625
+ pairs.push(a, b);
626
+ return true;
627
+ });
628
+ }
629
+
630
+ this.moveBuffer.length = 0;
631
+
632
+ for (let i = 0; i < pairs.length; i += 2) {
633
+ callback(
634
+ this.tree.getUserData(pairs[i]),
635
+ this.tree.getUserData(pairs[i + 1])
636
+ );
637
+ }
638
+ }
639
+ }
640
+
641
+ export { BroadPhase, DynamicTree, NULL_NODE };