@jgengine/core 0.6.0 → 0.8.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 (330) hide show
  1. package/CHANGELOG.md +214 -2
  2. package/README.md +2 -0
  3. package/dist/ai/crowd.d.ts +36 -0
  4. package/dist/ai/crowd.js +213 -0
  5. package/dist/ai/jobBoard.d.ts +49 -0
  6. package/dist/ai/jobBoard.js +155 -0
  7. package/dist/ai/spawnDirector.d.ts +61 -0
  8. package/dist/ai/spawnDirector.js +162 -0
  9. package/dist/ai/threat.d.ts +25 -0
  10. package/dist/ai/threat.js +67 -0
  11. package/dist/anim/easing.d.ts +14 -0
  12. package/dist/anim/easing.js +55 -0
  13. package/dist/audio/audioFalloff.d.ts +38 -0
  14. package/dist/audio/audioFalloff.js +32 -0
  15. package/dist/behaviour/behaviour.d.ts +136 -0
  16. package/dist/behaviour/behaviour.js +388 -0
  17. package/dist/board/laneBoard.d.ts +63 -0
  18. package/dist/board/laneBoard.js +114 -0
  19. package/dist/board/timelineBoard.d.ts +36 -0
  20. package/dist/board/timelineBoard.js +70 -0
  21. package/dist/cards/cardPile.d.ts +64 -0
  22. package/dist/cards/cardPile.js +175 -0
  23. package/dist/cards/modifierPipeline.d.ts +33 -0
  24. package/dist/cards/modifierPipeline.js +48 -0
  25. package/dist/combat/abilityKit.d.ts +47 -0
  26. package/dist/combat/abilityKit.js +139 -0
  27. package/dist/combat/animationState.d.ts +47 -0
  28. package/dist/combat/animationState.js +101 -0
  29. package/dist/combat/attackTags.d.ts +14 -0
  30. package/dist/combat/attackTags.js +26 -0
  31. package/dist/combat/breakMeters.d.ts +32 -0
  32. package/dist/combat/breakMeters.js +51 -0
  33. package/dist/combat/comboString.d.ts +35 -0
  34. package/dist/combat/comboString.js +59 -0
  35. package/dist/combat/death.d.ts +8 -0
  36. package/dist/combat/death.js +5 -2
  37. package/dist/combat/defensiveWindow.d.ts +34 -0
  38. package/dist/combat/defensiveWindow.js +66 -0
  39. package/dist/combat/downed.d.ts +35 -0
  40. package/dist/combat/downed.js +104 -0
  41. package/dist/combat/effects.d.ts +12 -1
  42. package/dist/combat/effects.js +31 -19
  43. package/dist/combat/hitReaction.d.ts +23 -0
  44. package/dist/combat/hitReaction.js +20 -0
  45. package/dist/combat/projectiles.d.ts +42 -5
  46. package/dist/combat/projectiles.js +98 -12
  47. package/dist/combat/resistance.d.ts +13 -0
  48. package/dist/combat/resistance.js +46 -0
  49. package/dist/combat/telegraph.d.ts +31 -0
  50. package/dist/combat/telegraph.js +51 -0
  51. package/dist/commands/commandRegistry.d.ts +1 -1
  52. package/dist/commands/commandRegistry.js +2 -1
  53. package/dist/crafting/crop.d.ts +59 -0
  54. package/dist/crafting/crop.js +181 -0
  55. package/dist/crafting/production.d.ts +64 -0
  56. package/dist/crafting/production.js +130 -0
  57. package/dist/crafting/recipe.d.ts +62 -0
  58. package/dist/crafting/recipe.js +101 -0
  59. package/dist/data/dataSource.d.ts +26 -0
  60. package/dist/data/dataSource.js +93 -0
  61. package/dist/data/devProxy.d.ts +11 -0
  62. package/dist/data/devProxy.js +38 -0
  63. package/dist/data/fetchJson.d.ts +20 -0
  64. package/dist/data/fetchJson.js +43 -0
  65. package/dist/data/jsonDataSource.d.ts +4 -0
  66. package/dist/data/jsonDataSource.js +6 -0
  67. package/dist/devtools/devtools.d.ts +157 -0
  68. package/dist/devtools/devtools.js +484 -0
  69. package/dist/devtools/transformTunables.d.ts +14 -0
  70. package/dist/devtools/transformTunables.js +38 -0
  71. package/dist/economy/sharedWallet.d.ts +28 -0
  72. package/dist/economy/sharedWallet.js +62 -0
  73. package/dist/economy/techTree.d.ts +40 -0
  74. package/dist/economy/techTree.js +66 -0
  75. package/dist/game/chat.d.ts +66 -0
  76. package/dist/game/chat.js +165 -0
  77. package/dist/game/cosmetics.d.ts +25 -0
  78. package/dist/game/cosmetics.js +51 -0
  79. package/dist/game/defineGame.d.ts +10 -2
  80. package/dist/game/defineGame.js +2 -1
  81. package/dist/game/events.d.ts +87 -0
  82. package/dist/game/levelSequence.d.ts +44 -0
  83. package/dist/game/levelSequence.js +78 -0
  84. package/dist/game/lootFilter.d.ts +33 -0
  85. package/dist/game/lootFilter.js +48 -0
  86. package/dist/game/ping.d.ts +67 -0
  87. package/dist/game/ping.js +86 -0
  88. package/dist/game/playableGame.d.ts +516 -8
  89. package/dist/game/playableGame.js +6 -1
  90. package/dist/game/progression.d.ts +1 -0
  91. package/dist/game/progression.js +23 -2
  92. package/dist/game/race.d.ts +100 -0
  93. package/dist/game/race.js +236 -0
  94. package/dist/game/runDraft.d.ts +34 -0
  95. package/dist/game/runDraft.js +97 -0
  96. package/dist/game/social.d.ts +69 -0
  97. package/dist/game/social.js +138 -0
  98. package/dist/game/spawnPoints.d.ts +19 -0
  99. package/dist/game/spawnPoints.js +23 -0
  100. package/dist/game/worldItem.d.ts +95 -0
  101. package/dist/game/worldItem.js +138 -0
  102. package/dist/input/actionBindings.d.ts +10 -0
  103. package/dist/input/actionBindings.js +14 -0
  104. package/dist/input/axisInput.d.ts +43 -0
  105. package/dist/input/axisInput.js +70 -0
  106. package/dist/input/gestureSurface.d.ts +31 -0
  107. package/dist/input/gestureSurface.js +120 -0
  108. package/dist/input/pointer.d.ts +69 -0
  109. package/dist/input/pointer.js +65 -0
  110. package/dist/input/touchScheme.d.ts +83 -0
  111. package/dist/input/touchScheme.js +115 -0
  112. package/dist/interaction/contextMenu.d.ts +30 -0
  113. package/dist/interaction/contextMenu.js +22 -0
  114. package/dist/interaction/qte.d.ts +20 -0
  115. package/dist/interaction/qte.js +24 -0
  116. package/dist/interaction/skillCheck.d.ts +20 -0
  117. package/dist/interaction/skillCheck.js +26 -0
  118. package/dist/inventory/shapedGrid.d.ts +56 -0
  119. package/dist/inventory/shapedGrid.js +176 -0
  120. package/dist/inventory/storageTier.d.ts +52 -0
  121. package/dist/inventory/storageTier.js +77 -0
  122. package/dist/item/affix.d.ts +54 -0
  123. package/dist/item/affix.js +115 -0
  124. package/dist/item/durability.d.ts +51 -0
  125. package/dist/item/durability.js +78 -0
  126. package/dist/item/modularItem.d.ts +46 -0
  127. package/dist/item/modularItem.js +75 -0
  128. package/dist/meta/changelog.d.ts +1 -1
  129. package/dist/meta/changelog.js +71 -1
  130. package/dist/movement/dash.d.ts +37 -0
  131. package/dist/movement/dash.js +65 -0
  132. package/dist/movement/movementModel.d.ts +36 -3
  133. package/dist/movement/movementModel.js +81 -5
  134. package/dist/movement/voxelController.d.ts +61 -0
  135. package/dist/movement/voxelController.js +229 -0
  136. package/dist/multiplayer/chatContract.d.ts +42 -0
  137. package/dist/multiplayer/chatContract.js +57 -0
  138. package/dist/multiplayer/combatSnapshot.d.ts +45 -0
  139. package/dist/multiplayer/combatSnapshot.js +89 -0
  140. package/dist/multiplayer/identity.d.ts +13 -0
  141. package/dist/multiplayer/identity.js +20 -0
  142. package/dist/multiplayer/lagCompensation.d.ts +43 -0
  143. package/dist/multiplayer/lagCompensation.js +154 -0
  144. package/dist/multiplayer/matchmaking.d.ts +33 -0
  145. package/dist/multiplayer/matchmaking.js +60 -0
  146. package/dist/multiplayer/poseSyncGate.d.ts +1 -0
  147. package/dist/multiplayer/poseSyncGate.js +13 -1
  148. package/dist/multiplayer/presenceModel.d.ts +3 -0
  149. package/dist/multiplayer/presenceModel.js +15 -1
  150. package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
  151. package/dist/multiplayer/simultaneousCommit.js +53 -0
  152. package/dist/multiplayer/voiceContract.d.ts +44 -0
  153. package/dist/multiplayer/voiceContract.js +108 -0
  154. package/dist/nav/navConstrain.d.ts +14 -0
  155. package/dist/nav/navConstrain.js +26 -0
  156. package/dist/nav/navFromEnvironment.d.ts +11 -0
  157. package/dist/nav/navFromEnvironment.js +16 -0
  158. package/dist/nav/navGrid.d.ts +56 -0
  159. package/dist/nav/navGrid.js +265 -0
  160. package/dist/nav/pathFollow.d.ts +31 -0
  161. package/dist/nav/pathFollow.js +69 -0
  162. package/dist/physics/ballisticSweep.d.ts +18 -0
  163. package/dist/physics/ballisticSweep.js +49 -0
  164. package/dist/physics/buoyancy.d.ts +49 -0
  165. package/dist/physics/buoyancy.js +112 -0
  166. package/dist/physics/carryable.d.ts +46 -0
  167. package/dist/physics/carryable.js +110 -0
  168. package/dist/physics/damageZones.d.ts +56 -0
  169. package/dist/physics/damageZones.js +107 -0
  170. package/dist/physics/forceVolume.d.ts +48 -0
  171. package/dist/physics/forceVolume.js +119 -0
  172. package/dist/physics/physicsWorld.d.ts +124 -2
  173. package/dist/physics/physicsWorld.js +493 -41
  174. package/dist/physics/ragdoll.d.ts +49 -0
  175. package/dist/physics/ragdoll.js +103 -0
  176. package/dist/physics/spatialGrid.d.ts +41 -0
  177. package/dist/physics/spatialGrid.js +143 -0
  178. package/dist/physics/structure.d.ts +83 -0
  179. package/dist/physics/structure.js +0 -0
  180. package/dist/physics/traversal.d.ts +75 -0
  181. package/dist/physics/traversal.js +138 -0
  182. package/dist/physics/vehicleBody.d.ts +99 -0
  183. package/dist/physics/vehicleBody.js +233 -0
  184. package/dist/puzzle/cellGrid.d.ts +26 -0
  185. package/dist/puzzle/cellGrid.js +110 -0
  186. package/dist/puzzle/fallingPiece.d.ts +24 -0
  187. package/dist/puzzle/fallingPiece.js +51 -0
  188. package/dist/random/nameGen.d.ts +18 -0
  189. package/dist/random/nameGen.js +35 -0
  190. package/dist/random/rng.d.ts +2 -0
  191. package/dist/random/rng.js +23 -0
  192. package/dist/runtime/adapter.d.ts +35 -0
  193. package/dist/runtime/adapter.js +37 -1
  194. package/dist/runtime/cameraDirector.d.ts +10 -0
  195. package/dist/runtime/cameraDirector.js +25 -0
  196. package/dist/runtime/gameContext.d.ts +107 -3
  197. package/dist/runtime/gameContext.js +253 -7
  198. package/dist/runtime/hostPersistence.d.ts +39 -0
  199. package/dist/runtime/hostPersistence.js +54 -0
  200. package/dist/runtime/inputSnapshot.d.ts +7 -0
  201. package/dist/runtime/inputSnapshot.js +12 -0
  202. package/dist/runtime/motionIntents.d.ts +25 -0
  203. package/dist/runtime/motionIntents.js +25 -0
  204. package/dist/runtime/persistenceScope.d.ts +32 -0
  205. package/dist/runtime/persistenceScope.js +64 -0
  206. package/dist/runtime/transport.d.ts +32 -0
  207. package/dist/scene/autoTarget.d.ts +15 -0
  208. package/dist/scene/autoTarget.js +54 -0
  209. package/dist/scene/behaviors.d.ts +13 -1
  210. package/dist/scene/behaviors.js +3 -0
  211. package/dist/scene/captureCheck.d.ts +7 -0
  212. package/dist/scene/captureCheck.js +11 -0
  213. package/dist/scene/entityStore.d.ts +9 -1
  214. package/dist/scene/entityStore.js +25 -2
  215. package/dist/scene/form.d.ts +40 -0
  216. package/dist/scene/form.js +56 -0
  217. package/dist/scene/mount.d.ts +62 -0
  218. package/dist/scene/mount.js +107 -0
  219. package/dist/scene/movementSpeed.d.ts +35 -0
  220. package/dist/scene/movementSpeed.js +20 -0
  221. package/dist/scene/objectQuery.d.ts +18 -0
  222. package/dist/scene/objectQuery.js +108 -0
  223. package/dist/scene/objectStore.d.ts +11 -0
  224. package/dist/scene/objectStore.js +109 -4
  225. package/dist/scene/paintLayer.d.ts +15 -0
  226. package/dist/scene/paintLayer.js +53 -0
  227. package/dist/scene/possession.d.ts +30 -0
  228. package/dist/scene/possession.js +51 -0
  229. package/dist/scene/roster.d.ts +25 -0
  230. package/dist/scene/roster.js +57 -0
  231. package/dist/scene/selection.d.ts +30 -0
  232. package/dist/scene/selection.js +38 -0
  233. package/dist/scene/spatial.d.ts +21 -0
  234. package/dist/scene/spatial.js +63 -1
  235. package/dist/scene/stationClaim.d.ts +43 -0
  236. package/dist/scene/stationClaim.js +109 -0
  237. package/dist/sensor/concealment.d.ts +21 -0
  238. package/dist/sensor/concealment.js +63 -0
  239. package/dist/sensor/freezeMonitor.d.ts +17 -0
  240. package/dist/sensor/freezeMonitor.js +35 -0
  241. package/dist/sensor/frustumSensor.d.ts +60 -0
  242. package/dist/sensor/frustumSensor.js +104 -0
  243. package/dist/sensor/hiddenStateProbe.d.ts +31 -0
  244. package/dist/sensor/hiddenStateProbe.js +40 -0
  245. package/dist/sensor/recordingBuffer.d.ts +29 -0
  246. package/dist/sensor/recordingBuffer.js +61 -0
  247. package/dist/sensor/revealQuery.d.ts +22 -0
  248. package/dist/sensor/revealQuery.js +36 -0
  249. package/dist/session/contestedChannel.d.ts +36 -0
  250. package/dist/session/contestedChannel.js +100 -0
  251. package/dist/session/extraction.d.ts +54 -0
  252. package/dist/session/extraction.js +108 -0
  253. package/dist/session/ring.d.ts +36 -0
  254. package/dist/session/ring.js +88 -0
  255. package/dist/session/roles.d.ts +6 -0
  256. package/dist/session/roles.js +32 -0
  257. package/dist/session/roundState.d.ts +79 -0
  258. package/dist/session/roundState.js +158 -0
  259. package/dist/stats/accumulatorMeter.d.ts +31 -0
  260. package/dist/stats/accumulatorMeter.js +81 -0
  261. package/dist/stats/eventMeter.d.ts +34 -0
  262. package/dist/stats/eventMeter.js +76 -0
  263. package/dist/stats/rollCheck.d.ts +15 -0
  264. package/dist/stats/rollCheck.js +15 -0
  265. package/dist/survival/decayMeter.d.ts +59 -0
  266. package/dist/survival/decayMeter.js +95 -0
  267. package/dist/survival/moodle.d.ts +48 -0
  268. package/dist/survival/moodle.js +91 -0
  269. package/dist/survival/regionHealth.d.ts +72 -0
  270. package/dist/survival/regionHealth.js +129 -0
  271. package/dist/tactics/fallingGrid.d.ts +62 -0
  272. package/dist/tactics/fallingGrid.js +158 -0
  273. package/dist/tactics/predictiveQuery.d.ts +34 -0
  274. package/dist/tactics/predictiveQuery.js +44 -0
  275. package/dist/tactics/snapshot.d.ts +18 -0
  276. package/dist/tactics/snapshot.js +65 -0
  277. package/dist/tactics/surface.d.ts +52 -0
  278. package/dist/tactics/surface.js +135 -0
  279. package/dist/tactics/tacticalGrid.d.ts +68 -0
  280. package/dist/tactics/tacticalGrid.js +261 -0
  281. package/dist/time/beatClock.d.ts +40 -0
  282. package/dist/time/beatClock.js +73 -0
  283. package/dist/time/simClock.d.ts +25 -1
  284. package/dist/time/simClock.js +28 -3
  285. package/dist/turn/commit.d.ts +27 -0
  286. package/dist/turn/commit.js +72 -0
  287. package/dist/turn/intent.d.ts +14 -0
  288. package/dist/turn/intent.js +29 -0
  289. package/dist/turn/turnLoop.d.ts +60 -0
  290. package/dist/turn/turnLoop.js +223 -0
  291. package/dist/world/buildPermissions.d.ts +47 -0
  292. package/dist/world/buildPermissions.js +134 -0
  293. package/dist/world/carve.d.ts +105 -0
  294. package/dist/world/carve.js +175 -0
  295. package/dist/world/connectors.d.ts +40 -0
  296. package/dist/world/connectors.js +67 -0
  297. package/dist/world/envField.d.ts +71 -0
  298. package/dist/world/envField.js +96 -0
  299. package/dist/world/environmentSummary.d.ts +53 -0
  300. package/dist/world/environmentSummary.js +129 -0
  301. package/dist/world/features.d.ts +87 -6
  302. package/dist/world/features.js +52 -2
  303. package/dist/world/fog.d.ts +40 -0
  304. package/dist/world/fog.js +137 -0
  305. package/dist/world/gridInstances.d.ts +13 -0
  306. package/dist/world/gridInstances.js +22 -0
  307. package/dist/world/markers.d.ts +52 -0
  308. package/dist/world/markers.js +96 -0
  309. package/dist/world/minimap.d.ts +42 -0
  310. package/dist/world/minimap.js +72 -0
  311. package/dist/world/placedStructureStore.d.ts +39 -0
  312. package/dist/world/placedStructureStore.js +95 -0
  313. package/dist/world/placementController.d.ts +69 -0
  314. package/dist/world/placementController.js +153 -0
  315. package/dist/world/realm.d.ts +61 -0
  316. package/dist/world/realm.js +66 -0
  317. package/dist/world/support.d.ts +27 -0
  318. package/dist/world/support.js +75 -0
  319. package/dist/world/terraform.d.ts +54 -0
  320. package/dist/world/terraform.js +186 -0
  321. package/dist/world/terrain.d.ts +23 -1
  322. package/dist/world/terrain.js +64 -1
  323. package/dist/world/voxelField.d.ts +54 -0
  324. package/dist/world/voxelField.js +241 -0
  325. package/dist/world/walls.d.ts +61 -0
  326. package/dist/world/walls.js +221 -0
  327. package/dist/world/weather.d.ts +92 -0
  328. package/dist/world/weather.js +169 -0
  329. package/llms.txt +4102 -0
  330. package/package.json +3 -2
