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