@jgengine/core 0.6.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.
- package/CHANGELOG.md +167 -2
- package/dist/ai/crowd.d.ts +36 -0
- package/dist/ai/crowd.js +213 -0
- package/dist/ai/jobBoard.d.ts +49 -0
- package/dist/ai/jobBoard.js +155 -0
- package/dist/ai/spawnDirector.d.ts +56 -0
- package/dist/ai/spawnDirector.js +150 -0
- package/dist/ai/threat.d.ts +25 -0
- package/dist/ai/threat.js +67 -0
- package/dist/audio/audioFalloff.d.ts +38 -0
- package/dist/audio/audioFalloff.js +32 -0
- package/dist/board/laneBoard.d.ts +63 -0
- package/dist/board/laneBoard.js +114 -0
- package/dist/board/timelineBoard.d.ts +36 -0
- package/dist/board/timelineBoard.js +70 -0
- package/dist/cards/cardPile.d.ts +63 -0
- package/dist/cards/cardPile.js +166 -0
- package/dist/cards/modifierPipeline.d.ts +33 -0
- package/dist/cards/modifierPipeline.js +48 -0
- package/dist/combat/abilityKit.d.ts +42 -0
- package/dist/combat/abilityKit.js +129 -0
- package/dist/combat/animationState.d.ts +47 -0
- package/dist/combat/animationState.js +101 -0
- package/dist/combat/attackTags.d.ts +14 -0
- package/dist/combat/attackTags.js +26 -0
- package/dist/combat/breakMeters.d.ts +32 -0
- package/dist/combat/breakMeters.js +51 -0
- package/dist/combat/comboString.d.ts +35 -0
- package/dist/combat/comboString.js +59 -0
- package/dist/combat/death.d.ts +8 -0
- package/dist/combat/death.js +5 -2
- package/dist/combat/defensiveWindow.d.ts +34 -0
- package/dist/combat/defensiveWindow.js +66 -0
- package/dist/combat/downed.d.ts +35 -0
- package/dist/combat/downed.js +104 -0
- package/dist/combat/effects.d.ts +11 -0
- package/dist/combat/effects.js +22 -13
- package/dist/combat/hitReaction.d.ts +23 -0
- package/dist/combat/hitReaction.js +20 -0
- package/dist/combat/resistance.d.ts +13 -0
- package/dist/combat/resistance.js +46 -0
- package/dist/combat/telegraph.d.ts +27 -0
- package/dist/combat/telegraph.js +37 -0
- package/dist/crafting/crop.d.ts +59 -0
- package/dist/crafting/crop.js +181 -0
- package/dist/crafting/production.d.ts +64 -0
- package/dist/crafting/production.js +130 -0
- package/dist/crafting/recipe.d.ts +62 -0
- package/dist/crafting/recipe.js +101 -0
- package/dist/economy/sharedWallet.d.ts +28 -0
- package/dist/economy/sharedWallet.js +62 -0
- package/dist/economy/techTree.d.ts +40 -0
- package/dist/economy/techTree.js +66 -0
- package/dist/game/cosmetics.d.ts +25 -0
- package/dist/game/cosmetics.js +51 -0
- package/dist/game/defineGame.d.ts +2 -0
- package/dist/game/events.d.ts +62 -0
- package/dist/game/lootFilter.d.ts +33 -0
- package/dist/game/lootFilter.js +48 -0
- package/dist/game/ping.d.ts +67 -0
- package/dist/game/ping.js +86 -0
- package/dist/game/playableGame.d.ts +281 -4
- package/dist/game/race.d.ts +95 -0
- package/dist/game/race.js +216 -0
- package/dist/game/runDraft.d.ts +34 -0
- package/dist/game/runDraft.js +97 -0
- package/dist/game/social.d.ts +27 -0
- package/dist/game/social.js +16 -0
- package/dist/game/worldItem.d.ts +92 -0
- package/dist/game/worldItem.js +132 -0
- package/dist/input/axisInput.d.ts +43 -0
- package/dist/input/axisInput.js +70 -0
- package/dist/input/pointer.d.ts +24 -0
- package/dist/input/pointer.js +17 -0
- package/dist/interaction/contextMenu.d.ts +30 -0
- package/dist/interaction/contextMenu.js +22 -0
- package/dist/interaction/qte.d.ts +20 -0
- package/dist/interaction/qte.js +24 -0
- package/dist/interaction/skillCheck.d.ts +20 -0
- package/dist/interaction/skillCheck.js +26 -0
- package/dist/inventory/shapedGrid.d.ts +56 -0
- package/dist/inventory/shapedGrid.js +176 -0
- package/dist/inventory/storageTier.d.ts +52 -0
- package/dist/inventory/storageTier.js +77 -0
- package/dist/item/affix.d.ts +54 -0
- package/dist/item/affix.js +133 -0
- package/dist/item/durability.d.ts +51 -0
- package/dist/item/durability.js +78 -0
- package/dist/item/modularItem.d.ts +46 -0
- package/dist/item/modularItem.js +75 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +35 -1
- package/dist/movement/dash.d.ts +37 -0
- package/dist/movement/dash.js +65 -0
- package/dist/multiplayer/combatSnapshot.d.ts +45 -0
- package/dist/multiplayer/combatSnapshot.js +89 -0
- package/dist/multiplayer/lagCompensation.d.ts +43 -0
- package/dist/multiplayer/lagCompensation.js +154 -0
- package/dist/multiplayer/matchmaking.d.ts +33 -0
- package/dist/multiplayer/matchmaking.js +60 -0
- package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
- package/dist/multiplayer/simultaneousCommit.js +53 -0
- package/dist/nav/navGrid.d.ts +46 -0
- package/dist/nav/navGrid.js +248 -0
- package/dist/nav/pathFollow.d.ts +29 -0
- package/dist/nav/pathFollow.js +67 -0
- package/dist/physics/buoyancy.d.ts +49 -0
- package/dist/physics/buoyancy.js +112 -0
- package/dist/physics/carryable.d.ts +46 -0
- package/dist/physics/carryable.js +110 -0
- package/dist/physics/damageZones.d.ts +56 -0
- package/dist/physics/damageZones.js +107 -0
- package/dist/physics/forceVolume.d.ts +48 -0
- package/dist/physics/forceVolume.js +119 -0
- package/dist/physics/physicsWorld.d.ts +83 -0
- package/dist/physics/physicsWorld.js +261 -1
- package/dist/physics/ragdoll.d.ts +49 -0
- package/dist/physics/ragdoll.js +103 -0
- package/dist/physics/spatialGrid.d.ts +41 -0
- package/dist/physics/spatialGrid.js +138 -0
- package/dist/physics/structure.d.ts +83 -0
- package/dist/physics/structure.js +0 -0
- package/dist/physics/traversal.d.ts +75 -0
- package/dist/physics/traversal.js +138 -0
- package/dist/physics/vehicleBody.d.ts +93 -0
- package/dist/physics/vehicleBody.js +234 -0
- package/dist/runtime/gameContext.d.ts +64 -2
- package/dist/runtime/gameContext.js +154 -4
- package/dist/runtime/hostPersistence.d.ts +18 -0
- package/dist/runtime/hostPersistence.js +4 -0
- package/dist/runtime/persistenceScope.d.ts +32 -0
- package/dist/runtime/persistenceScope.js +64 -0
- package/dist/scene/autoTarget.d.ts +15 -0
- package/dist/scene/autoTarget.js +54 -0
- package/dist/scene/behaviors.d.ts +13 -1
- package/dist/scene/behaviors.js +3 -0
- package/dist/scene/captureCheck.d.ts +7 -0
- package/dist/scene/captureCheck.js +11 -0
- package/dist/scene/entityStore.d.ts +1 -1
- package/dist/scene/form.d.ts +40 -0
- package/dist/scene/form.js +56 -0
- package/dist/scene/mount.d.ts +62 -0
- package/dist/scene/mount.js +107 -0
- package/dist/scene/possession.d.ts +30 -0
- package/dist/scene/possession.js +51 -0
- package/dist/scene/roster.d.ts +25 -0
- package/dist/scene/roster.js +57 -0
- package/dist/scene/selection.d.ts +30 -0
- package/dist/scene/selection.js +38 -0
- package/dist/scene/stationClaim.d.ts +43 -0
- package/dist/scene/stationClaim.js +109 -0
- package/dist/sensor/frustumSensor.d.ts +60 -0
- package/dist/sensor/frustumSensor.js +104 -0
- package/dist/sensor/hiddenStateProbe.d.ts +31 -0
- package/dist/sensor/hiddenStateProbe.js +40 -0
- package/dist/sensor/recordingBuffer.d.ts +29 -0
- package/dist/sensor/recordingBuffer.js +61 -0
- package/dist/sensor/revealQuery.d.ts +22 -0
- package/dist/sensor/revealQuery.js +36 -0
- package/dist/session/contestedChannel.d.ts +36 -0
- package/dist/session/contestedChannel.js +100 -0
- package/dist/session/extraction.d.ts +54 -0
- package/dist/session/extraction.js +108 -0
- package/dist/session/ring.d.ts +36 -0
- package/dist/session/ring.js +88 -0
- package/dist/session/roundState.d.ts +55 -0
- package/dist/session/roundState.js +129 -0
- package/dist/stats/accumulatorMeter.d.ts +31 -0
- package/dist/stats/accumulatorMeter.js +81 -0
- package/dist/stats/eventMeter.d.ts +34 -0
- package/dist/stats/eventMeter.js +76 -0
- package/dist/stats/rollCheck.d.ts +15 -0
- package/dist/stats/rollCheck.js +15 -0
- package/dist/survival/decayMeter.d.ts +59 -0
- package/dist/survival/decayMeter.js +95 -0
- package/dist/survival/moodle.d.ts +48 -0
- package/dist/survival/moodle.js +91 -0
- package/dist/survival/regionHealth.d.ts +72 -0
- package/dist/survival/regionHealth.js +129 -0
- package/dist/tactics/predictiveQuery.d.ts +34 -0
- package/dist/tactics/predictiveQuery.js +44 -0
- package/dist/tactics/snapshot.d.ts +18 -0
- package/dist/tactics/snapshot.js +65 -0
- package/dist/tactics/surface.d.ts +52 -0
- package/dist/tactics/surface.js +135 -0
- package/dist/tactics/tacticalGrid.d.ts +57 -0
- package/dist/tactics/tacticalGrid.js +244 -0
- package/dist/time/beatClock.d.ts +40 -0
- package/dist/time/beatClock.js +73 -0
- package/dist/turn/commit.d.ts +27 -0
- package/dist/turn/commit.js +72 -0
- package/dist/turn/turnLoop.d.ts +58 -0
- package/dist/turn/turnLoop.js +216 -0
- package/dist/world/buildPermissions.d.ts +47 -0
- package/dist/world/buildPermissions.js +134 -0
- package/dist/world/carve.d.ts +105 -0
- package/dist/world/carve.js +175 -0
- package/dist/world/connectors.d.ts +40 -0
- package/dist/world/connectors.js +67 -0
- package/dist/world/envField.d.ts +71 -0
- package/dist/world/envField.js +96 -0
- package/dist/world/fog.d.ts +40 -0
- package/dist/world/fog.js +137 -0
- package/dist/world/markers.d.ts +52 -0
- package/dist/world/markers.js +96 -0
- package/dist/world/minimap.d.ts +42 -0
- package/dist/world/minimap.js +72 -0
- package/dist/world/placedStructureStore.d.ts +39 -0
- package/dist/world/placedStructureStore.js +95 -0
- package/dist/world/placementController.d.ts +48 -0
- package/dist/world/placementController.js +97 -0
- package/dist/world/realm.d.ts +61 -0
- package/dist/world/realm.js +66 -0
- package/dist/world/support.d.ts +27 -0
- package/dist/world/support.js +75 -0
- package/dist/world/terraform.d.ts +54 -0
- package/dist/world/terraform.js +186 -0
- package/dist/world/walls.d.ts +61 -0
- package/dist/world/walls.js +221 -0
- package/dist/world/weather.d.ts +92 -0
- package/dist/world/weather.js +169 -0
- package/package.json +1 -1
|
@@ -26,6 +26,8 @@ export class PhysicsWorld {
|
|
|
26
26
|
sleepMove2;
|
|
27
27
|
sleepSteps;
|
|
28
28
|
wakeThreshold;
|
|
29
|
+
jointCapacity;
|
|
30
|
+
jointCorrection;
|
|
29
31
|
posX;
|
|
30
32
|
posY;
|
|
31
33
|
posZ;
|
|
@@ -56,6 +58,35 @@ export class PhysicsWorld {
|
|
|
56
58
|
awakeList;
|
|
57
59
|
awakeSlot;
|
|
58
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
|
+
};
|
|
59
90
|
bodyCount = 0;
|
|
60
91
|
accumulator = 0;
|
|
61
92
|
stats = {
|
|
@@ -86,6 +117,8 @@ export class PhysicsWorld {
|
|
|
86
117
|
this.sleepMove2 = sleepMoveEps * sleepMoveEps;
|
|
87
118
|
this.sleepSteps = config.sleepThresholdSteps ?? 30;
|
|
88
119
|
this.wakeThreshold = config.wakeThreshold ?? 0.5;
|
|
120
|
+
this.jointCapacity = Math.max(0, config.jointCapacity ?? 256);
|
|
121
|
+
this.jointCorrection = config.jointCorrection ?? 0.5;
|
|
89
122
|
const cap = config.capacity;
|
|
90
123
|
this.posX = new Float32Array(cap);
|
|
91
124
|
this.posY = new Float32Array(cap);
|
|
@@ -114,6 +147,22 @@ export class PhysicsWorld {
|
|
|
114
147
|
this.bodyCell = new Int32Array(cap);
|
|
115
148
|
this.awakeList = new Int32Array(cap);
|
|
116
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);
|
|
117
166
|
}
|
|
118
167
|
addAwake(i) {
|
|
119
168
|
if (this.awakeSlot[i] >= 0)
|
|
@@ -138,6 +187,109 @@ export class PhysicsWorld {
|
|
|
138
187
|
get cells() {
|
|
139
188
|
return { nx: this.nx, ny: this.ny, nz: this.nz, total: this.numCells };
|
|
140
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
|
+
}
|
|
141
293
|
addBody(options) {
|
|
142
294
|
if (this.bodyCount >= this.capacity)
|
|
143
295
|
throw new Error("PhysicsWorld: capacity exceeded");
|
|
@@ -194,6 +346,9 @@ export class PhysicsWorld {
|
|
|
194
346
|
this.bodyCount = 0;
|
|
195
347
|
this.accumulator = 0;
|
|
196
348
|
this.awakeCount = 0;
|
|
349
|
+
this.jointHigh = 0;
|
|
350
|
+
this.jFreeCount = 0;
|
|
351
|
+
this.jointActiveCount = 0;
|
|
197
352
|
}
|
|
198
353
|
/** Advance by a real frame delta; runs whole fixed substeps and carries the remainder. */
|
|
199
354
|
step(frameDt) {
|
|
@@ -243,8 +398,11 @@ export class PhysicsWorld {
|
|
|
243
398
|
}
|
|
244
399
|
this.buildGrid();
|
|
245
400
|
const iterations = this.solverIterations;
|
|
246
|
-
for (let it = 0; it < iterations; it += 1)
|
|
401
|
+
for (let it = 0; it < iterations; it += 1) {
|
|
247
402
|
this.solve(it === 0, it === iterations - 1);
|
|
403
|
+
if (this.jointActiveCount > 0)
|
|
404
|
+
this.solveJoints(dt);
|
|
405
|
+
}
|
|
248
406
|
for (let a = 0; a < this.awakeCount; a += 1)
|
|
249
407
|
this.constrainBounds(this.awakeList[a]);
|
|
250
408
|
this.updateSleep();
|
|
@@ -416,6 +574,17 @@ export class PhysicsWorld {
|
|
|
416
574
|
if (rvn < 0) {
|
|
417
575
|
const e = restitutionPass && -rvn > this.restitutionThreshold ? this.restitution : 0;
|
|
418
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
|
+
}
|
|
419
588
|
this.velX[i] -= jn * imI * nX;
|
|
420
589
|
this.velY[i] -= jn * imI * nY;
|
|
421
590
|
this.velZ[i] -= jn * imI * nZ;
|
|
@@ -452,6 +621,97 @@ export class PhysicsWorld {
|
|
|
452
621
|
this.posY[j] += corr * imJ * nY;
|
|
453
622
|
this.posZ[j] += corr * imJ * nZ;
|
|
454
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
|
+
}
|
|
455
715
|
updateSleep() {
|
|
456
716
|
// A body sleeps when it has actually stopped moving — measured by displacement over
|
|
457
717
|
// the substep, not instantaneous velocity, which can stay "stuck" nonzero on a body
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { PhysicsWorld } from "./physicsWorld.js";
|
|
2
|
+
export interface RagdollBoneSpec {
|
|
3
|
+
name: string;
|
|
4
|
+
position: readonly [number, number, number];
|
|
5
|
+
halfExtents: readonly [number, number, number];
|
|
6
|
+
/** Default 1. */
|
|
7
|
+
mass?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface RagdollLinkSpec {
|
|
10
|
+
a: string;
|
|
11
|
+
b: string;
|
|
12
|
+
/** `hinge` pins the shared anchor; `distance` (default) holds the bones' current separation. */
|
|
13
|
+
kind?: "hinge" | "distance";
|
|
14
|
+
/** World point for a hinge pin. Defaults to the midpoint of the two bone centers. */
|
|
15
|
+
anchor?: readonly [number, number, number];
|
|
16
|
+
}
|
|
17
|
+
export interface RagdollBalanceConfig {
|
|
18
|
+
/** Bone the motor keeps upright (torso/head). */
|
|
19
|
+
root: string;
|
|
20
|
+
/** Target height of the root above the floor the motor drives toward. */
|
|
21
|
+
targetHeight: number;
|
|
22
|
+
/** Corrective gain pulling the root back to `targetHeight`. Default 8. */
|
|
23
|
+
strength?: number;
|
|
24
|
+
/** Locomotion push applied to the root from the move vector (active-ragdoll walking). Default 6. */
|
|
25
|
+
moveForce?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface RagdollConfig {
|
|
28
|
+
bones: RagdollBoneSpec[];
|
|
29
|
+
links: RagdollLinkSpec[];
|
|
30
|
+
/** Optional active-ragdoll balance motor; omit for a purely floppy ragdoll. */
|
|
31
|
+
balance?: RagdollBalanceConfig;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A jointed multi-body character — floppy by default, or active-ragdoll when a balance motor is
|
|
35
|
+
* configured. Bones are `PhysicsWorld` bodies; links are joints on the joint API. `balance(dt, mx, mz)`
|
|
36
|
+
* drives the root toward its target height (staying upright) and pushes it by the move vector.
|
|
37
|
+
*/
|
|
38
|
+
export declare class Ragdoll {
|
|
39
|
+
readonly bones: Record<string, number>;
|
|
40
|
+
readonly links: number[];
|
|
41
|
+
private readonly world;
|
|
42
|
+
private readonly config?;
|
|
43
|
+
constructor(world: PhysicsWorld, bones: Record<string, number>, links: number[], balance?: RagdollBalanceConfig);
|
|
44
|
+
centerOfMass(): [number, number, number];
|
|
45
|
+
applyImpulse(bone: string, x: number, y: number, z: number): void;
|
|
46
|
+
balance(dt: number, moveX?: number, moveZ?: number): void;
|
|
47
|
+
remove(): void;
|
|
48
|
+
}
|
|
49
|
+
export declare function createRagdoll(world: PhysicsWorld, config: RagdollConfig): Ragdoll;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A jointed multi-body character — floppy by default, or active-ragdoll when a balance motor is
|
|
3
|
+
* configured. Bones are `PhysicsWorld` bodies; links are joints on the joint API. `balance(dt, mx, mz)`
|
|
4
|
+
* drives the root toward its target height (staying upright) and pushes it by the move vector.
|
|
5
|
+
*/
|
|
6
|
+
export class Ragdoll {
|
|
7
|
+
bones;
|
|
8
|
+
links;
|
|
9
|
+
world;
|
|
10
|
+
config;
|
|
11
|
+
constructor(world, bones, links, balance) {
|
|
12
|
+
this.world = world;
|
|
13
|
+
this.bones = bones;
|
|
14
|
+
this.links = links;
|
|
15
|
+
this.config = balance;
|
|
16
|
+
}
|
|
17
|
+
centerOfMass() {
|
|
18
|
+
const w = this.world;
|
|
19
|
+
let mx = 0;
|
|
20
|
+
let my = 0;
|
|
21
|
+
let mz = 0;
|
|
22
|
+
let total = 0;
|
|
23
|
+
for (const id of Object.values(this.bones)) {
|
|
24
|
+
const im = w.invMass[id];
|
|
25
|
+
const m = im > 0 ? 1 / im : 0;
|
|
26
|
+
mx += w.posX[id] * m;
|
|
27
|
+
my += w.posY[id] * m;
|
|
28
|
+
mz += w.posZ[id] * m;
|
|
29
|
+
total += m;
|
|
30
|
+
}
|
|
31
|
+
if (total === 0)
|
|
32
|
+
return [0, 0, 0];
|
|
33
|
+
return [mx / total, my / total, mz / total];
|
|
34
|
+
}
|
|
35
|
+
applyImpulse(bone, x, y, z) {
|
|
36
|
+
const w = this.world;
|
|
37
|
+
const id = this.bones[bone];
|
|
38
|
+
if (id === undefined)
|
|
39
|
+
return;
|
|
40
|
+
const im = w.invMass[id];
|
|
41
|
+
w.velX[id] += x * im;
|
|
42
|
+
w.velY[id] += y * im;
|
|
43
|
+
w.velZ[id] += z * im;
|
|
44
|
+
w.wake(id);
|
|
45
|
+
}
|
|
46
|
+
balance(dt, moveX = 0, moveZ = 0) {
|
|
47
|
+
const c = this.config;
|
|
48
|
+
if (c === undefined)
|
|
49
|
+
return;
|
|
50
|
+
const w = this.world;
|
|
51
|
+
const id = this.bones[c.root];
|
|
52
|
+
if (id === undefined)
|
|
53
|
+
return;
|
|
54
|
+
const strength = c.strength ?? 8;
|
|
55
|
+
const moveForce = c.moveForce ?? 6;
|
|
56
|
+
const err = c.targetHeight - w.posY[id];
|
|
57
|
+
w.velY[id] += err * strength * dt;
|
|
58
|
+
w.velX[id] += moveX * moveForce * dt;
|
|
59
|
+
w.velZ[id] += moveZ * moveForce * dt;
|
|
60
|
+
w.wake(id);
|
|
61
|
+
}
|
|
62
|
+
remove() {
|
|
63
|
+
for (const j of this.links)
|
|
64
|
+
this.world.removeJoint(j);
|
|
65
|
+
this.links.length = 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function createRagdoll(world, config) {
|
|
69
|
+
const bones = {};
|
|
70
|
+
for (const spec of config.bones) {
|
|
71
|
+
bones[spec.name] = world.addBody({
|
|
72
|
+
position: spec.position,
|
|
73
|
+
halfExtents: spec.halfExtents,
|
|
74
|
+
mass: spec.mass ?? 1,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const links = [];
|
|
78
|
+
for (const link of config.links) {
|
|
79
|
+
const a = bones[link.a];
|
|
80
|
+
const b = bones[link.b];
|
|
81
|
+
if (a === undefined || b === undefined)
|
|
82
|
+
continue;
|
|
83
|
+
if ((link.kind ?? "distance") === "hinge") {
|
|
84
|
+
const mid = link.anchor !== undefined
|
|
85
|
+
? [link.anchor[0], link.anchor[1], link.anchor[2]]
|
|
86
|
+
: [
|
|
87
|
+
(world.posX[a] + world.posX[b]) / 2,
|
|
88
|
+
(world.posY[a] + world.posY[b]) / 2,
|
|
89
|
+
(world.posZ[a] + world.posZ[b]) / 2,
|
|
90
|
+
];
|
|
91
|
+
links.push(world.hingeJoint({
|
|
92
|
+
bodyA: a,
|
|
93
|
+
bodyB: b,
|
|
94
|
+
anchorA: [mid[0] - world.posX[a], mid[1] - world.posY[a], mid[2] - world.posZ[a]],
|
|
95
|
+
anchorB: [mid[0] - world.posX[b], mid[1] - world.posY[b], mid[2] - world.posZ[b]],
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
links.push(world.distanceJoint({ bodyA: a, bodyB: b }));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return new Ragdoll(world, bones, links, config.balance);
|
|
103
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { PhysicsBounds } from "./physicsWorld.js";
|
|
2
|
+
export interface SpatialGridConfig {
|
|
3
|
+
/** Horizontal extent to index; only x/z of the bounds are used (a top-down plane). */
|
|
4
|
+
bounds: PhysicsBounds;
|
|
5
|
+
/** Cell edge; tune to ≈ the query/contact radius so a hit touches ≤ 4 cells. */
|
|
6
|
+
cellSize: number;
|
|
7
|
+
/** Maximum entities the index holds; fixed at construction. */
|
|
8
|
+
capacity: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A uniform-grid broad-phase over the x/z plane, separate from the rigid-body sim, for cheap
|
|
12
|
+
* same-tick proximity across hundreds–thousands of simple movers (swarm enemies). Rebuild each
|
|
13
|
+
* tick from the caller's own position arrays, then `queryCircle` (enemies hitting the player / an
|
|
14
|
+
* AoE) or `forEachPair` (mutual separation). Both are precise: no false negatives, no false
|
|
15
|
+
* positives beyond the exact distance test.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SpatialGrid {
|
|
18
|
+
readonly cellSize: number;
|
|
19
|
+
readonly nx: number;
|
|
20
|
+
readonly nz: number;
|
|
21
|
+
readonly capacity: number;
|
|
22
|
+
private readonly minX;
|
|
23
|
+
private readonly minZ;
|
|
24
|
+
private readonly numCells;
|
|
25
|
+
private readonly cellStart;
|
|
26
|
+
private readonly cursor;
|
|
27
|
+
private readonly sorted;
|
|
28
|
+
private readonly cellOfBody;
|
|
29
|
+
private xs;
|
|
30
|
+
private zs;
|
|
31
|
+
private entityCount;
|
|
32
|
+
constructor(config: SpatialGridConfig);
|
|
33
|
+
get count(): number;
|
|
34
|
+
private cellX;
|
|
35
|
+
private cellZ;
|
|
36
|
+
rebuild(count: number, xs: Float32Array, zs: Float32Array): void;
|
|
37
|
+
/** Fill `out` with every entity within `radius` of (x,z); returns the hit count (capped at `out.length`). */
|
|
38
|
+
queryCircle(x: number, z: number, radius: number, out: Int32Array): number;
|
|
39
|
+
/** Invoke `cb(a, b)` once for each unordered pair within `maxDistance` (mutual separation/overlap). */
|
|
40
|
+
forEachPair(maxDistance: number, cb: (a: number, b: number) => void): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A uniform-grid broad-phase over the x/z plane, separate from the rigid-body sim, for cheap
|
|
3
|
+
* same-tick proximity across hundreds–thousands of simple movers (swarm enemies). Rebuild each
|
|
4
|
+
* tick from the caller's own position arrays, then `queryCircle` (enemies hitting the player / an
|
|
5
|
+
* AoE) or `forEachPair` (mutual separation). Both are precise: no false negatives, no false
|
|
6
|
+
* positives beyond the exact distance test.
|
|
7
|
+
*/
|
|
8
|
+
export class SpatialGrid {
|
|
9
|
+
cellSize;
|
|
10
|
+
nx;
|
|
11
|
+
nz;
|
|
12
|
+
capacity;
|
|
13
|
+
minX;
|
|
14
|
+
minZ;
|
|
15
|
+
numCells;
|
|
16
|
+
cellStart;
|
|
17
|
+
cursor;
|
|
18
|
+
sorted;
|
|
19
|
+
cellOfBody;
|
|
20
|
+
xs = null;
|
|
21
|
+
zs = null;
|
|
22
|
+
entityCount = 0;
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.cellSize = config.cellSize;
|
|
25
|
+
this.capacity = config.capacity;
|
|
26
|
+
this.minX = config.bounds.min[0];
|
|
27
|
+
this.minZ = config.bounds.min[2];
|
|
28
|
+
const spanX = Math.max(config.bounds.max[0] - config.bounds.min[0], config.cellSize);
|
|
29
|
+
const spanZ = Math.max(config.bounds.max[2] - config.bounds.min[2], config.cellSize);
|
|
30
|
+
this.nx = Math.max(1, Math.ceil(spanX / config.cellSize));
|
|
31
|
+
this.nz = Math.max(1, Math.ceil(spanZ / config.cellSize));
|
|
32
|
+
this.numCells = this.nx * this.nz;
|
|
33
|
+
this.cellStart = new Int32Array(this.numCells + 1);
|
|
34
|
+
this.cursor = new Int32Array(this.numCells);
|
|
35
|
+
this.sorted = new Int32Array(config.capacity);
|
|
36
|
+
this.cellOfBody = new Int32Array(config.capacity);
|
|
37
|
+
}
|
|
38
|
+
get count() {
|
|
39
|
+
return this.entityCount;
|
|
40
|
+
}
|
|
41
|
+
cellX(x) {
|
|
42
|
+
const raw = Math.floor((x - this.minX) / this.cellSize);
|
|
43
|
+
return raw < 0 ? 0 : raw >= this.nx ? this.nx - 1 : raw;
|
|
44
|
+
}
|
|
45
|
+
cellZ(z) {
|
|
46
|
+
const raw = Math.floor((z - this.minZ) / this.cellSize);
|
|
47
|
+
return raw < 0 ? 0 : raw >= this.nz ? this.nz - 1 : raw;
|
|
48
|
+
}
|
|
49
|
+
rebuild(count, xs, zs) {
|
|
50
|
+
this.entityCount = count;
|
|
51
|
+
this.xs = xs;
|
|
52
|
+
this.zs = zs;
|
|
53
|
+
const start = this.cellStart;
|
|
54
|
+
start.fill(0);
|
|
55
|
+
for (let i = 0; i < count; i += 1) {
|
|
56
|
+
const c = this.cellZ(zs[i]) * this.nx + this.cellX(xs[i]);
|
|
57
|
+
this.cellOfBody[i] = c;
|
|
58
|
+
start[c + 1] += 1;
|
|
59
|
+
}
|
|
60
|
+
for (let c = 0; c < this.numCells; c += 1) {
|
|
61
|
+
start[c + 1] += start[c];
|
|
62
|
+
this.cursor[c] = start[c];
|
|
63
|
+
}
|
|
64
|
+
for (let i = 0; i < count; i += 1) {
|
|
65
|
+
const c = this.cellOfBody[i];
|
|
66
|
+
this.sorted[this.cursor[c]++] = i;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Fill `out` with every entity within `radius` of (x,z); returns the hit count (capped at `out.length`). */
|
|
70
|
+
queryCircle(x, z, radius, out) {
|
|
71
|
+
const xs = this.xs;
|
|
72
|
+
const zs = this.zs;
|
|
73
|
+
if (xs === null || zs === null)
|
|
74
|
+
return 0;
|
|
75
|
+
const r2 = radius * radius;
|
|
76
|
+
const cxMin = this.cellX(x - radius);
|
|
77
|
+
const cxMax = this.cellX(x + radius);
|
|
78
|
+
const czMin = this.cellZ(z - radius);
|
|
79
|
+
const czMax = this.cellZ(z + radius);
|
|
80
|
+
const limit = out.length;
|
|
81
|
+
let n = 0;
|
|
82
|
+
for (let cz = czMin; cz <= czMax; cz += 1) {
|
|
83
|
+
const rowBase = cz * this.nx;
|
|
84
|
+
for (let cx = cxMin; cx <= cxMax; cx += 1) {
|
|
85
|
+
const cell = rowBase + cx;
|
|
86
|
+
const end = this.cellStart[cell + 1];
|
|
87
|
+
for (let s = this.cellStart[cell]; s < end; s += 1) {
|
|
88
|
+
const id = this.sorted[s];
|
|
89
|
+
const dx = xs[id] - x;
|
|
90
|
+
const dz = zs[id] - z;
|
|
91
|
+
if (dx * dx + dz * dz <= r2) {
|
|
92
|
+
if (n >= limit)
|
|
93
|
+
return n;
|
|
94
|
+
out[n++] = id;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return n;
|
|
100
|
+
}
|
|
101
|
+
/** Invoke `cb(a, b)` once for each unordered pair within `maxDistance` (mutual separation/overlap). */
|
|
102
|
+
forEachPair(maxDistance, cb) {
|
|
103
|
+
const xs = this.xs;
|
|
104
|
+
const zs = this.zs;
|
|
105
|
+
if (xs === null || zs === null)
|
|
106
|
+
return;
|
|
107
|
+
const d2 = maxDistance * maxDistance;
|
|
108
|
+
const nx = this.nx;
|
|
109
|
+
const nz = this.nz;
|
|
110
|
+
for (let i = 0; i < this.entityCount; i += 1) {
|
|
111
|
+
const c = this.cellOfBody[i];
|
|
112
|
+
const cz = (c / nx) | 0;
|
|
113
|
+
const cx = c - cz * nx;
|
|
114
|
+
const x = xs[i];
|
|
115
|
+
const z = zs[i];
|
|
116
|
+
const z0 = cz > 0 ? cz - 1 : cz;
|
|
117
|
+
const z1 = cz < nz - 1 ? cz + 1 : cz;
|
|
118
|
+
const x0 = cx > 0 ? cx - 1 : cx;
|
|
119
|
+
const x1 = cx < nx - 1 ? cx + 1 : cx;
|
|
120
|
+
for (let gz = z0; gz <= z1; gz += 1) {
|
|
121
|
+
const rowBase = gz * nx;
|
|
122
|
+
for (let gx = x0; gx <= x1; gx += 1) {
|
|
123
|
+
const cell = rowBase + gx;
|
|
124
|
+
const end = this.cellStart[cell + 1];
|
|
125
|
+
for (let s = this.cellStart[cell]; s < end; s += 1) {
|
|
126
|
+
const j = this.sorted[s];
|
|
127
|
+
if (j <= i)
|
|
128
|
+
continue;
|
|
129
|
+
const dx = xs[j] - x;
|
|
130
|
+
const dz = zs[j] - z;
|
|
131
|
+
if (dx * dx + dz * dz <= d2)
|
|
132
|
+
cb(i, j);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|