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