@daneren2005/shared-memory-physics 1.7.0 → 1.7.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
@@ -93,7 +93,7 @@ has a `polygon` component holding its outline:
93
93
  | ----------------- | ------------------------------------- | ------------------------------------------------------------- |
94
94
  | `SHAPE_RECTANGLE` | `width`, `height` | a `width` x `height` box turned to `angle` – the default |
95
95
  | `SHAPE_CIRCLE` | `radius` | a circle of diameter `width`, which `angle` does not affect |
96
- | `SHAPE_CAPSULE` | `width`, `height`, `shape` | `width` from end to end, `height` thick, lying along `angle` |
96
+ | `SHAPE_CAPSULE` | `width`, `height`, `shape` | a pill along its longer side, as thick as its shorter side |
97
97
  | `SHAPE_POLYGON` | `vertices` | a convex 3-16 vertex outline, scaled and turned by its transform |
98
98
 
99
99
  ```ts
@@ -108,8 +108,18 @@ world.loadEntity({ x: 0, y: 0, vertices: [[-8, -6], [10, 0], [-8, 6]] });
108
108
  Polygon vertices are local to the entity. They must describe a convex boundary in clockwise or counter-clockwise
109
109
  order; concave, collinear, non-finite, and out-of-range outlines throw during loading. Their bounding box becomes
110
110
  the transform's initial `width` and `height`, and the stored vertices are normalized around that box's centre.
111
- Changing the transform size therefore scales the polygon, while `angle` rotates it around that centre. Give
112
- `vertices` by themselves rather than alongside `radius` or `width`/`height`.
111
+ Changing the transform size therefore scales the polygon, while `angle` rotates it around that centre. `vertices`
112
+ cannot be given alongside `radius`.
113
+
114
+ Give `width` and `height` with the `vertices` when the outline should sit tighter than the box the entity is sized
115
+ by - a sprite with the hull drawn inside it, say. The box then stays that size, the vertices are offsets from the
116
+ entity's centre rather than from their own bounds, and every one must lie inside the box (the broadphase indexes
117
+ the box, so an outline poking out of it would miss contacts):
118
+
119
+ ```ts
120
+ // A 40x40 sprite whose triangular hull fills only part of it; x/y and the drawn size stay the sprite's.
121
+ world.loadEntity({ x: 0, y: 0, width: 40, height: 40, vertices: [[-12, -15], [14, 0], [-12, 15]] });
122
+ ```
113
123
 
114
124
  `radius` is the same size said differently: it loads as a `width` and a `height` of the diameter, so there is
115
125
  still only one size in the block however the config spelled it. Giving both a `radius` and a `width` throws
@@ -118,9 +128,10 @@ rather than one quietly winning. A config that says nothing about its `shape` is
118
128
  have to name their shape**, and an
119
129
  unrecognised one throws rather than quietly becoming a rectangle.
120
130
 
121
- A capsule's `width` is its **whole** length, caps included, and it lies along the way the entity faces: a 40x10
122
- capsule is a 30-long core with a 5 radius cap at each end, and it is the same 10 thick all the way down. One
123
- told it is no longer than it is wide collapses to a circle rather than turning itself inside out.
131
+ A capsule lies along its **longer** side, caps included: a 40x10 capsule is a 30-long core along the way the entity
132
+ faces with a 5 radius cap at each end, the same 10 thick all the way down, and a 10x40 one is that same pill turned
133
+ a quarter, running along the entity's height instead - handy for art drawn nose-up. `capsuleCoreAngle` gives the
134
+ angle the core lies at. One as long as it is wide is a circle.
124
135
 
125
136
  Because a circle is a capsule whose core has no length, the three primitive shapes are *an oriented core grown by a
126
137
  radius* - a box with no radius, a point, or a segment - which leaves only three overlap tests rather than one
@@ -139,7 +150,7 @@ reflects around; a game reaching for its own response can use it the same way.
139
150
  `shapesOverlap` and `contactNormal` are the allocation-free primitive-shape functions. Their polygon counterparts
140
151
  are `polygonShapesOverlap` and `polygonContactNormal`, which additionally take the polygon component blocks.
141
152
  `shapeHalfWidth` / `shapeHalfHeight` (the axis-aligned box a shape is
142
- indexed under), `shapeRadius`, `shapeIsEmpty`, `capsuleHalfLength`, and the distance primitives underneath -
153
+ indexed under), `shapeRadius`, `shapeIsEmpty`, `capsuleHalfLength`, `capsuleCoreAngle`, and the distance primitives underneath -
143
154
  `segmentSegmentDistanceSquared`, `segmentBoxDistanceSquared` and `pointSegmentDistanceSquared`, each of which
144
155
  takes an optional `out` vector and fills it with the gap it measured. `orientedBoxesOverlap`, `boundsHalfWidth`
145
156
  and `boundsHalfHeight` are still there and still rectangle-only.
@@ -348,10 +359,15 @@ world.addSystem(new InterpolationSystem<Components>(world));
348
359
  world.loadEntity({ x: 0, y: 0, width: 10, height: 10, velocityX: 100, interpolate: true });
349
360
  ```
