@daneren2005/shared-memory-physics 1.0.0 → 1.2.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 CHANGED
@@ -5,6 +5,8 @@
5
5
  lives in the ECS's shared-memory blocks, so the simulation can run on a worker thread while the main thread
6
6
  reads positions straight off its entities.
7
7
 
8
+ Examples at https://daneren2005.github.io/shared-memory-physics/
9
+
8
10
  The ECS (`>=1.1.1`) and the shared-memory primitives it is built on are **peer dependencies**: a game must
9
11
  register these components against the same copy of the ECS it builds its world with.
10
12
 
@@ -14,11 +16,12 @@ npm install @daneren2005/shared-memory-physics @daneren2005/shared-memory-ecs @d
14
16
 
15
17
  ## Components
16
18
 
17
- | Component | Config | Serialization | Block |
18
- | ----------- | -------------------------------------------- | -------------------------- | ---------------------------------------------- |
19
- | `transform` | `width`, `height`, `angle?` / `radius` | `x`, `y` | `Float32Array` – where, how big, facing |
20
- | `velocity` | – | `velocityX?`, `velocityY?` | `Float32Array` – world units per second |
21
- | `body` | `shape?`, `collideCategory?`, `collideMask?` | – | `Uint32Array` – what shape, what collides with |
19
+ | Component | Config | Serialization | Block |
20
+ | --------------- | -------------------------------------------- | -------------------------- | ---------------------------------------------- |
21
+ | `transform` | `width`, `height`, `angle?` / `radius` | `x`, `y` | `Float32Array` – where, how big, facing |
22
+ | `velocity` | – | `velocityX?`, `velocityY?` | `Float32Array` – world units per second |
23
+ | `body` | `shape?`, `collideCategory?`, `collideMask?` | – | `Uint32Array` – what shape, what collides with |
24
+ | `interpolation` | `interpolate` | – | `Float32Array` – where to *draw* it |
22
25
 
23
26
  Entity configs are flat and shared across every component, so velocity is keyed as `velocityX` / `velocityY`
24
27
  rather than `x` / `y` (which already belong to the transform). Either velocity axis on its own is enough to
@@ -36,6 +39,10 @@ nothing here writes it - it is yours to set, usually from the heading, and colli
36
39
  `height` are the unrotated size; a box with no area never overlaps anything, not even another box sharing its
37
40
  exact position.
38
41
 
