@daneren2005/shared-memory-physics 1.3.0 → 1.5.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 +73 -8
- package/dist/components/body-component.d.ts +3 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +564 -393
- package/dist/index.js.map +1 -1
- package/dist/systems/collision.d.ts +18 -0
- package/dist/systems/physics-system.d.ts +5 -1
- package/dist/systems/physics-update.d.ts +20 -1
- package/dist/systems/spatial-bounds.d.ts +13 -0
- package/dist/world.d.ts +30 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -362,6 +362,39 @@ A no-op for an entity with no velocity or no interpolation, or while the system
|
|
|
362
362
|
by hand rather than through `PhysicsSystem`? Call the underlying `startSpawnInterpolation(entity, { stepMs,
|
|
363
363
|
stepFraction, tick })` directly.
|
|
364
364
|
|
|
365
|
+
### A moving death: `dieAtImpact`
|
|
366
|
+
|
|
367
|
+
The same lag runs the other way at the end of a life. A fast entity moves a whole step at a time and, with
|
|
368
|
+
`continuousCollisionDetection` on, is killed the run its *swept path* first crosses what it hits - which can be a
|
|
369
|
+
near-full stride before its step actually lands there. Remove it on the spot and the render, itself a step behind,
|
|
370
|
+
draws it blinking out well short of the target. A bullet dies in mid-air.
|
|
371
|
+
|
|
372
|
+
`dieAtImpact` fixes it as the mirror of the spawn seeding: instead of `callbacks.entityDied`, a collision callback
|
|
373
|
+
calls it to **end the dying entity's current segment on the exact point it struck** (`CollisionBroadphase.contactPoint`,
|
|
374
|
+
the same entry fraction the sweep already refines) and **defer the kill by one run** - the one extra step the render
|
|
375
|
+
needs to play that last stride onto the impact before the entity is gone:
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
onCollision(world, self, other, queries, callbacks) {
|
|
379
|
+
// `self` (a fast sensor bullet) just swept through `other`. Draw it reaching `other`, then die - rather than
|
|
380
|
+
// vanishing a stride short.
|
|
381
|
+
world.dieAtImpact(self, other);
|
|
382
|
+
|
|
383
|
+
// Gameplay still happens now: the hit lands this run, only the visual removal waits.
|
|
384
|
+
callbacks.emitEntityEvent(other.entityId, 'hit');
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Either side of the pair can be the one to kill - `dieAtImpact(dying, other)` takes whichever carried the fatal
|
|
389
|
+
role, working out that entity's own approach from its velocity. The struck entity is marked with a runtime `dying`
|
|
390
|
+
bit (`BODY_DYING_FLAG`) that leaves it out of the broadphase at once, so nothing runs into it again while it plays
|
|
391
|
+
out, and it is removed at the top of its next update. The bit lives in shared memory, so any worker stepping the
|
|
392
|
+
entity honours it. Nothing clears it - a dying entity is gone a run later.
|
|
393
|
+
|
|
394
|
+
`dieAtImpact` is set on the world every run by `createPhysicsUpdate`; keep a `callbacks.entityDied` fallback if you
|
|
395
|
+
call `onCollision` by hand without a `preRun`. It moves position only - raise your own hit or score event on the
|
|
396
|
+
spot, as above.
|
|
397
|
+
|
|
365
398
|
### The cost
|
|
366
399
|
|
|
367
400
|
32 bytes per interpolated entity, three extra writes per entity per physics step, and a per-frame pass of one
|
|
@@ -458,7 +491,9 @@ value another thread also touches with the atomics from `@daneren2005/shared-mem
|
|
|
458
491
|
the two entities in front of it - to credit a third for a kill, say - a callback can walk
|
|
459
492
|
`queries[COLLIDABLE_QUERY]`, the full collidable list. Anything that has to happen on the main thread goes
|
|
460
493
|
through `callbacks`: `entityDied`, `entityComponentChanged`, `createEntity`, and `emitEntityEvent` for an
|
|
461
|
-
event of your own - the same one the move above reports itself through.
|
|
494
|
+
event of your own - the same one the move above reports itself through. To kill a *fast* mover on impact and
|
|
495
|
+
still have it drawn reaching what it hit, call `world.dieAtImpact(dying, other)` in place of `entityDied` - see
|
|
496
|
+
[`dieAtImpact`](#a-moving-death-dieatimpact).
|
|
462
497
|
|
|
463
498
|
**What collides.** Everything with a transform and a `body`, not only the entities the system moves, so a
|
|
464
499
|
ship can run into a station that has no velocity of its own. Shapes that only just touch do not count as
|
|
@@ -632,14 +667,44 @@ body just swept, while the two-argument form (and every non-continuous body) tes
|
|
|
632
667
|
|
|
633
668
|
## Spatial queries
|
|
634
669
|
|
|
635
|
-
`
|
|
636
|
-
the
|
|
637
|
-
|
|
638
|
-
|
|
670
|
+
`PhysicalWorld` keeps every transformed entity in a live, shared `SharedSpatialMap`. The physics worker updates
|
|
671
|
+
the entry immediately after it writes a final position, while entity add/remove and transform/body component
|
|
672
|
+
changes are tracked on the main thread. That makes the same index available for occasional main-thread checks
|
|
673
|
+
such as picking an entity under the mouse, and for worker systems that would otherwise rebuild an index for only
|
|
674
|
+
a few queries:
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
import { PhysicalWorld, physicsRegistry } from '@daneren2005/shared-memory-physics';
|
|
678
|
+
|
|
679
|
+
const world = new PhysicalWorld(physicsRegistry, {
|
|
680
|
+
spatial: { gridSize: 50, maxEntities: 100_000 },
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
const clicked = world.findNearestSpatial(mouseX, mouseY, 0);
|
|
684
|
+
const nearby = world.searchSpatialAround(x, y, 100, 100);
|
|
685
|
+
```
|
|
639
686
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
687
|
+
`searchSpatial`, `searchSpatialAround`, `findNearestSpatial`, and `findNearbySpatial` return live entities and
|
|
688
|
+
accept entity filters. Shape-aware axis-aligned bounds are stored: rotated rectangles and capsules occupy the
|
|
689
|
+
box around their actual outline. A direct transform/body edit outside the physics update must be followed by
|
|
690
|
+
`world.updateSpatialEntity(entity)`. A custom physics wrapper that changes a transform after calling the library
|
|
691
|
+
update can call `updateSpatialMap(workerWorld, entityId, components)` instead.
|
|
692
|
+
|
|
693
|
+
`PhysicsSystem` requires a `PhysicalWorld` (or another `BaseWorld` that implements `PhysicalWorldSource`) and
|
|
694
|
+
automatically shares its map with the physics worker. Another worker system can receive the same map by calling
|
|
695
|
+
`addPhysicalWorldData(sourceWorld, runWorld)` from its system's `addDataToWorld`, then `getSpatialMap(runWorld)` in
|
|
696
|
+
its update. The worker world must come from `@daneren2005/shared-memory-ecs` 1.5.1 or newer so it carries the
|
|
697
|
+
shared heap used to reconstruct the map handle.
|
|
698
|
+
|
|
699
|
+
The live map is intentionally not the collision or bulk-query hot path. `CollisionBroadphase` and
|
|
700
|
+
`SpatialIndex` still use Flatbush snapshots: packing one immutable R-tree per run is faster when that run will
|
|
701
|
+
make a large number of queries. Use the live map for continuous availability and sparse queries; use
|
|
702
|
+
`SpatialIndex` for query-heavy systems such as targeting every entity every tick.
|
|
703
|
+
|
|
704
|
+
`SpatialIndex` is the same R-tree the collision broadphase is built on, without collide categories or shape
|
|
705
|
+
tests. Build one from anything shaped like a query result and it reads the transform (and the body, to know which
|
|
706
|
+
outline that transform describes) off each entity. Only the transform is needed, so it indexes anything with a
|
|
707
|
+
place in the world rather than only what collides:
|
|
643
708
|
|
|
644
709
|
```ts
|
|
645
710
|
import { SpatialIndex } from '@daneren2005/shared-memory-physics';
|
|
@@ -30,8 +30,11 @@ export declare const BODY_SIZE = 3;
|
|
|
30
30
|
export declare const BODY_SHAPE_MASK = 3;
|
|
31
31
|
export declare const BODY_SENSOR_FLAG = 4;
|
|
32
32
|
export declare const BODY_CCD_FLAG = 8;
|
|
33
|
+
export declare const BODY_DYING_FLAG = 16;
|
|
33
34
|
export declare function bodyShape(body: Uint32Array): number;
|
|
34
35
|
export declare function isSensor(body: Uint32Array): boolean;
|
|
35
36
|
export declare function isContinuous(body: Uint32Array): boolean;
|
|
37
|
+
export declare function isDying(body: Uint32Array): boolean;
|
|
38
|
+
export declare function markDying(body: Uint32Array): void;
|
|
36
39
|
export declare const bodyDefinition: ComponentDefinition<BodyComponent, Uint32Array, BodyConfig>;
|
|
37
40
|
export default bodyDefinition;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ 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, isSensor, isContinuous, bodyShape } from './components/body-component';
|
|
8
|
-
export { SHAPE_RECTANGLE, SHAPE_CIRCLE, SHAPE_CAPSULE, DEFAULT_COLLIDE_CATEGORY, DEFAULT_COLLIDE_MASK, BODY_FLAGS_INDEX, BODY_SHAPE_MASK, BODY_SENSOR_FLAG, BODY_CCD_FLAG, BODY_CATEGORY_INDEX, BODY_MASK_INDEX, BODY_SIZE, } from './components/body-component';
|
|
7
|
+
export { default as bodyDefinition, canCollide, isSensor, isContinuous, isDying, markDying, bodyShape } from './components/body-component';
|
|
8
|
+
export { SHAPE_RECTANGLE, SHAPE_CIRCLE, SHAPE_CAPSULE, DEFAULT_COLLIDE_CATEGORY, DEFAULT_COLLIDE_MASK, BODY_FLAGS_INDEX, BODY_SHAPE_MASK, BODY_SENSOR_FLAG, BODY_CCD_FLAG, BODY_DYING_FLAG, BODY_CATEGORY_INDEX, BODY_MASK_INDEX, BODY_SIZE, } from './components/body-component';
|
|
9
9
|
export type { BodyComponent, BodyConfig, } from './components/body-component';
|
|
10
10
|
export { default as bouncinessDefinition } from './components/bounciness-component';
|
|
11
11
|
export { BOUNCINESS_INDEX, BOUNCINESS_SIZE, } from './components/bounciness-component';
|
|
@@ -15,14 +15,16 @@ export { INTERPOLATION_X_INDEX, INTERPOLATION_Y_INDEX, INTERPOLATION_PREV_X_INDE
|
|
|
15
15
|
export type { InterpolationComponent, InterpolationConfig, SnappableEntity, SpawnableEntity, SpawnInterpolationOptions, } from './components/interpolation-component';
|
|
16
16
|
export { physicsRegistry } from './components/registry';
|
|
17
17
|
export type { PhysicsComponents, PhysicsUpdateComponents, InterpolationComponents, InterpolationUpdateComponents, } from './components/registry';
|
|
18
|
+
export { default as PhysicalWorld, addPhysicalWorldData, getSpatialMap } from './world';
|
|
19
|
+
export type { PhysicalSystemWorld, PhysicalWorldData, PhysicalWorldSource, PhysicalWorldEntity, PhysicalWorldFilter, PhysicalWorldOptions, } from './world';
|
|
18
20
|
export { shapesOverlap, contactNormal, shapeHalfWidth, shapeHalfHeight, shapeIsEmpty, shapeRadius, capsuleHalfLength, boundsHalfWidth, boundsHalfHeight, orientedBoxesOverlap, segmentSegmentDistanceSquared, segmentBoxDistanceSquared, pointSegmentDistanceSquared, } from './math/shapes';
|
|
19
21
|
export type { Vector } from './math/shapes';
|
|
20
22
|
export { default as CollisionBroadphase, COLLIDABLE_QUERY } from './systems/collision';
|
|
21
23
|
export type { CollisionComponents, CollisionEntity, CollisionFunction, MoveResult, MovingEntity, SweepResult } from './systems/collision';
|
|
22
24
|
export { default as SpatialIndex } from './systems/spatial-index';
|
|
23
25
|
export type { SpatialComponents, SpatialEntity, SpatialFilter } from './systems/spatial-index';
|
|
24
|
-
export { default as physicsUpdate, createPhysicsUpdate, POSITION_UPDATED_EVENT } from './systems/physics-update';
|
|
25
|
-
export type { PhysicsUpdateFunction, PhysicsUpdateMetadata, PhysicsUpdateOptions, PhysicsWorld } from './systems/physics-update';
|
|
26
|
+
export { default as physicsUpdate, createPhysicsUpdate, POSITION_UPDATED_EVENT, updateSpatialMap } from './systems/physics-update';
|
|
27
|
+
export type { DeathInterpolationEntity, PhysicsGroupConfig, PhysicsUpdateFunction, PhysicsUpdateMetadata, PhysicsUpdateOptions, PhysicsWorld } from './systems/physics-update';
|
|
26
28
|
export { default as PhysicsSystem, DEFAULT_PHYSICS_STEP_MS } from './systems/physics-system';
|
|
27
29
|
export type { PhysicsSystemConfig } from './systems/physics-system';
|
|
28
30
|
export { default as interpolationUpdate } from './systems/interpolation-update';
|