@jgengine/core 0.7.0 → 0.9.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 (329) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +2 -0
  3. package/dist/ai/flock.d.ts +36 -0
  4. package/dist/ai/flock.js +182 -0
  5. package/dist/ai/groupAssist.d.ts +19 -0
  6. package/dist/ai/groupAssist.js +59 -0
  7. package/dist/ai/laneSelect.d.ts +21 -0
  8. package/dist/ai/laneSelect.js +37 -0
  9. package/dist/ai/mobBrain.d.ts +68 -0
  10. package/dist/ai/mobBrain.js +115 -0
  11. package/dist/ai/spawnDirector.d.ts +5 -0
  12. package/dist/ai/spawnDirector.js +14 -2
  13. package/dist/ai/threat.d.ts +2 -0
  14. package/dist/ai/threat.js +34 -0
  15. package/dist/anim/easing.d.ts +14 -0
  16. package/dist/anim/easing.js +55 -0
  17. package/dist/behaviour/behaviour.d.ts +136 -0
  18. package/dist/behaviour/behaviour.js +388 -0
  19. package/dist/cards/cardPile.d.ts +1 -0
  20. package/dist/cards/cardPile.js +12 -3
  21. package/dist/cartridge/runtime.d.ts +15 -0
  22. package/dist/cartridge/runtime.js +556 -0
  23. package/dist/cartridge/spec.d.ts +188 -0
  24. package/dist/cartridge/spec.js +19 -0
  25. package/dist/cartridge/validate.d.ts +2 -0
  26. package/dist/cartridge/validate.js +78 -0
  27. package/dist/combat/abilityKit.d.ts +22 -2
  28. package/dist/combat/abilityKit.js +72 -13
  29. package/dist/combat/castRunner.d.ts +40 -0
  30. package/dist/combat/castRunner.js +56 -0
  31. package/dist/combat/comboPoints.d.ts +14 -0
  32. package/dist/combat/comboPoints.js +52 -0
  33. package/dist/combat/defensiveWindow.d.ts +1 -1
  34. package/dist/combat/defensiveWindow.js +5 -3
  35. package/dist/combat/effects.d.ts +1 -1
  36. package/dist/combat/effects.js +9 -6
  37. package/dist/combat/projectiles.d.ts +64 -6
  38. package/dist/combat/projectiles.js +232 -74
  39. package/dist/combat/resistance.d.ts +16 -4
  40. package/dist/combat/resistance.js +43 -7
  41. package/dist/combat/resourcePool.d.ts +20 -0
  42. package/dist/combat/resourcePool.js +55 -0
  43. package/dist/combat/shotOrigin.d.ts +40 -0
  44. package/dist/combat/shotOrigin.js +57 -0
  45. package/dist/combat/telegraph.d.ts +30 -1
  46. package/dist/combat/telegraph.js +61 -0
  47. package/dist/commands/commandRegistry.d.ts +1 -1
  48. package/dist/commands/commandRegistry.js +2 -1
  49. package/dist/crafting/recipe.js +4 -1
  50. package/dist/data/dataSource.d.ts +26 -0
  51. package/dist/data/dataSource.js +93 -0
  52. package/dist/data/devProxy.d.ts +11 -0
  53. package/dist/data/devProxy.js +38 -0
  54. package/dist/data/fetchJson.d.ts +20 -0
  55. package/dist/data/fetchJson.js +43 -0
  56. package/dist/data/jsonDataSource.d.ts +4 -0
  57. package/dist/data/jsonDataSource.js +6 -0
  58. package/dist/devtools/devtools.d.ts +205 -0
  59. package/dist/devtools/devtools.js +832 -0
  60. package/dist/devtools/transformTunables.d.ts +14 -0
  61. package/dist/devtools/transformTunables.js +38 -0
  62. package/dist/devtools/tunableSchema.d.ts +140 -0
  63. package/dist/devtools/tunableSchema.js +476 -0
  64. package/dist/faction/factions.d.ts +33 -0
  65. package/dist/faction/factions.js +76 -0
  66. package/dist/faction/reputation.d.ts +32 -0
  67. package/dist/faction/reputation.js +82 -0
  68. package/dist/game/chat.d.ts +68 -0
  69. package/dist/game/chat.js +173 -0
  70. package/dist/game/chatFilter.d.ts +15 -0
  71. package/dist/game/chatFilter.js +55 -0
  72. package/dist/game/controlGate.d.ts +4 -0
  73. package/dist/game/controlGate.js +7 -0
  74. package/dist/game/defineGame.d.ts +10 -2
  75. package/dist/game/defineGame.js +2 -1
  76. package/dist/game/events.d.ts +35 -0
  77. package/dist/game/gamePhase.d.ts +20 -0
  78. package/dist/game/gamePhase.js +21 -0
  79. package/dist/game/levelSequence.d.ts +44 -0
  80. package/dist/game/levelSequence.js +78 -0
  81. package/dist/game/lootTable.d.ts +10 -1
  82. package/dist/game/lootTable.js +27 -7
  83. package/dist/game/playableGame.d.ts +286 -6
  84. package/dist/game/playableGame.js +6 -1
  85. package/dist/game/progression.d.ts +1 -0
  86. package/dist/game/progression.js +23 -2
  87. package/dist/game/quest.d.ts +7 -3
  88. package/dist/game/quest.js +17 -8
  89. package/dist/game/race.d.ts +44 -1
  90. package/dist/game/race.js +116 -11
  91. package/dist/game/recordBook.d.ts +29 -0
  92. package/dist/game/recordBook.js +86 -0
  93. package/dist/game/social.d.ts +42 -0
  94. package/dist/game/social.js +122 -0
  95. package/dist/game/spawnPoints.d.ts +19 -0
  96. package/dist/game/spawnPoints.js +23 -0
  97. package/dist/game/talents.d.ts +47 -0
  98. package/dist/game/talents.js +140 -0
  99. package/dist/game/worldItem.d.ts +3 -0
  100. package/dist/game/worldItem.js +12 -6
  101. package/dist/index.d.ts +1 -0
  102. package/dist/index.js +1 -0
  103. package/dist/input/actionBindings.d.ts +10 -0
  104. package/dist/input/actionBindings.js +14 -0
  105. package/dist/input/axisInput.d.ts +26 -3
  106. package/dist/input/axisInput.js +44 -4
  107. package/dist/input/bindingOverrides.d.ts +25 -0
  108. package/dist/input/bindingOverrides.js +95 -0
  109. package/dist/input/gestureSurface.d.ts +31 -0
  110. package/dist/input/gestureSurface.js +120 -0
  111. package/dist/input/pointer.d.ts +45 -0
  112. package/dist/input/pointer.js +48 -0
  113. package/dist/input/pointerAxis.d.ts +30 -0
  114. package/dist/input/pointerAxis.js +31 -0
  115. package/dist/input/touchScheme.d.ts +89 -0
  116. package/dist/input/touchScheme.js +153 -0
  117. package/dist/item/affix.d.ts +1 -1
  118. package/dist/item/affix.js +1 -19
  119. package/dist/meta/changelog.d.ts +1 -1
  120. package/dist/meta/changelog.js +59 -1
  121. package/dist/movement/dash.d.ts +3 -0
  122. package/dist/movement/dash.js +19 -5
  123. package/dist/movement/glideModel.d.ts +44 -0
  124. package/dist/movement/glideModel.js +54 -0
  125. package/dist/movement/grappleSwing.d.ts +34 -0
  126. package/dist/movement/grappleSwing.js +56 -0
  127. package/dist/movement/leaderTrail.d.ts +29 -0
  128. package/dist/movement/leaderTrail.js +67 -0
  129. package/dist/movement/movementModel.d.ts +36 -3
  130. package/dist/movement/movementModel.js +81 -5
  131. package/dist/movement/steering.d.ts +27 -0
  132. package/dist/movement/steering.js +40 -0
  133. package/dist/movement/voxelController.d.ts +61 -0
  134. package/dist/movement/voxelController.js +229 -0
  135. package/dist/multiplayer/chatContract.d.ts +42 -0
  136. package/dist/multiplayer/chatContract.js +57 -0
  137. package/dist/multiplayer/feedWriteGate.d.ts +10 -0
  138. package/dist/multiplayer/feedWriteGate.js +12 -0
  139. package/dist/multiplayer/identity.d.ts +13 -0
  140. package/dist/multiplayer/identity.js +20 -0
  141. package/dist/multiplayer/poseSyncGate.d.ts +1 -0
  142. package/dist/multiplayer/poseSyncGate.js +13 -1
  143. package/dist/multiplayer/presenceModel.d.ts +17 -2
  144. package/dist/multiplayer/presenceModel.js +30 -4
  145. package/dist/multiplayer/voiceContract.d.ts +44 -0
  146. package/dist/multiplayer/voiceContract.js +108 -0
  147. package/dist/nav/corridors.d.ts +30 -0
  148. package/dist/nav/corridors.js +69 -0
  149. package/dist/nav/navConstrain.d.ts +14 -0
  150. package/dist/nav/navConstrain.js +26 -0
  151. package/dist/nav/navFromEnvironment.d.ts +11 -0
  152. package/dist/nav/navFromEnvironment.js +16 -0
  153. package/dist/nav/navGrid.d.ts +10 -0
  154. package/dist/nav/navGrid.js +48 -5
  155. package/dist/nav/pathFollow.d.ts +4 -2
  156. package/dist/nav/pathFollow.js +5 -3
  157. package/dist/nav/railGraph.d.ts +66 -0
  158. package/dist/nav/railGraph.js +129 -0
  159. package/dist/nav/timetable.d.ts +41 -0
  160. package/dist/nav/timetable.js +94 -0
  161. package/dist/physics/ballisticSweep.d.ts +18 -0
  162. package/dist/physics/ballisticSweep.js +49 -0
  163. package/dist/physics/buoyancy.d.ts +6 -0
  164. package/dist/physics/buoyancy.js +14 -0
  165. package/dist/physics/flowTube.d.ts +32 -0
  166. package/dist/physics/flowTube.js +64 -0
  167. package/dist/physics/forceVolume.d.ts +27 -0
  168. package/dist/physics/forceVolume.js +43 -5
  169. package/dist/physics/kinematicVehicle.d.ts +52 -0
  170. package/dist/physics/kinematicVehicle.js +81 -0
  171. package/dist/physics/physicsWorld.d.ts +54 -8
  172. package/dist/physics/physicsWorld.js +400 -79
  173. package/dist/physics/ragdoll.js +1 -1
  174. package/dist/physics/spatialGrid.d.ts +1 -1
  175. package/dist/physics/spatialGrid.js +5 -0
  176. package/dist/physics/vehicleBody.d.ts +6 -0
  177. package/dist/physics/vehicleBody.js +18 -18
  178. package/dist/puzzle/cellGrid.d.ts +26 -0
  179. package/dist/puzzle/cellGrid.js +110 -0
  180. package/dist/puzzle/fallingPiece.d.ts +24 -0
  181. package/dist/puzzle/fallingPiece.js +51 -0
  182. package/dist/random/nameGen.d.ts +18 -0
  183. package/dist/random/nameGen.js +35 -0
  184. package/dist/random/rng.d.ts +2 -0
  185. package/dist/random/rng.js +23 -0
  186. package/dist/random/seedLink.d.ts +5 -0
  187. package/dist/random/seedLink.js +35 -0
  188. package/dist/runtime/adapter.d.ts +35 -0
  189. package/dist/runtime/adapter.js +37 -1
  190. package/dist/runtime/cameraDirector.d.ts +15 -0
  191. package/dist/runtime/cameraDirector.js +31 -0
  192. package/dist/runtime/commandRunner.d.ts +2 -2
  193. package/dist/runtime/commandRunner.js +2 -2
  194. package/dist/runtime/gameContext.d.ts +65 -2
  195. package/dist/runtime/gameContext.js +280 -9
  196. package/dist/runtime/gameRuntime.d.ts +5 -1
  197. package/dist/runtime/gameRuntime.js +13 -12
  198. package/dist/runtime/hostPersistence.d.ts +30 -1
  199. package/dist/runtime/hostPersistence.js +70 -5
  200. package/dist/runtime/inputSnapshot.d.ts +12 -0
  201. package/dist/runtime/inputSnapshot.js +17 -0
  202. package/dist/runtime/motionIntents.d.ts +33 -0
  203. package/dist/runtime/motionIntents.js +43 -0
  204. package/dist/runtime/snapshot.d.ts +1 -0
  205. package/dist/runtime/snapshot.js +1 -1
  206. package/dist/runtime/transport.d.ts +32 -0
  207. package/dist/runtime/visibility.d.ts +74 -0
  208. package/dist/runtime/visibility.js +127 -0
  209. package/dist/scene/colliders.d.ts +44 -0
  210. package/dist/scene/colliders.js +77 -0
  211. package/dist/scene/entityStore.d.ts +30 -5
  212. package/dist/scene/entityStore.js +83 -13
  213. package/dist/scene/movementSpeed.d.ts +35 -0
  214. package/dist/scene/movementSpeed.js +20 -0
  215. package/dist/scene/objectQuery.d.ts +29 -0
  216. package/dist/scene/objectQuery.js +140 -0
  217. package/dist/scene/objectStore.d.ts +13 -0
  218. package/dist/scene/objectStore.js +130 -6
  219. package/dist/scene/paintLayer.d.ts +15 -0
  220. package/dist/scene/paintLayer.js +53 -0
  221. package/dist/scene/sceneRaycast.d.ts +80 -0
  222. package/dist/scene/sceneRaycast.js +284 -0
  223. package/dist/scene/spatial.d.ts +17 -1
  224. package/dist/scene/spatial.js +140 -17
  225. package/dist/sensor/concealment.d.ts +21 -0
  226. package/dist/sensor/concealment.js +63 -0
  227. package/dist/sensor/freezeMonitor.d.ts +17 -0
  228. package/dist/sensor/freezeMonitor.js +35 -0
  229. package/dist/sensor/recordingBuffer.d.ts +6 -0
  230. package/dist/sensor/recordingBuffer.js +7 -0
  231. package/dist/sensor/replayLoop.d.ts +39 -0
  232. package/dist/sensor/replayLoop.js +64 -0
  233. package/dist/sensor/visionCone.d.ts +31 -0
  234. package/dist/sensor/visionCone.js +72 -0
  235. package/dist/session/extraction.d.ts +2 -2
  236. package/dist/session/extraction.js +13 -2
  237. package/dist/session/roles.d.ts +6 -0
  238. package/dist/session/roles.js +32 -0
  239. package/dist/session/roundState.d.ts +41 -17
  240. package/dist/session/roundState.js +67 -38
  241. package/dist/settings/settingsModel.d.ts +97 -0
  242. package/dist/settings/settingsModel.js +100 -0
  243. package/dist/stats/statModifiers.d.ts +5 -2
  244. package/dist/stats/statModifiers.js +32 -14
  245. package/dist/store/observableKeyedStore.js +8 -2
  246. package/dist/survival/decayMeter.d.ts +1 -1
  247. package/dist/survival/decayMeter.js +0 -1
  248. package/dist/tactics/fallingGrid.d.ts +62 -0
  249. package/dist/tactics/fallingGrid.js +158 -0
  250. package/dist/tactics/tacticalGrid.d.ts +11 -0
  251. package/dist/tactics/tacticalGrid.js +17 -0
  252. package/dist/time/beatClock.d.ts +18 -0
  253. package/dist/time/beatClock.js +24 -0
  254. package/dist/time/idleProgress.d.ts +29 -0
  255. package/dist/time/idleProgress.js +31 -0
  256. package/dist/time/serverTick.d.ts +7 -1
  257. package/dist/time/serverTick.js +27 -4
  258. package/dist/time/simClock.d.ts +25 -1
  259. package/dist/time/simClock.js +28 -3
  260. package/dist/time/stateSchedule.d.ts +57 -0
  261. package/dist/time/stateSchedule.js +121 -0
  262. package/dist/turn/commit.d.ts +2 -2
  263. package/dist/turn/commit.js +2 -2
  264. package/dist/turn/intent.d.ts +14 -0
  265. package/dist/turn/intent.js +29 -0
  266. package/dist/turn/turnLoop.d.ts +2 -0
  267. package/dist/turn/turnLoop.js +8 -1
  268. package/dist/ui/hudLayout.d.ts +58 -0
  269. package/dist/ui/hudLayout.js +207 -0
  270. package/dist/ui/hudScale.d.ts +44 -0
  271. package/dist/ui/hudScale.js +55 -0
  272. package/dist/visibility/assetStreaming.d.ts +66 -0
  273. package/dist/visibility/assetStreaming.js +201 -0
  274. package/dist/visibility/bounds.d.ts +66 -0
  275. package/dist/visibility/bounds.js +113 -0
  276. package/dist/visibility/camera.d.ts +21 -0
  277. package/dist/visibility/camera.js +3 -0
  278. package/dist/visibility/config.d.ts +21 -0
  279. package/dist/visibility/config.js +1 -0
  280. package/dist/visibility/diagnostics.d.ts +26 -0
  281. package/dist/visibility/diagnostics.js +36 -0
  282. package/dist/visibility/distance.d.ts +11 -0
  283. package/dist/visibility/distance.js +30 -0
  284. package/dist/visibility/frustum.d.ts +47 -0
  285. package/dist/visibility/frustum.js +186 -0
  286. package/dist/visibility/occlusion.d.ts +36 -0
  287. package/dist/visibility/occlusion.js +45 -0
  288. package/dist/visibility/settings.d.ts +75 -0
  289. package/dist/visibility/settings.js +49 -0
  290. package/dist/visibility/simulationCulling.d.ts +34 -0
  291. package/dist/visibility/simulationCulling.js +36 -0
  292. package/dist/visibility/spatialIndex.d.ts +45 -0
  293. package/dist/visibility/spatialIndex.js +198 -0
  294. package/dist/visibility/visibilitySystem.d.ts +90 -0
  295. package/dist/visibility/visibilitySystem.js +257 -0
  296. package/dist/world/buildings.d.ts +6 -0
  297. package/dist/world/buildings.js +152 -0
  298. package/dist/world/carve.d.ts +7 -2
  299. package/dist/world/carve.js +17 -7
  300. package/dist/world/cellStates.d.ts +45 -0
  301. package/dist/world/cellStates.js +103 -0
  302. package/dist/world/environmentSummary.d.ts +61 -0
  303. package/dist/world/environmentSummary.js +138 -0
  304. package/dist/world/features.d.ts +138 -9
  305. package/dist/world/features.js +73 -2
  306. package/dist/world/gridInstances.d.ts +12 -0
  307. package/dist/world/gridInstances.js +160 -0
  308. package/dist/world/lod.d.ts +17 -0
  309. package/dist/world/lod.js +66 -0
  310. package/dist/world/mapLayers.d.ts +60 -0
  311. package/dist/world/mapLayers.js +44 -0
  312. package/dist/world/massing.d.ts +45 -0
  313. package/dist/world/massing.js +581 -0
  314. package/dist/world/minimap.d.ts +13 -1
  315. package/dist/world/minimap.js +21 -2
  316. package/dist/world/placementController.d.ts +22 -1
  317. package/dist/world/placementController.js +56 -0
  318. package/dist/world/terrain.d.ts +62 -1
  319. package/dist/world/terrain.js +253 -9
  320. package/dist/world/voxelField.d.ts +54 -0
  321. package/dist/world/voxelField.js +241 -0
  322. package/dist/world/water.d.ts +7 -0
  323. package/dist/world/water.js +19 -4
  324. package/dist/world/weather.d.ts +3 -18
  325. package/dist/world/weather.js +4 -17
  326. package/dist/world/windZones.d.ts +47 -0
  327. package/dist/world/windZones.js +59 -0
  328. package/llms.txt +4983 -0
  329. package/package.json +14 -3
