@phalanx-engine/physics 0.1.0 → 0.1.2

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 CHANGED
@@ -125,7 +125,7 @@ import {
125
125
  TRANSFORM_COMPONENT_TYPE,
126
126
  INTERPOLATION_COMPONENT_TYPE,
127
127
  } from '@phalanx-engine/physics';
128
- import { FP, FPVector3 } from '@phalanx-engine/math';
128
+ import { FP, FPVector3, FPQuaternion } from '@phalanx-engine/math';
129
129
 
130
130
  // Register canonical component type symbols from phalanx-physics
131
131
  export const ComponentType = createComponentTypeRegistry({
@@ -144,7 +144,11 @@ class MovementSystem extends GameSystem {
144
144
  class RenderSystem extends GameSystem {
145
145
  public override update(_dt: number): void {
146
146
  const sample = this.physics?.getInterpolatedTransform(entityId);
147
- if (sample) mesh.position.set(sample.position.x, sample.position.y, sample.position.z);
147
+ if (sample) {
148
+ mesh.position.set(sample.position.x, sample.position.y, sample.position.z);
149
+ // sample.rotation is a float quaternion { x, y, z, w } — apply it as-is.
150
+ mesh.quaternion.set(sample.rotation.x, sample.rotation.y, sample.rotation.z, sample.rotation.w);
151
+ }
148
152
  }
149
153
  }
150
154
  const movementSystem = new MovementSystem();
@@ -176,8 +180,9 @@ world.start();
176
180
  // 4. Add transform, interpolation, and physics body to entities
177
181
  declare const entity: { id: number; addComponent(c: unknown): void };
178
182
  const fpPosition = FPVector3.FromFloat(0, 0, 0);
179
- entity.addComponent(new TransformComponent(entity.id, fpPosition));
180
- entity.addComponent(new InterpolationComponent(fpPosition));
183
+ const fpRotation = FPQuaternion.Identity();
184
+ entity.addComponent(new TransformComponent(entity.id, fpPosition, fpRotation));
185
+ entity.addComponent(new InterpolationComponent(fpPosition, fpRotation));
181
186
  entity.addComponent(new PhysicsBodyComponent(entity.id, { radius: FP.FromFloat(1.0) }));
182
187
  world.entityManager.addEntity(entity as any);
183
188
 
@@ -282,7 +287,7 @@ class PhysicsWorld {
282
287
 
283
288
  - **`getSystems()`** — Returns both `physicsSystem` (register as tick system) and `interpolationSystem` (register as frame system).
284
289
  - **`getEntityPosition(entityId)`** — Fixed-point position for gameplay queries (e.g. ability targeting).
285
- - **`getInterpolatedTransform(entityId)`** — Interpolated float position/rotation for rendering, populated after `InterpolationSystem` runs.
290
+ - **`getInterpolatedTransform(entityId)`** — Interpolated float transform for rendering, populated after `InterpolationSystem` runs. Returns an `InterpolatedTransformSample` with `position: { x, y, z }` and `rotation: { x, y, z, w }` — rotation is a float quaternion (slerped between tick samples), apply it directly to a mesh quaternion.
286
291
 
287
292
  - **`applyImpulse(entityId, vx, vz)`** — Set body velocity (replaces, does not accumulate). Re-enables previously ejected bodies.
288
293
  - **`isSettled(threshold?)`** — Pure query: `true` when all non-static, non-ignored bodies are below velocity threshold (default from config, falling back to `FP.FromFloat(0.01)`).
@@ -314,15 +319,18 @@ class TransformComponent extends SoAComponent<typeof TransformSoASchema.definiti
314
319
  static readonly soaSchema: typeof TransformSoASchema;
315
320
  readonly type: symbol; // TRANSFORM_COMPONENT_TYPE
316
321
 
317
- constructor(entityId: number, initialPosition?: FPVector3, initialRotation?: FPVector3);
322
+ constructor(entityId: number, initialPosition?: FPVector3, initialRotation?: FPQuaternion);
318
323
 
319
- fpPosition: FPVector3; // get/set — authoritative fixed-point position
320
- fpRotation: FPVector3; // get/set — authoritative fixed-point rotation (radians)
321
- fpRotationY: FixedPoint; // get/set — convenience for Y-axis rotation
324
+ fpPosition: FPVector3; // get/set — authoritative fixed-point position
325
+ fpRotation: FPQuaternion; // get/set — authoritative rotation quaternion
326
+ fpRotationEuler: FPVector3; // get/set — computed Euler view (radians, XYZ order)
327
+ fpRotationY: FixedPoint; // get/set — convenience Y-yaw (radians)
322
328
  }
323
329
  ```
324
330
 
325
- `TransformSoASchema` fields: `fpPositionX/Y/Z`, `fpRotationX/Y/Z` (all `i64`).
331
+ Rotation is authoritatively a quaternion (`fpRotation`). `fpRotationEuler` (XYZ Euler radians) and `fpRotationY` (yaw) are computed views over it — recomputed on each access, mirroring Unity's `Transform.rotation` / `Transform.eulerAngles`. Setting `fpRotationEuler` calls `FPQuaternion.FromEulerXYZ`; setting `fpRotationY` builds a yaw rotation around `FPVector3.Up`.
332
+
333
+ `TransformSoASchema` fields: `fpPositionX/Y/Z`, `fpRotationX/Y/Z/W` (all `i64`). Rotation defaults to the identity quaternion (`w = 1`).
326
334
 
327
335
  ### `InterpolationComponent`
328
336
 
@@ -330,10 +338,10 @@ class TransformComponent extends SoAComponent<typeof TransformSoASchema.definiti
330
338
  class InterpolationComponent implements IComponent {
331
339
  readonly type: symbol; // INTERPOLATION_COMPONENT_TYPE
332
340
 
333
- constructor(initialPosition?: FPVector3, initialRotation?: FPVector3);
341
+ constructor(initialPosition?: FPVector3, initialRotation?: FPQuaternion);
334
342
 
335
343
  snapshot(): void; // copy current → previous (called by InterpolationSystem before tick)
336
- capture(fpPosition: FPVector3, fpRotation: FPVector3): void; // capture authoritative state after tick
344
+ capture(fpPosition: FPVector3, fpRotation: FPQuaternion): void; // capture authoritative state after tick
337
345
  }
338
346
  ```
339
347
 
@@ -1,15 +1,16 @@
1
1
  import type { IComponent } from '@phalanx-engine/ecs';
2
- import type { FPVector3 as FPVector3Type } from '@phalanx-engine/math';
2
+ import type { FPVector3 as FPVector3Type, FPQuaternion as FPQuaternionType } from '@phalanx-engine/math';
3
3
  export declare const INTERPOLATION_COMPONENT_TYPE: symbol;
4
4
  export declare class InterpolationComponent implements IComponent {
5
5
  readonly type: symbol;
6
6
  readonly previousFpPosition: FPVector3Type;
7
7
  readonly currentFpPosition: FPVector3Type;
8
- readonly previousFpRotation: FPVector3Type;
9
- readonly currentFpRotation: FPVector3Type;
10
- constructor(initialPosition?: FPVector3Type, initialRotation?: FPVector3Type);
8
+ readonly previousFpRotation: FPQuaternionType;
9
+ readonly currentFpRotation: FPQuaternionType;
10
+ constructor(initialPosition?: FPVector3Type, initialRotation?: FPQuaternionType);
11
11
  snapshot(): void;
12
- capture(fpPosition: FPVector3Type, fpRotation: FPVector3Type): void;
12
+ capture(fpPosition: FPVector3Type, fpRotation: FPQuaternionType): void;
13
13
  private copyFpVector3;
14
+ private copyFpQuaternion;
14
15
  }
15
16
  //# sourceMappingURL=InterpolationComponent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"InterpolationComponent.d.ts","sourceRoot":"","sources":["../../src/components/InterpolationComponent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAMvE,eAAO,MAAM,4BAA4B,EAAE,MAAgC,CAAC;AAS5E,qBAAa,sBAAuB,YAAW,UAAU;IACvD,SAAgB,IAAI,SAAgC;IAEpD,SAAgB,kBAAkB,EAAE,aAAa,CAAoC;IACrF,SAAgB,iBAAiB,EAAE,aAAa,CAAoC;IACpF,SAAgB,kBAAkB,EAAE,aAAa,CAAoC;IACrF,SAAgB,iBAAiB,EAAE,aAAa,CAAoC;gBAExE,eAAe,CAAC,EAAE,aAAa,EAAE,eAAe,CAAC,EAAE,aAAa;IAYrE,QAAQ,IAAI,IAAI;IAMhB,OAAO,CAAC,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,GAAG,IAAI;IAK1E,OAAO,CAAC,aAAa;CAKtB"}
1
+ {"version":3,"file":"InterpolationComponent.d.ts","sourceRoot":"","sources":["../../src/components/InterpolationComponent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,KAAK,EACV,SAAS,IAAI,aAAa,EAC1B,YAAY,IAAI,gBAAgB,EACjC,MAAM,sBAAsB,CAAC;AAM9B,eAAO,MAAM,4BAA4B,EAAE,MAAgC,CAAC;AAU5E,qBAAa,sBAAuB,YAAW,UAAU;IACvD,SAAgB,IAAI,SAAgC;IAEpD,SAAgB,kBAAkB,EAAE,aAAa,CAAoC;IACrF,SAAgB,iBAAiB,EAAE,aAAa,CAAoC;IACpF,SAAgB,kBAAkB,EAAE,gBAAgB,CAA8C;IAClG,SAAgB,iBAAiB,EAAE,gBAAgB,CAA8C;gBAErF,eAAe,CAAC,EAAE,aAAa,EAAE,eAAe,CAAC,EAAE,gBAAgB;IAYxE,QAAQ,IAAI,IAAI;IAMhB,OAAO,CAAC,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,GAAG,IAAI;IAK7E,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,gBAAgB;CAMzB"}
@@ -4,29 +4,35 @@ export class InterpolationComponent {
4
4
  type = INTERPOLATION_COMPONENT_TYPE;
5
5
  previousFpPosition = { x: FP._0, y: FP._0, z: FP._0 };
6
6
  currentFpPosition = { x: FP._0, y: FP._0, z: FP._0 };
7
- previousFpRotation = { x: FP._0, y: FP._0, z: FP._0 };
8
- currentFpRotation = { x: FP._0, y: FP._0, z: FP._0 };
7
+ previousFpRotation = { x: FP._0, y: FP._0, z: FP._0, w: FP._1 };
8
+ currentFpRotation = { x: FP._0, y: FP._0, z: FP._0, w: FP._1 };
9
9
  constructor(initialPosition, initialRotation) {
10
10
  if (initialPosition) {
11
11
  this.copyFpVector3(this.previousFpPosition, initialPosition);
12
12
  this.copyFpVector3(this.currentFpPosition, initialPosition);
13
13
  }
14
14
  if (initialRotation) {
15
- this.copyFpVector3(this.previousFpRotation, initialRotation);
16
- this.copyFpVector3(this.currentFpRotation, initialRotation);
15
+ this.copyFpQuaternion(this.previousFpRotation, initialRotation);
16
+ this.copyFpQuaternion(this.currentFpRotation, initialRotation);
17
17
  }
18
18
  }
19
19
  snapshot() {
20
20
  this.copyFpVector3(this.previousFpPosition, this.currentFpPosition);
21
- this.copyFpVector3(this.previousFpRotation, this.currentFpRotation);
21
+ this.copyFpQuaternion(this.previousFpRotation, this.currentFpRotation);
22
22
  }
23
23
  capture(fpPosition, fpRotation) {
24
24
  this.copyFpVector3(this.currentFpPosition, fpPosition);
25
- this.copyFpVector3(this.currentFpRotation, fpRotation);
25
+ this.copyFpQuaternion(this.currentFpRotation, fpRotation);
26
26
  }
27
27
  copyFpVector3(target, source) {
28
28
  target.x = source.x;
29
29
  target.y = source.y;
30
30
  target.z = source.z;
31
31
  }
32
+ copyFpQuaternion(target, source) {
33
+ target.x = source.x;
34
+ target.y = source.y;
35
+ target.z = source.z;
36
+ target.w = source.w;
37
+ }
32
38
  }
@@ -1,5 +1,5 @@
1
1
  import { SoAComponent } from '@phalanx-engine/ecs';
2
- import { type FixedPoint, type FPVector3 as FPVector3Type } from '@phalanx-engine/math';
2
+ import { type FixedPoint, type FPVector3 as FPVector3Type, type FPQuaternion as FPQuaternionType } from '@phalanx-engine/math';
3
3
  export declare const TransformSoASchema: import("@phalanx-engine/ecs").SoASchema<{
4
4
  fpPositionX: "i64";
5
5
  fpPositionY: "i64";
@@ -7,6 +7,7 @@ export declare const TransformSoASchema: import("@phalanx-engine/ecs").SoASchema
7
7
  fpRotationX: "i64";
8
8
  fpRotationY: "i64";
9
9
  fpRotationZ: "i64";
10
+ fpRotationW: "i64";
10
11
  }>;
11
12
  export declare const TRANSFORM_COMPONENT_TYPE: symbol;
12
13
  export declare class TransformComponent extends SoAComponent<typeof TransformSoASchema.definition> {
@@ -18,14 +19,17 @@ export declare class TransformComponent extends SoAComponent<typeof TransformSoA
18
19
  fpRotationX: "i64";
19
20
  fpRotationY: "i64";
20
21
  fpRotationZ: "i64";
22
+ fpRotationW: "i64";
21
23
  }>;
22
24
  private readonly _fpPosition;
23
25
  private readonly _fpRotation;
24
- constructor(entityId: number, initialPosition?: FPVector3Type, initialRotation?: FPVector3Type);
26
+ constructor(entityId: number, initialPosition?: FPVector3Type, initialRotation?: FPQuaternionType);
25
27
  get fpPosition(): FPVector3Type;
26
28
  set fpPosition(value: FPVector3Type);
27
- get fpRotation(): FPVector3Type;
28
- set fpRotation(value: FPVector3Type);
29
+ get fpRotation(): FPQuaternionType;
30
+ set fpRotation(value: FPQuaternionType);
31
+ get fpRotationEuler(): FPVector3Type;
32
+ set fpRotationEuler(value: FPVector3Type);
29
33
  get fpRotationY(): FixedPoint;
30
34
  set fpRotationY(value: FixedPoint);
31
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TransformComponent.d.ts","sourceRoot":"","sources":["../../src/components/TransformComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAmB,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAiB,KAAK,UAAU,EAAE,KAAK,SAAS,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAQvG,eAAO,MAAM,kBAAkB;;;;;;;EAU9B,CAAC;AAMF,eAAO,MAAM,wBAAwB,EAAE,MAA4B,CAAC;AAapE,qBAAa,kBAAmB,SAAQ,YAAY,CAAC,OAAO,kBAAkB,CAAC,UAAU,CAAC;IACxF,SAAgB,IAAI,SAA4B;IAChD,gBAAuB,SAAS;;;;;;;OAAsB;IAEtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmD;IAC/E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmD;gBAG7E,QAAQ,EAAE,MAAM,EAChB,eAAe,CAAC,EAAE,aAAa,EAC/B,eAAe,CAAC,EAAE,aAAa;IAejC,IAAW,UAAU,IAAI,aAAa,CASrC;IAED,IAAW,UAAU,CAAC,KAAK,EAAE,aAAa,EAQzC;IAED,IAAW,UAAU,IAAI,aAAa,CAQrC;IAED,IAAW,UAAU,CAAC,KAAK,EAAE,aAAa,EAOzC;IAED,IAAW,WAAW,IAAI,UAAU,CAInC;IAED,IAAW,WAAW,CAAC,KAAK,EAAE,UAAU,EAIvC;CACF"}
1
+ {"version":3,"file":"TransformComponent.d.ts","sourceRoot":"","sources":["../../src/components/TransformComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAmB,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,SAAS,IAAI,aAAa,EAC/B,KAAK,YAAY,IAAI,gBAAgB,EACtC,MAAM,sBAAsB,CAAC;AAU9B,eAAO,MAAM,kBAAkB;;;;;;;;EAW9B,CAAC;AAMF,eAAO,MAAM,wBAAwB,EAAE,MAA4B,CAAC;AAiBpE,qBAAa,kBAAmB,SAAQ,YAAY,CAAC,OAAO,kBAAkB,CAAC,UAAU,CAAC;IACxF,SAAgB,IAAI,SAA4B;IAChD,gBAAuB,SAAS;;;;;;;;OAAsB;IAEtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmD;IAC/E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgE;gBAG1F,QAAQ,EAAE,MAAM,EAChB,eAAe,CAAC,EAAE,aAAa,EAC/B,eAAe,CAAC,EAAE,gBAAgB;IAgBpC,IAAW,UAAU,IAAI,aAAa,CASrC;IAED,IAAW,UAAU,CAAC,KAAK,EAAE,aAAa,EAQzC;IAGD,IAAW,UAAU,IAAI,gBAAgB,CASxC;IAED,IAAW,UAAU,CAAC,KAAK,EAAE,gBAAgB,EAQ5C;IAOD,IAAW,eAAe,IAAI,aAAa,CAE1C;IAED,IAAW,eAAe,CAAC,KAAK,EAAE,aAAa,EAE9C;IAGD,IAAW,WAAW,IAAI,UAAU,CAMnC;IAED,IAAW,WAAW,CAAC,KAAK,EAAE,UAAU,EAEvC;CACF"}
@@ -1,5 +1,5 @@
1
1
  import { SoAComponent, defineSoASchema } from '@phalanx-engine/ecs';
2
- import { FP, FPVector3 } from '@phalanx-engine/math';
2
+ import { FP, FPVector3, FPQuaternion, } from '@phalanx-engine/math';
3
3
  export const TransformSoASchema = defineSoASchema({
4
4
  fpPositionX: 'i64',
5
5
  fpPositionY: 'i64',
@@ -7,16 +7,17 @@ export const TransformSoASchema = defineSoASchema({
7
7
  fpRotationX: 'i64',
8
8
  fpRotationY: 'i64',
9
9
  fpRotationZ: 'i64',
10
+ fpRotationW: 'i64',
10
11
  }, 'Transform');
11
12
  export const TRANSFORM_COMPONENT_TYPE = Symbol('Transform');
12
13
  export class TransformComponent extends SoAComponent {
13
14
  type = TRANSFORM_COMPONENT_TYPE;
14
15
  static soaSchema = TransformSoASchema;
15
16
  _fpPosition = { x: FP._0, y: FP._0, z: FP._0 };
16
- _fpRotation = { x: FP._0, y: FP._0, z: FP._0 };
17
+ _fpRotation = { x: FP._0, y: FP._0, z: FP._0, w: FP._1 };
17
18
  constructor(entityId, initialPosition, initialRotation) {
18
19
  const position = initialPosition ?? FPVector3.Zero;
19
- const rotation = initialRotation ?? FPVector3.Zero;
20
+ const rotation = initialRotation ?? FPQuaternion.Identity();
20
21
  super(TransformSoASchema, entityId, {
21
22
  fpPositionX: FP.ToRaw(position.x),
22
23
  fpPositionY: FP.ToRaw(position.y),
@@ -24,6 +25,7 @@ export class TransformComponent extends SoAComponent {
24
25
  fpRotationX: FP.ToRaw(rotation.x),
25
26
  fpRotationY: FP.ToRaw(rotation.y),
26
27
  fpRotationZ: FP.ToRaw(rotation.z),
28
+ fpRotationW: FP.ToRaw(rotation.w),
27
29
  });
28
30
  }
29
31
  get fpPosition() {
@@ -50,6 +52,7 @@ export class TransformComponent extends SoAComponent {
50
52
  this._fpRotation.x = FP.FromRaw(this.store.arrays.fpRotationX[idx]);
51
53
  this._fpRotation.y = FP.FromRaw(this.store.arrays.fpRotationY[idx]);
52
54
  this._fpRotation.z = FP.FromRaw(this.store.arrays.fpRotationZ[idx]);
55
+ this._fpRotation.w = FP.FromRaw(this.store.arrays.fpRotationW[idx]);
53
56
  return this._fpRotation;
54
57
  }
55
58
  set fpRotation(value) {
@@ -59,17 +62,22 @@ export class TransformComponent extends SoAComponent {
59
62
  this.store.arrays.fpRotationX[idx] = FP.ToRaw(value.x);
60
63
  this.store.arrays.fpRotationY[idx] = FP.ToRaw(value.y);
61
64
  this.store.arrays.fpRotationZ[idx] = FP.ToRaw(value.z);
65
+ this.store.arrays.fpRotationW[idx] = FP.ToRaw(value.w);
66
+ }
67
+ get fpRotationEuler() {
68
+ return FPQuaternion.ToEulerXYZ(this.fpRotation);
69
+ }
70
+ set fpRotationEuler(value) {
71
+ this.fpRotation = FPQuaternion.FromEulerXYZ(value);
62
72
  }
63
73
  get fpRotationY() {
64
- const idx = this.getIndex();
65
- if (idx === -1)
66
- return FP._0;
67
- return FP.FromRaw(this.store.arrays.fpRotationY[idx]);
74
+ const q = this.fpRotation;
75
+ const two = FP.FromInt(2);
76
+ const sinY = FP.Mul(two, FP.Add(FP.Mul(q.w, q.y), FP.Mul(q.x, q.z)));
77
+ const cosY = FP.Sub(FP._1, FP.Mul(two, FP.Add(FP.Mul(q.y, q.y), FP.Mul(q.z, q.z))));
78
+ return FP.Atan2(sinY, cosY);
68
79
  }
69
80
  set fpRotationY(value) {
70
- const idx = this.getIndex();
71
- if (idx === -1)
72
- return;
73
- this.store.arrays.fpRotationY[idx] = FP.ToRaw(value);
81
+ this.fpRotation = FPQuaternion.FromYaw(value);
74
82
  }
75
83
  }
@@ -9,6 +9,7 @@ export interface InterpolatedTransformSample {
9
9
  x: number;
10
10
  y: number;
11
11
  z: number;
12
+ w: number;
12
13
  };
13
14
  }
14
15
  export declare class InterpolationSystem extends GameSystem implements IBeforeTick, IAfterTick, IBeforeFrame {
@@ -23,6 +24,7 @@ export declare class InterpolationSystem extends GameSystem implements IBeforeTi
23
24
  capture(): void;
24
25
  interpolate(alpha: number): void;
25
26
  getInterpolatedTransform(entityId: number): InterpolatedTransformSample | undefined;
26
- private readFpVector3;
27
+ private readFpPosition;
28
+ private readFpRotation;
27
29
  }
28
30
  //# sourceMappingURL=InterpolationSystem.d.ts.map
@@ -1 +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"}
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;AAc7B,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE9C,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D;AAWD,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;IAmCf,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAkChC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,2BAA2B,GAAG,SAAS;IAI1F,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,cAAc;CASvB"}
@@ -1,16 +1,7 @@
1
1
  import { GameSystem, } from '@phalanx-engine/ecs';
2
- import { FP, FPVector3 } from '@phalanx-engine/math';
2
+ import { FP, FPVector3, FPQuaternion, } from '@phalanx-engine/math';
3
3
  import { INTERPOLATION_COMPONENT_TYPE, } from '../components';
4
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
5
  function lerpScalar(from, to, t) {
15
6
  const clamped = Math.max(0, Math.min(1, t));
16
7
  return from + (to - from) * clamped;
@@ -47,8 +38,8 @@ export class InterpolationSystem extends GameSystem {
47
38
  const transformIndex = this.transformStore.indexOf(entity.id);
48
39
  if (!interpolation || transformIndex === -1)
49
40
  continue;
50
- const fpPosition = this.readFpVector3(transformIndex, 'fpPositionX', 'fpPositionY', 'fpPositionZ');
51
- const fpRotation = this.readFpVector3(transformIndex, 'fpRotationX', 'fpRotationY', 'fpRotationZ');
41
+ const fpPosition = this.readFpPosition(transformIndex);
42
+ const fpRotation = this.readFpRotation(transformIndex);
52
43
  if (!this.capturedEntities.has(entity.id)) {
53
44
  interpolation.capture(fpPosition, fpRotation);
54
45
  interpolation.snapshot();
@@ -66,6 +57,7 @@ export class InterpolationSystem extends GameSystem {
66
57
  }
67
58
  interpolate(alpha) {
68
59
  const clampedAlpha = Math.max(0, Math.min(1, alpha));
60
+ const fpAlpha = FP.FromFloat(clampedAlpha);
69
61
  this.interpolatedSamples.clear();
70
62
  const entities = this.entityManager.queryEntities(INTERPOLATION_COMPONENT_TYPE, TRANSFORM_COMPONENT_TYPE);
71
63
  for (const entity of entities) {
@@ -74,31 +66,35 @@ export class InterpolationSystem extends GameSystem {
74
66
  continue;
75
67
  const previousPosition = FPVector3.ToFloat(interpolation.previousFpPosition);
76
68
  const currentPosition = FPVector3.ToFloat(interpolation.currentFpPosition);
77
- const previousRotation = FPVector3.ToFloat(interpolation.previousFpRotation);
78
- const currentRotation = FPVector3.ToFloat(interpolation.currentFpRotation);
69
+ const interpolatedRotation = FPQuaternion.Slerp(interpolation.previousFpRotation, interpolation.currentFpRotation, fpAlpha);
79
70
  this.interpolatedSamples.set(entity.id, {
80
71
  position: {
81
72
  x: lerpScalar(previousPosition.x, currentPosition.x, clampedAlpha),
82
73
  y: lerpScalar(previousPosition.y, currentPosition.y, clampedAlpha),
83
74
  z: lerpScalar(previousPosition.z, currentPosition.z, clampedAlpha),
84
75
  },
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
- },
76
+ rotation: FPQuaternion.ToFloat(interpolatedRotation),
90
77
  });
91
78
  }
92
79
  }
93
80
  getInterpolatedTransform(entityId) {
94
81
  return this.interpolatedSamples.get(entityId);
95
82
  }
96
- readFpVector3(index, xKey, yKey, zKey) {
83
+ readFpPosition(index) {
84
+ const arrays = this.transformStore.arrays;
85
+ return {
86
+ x: FP.FromRaw(arrays.fpPositionX[index]),
87
+ y: FP.FromRaw(arrays.fpPositionY[index]),
88
+ z: FP.FromRaw(arrays.fpPositionZ[index]),
89
+ };
90
+ }
91
+ readFpRotation(index) {
97
92
  const arrays = this.transformStore.arrays;
98
93
  return {
99
- x: FP.FromRaw(arrays[xKey][index]),
100
- y: FP.FromRaw(arrays[yKey][index]),
101
- z: FP.FromRaw(arrays[zKey][index]),
94
+ x: FP.FromRaw(arrays.fpRotationX[index]),
95
+ y: FP.FromRaw(arrays.fpRotationY[index]),
96
+ z: FP.FromRaw(arrays.fpRotationZ[index]),
97
+ w: FP.FromRaw(arrays.fpRotationW[index]),
102
98
  };
103
99
  }
104
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phalanx-engine/physics",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Deterministic fixed-point physics engine for Phalanx Engine - spatial hashing, collision detection, and resolution",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,8 +18,8 @@
18
18
  "devDependencies": {
19
19
  "typescript": "~5.9.3",
20
20
  "vitest": "^1.0.0",
21
- "@phalanx-engine/ecs": "0.1.0",
22
- "@phalanx-engine/math": "0.1.0"
21
+ "@phalanx-engine/ecs": "0.1.2",
22
+ "@phalanx-engine/math": "0.1.2"
23
23
  },
24
24
  "keywords": [
25
25
  "physics",