@jgengine/core 0.5.0 → 0.7.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 (244) hide show
  1. package/CHANGELOG.md +191 -2
  2. package/dist/ai/crowd.d.ts +36 -0
  3. package/dist/ai/crowd.js +213 -0
  4. package/dist/ai/jobBoard.d.ts +49 -0
  5. package/dist/ai/jobBoard.js +155 -0
  6. package/dist/ai/spawnDirector.d.ts +56 -0
  7. package/dist/ai/spawnDirector.js +150 -0
  8. package/dist/ai/threat.d.ts +25 -0
  9. package/dist/ai/threat.js +67 -0
  10. package/dist/audio/audioFalloff.d.ts +38 -0
  11. package/dist/audio/audioFalloff.js +32 -0
  12. package/dist/board/laneBoard.d.ts +63 -0
  13. package/dist/board/laneBoard.js +114 -0
  14. package/dist/board/timelineBoard.d.ts +36 -0
  15. package/dist/board/timelineBoard.js +70 -0
  16. package/dist/cards/cardPile.d.ts +63 -0
  17. package/dist/cards/cardPile.js +166 -0
  18. package/dist/cards/modifierPipeline.d.ts +33 -0
  19. package/dist/cards/modifierPipeline.js +48 -0
  20. package/dist/combat/abilityKit.d.ts +42 -0
  21. package/dist/combat/abilityKit.js +129 -0
  22. package/dist/combat/animationState.d.ts +47 -0
  23. package/dist/combat/animationState.js +101 -0
  24. package/dist/combat/attackTags.d.ts +14 -0
  25. package/dist/combat/attackTags.js +26 -0
  26. package/dist/combat/breakMeters.d.ts +32 -0
  27. package/dist/combat/breakMeters.js +51 -0
  28. package/dist/combat/comboString.d.ts +35 -0
  29. package/dist/combat/comboString.js +59 -0
  30. package/dist/combat/death.d.ts +8 -0
  31. package/dist/combat/death.js +5 -2
  32. package/dist/combat/defensiveWindow.d.ts +34 -0
  33. package/dist/combat/defensiveWindow.js +66 -0
  34. package/dist/combat/downed.d.ts +35 -0
  35. package/dist/combat/downed.js +104 -0
  36. package/dist/combat/effects.d.ts +11 -0
  37. package/dist/combat/effects.js +22 -13
  38. package/dist/combat/hitReaction.d.ts +23 -0
  39. package/dist/combat/hitReaction.js +20 -0
  40. package/dist/combat/resistance.d.ts +13 -0
  41. package/dist/combat/resistance.js +46 -0
  42. package/dist/combat/telegraph.d.ts +27 -0
  43. package/dist/combat/telegraph.js +37 -0
  44. package/dist/crafting/crop.d.ts +59 -0
  45. package/dist/crafting/crop.js +181 -0
  46. package/dist/crafting/production.d.ts +64 -0
  47. package/dist/crafting/production.js +130 -0
  48. package/dist/crafting/recipe.d.ts +62 -0
  49. package/dist/crafting/recipe.js +101 -0
  50. package/dist/economy/sharedWallet.d.ts +28 -0
  51. package/dist/economy/sharedWallet.js +62 -0
  52. package/dist/economy/techTree.d.ts +40 -0
  53. package/dist/economy/techTree.js +66 -0
  54. package/dist/game/cosmetics.d.ts +25 -0
  55. package/dist/game/cosmetics.js +51 -0
  56. package/dist/game/defineGame.d.ts +5 -0
  57. package/dist/game/events.d.ts +62 -0
  58. package/dist/game/lootFilter.d.ts +33 -0
  59. package/dist/game/lootFilter.js +48 -0
  60. package/dist/game/ping.d.ts +67 -0
  61. package/dist/game/ping.js +86 -0
  62. package/dist/game/playableGame.d.ts +291 -5
  63. package/dist/game/race.d.ts +95 -0
  64. package/dist/game/race.js +216 -0
  65. package/dist/game/runDraft.d.ts +34 -0
  66. package/dist/game/runDraft.js +97 -0
  67. package/dist/game/social.d.ts +27 -0
  68. package/dist/game/social.js +16 -0
  69. package/dist/game/worldItem.d.ts +92 -0
  70. package/dist/game/worldItem.js +132 -0
  71. package/dist/input/axisInput.d.ts +43 -0
  72. package/dist/input/axisInput.js +70 -0
  73. package/dist/input/pointer.d.ts +24 -0
  74. package/dist/input/pointer.js +17 -0
  75. package/dist/interaction/contextMenu.d.ts +30 -0
  76. package/dist/interaction/contextMenu.js +22 -0
  77. package/dist/interaction/qte.d.ts +20 -0
  78. package/dist/interaction/qte.js +24 -0
  79. package/dist/interaction/skillCheck.d.ts +20 -0
  80. package/dist/interaction/skillCheck.js +26 -0
  81. package/dist/inventory/shapedGrid.d.ts +56 -0
  82. package/dist/inventory/shapedGrid.js +176 -0
  83. package/dist/inventory/storageTier.d.ts +52 -0
  84. package/dist/inventory/storageTier.js +77 -0
  85. package/dist/item/affix.d.ts +54 -0
  86. package/dist/item/affix.js +133 -0
  87. package/dist/item/durability.d.ts +51 -0
  88. package/dist/item/durability.js +78 -0
  89. package/dist/item/modularItem.d.ts +46 -0
  90. package/dist/item/modularItem.js +75 -0
  91. package/dist/meta/changelog.d.ts +1 -1
  92. package/dist/meta/changelog.js +53 -1
  93. package/dist/movement/dash.d.ts +37 -0
  94. package/dist/movement/dash.js +65 -0
  95. package/dist/multiplayer/combatSnapshot.d.ts +45 -0
  96. package/dist/multiplayer/combatSnapshot.js +89 -0
  97. package/dist/multiplayer/lagCompensation.d.ts +43 -0
  98. package/dist/multiplayer/lagCompensation.js +154 -0
  99. package/dist/multiplayer/matchmaking.d.ts +33 -0
  100. package/dist/multiplayer/matchmaking.js +60 -0
  101. package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
  102. package/dist/multiplayer/simultaneousCommit.js +53 -0
  103. package/dist/nav/navGrid.d.ts +46 -0
  104. package/dist/nav/navGrid.js +248 -0
  105. package/dist/nav/pathFollow.d.ts +29 -0
  106. package/dist/nav/pathFollow.js +67 -0
  107. package/dist/physics/buoyancy.d.ts +49 -0
  108. package/dist/physics/buoyancy.js +112 -0
  109. package/dist/physics/carryable.d.ts +46 -0
  110. package/dist/physics/carryable.js +110 -0
  111. package/dist/physics/damageZones.d.ts +56 -0
  112. package/dist/physics/damageZones.js +107 -0
  113. package/dist/physics/forceVolume.d.ts +48 -0
  114. package/dist/physics/forceVolume.js +119 -0
  115. package/dist/physics/physicsWorld.d.ts +219 -0
  116. package/dist/physics/physicsWorld.js +749 -0
  117. package/dist/physics/ragdoll.d.ts +49 -0
  118. package/dist/physics/ragdoll.js +103 -0
  119. package/dist/physics/spatialGrid.d.ts +41 -0
  120. package/dist/physics/spatialGrid.js +138 -0
  121. package/dist/physics/structure.d.ts +83 -0
  122. package/dist/physics/structure.js +0 -0
  123. package/dist/physics/traversal.d.ts +75 -0
  124. package/dist/physics/traversal.js +138 -0
  125. package/dist/physics/vehicleBody.d.ts +93 -0
  126. package/dist/physics/vehicleBody.js +234 -0
  127. package/dist/runtime/gameContext.d.ts +66 -2
  128. package/dist/runtime/gameContext.js +157 -4
  129. package/dist/runtime/hostPersistence.d.ts +18 -0
  130. package/dist/runtime/hostPersistence.js +4 -0
  131. package/dist/runtime/persistenceScope.d.ts +32 -0
  132. package/dist/runtime/persistenceScope.js +64 -0
  133. package/dist/scene/assetCatalog.d.ts +13 -0
  134. package/dist/scene/autoTarget.d.ts +15 -0
  135. package/dist/scene/autoTarget.js +54 -0
  136. package/dist/scene/behaviors.d.ts +13 -1
  137. package/dist/scene/behaviors.js +3 -0
  138. package/dist/scene/captureCheck.d.ts +7 -0
  139. package/dist/scene/captureCheck.js +11 -0
  140. package/dist/scene/entityStore.d.ts +7 -1
  141. package/dist/scene/entityStore.js +15 -1
  142. package/dist/scene/form.d.ts +40 -0
  143. package/dist/scene/form.js +56 -0
  144. package/dist/scene/mount.d.ts +62 -0
  145. package/dist/scene/mount.js +107 -0
  146. package/dist/scene/possession.d.ts +30 -0
  147. package/dist/scene/possession.js +51 -0
  148. package/dist/scene/roster.d.ts +25 -0
  149. package/dist/scene/roster.js +57 -0
  150. package/dist/scene/selection.d.ts +30 -0
  151. package/dist/scene/selection.js +38 -0
  152. package/dist/scene/stationClaim.d.ts +43 -0
  153. package/dist/scene/stationClaim.js +109 -0
  154. package/dist/sensor/frustumSensor.d.ts +60 -0
  155. package/dist/sensor/frustumSensor.js +104 -0
  156. package/dist/sensor/hiddenStateProbe.d.ts +31 -0
  157. package/dist/sensor/hiddenStateProbe.js +40 -0
  158. package/dist/sensor/recordingBuffer.d.ts +29 -0
  159. package/dist/sensor/recordingBuffer.js +61 -0
  160. package/dist/sensor/revealQuery.d.ts +22 -0
  161. package/dist/sensor/revealQuery.js +36 -0
  162. package/dist/session/contestedChannel.d.ts +36 -0
  163. package/dist/session/contestedChannel.js +100 -0
  164. package/dist/session/extraction.d.ts +54 -0
  165. package/dist/session/extraction.js +108 -0
  166. package/dist/session/ring.d.ts +36 -0
  167. package/dist/session/ring.js +88 -0
  168. package/dist/session/roundState.d.ts +55 -0
  169. package/dist/session/roundState.js +129 -0
  170. package/dist/stats/accumulatorMeter.d.ts +31 -0
  171. package/dist/stats/accumulatorMeter.js +81 -0
  172. package/dist/stats/eventMeter.d.ts +34 -0
  173. package/dist/stats/eventMeter.js +76 -0
  174. package/dist/stats/rollCheck.d.ts +15 -0
  175. package/dist/stats/rollCheck.js +15 -0
  176. package/dist/survival/decayMeter.d.ts +59 -0
  177. package/dist/survival/decayMeter.js +95 -0
  178. package/dist/survival/moodle.d.ts +48 -0
  179. package/dist/survival/moodle.js +91 -0
  180. package/dist/survival/regionHealth.d.ts +72 -0
  181. package/dist/survival/regionHealth.js +129 -0
  182. package/dist/tactics/predictiveQuery.d.ts +34 -0
  183. package/dist/tactics/predictiveQuery.js +44 -0
  184. package/dist/tactics/snapshot.d.ts +18 -0
  185. package/dist/tactics/snapshot.js +65 -0
  186. package/dist/tactics/surface.d.ts +52 -0
  187. package/dist/tactics/surface.js +135 -0
  188. package/dist/tactics/tacticalGrid.d.ts +57 -0
  189. package/dist/tactics/tacticalGrid.js +244 -0
  190. package/dist/time/beatClock.d.ts +40 -0
  191. package/dist/time/beatClock.js +73 -0
  192. package/dist/time/simClock.d.ts +62 -0
  193. package/dist/time/simClock.js +132 -0
  194. package/dist/turn/commit.d.ts +27 -0
  195. package/dist/turn/commit.js +72 -0
  196. package/dist/turn/turnLoop.d.ts +58 -0
  197. package/dist/turn/turnLoop.js +216 -0
  198. package/dist/world/buildPermissions.d.ts +47 -0
  199. package/dist/world/buildPermissions.js +134 -0
  200. package/dist/world/buildingIndex.d.ts +16 -0
  201. package/dist/world/buildingIndex.js +63 -0
  202. package/dist/world/buildings.d.ts +98 -0
  203. package/dist/world/buildings.js +320 -0
  204. package/dist/world/carve.d.ts +105 -0
  205. package/dist/world/carve.js +175 -0
  206. package/dist/world/connectors.d.ts +40 -0
  207. package/dist/world/connectors.js +67 -0
  208. package/dist/world/envField.d.ts +71 -0
  209. package/dist/world/envField.js +96 -0
  210. package/dist/world/features.d.ts +105 -1
  211. package/dist/world/features.js +95 -0
  212. package/dist/world/fog.d.ts +40 -0
  213. package/dist/world/fog.js +137 -0
  214. package/dist/world/markers.d.ts +52 -0
  215. package/dist/world/markers.js +96 -0
  216. package/dist/world/minimap.d.ts +42 -0
  217. package/dist/world/minimap.js +72 -0
  218. package/dist/world/placedStructureStore.d.ts +39 -0
  219. package/dist/world/placedStructureStore.js +95 -0
  220. package/dist/world/placementController.d.ts +48 -0
  221. package/dist/world/placementController.js +97 -0
  222. package/dist/world/realm.d.ts +61 -0
  223. package/dist/world/realm.js +66 -0
  224. package/dist/world/regions.d.ts +66 -0
  225. package/dist/world/regions.js +158 -0
  226. package/dist/world/scatter.d.ts +24 -0
  227. package/dist/world/scatter.js +126 -0
  228. package/dist/world/scatterItems.d.ts +38 -0
  229. package/dist/world/scatterItems.js +85 -0
  230. package/dist/world/support.d.ts +27 -0
  231. package/dist/world/support.js +75 -0
  232. package/dist/world/terraform.d.ts +54 -0
  233. package/dist/world/terraform.js +186 -0
  234. package/dist/world/terrain.d.ts +54 -0
  235. package/dist/world/terrain.js +181 -0
  236. package/dist/world/walls.d.ts +61 -0
  237. package/dist/world/walls.js +221 -0
  238. package/dist/world/water.d.ts +27 -0
  239. package/dist/world/water.js +125 -0
  240. package/dist/world/weather.d.ts +92 -0
  241. package/dist/world/weather.js +169 -0
  242. package/dist/world/wind.d.ts +17 -0
  243. package/dist/world/wind.js +40 -0
  244. package/package.json +1 -1
