@phalanx-engine/physics 0.1.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/README.md +437 -0
- package/dist/PhysicsWorld.d.ts +35 -0
- package/dist/PhysicsWorld.d.ts.map +1 -0
- package/dist/PhysicsWorld.js +112 -0
- package/dist/PhysicsWorldConfig.d.ts +21 -0
- package/dist/PhysicsWorldConfig.d.ts.map +1 -0
- package/dist/PhysicsWorldConfig.js +1 -0
- package/dist/collision/CollisionManifold.d.ts +9 -0
- package/dist/collision/CollisionManifold.d.ts.map +1 -0
- package/dist/collision/CollisionManifold.js +1 -0
- package/dist/collision/NarrowPhase.d.ts +8 -0
- package/dist/collision/NarrowPhase.d.ts.map +1 -0
- package/dist/collision/NarrowPhase.js +112 -0
- package/dist/collision/SpatialHashGrid.d.ts +19 -0
- package/dist/collision/SpatialHashGrid.d.ts.map +1 -0
- package/dist/collision/SpatialHashGrid.js +125 -0
- package/dist/collision/index.d.ts +4 -0
- package/dist/collision/index.d.ts.map +1 -0
- package/dist/collision/index.js +2 -0
- package/dist/components/InterpolationComponent.d.ts +15 -0
- package/dist/components/InterpolationComponent.d.ts.map +1 -0
- package/dist/components/InterpolationComponent.js +32 -0
- package/dist/components/PhysicsBodyComponent.d.ts +53 -0
- package/dist/components/PhysicsBodyComponent.d.ts.map +1 -0
- package/dist/components/PhysicsBodyComponent.js +157 -0
- package/dist/components/TransformComponent.d.ts +32 -0
- package/dist/components/TransformComponent.d.ts.map +1 -0
- package/dist/components/TransformComponent.js +75 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +3 -0
- package/dist/events.d.ts +7 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +6 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/systems/CollisionSystem.d.ts +20 -0
- package/dist/systems/CollisionSystem.d.ts.map +1 -0
- package/dist/systems/CollisionSystem.js +150 -0
- package/dist/systems/InterpolationSystem.d.ts +28 -0
- package/dist/systems/InterpolationSystem.d.ts.map +1 -0
- package/dist/systems/InterpolationSystem.js +104 -0
- package/dist/systems/PhysicsSystem.d.ts +41 -0
- package/dist/systems/PhysicsSystem.d.ts.map +1 -0
- package/dist/systems/PhysicsSystem.js +316 -0
- package/dist/systems/index.d.ts +5 -0
- package/dist/systems/index.d.ts.map +1 -0
- package/dist/systems/index.js +3 -0
- package/dist/tick/AutonomousPhysicsTickProvider.d.ts +18 -0
- package/dist/tick/AutonomousPhysicsTickProvider.d.ts.map +1 -0
- package/dist/tick/AutonomousPhysicsTickProvider.js +39 -0
- package/dist/tick/ExternalPhysicsTickProvider.d.ts +8 -0
- package/dist/tick/ExternalPhysicsTickProvider.d.ts.map +1 -0
- package/dist/tick/ExternalPhysicsTickProvider.js +6 -0
- package/dist/tick/IPhysicsTickProvider.d.ts +5 -0
- package/dist/tick/IPhysicsTickProvider.d.ts.map +1 -0
- package/dist/tick/IPhysicsTickProvider.js +1 -0
- package/dist/tick/index.d.ts +5 -0
- package/dist/tick/index.d.ts.map +1 -0
- package/dist/tick/index.js +2 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { GameSystem } from '@phalanx-engine/ecs';
|
|
2
|
+
import { FP } from '@phalanx-engine/math';
|
|
3
|
+
import { PhysicsSoASchema } from '../components/PhysicsBodyComponent';
|
|
4
|
+
import { TransformSoASchema } from '../components/TransformComponent';
|
|
5
|
+
import { SpatialHashGrid } from '../collision/SpatialHashGrid';
|
|
6
|
+
import { NarrowPhase } from '../collision/NarrowPhase';
|
|
7
|
+
import { PhysicsEvents } from '../events';
|
|
8
|
+
const SEPARATION_HALF = FP.FromFloat(0.5);
|
|
9
|
+
export class PhysicsSystem extends GameSystem {
|
|
10
|
+
physicsStore;
|
|
11
|
+
transformStore;
|
|
12
|
+
config;
|
|
13
|
+
spatialGrid;
|
|
14
|
+
collisionFilter = null;
|
|
15
|
+
externalTickProvider = null;
|
|
16
|
+
providerStarted = false;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
super();
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.spatialGrid = new SpatialHashGrid(config.gridCellSize);
|
|
21
|
+
}
|
|
22
|
+
init(context) {
|
|
23
|
+
super.init(context);
|
|
24
|
+
this.physicsStore = this.entityManager.getOrCreateSoAStore(PhysicsSoASchema);
|
|
25
|
+
this.transformStore = this.entityManager.getOrCreateSoAStore(TransformSoASchema);
|
|
26
|
+
this.tryStartProvider();
|
|
27
|
+
}
|
|
28
|
+
tryStartProvider() {
|
|
29
|
+
if (this.providerStarted ||
|
|
30
|
+
!this.physicsStore ||
|
|
31
|
+
!this.transformStore ||
|
|
32
|
+
!this.externalTickProvider)
|
|
33
|
+
return;
|
|
34
|
+
this.providerStarted = true;
|
|
35
|
+
this.externalTickProvider.start(() => this.step());
|
|
36
|
+
}
|
|
37
|
+
setCollisionFilter(filter) {
|
|
38
|
+
this.collisionFilter = filter;
|
|
39
|
+
}
|
|
40
|
+
step() {
|
|
41
|
+
const subDt = FP.Div(this.config.tickDt, FP.FromFloat(this.config.subSteps));
|
|
42
|
+
for (let i = 0; i < this.config.subSteps; i++) {
|
|
43
|
+
this.applyVelocities(subDt);
|
|
44
|
+
this.rebuildSpatialGrid();
|
|
45
|
+
this.detectAndResolve();
|
|
46
|
+
this.applyFriction();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
processTick(_tick) {
|
|
50
|
+
if (this.externalTickProvider)
|
|
51
|
+
return;
|
|
52
|
+
this.step();
|
|
53
|
+
}
|
|
54
|
+
setTickProvider(provider) {
|
|
55
|
+
this.externalTickProvider?.stop();
|
|
56
|
+
this.providerStarted = false;
|
|
57
|
+
this.externalTickProvider = provider;
|
|
58
|
+
this.tryStartProvider();
|
|
59
|
+
}
|
|
60
|
+
applyImpulse(entityId, vx, vz) {
|
|
61
|
+
const physIndex = this.physicsStore.indexOf(entityId);
|
|
62
|
+
if (physIndex === -1)
|
|
63
|
+
return;
|
|
64
|
+
this.physicsStore.arrays.ignorePhysics[physIndex] = 0;
|
|
65
|
+
this.physicsStore.arrays.velocityX[physIndex] = FP.ToRaw(vx);
|
|
66
|
+
this.physicsStore.arrays.velocityZ[physIndex] = FP.ToRaw(vz);
|
|
67
|
+
}
|
|
68
|
+
isSettled(threshold) {
|
|
69
|
+
const thresh = threshold ?? FP.FromFloat(0.01);
|
|
70
|
+
const threshSq = FP.Mul(thresh, thresh);
|
|
71
|
+
const velX = this.physicsStore.arrays.velocityX;
|
|
72
|
+
const velZ = this.physicsStore.arrays.velocityZ;
|
|
73
|
+
const isStatic = this.physicsStore.arrays.isStatic;
|
|
74
|
+
const ignore = this.physicsStore.arrays.ignorePhysics;
|
|
75
|
+
for (const entityId of this.physicsStore.entityIds()) {
|
|
76
|
+
const i = this.physicsStore.indexOf(entityId);
|
|
77
|
+
if (isStatic[i] === 1 || ignore[i] === 1)
|
|
78
|
+
continue;
|
|
79
|
+
const vx = FP.FromRaw(velX[i]);
|
|
80
|
+
const vz = FP.FromRaw(velZ[i]);
|
|
81
|
+
if (FP.Gt(FP.Add(FP.Mul(vx, vx), FP.Mul(vz, vz)), threshSq))
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
applyVelocities(dt) {
|
|
87
|
+
const physVelocityX = this.physicsStore.arrays.velocityX;
|
|
88
|
+
const physVelocityZ = this.physicsStore.arrays.velocityZ;
|
|
89
|
+
const physIsStatic = this.physicsStore.arrays.isStatic;
|
|
90
|
+
const physIgnorePhysics = this.physicsStore.arrays.ignorePhysics;
|
|
91
|
+
const fpPosXArr = this.transformStore.arrays.fpPositionX;
|
|
92
|
+
const fpPosZArr = this.transformStore.arrays.fpPositionZ;
|
|
93
|
+
const maxVelSq = FP.Mul(this.config.maxVelocity, this.config.maxVelocity);
|
|
94
|
+
const bounds = this.config.worldBounds;
|
|
95
|
+
const pendingBoundsExits = [];
|
|
96
|
+
for (const entityId of this.physicsStore.entityIds()) {
|
|
97
|
+
const physIndex = this.physicsStore.indexOf(entityId);
|
|
98
|
+
if (physIsStatic[physIndex] === 1)
|
|
99
|
+
continue;
|
|
100
|
+
if (physIgnorePhysics[physIndex] === 1)
|
|
101
|
+
continue;
|
|
102
|
+
const transformIndex = this.transformStore.indexOf(entityId);
|
|
103
|
+
if (transformIndex === -1)
|
|
104
|
+
continue;
|
|
105
|
+
let velX = FP.FromRaw(physVelocityX[physIndex]);
|
|
106
|
+
let velZ = FP.FromRaw(physVelocityZ[physIndex]);
|
|
107
|
+
const velMagSq = FP.Add(FP.Mul(velX, velX), FP.Mul(velZ, velZ));
|
|
108
|
+
if (FP.Gt(velMagSq, maxVelSq)) {
|
|
109
|
+
const scale = FP.Div(this.config.maxVelocity, FP.Sqrt(velMagSq));
|
|
110
|
+
velX = FP.Mul(velX, scale);
|
|
111
|
+
velZ = FP.Mul(velZ, scale);
|
|
112
|
+
physVelocityX[physIndex] = FP.ToRaw(velX);
|
|
113
|
+
physVelocityZ[physIndex] = FP.ToRaw(velZ);
|
|
114
|
+
}
|
|
115
|
+
const posX = FP.FromRaw(fpPosXArr[transformIndex]);
|
|
116
|
+
const posZ = FP.FromRaw(fpPosZArr[transformIndex]);
|
|
117
|
+
let newPosX = FP.Add(posX, FP.Mul(velX, dt));
|
|
118
|
+
let newPosZ = FP.Add(posZ, FP.Mul(velZ, dt));
|
|
119
|
+
if (bounds) {
|
|
120
|
+
const outOfBounds = FP.Lt(newPosX, bounds.minX) || FP.Gt(newPosX, bounds.maxX) ||
|
|
121
|
+
FP.Lt(newPosZ, bounds.minZ) || FP.Gt(newPosZ, bounds.maxZ);
|
|
122
|
+
if (outOfBounds && this.config.ejectOnBoundsExit) {
|
|
123
|
+
this.physicsStore.arrays.ignorePhysics[physIndex] = 1;
|
|
124
|
+
this.physicsStore.arrays.velocityX[physIndex] = FP.ToRaw(FP._0);
|
|
125
|
+
this.physicsStore.arrays.velocityZ[physIndex] = FP.ToRaw(FP._0);
|
|
126
|
+
newPosX = FP.Clamp(newPosX, bounds.minX, bounds.maxX);
|
|
127
|
+
newPosZ = FP.Clamp(newPosZ, bounds.minZ, bounds.maxZ);
|
|
128
|
+
pendingBoundsExits.push({ entityId });
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
newPosX = FP.Clamp(newPosX, bounds.minX, bounds.maxX);
|
|
132
|
+
newPosZ = FP.Clamp(newPosZ, bounds.minZ, bounds.maxZ);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
fpPosXArr[transformIndex] = FP.ToRaw(newPosX);
|
|
136
|
+
fpPosZArr[transformIndex] = FP.ToRaw(newPosZ);
|
|
137
|
+
}
|
|
138
|
+
for (const evt of pendingBoundsExits) {
|
|
139
|
+
this.eventBus.emit(PhysicsEvents.BOUNDS_EXIT, evt);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
rebuildSpatialGrid() {
|
|
143
|
+
this.spatialGrid.clear();
|
|
144
|
+
const physLastX = this.physicsStore.arrays.lastX;
|
|
145
|
+
const physLastZ = this.physicsStore.arrays.lastZ;
|
|
146
|
+
const physRadius = this.physicsStore.arrays.radius;
|
|
147
|
+
const fpPosXArr = this.transformStore.arrays.fpPositionX;
|
|
148
|
+
const fpPosZArr = this.transformStore.arrays.fpPositionZ;
|
|
149
|
+
for (const entityId of this.physicsStore.entityIds()) {
|
|
150
|
+
const physIndex = this.physicsStore.indexOf(entityId);
|
|
151
|
+
const transformIndex = this.transformStore.indexOf(entityId);
|
|
152
|
+
if (transformIndex === -1)
|
|
153
|
+
continue;
|
|
154
|
+
const posX = FP.FromRaw(fpPosXArr[transformIndex]);
|
|
155
|
+
const posZ = FP.FromRaw(fpPosZArr[transformIndex]);
|
|
156
|
+
const radius = FP.FromRaw(physRadius[physIndex]);
|
|
157
|
+
physLastX[physIndex] = FP.ToFloat(posX);
|
|
158
|
+
physLastZ[physIndex] = FP.ToFloat(posZ);
|
|
159
|
+
this.spatialGrid.insert(entityId, posX, posZ, radius);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
detectAndResolve() {
|
|
163
|
+
const pairs = this.spatialGrid.queryPairs();
|
|
164
|
+
const physVelocityX = this.physicsStore.arrays.velocityX;
|
|
165
|
+
const physVelocityZ = this.physicsStore.arrays.velocityZ;
|
|
166
|
+
const physRadius = this.physicsStore.arrays.radius;
|
|
167
|
+
const physMass = this.physicsStore.arrays.mass;
|
|
168
|
+
const physIsStatic = this.physicsStore.arrays.isStatic;
|
|
169
|
+
const physIgnorePhysics = this.physicsStore.arrays.ignorePhysics;
|
|
170
|
+
const physRestitution = this.physicsStore.arrays.restitution;
|
|
171
|
+
const fpPosXArr = this.transformStore.arrays.fpPositionX;
|
|
172
|
+
const fpPosZArr = this.transformStore.arrays.fpPositionZ;
|
|
173
|
+
for (const [entityIdA, entityIdB] of pairs) {
|
|
174
|
+
const physIndexA = this.physicsStore.indexOf(entityIdA);
|
|
175
|
+
const physIndexB = this.physicsStore.indexOf(entityIdB);
|
|
176
|
+
if (physIndexA === -1 || physIndexB === -1)
|
|
177
|
+
continue;
|
|
178
|
+
if (physIgnorePhysics[physIndexA] === 1 || physIgnorePhysics[physIndexB] === 1) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (this.collisionFilter && !this.collisionFilter(entityIdA, entityIdB)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const transformIndexA = this.transformStore.indexOf(entityIdA);
|
|
185
|
+
const transformIndexB = this.transformStore.indexOf(entityIdB);
|
|
186
|
+
if (transformIndexA === -1 || transformIndexB === -1)
|
|
187
|
+
continue;
|
|
188
|
+
const posAX = FP.FromRaw(fpPosXArr[transformIndexA]);
|
|
189
|
+
const posAZ = FP.FromRaw(fpPosZArr[transformIndexA]);
|
|
190
|
+
const posBX = FP.FromRaw(fpPosXArr[transformIndexB]);
|
|
191
|
+
const posBZ = FP.FromRaw(fpPosZArr[transformIndexB]);
|
|
192
|
+
const radiusA = FP.FromRaw(physRadius[physIndexA]);
|
|
193
|
+
const radiusB = FP.FromRaw(physRadius[physIndexB]);
|
|
194
|
+
const manifold = NarrowPhase.circleVsCircle(posAX, posAZ, radiusA, posBX, posBZ, radiusB, entityIdA, entityIdB);
|
|
195
|
+
if (!manifold)
|
|
196
|
+
continue;
|
|
197
|
+
const restitutionA = FP.FromRaw(physRestitution[physIndexA]);
|
|
198
|
+
const restitutionB = FP.FromRaw(physRestitution[physIndexB]);
|
|
199
|
+
const restitution = (restitutionA === FP._0 && restitutionB === FP._0)
|
|
200
|
+
? FP._1
|
|
201
|
+
: FP.Div(FP.Add(restitutionA, restitutionB), FP.FromFloat(2));
|
|
202
|
+
this.resolveCollision(manifold, restitution, physIndexA, physIndexB, transformIndexA, transformIndexB, physVelocityX, physVelocityZ, physMass, physIsStatic, fpPosXArr, fpPosZArr);
|
|
203
|
+
const event = {
|
|
204
|
+
entityA: entityIdA,
|
|
205
|
+
entityB: entityIdB,
|
|
206
|
+
manifold,
|
|
207
|
+
};
|
|
208
|
+
this.eventBus.emit(PhysicsEvents.COLLISION, event);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
resolveCollision(manifold, restitution, physIndexA, physIndexB, transformIndexA, transformIndexB, physVelocityX, physVelocityZ, physMass, physIsStatic, fpPosXArr, fpPosZArr) {
|
|
212
|
+
const isStaticA = physIsStatic[physIndexA] === 1;
|
|
213
|
+
const isStaticB = physIsStatic[physIndexB] === 1;
|
|
214
|
+
if (isStaticA && isStaticB)
|
|
215
|
+
return;
|
|
216
|
+
const massA = FP.FromRaw(physMass[physIndexA]);
|
|
217
|
+
const massB = FP.FromRaw(physMass[physIndexB]);
|
|
218
|
+
const totalMass = FP.Add(massA, massB);
|
|
219
|
+
const nx = manifold.normalX;
|
|
220
|
+
const nz = manifold.normalZ;
|
|
221
|
+
const overlap = manifold.penetration;
|
|
222
|
+
const pushForce = FP.Mul(FP.Mul(overlap, this.config.pushStrength), restitution);
|
|
223
|
+
const ratioA = isStaticA ? FP._0 : (isStaticB ? FP._1 : FP.Div(massB, totalMass));
|
|
224
|
+
const ratioB = isStaticB ? FP._0 : (isStaticA ? FP._1 : FP.Div(massA, totalMass));
|
|
225
|
+
if (!isStaticA) {
|
|
226
|
+
const pushA = FP.Mul(pushForce, ratioA);
|
|
227
|
+
const velAx = FP.FromRaw(physVelocityX[physIndexA]);
|
|
228
|
+
const velAz = FP.FromRaw(physVelocityZ[physIndexA]);
|
|
229
|
+
physVelocityX[physIndexA] = FP.ToRaw(FP.Sub(velAx, FP.Mul(nx, pushA)));
|
|
230
|
+
physVelocityZ[physIndexA] = FP.ToRaw(FP.Sub(velAz, FP.Mul(nz, pushA)));
|
|
231
|
+
}
|
|
232
|
+
if (!isStaticB) {
|
|
233
|
+
const pushB = FP.Mul(pushForce, ratioB);
|
|
234
|
+
const velBx = FP.FromRaw(physVelocityX[physIndexB]);
|
|
235
|
+
const velBz = FP.FromRaw(physVelocityZ[physIndexB]);
|
|
236
|
+
physVelocityX[physIndexB] = FP.ToRaw(FP.Add(velBx, FP.Mul(nx, pushB)));
|
|
237
|
+
physVelocityZ[physIndexB] = FP.ToRaw(FP.Add(velBz, FP.Mul(nz, pushB)));
|
|
238
|
+
}
|
|
239
|
+
const separation = FP.Mul(overlap, SEPARATION_HALF);
|
|
240
|
+
if (!isStaticA) {
|
|
241
|
+
const sepA = FP.Mul(separation, ratioA);
|
|
242
|
+
const posAX = FP.FromRaw(fpPosXArr[transformIndexA]);
|
|
243
|
+
const posAZ = FP.FromRaw(fpPosZArr[transformIndexA]);
|
|
244
|
+
const newAX = FP.Sub(posAX, FP.Mul(nx, sepA));
|
|
245
|
+
const newAZ = FP.Sub(posAZ, FP.Mul(nz, sepA));
|
|
246
|
+
fpPosXArr[transformIndexA] = FP.ToRaw(newAX);
|
|
247
|
+
fpPosZArr[transformIndexA] = FP.ToRaw(newAZ);
|
|
248
|
+
}
|
|
249
|
+
if (!isStaticB) {
|
|
250
|
+
const sepB = FP.Mul(separation, ratioB);
|
|
251
|
+
const posBX = FP.FromRaw(fpPosXArr[transformIndexB]);
|
|
252
|
+
const posBZ = FP.FromRaw(fpPosZArr[transformIndexB]);
|
|
253
|
+
const newBX = FP.Add(posBX, FP.Mul(nx, sepB));
|
|
254
|
+
const newBZ = FP.Add(posBZ, FP.Mul(nz, sepB));
|
|
255
|
+
fpPosXArr[transformIndexB] = FP.ToRaw(newBX);
|
|
256
|
+
fpPosZArr[transformIndexB] = FP.ToRaw(newBZ);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
applyFriction() {
|
|
260
|
+
const physVelocityX = this.physicsStore.arrays.velocityX;
|
|
261
|
+
const physVelocityZ = this.physicsStore.arrays.velocityZ;
|
|
262
|
+
const physFriction = this.physicsStore.arrays.friction;
|
|
263
|
+
const physIsStatic = this.physicsStore.arrays.isStatic;
|
|
264
|
+
const physIgnore = this.physicsStore.arrays.ignorePhysics;
|
|
265
|
+
for (const entityId of this.physicsStore.entityIds()) {
|
|
266
|
+
const physIndex = this.physicsStore.indexOf(entityId);
|
|
267
|
+
if (physIsStatic[physIndex] === 1)
|
|
268
|
+
continue;
|
|
269
|
+
if (physIgnore[physIndex] === 1)
|
|
270
|
+
continue;
|
|
271
|
+
const frictionRaw = physFriction[physIndex];
|
|
272
|
+
const friction = frictionRaw === 0n
|
|
273
|
+
? this.config.defaultFriction
|
|
274
|
+
: FP.FromRaw(frictionRaw);
|
|
275
|
+
physVelocityX[physIndex] = FP.ToRaw(FP.Mul(FP.FromRaw(physVelocityX[physIndex]), friction));
|
|
276
|
+
physVelocityZ[physIndex] = FP.ToRaw(FP.Mul(FP.FromRaw(physVelocityZ[physIndex]), friction));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
getPhysicsStore() {
|
|
280
|
+
return this.physicsStore;
|
|
281
|
+
}
|
|
282
|
+
getTransformStore() {
|
|
283
|
+
return this.transformStore;
|
|
284
|
+
}
|
|
285
|
+
getConfig() {
|
|
286
|
+
return this.config;
|
|
287
|
+
}
|
|
288
|
+
getEntityPosition(entityId) {
|
|
289
|
+
const transformIndex = this.transformStore.indexOf(entityId);
|
|
290
|
+
if (transformIndex === -1) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
const physIndex = this.physicsStore.indexOf(entityId);
|
|
294
|
+
if (physIndex === -1) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
const fpPosXArr = this.transformStore.arrays.fpPositionX;
|
|
298
|
+
const fpPosZArr = this.transformStore.arrays.fpPositionZ;
|
|
299
|
+
return {
|
|
300
|
+
x: FP.FromRaw(fpPosXArr[transformIndex]),
|
|
301
|
+
z: FP.FromRaw(fpPosZArr[transformIndex]),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
getSpatialGrid() {
|
|
305
|
+
return this.spatialGrid;
|
|
306
|
+
}
|
|
307
|
+
getEventBus() {
|
|
308
|
+
return this.eventBus;
|
|
309
|
+
}
|
|
310
|
+
dispose() {
|
|
311
|
+
this.externalTickProvider?.stop();
|
|
312
|
+
this.providerStarted = false;
|
|
313
|
+
super.dispose();
|
|
314
|
+
this.spatialGrid.clear();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { PhysicsSystem } from './PhysicsSystem';
|
|
2
|
+
export { CollisionSystem } from './CollisionSystem';
|
|
3
|
+
export { InterpolationSystem } from './InterpolationSystem';
|
|
4
|
+
export type { InterpolatedTransformSample } from './InterpolationSystem';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/systems/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IPhysicsTickProvider } from './IPhysicsTickProvider';
|
|
2
|
+
export interface AutonomousProviderOptions {
|
|
3
|
+
maxSteps?: number;
|
|
4
|
+
isSettled: () => boolean;
|
|
5
|
+
onSettled: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare class AutonomousPhysicsTickProvider implements IPhysicsTickProvider {
|
|
8
|
+
private running;
|
|
9
|
+
private steps;
|
|
10
|
+
private onStepFn;
|
|
11
|
+
private readonly options;
|
|
12
|
+
constructor(options: AutonomousProviderOptions);
|
|
13
|
+
start(onStep: () => void): void;
|
|
14
|
+
stop(): void;
|
|
15
|
+
private schedule;
|
|
16
|
+
private tick;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=AutonomousPhysicsTickProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AutonomousPhysicsTickProvider.d.ts","sourceRoot":"","sources":["../../src/tick/AutonomousPhysicsTickProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,MAAM,WAAW,yBAAyB;IAExC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,SAAS,EAAE,MAAM,OAAO,CAAC;IAEzB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,qBAAa,6BAA8B,YAAW,oBAAoB;IACxE,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;gBAElD,OAAO,EAAE,yBAAyB;IAI9C,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI;IAQ/B,IAAI,IAAI,IAAI;IAMZ,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,IAAI;CAWb"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export class AutonomousPhysicsTickProvider {
|
|
2
|
+
running = false;
|
|
3
|
+
steps = 0;
|
|
4
|
+
onStepFn = null;
|
|
5
|
+
options;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = { maxSteps: 10_000, ...options };
|
|
8
|
+
}
|
|
9
|
+
start(onStep) {
|
|
10
|
+
this.stop();
|
|
11
|
+
this.running = true;
|
|
12
|
+
this.steps = 0;
|
|
13
|
+
this.onStepFn = onStep;
|
|
14
|
+
this.schedule();
|
|
15
|
+
}
|
|
16
|
+
stop() {
|
|
17
|
+
this.running = false;
|
|
18
|
+
this.onStepFn = null;
|
|
19
|
+
this.steps = 0;
|
|
20
|
+
}
|
|
21
|
+
schedule() {
|
|
22
|
+
const next = typeof setImmediate !== 'undefined'
|
|
23
|
+
? (fn) => setImmediate(fn)
|
|
24
|
+
: (fn) => setTimeout(fn, 0);
|
|
25
|
+
next(() => this.tick());
|
|
26
|
+
}
|
|
27
|
+
tick() {
|
|
28
|
+
if (!this.running)
|
|
29
|
+
return;
|
|
30
|
+
this.onStepFn();
|
|
31
|
+
this.steps++;
|
|
32
|
+
if (this.options.isSettled() || this.steps >= this.options.maxSteps) {
|
|
33
|
+
this.running = false;
|
|
34
|
+
this.options.onSettled();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.schedule();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IPhysicsTickProvider } from './IPhysicsTickProvider';
|
|
2
|
+
export declare class ExternalPhysicsTickProvider implements IPhysicsTickProvider {
|
|
3
|
+
private onStepFn;
|
|
4
|
+
start(onStep: () => void): void;
|
|
5
|
+
stop(): void;
|
|
6
|
+
tick(): void;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=ExternalPhysicsTickProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalPhysicsTickProvider.d.ts","sourceRoot":"","sources":["../../src/tick/ExternalPhysicsTickProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,qBAAa,2BAA4B,YAAW,oBAAoB;IACtE,OAAO,CAAC,QAAQ,CAA6B;IAC7C,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI;IAC/B,IAAI,IAAI,IAAI;IAEZ,IAAI,IAAI,IAAI;CACb"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IPhysicsTickProvider.d.ts","sourceRoot":"","sources":["../../src/tick/IPhysicsTickProvider.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,oBAAoB;IAKnC,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAGhC,IAAI,IAAI,IAAI,CAAC;CACd"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { IPhysicsTickProvider } from './IPhysicsTickProvider';
|
|
2
|
+
export { AutonomousPhysicsTickProvider } from './AutonomousPhysicsTickProvider';
|
|
3
|
+
export type { AutonomousProviderOptions } from './AutonomousPhysicsTickProvider';
|
|
4
|
+
export { ExternalPhysicsTickProvider } from './ExternalPhysicsTickProvider';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tick/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,YAAY,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AACjF,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FixedPoint } from '@phalanx-engine/math';
|
|
2
|
+
import type { CollisionManifold } from './collision/CollisionManifold';
|
|
3
|
+
export interface CollisionFilter {
|
|
4
|
+
category: number;
|
|
5
|
+
mask: number;
|
|
6
|
+
}
|
|
7
|
+
export interface CollisionEvent {
|
|
8
|
+
entityA: number;
|
|
9
|
+
entityB: number;
|
|
10
|
+
manifold: CollisionManifold;
|
|
11
|
+
}
|
|
12
|
+
export interface BoundsExitEvent {
|
|
13
|
+
entityId: number;
|
|
14
|
+
}
|
|
15
|
+
export interface PhysicsConfig {
|
|
16
|
+
tickDt: FixedPoint;
|
|
17
|
+
subSteps: number;
|
|
18
|
+
maxVelocity: FixedPoint;
|
|
19
|
+
defaultFriction: FixedPoint;
|
|
20
|
+
pushStrength: FixedPoint;
|
|
21
|
+
gridCellSize: FixedPoint;
|
|
22
|
+
worldBounds?: {
|
|
23
|
+
minX: FixedPoint;
|
|
24
|
+
minZ: FixedPoint;
|
|
25
|
+
maxX: FixedPoint;
|
|
26
|
+
maxZ: FixedPoint;
|
|
27
|
+
};
|
|
28
|
+
ejectOnBoundsExit?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface PhysicsBodyConfig {
|
|
31
|
+
radius: FixedPoint;
|
|
32
|
+
mass?: FixedPoint;
|
|
33
|
+
isStatic?: boolean;
|
|
34
|
+
restitution?: FixedPoint;
|
|
35
|
+
friction?: FixedPoint;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAQvE,MAAM,WAAW,eAAe;IAE9B,QAAQ,EAAE,MAAM,CAAC;IAEjB,IAAI,EAAE,MAAM,CAAC;CACd;AAKD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,iBAAiB,CAAC;CAC7B;AAKD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,MAAM,WAAW,aAAa;IAE5B,MAAM,EAAE,UAAU,CAAC;IAEnB,QAAQ,EAAE,MAAM,CAAC;IAEjB,WAAW,EAAE,UAAU,CAAC;IAExB,eAAe,EAAE,UAAU,CAAC;IAE5B,YAAY,EAAE,UAAU,CAAC;IAEzB,YAAY,EAAE,UAAU,CAAC;IAEzB,WAAW,CAAC,EAAE;QACZ,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,UAAU,CAAC;KAClB,CAAC;IAMF,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAKD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,EAAE,UAAU,CAAC;CACvB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phalanx-engine/physics",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deterministic fixed-point physics engine for Phalanx Engine - spatial hashing, collision detection, and resolution",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"@phalanx-engine/ecs": "^0.1.0",
|
|
16
|
+
"@phalanx-engine/math": "^0.1.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "~5.9.3",
|
|
20
|
+
"vitest": "^1.0.0",
|
|
21
|
+
"@phalanx-engine/ecs": "0.1.0",
|
|
22
|
+
"@phalanx-engine/math": "0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"physics",
|
|
26
|
+
"deterministic",
|
|
27
|
+
"fixed-point",
|
|
28
|
+
"collision-detection",
|
|
29
|
+
"spatial-hash",
|
|
30
|
+
"ecs",
|
|
31
|
+
"phalanx"
|
|
32
|
+
],
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/phaeton-forge/phalanx-engine.git",
|
|
38
|
+
"directory": "phalanx-physics"
|
|
39
|
+
},
|
|
40
|
+
"bugs": "https://github.com/phaeton-forge/phalanx-engine/issues",
|
|
41
|
+
"homepage": "https://github.com/phaeton-forge/phalanx-engine/tree/main/phalanx-physics#readme",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"README.md"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc",
|
|
51
|
+
"clean": "rm -rf dist",
|
|
52
|
+
"rebuild": "npm run clean && npm run build",
|
|
53
|
+
"test": "vitest run"
|
|
54
|
+
}
|
|
55
|
+
}
|