350
361
 
351
- It takes no configuration and is not wired to the physics system. Physics publishes the transform, step
352
- duration, cumulative simulated time, and a tick stamp. Rendering caches each coherent target and its timing.
353
- If it catches physics mid-write, it continues consuming the cached segment until another complete snapshot
354
- is available.
362
+ It takes no configuration. Physics publishes the transform, step duration, cumulative simulated time, and a tick
363
+ stamp. Rendering caches each coherent target and its timing. If it catches physics mid-write, it continues
364
+ consuming the cached segment until another complete snapshot is available.
365
+
366
+ A worker publishes a run entity by entity while the main thread renders, so one frame can find some entities of a
367
+ run at its new step and the rest at the old one - two ships flying in formation would then drift apart on screen.
368
+ Each entity therefore keeps the publication it replaced, and `InterpolationSystem` snapshots every
369
+ `PhysicsSystem`'s committed tick once per frame: a publication newer than its run's commit is drawn from the
370
+ previous one until the whole run has landed. Both systems must share the same world object for this to apply.
355
371
  A game that retunes `deltaBetweenRuns` mid-flight is followed with nothing told to this system.
356
372
 
357
373
  Then draw from `interpolation` instead of `transform`, keeping the size and facing where they have always been:
@@ -489,15 +505,15 @@ spot, as above.
489
505
 
490
506
  The renderer retains the last coherent target while physics publishes a new step, so an overlapping read can keep advancing without freezing a frame.
491
507
 
492
- 44 bytes per interpolated entity, four publication writes per entity per physics step, and a per-frame pass of one
508
+ 68 bytes per interpolated entity, ten publication writes per entity per physics step, and a per-frame pass of one
493
509
  lerp per entity on the main thread - order 0.2ms at 10,000 entities. An entity without the component pays
494
510
  nothing at all. `forceMainThread: false` plus a `getWorker` moves the pass to a worker for worlds large enough
495
511
  that it shows up in a profile, at the price of the render position being one frame stale; because consumers only
496
512
  ever read `interpolation.x`, that is a constructor flag rather than a migration.
497
513
 
498
514
  Physics marks the tick dirty before changing timing and position, then publishes the completed tick with
499
- a release store. The reader accepts a new snapshot only when its tick reads match; otherwise it continues
500
- toward the cached target.
515
+ a release store. The reader accepts a new snapshot only when its tick reads match and its run has committed;
516
+ otherwise it reads the previous publication, and failing that continues toward the cached target.
501
517
 
502
518
  ## Collisions
503
519
 
@@ -14,6 +14,8 @@ export interface InterpolationComponent {
14
14
  targetX: number;
15
15
  targetY: number;
16
16
  syncedStepDuration: number;
17
+ previousX: number;
18
+ previousY: number;
17
19
  }
