@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.
Files changed (65) hide show
  1. package/README.md +437 -0
  2. package/dist/PhysicsWorld.d.ts +35 -0
  3. package/dist/PhysicsWorld.d.ts.map +1 -0
  4. package/dist/PhysicsWorld.js +112 -0
  5. package/dist/PhysicsWorldConfig.d.ts +21 -0
  6. package/dist/PhysicsWorldConfig.d.ts.map +1 -0
  7. package/dist/PhysicsWorldConfig.js +1 -0
  8. package/dist/collision/CollisionManifold.d.ts +9 -0
  9. package/dist/collision/CollisionManifold.d.ts.map +1 -0
  10. package/dist/collision/CollisionManifold.js +1 -0
  11. package/dist/collision/NarrowPhase.d.ts +8 -0
  12. package/dist/collision/NarrowPhase.d.ts.map +1 -0
  13. package/dist/collision/NarrowPhase.js +112 -0
  14. package/dist/collision/SpatialHashGrid.d.ts +19 -0
  15. package/dist/collision/SpatialHashGrid.d.ts.map +1 -0
  16. package/dist/collision/SpatialHashGrid.js +125 -0
  17. package/dist/collision/index.d.ts +4 -0
  18. package/dist/collision/index.d.ts.map +1 -0
  19. package/dist/collision/index.js +2 -0
  20. package/dist/components/InterpolationComponent.d.ts +15 -0
  21. package/dist/components/InterpolationComponent.d.ts.map +1 -0
  22. package/dist/components/InterpolationComponent.js +32 -0
  23. package/dist/components/PhysicsBodyComponent.d.ts +53 -0
  24. package/dist/components/PhysicsBodyComponent.d.ts.map +1 -0
  25. package/dist/components/PhysicsBodyComponent.js +157 -0
  26. package/dist/components/TransformComponent.d.ts +32 -0
  27. package/dist/components/TransformComponent.d.ts.map +1 -0
  28. package/dist/components/TransformComponent.js +75 -0
  29. package/dist/components/index.d.ts +4 -0
  30. package/dist/components/index.d.ts.map +1 -0
  31. package/dist/components/index.js +3 -0
  32. package/dist/events.d.ts +7 -0
  33. package/dist/events.d.ts.map +1 -0
  34. package/dist/events.js +6 -0
  35. package/dist/index.d.ts +17 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +8 -0
  38. package/dist/systems/CollisionSystem.d.ts +20 -0
  39. package/dist/systems/CollisionSystem.d.ts.map +1 -0
  40. package/dist/systems/CollisionSystem.js +150 -0
  41. package/dist/systems/InterpolationSystem.d.ts +28 -0
  42. package/dist/systems/InterpolationSystem.d.ts.map +1 -0
  43. package/dist/systems/InterpolationSystem.js +104 -0
  44. package/dist/systems/PhysicsSystem.d.ts +41 -0
  45. package/dist/systems/PhysicsSystem.d.ts.map +1 -0
  46. package/dist/systems/PhysicsSystem.js +316 -0
  47. package/dist/systems/index.d.ts +5 -0
  48. package/dist/systems/index.d.ts.map +1 -0
  49. package/dist/systems/index.js +3 -0
  50. package/dist/tick/AutonomousPhysicsTickProvider.d.ts +18 -0
  51. package/dist/tick/AutonomousPhysicsTickProvider.d.ts.map +1 -0
  52. package/dist/tick/AutonomousPhysicsTickProvider.js +39 -0
  53. package/dist/tick/ExternalPhysicsTickProvider.d.ts +8 -0
  54. package/dist/tick/ExternalPhysicsTickProvider.d.ts.map +1 -0
  55. package/dist/tick/ExternalPhysicsTickProvider.js +6 -0
  56. package/dist/tick/IPhysicsTickProvider.d.ts +5 -0
  57. package/dist/tick/IPhysicsTickProvider.d.ts.map +1 -0
  58. package/dist/tick/IPhysicsTickProvider.js +1 -0
  59. package/dist/tick/index.d.ts +5 -0
  60. package/dist/tick/index.d.ts.map +1 -0
  61. package/dist/tick/index.js +2 -0
  62. package/dist/types.d.ts +37 -0
  63. package/dist/types.d.ts.map +1 -0
  64. package/dist/types.js +1 -0
  65. package/package.json +55 -0