@@ -1,5 +1,10 @@
1
+ export const SHAPE_BOX = 0;
2
+ export const SHAPE_SPHERE = 1;
1
3
  const FLAG_SLEEPING = 1;
2
4
  const FLAG_STATIC = 2;
5
+ const FLAG_DEAD = 4;
6
+ /** Cap on `nx*ny*nz` broadphase cells — guards a huge-bounds/tiny-cellSize config from hanging `step()`. */
7
+ export const MAX_BROADPHASE_CELLS = 1_000_000;
3
8
  /** Grid cell containing a point, clamped into the grid. Pure — exported for tests. */
4
9
  export function cellCoord(value, min, cellSize, cells) {
5
10
  const raw = Math.floor((value - min) / cellSize);
@@ -26,6 +31,8 @@ export class PhysicsWorld {
26
31
  sleepMove2;
27
32
  sleepSteps;
28
33
  wakeThreshold;
34
+ jointCapacity;
35
+ jointCorrection;
29
36
  posX;
30
37
  posY;
31
38
  posZ;
@@ -37,6 +44,8 @@ export class PhysicsWorld {
37
44
  halfZ;
38
45
  invMass;
39
46
  flags;
47
+ /** `SHAPE_BOX` (0) or `SHAPE_SPHERE` (1) per body; spheres keep their radius in halfX/halfY/halfZ. */
48
+ shape;
40
49
  /** 1 for a body that took part in a contact this frame; reset each `step()`. Render tint reads this. */
41
50
  contact;
42
51
  sleepTimer;
@@ -56,6 +65,41 @@ export class PhysicsWorld {
56
65
  awakeList;
57
66
  awakeSlot;
58
67
  awakeCount = 0;
68
+ jSpring;
69
+ jActive;
70
+ jBodyA;
71
+ jBodyB;
72
+ jAnchorAX;
73
+ jAnchorAY;
74
+ jAnchorAZ;
75
+ jAnchorBX;
76
+ jAnchorBY;
77
+ jAnchorBZ;
78
+ jRest;
79
+ jStiffness;
80
+ jDamping;
81
+ jMaxImpulse;
82
+ jFree;
83
+ jFreeCount = 0;
84
+ jointHigh = 0;
85
+ jointActiveCount = 0;
86
+ // Removed bodies tombstone (FLAG_DEAD) rather than compact: `id` is the SoA slot itself, handed
87
+ // out to callers and stored raw in joints/game state, so a slot can never move once allocated.
88
+ // `bodyHigh` is the high-water mark of ever-used slots; `bodyFree` recycles dead ones for addBody.
89
+ bodyFree;
90
+ bodyFreeCount = 0;
91
+ bodyHigh = 0;
92
+ collisionListener = null;
93
+ collisionMinSpeed = 0;
94
+ collisionEvent = {
95
+ a: 0,
96
+ b: 0,
97
+ nx: 0,
98
+ ny: 0,
99
+ nz: 0,
100
+ approachSpeed: 0,
101
+ impulse: 0,
102
+ };
59
103
  bodyCount = 0;
60
104
  accumulator = 0;
61
105
  stats = {
@@ -86,6 +130,8 @@ export class PhysicsWorld {
86
130
  this.sleepMove2 = sleepMoveEps * sleepMoveEps;
87
131
  this.sleepSteps = config.sleepThresholdSteps ?? 30;
88
132
  this.wakeThreshold = config.wakeThreshold ?? 0.5;
133
+ this.jointCapacity = Math.max(0, config.jointCapacity ?? 256);
134
+ this.jointCorrection = config.jointCorrection ?? 0.5;
89
135
  const cap = config.capacity;
90
136
  this.posX = new Float32Array(cap);
91
137
  this.posY = new Float32Array(cap);
@@ -98,6 +144,7 @@ export class PhysicsWorld {
98
144
  this.halfZ = new Float32Array(cap);
99
145
  this.invMass = new Float32Array(cap);
100
146
  this.flags = new Uint8Array(cap);
147
+ this.shape = new Uint8Array(cap);
101
148
  this.contact = new Uint8Array(cap);
102
149
  this.sleepTimer = new Uint16Array(cap);
103
150
  this.prevX = new Float32Array(cap);
@@ -108,12 +155,34 @@ export class PhysicsWorld {
108
155
  this.ny = Math.max(1, Math.ceil(span(1) / this.cellSize));
109
156
  this.nz = Math.max(1, Math.ceil(span(2) / this.cellSize));
110
157
  this.numCells = this.nx * this.ny * this.nz;
158
+ if (this.numCells > MAX_BROADPHASE_CELLS) {
159
+ throw new Error(`PhysicsWorld: broadphase grid too large (${this.nx}x${this.ny}x${this.nz} = ${this.numCells} cells, ` +
160
+ `cap ${MAX_BROADPHASE_CELLS}). bounds=[${config.bounds.min}]..[${config.bounds.max}] cellSize=${this.cellSize}; ` +
161
+ `raise cellSize or shrink bounds.`);
162
+ }
111
163
  this.cellStart = new Int32Array(this.numCells + 1);
112
164
  this.cursor = new Int32Array(this.numCells);
113
165
  this.sorted = new Int32Array(cap);
114
166
  this.bodyCell = new Int32Array(cap);
115
167
  this.awakeList = new Int32Array(cap);
116
168
  this.awakeSlot = new Int32Array(cap).fill(-1);
169
+ this.bodyFree = new Int32Array(cap);
170
+ const jc = this.jointCapacity;
171
+ this.jSpring = new Uint8Array(jc);
172
+ this.jActive = new Uint8Array(jc);
173
+ this.jBodyA = new Int32Array(jc);
174
+ this.jBodyB = new Int32Array(jc);
175
+ this.jAnchorAX = new Float32Array(jc);
176
+ this.jAnchorAY = new Float32Array(jc);
177
+ this.jAnchorAZ = new Float32Array(jc);
178
+ this.jAnchorBX = new Float32Array(jc);
179
+ this.jAnchorBY = new Float32Array(jc);
180
+ this.jAnchorBZ = new Float32Array(jc);
181
+ this.jRest = new Float32Array(jc);
182
+ this.jStiffness = new Float32Array(jc);
183
+ this.jDamping = new Float32Array(jc);
184
+ this.jMaxImpulse = new Float32Array(jc);
185
+ this.jFree = new Int32Array(jc);
117
186
  }
118
187
  addAwake(i) {
119
188
  if (this.awakeSlot[i] >= 0)
@@ -135,13 +204,133 @@ export class PhysicsWorld {
135
204
  get count() {
136
205
  return this.bodyCount;
137
206
  }
207
+ /** One past the highest slot `addBody` has ever handed out — the range `removeBody` leaves holes in. */
208
+ get highWater() {
209
+ return this.bodyHigh;
210
+ }
138
211
  get cells() {
139
212
  return { nx: this.nx, ny: this.ny, nz: this.nz, total: this.numCells };
140
213
  }
214
+ get jointCount() {
215
+ return this.jointActiveCount;
216
+ }
217
+ /**
218
+ * Deliver every impacting contact (approach speed ≥ `minApproachSpeed`) to `listener` during
219
+ * `step`. The event object is reused — read/copy it, never retain. Pass `null` to detach. This is
220
+ * the seam crash-damage and destruction read; contacts otherwise stay inside the sim.
221
+ */
222
+ onCollision(listener, minApproachSpeed = 0) {
223
+ this.collisionListener = listener;
224
+ this.collisionMinSpeed = minApproachSpeed;
225
+ }
226
+ hingeJoint(options) {
227
+ return this.addJoint(options, false, 0);
228
+ }
229
+ fixedJoint(options) {
230
+ return this.addJoint(options, false, 0);
231
+ }
232
+ distanceJoint(options) {
233
+ return this.addJoint(options, false, null);
234
+ }
235
+ springJoint(options) {
236
+ return this.addJoint(options, true, null);
237
+ }
238
+ addJoint(options, spring, restOverride) {
239
+ let id;
240
+ if (this.jFreeCount > 0) {
241
+ id = this.jFree[--this.jFreeCount];
242
+ }
243
+ else {
244
+ if (this.jointHigh >= this.jointCapacity)
245
+ throw new Error("PhysicsWorld: joint capacity exceeded");
246
+ id = this.jointHigh++;
247
+ }
248
+ const bodyB = options.bodyB ?? -1;
249
+ const aA = options.anchorA;
250
+ const aB = options.anchorB;
251
+ this.jBodyA[id] = options.bodyA;
252
+ this.jBodyB[id] = bodyB;
253
+ this.jAnchorAX[id] = aA?.[0] ?? 0;
254
+ this.jAnchorAY[id] = aA?.[1] ?? 0;
255
+ this.jAnchorAZ[id] = aA?.[2] ?? 0;
256
+ this.jAnchorBX[id] = aB?.[0] ?? 0;
257
+ this.jAnchorBY[id] = aB?.[1] ?? 0;
258
+ this.jAnchorBZ[id] = aB?.[2] ?? 0;
259
+ this.jSpring[id] = spring ? 1 : 0;
260
+ this.jStiffness[id] = options.stiffness ?? 40;
261
+ this.jDamping[id] = options.damping ?? 6;
262
+ this.jMaxImpulse[id] = options.maxImpulse ?? Number.POSITIVE_INFINITY;
263
+ this.jRest[id] = restOverride ?? options.restLength ?? this.jointDistance(id);
264
+ this.jActive[id] = 1;
265
+ this.jointActiveCount += 1;
266
+ return id;
267
+ }
268
+ removeJoint(id) {
269
+ if (this.jActive[id] !== 1)
270
+ return;
271
+ this.jActive[id] = 0;
272
+ this.jFree[this.jFreeCount++] = id;
273
+ this.jointActiveCount -= 1;
274
+ }
275
+ /** Move a joint's world anchor point (only meaningful when `bodyB` is a world anchor). */
276
+ setJointAnchor(id, x, y, z) {
277
+ this.jAnchorBX[id] = x;
278
+ this.jAnchorBY[id] = y;
279
+ this.jAnchorBZ[id] = z;
280
+ }
281
+ /** Move a joint's local anchor offset on A (world axes, no rotation applied by the solver). */
282
+ setJointAnchorA(id, x, y, z) {
283
+ this.jAnchorAX[id] = x;
284
+ this.jAnchorAY[id] = y;
285
+ this.jAnchorAZ[id] = z;
286
+ }
287
+ setJointRest(id, restLength) {
288
+ this.jRest[id] = restLength;
289
+ }
290
+ jointDistance(id) {
291
+ const bA = this.jBodyA[id];
292
+ const bB = this.jBodyB[id];
293
+ const ax = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
294
+ const ay = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
295
+ const az = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
296
+ const bx = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
297
+ const by = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
298
+ const bz = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
299
+ const dx = bx - ax;
300
+ const dy = by - ay;
301
+ const dz = bz - az;
302
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
303
+ }
304
+ /** Write [ax,ay,az,bx,by,bz] world endpoints per active joint into `out`; returns the joint count. */
305
+ readJointSegments(out) {
306
+ let n = 0;
307
+ for (let id = 0; id < this.jointHigh; id += 1) {
308
+ if (this.jActive[id] !== 1)
309
+ continue;
310
+ const bA = this.jBodyA[id];
311
+ const bB = this.jBodyB[id];
312
+ const o = n * 6;
313
+ out[o] = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
314
+ out[o + 1] = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
315
+ out[o + 2] = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
316
+ out[o + 3] = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
317
+ out[o + 4] = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
318
+ out[o + 5] = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
319
+ n += 1;
320
+ }
321
+ return n;
322
+ }
141
323
  addBody(options) {
142
- if (this.bodyCount >= this.capacity)
143
- throw new Error("PhysicsWorld: capacity exceeded");
144
- const i = this.bodyCount++;
324
+ let i;
325
+ if (this.bodyFreeCount > 0) {
326
+ i = this.bodyFree[--this.bodyFreeCount];
327
+ }
328
+ else {
329
+ if (this.bodyHigh >= this.capacity)
330
+ throw new Error("PhysicsWorld: capacity exceeded");
331
+ i = this.bodyHigh++;
332
+ }
333
+ this.bodyCount += 1;
145
334
  this.posX[i] = options.position[0];
146
335
  this.posY[i] = options.position[1];
147
336
  this.posZ[i] = options.position[2];
@@ -149,9 +338,18 @@ export class PhysicsWorld {
149
338
  this.velX[i] = v?.[0] ?? 0;
150
339
  this.velY[i] = v?.[1] ?? 0;
151
340
  this.velZ[i] = v?.[2] ?? 0;
152
- this.halfX[i] = options.halfExtents[0];
153
- this.halfY[i] = options.halfExtents[1];
154
- this.halfZ[i] = options.halfExtents[2];
341
+ if (options.shape === "sphere") {
342
+ this.halfX[i] = options.radius;
343
+ this.halfY[i] = options.radius;
344
+ this.halfZ[i] = options.radius;
345
+ this.shape[i] = SHAPE_SPHERE;
346
+ }
347
+ else {
348
+ this.halfX[i] = options.halfExtents[0];
349
+ this.halfY[i] = options.halfExtents[1];
350
+ this.halfZ[i] = options.halfExtents[2];
351
+ this.shape[i] = SHAPE_BOX;
352
+ }
155
353
  const isStatic = options.static === true;
156
354
  const mass = options.mass ?? 1;
157
355
  this.invMass[i] = isStatic || mass <= 0 ? 0 : 1 / mass;
@@ -162,17 +360,69 @@ export class PhysicsWorld {
162
360
  f |= FLAG_SLEEPING;
163
361
  this.flags[i] = f;
164
362
  this.sleepTimer[i] = 0;
363
+ this.contact[i] = 0;
165
364
  this.awakeSlot[i] = -1;
166
365
  this.bodyCell[i] = this.cellOf(i);
167
366
  if ((f & FLAG_SLEEPING) === 0)
168
367
  this.addAwake(i);
169
368
  return i;
170
369
  }
370
+ /**
371
+ * Remove a body: it drops out of integration, the solver, and the broadphase, and its slot is
372
+ * queued for the next `addBody` to reuse. `id` and every other body's `id` stay valid — a dead
373
+ * slot is tombstoned (`FLAG_DEAD`), never swapped, since ids are handed out raw and stored as-is
374
+ * in joints and game state. There is no persistent contact set to consult, so — conservatively —
375
+ * any sleeping body whose AABB touches the removed body's (padded by `slop`) is woken; a body
376
+ * that was resting on it always qualifies.
377
+ */
378
+ removeBody(id) {
379
+ if (id < 0 || id >= this.bodyHigh)
380
+ return;
381
+ if ((this.flags[id] & FLAG_DEAD) !== 0)
382
+ return;
383
+ this.wakeTouching(id);
384
+ this.removeAwake(id);
385
+ this.flags[id] = FLAG_DEAD;
386
+ this.invMass[id] = 0;
387
+ this.velX[id] = 0;
388
+ this.velY[id] = 0;
389
+ this.velZ[id] = 0;
390
+ this.sleepTimer[id] = 0;
391
+ this.contact[id] = 0;
392
+ this.bodyCount -= 1;
393
+ this.bodyFree[this.bodyFreeCount++] = id;
394
+ }
395
+ wakeTouching(id) {
396
+ const pad = this.slop + 0.01;
397
+ const minX = this.posX[id] - this.halfX[id] - pad;
398
+ const maxX = this.posX[id] + this.halfX[id] + pad;
399
+ const minY = this.posY[id] - this.halfY[id] - pad;
400
+ const maxY = this.posY[id] + this.halfY[id] + pad;
401
+ const minZ = this.posZ[id] - this.halfZ[id] - pad;
402
+ const maxZ = this.posZ[id] + this.halfZ[id] + pad;
403
+ for (let j = 0; j < this.bodyHigh; j += 1) {
404
+ if (j === id)
405
+ continue;
406
+ const fj = this.flags[j];
407
+ if ((fj & (FLAG_DEAD | FLAG_STATIC)) !== 0 || (fj & FLAG_SLEEPING) === 0)
408
+ continue;
409
+ if (this.posX[j] - this.halfX[j] > maxX || this.posX[j] + this.halfX[j] < minX)
410
+ continue;
411
+ if (this.posY[j] - this.halfY[j] > maxY || this.posY[j] + this.halfY[j] < minY)
412
+ continue;
413
+ if (this.posZ[j] - this.halfZ[j] > maxZ || this.posZ[j] + this.halfZ[j] < minZ)
414
+ continue;
415
+ this.wake(j);
416
+ }
417
+ }
171
418
  isSleeping(i) {
172
419
  return (this.flags[i] & FLAG_SLEEPING) !== 0;
173
420
  }
421
+ isAlive(i) {
422
+ return (this.flags[i] & FLAG_DEAD) === 0;
423
+ }
174
424
  wake(i) {
175
- if ((this.flags[i] & FLAG_STATIC) !== 0)
425
+ if ((this.flags[i] & (FLAG_STATIC | FLAG_DEAD)) !== 0)
176
426
  return;
177
427
  this.flags[i] &= ~FLAG_SLEEPING;
178
428
  this.sleepTimer[i] = 0;
@@ -181,9 +431,40 @@ export class PhysicsWorld {
181
431
  this.prevZ[i] = this.posZ[i];
182
432
  this.addAwake(i);
183
433
  }
434
+ /** Overwrite a body's velocity directly, waking it if asleep. */
435
+ setVelocity(id, x, y, z) {
436
+ if ((this.flags[id] & FLAG_DEAD) !== 0)
437
+ return;
438
+ this.velX[id] = x;
439
+ this.velY[id] = y;
440
+ this.velZ[id] = z;
441
+ this.wake(id);
442
+ }
443
+ /** Move a body, keeping its current velocity, and wake it if asleep. */
444
+ setPosition(id, x, y, z) {
445
+ if ((this.flags[id] & FLAG_DEAD) !== 0)
446
+ return;
447
+ this.posX[id] = x;
448
+ this.posY[id] = y;
449
+ this.posZ[id] = z;
450
+ this.prevX[id] = x;
451
+ this.prevY[id] = y;
452
+ this.prevZ[id] = z;
453
+ this.bodyCell[id] = this.cellOf(id);
454
+ this.wake(id);
455
+ }
456
+ /** Move a body and zero its velocity — a hard reset (respawn, teleporter) instead of a slide. */
457
+ teleport(id, x, y, z) {
458
+ this.setPosition(id, x, y, z);
459
+ if ((this.flags[id] & FLAG_DEAD) !== 0)
460
+ return;
461
+ this.velX[id] = 0;
462
+ this.velY[id] = 0;
463
+ this.velZ[id] = 0;
464
+ }
184
465
  wakeAll() {
185
- for (let i = 0; i < this.bodyCount; i += 1) {
186
- if ((this.flags[i] & FLAG_STATIC) === 0) {
466
+ for (let i = 0; i < this.bodyHigh; i += 1) {
467
+ if ((this.flags[i] & (FLAG_STATIC | FLAG_DEAD)) === 0) {
187
468
  this.flags[i] &= ~FLAG_SLEEPING;
188
469
  this.sleepTimer[i] = 0;
189
470
  this.addAwake(i);
@@ -192,8 +473,13 @@ export class PhysicsWorld {
192
473
  }
193
474
  clear() {
194
475
  this.bodyCount = 0;
476
+ this.bodyHigh = 0;
477
+ this.bodyFreeCount = 0;
195
478
  this.accumulator = 0;
196
479
  this.awakeCount = 0;
480
+ this.jointHigh = 0;
481
+ this.jFreeCount = 0;
482
+ this.jointActiveCount = 0;
197
483
  }
198
484
  /** Advance by a real frame delta; runs whole fixed substeps and carries the remainder. */
199
485
  step(frameDt) {
@@ -205,7 +491,7 @@ export class PhysicsWorld {
205
491
  let substeps = 0;
206
492
  this.stats.contacts = 0;
207
493
  this.stats.pairs = 0;
208
- this.contact.fill(0, 0, this.bodyCount);
494
+ this.contact.fill(0, 0, this.bodyHigh);
209
495
  while (this.accumulator >= this.fixedDt) {
210
496
  this.substep(this.fixedDt);
211
497
  this.accumulator -= this.fixedDt;
@@ -243,8 +529,11 @@ export class PhysicsWorld {
243
529
  }
244
530
  this.buildGrid();
245
531
  const iterations = this.solverIterations;
246
- for (let it = 0; it < iterations; it += 1)
532
+ for (let it = 0; it < iterations; it += 1) {
247
533
  this.solve(it === 0, it === iterations - 1);
534
+ if (this.jointActiveCount > 0)
535
+ this.solveJoints(dt);
536
+ }
248
537
  for (let a = 0; a < this.awakeCount; a += 1)
249
538
  this.constrainBounds(this.awakeList[a]);
250
539
  this.updateSleep();
@@ -295,7 +584,7 @@ export class PhysicsWorld {
295
584
  }
296
585
  }
297
586
  buildGrid() {
298
- const n = this.bodyCount;
587
+ const n = this.bodyHigh;
299
588
  const start = this.cellStart;
300
589
  start.fill(0);
301
590
  // Sleeping bodies never move, so their cell index is cached from addBody / their last awake
@@ -305,13 +594,19 @@ export class PhysicsWorld {
305
594
  const i = this.awakeList[a];
306
595
  this.bodyCell[i] = this.cellOf(i);
307
596
  }
308
- for (let i = 0; i < n; i += 1)
597
+ // Dead slots (tombstoned by removeBody) sit inside [0, bodyHigh) but never enter the grid.
598
+ for (let i = 0; i < n; i += 1) {
599
+ if ((this.flags[i] & FLAG_DEAD) !== 0)
600
+ continue;
309
601
  start[this.bodyCell[i] + 1] += 1;
602
+ }
310
603
  for (let c = 0; c < this.numCells; c += 1) {
311
604
  start[c + 1] += start[c];
312
605
  this.cursor[c] = start[c];
313
606
  }
314
607
  for (let i = 0; i < n; i += 1) {
608
+ if ((this.flags[i] & FLAG_DEAD) !== 0)
609
+ continue;
315
610
  const c = this.bodyCell[i];
316
611
  this.sorted[this.cursor[c]++] = i;
317
612
  }
@@ -362,39 +657,94 @@ export class PhysicsWorld {
362
657
  }
363
658
  }
364
659
  resolve(i, j, restitutionPass, countPass) {
365
- const dx = this.posX[j] - this.posX[i];
366
- const px = this.halfX[i] + this.halfX[j] - Math.abs(dx);
367
- if (px <= 0)
368
- return;
369
- const dy = this.posY[j] - this.posY[i];
370
- const py = this.halfY[i] + this.halfY[j] - Math.abs(dy);
371
- if (py <= 0)
372
- return;
373
- const dz = this.posZ[j] - this.posZ[i];
374
- const pz = this.halfZ[i] + this.halfZ[j] - Math.abs(dz);
375
- if (pz <= 0)
376
- return;
377
- if (countPass) {
378
- this.stats.pairs += 1;
379
- this.stats.contacts += 1;
380
- this.contact[i] = 1;
381
- this.contact[j] = 1;
382
- }
383
660
  let nX = 0;
384
661
  let nY = 0;
385
662
  let nZ = 0;
386
- let pen;
387
- if (px <= py && px <= pz) {
388
- nX = dx < 0 ? -1 : 1;
389
- pen = px;
663
+ let pen = 0;
664
+ const sphereI = this.shape[i] === SHAPE_SPHERE;
665
+ const sphereJ = this.shape[j] === SHAPE_SPHERE;
666
+ let needsAxisNormal = true;
667
+ if (sphereI && sphereJ) {
668
+ const dx = this.posX[j] - this.posX[i];
669
+ const dy = this.posY[j] - this.posY[i];
670
+ const dz = this.posZ[j] - this.posZ[i];
671
+ const radiusSum = this.halfX[i] + this.halfX[j];
672
+ const dist2 = dx * dx + dy * dy + dz * dz;
673
+ if (dist2 >= radiusSum * radiusSum)
674
+ return;
675
+ const dist = Math.sqrt(dist2);
676
+ if (dist > 1e-9) {
677
+ nX = dx / dist;
678
+ nY = dy / dist;
679
+ nZ = dz / dist;
680
+ pen = radiusSum - dist;
681
+ }
682
+ else {
683
+ nY = 1;
684
+ pen = radiusSum;
685
+ }
686
+ needsAxisNormal = false;
390
687
  }
391
- else if (py <= pz) {
392
- nY = dy < 0 ? -1 : 1;
393
- pen = py;
688
+ else if (sphereI !== sphereJ) {
689
+ const box = sphereI ? j : i;
690
+ const sph = sphereI ? i : j;
691
+ const loX = this.posX[box] - this.halfX[box];
692
+ const hiX = this.posX[box] + this.halfX[box];
693
+ const loY = this.posY[box] - this.halfY[box];
694
+ const hiY = this.posY[box] + this.halfY[box];
695
+ const loZ = this.posZ[box] - this.halfZ[box];
696
+ const hiZ = this.posZ[box] + this.halfZ[box];
697
+ const sx = this.posX[sph];
698
+ const sy = this.posY[sph];
699
+ const sz = this.posZ[sph];
700
+ const dx = sx - (sx < loX ? loX : sx > hiX ? hiX : sx);
701
+ const dy = sy - (sy < loY ? loY : sy > hiY ? hiY : sy);
702
+ const dz = sz - (sz < loZ ? loZ : sz > hiZ ? hiZ : sz);
703
+ const dist2 = dx * dx + dy * dy + dz * dz;
704
+ if (dist2 > 1e-12) {
705
+ const radius = this.halfX[sph];
706
+ if (dist2 >= radius * radius)
707
+ return;
708
+ const dist = Math.sqrt(dist2);
709
+ const towardSphere = sph === j ? 1 / dist : -1 / dist;
710
+ nX = dx * towardSphere;
711
+ nY = dy * towardSphere;
712
+ nZ = dz * towardSphere;
713
+ pen = radius - dist;
714
+ needsAxisNormal = false;
715
+ }
394
716
  }
395
- else {
396
- nZ = dz < 0 ? -1 : 1;
397
- pen = pz;
717
+ if (needsAxisNormal) {
718
+ const dx = this.posX[j] - this.posX[i];
719
+ const px = this.halfX[i] + this.halfX[j] - Math.abs(dx);
720
+ if (px <= 0)
721
+ return;
722
+ const dy = this.posY[j] - this.posY[i];
723
+ const py = this.halfY[i] + this.halfY[j] - Math.abs(dy);
724
+ if (py <= 0)
725
+ return;
726
+ const dz = this.posZ[j] - this.posZ[i];
727
+ const pz = this.halfZ[i] + this.halfZ[j] - Math.abs(dz);
728
+ if (pz <= 0)
729
+ return;
730
+ if (px <= py && px <= pz) {
731
+ nX = dx < 0 ? -1 : 1;
732
+ pen = px;
733
+ }
734
+ else if (py <= pz) {
735
+ nY = dy < 0 ? -1 : 1;
736
+ pen = py;
737
+ }
738
+ else {
739
+ nZ = dz < 0 ? -1 : 1;
740
+ pen = pz;
741
+ }
742
+ }
743
+ if (countPass) {
744
+ this.stats.pairs += 1;
745
+ this.stats.contacts += 1;
746
+ this.contact[i] = 1;
747
+ this.contact[j] = 1;
398
748
  }
399
749
  const rvx = this.velX[j] - this.velX[i];
400
750
  const rvy = this.velY[j] - this.velY[i];
@@ -416,6 +766,17 @@ export class PhysicsWorld {
416
766
  if (rvn < 0) {
417
767
  const e = restitutionPass && -rvn > this.restitutionThreshold ? this.restitution : 0;
418
768
  const jn = (-(1 + e) * rvn) / imSum;
769
+ if (restitutionPass && this.collisionListener !== null && -rvn >= this.collisionMinSpeed) {
770
+ const ev = this.collisionEvent;
771
+ ev.a = i;
772
+ ev.b = j;
773
+ ev.nx = nX;
774
+ ev.ny = nY;
775
+ ev.nz = nZ;
776
+ ev.approachSpeed = -rvn;
777
+ ev.impulse = jn;
778
+ this.collisionListener(ev);
779
+ }
419
780
  this.velX[i] -= jn * imI * nX;
420
781
  this.velY[i] -= jn * imI * nY;
421
782
  this.velZ[i] -= jn * imI * nZ;
@@ -452,6 +813,97 @@ export class PhysicsWorld {
452
813
  this.posY[j] += corr * imJ * nY;
453
814
  this.posZ[j] += corr * imJ * nZ;
454
815
  }
816
+ solveJoints(dt) {
817
+ const beta = this.jointCorrection;
818
+ const slop = this.slop;
819
+ for (let id = 0; id < this.jointHigh; id += 1) {
820
+ if (this.jActive[id] !== 1)
821
+ continue;
822
+ const bA = this.jBodyA[id];
823
+ const bB = this.jBodyB[id];
824
+ const ax = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
825
+ const ay = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
826
+ const az = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
827
+ const bx = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
828
+ const by = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
829
+ const bz = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
830
+ const dx = bx - ax;
831
+ const dy = by - ay;
832
+ const dz = bz - az;
833
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
834
+ if (dist < 1e-9)
835
+ continue;
836
+ const nX = dx / dist;
837
+ const nY = dy / dist;
838
+ const nZ = dz / dist;
839
+ const c = dist - this.jRest[id];
840
+ const imA = bA >= 0 ? this.invMass[bA] : 0;
841
+ const imB = bB >= 0 ? this.invMass[bB] : 0;
842
+ const imSum = imA + imB;
843
+ if (imSum === 0)
844
+ continue;
845
+ const vax = bA >= 0 ? this.velX[bA] : 0;
846
+ const vay = bA >= 0 ? this.velY[bA] : 0;
847
+ const vaz = bA >= 0 ? this.velZ[bA] : 0;
848
+ const vbx = bB >= 0 ? this.velX[bB] : 0;
849
+ const vby = bB >= 0 ? this.velY[bB] : 0;
850
+ const vbz = bB >= 0 ? this.velZ[bB] : 0;
851
+ const rvn = (vbx - vax) * nX + (vby - vay) * nY + (vbz - vaz) * nZ;
852
+ if (this.jSpring[id] === 1) {
853
+ if (Math.abs(c) > slop) {
854
+ if (bA >= 0)
855
+ this.wake(bA);
856
+ if (bB >= 0)
857
+ this.wake(bB);
858
+ }
859
+ let jn = (-(this.jStiffness[id] * c + this.jDamping[id] * rvn) * dt) / imSum;
860
+ const maxJ = this.jMaxImpulse[id];
861
+ if (jn > maxJ)
862
+ jn = maxJ;
863
+ else if (jn < -maxJ)
864
+ jn = -maxJ;
865
+ if (bA >= 0) {
866
+ this.velX[bA] -= jn * imA * nX;
867
+ this.velY[bA] -= jn * imA * nY;
868
+ this.velZ[bA] -= jn * imA * nZ;
869
+ }
870
+ if (bB >= 0) {
871
+ this.velX[bB] += jn * imB * nX;
872
+ this.velY[bB] += jn * imB * nY;
873
+ this.velZ[bB] += jn * imB * nZ;
874
+ }
875
+ continue;
876
+ }
877
+ if (Math.abs(c) > slop) {
878
+ if (bA >= 0)
879
+ this.wake(bA);
880
+ if (bB >= 0)
881
+ this.wake(bB);
882
+ }
883
+ const jn = -rvn / imSum;
884
+ if (bA >= 0) {
885
+ this.velX[bA] -= jn * imA * nX;
886
+ this.velY[bA] -= jn * imA * nY;
887
+ this.velZ[bA] -= jn * imA * nZ;
888
+ }
889
+ if (bB >= 0) {
890
+ this.velX[bB] += jn * imB * nX;
891
+ this.velY[bB] += jn * imB * nY;
892
+ this.velZ[bB] += jn * imB * nZ;
893
+ }
894
+ const corr = (c * beta) / imSum;
895
+ if (bA >= 0) {
896
+ this.posX[bA] += corr * imA * nX;
897
+ this.posY[bA] += corr * imA * nY;
898
+ this.posZ[bA] += corr * imA * nZ;
899
+ }
900
+ if (bB >= 0) {
901
+ this.posX[bB] -= corr * imB * nX;
902
+ this.posY[bB] -= corr * imB * nY;
903
+ this.posZ[bB] -= corr * imB * nZ;
904
+ }
905
+ }
906
+ }
455
907
  updateSleep() {
456
908
  // A body sleeps when it has actually stopped moving — measured by displacement over
457
909
  // the substep, not instantaneous velocity, which can stay "stuck" nonzero on a body