42
+ `interpolation` is where to **draw** an entity, which stops being the same thing as where it is the moment
43
+ physics runs less often than the screen refreshes. It is opt-in per entity (`interpolate: true`) and filled in
44
+ by [`InterpolationSystem`](#interpolation) - see that section for what it costs and what it buys.
45
+
39
46
  The `body` is what makes an entity collidable, and a size loads one just as it loads a transform - so
40
47
  everything with a place in the world collides with everything else until you say otherwise. All of it is
41
48
  defining config out of your own template, which is why `save` returns nothing for it: a reloaded entity gets
@@ -121,24 +128,77 @@ world.update(1000); // one second: the entity above is now at x = 10
121
128
  `elapsedTime` is in milliseconds (what `BaseWorld#update` is driven with) and velocity is in world units per
122
129
  **second**, so a run covering 16ms moves an entity 16/1000ths of its velocity.
123
130
 
124
- An entity that moved is reported back to the main thread as a single `position-updated` event carrying where
125
- it ended up, so anything you keep keyed off position can follow a move rather than having to poll the block:
131
+ ### The step
132
+
133
+ `PhysicsSystem` runs on a fixed step of **50ms** unless you say otherwise (`DEFAULT_PHYSICS_STEP_MS`). Collision
134
+ is by a distance the whole of it, so running it 20 times a second rather than 60 is two thirds less of the frame
135
+ spent on it for a simulation that is no less correct - only coarser. What that costs is smoothness, which is
136
+ what [`InterpolationSystem`](#interpolation) is for.
137
+
138
+ ```ts
139
+ new PhysicsSystem(world, { deltaBetweenRuns: 0 }); // back to every frame
140
+ ```
141
+
142
+ The step has one real ceiling: a move is swept where it *ends*, so **a longer step is a longer move, and a move
143
+ longer than the thing in its way is thick goes clean through it**. At 50ms an entity crossing 300 units a second
144
+ covers 15 of them per step, so anything it must be stopped by has to be thicker than that. A game with genuinely
145
+ fast entities either keeps the step short or makes its walls thick.
146
+
147
+ The system accumulates frame time and runs once it has a step's worth, so a 16ms frame runs physics on roughly
148
+ every third one and hands it the whole 50ms. Nothing is lost and nothing is run twice.
149
+
150
+ `PhysicsSystem` also stamps each run with a `tick`, which is what the interpolation block is published under.
151
+ A game that subclasses the system to attach per-run data of its own **must call `super.addDataToWorld(world)`**:
152
+
153
+ ```ts
154
+ class GamePhysicsSystem extends PhysicsSystem<Components, GameComponents, GameWorld> {
155
+ addDataToWorld(world: GameWorld): void {
156
+ super.addDataToWorld(world); // without this, nothing gets a tick
157
+ world.bounds = this.bounds;
158
+ }
159
+ }
160
+ ```
161
+
162
+ `GameWorld` extends `PhysicsWorld` (which is `ComponentSystemWorld` plus that `tick`) rather than
163
+ `ComponentSystemWorld` directly.
164
+
165
+ A run's moves are reported back to the main thread as a single `position-updated` event **on the system**,
166
+ carrying the ids of everything that moved, so anything you keep keyed off position can follow the moves
167
+ rather than having to poll every block:
126
168
 
127
169
  ```ts
128
170
  import { POSITION_UPDATED_EVENT } from '@daneren2005/shared-memory-physics';
129
171
 
130
- entity.on(POSITION_UPDATED_EVENT, (x, y) => {
131
- // 12.5, -3
172
+ const physics = world.addSystem(new PhysicsSystem(world));
173
+
174
+ physics.on(POSITION_UPDATED_EVENT, (entityIds: Array<number>) => {
175
+ for(const eid of entityIds) {
176
+ const transform = world.getEntityByEid(eid)?.components.transform;
177
+ // transform.x / transform.y are where it ended up
178
+ }
132
179
  });
133
180
  ```
134
181
 
135
- Both axes come with it even when only one of them moved, so a listener always has the whole position without
136
- having to remember the last one it was told - and a diagonal move costs one event across the worker boundary
137
- rather than the two a `component-property-updated` per property would. The move is not reported as a property
138
- change as well, so a listener on `component-property-updated` no longer hears about positions at all.
182
+ Nearly every entity moves on nearly every run, so this is the one thing a physics system must not report an
183
+ entity at a time: an event object apiece in the worker, cloned across the boundary, then a lookup and an emit
184
+ apiece on the main thread costs more, at a few thousand entities, than moving them did. One array of ids
185
+ costs one listener call for the whole run.
186
+
187
+ No position travels with the id, because there is no need: the transform is a `SharedArrayBuffer` block, so
188
+ the position the worker wrote is already on the main thread by the time the event lands - read it off the
189
+ entity (or straight off the block). The move is not reported as a property change either, so a listener on
190
+ `component-property-updated` never hears about positions.
139
191
 
140
- An entity that did not move says nothing, so a world of still entities stays quiet - and so does one held
141
- against something it cannot move past.
192
+ An entity that did not move is not in the list, so a world of still entities stays quiet - and so does one
193
+ held against something it cannot move past.
194
+
195
+ **A run only reports at all when something is listening.** The bill for this event is paid in the worker - an id
196
+ per moved entity pushed into the run's event array, and that array structured-cloned back across the boundary -
197
+ so `PhysicsSystem` checks per run whether it has a listener and tells the update not to bother when it does not.
198
+ That matters most for a game rendering from [`interpolation`](#interpolation), which wants nothing to do with an
199
+ event that fires at the physics rate: it is the rate that game is drawing *around*. Nothing has to be
200
+ configured for it, but `reportMoves: true` forces it on for a listener attached somewhere the system cannot see
201
+ it, and `reportMoves: false` turns it off regardless.
142
202
 
143
203
  The move is written with `addAtomicFloat32` rather than a plain `+=`, so a game system on another thread can
144
204
  add to the same position in the same instant without either move being lost.
@@ -167,13 +227,130 @@ world.addSystem(new PhysicsSystem(world, {
167
227
  Without a `getWorker` (or where Web Workers / `SharedArrayBuffer` are unavailable) the exact same
168
228
  `physicsUpdate` runs in-process on the main thread instead.
169
229
 
230
+ ## Interpolation
231
+
232
+ A 50ms physics step against a 16ms frame means the transform only changes on one frame in three, and an entity
233
+ drawn straight off it visibly stutters. `InterpolationSystem` fills in a **render position** that changes every
234
+ frame instead, by blending between the two positions physics published either side of its last step:
235
+
236
+ ```
237
+ render = prev + (current - prev) * alpha
238
+ ```
239
+
240
+ Every position it writes is therefore one the simulation actually produced - somewhere on the segment between
241
+ two real ones, never a guess past the end of one. There is nothing to tune and no case where it draws something
242
+ that did not happen: an entity that stopped against a wall is drawn easing into the wall and stopping, and one
243
+ that turned a corner is drawn turning the corner rather than carrying on into its old heading for a frame and
244
+ being snapped back. The cost is **one step of latency, always** - what is on screen is where the world was a
245
+ step ago.
246
+
247
+ ```ts
248
+ import { InterpolationSystem, PhysicsSystem } from '@daneren2005/shared-memory-physics';
249
+
250
+ world.addSystem(new PhysicsSystem<Components>(world, { getWorker }));
251
+ world.addSystem(new InterpolationSystem<Components>(world));
252
+
253
+ world.loadEntity({ x: 0, y: 0, width: 10, height: 10, velocityX: 100, interpolate: true });
254
+ ```
255
+
256
+ It takes no configuration and is not wired to the physics system. Everything it needs - the two positions, how
257
+ much simulated time lies between them, and whether that pair is new - is published into the block by whichever
258
+ run wrote it, so a game that retunes `deltaBetweenRuns` mid-flight is followed with nothing told to this system.
259
+
260
+ Then draw from `interpolation` instead of `transform`, keeping the size and facing where they have always been:
261
+
262
+ ```ts
263
+ const { x, y } = entity.components.interpolation ?? entity.components.transform!;
264
+ const { width, height, angle } = entity.components.transform!;
265
+ ```
266
+
267
+ Draw from it **every frame**, not from `position-updated`. That event fires once per physics step, which is
268
+ exactly the cadence interpolation exists to hide - a sprite moved by it would be as choppy as one reading the
269
+ transform. Once nothing is listening to it, `PhysicsSystem` stops reporting moves at all and the whole event
270
+ costs nothing; see [the note above](#the-step).
271
+
272
+ **Only rendering should read it.** Range checks, targeting, AI steering, "can I build here" and anything you
273
+ persist all want `transform` - the real position. The render position is a function of local frame timing, so
274
+ two machines drawing the same simulation at 144Hz and 30Hz hold *different* numbers in it at the same tick;
275
+ anything that branches on it stops being deterministic. Nothing in this library ever reads it back, which is
276
+ what keeps the simulation itself unaffected.
277
+
278
+ ### A slow physics run costs latency and nothing else
279
+
280
+ This is the property the whole thing is for, and it is worth being explicit about because it is easy to build
281
+ something that looks right and does not have it. **If a physics run takes longer than a frame, nothing about the
282
+ motion changes** - it is drawn a little further behind, at exactly the same speed. A worker that consistently
283
+ takes 30ms to come back costs 30ms of latency, which is not something an eye can see; only *variance* in how
284
+ late it is can be seen at all.
285
+
286
+ That works because the pacing is driven by the steps that have **landed** in the block, not by the physics
287
+ system's own accumulator. The accumulator resets when a run is *posted*, and on a worker thread that is not when
288
+ its results arrive - a run posted with 14ms of leftover and taking 30ms to come back leaves a whole frame where
289
+ the accumulator says "0.3 of the way into the new step" while the block still holds the one before it, so the
290
+ entity is drawn 0.3 along a segment it was drawn 0.96 along last frame. Backwards, then a lurch forward, on
291
+ every step.
292
+
293
+ For the same reason each run publishes **how much simulated time it covered** rather than that being assumed to
294
+ be the step: a run that comes back late leaves more than a step's worth banked, and the next one covers two at
295
+ once. Dividing that segment by the step would draw it at double speed.
296
+
297
+ Ordering against the physics system is only a preference - after it means a step is picked up on the frame it
298
+ happened rather than the one after, which is one frame of latency and nothing else.
299
+
300
+ ### What it does not need
301
+
302
+ - **Pause and `timeScale`** are free. `BaseWorld#runUpdate` skips every system while paused and scales the
303
+ elapsed time it hands them, so the pacing stops and slows with the simulation on its own.
304
+ - **Spawns** are free. The block is seeded from the entity's config, so something added between steps is drawn
305
+ standing where it was put rather than sliding in from the origin.
306
+ - **Velocity changes, collisions and coming to rest** are all just steps the simulation took, and are drawn as
307
+ they happened.
308
+
309
+ ### What it does need: `snapEntity`
310
+
311
+ A teleport and a long move are the same two numbers, so this is the one thing blending cannot work out for
312
+ itself. Write a transform directly and the entity is drawn sliding the whole way there over the next step:
313
+
314
+ ```ts
315
+ import { snapEntity } from '@daneren2005/shared-memory-physics';
316
+
317
+ entity.components.transform!.x = 500;
318
+ snapEntity(entity); // drawn at 500 on the very next frame
319
+ ```
320
+
321
+ ### The cost
322
+
323
+ 32 bytes per interpolated entity, three extra writes per entity per physics step, and a per-frame pass of one
324
+ lerp per entity on the main thread - order 0.2ms at 10,000 entities. An entity without the component pays
325
+ nothing at all. `forceMainThread: false` plus a `getWorker` moves the pass to a worker for worlds large enough
326
+ that it shows up in a profile, at the price of the render position being one frame stale; because consumers only
327
+ ever read `interpolation.x`, that is a constructor flag rather than a migration.
328
+
329
+ Physics writes `prev` and then the transform on the worker thread while the main thread reads both, so the block
330
+ carries a tick stamp that is written **last**, with a release store. The update reads it on either side of its
331
+ own reads and drops the frame if it changed, rather than blending a `prev` from one step against a transform
332
+ from another - which is the one mismatch that would draw an entity moving backwards.
333
+
170
334
  ## Collisions
171
335
 
172
- `createPhysicsUpdate` builds an update that, right after moving each entity, tells a callback of yours what
173
- that entity has moved into. The callback runs wherever the update runs - on the worker thread, in the same
174
- pass as the movement - so it cannot be handed to `PhysicsSystem` at construction time: functions do not
175
- survive `postMessage`. It reaches the worker by being baked into a module that both the worker file and the
176
- main thread import.
336
+ `createPhysicsUpdate` builds an update whose entities notice each other: every move is swept against whatever
337
+ is in front of it, so nothing is ever moved into anything else, and a callback of yours is told what each
338
+ entity ran into. The callback runs wherever the update runs - on the worker thread, in the same pass as the
339
+ movement - so it cannot be handed to `PhysicsSystem` at construction time: functions do not survive
340
+ `postMessage`. It reaches the worker by being baked into a module that both the worker file and the main
341
+ thread import.
342
+
343
+ The callback is optional and the [sweep](#stopping-at-the-edge) is not. `createPhysicsUpdate()` with nothing
344
+ asked of it at all is movement that comes to rest against walls, terrain and other units and says nothing
345
+ about any of it:
346
+
347
+ ```ts
348
+ // Stopped by everything, responds to nothing.
349
+ export const gamePhysicsUpdate = createPhysicsUpdate<Components>();
350
+ ```
351
+
352
+ Movement that walks straight through the world is the bare [`physicsUpdate`](#using-physicsupdate-in-your-own-system)
353
+ instead, which is also the only one that costs nothing per entity for the collidable query.
177
354
 
178
355
  ```ts
179
356
  // game-physics-update.ts - imported by *both* the worker file and the system below
@@ -247,6 +424,9 @@ An entity is never moved into another one. Before anything is written to the tra
247
424
  against everything along its path, and the entity is put down on the edge of whatever is in the way - then
248
425
  `onCollision` runs for it, exactly as it would if the two had ended up overlapping.
249
426
 
427
+ This is every update `createPhysicsUpdate` builds, with or without a callback: where an entity is allowed to
428
+ end up is physics' decision, and what the game does about the contact is a separate one.
429
+
250
430
  ```ts
251
431
  // A ship at x = 0 and a station at x = 30, both 10 wide, with a velocity that asks for 25 in this run.
252
432
  world.update(1000);
@@ -410,11 +590,48 @@ ask it directly rather than building the sort yourself - it settles as soon as t
410
590
  measured to an entity's **box**, not to its centre, so a large target is as near as its nearest edge and
411
591
  `maxDistance` reads as a range past the hull.
412
592
 
593
+ ## Examples
594
+
595
+ Three runnable examples live in [`examples/`](./examples), rendered with [Phaser](https://phaser.io) (a dev
596
+ dependency - it is not part of the library) and driven by plain HTML controls beside the canvas:
597
+
598
+ ```sh
599
+ npm start # http://127.0.0.1:8080
600
+ ```
601
+
602
+ - **Bouncing circles** - circles with a random position and heading, reflected off each other and off the
603
+ walls by an `onCollision` that mirrors the velocity about the contact normal.
604
+ - **Bouncing rectangles** - the same, on boxes of random width and height, where the normal is the axis the
605
+ two are least through each other on rather than the line between their centres.
606
+ - **Walking into a wall** - one unit between two boxes, turned around on a timer so it presses into each of
607
+ them in turn. It starts on a deliberately long 100ms physics step, so turning **interpolate rendering** off is
608
+ a direct before-and-after on the same running scene: the same simulation, drawn six frames at a time instead
609
+ of one.
610
+
611
+ Every page has a **physics step** slider (starting at the library's own 50ms) and an **interpolate rendering**
612
+ toggle, so the trade the [interpolation section](#interpolation) describes can be looked at rather than
613
+ reasoned about.
614
+
615
+ The examples import the library as `@daneren2005/shared-memory-physics`, aliased at the source next door, so
616
+ the code reads the way a game's would and editing a system hot-reloads the running page. `npm start` also
617
+ serves the two [cross-origin isolation](https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated)
618
+ headers `SharedArrayBuffer` needs, so physics genuinely runs on a worker there; where a browser will not hand
619
+ one out, the ECS runs the same update in-process instead and the **Run physics in a worker** toggle says so.
620
+
621
+ The walls the examples bounce off are ordinary entities - a transform and a body, no velocity - so they are
622
+ stopped against by the same sweep that stops anything against anything.
623
+
624
+ Every push to `dev` or `production` publishes them to GitHub Pages, both to the same place - so the site shows
625
+ whichever branch pushed last, which is what you want while the examples are changing faster than releases are
626
+ cut. See [`.github/workflows/pages.yml`](./.github/workflows/pages.yml) for the one-time repository setting it
627
+ needs, and for the one line to drop when it should go back to publishing releases only.
628
+
413
629
  ## Building
414
630
 
415
631
  ```sh
416
632
  npm install
417
- npm run build # emits dist/ (js + d.ts)
633
+ npm run build # emits dist/ (js + d.ts)
634
+ npm run build:examples # emits examples/dist/
418
635
  npm run type-check
419
636
  npm test
420
637
  ```
@@ -4,6 +4,7 @@ export interface BodyComponent {
4
4
  shape: number;
5
5
  collideCategory: number;
6
6
  collideMask: number;
7
+ sensor: boolean;
7
8
  }
8
9
  export declare const SHAPE_RECTANGLE = 1;
9
10
  export declare const SHAPE_CIRCLE = 2;
@@ -18,10 +19,13 @@ export interface BodyConfig {
18
19
  radius?: number;
19
20
  collideCategory?: number;
20
21
  collideMask?: number;
22
+ sensor?: boolean;
21
23
  }
22
24
  export declare const BODY_SHAPE_INDEX = 0;
23
25
  export declare const BODY_CATEGORY_INDEX = 1;
24
26
  export declare const BODY_MASK_INDEX = 2;
25
- export declare const BODY_SIZE = 3;
27
+ export declare const BODY_SENSOR_INDEX = 3;
28
+ export declare const BODY_SIZE = 4;
29
+ export declare function isSensor(body: Uint32Array): boolean;
26
30
  export declare const bodyDefinition: ComponentDefinition<BodyComponent, Uint32Array, BodyConfig>;
27
31
  export default bodyDefinition;
@@ -0,0 +1,12 @@
1
+ import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';
2
+ export interface BouncinessComponent {
3
+ index: number;
4
+ bounciness: number;
5
+ }
6
+ export interface BouncinessConfig {
7
+ bounciness?: number;
8
+ }
9
+ export declare const BOUNCINESS_INDEX = 0;
10
+ export declare const BOUNCINESS_SIZE = 1;
11
+ export declare const bouncinessDefinition: ComponentDefinition<BouncinessComponent, Float32Array, BouncinessConfig>;
12
+ export default bouncinessDefinition;
@@ -0,0 +1,32 @@
1
+ import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';
2
+ import type { TransformComponent } from './transform-component';
3
+ export interface InterpolationComponent {
4
+ index: number;
5
+ x: number;
6
+ y: number;
7
+ prevX: number;
8
+ prevY: number;
9
+ }
10
+ export interface InterpolationConfig {
11
+ interpolate?: boolean;
12
+ x?: number;
13
+ y?: number;
14
+ }
15
+ export declare const INTERPOLATION_X_INDEX = 0;
16
+ export declare const INTERPOLATION_Y_INDEX = 1;
17
+ export declare const INTERPOLATION_PREV_X_INDEX = 2;
18
+ export declare const INTERPOLATION_PREV_Y_INDEX = 3;
19
+ export declare const INTERPOLATION_PROGRESS_INDEX = 4;
20
+ export declare const INTERPOLATION_SYNCED_TICK_INDEX = 5;
21
+ export declare const INTERPOLATION_DURATION_INDEX = 6;
22
+ export declare const INTERPOLATION_TICK_INDEX = 7;
23
+ export declare const INTERPOLATION_SIZE = 8;
24
+ export declare const interpolationDefinition: ComponentDefinition<InterpolationComponent, Float32Array, InterpolationConfig>;
25
+ export interface SnappableEntity {
26
+ components: {
27
+ transform?: TransformComponent;
28
+ interpolation?: InterpolationComponent;
29
+ };
30
+ }
31
+ export declare function snapEntity(entity: SnappableEntity): void;
32
+ export default interpolationDefinition;
@@ -1,18 +1,34 @@
1
1
  import { type BodyComponent } from './body-component';
2
+ import { type BouncinessComponent } from './bounciness-component';
3
+ import { type InterpolationComponent } from './interpolation-component';
2
4
  import { type TransformComponent } from './transform-component';
3
5
  import { type VelocityComponent } from './velocity-component';
4
6
  export declare const physicsRegistry: {
5
7
  transform: import("@daneren2005/shared-memory-ecs").ComponentDefinition<TransformComponent, Float32Array<ArrayBufferLike>, import("./transform-component").TransformConfig, import("./transform-component").TransformSerialization>;
6
8
  velocity: import("@daneren2005/shared-memory-ecs").ComponentDefinition<VelocityComponent, Float32Array<ArrayBufferLike>, import("./velocity-component").VelocityConfig, import("./velocity-component").VelocityConfig>;
7
9
  body: import("@daneren2005/shared-memory-ecs").ComponentDefinition<BodyComponent, Uint32Array<ArrayBufferLike>, import("./body-component").BodyConfig, object>;
10
+ bounciness: import("@daneren2005/shared-memory-ecs").ComponentDefinition<BouncinessComponent, Float32Array<ArrayBufferLike>, import("./bounciness-component").BouncinessConfig, object>;
11
+ interpolation: import("@daneren2005/shared-memory-ecs").ComponentDefinition<InterpolationComponent, Float32Array<ArrayBufferLike>, import("./interpolation-component").InterpolationConfig, object>;
8
12
  };
9
13
  export type PhysicsComponents = {
10
14
  transform: TransformComponent;
11
15
  velocity: VelocityComponent;
12
16
  body: BodyComponent;
17
+ bounciness: BouncinessComponent;
18
+ interpolation: InterpolationComponent;
19
+ };
20
+ export type InterpolationComponents = {
21
+ transform: TransformComponent;
22
+ interpolation: InterpolationComponent;
13
23
  };
14
24
  export type PhysicsUpdateComponents = {
15
25
  transform: Float32Array;
16
26
  velocity: Float32Array;
17
27
  body?: Uint32Array;
28
+ bounciness?: Float32Array;
29
+ interpolation?: Float32Array;
30
+ };
31
+ export type InterpolationUpdateComponents = {
32
+ transform: Float32Array;
33
+ interpolation: Float32Array;
18
34
  };
package/dist/index.d.ts CHANGED
@@ -4,17 +4,26 @@ export type { TransformComponent, TransformConfig, TransformSerialization, } fro
4
4
  export { default as velocityDefinition } from './components/velocity-component';
5
5
  export { VELOCITY_X_INDEX, VELOCITY_Y_INDEX, VELOCITY_SIZE, } from './components/velocity-component';
6
6
  export type { VelocityComponent, VelocityConfig, VelocitySerialization, } from './components/velocity-component';
7
- export { default as bodyDefinition, canCollide } from './components/body-component';
8
- export { SHAPE_RECTANGLE, SHAPE_CIRCLE, SHAPE_CAPSULE, DEFAULT_COLLIDE_CATEGORY, DEFAULT_COLLIDE_MASK, BODY_SHAPE_INDEX, BODY_CATEGORY_INDEX, BODY_MASK_INDEX, BODY_SIZE, } from './components/body-component';
7
+ export { default as bodyDefinition, canCollide, isSensor } from './components/body-component';
8
+ export { SHAPE_RECTANGLE, SHAPE_CIRCLE, SHAPE_CAPSULE, DEFAULT_COLLIDE_CATEGORY, DEFAULT_COLLIDE_MASK, BODY_SHAPE_INDEX, BODY_CATEGORY_INDEX, BODY_MASK_INDEX, BODY_SENSOR_INDEX, BODY_SIZE, } from './components/body-component';
9
9
  export type { BodyComponent, BodyConfig, } from './components/body-component';
10
+ export { default as bouncinessDefinition } from './components/bounciness-component';
11
+ export { BOUNCINESS_INDEX, BOUNCINESS_SIZE, } from './components/bounciness-component';
12
+ export type { BouncinessComponent, BouncinessConfig, } from './components/bounciness-component';
13
+ export { default as interpolationDefinition, snapEntity } from './components/interpolation-component';
14
+ export { INTERPOLATION_X_INDEX, INTERPOLATION_Y_INDEX, INTERPOLATION_PREV_X_INDEX, INTERPOLATION_PREV_Y_INDEX, INTERPOLATION_PROGRESS_INDEX, INTERPOLATION_SYNCED_TICK_INDEX, INTERPOLATION_DURATION_INDEX, INTERPOLATION_TICK_INDEX, INTERPOLATION_SIZE, } from './components/interpolation-component';
15
+ export type { InterpolationComponent, InterpolationConfig, SnappableEntity, } from './components/interpolation-component';
10
16
  export { physicsRegistry } from './components/registry';
11
- export type { PhysicsComponents, PhysicsUpdateComponents } from './components/registry';
17
+ export type { PhysicsComponents, PhysicsUpdateComponents, InterpolationComponents, InterpolationUpdateComponents, } from './components/registry';
12
18
  export { shapesOverlap, shapeHalfWidth, shapeHalfHeight, shapeIsEmpty, shapeRadius, capsuleHalfLength, boundsHalfWidth, boundsHalfHeight, orientedBoxesOverlap, segmentSegmentDistanceSquared, segmentBoxDistanceSquared, pointSegmentDistanceSquared, } from './math/shapes';
13
19
  export { default as CollisionBroadphase, COLLIDABLE_QUERY } from './systems/collision';
14
- export type { CollisionComponents, CollisionEntity, CollisionFunction, MovingEntity, SweepResult } from './systems/collision';
20
+ export type { CollisionComponents, CollisionEntity, CollisionFunction, MoveResult, MovingEntity, SweepResult } from './systems/collision';
15
21
  export { default as SpatialIndex } from './systems/spatial-index';
16
22
  export type { SpatialComponents, SpatialEntity, SpatialFilter } from './systems/spatial-index';
17
23
  export { default as physicsUpdate, createPhysicsUpdate, POSITION_UPDATED_EVENT } from './systems/physics-update';
18
- export type { PhysicsUpdateFunction, PhysicsUpdateMetadata, PhysicsUpdateOptions } from './systems/physics-update';
19
- export { default as PhysicsSystem } from './systems/physics-system';
24
+ export type { PhysicsUpdateFunction, PhysicsUpdateMetadata, PhysicsUpdateOptions, PhysicsWorld } from './systems/physics-update';
25
+ export { default as PhysicsSystem, DEFAULT_PHYSICS_STEP_MS } from './systems/physics-system';
20
26
  export type { PhysicsSystemConfig } from './systems/physics-system';
27
+ export { default as interpolationUpdate } from './systems/interpolation-update';
28
+ export { default as InterpolationSystem } from './systems/interpolation-system';
29
+ export type { InterpolationSystemConfig } from './systems/interpolation-system';