@@ -0,0 +1,7 @@
1
+ export declare const PhysicsEvents: {
2
+ readonly COLLISION: "physics:collision";
3
+ readonly TRIGGER_ENTER: "physics:trigger:enter";
4
+ readonly TRIGGER_EXIT: "physics:trigger:exit";
5
+ readonly BOUNDS_EXIT: "physics:bounds:exit";
6
+ };
7
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa;;;;;CAMhB,CAAC"}
package/dist/events.js ADDED
@@ -0,0 +1,6 @@
1
+ export const PhysicsEvents = {
2
+ COLLISION: 'physics:collision',
3
+ TRIGGER_ENTER: 'physics:trigger:enter',
4
+ TRIGGER_EXIT: 'physics:trigger:exit',
5
+ BOUNDS_EXIT: 'physics:bounds:exit',
6
+ };
@@ -0,0 +1,17 @@
1
+ export { PhysicsBodyComponent, PhysicsSoASchema, PHYSICS_BODY_COMPONENT_TYPE, TransformComponent, TransformSoASchema, TRANSFORM_COMPONENT_TYPE, InterpolationComponent, INTERPOLATION_COMPONENT_TYPE, } from './components';
2
+ export type { PhysicsBodyConfig } from './types';
3
+ export { SpatialHashGrid } from './collision/SpatialHashGrid';
4
+ export { NarrowPhase } from './collision/NarrowPhase';
5
+ export type { CollisionManifold } from './collision/CollisionManifold';
6
+ export { PhysicsSystem, InterpolationSystem } from './systems';
7
+ export type { InterpolatedTransformSample } from './systems';
8
+ export { PhysicsWorld } from './PhysicsWorld';
9
+ export type { PhysicsWorldConfig } from './PhysicsWorldConfig';
10
+ export type { CollisionFilter, CollisionEvent, PhysicsConfig } from './types';
11
+ export { PhysicsEvents } from './events';
12
+ export type { IPhysicsTickProvider } from './tick/IPhysicsTickProvider';
13
+ export { AutonomousPhysicsTickProvider } from './tick/AutonomousPhysicsTickProvider';
14
+ export type { AutonomousProviderOptions } from './tick/AutonomousPhysicsTickProvider';
15
+ export { ExternalPhysicsTickProvider } from './tick/ExternalPhysicsTickProvider';
16
+ export type { BoundsExitEvent } from './types';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,2BAA2B,EAC3B,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,4BAA4B,GAC7B,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAGjD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,YAAY,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAGvE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC/D,YAAY,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC;AAG7D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,YAAY,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,6BAA6B,EAAE,MAAM,sCAAsC,CAAC;AACrF,YAAY,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AACtF,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AACjF,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { PhysicsBodyComponent, PhysicsSoASchema, PHYSICS_BODY_COMPONENT_TYPE, TransformComponent, TransformSoASchema, TRANSFORM_COMPONENT_TYPE, InterpolationComponent, INTERPOLATION_COMPONENT_TYPE, } from './components';
2
+ export { SpatialHashGrid } from './collision/SpatialHashGrid';
3
+ export { NarrowPhase } from './collision/NarrowPhase';
4
+ export { PhysicsSystem, InterpolationSystem } from './systems';
5
+ export { PhysicsWorld } from './PhysicsWorld';
6
+ export { PhysicsEvents } from './events';
7
+ export { AutonomousPhysicsTickProvider } from './tick/AutonomousPhysicsTickProvider';
8
+ export { ExternalPhysicsTickProvider } from './tick/ExternalPhysicsTickProvider';
@@ -0,0 +1,20 @@
1
+ import { GameSystem, type SystemContext } from '@phalanx-engine/ecs';
2
+ import { type FixedPoint } from '@phalanx-engine/math';
3
+ import { SpatialHashGrid } from '../collision/SpatialHashGrid';
4
+ export declare class CollisionSystem extends GameSystem {
5
+ private physicsStore;
6
+ private transformStore;
7
+ private readonly spatialGrid;
8
+ private pushStrength;
9
+ private collisionFilter;
10
+ constructor(gridCellSize: FixedPoint, pushStrength?: FixedPoint);
11
+ setCollisionFilter(filter: (entityA: number, entityB: number) => boolean): void;
12
+ init(context: SystemContext): void;
13
+ getSpatialGrid(): SpatialHashGrid;
14
+ processTick(_tick: number): void;
15
+ private rebuildSpatialGrid;
16
+ private detectAndResolve;
17
+ private resolveCollision;
18
+ dispose(): void;
19
+ }
20
+ //# sourceMappingURL=CollisionSystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollisionSystem.d.ts","sourceRoot":"","sources":["../../src/systems/CollisionSystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7F,OAAO,EAAM,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAe/D,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,OAAO,CAAC,YAAY,CAAyD;IAC7E,OAAO,CAAC,cAAc,CAA2D;IACjF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkB;IAC9C,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,eAAe,CAAgE;gBAE3E,YAAY,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,UAAU;IAUxD,kBAAkB,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI;IAItE,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAO3C,cAAc,IAAI,eAAe;IAIxB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQhD,OAAO,CAAC,kBAAkB;IA8B1B,OAAO,CAAC,gBAAgB;IAyExB,OAAO,CAAC,gBAAgB;IAkER,OAAO,IAAI,IAAI;CAIhC"}
@@ -0,0 +1,150 @@
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 CollisionSystem extends GameSystem {
10
+ physicsStore;
11
+ transformStore;
12
+ spatialGrid;
13
+ pushStrength;
14
+ collisionFilter = null;
15
+ constructor(gridCellSize, pushStrength) {
16
+ super();
17
+ this.spatialGrid = new SpatialHashGrid(gridCellSize);
18
+ this.pushStrength = pushStrength ?? FP.FromFloat(15.0);
19
+ }
20
+ setCollisionFilter(filter) {
21
+ this.collisionFilter = filter;
22
+ }
23
+ init(context) {
24
+ super.init(context);
25
+ this.physicsStore = this.entityManager.getOrCreateSoAStore(PhysicsSoASchema);
26
+ this.transformStore = this.entityManager.getOrCreateSoAStore(TransformSoASchema);
27
+ }
28
+ getSpatialGrid() {
29
+ return this.spatialGrid;
30
+ }
31
+ processTick(_tick) {
32
+ this.rebuildSpatialGrid();
33
+ this.detectAndResolve();
34
+ }
35
+ rebuildSpatialGrid() {
36
+ this.spatialGrid.clear();
37
+ const physLastX = this.physicsStore.arrays.lastX;
38
+ const physLastZ = this.physicsStore.arrays.lastZ;
39
+ const physRadius = this.physicsStore.arrays.radius;
40
+ const fpPosXArr = this.transformStore.arrays.fpPositionX;
41
+ const fpPosZArr = this.transformStore.arrays.fpPositionZ;
42
+ for (const entityId of this.physicsStore.entityIds()) {
43
+ const physIndex = this.physicsStore.indexOf(entityId);
44
+ const transformIndex = this.transformStore.indexOf(entityId);
45
+ if (transformIndex === -1)
46
+ continue;
47
+ const posX = FP.FromRaw(fpPosXArr[transformIndex]);
48
+ const posZ = FP.FromRaw(fpPosZArr[transformIndex]);
49
+ const radius = FP.FromRaw(physRadius[physIndex]);
50
+ physLastX[physIndex] = FP.ToFloat(posX);
51
+ physLastZ[physIndex] = FP.ToFloat(posZ);
52
+ this.spatialGrid.insert(entityId, posX, posZ, radius);
53
+ }
54
+ }
55
+ detectAndResolve() {
56
+ const pairs = this.spatialGrid.queryPairs();
57
+ const physVelocityX = this.physicsStore.arrays.velocityX;
58
+ const physVelocityZ = this.physicsStore.arrays.velocityZ;
59
+ const physRadius = this.physicsStore.arrays.radius;
60
+ const physMass = this.physicsStore.arrays.mass;
61
+ const physIsStatic = this.physicsStore.arrays.isStatic;
62
+ const physIgnorePhysics = this.physicsStore.arrays.ignorePhysics;
63
+ const fpPosXArr = this.transformStore.arrays.fpPositionX;
64
+ const fpPosZArr = this.transformStore.arrays.fpPositionZ;
65
+ for (const [entityIdA, entityIdB] of pairs) {
66
+ const physIndexA = this.physicsStore.indexOf(entityIdA);
67
+ const physIndexB = this.physicsStore.indexOf(entityIdB);
68
+ if (physIndexA === -1 || physIndexB === -1)
69
+ continue;
70
+ if (physIgnorePhysics[physIndexA] === 1 || physIgnorePhysics[physIndexB] === 1) {
71
+ continue;
72
+ }
73
+ if (this.collisionFilter && !this.collisionFilter(entityIdA, entityIdB)) {
74
+ continue;
75
+ }
76
+ const transformIndexA = this.transformStore.indexOf(entityIdA);
77
+ const transformIndexB = this.transformStore.indexOf(entityIdB);
78
+ if (transformIndexA === -1 || transformIndexB === -1)
79
+ continue;
80
+ const posAX = FP.FromRaw(fpPosXArr[transformIndexA]);
81
+ const posAZ = FP.FromRaw(fpPosZArr[transformIndexA]);
82
+ const posBX = FP.FromRaw(fpPosXArr[transformIndexB]);
83
+ const posBZ = FP.FromRaw(fpPosZArr[transformIndexB]);
84
+ const radiusA = FP.FromRaw(physRadius[physIndexA]);
85
+ const radiusB = FP.FromRaw(physRadius[physIndexB]);
86
+ const manifold = NarrowPhase.circleVsCircle(posAX, posAZ, radiusA, posBX, posBZ, radiusB, entityIdA, entityIdB);
87
+ if (!manifold)
88
+ continue;
89
+ this.resolveCollision(manifold, physIndexA, physIndexB, transformIndexA, transformIndexB, physVelocityX, physVelocityZ, physMass, physIsStatic, fpPosXArr, fpPosZArr);
90
+ const event = {
91
+ entityA: entityIdA,
92
+ entityB: entityIdB,
93
+ manifold,
94
+ };
95
+ this.eventBus.emit(PhysicsEvents.COLLISION, event);
96
+ }
97
+ }
98
+ resolveCollision(manifold, physIndexA, physIndexB, transformIndexA, transformIndexB, physVelocityX, physVelocityZ, physMass, physIsStatic, fpPosXArr, fpPosZArr) {
99
+ const isStaticA = physIsStatic[physIndexA] === 1;
100
+ const isStaticB = physIsStatic[physIndexB] === 1;
101
+ if (isStaticA && isStaticB)
102
+ return;
103
+ const massA = FP.FromRaw(physMass[physIndexA]);
104
+ const massB = FP.FromRaw(physMass[physIndexB]);
105
+ const totalMass = FP.Add(massA, massB);
106
+ const nx = manifold.normalX;
107
+ const nz = manifold.normalZ;
108
+ const overlap = manifold.penetration;
109
+ const pushForce = FP.Mul(overlap, this.pushStrength);
110
+ const ratioA = isStaticA ? FP._0 : (isStaticB ? FP._1 : FP.Div(massB, totalMass));
111
+ const ratioB = isStaticB ? FP._0 : (isStaticA ? FP._1 : FP.Div(massA, totalMass));
112
+ if (!isStaticA) {
113
+ const pushA = FP.Mul(pushForce, ratioA);
114
+ const velAx = FP.FromRaw(physVelocityX[physIndexA]);
115
+ const velAz = FP.FromRaw(physVelocityZ[physIndexA]);
116
+ physVelocityX[physIndexA] = FP.ToRaw(FP.Sub(velAx, FP.Mul(nx, pushA)));
117
+ physVelocityZ[physIndexA] = FP.ToRaw(FP.Sub(velAz, FP.Mul(nz, pushA)));
118
+ }
119
+ if (!isStaticB) {
120
+ const pushB = FP.Mul(pushForce, ratioB);
121
+ const velBx = FP.FromRaw(physVelocityX[physIndexB]);
122
+ const velBz = FP.FromRaw(physVelocityZ[physIndexB]);
123
+ physVelocityX[physIndexB] = FP.ToRaw(FP.Add(velBx, FP.Mul(nx, pushB)));
124
+ physVelocityZ[physIndexB] = FP.ToRaw(FP.Add(velBz, FP.Mul(nz, pushB)));
125
+ }
126
+ const separation = FP.Mul(overlap, SEPARATION_HALF);
127
+ if (!isStaticA) {
128
+ const sepA = FP.Mul(separation, ratioA);
129
+ const posAX = FP.FromRaw(fpPosXArr[transformIndexA]);
130
+ const posAZ = FP.FromRaw(fpPosZArr[transformIndexA]);
131
+ const newAX = FP.Sub(posAX, FP.Mul(nx, sepA));
132
+ const newAZ = FP.Sub(posAZ, FP.Mul(nz, sepA));
133
+ fpPosXArr[transformIndexA] = FP.ToRaw(newAX);
134
+ fpPosZArr[transformIndexA] = FP.ToRaw(newAZ);
135
+ }
136
+ if (!isStaticB) {
137
+ const sepB = FP.Mul(separation, ratioB);
138
+ const posBX = FP.FromRaw(fpPosXArr[transformIndexB]);
139
+ const posBZ = FP.FromRaw(fpPosZArr[transformIndexB]);
140
+ const newBX = FP.Add(posBX, FP.Mul(nx, sepB));
141
+ const newBZ = FP.Add(posBZ, FP.Mul(nz, sepB));
142
+ fpPosXArr[transformIndexB] = FP.ToRaw(newBX);
143
+ fpPosZArr[transformIndexB] = FP.ToRaw(newBZ);
144
+ }
145
+ }
146
+ dispose() {
147
+ super.dispose();
148
+ this.spatialGrid.clear();
149
+ }
150
+ }
@@ -0,0 +1,28 @@
1
+ import { GameSystem, type CommandsBatch, type IAfterTick, type IBeforeFrame, type IBeforeTick, type SystemContext } from '@phalanx-engine/ecs';
2
+ export interface InterpolatedTransformSample {
3
+ position: {
4
+ x: number;
5
+ y: number;
6
+ z: number;
7
+ };
8
+ rotation: {
9
+ x: number;
10
+ y: number;
11
+ z: number;
12
+ };
13
+ }
14
+ export declare class InterpolationSystem extends GameSystem implements IBeforeTick, IAfterTick, IBeforeFrame {
15
+ private transformStore;
16
+ private readonly interpolatedSamples;
17
+ private readonly capturedEntities;
18
+ init(context: SystemContext): void;
19
+ beforeTick(_tick: number, _commands: CommandsBatch): void;
20
+ afterTick(_tick: number): void;
21
+ beforeFrame(alpha: number, _dt: number): void;
22
+ snapshot(): void;
23
+ capture(): void;
24
+ interpolate(alpha: number): void;
25
+ getInterpolatedTransform(entityId: number): InterpolatedTransformSample | undefined;
26
+ private readFpVector3;
27
+ }
28
+ //# sourceMappingURL=InterpolationSystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InterpolationSystem.d.ts","sourceRoot":"","sources":["../../src/systems/InterpolationSystem.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,WAAW,EAEhB,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAQ7B,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAoBD,qBAAa,mBACX,SAAQ,UACR,YAAW,WAAW,EAAE,UAAU,EAAE,YAAY;IAEhD,OAAO,CAAC,cAAc,CAA2D;IACjF,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAkD;IAEtF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IAEtC,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAK3C,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,GAAG,IAAI;IAIzD,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI9B,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAI7C,QAAQ,IAAI,IAAI;IAOhB,OAAO,IAAI,IAAI;IA6Cf,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAiChC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,2BAA2B,GAAG,SAAS;IAI1F,OAAO,CAAC,aAAa;CAatB"}
@@ -0,0 +1,104 @@
1
+ import { GameSystem, } from '@phalanx-engine/ecs';
2
+ import { FP, FPVector3 } from '@phalanx-engine/math';
3
+ import { INTERPOLATION_COMPONENT_TYPE, } from '../components';
4
+ import { TRANSFORM_COMPONENT_TYPE, TransformSoASchema } from '../components';
5
+ function lerpAngle(from, to, t) {
6
+ const clamped = Math.max(0, Math.min(1, t));
7
+ let delta = to - from;
8
+ if (delta > Math.PI)
9
+ delta -= 2 * Math.PI;
10
+ if (delta < -Math.PI)
11
+ delta += 2 * Math.PI;
12
+ return from + delta * clamped;
13
+ }
14
+ function lerpScalar(from, to, t) {
15
+ const clamped = Math.max(0, Math.min(1, t));
16
+ return from + (to - from) * clamped;
17
+ }
18
+ export class InterpolationSystem extends GameSystem {
19
+ transformStore;
20
+ interpolatedSamples = new Map();
21
+ capturedEntities = new Set();
22
+ init(context) {
23
+ super.init(context);
24
+ this.transformStore = this.entityManager.getOrCreateSoAStore(TransformSoASchema);
25
+ }
26
+ beforeTick(_tick, _commands) {
27
+ this.snapshot();
28
+ }
29
+ afterTick(_tick) {
30
+ this.capture();
31
+ }
32
+ beforeFrame(alpha, _dt) {
33
+ this.interpolate(alpha);
34
+ }
35
+ snapshot() {
36
+ const entities = this.entityManager.queryEntities(INTERPOLATION_COMPONENT_TYPE);
37
+ for (const entity of entities) {
38
+ entity.getComponent(INTERPOLATION_COMPONENT_TYPE)?.snapshot();
39
+ }
40
+ }
41
+ capture() {
42
+ const entities = this.entityManager.queryEntities(INTERPOLATION_COMPONENT_TYPE, TRANSFORM_COMPONENT_TYPE);
43
+ const activeEntityIds = new Set();
44
+ for (const entity of entities) {
45
+ activeEntityIds.add(entity.id);
46
+ const interpolation = entity.getComponent(INTERPOLATION_COMPONENT_TYPE);
47
+ const transformIndex = this.transformStore.indexOf(entity.id);
48
+ if (!interpolation || transformIndex === -1)
49
+ continue;
50
+ const fpPosition = this.readFpVector3(transformIndex, 'fpPositionX', 'fpPositionY', 'fpPositionZ');
51
+ const fpRotation = this.readFpVector3(transformIndex, 'fpRotationX', 'fpRotationY', 'fpRotationZ');
52
+ if (!this.capturedEntities.has(entity.id)) {
53
+ interpolation.capture(fpPosition, fpRotation);
54
+ interpolation.snapshot();
55
+ this.capturedEntities.add(entity.id);
56
+ continue;
57
+ }
58
+ interpolation.capture(fpPosition, fpRotation);
59
+ }
60
+ for (const entityId of this.capturedEntities) {
61
+ if (!activeEntityIds.has(entityId)) {
62
+ this.capturedEntities.delete(entityId);
63
+ this.interpolatedSamples.delete(entityId);
64
+ }
65
+ }
66
+ }
67
+ interpolate(alpha) {
68
+ const clampedAlpha = Math.max(0, Math.min(1, alpha));
69
+ this.interpolatedSamples.clear();
70
+ const entities = this.entityManager.queryEntities(INTERPOLATION_COMPONENT_TYPE, TRANSFORM_COMPONENT_TYPE);
71
+ for (const entity of entities) {
72
+ const interpolation = entity.getComponent(INTERPOLATION_COMPONENT_TYPE);
73
+ if (!interpolation)
74
+ continue;
75
+ const previousPosition = FPVector3.ToFloat(interpolation.previousFpPosition);
76
+ const currentPosition = FPVector3.ToFloat(interpolation.currentFpPosition);
77
+ const previousRotation = FPVector3.ToFloat(interpolation.previousFpRotation);
78
+ const currentRotation = FPVector3.ToFloat(interpolation.currentFpRotation);
79
+ this.interpolatedSamples.set(entity.id, {
80
+ position: {
81
+ x: lerpScalar(previousPosition.x, currentPosition.x, clampedAlpha),
82
+ y: lerpScalar(previousPosition.y, currentPosition.y, clampedAlpha),
83
+ z: lerpScalar(previousPosition.z, currentPosition.z, clampedAlpha),
84
+ },
85
+ rotation: {
86
+ x: lerpAngle(previousRotation.x, currentRotation.x, clampedAlpha),
87
+ y: lerpAngle(previousRotation.y, currentRotation.y, clampedAlpha),
88
+ z: lerpAngle(previousRotation.z, currentRotation.z, clampedAlpha),
89
+ },
90
+ });
91
+ }
92
+ }
93
+ getInterpolatedTransform(entityId) {
94
+ return this.interpolatedSamples.get(entityId);
95
+ }
96
+ readFpVector3(index, xKey, yKey, zKey) {
97
+ const arrays = this.transformStore.arrays;
98
+ return {
99
+ x: FP.FromRaw(arrays[xKey][index]),
100
+ y: FP.FromRaw(arrays[yKey][index]),
101
+ z: FP.FromRaw(arrays[zKey][index]),
102
+ };
103
+ }
104
+ }
@@ -0,0 +1,41 @@
1
+ import { GameSystem, type EventBus, type SoAComponentStore, type SystemContext } from '@phalanx-engine/ecs';
2
+ import { type FixedPoint } from '@phalanx-engine/math';
3
+ import { PhysicsSoASchema } from '../components/PhysicsBodyComponent';
4
+ import { TransformSoASchema } from '../components/TransformComponent';
5
+ import { SpatialHashGrid } from '../collision/SpatialHashGrid';
6
+ import type { PhysicsConfig } from '../types';
7
+ import type { IPhysicsTickProvider } from '../tick/IPhysicsTickProvider';
8
+ export declare class PhysicsSystem extends GameSystem {
9
+ private physicsStore;
10
+ private transformStore;
11
+ private config;
12
+ private readonly spatialGrid;
13
+ private collisionFilter;
14
+ private externalTickProvider;
15
+ private providerStarted;
16
+ constructor(config: PhysicsConfig);
17
+ init(context: SystemContext): void;
18
+ private tryStartProvider;
19
+ setCollisionFilter(filter: (entityA: number, entityB: number) => boolean): void;
20
+ step(): void;
21
+ processTick(_tick: number): void;
22
+ setTickProvider(provider: IPhysicsTickProvider): void;
23
+ applyImpulse(entityId: number, vx: FixedPoint, vz: FixedPoint): void;
24
+ isSettled(threshold?: FixedPoint): boolean;
25
+ private applyVelocities;
26
+ private rebuildSpatialGrid;
27
+ private detectAndResolve;
28
+ private resolveCollision;
29
+ private applyFriction;
30
+ getPhysicsStore(): SoAComponentStore<typeof PhysicsSoASchema.definition>;
31
+ getTransformStore(): SoAComponentStore<typeof TransformSoASchema.definition>;
32
+ getConfig(): PhysicsConfig;
33
+ getEntityPosition(entityId: number): {
34
+ x: FixedPoint;
35
+ z: FixedPoint;
36
+ } | undefined;
37
+ getSpatialGrid(): SpatialHashGrid;
38
+ getEventBus(): EventBus;
39
+ dispose(): void;
40
+ }
41
+ //# sourceMappingURL=PhysicsSystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PhysicsSystem.d.ts","sourceRoot":"","sources":["../../src/systems/PhysicsSystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,KAAK,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC5G,OAAO,EAAM,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAI/D,OAAO,KAAK,EAAE,aAAa,EAAmC,MAAM,UAAU,CAAC;AAC/E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAgBzE,qBAAa,aAAc,SAAQ,UAAU;IAC3C,OAAO,CAAC,YAAY,CAAyD;IAC7E,OAAO,CAAC,cAAc,CAA2D;IACjF,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkB;IAC9C,OAAO,CAAC,eAAe,CAAgE;IACvF,OAAO,CAAC,oBAAoB,CAAqC;IACjE,OAAO,CAAC,eAAe,CAAS;gBAEpB,MAAM,EAAE,aAAa;IAMjB,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAWlD,OAAO,CAAC,gBAAgB;IAejB,kBAAkB,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI;IAQ/E,IAAI,IAAI,IAAI;IAUH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAMzC,eAAe,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI;IAerD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,GAAG,IAAI;IAiBpE,SAAS,CAAC,SAAS,CAAC,EAAE,UAAU,GAAG,OAAO;IAsBjD,OAAO,CAAC,eAAe;IA8EvB,OAAO,CAAC,kBAAkB;IA8B1B,OAAO,CAAC,gBAAgB;IAkFxB,OAAO,CAAC,gBAAgB;IAuExB,OAAO,CAAC,aAAa;IAwBd,eAAe,IAAI,iBAAiB,CAAC,OAAO,gBAAgB,CAAC,UAAU,CAAC;IAKxE,iBAAiB,IAAI,iBAAiB,CAAC,OAAO,kBAAkB,CAAC,UAAU,CAAC;IAK5E,SAAS,IAAI,aAAa;IAQ1B,iBAAiB,CACtB,QAAQ,EAAE,MAAM,GACf;QAAE,CAAC,EAAE,UAAU,CAAC;QAAC,CAAC,EAAE,UAAU,CAAA;KAAE,GAAG,SAAS;IAkBxC,cAAc,IAAI,eAAe;IAKjC,WAAW,IAAI,QAAQ;IAId,OAAO,IAAI,IAAI;CAMhC"}