18
20
  export interface InterpolationConfig {
19
21
  interpolate?: boolean;
@@ -31,7 +33,13 @@ export declare const INTERPOLATION_REMAINING_DURATION_INDEX = 7;
31
33
  export declare const INTERPOLATION_TARGET_X_INDEX = 8;
32
34
  export declare const INTERPOLATION_TARGET_Y_INDEX = 9;
33
35
  export declare const INTERPOLATION_SYNCED_STEP_DURATION_INDEX = 10;
34
- export declare const INTERPOLATION_SIZE = 11;
36
+ export declare const INTERPOLATION_CHANNEL_INDEX = 11;
37
+ export declare const INTERPOLATION_PREVIOUS_X_INDEX = 12;
38
+ export declare const INTERPOLATION_PREVIOUS_Y_INDEX = 13;
39
+ export declare const INTERPOLATION_PREVIOUS_DURATION_INDEX = 14;
40
+ export declare const INTERPOLATION_PREVIOUS_TOTAL_DURATION_INDEX = 15;
41
+ export declare const INTERPOLATION_PREVIOUS_TICK_INDEX = 16;
42
+ export declare const INTERPOLATION_SIZE = 17;
35
43
  export declare const interpolationDefinition: ComponentDefinition<InterpolationComponent, Float32Array, InterpolationConfig>;
36
44
  export interface SnappableEntity {
37
45
  components: {
@@ -2,6 +2,8 @@ import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';
2
2
  export type PolygonVertex = readonly [x: number, y: number];
3
3
  export interface PolygonConfig {
4
4
  vertices?: ReadonlyArray<PolygonVertex>;
5
+ width?: number;
6
+ height?: number;
5
7
  }
6
8
  export interface PolygonComponent {
7
9
  index: number;
@@ -18,5 +20,5 @@ export interface PreparedPolygon {
18
20
  vertices: Array<number>;
19
21
  }
20
22
  export declare const polygonDefinition: ComponentDefinition<PolygonComponent, Float32Array, PolygonConfig>;
21
- export declare function preparePolygon(vertices: ReadonlyArray<PolygonVertex> | undefined): PreparedPolygon;
23
+ export declare function preparePolygon(vertices: ReadonlyArray<PolygonVertex> | undefined, boxWidth?: number, boxHeight?: number): PreparedPolygon;
22
24
  export default polygonDefinition;
package/dist/index.d.ts CHANGED
@@ -17,13 +17,13 @@ export { default as bouncinessDefinition } from './components/bounciness-compone
17
17
  export { BOUNCINESS_INDEX, BOUNCINESS_SIZE, } from './components/bounciness-component';
18
18
  export type { BouncinessComponent, BouncinessConfig, } from './components/bounciness-component';
19
19
  export { default as interpolationDefinition, snapEntity, startSpawnInterpolation } from './components/interpolation-component';
20
- export { INTERPOLATION_X_INDEX, INTERPOLATION_Y_INDEX, INTERPOLATION_SYNCED_TICK_INDEX, INTERPOLATION_DURATION_INDEX, INTERPOLATION_TICK_INDEX, INTERPOLATION_TOTAL_DURATION_INDEX, INTERPOLATION_SYNCED_DURATION_INDEX, INTERPOLATION_REMAINING_DURATION_INDEX, INTERPOLATION_TARGET_X_INDEX, INTERPOLATION_TARGET_Y_INDEX, INTERPOLATION_SYNCED_STEP_DURATION_INDEX, INTERPOLATION_SIZE, } from './components/interpolation-component';
20
+ export { INTERPOLATION_X_INDEX, INTERPOLATION_Y_INDEX, INTERPOLATION_SYNCED_TICK_INDEX, INTERPOLATION_DURATION_INDEX, INTERPOLATION_TICK_INDEX, INTERPOLATION_TOTAL_DURATION_INDEX, INTERPOLATION_SYNCED_DURATION_INDEX, INTERPOLATION_REMAINING_DURATION_INDEX, INTERPOLATION_TARGET_X_INDEX, INTERPOLATION_TARGET_Y_INDEX, INTERPOLATION_SYNCED_STEP_DURATION_INDEX, INTERPOLATION_CHANNEL_INDEX, INTERPOLATION_PREVIOUS_X_INDEX, INTERPOLATION_PREVIOUS_Y_INDEX, INTERPOLATION_PREVIOUS_DURATION_INDEX, INTERPOLATION_PREVIOUS_TOTAL_DURATION_INDEX, INTERPOLATION_PREVIOUS_TICK_INDEX, INTERPOLATION_SIZE, } from './components/interpolation-component';
21
21
  export type { InterpolationComponent, InterpolationConfig, SnappableEntity, SpawnableEntity, SpawnInterpolationOptions, } from './components/interpolation-component';
22
22
  export { physicsRegistry } from './components/registry';
23
23
  export type { PhysicsComponents, PhysicsUpdateComponents, InterpolationComponents, InterpolationUpdateComponents, } from './components/registry';
24
24
  export { default as PhysicalWorld, addPhysicalWorldData, getSpatialMap } from './world';
25
25
  export type { PhysicalSystemWorld, PhysicalWorldData, PhysicalWorldSource, PhysicalWorldEntity, PhysicalWorldFilter, PhysicalWorldOptions, } from './world';
26
- export { shapesOverlap, contactNormal, shapeHalfWidth, shapeHalfHeight, shapeIsEmpty, shapeRadius, capsuleHalfLength, boundsHalfWidth, boundsHalfHeight, orientedBoxesOverlap, segmentSegmentDistanceSquared, segmentBoxDistanceSquared, pointSegmentDistanceSquared, } from './math/shapes';
26
+ export { shapesOverlap, contactNormal, shapeHalfWidth, shapeHalfHeight, shapeIsEmpty, shapeRadius, capsuleHalfLength, capsuleCoreAngle, boundsHalfWidth, boundsHalfHeight, orientedBoxesOverlap, segmentSegmentDistanceSquared, segmentBoxDistanceSquared, pointSegmentDistanceSquared, } from './math/shapes';
27
27
  export type { Vector } from './math/shapes';
28
28
  export { polygonShapesOverlap, polygonContactNormal } from './math/polygons';
29
29
  export { default as CollisionBroadphase, COLLIDABLE_QUERY } from './systems/collision';
@@ -37,5 +37,6 @@ export type { PhysicsSystemConfig, VelocityAssignment } from './systems/physics-
37
37
  export { default as integrateDynamics } from './systems/dynamics';
38
38
  export type { DynamicsCommand, DynamicsCommandBuffer, DynamicsCommandQueue, DynamicsCommands, DynamicsWorld } from './systems/dynamics';
39
39
  export { default as interpolationUpdate } from './systems/interpolation-update';
40
+ export type { InterpolationWorld } from './systems/interpolation-update';
40
41
  export { default as InterpolationSystem } from './systems/interpolation-system';
41
42
  export type { InterpolationSystemConfig } from './systems/interpolation-system';