@codexo/exojs-physics 0.13.0 → 0.15.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 +17 -10
- package/dist/esm/Collider.d.ts +17 -6
- package/dist/esm/Collider.js +28 -6
- package/dist/esm/Collider.js.map +1 -1
- package/dist/esm/ContactGraph.d.ts +49 -3
- package/dist/esm/ContactGraph.js +132 -44
- package/dist/esm/ContactGraph.js.map +1 -1
- package/dist/esm/PhysicsBody.d.ts +145 -15
- package/dist/esm/PhysicsBody.js +282 -21
- package/dist/esm/PhysicsBody.js.map +1 -1
- package/dist/esm/PhysicsWorld.d.ts +177 -39
- package/dist/esm/PhysicsWorld.js +412 -35
- package/dist/esm/PhysicsWorld.js.map +1 -1
- package/dist/esm/backend/NativePhysicsBackend.d.ts +5 -0
- package/dist/esm/backend/NativePhysicsBackend.js +14 -0
- package/dist/esm/backend/NativePhysicsBackend.js.map +1 -1
- package/dist/esm/backend/PhysicsBackend.d.ts +9 -1
- package/dist/esm/binding/BindingRegistry.d.ts +1 -2
- package/dist/esm/binding/BindingRegistry.js +2 -2
- package/dist/esm/binding/BindingRegistry.js.map +1 -1
- package/dist/esm/binding/PhysicsBinding.d.ts +7 -18
- package/dist/esm/binding/PhysicsBinding.js +9 -8
- package/dist/esm/binding/PhysicsBinding.js.map +1 -1
- package/dist/esm/broadphase/SweepAndPrune.d.ts +1 -0
- package/dist/esm/broadphase/SweepAndPrune.js +32 -3
- package/dist/esm/broadphase/SweepAndPrune.js.map +1 -1
- package/dist/esm/collision/CollisionProxy.d.ts +2 -2
- package/dist/esm/collision/narrowphase.js +91 -38
- package/dist/esm/collision/narrowphase.js.map +1 -1
- package/dist/esm/debug/PhysicsDebugDraw.d.ts +8 -1
- package/dist/esm/debug/PhysicsDebugDraw.js +26 -2
- package/dist/esm/debug/PhysicsDebugDraw.js.map +1 -1
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/joints/DistanceJoint.d.ts +71 -0
- package/dist/esm/joints/DistanceJoint.js +176 -0
- package/dist/esm/joints/DistanceJoint.js.map +1 -0
- package/dist/esm/joints/Joint.d.ts +25 -0
- package/dist/esm/joints/Joint.js +24 -0
- package/dist/esm/joints/Joint.js.map +1 -0
- package/dist/esm/joints/MouseJoint.d.ts +57 -0
- package/dist/esm/joints/MouseJoint.js +137 -0
- package/dist/esm/joints/MouseJoint.js.map +1 -0
- package/dist/esm/joints/PrismaticJoint.d.ts +85 -0
- package/dist/esm/joints/PrismaticJoint.js +241 -0
- package/dist/esm/joints/PrismaticJoint.js.map +1 -0
- package/dist/esm/joints/RevoluteJoint.d.ts +81 -0
- package/dist/esm/joints/RevoluteJoint.js +217 -0
- package/dist/esm/joints/RevoluteJoint.js.map +1 -0
- package/dist/esm/joints/WeldJoint.d.ts +61 -0
- package/dist/esm/joints/WeldJoint.js +159 -0
- package/dist/esm/joints/WeldJoint.js.map +1 -0
- package/dist/esm/joints/WheelJoint.d.ts +92 -0
- package/dist/esm/joints/WheelJoint.js +256 -0
- package/dist/esm/joints/WheelJoint.js.map +1 -0
- package/dist/esm/math.js +15 -1
- package/dist/esm/math.js.map +1 -1
- package/dist/esm/physicsBuildInfo.js +2 -2
- package/dist/esm/public.d.ts +9 -2
- package/dist/esm/query/QueryEngine.d.ts +2 -2
- package/dist/esm/query/QueryEngine.js +13 -4
- package/dist/esm/query/QueryEngine.js.map +1 -1
- package/dist/esm/shapes/AnyShape.d.ts +9 -0
- package/dist/esm/shapes/CircleShape.d.ts +1 -2
- package/dist/esm/shapes/CircleShape.js.map +1 -1
- package/dist/esm/shapes/PolygonShape.d.ts +1 -2
- package/dist/esm/shapes/PolygonShape.js +45 -17
- package/dist/esm/shapes/PolygonShape.js.map +1 -1
- package/dist/esm/shapes/index.d.ts +1 -0
- package/dist/esm/solver/ContactSolver.d.ts +87 -0
- package/dist/esm/solver/ContactSolver.js +490 -0
- package/dist/esm/solver/ContactSolver.js.map +1 -0
- package/dist/esm/sort.d.ts +17 -0
- package/dist/esm/sort.js +54 -0
- package/dist/esm/sort.js.map +1 -0
- package/package.json +3 -3
|
@@ -2,48 +2,108 @@ import type { SceneNode } from '@codexo/exojs';
|
|
|
2
2
|
import { Signal, Vector } from '@codexo/exojs';
|
|
3
3
|
import type { Aabb } from './Aabb';
|
|
4
4
|
import type { PhysicsBackend } from './backend/PhysicsBackend';
|
|
5
|
-
import type {
|
|
6
|
-
import
|
|
5
|
+
import type { PhysicsBinding } from './binding/PhysicsBinding';
|
|
6
|
+
import { Collider } from './Collider';
|
|
7
7
|
import type { CollisionEvent, SensorEvent } from './events';
|
|
8
|
-
import type {
|
|
8
|
+
import type { Joint } from './joints/Joint';
|
|
9
|
+
import type { BodyOwner } from './PhysicsBody';
|
|
9
10
|
import { PhysicsBody } from './PhysicsBody';
|
|
10
11
|
import type { QueryFilter, RayHit } from './query/QueryEngine';
|
|
11
|
-
import type {
|
|
12
|
+
import type { AnyShape } from './shapes/AnyShape';
|
|
12
13
|
import { TimeStepper } from './TimeStepper';
|
|
13
|
-
import type { VectorLike } from './types';
|
|
14
|
+
import type { BodyType, CollisionFilter, VectorLike } from './types';
|
|
14
15
|
/** Construction options for a {@link PhysicsWorld}. */
|
|
15
16
|
export interface PhysicsWorldOptions {
|
|
16
|
-
/** Gravity in px/s² (+Y down).
|
|
17
|
+
/** Gravity in px/s² (+Y down). Integrated each sub-step. Default `(0, 0)`. */
|
|
17
18
|
gravity?: VectorLike;
|
|
18
19
|
/** Fixed timestep in seconds. Default `1 / 60`. */
|
|
19
20
|
fixedDelta?: number;
|
|
20
|
-
/** Maximum
|
|
21
|
+
/** Maximum fixed steps per `step` call (spiral-of-death guard). Default `8`. */
|
|
21
22
|
maxSubSteps?: number;
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
/**
|
|
24
|
+
* TGS-Soft sub-steps per fixed step (the solver's stiffness scales with this,
|
|
25
|
+
* not iteration count). Default `4`. Must be ≥ 1. Values below `2` visibly
|
|
26
|
+
* degrade tall-stack stability (a 10-box tower jitters at `1`), so the default
|
|
27
|
+
* is load-bearing — do not lower it for performance.
|
|
28
|
+
*/
|
|
29
|
+
subStepCount?: number;
|
|
30
|
+
/** Soft-contact stiffness in Hz (the contact behaves as a damped spring at this frequency). Default `30`. */
|
|
31
|
+
contactHertz?: number;
|
|
32
|
+
/** Soft-contact damping ratio (≥ 1 keeps contacts from oscillating). Default `10`. */
|
|
33
|
+
dampingRatio?: number;
|
|
34
|
+
/** Put resting bodies to sleep so they skip integration and solving. Default `true`. */
|
|
35
|
+
enableSleeping?: boolean;
|
|
36
|
+
/** Linear speed at or below which a body is a sleep candidate, px/s. Default `5`. */
|
|
37
|
+
sleepLinearVelocity?: number;
|
|
38
|
+
/** Angular speed at or below which a body is a sleep candidate, rad/s. Default `0.06`. */
|
|
39
|
+
sleepAngularVelocity?: number;
|
|
40
|
+
/** Seconds a body must stay below the sleep thresholds before it sleeps. Default `0.5`. */
|
|
41
|
+
timeToSleep?: number;
|
|
28
42
|
}
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
/**
|
|
44
|
+
* {@link PhysicsWorld.attach} convenience options: a body type plus a single
|
|
45
|
+
* collider, attached to a scene node in one call.
|
|
46
|
+
*/
|
|
47
|
+
export interface AttachOptions {
|
|
48
|
+
/** Simulation role of the created body. Default `'dynamic'`. */
|
|
49
|
+
type?: BodyType;
|
|
50
|
+
/** Initial world position of the body. Default the node's position is left untouched and `(0, 0)` is used. */
|
|
32
51
|
position?: VectorLike;
|
|
33
|
-
/**
|
|
52
|
+
/** Initial rotation (radians) of the body. Default `0`. */
|
|
34
53
|
angle?: number;
|
|
54
|
+
/** Per-body multiplier on world gravity. Default `1`. */
|
|
55
|
+
gravityScale?: number;
|
|
56
|
+
/** When `true`, the body never rotates under contacts. Default `false`. */
|
|
57
|
+
fixedRotation?: boolean;
|
|
58
|
+
/** The collider geometry. */
|
|
59
|
+
shape: AnyShape;
|
|
60
|
+
/** Body-local offset of the collider. Default `(0, 0)`. */
|
|
61
|
+
offset?: VectorLike;
|
|
62
|
+
/** Body-local rotation of the collider (radians). Default `0`. */
|
|
63
|
+
rotation?: number;
|
|
64
|
+
/** Collider density (mass per px²). Default `1`. */
|
|
65
|
+
density?: number;
|
|
66
|
+
/** Coulomb friction coefficient. Default `0.2`. */
|
|
67
|
+
friction?: number;
|
|
68
|
+
/** Restitution / bounciness in `[0, 1]`. Default `0`. */
|
|
69
|
+
restitution?: number;
|
|
70
|
+
/** When `true`, the collider generates overlap events but no contact response. Default `false`. */
|
|
71
|
+
isSensor?: boolean;
|
|
72
|
+
/** Category/mask/group collision filter; partials merge over the defaults. */
|
|
73
|
+
filter?: Partial<CollisionFilter>;
|
|
35
74
|
}
|
|
36
75
|
/**
|
|
37
76
|
* The collision/query world: owns bodies, colliders, the detection backend,
|
|
38
77
|
* bindings, the query engine and the fixed-step accumulator. Stepped by the
|
|
39
|
-
* caller (commonly from a `Scene.update`),
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
78
|
+
* caller (commonly from a `Scene.update`), each fixed sub-step it integrates
|
|
79
|
+
* body velocities, runs broad- and narrow-phase detection, solves contacts and
|
|
80
|
+
* integrates positions, then fires immutable contact/sensor events and writes
|
|
81
|
+
* bound node transforms. It holds **no module-level state**, so any number of
|
|
82
|
+
* worlds run in isolation (gate I-1).
|
|
43
83
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
84
|
+
* The dynamics are a native, warm-started **TGS-Soft** solver (Box2D-v3 "soft
|
|
85
|
+
* step"): each fixed step runs detection once, then several sub-steps, each
|
|
86
|
+
* integrating gravity over the sub-step and solving contacts with a soft
|
|
87
|
+
* position bias plus a bias-free relax pass; a 2-point block normal solve
|
|
88
|
+
* propagates stack loads, and restitution is a separate final pass. Decoupling
|
|
89
|
+
* stiffness from the iteration count keeps tall towers stable. The detection
|
|
90
|
+
* backend sits behind an internal seam, so the solver is swappable without
|
|
91
|
+
* touching this public surface.
|
|
92
|
+
*
|
|
93
|
+
* **Operating envelope.** The soft solver trades a little accuracy for
|
|
94
|
+
* robustness, so it has a few documented limits — each stays finite/stable and
|
|
95
|
+
* each is pinned by a gate in `dynamics.test.ts`:
|
|
96
|
+
* - **Mass ratio** — resting stacks are slop-accurate up to ~100:1. Beyond that
|
|
97
|
+
* the velocity-capped soft push-out (`maxBiasVelocity`) lets the lighter body
|
|
98
|
+
* settle progressively deeper (≈6px at 500:1, fully through a thin floor by
|
|
99
|
+
* ~5000:1) — always finite, never exploding (SG-MR3).
|
|
100
|
+
* - **No CCD** — detection runs once per fixed step with no swept test, so a
|
|
101
|
+
* body that travels farther than an obstacle's thickness in one step tunnels
|
|
102
|
+
* straight through it (it stays finite). Reliably stopping fast projectiles is
|
|
103
|
+
* a future bullet-mode feature (SG-X5).
|
|
104
|
+
* - **{@link PhysicsWorldOptions.subStepCount}** — the default `4` is
|
|
105
|
+
* load-bearing for tall-stack stability; lowering it below `2` visibly
|
|
106
|
+
* degrades stacking, so do not reduce it for performance.
|
|
47
107
|
*/
|
|
48
108
|
export declare class PhysicsWorld implements BodyOwner {
|
|
49
109
|
/** Fires when two solid colliders begin touching. Argument is an immutable snapshot. */
|
|
@@ -54,22 +114,39 @@ export declare class PhysicsWorld implements BodyOwner {
|
|
|
54
114
|
readonly onSensorEnter: Signal<[SensorEvent]>;
|
|
55
115
|
/** Fires when a collider leaves a sensor. */
|
|
56
116
|
readonly onSensorExit: Signal<[SensorEvent]>;
|
|
57
|
-
/** World gravity (px/s², +Y down).
|
|
117
|
+
/** World gravity (px/s², +Y down). Integrated each sub-step. */
|
|
58
118
|
readonly gravity: Vector;
|
|
59
119
|
/** The fixed-step accumulator. */
|
|
60
120
|
readonly timeStepper: TimeStepper;
|
|
61
|
-
/**
|
|
62
|
-
readonly
|
|
63
|
-
/**
|
|
64
|
-
readonly
|
|
65
|
-
/**
|
|
66
|
-
readonly
|
|
121
|
+
/** TGS-Soft sub-steps per fixed step. */
|
|
122
|
+
readonly subStepCount: number;
|
|
123
|
+
/** Soft-contact stiffness in Hz. */
|
|
124
|
+
readonly contactHertz: number;
|
|
125
|
+
/** Soft-contact damping ratio. */
|
|
126
|
+
readonly dampingRatio: number;
|
|
127
|
+
/** Whether resting bodies are put to sleep. */
|
|
128
|
+
readonly enableSleeping: boolean;
|
|
129
|
+
/** Linear sleep threshold (px/s). */
|
|
130
|
+
readonly sleepLinearVelocity: number;
|
|
131
|
+
/** Angular sleep threshold (rad/s). */
|
|
132
|
+
readonly sleepAngularVelocity: number;
|
|
133
|
+
/** Seconds below the thresholds before a body sleeps. */
|
|
134
|
+
readonly timeToSleep: number;
|
|
67
135
|
private readonly _backend;
|
|
68
136
|
private readonly _bodies;
|
|
69
137
|
private readonly _colliders;
|
|
138
|
+
private readonly _joints;
|
|
70
139
|
private readonly _bindings;
|
|
71
140
|
private readonly _query;
|
|
72
141
|
private readonly _commands;
|
|
142
|
+
/** Pooled union-find parent array for the per-step island pass (reused; sized to the body count). */
|
|
143
|
+
private readonly _islandParent;
|
|
144
|
+
/** Pooled per-island minimum sleep time, indexed by union-find root. */
|
|
145
|
+
private readonly _islandMinSleep;
|
|
146
|
+
/** Pooled ray-hit buffer + origin/direction for the CCD swept test. */
|
|
147
|
+
private readonly _ccdHits;
|
|
148
|
+
private readonly _ccdOrigin;
|
|
149
|
+
private readonly _ccdDir;
|
|
73
150
|
private _nextBodyId;
|
|
74
151
|
private _nextColliderId;
|
|
75
152
|
private _dispatching;
|
|
@@ -79,21 +156,46 @@ export declare class PhysicsWorld implements BodyOwner {
|
|
|
79
156
|
get bodies(): readonly PhysicsBody[];
|
|
80
157
|
/** Live colliders (read-only view). */
|
|
81
158
|
get colliders(): readonly Collider[];
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Add a body to the world: allocates the body and its collider ids, registers
|
|
161
|
+
* the colliders, computes the mass model and tracks the body for stepping.
|
|
162
|
+
* Construct the body freely first (`new PhysicsBody({ … })`), then add it.
|
|
163
|
+
* Safe to call inside an event callback — the body push is deferred to the end
|
|
164
|
+
* of the step, exactly like collider registration. Returns the body.
|
|
165
|
+
*
|
|
166
|
+
* @throws if the body has already been added to a world.
|
|
167
|
+
*/
|
|
168
|
+
add(body: PhysicsBody): PhysicsBody;
|
|
169
|
+
/**
|
|
170
|
+
* Convenience: create a body carrying a single collider, add it to the world
|
|
171
|
+
* and bind it to `node` in one call. The node tracks `body.position` after each
|
|
172
|
+
* step. Returns the body. Equivalent to `new PhysicsBody(...)` + `add` + `bind`.
|
|
173
|
+
*/
|
|
174
|
+
attach(node: SceneNode, options: AttachOptions): PhysicsBody;
|
|
86
175
|
/** Destroy a body and its colliders. Deferred when called inside a callback. */
|
|
87
176
|
destroyBody(body: PhysicsBody): void;
|
|
88
177
|
/** Destroy a single collider, recomputing its body's mass. Deferred when called inside a callback. */
|
|
89
178
|
destroyCollider(collider: Collider): void;
|
|
179
|
+
/** Live joints (read-only view). */
|
|
180
|
+
get joints(): readonly Joint[];
|
|
181
|
+
/**
|
|
182
|
+
* Add a constraint joint. Construct it first (`new DistanceJoint({ … })`),
|
|
183
|
+
* then add it. Wakes both bodies; safe inside a callback (registration is
|
|
184
|
+
* deferred). Returns the joint.
|
|
185
|
+
*/
|
|
186
|
+
addJoint<T extends Joint>(joint: T): T;
|
|
187
|
+
/** Remove a joint, waking both bodies so they respond to the lost constraint. Deferred when called inside a callback. */
|
|
188
|
+
removeJoint(joint: Joint): void;
|
|
90
189
|
/**
|
|
91
|
-
* Advance the world by `frameDeltaSeconds`. Accumulates into fixed
|
|
92
|
-
* runs detection
|
|
190
|
+
* Advance the world by `frameDeltaSeconds`. Accumulates into fixed steps; each
|
|
191
|
+
* fixed step runs detection once, then a TGS-Soft sub-step loop (integrate
|
|
192
|
+
* gravity, solve contacts with a soft bias, integrate positions, relax) and a
|
|
193
|
+
* restitution pass, then writes the accumulated motion into each body. Finally
|
|
194
|
+
* dispatches events and writes bound node transforms.
|
|
93
195
|
*/
|
|
94
196
|
step(frameDeltaSeconds: number): void;
|
|
95
197
|
/** Link a body to a scene node; the node tracks the body after each step. */
|
|
96
|
-
bind(body: PhysicsBody, node: SceneNode
|
|
198
|
+
bind(body: PhysicsBody, node: SceneNode): PhysicsBinding;
|
|
97
199
|
/** Remove a body↔node link. */
|
|
98
200
|
unbind(body: PhysicsBody): void;
|
|
99
201
|
/** Colliders containing `point`. Fresh array. */
|
|
@@ -107,7 +209,7 @@ export declare class PhysicsWorld implements BodyOwner {
|
|
|
107
209
|
/** All collider hits along the ray, sorted by distance. Writes into `out` (cleared) if given. */
|
|
108
210
|
rayCastAll(origin: VectorLike, direction: VectorLike, filter?: QueryFilter, out?: RayHit[], maxDistance?: number): RayHit[];
|
|
109
211
|
/** Colliders overlapping `shape` placed at `position`/`angle`. Fresh array. */
|
|
110
|
-
overlapShape(shape:
|
|
212
|
+
overlapShape(shape: AnyShape, position: VectorLike, filter?: QueryFilter, angle?: number): Collider[];
|
|
111
213
|
/** Release every body, collider, binding and backend resource. */
|
|
112
214
|
destroy(): void;
|
|
113
215
|
_allocateColliderId(): number;
|
|
@@ -115,6 +217,42 @@ export declare class PhysicsWorld implements BodyOwner {
|
|
|
115
217
|
/** The detection backend (internal; consumed by the debug draw layer). */
|
|
116
218
|
get backend(): PhysicsBackend;
|
|
117
219
|
private _dispatchEvents;
|
|
220
|
+
/**
|
|
221
|
+
* Accumulate per-body sleep timers and put/keep islands of resting bodies
|
|
222
|
+
* asleep so a stack sleeps and wakes as one unit. An island is a connected
|
|
223
|
+
* component of dynamic bodies joined by touching solid contacts (static and
|
|
224
|
+
* kinematic bodies are boundaries, not nodes); it sleeps once every member has
|
|
225
|
+
* stayed below the sleep thresholds for `timeToSleep`, and wakes the instant
|
|
226
|
+
* any member does (e.g. an awake body merges into it via a new contact).
|
|
227
|
+
* Deterministic: union-find roots break ties by lower index and the contact
|
|
228
|
+
* set is id-sorted.
|
|
229
|
+
*/
|
|
230
|
+
private _updateSleeping;
|
|
231
|
+
/** Union-find union by lower index (deterministic roots). */
|
|
232
|
+
private _union;
|
|
233
|
+
/** Union-find find with path halving. */
|
|
234
|
+
private _find;
|
|
235
|
+
/** Build each joint's per-frame constraint data (once per fixed step). */
|
|
236
|
+
private _prepareJoints;
|
|
237
|
+
/** Re-apply each joint's accumulated impulse (each sub-step). */
|
|
238
|
+
private _warmStartJoints;
|
|
239
|
+
/** One joint velocity pass (each sub-step, after the contacts). */
|
|
240
|
+
private _solveJoints;
|
|
241
|
+
/** Whether any dynamic body is flagged for continuous collision (bullet mode). */
|
|
242
|
+
private _hasBullets;
|
|
243
|
+
/** Snapshot each bullet's centre of mass at the start of the fixed step (the swept-test origin). */
|
|
244
|
+
private _recordBulletPositions;
|
|
245
|
+
/**
|
|
246
|
+
* Sweep each bullet's centre of mass along this fixed step's motion against every
|
|
247
|
+
* other body's colliders; if it would cross one, clamp the body just short of the
|
|
248
|
+
* surface and resolve the impact about the surface normal (a slide for a non-bouncy
|
|
249
|
+
* body, an elastic reflection as restitution → 1) so it cannot tunnel. Sweeps the
|
|
250
|
+
* centre point — good for small/point-like projectiles; a full swept-shape TOI for
|
|
251
|
+
* large fast bodies is backlog (raise sub-steps or thicken geometry meanwhile).
|
|
252
|
+
*/
|
|
253
|
+
private _advanceBullets;
|
|
254
|
+
/** The highest restitution among a body's colliders (its CCD bounce factor). */
|
|
255
|
+
private _bulletRestitution;
|
|
118
256
|
/** Run `command` now, or queue it when inside an event dispatch (deferred to end of step). */
|
|
119
257
|
private _defer;
|
|
120
258
|
private _drainCommands;
|