@@ -0,0 +1,749 @@
1
+ const FLAG_SLEEPING = 1;
2
+ const FLAG_STATIC = 2;
3
+ /** Grid cell containing a point, clamped into the grid. Pure — exported for tests. */
4
+ export function cellCoord(value, min, cellSize, cells) {
5
+ const raw = Math.floor((value - min) / cellSize);
6
+ return raw < 0 ? 0 : raw >= cells ? cells - 1 : raw;
7
+ }
8
+ /** Linear cell index from clamped 3D coords. Pure — exported for tests. */
9
+ export function cellIndex(cx, cy, cz, nx, ny) {
10
+ return (cz * ny + cy) * nx + cx;
11
+ }
12
+ export class PhysicsWorld {
13
+ capacity;
14
+ bounds;
15
+ gravity;
16
+ cellSize;
17
+ fixedDt;
18
+ maxSubsteps;
19
+ restitution;
20
+ restitutionThreshold;
21
+ friction;
22
+ linearDamping;
23
+ solverIterations;
24
+ correctionFactor;
25
+ slop;
26
+ sleepMove2;
27
+ sleepSteps;
28
+ wakeThreshold;
29
+ jointCapacity;
30
+ jointCorrection;
31
+ posX;
32
+ posY;
33
+ posZ;
34
+ velX;
35
+ velY;
36
+ velZ;
37
+ halfX;
38
+ halfY;
39
+ halfZ;
40
+ invMass;
41
+ flags;
42
+ /** 1 for a body that took part in a contact this frame; reset each `step()`. Render tint reads this. */
43
+ contact;
44
+ sleepTimer;
45
+ prevX;
46
+ prevY;
47
+ prevZ;
48
+ nx;
49
+ ny;
50
+ nz;
51
+ numCells;
52
+ cellStart;
53
+ cursor;
54
+ sorted;
55
+ bodyCell;
56
+ // Awake-body index: the hot loops (integrate, solver outer, bounds, sleep) walk only these,
57
+ // so per-substep work outside the grid rebuild scales with the awake count, not the total.
58
+ awakeList;
59
+ awakeSlot;
60
+ awakeCount = 0;
61
+ jSpring;
62
+ jActive;
63
+ jBodyA;
64
+ jBodyB;
65
+ jAnchorAX;
66
+ jAnchorAY;
67
+ jAnchorAZ;
68
+ jAnchorBX;
69
+ jAnchorBY;
70
+ jAnchorBZ;
71
+ jRest;
72
+ jStiffness;
73
+ jDamping;
74
+ jMaxImpulse;
75
+ jFree;
76
+ jFreeCount = 0;
77
+ jointHigh = 0;
78
+ jointActiveCount = 0;
79
+ collisionListener = null;
80
+ collisionMinSpeed = 0;
81
+ collisionEvent = {
82
+ a: 0,
83
+ b: 0,
84
+ nx: 0,
85
+ ny: 0,
86
+ nz: 0,
87
+ approachSpeed: 0,
88
+ impulse: 0,
89
+ };
90
+ bodyCount = 0;
91
+ accumulator = 0;
92
+ stats = {
93
+ count: 0,
94
+ awake: 0,
95
+ sleeping: 0,
96
+ contacts: 0,
97
+ pairs: 0,
98
+ substeps: 0,
99
+ stepMs: 0,
100
+ };
101
+ constructor(config) {
102
+ this.capacity = config.capacity;
103
+ this.bounds = { min: config.bounds.min, max: config.bounds.max };
104
+ this.gravity = config.gravity ?? -20;
105
+ this.cellSize = config.cellSize ?? 1;
106
+ this.fixedDt = config.fixedDt ?? 1 / 60;
107
+ this.maxSubsteps = config.maxSubsteps ?? 4;
108
+ this.restitution = config.restitution ?? 0.2;
109
+ this.restitutionThreshold = config.restitutionThreshold ?? 1;
110
+ this.friction = config.friction ?? 0.4;
111
+ this.linearDamping = config.linearDamping ?? 0.2;
112
+ this.solverIterations = Math.max(1, config.solverIterations ?? 4);
113
+ this.correctionFactor = config.correctionFactor ?? 0.2;
114
+ this.slop = config.slop ?? 0.005;
115
+ const sleepV = config.sleepLinearVelocity ?? 0.08;
116
+ const sleepMoveEps = sleepV * this.fixedDt;
117
+ this.sleepMove2 = sleepMoveEps * sleepMoveEps;
118
+ this.sleepSteps = config.sleepThresholdSteps ?? 30;
119
+ this.wakeThreshold = config.wakeThreshold ?? 0.5;
120
+ this.jointCapacity = Math.max(0, config.jointCapacity ?? 256);
121
+ this.jointCorrection = config.jointCorrection ?? 0.5;
122
+ const cap = config.capacity;
123
+ this.posX = new Float32Array(cap);
124
+ this.posY = new Float32Array(cap);
125
+ this.posZ = new Float32Array(cap);
126
+ this.velX = new Float32Array(cap);
127
+ this.velY = new Float32Array(cap);
128
+ this.velZ = new Float32Array(cap);
129
+ this.halfX = new Float32Array(cap);
130
+ this.halfY = new Float32Array(cap);
131
+ this.halfZ = new Float32Array(cap);
132
+ this.invMass = new Float32Array(cap);
133
+ this.flags = new Uint8Array(cap);
134
+ this.contact = new Uint8Array(cap);
135
+ this.sleepTimer = new Uint16Array(cap);
136
+ this.prevX = new Float32Array(cap);
137
+ this.prevY = new Float32Array(cap);
138
+ this.prevZ = new Float32Array(cap);
139
+ const span = (i) => Math.max(config.bounds.max[i] - config.bounds.min[i], this.cellSize);
140
+ this.nx = Math.max(1, Math.ceil(span(0) / this.cellSize));
141
+ this.ny = Math.max(1, Math.ceil(span(1) / this.cellSize));
142
+ this.nz = Math.max(1, Math.ceil(span(2) / this.cellSize));
143
+ this.numCells = this.nx * this.ny * this.nz;
144
+ this.cellStart = new Int32Array(this.numCells + 1);
145
+ this.cursor = new Int32Array(this.numCells);
146
+ this.sorted = new Int32Array(cap);
147
+ this.bodyCell = new Int32Array(cap);
148
+ this.awakeList = new Int32Array(cap);
149
+ this.awakeSlot = new Int32Array(cap).fill(-1);
150
+ const jc = this.jointCapacity;
151
+ this.jSpring = new Uint8Array(jc);
152
+ this.jActive = new Uint8Array(jc);
153
+ this.jBodyA = new Int32Array(jc);
154
+ this.jBodyB = new Int32Array(jc);
155
+ this.jAnchorAX = new Float32Array(jc);
156
+ this.jAnchorAY = new Float32Array(jc);
157
+ this.jAnchorAZ = new Float32Array(jc);
158
+ this.jAnchorBX = new Float32Array(jc);
159
+ this.jAnchorBY = new Float32Array(jc);
160
+ this.jAnchorBZ = new Float32Array(jc);
161
+ this.jRest = new Float32Array(jc);
162
+ this.jStiffness = new Float32Array(jc);
163
+ this.jDamping = new Float32Array(jc);
164
+ this.jMaxImpulse = new Float32Array(jc);
165
+ this.jFree = new Int32Array(jc);
166
+ }
167
+ addAwake(i) {
168
+ if (this.awakeSlot[i] >= 0)
169
+ return;
170
+ this.awakeSlot[i] = this.awakeCount;
171
+ this.awakeList[this.awakeCount] = i;
172
+ this.awakeCount += 1;
173
+ }
174
+ removeAwake(i) {
175
+ const slot = this.awakeSlot[i];
176
+ if (slot < 0)
177
+ return;
178
+ this.awakeCount -= 1;
179
+ const last = this.awakeList[this.awakeCount];
180
+ this.awakeList[slot] = last;
181
+ this.awakeSlot[last] = slot;
182
+ this.awakeSlot[i] = -1;
183
+ }
184
+ get count() {
185
+ return this.bodyCount;
186
+ }
187
+ get cells() {
188
+ return { nx: this.nx, ny: this.ny, nz: this.nz, total: this.numCells };
189
+ }
190
+ get jointCount() {
191
+ return this.jointActiveCount;
192
+ }
193
+ /**
194
+ * Deliver every impacting contact (approach speed ≥ `minApproachSpeed`) to `listener` during
195
+ * `step`. The event object is reused — read/copy it, never retain. Pass `null` to detach. This is
196
+ * the seam crash-damage and destruction read; contacts otherwise stay inside the sim.
197
+ */
198
+ onCollision(listener, minApproachSpeed = 0) {
199
+ this.collisionListener = listener;
200
+ this.collisionMinSpeed = minApproachSpeed;
201
+ }
202
+ hingeJoint(options) {
203
+ return this.addJoint(options, false, 0);
204
+ }
205
+ fixedJoint(options) {
206
+ return this.addJoint(options, false, 0);
207
+ }
208
+ distanceJoint(options) {
209
+ return this.addJoint(options, false, null);
210
+ }
211
+ springJoint(options) {
212
+ return this.addJoint(options, true, null);
213
+ }
214
+ addJoint(options, spring, restOverride) {
215
+ let id;
216
+ if (this.jFreeCount > 0) {
217
+ id = this.jFree[--this.jFreeCount];
218
+ }
219
+ else {
220
+ if (this.jointHigh >= this.jointCapacity)
221
+ throw new Error("PhysicsWorld: joint capacity exceeded");
222
+ id = this.jointHigh++;
223
+ }
224
+ const bodyB = options.bodyB ?? -1;
225
+ const aA = options.anchorA;
226
+ const aB = options.anchorB;
227
+ this.jBodyA[id] = options.bodyA;
228
+ this.jBodyB[id] = bodyB;
229
+ this.jAnchorAX[id] = aA?.[0] ?? 0;
230
+ this.jAnchorAY[id] = aA?.[1] ?? 0;
231
+ this.jAnchorAZ[id] = aA?.[2] ?? 0;
232
+ this.jAnchorBX[id] = aB?.[0] ?? 0;
233
+ this.jAnchorBY[id] = aB?.[1] ?? 0;
234
+ this.jAnchorBZ[id] = aB?.[2] ?? 0;
235
+ this.jSpring[id] = spring ? 1 : 0;
236
+ this.jStiffness[id] = options.stiffness ?? 40;
237
+ this.jDamping[id] = options.damping ?? 6;
238
+ this.jMaxImpulse[id] = options.maxImpulse ?? Number.POSITIVE_INFINITY;
239
+ this.jRest[id] = restOverride ?? options.restLength ?? this.jointDistance(id);
240
+ this.jActive[id] = 1;
241
+ this.jointActiveCount += 1;
242
+ return id;
243
+ }
244
+ removeJoint(id) {
245
+ if (this.jActive[id] !== 1)
246
+ return;
247
+ this.jActive[id] = 0;
248
+ this.jFree[this.jFreeCount++] = id;
249
+ this.jointActiveCount -= 1;
250
+ }
251
+ /** Move a joint's world anchor point (only meaningful when `bodyB` is a world anchor). */
252
+ setJointAnchor(id, x, y, z) {
253
+ this.jAnchorBX[id] = x;
254
+ this.jAnchorBY[id] = y;
255
+ this.jAnchorBZ[id] = z;
256
+ }
257
+ setJointRest(id, restLength) {
258
+ this.jRest[id] = restLength;
259
+ }
260
+ jointDistance(id) {
261
+ const bA = this.jBodyA[id];
262
+ const bB = this.jBodyB[id];
263
+ const ax = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
264
+ const ay = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
265
+ const az = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
266
+ const bx = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
267
+ const by = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
268
+ const bz = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
269
+ const dx = bx - ax;
270
+ const dy = by - ay;
271
+ const dz = bz - az;
272
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
273
+ }
274
+ /** Write [ax,ay,az,bx,by,bz] world endpoints per active joint into `out`; returns the joint count. */
275
+ readJointSegments(out) {
276
+ let n = 0;
277
+ for (let id = 0; id < this.jointHigh; id += 1) {
278
+ if (this.jActive[id] !== 1)
279
+ continue;
280
+ const bA = this.jBodyA[id];
281
+ const bB = this.jBodyB[id];
282
+ const o = n * 6;
283
+ out[o] = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
284
+ out[o + 1] = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
285
+ out[o + 2] = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
286
+ out[o + 3] = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
287
+ out[o + 4] = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
288
+ out[o + 5] = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
289
+ n += 1;
290
+ }
291
+ return n;
292
+ }
293
+ addBody(options) {
294
+ if (this.bodyCount >= this.capacity)
295
+ throw new Error("PhysicsWorld: capacity exceeded");
296
+ const i = this.bodyCount++;
297
+ this.posX[i] = options.position[0];
298
+ this.posY[i] = options.position[1];
299
+ this.posZ[i] = options.position[2];
300
+ const v = options.velocity;
301
+ this.velX[i] = v?.[0] ?? 0;
302
+ this.velY[i] = v?.[1] ?? 0;
303
+ 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];
307
+ const isStatic = options.static === true;
308
+ const mass = options.mass ?? 1;
309
+ this.invMass[i] = isStatic || mass <= 0 ? 0 : 1 / mass;
310
+ let f = 0;
311
+ if (isStatic)
312
+ f |= FLAG_STATIC | FLAG_SLEEPING;
313
+ else if (options.asleep === true)
314
+ f |= FLAG_SLEEPING;
315
+ this.flags[i] = f;
316
+ this.sleepTimer[i] = 0;
317
+ this.awakeSlot[i] = -1;
318
+ this.bodyCell[i] = this.cellOf(i);
319
+ if ((f & FLAG_SLEEPING) === 0)
320
+ this.addAwake(i);
321
+ return i;
322
+ }
323
+ isSleeping(i) {
324
+ return (this.flags[i] & FLAG_SLEEPING) !== 0;
325
+ }
326
+ wake(i) {
327
+ if ((this.flags[i] & FLAG_STATIC) !== 0)
328
+ return;
329
+ this.flags[i] &= ~FLAG_SLEEPING;
330
+ this.sleepTimer[i] = 0;
331
+ this.prevX[i] = this.posX[i];
332
+ this.prevY[i] = this.posY[i];
333
+ this.prevZ[i] = this.posZ[i];
334
+ this.addAwake(i);
335
+ }
336
+ wakeAll() {
337
+ for (let i = 0; i < this.bodyCount; i += 1) {
338
+ if ((this.flags[i] & FLAG_STATIC) === 0) {
339
+ this.flags[i] &= ~FLAG_SLEEPING;
340
+ this.sleepTimer[i] = 0;
341
+ this.addAwake(i);
342
+ }
343
+ }
344
+ }
345
+ clear() {
346
+ this.bodyCount = 0;
347
+ this.accumulator = 0;
348
+ this.awakeCount = 0;
349
+ this.jointHigh = 0;
350
+ this.jFreeCount = 0;
351
+ this.jointActiveCount = 0;
352
+ }
353
+ /** Advance by a real frame delta; runs whole fixed substeps and carries the remainder. */
354
+ step(frameDt) {
355
+ const start = performanceNow();
356
+ this.accumulator += frameDt;
357
+ const cap = this.maxSubsteps * this.fixedDt;
358
+ if (this.accumulator > cap)
359
+ this.accumulator = cap;
360
+ let substeps = 0;
361
+ this.stats.contacts = 0;
362
+ this.stats.pairs = 0;
363
+ this.contact.fill(0, 0, this.bodyCount);
364
+ while (this.accumulator >= this.fixedDt) {
365
+ this.substep(this.fixedDt);
366
+ this.accumulator -= this.fixedDt;
367
+ substeps += 1;
368
+ }
369
+ this.stats.count = this.bodyCount;
370
+ this.stats.awake = this.awakeCount;
371
+ this.stats.sleeping = this.bodyCount - this.awakeCount;
372
+ this.stats.substeps = substeps;
373
+ this.stats.stepMs = performanceNow() - start;
374
+ return this.stats;
375
+ }
376
+ getStats() {
377
+ return this.stats;
378
+ }
379
+ substep(dt) {
380
+ const gdt = this.gravity * dt;
381
+ const damp = this.linearDamping > 0 ? Math.max(0, 1 - this.linearDamping * dt) : 1;
382
+ const awakeBefore = this.awakeCount;
383
+ for (let a = 0; a < awakeBefore; a += 1) {
384
+ const i = this.awakeList[a];
385
+ this.prevX[i] = this.posX[i];
386
+ this.prevY[i] = this.posY[i];
387
+ this.prevZ[i] = this.posZ[i];
388
+ this.velY[i] += gdt;
389
+ if (damp !== 1) {
390
+ this.velX[i] *= damp;
391
+ this.velY[i] *= damp;
392
+ this.velZ[i] *= damp;
393
+ }
394
+ this.posX[i] += this.velX[i] * dt;
395
+ this.posY[i] += this.velY[i] * dt;
396
+ this.posZ[i] += this.velZ[i] * dt;
397
+ this.constrainBounds(i);
398
+ }
399
+ this.buildGrid();
400
+ const iterations = this.solverIterations;
401
+ for (let it = 0; it < iterations; it += 1) {
402
+ this.solve(it === 0, it === iterations - 1);
403
+ if (this.jointActiveCount > 0)
404
+ this.solveJoints(dt);
405
+ }
406
+ for (let a = 0; a < this.awakeCount; a += 1)
407
+ this.constrainBounds(this.awakeList[a]);
408
+ this.updateSleep();
409
+ }
410
+ constrainBounds(i) {
411
+ const min = this.bounds.min;
412
+ const max = this.bounds.max;
413
+ const e = this.restitution;
414
+ const rt = this.restitutionThreshold;
415
+ const loX = min[0] + this.halfX[i];
416
+ const hiX = max[0] - this.halfX[i];
417
+ const loY = min[1] + this.halfY[i];
418
+ const hiY = max[1] - this.halfY[i];
419
+ const loZ = min[2] + this.halfZ[i];
420
+ const hiZ = max[2] - this.halfZ[i];
421
+ if (this.posX[i] < loX) {
422
+ this.posX[i] = loX;
423
+ if (this.velX[i] < 0)
424
+ this.velX[i] = -this.velX[i] < rt ? 0 : this.velX[i] * -e;
425
+ }
426
+ else if (this.posX[i] > hiX) {
427
+ this.posX[i] = hiX;
428
+ if (this.velX[i] > 0)
429
+ this.velX[i] = this.velX[i] < rt ? 0 : -this.velX[i] * e;
430
+ }
431
+ if (this.posZ[i] < loZ) {
432
+ this.posZ[i] = loZ;
433
+ if (this.velZ[i] < 0)
434
+ this.velZ[i] = -this.velZ[i] < rt ? 0 : this.velZ[i] * -e;
435
+ }
436
+ else if (this.posZ[i] > hiZ) {
437
+ this.posZ[i] = hiZ;
438
+ if (this.velZ[i] > 0)
439
+ this.velZ[i] = this.velZ[i] < rt ? 0 : -this.velZ[i] * e;
440
+ }
441
+ if (this.posY[i] < loY) {
442
+ this.posY[i] = loY;
443
+ if (this.velY[i] < 0)
444
+ this.velY[i] = -this.velY[i] < rt ? 0 : this.velY[i] * -e;
445
+ const tf = 1 - this.friction;
446
+ this.velX[i] *= tf;
447
+ this.velZ[i] *= tf;
448
+ }
449
+ else if (this.posY[i] > hiY) {
450
+ this.posY[i] = hiY;
451
+ if (this.velY[i] > 0)
452
+ this.velY[i] = this.velY[i] < rt ? 0 : -this.velY[i] * e;
453
+ }
454
+ }
455
+ buildGrid() {
456
+ const n = this.bodyCount;
457
+ const start = this.cellStart;
458
+ 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
+ const awakeCount = this.awakeCount;
462
+ for (let a = 0; a < awakeCount; a += 1) {
463
+ const i = this.awakeList[a];
464
+ this.bodyCell[i] = this.cellOf(i);
465
+ }
466
+ for (let i = 0; i < n; i += 1)
467
+ start[this.bodyCell[i] + 1] += 1;
468
+ for (let c = 0; c < this.numCells; c += 1) {
469
+ start[c + 1] += start[c];
470
+ this.cursor[c] = start[c];
471
+ }
472
+ for (let i = 0; i < n; i += 1) {
473
+ const c = this.bodyCell[i];
474
+ this.sorted[this.cursor[c]++] = i;
475
+ }
476
+ }
477
+ cellOf(i) {
478
+ const cx = cellCoord(this.posX[i], this.bounds.min[0], this.cellSize, this.nx);
479
+ const cy = cellCoord(this.posY[i], this.bounds.min[1], this.cellSize, this.ny);
480
+ const cz = cellCoord(this.posZ[i], this.bounds.min[2], this.cellSize, this.nz);
481
+ return cellIndex(cx, cy, cz, this.nx, this.ny);
482
+ }
483
+ solve(restitutionPass, countPass) {
484
+ const nx = this.nx;
485
+ 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
+ for (let a = 0; a < this.awakeCount; a += 1) {
491
+ 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) {
504
+ const rowBase = (z * ny + y) * nx;
505
+ for (let x = x0; x <= x1; x += 1) {
506
+ const cell = rowBase + x;
507
+ const end = this.cellStart[cell + 1];
508
+ for (let s = this.cellStart[cell]; s < end; s += 1) {
509
+ const j = this.sorted[s];
510
+ if (j === i)
511
+ continue;
512
+ const jSleeping = (this.flags[j] & FLAG_SLEEPING) !== 0;
513
+ if (!jSleeping && j < i)
514
+ continue;
515
+ this.resolve(i, j, restitutionPass, countPass);
516
+ }
517
+ }
518
+ }
519
+ }
520
+ }
521
+ }
522
+ 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
+ let nX = 0;
542
+ let nY = 0;
543
+ let nZ = 0;
544
+ let pen;
545
+ if (px <= py && px <= pz) {
546
+ nX = dx < 0 ? -1 : 1;
547
+ pen = px;
548
+ }
549
+ else if (py <= pz) {
550
+ nY = dy < 0 ? -1 : 1;
551
+ pen = py;
552
+ }
553
+ else {
554
+ nZ = dz < 0 ? -1 : 1;
555
+ pen = pz;
556
+ }
557
+ const rvx = this.velX[j] - this.velX[i];
558
+ const rvy = this.velY[j] - this.velY[i];
559
+ const rvz = this.velZ[j] - this.velZ[i];
560
+ const rvn = rvx * nX + rvy * nY + rvz * nZ;
561
+ const imI = this.invMass[i];
562
+ let imJ = this.invMass[j];
563
+ // A sleeping neighbor only wakes on a genuine impact; a resting contact treats it as a
564
+ // static collider (imJ = 0) so a settled body can rest on the bed without stirring it awake.
565
+ if ((this.flags[j] & FLAG_SLEEPING) !== 0) {
566
+ if (-rvn > this.wakeThreshold)
567
+ this.wake(j);
568
+ else
569
+ imJ = 0;
570
+ }
571
+ const imSum = imI + imJ;
572
+ if (imSum === 0)
573
+ return;
574
+ if (rvn < 0) {
575
+ const e = restitutionPass && -rvn > this.restitutionThreshold ? this.restitution : 0;
576
+ const jn = (-(1 + e) * rvn) / imSum;
577
+ if (restitutionPass && this.collisionListener !== null && -rvn >= this.collisionMinSpeed) {
578
+ const ev = this.collisionEvent;
579
+ ev.a = i;
580
+ ev.b = j;
581
+ ev.nx = nX;
582
+ ev.ny = nY;
583
+ ev.nz = nZ;
584
+ ev.approachSpeed = -rvn;
585
+ ev.impulse = jn;
586
+ this.collisionListener(ev);
587
+ }
588
+ this.velX[i] -= jn * imI * nX;
589
+ this.velY[i] -= jn * imI * nY;
590
+ this.velZ[i] -= jn * imI * nZ;
591
+ this.velX[j] += jn * imJ * nX;
592
+ this.velY[j] += jn * imJ * nY;
593
+ this.velZ[j] += jn * imJ * nZ;
594
+ let tx = rvx - rvn * nX;
595
+ let ty = rvy - rvn * nY;
596
+ let tz = rvz - rvn * nZ;
597
+ const tlen = Math.sqrt(tx * tx + ty * ty + tz * tz);
598
+ if (tlen > 1e-6) {
599
+ tx /= tlen;
600
+ ty /= tlen;
601
+ tz /= tlen;
602
+ let jt = -(rvx * tx + rvy * ty + rvz * tz) / imSum;
603
+ const maxF = this.friction * jn;
604
+ if (jt > maxF)
605
+ jt = maxF;
606
+ else if (jt < -maxF)
607
+ jt = -maxF;
608
+ this.velX[i] -= jt * imI * tx;
609
+ this.velY[i] -= jt * imI * ty;
610
+ this.velZ[i] -= jt * imI * tz;
611
+ this.velX[j] += jt * imJ * tx;
612
+ this.velY[j] += jt * imJ * ty;
613
+ this.velZ[j] += jt * imJ * tz;
614
+ }
615
+ }
616
+ const corr = (Math.max(pen - this.slop, 0) / imSum) * this.correctionFactor;
617
+ this.posX[i] -= corr * imI * nX;
618
+ this.posY[i] -= corr * imI * nY;
619
+ this.posZ[i] -= corr * imI * nZ;
620
+ this.posX[j] += corr * imJ * nX;
621
+ this.posY[j] += corr * imJ * nY;
622
+ this.posZ[j] += corr * imJ * nZ;
623
+ }
624
+ solveJoints(dt) {
625
+ const beta = this.jointCorrection;
626
+ const slop = this.slop;
627
+ for (let id = 0; id < this.jointHigh; id += 1) {
628
+ if (this.jActive[id] !== 1)
629
+ continue;
630
+ const bA = this.jBodyA[id];
631
+ const bB = this.jBodyB[id];
632
+ const ax = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
633
+ const ay = (bA >= 0 ? this.posY[bA] : 0) + this.jAnchorAY[id];
634
+ const az = (bA >= 0 ? this.posZ[bA] : 0) + this.jAnchorAZ[id];
635
+ const bx = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
636
+ const by = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
637
+ 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;
641
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
642
+ if (dist < 1e-9)
643
+ continue;
644
+ const nX = dx / dist;
645
+ const nY = dy / dist;
646
+ const nZ = dz / dist;
647
+ 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
+ const vax = bA >= 0 ? this.velX[bA] : 0;
654
+ const vay = bA >= 0 ? this.velY[bA] : 0;
655
+ const vaz = bA >= 0 ? this.velZ[bA] : 0;
656
+ const vbx = bB >= 0 ? this.velX[bB] : 0;
657
+ const vby = bB >= 0 ? this.velY[bB] : 0;
658
+ const vbz = bB >= 0 ? this.velZ[bB] : 0;
659
+ const rvn = (vbx - vax) * nX + (vby - vay) * nY + (vbz - vaz) * nZ;
660
+ if (this.jSpring[id] === 1) {
661
+ if (Math.abs(c) > slop) {
662
+ if (bA >= 0)
663
+ this.wake(bA);
664
+ if (bB >= 0)
665
+ this.wake(bB);
666
+ }
667
+ let jn = (-(this.jStiffness[id] * c + this.jDamping[id] * rvn) * dt) / imSum;
668
+ const maxJ = this.jMaxImpulse[id];
669
+ if (jn > maxJ)
670
+ jn = maxJ;
671
+ else if (jn < -maxJ)
672
+ jn = -maxJ;
673
+ if (bA >= 0) {
674
+ this.velX[bA] -= jn * imA * nX;
675
+ this.velY[bA] -= jn * imA * nY;
676
+ this.velZ[bA] -= jn * imA * nZ;
677
+ }
678
+ if (bB >= 0) {
679
+ this.velX[bB] += jn * imB * nX;
680
+ this.velY[bB] += jn * imB * nY;
681
+ this.velZ[bB] += jn * imB * nZ;
682
+ }
683
+ continue;
684
+ }
685
+ if (Math.abs(c) > slop) {
686
+ if (bA >= 0)
687
+ this.wake(bA);
688
+ if (bB >= 0)
689
+ this.wake(bB);
690
+ }
691
+ const jn = -rvn / imSum;
692
+ if (bA >= 0) {
693
+ this.velX[bA] -= jn * imA * nX;
694
+ this.velY[bA] -= jn * imA * nY;
695
+ this.velZ[bA] -= jn * imA * nZ;
696
+ }
697
+ if (bB >= 0) {
698
+ this.velX[bB] += jn * imB * nX;
699
+ this.velY[bB] += jn * imB * nY;
700
+ this.velZ[bB] += jn * imB * nZ;
701
+ }
702
+ const corr = (c * beta) / imSum;
703
+ if (bA >= 0) {
704
+ this.posX[bA] += corr * imA * nX;
705
+ this.posY[bA] += corr * imA * nY;
706
+ this.posZ[bA] += corr * imA * nZ;
707
+ }
708
+ if (bB >= 0) {
709
+ this.posX[bB] -= corr * imB * nX;
710
+ this.posY[bB] -= corr * imB * nY;
711
+ this.posZ[bB] -= corr * imB * nZ;
712
+ }
713
+ }
714
+ }
715
+ updateSleep() {
716
+ // A body sleeps when it has actually stopped moving — measured by displacement over
717
+ // the substep, not instantaneous velocity, which can stay "stuck" nonzero on a body
718
+ // pinned between contacts even while its position is at rest.
719
+ const moveEps2 = this.sleepMove2;
720
+ let a = 0;
721
+ while (a < this.awakeCount) {
722
+ const i = this.awakeList[a];
723
+ const dx = this.posX[i] - this.prevX[i];
724
+ const dy = this.posY[i] - this.prevY[i];
725
+ const dz = this.posZ[i] - this.prevZ[i];
726
+ if (dx * dx + dy * dy + dz * dz <= moveEps2) {
727
+ const t = this.sleepTimer[i] + 1;
728
+ if (t >= this.sleepSteps) {
729
+ this.flags[i] |= FLAG_SLEEPING;
730
+ this.velX[i] = 0;
731
+ this.velY[i] = 0;
732
+ this.velZ[i] = 0;
733
+ this.sleepTimer[i] = 0;
734
+ this.removeAwake(i);
735
+ continue;
736
+ }
737
+ this.sleepTimer[i] = t;
738
+ }
739
+ else {
740
+ this.sleepTimer[i] = 0;
741
+ }
742
+ a += 1;
743
+ }
744
+ }
745
+ }
746
+ function performanceNow() {
747
+ const perf = globalThis.performance;
748
+ return perf !== undefined ? perf.now() : 0;
749
+ }