@@ -1,5 +1,14 @@
1
+ export const SHAPE_BOX = 0;
2
+ export const SHAPE_SPHERE = 1;
3
+ export const JOINT_FIXED = 0;
4
+ export const JOINT_HINGE = 1;
5
+ export const JOINT_DISTANCE = 2;
6
+ export const JOINT_SPRING = 3;
1
7
  const FLAG_SLEEPING = 1;
2
8
  const FLAG_STATIC = 2;
9
+ const FLAG_DEAD = 4;
10
+ /** Cap on `nx*ny*nz` broadphase cells — guards a huge-bounds/tiny-cellSize config from hanging `step()`. */
11
+ export const MAX_BROADPHASE_CELLS = 1_000_000;
3
12
  /** Grid cell containing a point, clamped into the grid. Pure — exported for tests. */
4
13
  export function cellCoord(value, min, cellSize, cells) {
5
14
  const raw = Math.floor((value - min) / cellSize);
@@ -39,6 +48,8 @@ export class PhysicsWorld {
39
48
  halfZ;
40
49
  invMass;
41
50
  flags;
51
+ /** `SHAPE_BOX` (0) or `SHAPE_SPHERE` (1) per body; spheres keep their radius in halfX/halfY/halfZ. */
52
+ shape;
42
53
  /** 1 for a body that took part in a contact this frame; reset each `step()`. Render tint reads this. */
43
54
  contact;
44
55
  sleepTimer;
@@ -58,7 +69,7 @@ export class PhysicsWorld {
58
69
  awakeList;
59
70
  awakeSlot;
60
71
  awakeCount = 0;
61
- jSpring;
72
+ jKind;
62
73
  jActive;
63
74
  jBodyA;
64
75
  jBodyB;
@@ -68,6 +79,9 @@ export class PhysicsWorld {
68
79
  jAnchorBX;
69
80
  jAnchorBY;
70
81
  jAnchorBZ;
82
+ jAxisX;
83
+ jAxisY;
84
+ jAxisZ;
71
85
  jRest;
72
86
  jStiffness;
73
87
  jDamping;
@@ -76,6 +90,12 @@ export class PhysicsWorld {
76
90
  jFreeCount = 0;
77
91
  jointHigh = 0;
78
92
  jointActiveCount = 0;
93
+ // Removed bodies tombstone (FLAG_DEAD) rather than compact: `id` is the SoA slot itself, handed
94
+ // out to callers and stored raw in joints/game state, so a slot can never move once allocated.
95
+ // `bodyHigh` is the high-water mark of ever-used slots; `bodyFree` recycles dead ones for addBody.
96
+ bodyFree;
97
+ bodyFreeCount = 0;
98
+ bodyHigh = 0;
79
99
  collisionListener = null;
80
100
  collisionMinSpeed = 0;
81
101
  collisionEvent = {
@@ -131,6 +151,7 @@ export class PhysicsWorld {
131
151
  this.halfZ = new Float32Array(cap);
132
152
  this.invMass = new Float32Array(cap);
133
153
  this.flags = new Uint8Array(cap);
154
+ this.shape = new Uint8Array(cap);
134
155
  this.contact = new Uint8Array(cap);
135
156
  this.sleepTimer = new Uint16Array(cap);
136
157
  this.prevX = new Float32Array(cap);
@@ -141,14 +162,20 @@ export class PhysicsWorld {
141
162
  this.ny = Math.max(1, Math.ceil(span(1) / this.cellSize));
142
163
  this.nz = Math.max(1, Math.ceil(span(2) / this.cellSize));
143
164
  this.numCells = this.nx * this.ny * this.nz;
165
+ if (this.numCells > MAX_BROADPHASE_CELLS) {
166
+ throw new Error(`PhysicsWorld: broadphase grid too large (${this.nx}x${this.ny}x${this.nz} = ${this.numCells} cells, ` +
167
+ `cap ${MAX_BROADPHASE_CELLS}). bounds=[${config.bounds.min}]..[${config.bounds.max}] cellSize=${this.cellSize}; ` +
168
+ `raise cellSize or shrink bounds.`);
169
+ }
144
170
  this.cellStart = new Int32Array(this.numCells + 1);
145
171
  this.cursor = new Int32Array(this.numCells);
146
172
  this.sorted = new Int32Array(cap);
147
173
  this.bodyCell = new Int32Array(cap);
148
174
  this.awakeList = new Int32Array(cap);
149
175
  this.awakeSlot = new Int32Array(cap).fill(-1);
176
+ this.bodyFree = new Int32Array(cap);
150
177
  const jc = this.jointCapacity;
151
- this.jSpring = new Uint8Array(jc);
178
+ this.jKind = new Uint8Array(jc);
152
179
  this.jActive = new Uint8Array(jc);
153
180
  this.jBodyA = new Int32Array(jc);
154
181
  this.jBodyB = new Int32Array(jc);
@@ -158,6 +185,9 @@ export class PhysicsWorld {
158
185
  this.jAnchorBX = new Float32Array(jc);
159
186
  this.jAnchorBY = new Float32Array(jc);
160
187
  this.jAnchorBZ = new Float32Array(jc);
188
+ this.jAxisX = new Float32Array(jc);
189
+ this.jAxisY = new Float32Array(jc);
190
+ this.jAxisZ = new Float32Array(jc);
161
191
  this.jRest = new Float32Array(jc);
162
192
  this.jStiffness = new Float32Array(jc);
163
193
  this.jDamping = new Float32Array(jc);
@@ -184,6 +214,10 @@ export class PhysicsWorld {
184
214
  get count() {
185
215
  return this.bodyCount;
186
216
  }
217
+ /** One past the highest slot `addBody` has ever handed out — the range `removeBody` leaves holes in. */
218
+ get highWater() {
219
+ return this.bodyHigh;
220
+ }
187
221
  get cells() {
188
222
  return { nx: this.nx, ny: this.ny, nz: this.nz, total: this.numCells };
189
223
  }
@@ -200,18 +234,25 @@ export class PhysicsWorld {
200
234
  this.collisionMinSpeed = minApproachSpeed;
201
235
  }
202
236
  hingeJoint(options) {
203
- return this.addJoint(options, false, 0);
237
+ const raw = options.axis ?? [0, 1, 0];
238
+ const len = Math.hypot(raw[0], raw[1], raw[2]);
239
+ if (!(len > 1e-9))
240
+ throw new Error("PhysicsWorld: hingeJoint axis must be non-zero");
241
+ return this.addJoint(options, JOINT_HINGE, 0, [raw[0] / len, raw[1] / len, raw[2] / len]);
204
242
  }
205
243
  fixedJoint(options) {
206
- return this.addJoint(options, false, 0);
244
+ return this.addJoint(options, JOINT_FIXED, 0, null);
207
245
  }
208
246
  distanceJoint(options) {
209
- return this.addJoint(options, false, null);
247
+ return this.addJoint(options, JOINT_DISTANCE, null, null);
210
248
  }
211
249
  springJoint(options) {
212
- return this.addJoint(options, true, null);
250
+ return this.addJoint(options, JOINT_SPRING, null, null);
213
251
  }
214
- addJoint(options, spring, restOverride) {
252
+ addJoint(options, kind, restOverride, axis) {
253
+ if (kind !== JOINT_FIXED && kind !== JOINT_HINGE && kind !== JOINT_DISTANCE && kind !== JOINT_SPRING) {
254
+ throw new Error(`PhysicsWorld: joint kind ${kind} is not implemented`);
255
+ }
215
256
  let id;
216
257
  if (this.jFreeCount > 0) {
217
258
  id = this.jFree[--this.jFreeCount];
@@ -232,7 +273,10 @@ export class PhysicsWorld {
232
273
  this.jAnchorBX[id] = aB?.[0] ?? 0;
233
274
  this.jAnchorBY[id] = aB?.[1] ?? 0;
234
275
  this.jAnchorBZ[id] = aB?.[2] ?? 0;
235
- this.jSpring[id] = spring ? 1 : 0;
276
+ this.jKind[id] = kind;
277
+ this.jAxisX[id] = axis?.[0] ?? 0;
278
+ this.jAxisY[id] = axis?.[1] ?? 0;
279
+ this.jAxisZ[id] = axis?.[2] ?? 0;
236
280
  this.jStiffness[id] = options.stiffness ?? 40;
237
281
  this.jDamping[id] = options.damping ?? 6;
238
282
  this.jMaxImpulse[id] = options.maxImpulse ?? Number.POSITIVE_INFINITY;
@@ -254,6 +298,12 @@ export class PhysicsWorld {
254
298
  this.jAnchorBY[id] = y;
255
299
  this.jAnchorBZ[id] = z;
256
300
  }
301
+ /** Move a joint's local anchor offset on A (world axes, no rotation applied by the solver). */
302
+ setJointAnchorA(id, x, y, z) {
303
+ this.jAnchorAX[id] = x;
304
+ this.jAnchorAY[id] = y;
305
+ this.jAnchorAZ[id] = z;
306
+ }
257
307
  setJointRest(id, restLength) {
258
308
  this.jRest[id] = restLength;
259
309
  }
@@ -291,9 +341,16 @@ export class PhysicsWorld {
291
341
  return n;
292
342
  }
293
343
  addBody(options) {
294
- if (this.bodyCount >= this.capacity)
295
- throw new Error("PhysicsWorld: capacity exceeded");
296
- const i = this.bodyCount++;
344
+ let i;
345
+ if (this.bodyFreeCount > 0) {
346
+ i = this.bodyFree[--this.bodyFreeCount];
347
+ }
348
+ else {
349
+ if (this.bodyHigh >= this.capacity)
350
+ throw new Error("PhysicsWorld: capacity exceeded");
351
+ i = this.bodyHigh++;
352
+ }
353
+ this.bodyCount += 1;
297
354
  this.posX[i] = options.position[0];
298
355
  this.posY[i] = options.position[1];
299
356
  this.posZ[i] = options.position[2];
@@ -301,9 +358,18 @@ export class PhysicsWorld {
301
358
  this.velX[i] = v?.[0] ?? 0;
302
359
  this.velY[i] = v?.[1] ?? 0;
303
360
  this.velZ[i] = v?.[2] ?? 0;
304
- this.halfX[i] = options.halfExtents[0];
305
- this.halfY[i] = options.halfExtents[1];
306
- this.halfZ[i] = options.halfExtents[2];
361
+ if (options.shape === "sphere") {
362
+ this.halfX[i] = options.radius;
363
+ this.halfY[i] = options.radius;
364
+ this.halfZ[i] = options.radius;
365
+ this.shape[i] = SHAPE_SPHERE;
366
+ }
367
+ else {
368
+ this.halfX[i] = options.halfExtents[0];
369
+ this.halfY[i] = options.halfExtents[1];
370
+ this.halfZ[i] = options.halfExtents[2];
371
+ this.shape[i] = SHAPE_BOX;
372
+ }
307
373
  const isStatic = options.static === true;
308
374
  const mass = options.mass ?? 1;
309
375
  this.invMass[i] = isStatic || mass <= 0 ? 0 : 1 / mass;
@@ -314,17 +380,69 @@ export class PhysicsWorld {
314
380
  f |= FLAG_SLEEPING;
315
381
  this.flags[i] = f;
316
382
  this.sleepTimer[i] = 0;
383
+ this.contact[i] = 0;
317
384
  this.awakeSlot[i] = -1;
318
385
  this.bodyCell[i] = this.cellOf(i);
319
386
  if ((f & FLAG_SLEEPING) === 0)
320
387
  this.addAwake(i);
321
388
  return i;
322
389
  }
390
+ /**
391
+ * Remove a body: it drops out of integration, the solver, and the broadphase, and its slot is
392
+ * queued for the next `addBody` to reuse. `id` and every other body's `id` stay valid — a dead
393
+ * slot is tombstoned (`FLAG_DEAD`), never swapped, since ids are handed out raw and stored as-is
394
+ * in joints and game state. There is no persistent contact set to consult, so — conservatively —
395
+ * any sleeping body whose AABB touches the removed body's (padded by `slop`) is woken; a body
396
+ * that was resting on it always qualifies.
397
+ */
398
+ removeBody(id) {
399
+ if (id < 0 || id >= this.bodyHigh)
400
+ return;
401
+ if ((this.flags[id] & FLAG_DEAD) !== 0)
402
+ return;
403
+ this.wakeTouching(id);
404
+ this.removeAwake(id);
405
+ this.flags[id] = FLAG_DEAD;
406
+ this.invMass[id] = 0;
407
+ this.velX[id] = 0;
408
+ this.velY[id] = 0;
409
+ this.velZ[id] = 0;
410
+ this.sleepTimer[id] = 0;
411
+ this.contact[id] = 0;
412
+ this.bodyCount -= 1;
413
+ this.bodyFree[this.bodyFreeCount++] = id;
414
+ }
415
+ wakeTouching(id) {
416
+ const pad = this.slop + 0.01;
417
+ const minX = this.posX[id] - this.halfX[id] - pad;
418
+ const maxX = this.posX[id] + this.halfX[id] + pad;
419
+ const minY = this.posY[id] - this.halfY[id] - pad;
420
+ const maxY = this.posY[id] + this.halfY[id] + pad;
421
+ const minZ = this.posZ[id] - this.halfZ[id] - pad;
422
+ const maxZ = this.posZ[id] + this.halfZ[id] + pad;
423
+ for (let j = 0; j < this.bodyHigh; j += 1) {
424
+ if (j === id)
425
+ continue;
426
+ const fj = this.flags[j];
427
+ if ((fj & (FLAG_DEAD | FLAG_STATIC)) !== 0 || (fj & FLAG_SLEEPING) === 0)
428
+ continue;
429
+ if (this.posX[j] - this.halfX[j] > maxX || this.posX[j] + this.halfX[j] < minX)
430
+ continue;
431
+ if (this.posY[j] - this.halfY[j] > maxY || this.posY[j] + this.halfY[j] < minY)
432
+ continue;
433
+ if (this.posZ[j] - this.halfZ[j] > maxZ || this.posZ[j] + this.halfZ[j] < minZ)
434
+ continue;
435
+ this.wake(j);
436
+ }
437
+ }
323
438
  isSleeping(i) {
324
439
  return (this.flags[i] & FLAG_SLEEPING) !== 0;
325
440
  }
441
+ isAlive(i) {
442
+ return (this.flags[i] & FLAG_DEAD) === 0;
443
+ }
326
444
  wake(i) {
327
- if ((this.flags[i] & FLAG_STATIC) !== 0)
445
+ if ((this.flags[i] & (FLAG_STATIC | FLAG_DEAD)) !== 0)
328
446
  return;
329
447
  this.flags[i] &= ~FLAG_SLEEPING;
330
448
  this.sleepTimer[i] = 0;
@@ -333,9 +451,40 @@ export class PhysicsWorld {
333
451
  this.prevZ[i] = this.posZ[i];
334
452
  this.addAwake(i);
335
453
  }
454
+ /** Overwrite a body's velocity directly, waking it if asleep. */
455
+ setVelocity(id, x, y, z) {
456
+ if ((this.flags[id] & FLAG_DEAD) !== 0)
457
+ return;
458
+ this.velX[id] = x;
459
+ this.velY[id] = y;
460
+ this.velZ[id] = z;
461
+ this.wake(id);
462
+ }
463
+ /** Move a body, keeping its current velocity, and wake it if asleep. */
464
+ setPosition(id, x, y, z) {
465
+ if ((this.flags[id] & FLAG_DEAD) !== 0)
466
+ return;
467
+ this.posX[id] = x;
468
+ this.posY[id] = y;
469
+ this.posZ[id] = z;
470
+ this.prevX[id] = x;
471
+ this.prevY[id] = y;
472
+ this.prevZ[id] = z;
473
+ this.bodyCell[id] = this.cellOf(id);
474
+ this.wake(id);
475
+ }
476
+ /** Move a body and zero its velocity — a hard reset (respawn, teleporter) instead of a slide. */
477
+ teleport(id, x, y, z) {
478
+ this.setPosition(id, x, y, z);
479
+ if ((this.flags[id] & FLAG_DEAD) !== 0)
480
+ return;
481
+ this.velX[id] = 0;
482
+ this.velY[id] = 0;
483
+ this.velZ[id] = 0;
484
+ }
336
485
  wakeAll() {
337
- for (let i = 0; i < this.bodyCount; i += 1) {
338
- if ((this.flags[i] & FLAG_STATIC) === 0) {
486
+ for (let i = 0; i < this.bodyHigh; i += 1) {
487
+ if ((this.flags[i] & (FLAG_STATIC | FLAG_DEAD)) === 0) {
339
488
  this.flags[i] &= ~FLAG_SLEEPING;
340
489
  this.sleepTimer[i] = 0;
341
490
  this.addAwake(i);
@@ -344,6 +493,8 @@ export class PhysicsWorld {
344
493
  }
345
494
  clear() {
346
495
  this.bodyCount = 0;
496
+ this.bodyHigh = 0;
497
+ this.bodyFreeCount = 0;
347
498
  this.accumulator = 0;
348
499
  this.awakeCount = 0;
349
500
  this.jointHigh = 0;
@@ -360,7 +511,7 @@ export class PhysicsWorld {
360
511
  let substeps = 0;
361
512
  this.stats.contacts = 0;
362
513
  this.stats.pairs = 0;
363
- this.contact.fill(0, 0, this.bodyCount);
514
+ this.contact.fill(0, 0, this.bodyHigh);
364
515
  while (this.accumulator >= this.fixedDt) {
365
516
  this.substep(this.fixedDt);
366
517
  this.accumulator -= this.fixedDt;
@@ -453,25 +604,59 @@ export class PhysicsWorld {
453
604
  }
454
605
  }
455
606
  buildGrid() {
456
- const n = this.bodyCount;
607
+ const n = this.bodyHigh;
457
608
  const start = this.cellStart;
458
609
  start.fill(0);
459
- // Sleeping bodies never move, so their cell index is cached from addBody / their last awake
460
- // substep — only awake bodies pay the cell recomputation each substep.
461
610
  const awakeCount = this.awakeCount;
462
611
  for (let a = 0; a < awakeCount; a += 1) {
463
612
  const i = this.awakeList[a];
464
613
  this.bodyCell[i] = this.cellOf(i);
465
614
  }
466
- for (let i = 0; i < n; i += 1)
467
- start[this.bodyCell[i] + 1] += 1;
615
+ const nx = this.nx;
616
+ const ny = this.ny;
617
+ for (let i = 0; i < n; i += 1) {
618
+ if ((this.flags[i] & FLAG_DEAD) !== 0)
619
+ continue;
620
+ const x0 = cellCoord(this.posX[i] - this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
621
+ const x1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
622
+ const y0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
623
+ const y1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
624
+ const z0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
625
+ const z1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
626
+ for (let z = z0; z <= z1; z += 1) {
627
+ for (let y = y0; y <= y1; y += 1) {
628
+ const rowBase = (z * ny + y) * nx;
629
+ for (let x = x0; x <= x1; x += 1)
630
+ start[rowBase + x + 1] += 1;
631
+ }
632
+ }
633
+ }
468
634
  for (let c = 0; c < this.numCells; c += 1) {
469
635
  start[c + 1] += start[c];
470
636
  this.cursor[c] = start[c];
471
637
  }
638
+ const total = start[this.numCells];
639
+ if (total > this.sorted.length) {
640
+ this.sorted = new Int32Array(Math.max(total, this.sorted.length * 2));
641
+ }
472
642
  for (let i = 0; i < n; i += 1) {
473
- const c = this.bodyCell[i];
474
- this.sorted[this.cursor[c]++] = i;
643
+ if ((this.flags[i] & FLAG_DEAD) !== 0)
644
+ continue;
645
+ const x0 = cellCoord(this.posX[i] - this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
646
+ const x1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
647
+ const y0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
648
+ const y1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
649
+ const z0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
650
+ const z1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
651
+ for (let z = z0; z <= z1; z += 1) {
652
+ for (let y = y0; y <= y1; y += 1) {
653
+ const rowBase = (z * ny + y) * nx;
654
+ for (let x = x0; x <= x1; x += 1) {
655
+ const c = rowBase + x;
656
+ this.sorted[this.cursor[c]++] = i;
657
+ }
658
+ }
659
+ }
475
660
  }
476
661
  }
477
662
  cellOf(i) {
@@ -483,26 +668,18 @@ export class PhysicsWorld {
483
668
  solve(restitutionPass, countPass) {
484
669
  const nx = this.nx;
485
670
  const ny = this.ny;
486
- const nz = this.nz;
487
- const nxny = nx * ny;
488
- // A contact may wake a sleeping body mid-pass, appending it to the awake list; re-read the
489
- // count each iteration so it is solved this substep too.
490
671
  for (let a = 0; a < this.awakeCount; a += 1) {
491
672
  const i = this.awakeList[a];
492
- const c = this.bodyCell[i];
493
- const cz = (c / nxny) | 0;
494
- const cy = ((c - cz * nxny) / nx) | 0;
495
- const cx = c - cz * nxny - cy * nx;
496
- const z0 = cz > 0 ? cz - 1 : cz;
497
- const z1 = cz < nz - 1 ? cz + 1 : cz;
498
- const y0 = cy > 0 ? cy - 1 : cy;
499
- const y1 = cy < ny - 1 ? cy + 1 : cy;
500
- const x0 = cx > 0 ? cx - 1 : cx;
501
- const x1 = cx < nx - 1 ? cx + 1 : cx;
502
- for (let z = z0; z <= z1; z += 1) {
503
- for (let y = y0; y <= y1; y += 1) {
673
+ const ix0 = cellCoord(this.posX[i] - this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
674
+ const ix1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
675
+ const iy0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
676
+ const iy1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
677
+ const iz0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
678
+ const iz1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
679
+ for (let z = iz0; z <= iz1; z += 1) {
680
+ for (let y = iy0; y <= iy1; y += 1) {
504
681
  const rowBase = (z * ny + y) * nx;
505
- for (let x = x0; x <= x1; x += 1) {
682
+ for (let x = ix0; x <= ix1; x += 1) {
506
683
  const cell = rowBase + x;
507
684
  const end = this.cellStart[cell + 1];
508
685
  for (let s = this.cellStart[cell]; s < end; s += 1) {
@@ -512,6 +689,14 @@ export class PhysicsWorld {
512
689
  const jSleeping = (this.flags[j] & FLAG_SLEEPING) !== 0;
513
690
  if (!jSleeping && j < i)
514
691
  continue;
692
+ const jx0 = cellCoord(this.posX[j] - this.halfX[j], this.bounds.min[0], this.cellSize, this.nx);
693
+ const jy0 = cellCoord(this.posY[j] - this.halfY[j], this.bounds.min[1], this.cellSize, this.ny);
694
+ const jz0 = cellCoord(this.posZ[j] - this.halfZ[j], this.bounds.min[2], this.cellSize, this.nz);
695
+ const mx0 = ix0 > jx0 ? ix0 : jx0;
696
+ const my0 = iy0 > jy0 ? iy0 : jy0;
697
+ const mz0 = iz0 > jz0 ? iz0 : jz0;
698
+ if (x !== mx0 || y !== my0 || z !== mz0)
699
+ continue;
515
700
  this.resolve(i, j, restitutionPass, countPass);
516
701
  }
517
702
  }
@@ -520,39 +705,94 @@ export class PhysicsWorld {
520
705
  }
521
706
  }
522
707
  resolve(i, j, restitutionPass, countPass) {
523
- const dx = this.posX[j] - this.posX[i];
524
- const px = this.halfX[i] + this.halfX[j] - Math.abs(dx);
525
- if (px <= 0)
526
- return;
527
- const dy = this.posY[j] - this.posY[i];
528
- const py = this.halfY[i] + this.halfY[j] - Math.abs(dy);
529
- if (py <= 0)
530
- return;
531
- const dz = this.posZ[j] - this.posZ[i];
532
- const pz = this.halfZ[i] + this.halfZ[j] - Math.abs(dz);
533
- if (pz <= 0)
534
- return;
535
- if (countPass) {
536
- this.stats.pairs += 1;
537
- this.stats.contacts += 1;
538
- this.contact[i] = 1;
539
- this.contact[j] = 1;
540
- }
541
708
  let nX = 0;
542
709
  let nY = 0;
543
710
  let nZ = 0;
544
- let pen;
545
- if (px <= py && px <= pz) {
546
- nX = dx < 0 ? -1 : 1;
547
- pen = px;
711
+ let pen = 0;
712
+ const sphereI = this.shape[i] === SHAPE_SPHERE;
713
+ const sphereJ = this.shape[j] === SHAPE_SPHERE;
714
+ let needsAxisNormal = true;
715
+ if (sphereI && sphereJ) {
716
+ const dx = this.posX[j] - this.posX[i];
717
+ const dy = this.posY[j] - this.posY[i];
718
+ const dz = this.posZ[j] - this.posZ[i];
719
+ const radiusSum = this.halfX[i] + this.halfX[j];
720
+ const dist2 = dx * dx + dy * dy + dz * dz;
721
+ if (dist2 >= radiusSum * radiusSum)
722
+ return;
723
+ const dist = Math.sqrt(dist2);
724
+ if (dist > 1e-9) {
725
+ nX = dx / dist;
726
+ nY = dy / dist;
727
+ nZ = dz / dist;
728
+ pen = radiusSum - dist;
729
+ }
730
+ else {
731
+ nY = 1;
732
+ pen = radiusSum;
733
+ }
734
+ needsAxisNormal = false;
548
735
  }
549
- else if (py <= pz) {
550
- nY = dy < 0 ? -1 : 1;
551
- pen = py;
736
+ else if (sphereI !== sphereJ) {
737
+ const box = sphereI ? j : i;
738
+ const sph = sphereI ? i : j;
739
+ const loX = this.posX[box] - this.halfX[box];
740
+ const hiX = this.posX[box] + this.halfX[box];
741
+ const loY = this.posY[box] - this.halfY[box];
742
+ const hiY = this.posY[box] + this.halfY[box];
743
+ const loZ = this.posZ[box] - this.halfZ[box];
744
+ const hiZ = this.posZ[box] + this.halfZ[box];
745
+ const sx = this.posX[sph];
746
+ const sy = this.posY[sph];
747
+ const sz = this.posZ[sph];
748
+ const dx = sx - (sx < loX ? loX : sx > hiX ? hiX : sx);
749
+ const dy = sy - (sy < loY ? loY : sy > hiY ? hiY : sy);
750
+ const dz = sz - (sz < loZ ? loZ : sz > hiZ ? hiZ : sz);
751
+ const dist2 = dx * dx + dy * dy + dz * dz;
752
+ if (dist2 > 1e-12) {
753
+ const radius = this.halfX[sph];
754
+ if (dist2 >= radius * radius)
755
+ return;
756
+ const dist = Math.sqrt(dist2);
757
+ const towardSphere = sph === j ? 1 / dist : -1 / dist;
758
+ nX = dx * towardSphere;
759
+ nY = dy * towardSphere;
760
+ nZ = dz * towardSphere;
761
+ pen = radius - dist;
762
+ needsAxisNormal = false;
763
+ }
552
764
  }
553
- else {
554
- nZ = dz < 0 ? -1 : 1;
555
- pen = pz;
765
+ if (needsAxisNormal) {
766
+ const dx = this.posX[j] - this.posX[i];
767
+ const px = this.halfX[i] + this.halfX[j] - Math.abs(dx);
768
+ if (px <= 0)
769
+ return;
770
+ const dy = this.posY[j] - this.posY[i];
771
+ const py = this.halfY[i] + this.halfY[j] - Math.abs(dy);
772
+ if (py <= 0)
773
+ return;
774
+ const dz = this.posZ[j] - this.posZ[i];
775
+ const pz = this.halfZ[i] + this.halfZ[j] - Math.abs(dz);
776
+ if (pz <= 0)
777
+ return;
778
+ if (px <= py && px <= pz) {
779
+ nX = dx < 0 ? -1 : 1;
780
+ pen = px;
781
+ }
782
+ else if (py <= pz) {
783
+ nY = dy < 0 ? -1 : 1;
784
+ pen = py;
785
+ }
786
+ else {
787
+ nZ = dz < 0 ? -1 : 1;
788
+ pen = pz;
789
+ }
790
+ }
791
+ if (countPass) {
792
+ this.stats.pairs += 1;
793
+ this.stats.contacts += 1;
794
+ this.contact[i] = 1;
795
+ this.contact[j] = 1;
556
796
  }
557
797
  const rvx = this.velX[j] - this.velX[i];
558
798
  const rvy = this.velY[j] - this.velY[i];
@@ -627,6 +867,7 @@ export class PhysicsWorld {
627
867
  for (let id = 0; id < this.jointHigh; id += 1) {
628
868
  if (this.jActive[id] !== 1)
629
869
  continue;
870
+ const kind = this.jKind[id];
630
871
  const bA = this.jBodyA[id];
631
872
  const bB = this.jBodyB[id];
632
873
  const ax = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
@@ -635,9 +876,91 @@ export class PhysicsWorld {
635
876
  const bx = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
636
877
  const by = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
637
878
  const bz = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
638
- const dx = bx - ax;
639
- const dy = by - ay;
640
- const dz = bz - az;
879
+ let dx = bx - ax;
880
+ let dy = by - ay;
881
+ let dz = bz - az;
882
+ const imA = bA >= 0 ? this.invMass[bA] : 0;
883
+ const imB = bB >= 0 ? this.invMass[bB] : 0;
884
+ const imSum = imA + imB;
885
+ if (imSum === 0)
886
+ continue;
887
+ if (kind === JOINT_HINGE) {
888
+ const ux = this.jAxisX[id];
889
+ const uy = this.jAxisY[id];
890
+ const uz = this.jAxisZ[id];
891
+ const along = dx * ux + dy * uy + dz * uz;
892
+ dx -= along * ux;
893
+ dy -= along * uy;
894
+ dz -= along * uz;
895
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
896
+ const vax = bA >= 0 ? this.velX[bA] : 0;
897
+ const vay = bA >= 0 ? this.velY[bA] : 0;
898
+ const vaz = bA >= 0 ? this.velZ[bA] : 0;
899
+ const vbx = bB >= 0 ? this.velX[bB] : 0;
900
+ const vby = bB >= 0 ? this.velY[bB] : 0;
901
+ const vbz = bB >= 0 ? this.velZ[bB] : 0;
902
+ let rvx = vbx - vax;
903
+ let rvy = vby - vay;
904
+ let rvz = vbz - vaz;
905
+ const rvAlong = rvx * ux + rvy * uy + rvz * uz;
906
+ rvx -= rvAlong * ux;
907
+ rvy -= rvAlong * uy;
908
+ rvz -= rvAlong * uz;
909
+ if (dist > 1e-9) {
910
+ const nX = dx / dist;
911
+ const nY = dy / dist;
912
+ const nZ = dz / dist;
913
+ if (dist > slop) {
914
+ if (bA >= 0)
915
+ this.wake(bA);
916
+ if (bB >= 0)
917
+ this.wake(bB);
918
+ }
919
+ const rvn = rvx * nX + rvy * nY + rvz * nZ;
920
+ const jn = -rvn / imSum;
921
+ if (bA >= 0) {
922
+ this.velX[bA] -= jn * imA * nX;
923
+ this.velY[bA] -= jn * imA * nY;
924
+ this.velZ[bA] -= jn * imA * nZ;
925
+ }
926
+ if (bB >= 0) {
927
+ this.velX[bB] += jn * imB * nX;
928
+ this.velY[bB] += jn * imB * nY;
929
+ this.velZ[bB] += jn * imB * nZ;
930
+ }
931
+ const corr = (dist * beta) / imSum;
932
+ if (bA >= 0) {
933
+ this.posX[bA] += corr * imA * nX;
934
+ this.posY[bA] += corr * imA * nY;
935
+ this.posZ[bA] += corr * imA * nZ;
936
+ }
937
+ if (bB >= 0) {
938
+ this.posX[bB] -= corr * imB * nX;
939
+ this.posY[bB] -= corr * imB * nY;
940
+ this.posZ[bB] -= corr * imB * nZ;
941
+ }
942
+ }
943
+ else {
944
+ const rvl = Math.sqrt(rvx * rvx + rvy * rvy + rvz * rvz);
945
+ if (rvl > 1e-9) {
946
+ const nX = rvx / rvl;
947
+ const nY = rvy / rvl;
948
+ const nZ = rvz / rvl;
949
+ const jn = -rvl / imSum;
950
+ if (bA >= 0) {
951
+ this.velX[bA] -= jn * imA * nX;
952
+ this.velY[bA] -= jn * imA * nY;
953
+ this.velZ[bA] -= jn * imA * nZ;
954
+ }
955
+ if (bB >= 0) {
956
+ this.velX[bB] += jn * imB * nX;
957
+ this.velY[bB] += jn * imB * nY;
958
+ this.velZ[bB] += jn * imB * nZ;
959
+ }
960
+ }
961
+ }
962
+ continue;
963
+ }
641
964
  const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
642
965
  if (dist < 1e-9)
643
966
  continue;
@@ -645,11 +968,6 @@ export class PhysicsWorld {
645
968
  const nY = dy / dist;
646
969
  const nZ = dz / dist;
647
970
  const c = dist - this.jRest[id];
648
- const imA = bA >= 0 ? this.invMass[bA] : 0;
649
- const imB = bB >= 0 ? this.invMass[bB] : 0;
650
- const imSum = imA + imB;
651
- if (imSum === 0)
652
- continue;
653
971
  const vax = bA >= 0 ? this.velX[bA] : 0;
654
972
  const vay = bA >= 0 ? this.velY[bA] : 0;
655
973
  const vaz = bA >= 0 ? this.velZ[bA] : 0;
@@ -657,7 +975,7 @@ export class PhysicsWorld {
657
975
  const vby = bB >= 0 ? this.velY[bB] : 0;
658
976
  const vbz = bB >= 0 ? this.velZ[bB] : 0;
659
977
  const rvn = (vbx - vax) * nX + (vby - vay) * nY + (vbz - vaz) * nZ;
660
- if (this.jSpring[id] === 1) {
978
+ if (kind === JOINT_SPRING) {
661
979
  if (Math.abs(c) > slop) {
662
980
  if (bA >= 0)
663
981
  this.wake(bA);
@@ -682,6 +1000,9 @@ export class PhysicsWorld {
682
1000
  }
683
1001
  continue;
684
1002
  }
1003
+ if (kind !== JOINT_FIXED && kind !== JOINT_DISTANCE) {
1004
+ throw new Error(`PhysicsWorld: joint kind ${kind} is not implemented`);
1005
+ }
685
1006
  if (Math.abs(c) > slop) {
686
1007
  if (bA >= 0)
687
1008
  this.wake(bA);