@codexo/exojs-physics 0.13.0 → 0.14.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 +113 -15
- package/dist/esm/PhysicsBody.js +224 -21
- package/dist/esm/PhysicsBody.js.map +1 -1
- package/dist/esm/PhysicsWorld.d.ts +107 -35
- package/dist/esm/PhysicsWorld.js +136 -31
- 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/PhysicsBinding.d.ts +6 -6
- package/dist/esm/binding/PhysicsBinding.js +8 -5
- 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 +2 -1
- package/dist/esm/debug/PhysicsDebugDraw.js +2 -1
- package/dist/esm/debug/PhysicsDebugDraw.js.map +1 -1
- package/dist/esm/physicsBuildInfo.js +2 -2
- package/dist/esm/public.d.ts +2 -1
- 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 +483 -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 +4 -4
|
@@ -3,47 +3,102 @@ import { Signal, Vector } from '@codexo/exojs';
|
|
|
3
3
|
import type { Aabb } from './Aabb';
|
|
4
4
|
import type { PhysicsBackend } from './backend/PhysicsBackend';
|
|
5
5
|
import type { BindingOptions, PhysicsBinding } from './binding/PhysicsBinding';
|
|
6
|
-
import
|
|
6
|
+
import { Collider } from './Collider';
|
|
7
7
|
import type { CollisionEvent, SensorEvent } from './events';
|
|
8
|
-
import type {
|
|
8
|
+
import type { BodyOwner } from './PhysicsBody';
|
|
9
9
|
import { PhysicsBody } from './PhysicsBody';
|
|
10
10
|
import type { QueryFilter, RayHit } from './query/QueryEngine';
|
|
11
|
-
import type {
|
|
11
|
+
import type { AnyShape } from './shapes/AnyShape';
|
|
12
12
|
import { TimeStepper } from './TimeStepper';
|
|
13
|
-
import type { VectorLike } from './types';
|
|
13
|
+
import type { BodyType, CollisionFilter, VectorLike } from './types';
|
|
14
14
|
/** Construction options for a {@link PhysicsWorld}. */
|
|
15
15
|
export interface PhysicsWorldOptions {
|
|
16
|
-
/** Gravity in px/s² (+Y down).
|
|
16
|
+
/** Gravity in px/s² (+Y down). Integrated each sub-step. Default `(0, 0)`. */
|
|
17
17
|
gravity?: VectorLike;
|
|
18
18
|
/** Fixed timestep in seconds. Default `1 / 60`. */
|
|
19
19
|
fixedDelta?: number;
|
|
20
|
-
/** Maximum
|
|
20
|
+
/** Maximum fixed steps per `step` call (spiral-of-death guard). Default `8`. */
|
|
21
21
|
maxSubSteps?: number;
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
/**
|
|
23
|
+
* TGS-Soft sub-steps per fixed step (the solver's stiffness scales with this,
|
|
24
|
+
* not iteration count). Default `4`. Must be ≥ 1. Values below `2` visibly
|
|
25
|
+
* degrade tall-stack stability (a 10-box tower jitters at `1`), so the default
|
|
26
|
+
* is load-bearing — do not lower it for performance.
|
|
27
|
+
*/
|
|
28
|
+
subStepCount?: number;
|
|
29
|
+
/** Soft-contact stiffness in Hz (the contact behaves as a damped spring at this frequency). Default `30`. */
|
|
30
|
+
contactHertz?: number;
|
|
31
|
+
/** Soft-contact damping ratio (≥ 1 keeps contacts from oscillating). Default `10`. */
|
|
32
|
+
dampingRatio?: number;
|
|
33
|
+
/** Interpolate bound nodes between fixed steps (reserved; no effect yet). Default `true`. */
|
|
27
34
|
interpolation?: boolean;
|
|
28
35
|
}
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
/**
|
|
37
|
+
* {@link PhysicsWorld.attach} convenience options: a body type plus a single
|
|
38
|
+
* collider, attached to a scene node in one call.
|
|
39
|
+
*/
|
|
40
|
+
export interface AttachOptions {
|
|
41
|
+
/** Simulation role of the created body. Default `'dynamic'`. */
|
|
42
|
+
type?: BodyType;
|
|
43
|
+
/** Initial world position of the body. Default the node's position is left untouched and `(0, 0)` is used. */
|
|
32
44
|
position?: VectorLike;
|
|
33
|
-
/**
|
|
45
|
+
/** Initial rotation (radians) of the body. Default `0`. */
|
|
34
46
|
angle?: number;
|
|
47
|
+
/** Per-body multiplier on world gravity. Default `1`. */
|
|
48
|
+
gravityScale?: number;
|
|
49
|
+
/** When `true`, the body never rotates under contacts. Default `false`. */
|
|
50
|
+
fixedRotation?: boolean;
|
|
51
|
+
/** The collider geometry. */
|
|
52
|
+
shape: AnyShape;
|
|
53
|
+
/** Body-local offset of the collider. Default `(0, 0)`. */
|
|
54
|
+
offset?: VectorLike;
|
|
55
|
+
/** Body-local rotation of the collider (radians). Default `0`. */
|
|
56
|
+
rotation?: number;
|
|
57
|
+
/** Collider density (mass per px²). Default `1`. */
|
|
58
|
+
density?: number;
|
|
59
|
+
/** Coulomb friction coefficient. Default `0.2`. */
|
|
60
|
+
friction?: number;
|
|
61
|
+
/** Restitution / bounciness in `[0, 1]`. Default `0`. */
|
|
62
|
+
restitution?: number;
|
|
63
|
+
/** When `true`, the collider generates overlap events but no contact response. Default `false`. */
|
|
64
|
+
isSensor?: boolean;
|
|
65
|
+
/** Category/mask/group collision filter; partials merge over the defaults. */
|
|
66
|
+
filter?: Partial<CollisionFilter>;
|
|
67
|
+
/** Binding options forwarded to {@link PhysicsWorld.bind}. */
|
|
68
|
+
binding?: BindingOptions;
|
|
35
69
|
}
|
|
36
70
|
/**
|
|
37
71
|
* The collision/query world: owns bodies, colliders, the detection backend,
|
|
38
72
|
* bindings, the query engine and the fixed-step accumulator. Stepped by the
|
|
39
|
-
* caller (commonly from a `Scene.update`),
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
73
|
+
* caller (commonly from a `Scene.update`), each fixed sub-step it integrates
|
|
74
|
+
* body velocities, runs broad- and narrow-phase detection, solves contacts and
|
|
75
|
+
* integrates positions, then fires immutable contact/sensor events and writes
|
|
76
|
+
* bound node transforms. It holds **no module-level state**, so any number of
|
|
77
|
+
* worlds run in isolation (gate I-1).
|
|
78
|
+
*
|
|
79
|
+
* The dynamics are a native, warm-started **TGS-Soft** solver (Box2D-v3 "soft
|
|
80
|
+
* step"): each fixed step runs detection once, then several sub-steps, each
|
|
81
|
+
* integrating gravity over the sub-step and solving contacts with a soft
|
|
82
|
+
* position bias plus a bias-free relax pass; a 2-point block normal solve
|
|
83
|
+
* propagates stack loads, and restitution is a separate final pass. Decoupling
|
|
84
|
+
* stiffness from the iteration count keeps tall towers stable. The detection
|
|
85
|
+
* backend sits behind an internal seam, so the solver is swappable without
|
|
86
|
+
* touching this public surface.
|
|
43
87
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
88
|
+
* **Operating envelope.** The soft solver trades a little accuracy for
|
|
89
|
+
* robustness, so it has a few documented limits — each stays finite/stable and
|
|
90
|
+
* each is pinned by a gate in `dynamics.test.ts`:
|
|
91
|
+
* - **Mass ratio** — resting stacks are slop-accurate up to ~100:1. Beyond that
|
|
92
|
+
* the velocity-capped soft push-out (`maxBiasVelocity`) lets the lighter body
|
|
93
|
+
* settle progressively deeper (≈6px at 500:1, fully through a thin floor by
|
|
94
|
+
* ~5000:1) — always finite, never exploding (SG-MR3).
|
|
95
|
+
* - **No CCD** — detection runs once per fixed step with no swept test, so a
|
|
96
|
+
* body that travels farther than an obstacle's thickness in one step tunnels
|
|
97
|
+
* straight through it (it stays finite). Reliably stopping fast projectiles is
|
|
98
|
+
* a future bullet-mode feature (SG-X5).
|
|
99
|
+
* - **{@link PhysicsWorldOptions.subStepCount}** — the default `4` is
|
|
100
|
+
* load-bearing for tall-stack stability; lowering it below `2` visibly
|
|
101
|
+
* degrades stacking, so do not reduce it for performance.
|
|
47
102
|
*/
|
|
48
103
|
export declare class PhysicsWorld implements BodyOwner {
|
|
49
104
|
/** Fires when two solid colliders begin touching. Argument is an immutable snapshot. */
|
|
@@ -54,16 +109,18 @@ export declare class PhysicsWorld implements BodyOwner {
|
|
|
54
109
|
readonly onSensorEnter: Signal<[SensorEvent]>;
|
|
55
110
|
/** Fires when a collider leaves a sensor. */
|
|
56
111
|
readonly onSensorExit: Signal<[SensorEvent]>;
|
|
57
|
-
/** World gravity (px/s², +Y down).
|
|
112
|
+
/** World gravity (px/s², +Y down). Integrated each sub-step. */
|
|
58
113
|
readonly gravity: Vector;
|
|
59
114
|
/** The fixed-step accumulator. */
|
|
60
115
|
readonly timeStepper: TimeStepper;
|
|
61
|
-
/** Whether bound nodes interpolate between
|
|
116
|
+
/** Whether bound nodes interpolate between fixed steps (reserved; no effect yet). */
|
|
62
117
|
readonly interpolation: boolean;
|
|
63
|
-
/**
|
|
64
|
-
readonly
|
|
65
|
-
/**
|
|
66
|
-
readonly
|
|
118
|
+
/** TGS-Soft sub-steps per fixed step. */
|
|
119
|
+
readonly subStepCount: number;
|
|
120
|
+
/** Soft-contact stiffness in Hz. */
|
|
121
|
+
readonly contactHertz: number;
|
|
122
|
+
/** Soft-contact damping ratio. */
|
|
123
|
+
readonly dampingRatio: number;
|
|
67
124
|
private readonly _backend;
|
|
68
125
|
private readonly _bodies;
|
|
69
126
|
private readonly _colliders;
|
|
@@ -79,17 +136,32 @@ export declare class PhysicsWorld implements BodyOwner {
|
|
|
79
136
|
get bodies(): readonly PhysicsBody[];
|
|
80
137
|
/** Live colliders (read-only view). */
|
|
81
138
|
get colliders(): readonly Collider[];
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Add a body to the world: allocates the body and its collider ids, registers
|
|
141
|
+
* the colliders, computes the mass model and tracks the body for stepping.
|
|
142
|
+
* Construct the body freely first (`new PhysicsBody({ … })`), then add it.
|
|
143
|
+
* Safe to call inside an event callback — the body push is deferred to the end
|
|
144
|
+
* of the step, exactly like collider registration. Returns the body.
|
|
145
|
+
*
|
|
146
|
+
* @throws if the body has already been added to a world.
|
|
147
|
+
*/
|
|
148
|
+
add(body: PhysicsBody): PhysicsBody;
|
|
149
|
+
/**
|
|
150
|
+
* Convenience: create a body carrying a single collider, add it to the world
|
|
151
|
+
* and bind it to `node` in one call. The node tracks `body.position` after each
|
|
152
|
+
* step. Returns the body. Equivalent to `new PhysicsBody(...)` + `add` + `bind`.
|
|
153
|
+
*/
|
|
154
|
+
attach(node: SceneNode, options: AttachOptions): PhysicsBody;
|
|
86
155
|
/** Destroy a body and its colliders. Deferred when called inside a callback. */
|
|
87
156
|
destroyBody(body: PhysicsBody): void;
|
|
88
157
|
/** Destroy a single collider, recomputing its body's mass. Deferred when called inside a callback. */
|
|
89
158
|
destroyCollider(collider: Collider): void;
|
|
90
159
|
/**
|
|
91
|
-
* Advance the world by `frameDeltaSeconds`. Accumulates into fixed
|
|
92
|
-
* runs detection
|
|
160
|
+
* Advance the world by `frameDeltaSeconds`. Accumulates into fixed steps; each
|
|
161
|
+
* fixed step runs detection once, then a TGS-Soft sub-step loop (integrate
|
|
162
|
+
* gravity, solve contacts with a soft bias, integrate positions, relax) and a
|
|
163
|
+
* restitution pass, then writes the accumulated motion into each body. Finally
|
|
164
|
+
* dispatches events and writes bound node transforms.
|
|
93
165
|
*/
|
|
94
166
|
step(frameDeltaSeconds: number): void;
|
|
95
167
|
/** Link a body to a scene node; the node tracks the body after each step. */
|
|
@@ -107,7 +179,7 @@ export declare class PhysicsWorld implements BodyOwner {
|
|
|
107
179
|
/** All collider hits along the ray, sorted by distance. Writes into `out` (cleared) if given. */
|
|
108
180
|
rayCastAll(origin: VectorLike, direction: VectorLike, filter?: QueryFilter, out?: RayHit[], maxDistance?: number): RayHit[];
|
|
109
181
|
/** Colliders overlapping `shape` placed at `position`/`angle`. Fresh array. */
|
|
110
|
-
overlapShape(shape:
|
|
182
|
+
overlapShape(shape: AnyShape, position: VectorLike, filter?: QueryFilter, angle?: number): Collider[];
|
|
111
183
|
/** Release every body, collider, binding and backend resource. */
|
|
112
184
|
destroy(): void;
|
|
113
185
|
_allocateColliderId(): number;
|
package/dist/esm/PhysicsWorld.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Signal, Vector } from '@codexo/exojs';
|
|
2
2
|
import { NativePhysicsBackend } from './backend/NativePhysicsBackend.js';
|
|
3
3
|
import { BindingRegistry } from './binding/BindingRegistry.js';
|
|
4
|
+
import { Collider } from './Collider.js';
|
|
4
5
|
import { PhysicsBody } from './PhysicsBody.js';
|
|
5
6
|
import { QueryEngine } from './query/QueryEngine.js';
|
|
6
7
|
import { TimeStepper } from './TimeStepper.js';
|
|
@@ -8,14 +9,35 @@ import { TimeStepper } from './TimeStepper.js';
|
|
|
8
9
|
/**
|
|
9
10
|
* The collision/query world: owns bodies, colliders, the detection backend,
|
|
10
11
|
* bindings, the query engine and the fixed-step accumulator. Stepped by the
|
|
11
|
-
* caller (commonly from a `Scene.update`),
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* caller (commonly from a `Scene.update`), each fixed sub-step it integrates
|
|
13
|
+
* body velocities, runs broad- and narrow-phase detection, solves contacts and
|
|
14
|
+
* integrates positions, then fires immutable contact/sensor events and writes
|
|
15
|
+
* bound node transforms. It holds **no module-level state**, so any number of
|
|
16
|
+
* worlds run in isolation (gate I-1).
|
|
15
17
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
18
|
+
* The dynamics are a native, warm-started **TGS-Soft** solver (Box2D-v3 "soft
|
|
19
|
+
* step"): each fixed step runs detection once, then several sub-steps, each
|
|
20
|
+
* integrating gravity over the sub-step and solving contacts with a soft
|
|
21
|
+
* position bias plus a bias-free relax pass; a 2-point block normal solve
|
|
22
|
+
* propagates stack loads, and restitution is a separate final pass. Decoupling
|
|
23
|
+
* stiffness from the iteration count keeps tall towers stable. The detection
|
|
24
|
+
* backend sits behind an internal seam, so the solver is swappable without
|
|
25
|
+
* touching this public surface.
|
|
26
|
+
*
|
|
27
|
+
* **Operating envelope.** The soft solver trades a little accuracy for
|
|
28
|
+
* robustness, so it has a few documented limits — each stays finite/stable and
|
|
29
|
+
* each is pinned by a gate in `dynamics.test.ts`:
|
|
30
|
+
* - **Mass ratio** — resting stacks are slop-accurate up to ~100:1. Beyond that
|
|
31
|
+
* the velocity-capped soft push-out (`maxBiasVelocity`) lets the lighter body
|
|
32
|
+
* settle progressively deeper (≈6px at 500:1, fully through a thin floor by
|
|
33
|
+
* ~5000:1) — always finite, never exploding (SG-MR3).
|
|
34
|
+
* - **No CCD** — detection runs once per fixed step with no swept test, so a
|
|
35
|
+
* body that travels farther than an obstacle's thickness in one step tunnels
|
|
36
|
+
* straight through it (it stays finite). Reliably stopping fast projectiles is
|
|
37
|
+
* a future bullet-mode feature (SG-X5).
|
|
38
|
+
* - **{@link PhysicsWorldOptions.subStepCount}** — the default `4` is
|
|
39
|
+
* load-bearing for tall-stack stability; lowering it below `2` visibly
|
|
40
|
+
* degrades stacking, so do not reduce it for performance.
|
|
19
41
|
*/
|
|
20
42
|
class PhysicsWorld {
|
|
21
43
|
/** Fires when two solid colliders begin touching. Argument is an immutable snapshot. */
|
|
@@ -26,16 +48,18 @@ class PhysicsWorld {
|
|
|
26
48
|
onSensorEnter = new Signal();
|
|
27
49
|
/** Fires when a collider leaves a sensor. */
|
|
28
50
|
onSensorExit = new Signal();
|
|
29
|
-
/** World gravity (px/s², +Y down).
|
|
51
|
+
/** World gravity (px/s², +Y down). Integrated each sub-step. */
|
|
30
52
|
gravity;
|
|
31
53
|
/** The fixed-step accumulator. */
|
|
32
54
|
timeStepper;
|
|
33
|
-
/** Whether bound nodes interpolate between
|
|
55
|
+
/** Whether bound nodes interpolate between fixed steps (reserved; no effect yet). */
|
|
34
56
|
interpolation;
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
|
|
57
|
+
/** TGS-Soft sub-steps per fixed step. */
|
|
58
|
+
subStepCount;
|
|
59
|
+
/** Soft-contact stiffness in Hz. */
|
|
60
|
+
contactHertz;
|
|
61
|
+
/** Soft-contact damping ratio. */
|
|
62
|
+
dampingRatio;
|
|
39
63
|
_backend = new NativePhysicsBackend();
|
|
40
64
|
_bodies = [];
|
|
41
65
|
_colliders = [];
|
|
@@ -48,10 +72,18 @@ class PhysicsWorld {
|
|
|
48
72
|
_destroyed = false;
|
|
49
73
|
constructor(options = {}) {
|
|
50
74
|
this.gravity = new Vector(options.gravity?.x ?? 0, options.gravity?.y ?? 0);
|
|
51
|
-
this.timeStepper = new TimeStepper({
|
|
75
|
+
this.timeStepper = new TimeStepper({
|
|
76
|
+
...(options.fixedDelta !== undefined && { fixedDelta: options.fixedDelta }),
|
|
77
|
+
...(options.maxSubSteps !== undefined && { maxSubSteps: options.maxSubSteps }),
|
|
78
|
+
});
|
|
52
79
|
this.interpolation = options.interpolation ?? true;
|
|
53
|
-
|
|
54
|
-
|
|
80
|
+
const subStepCount = options.subStepCount ?? 4;
|
|
81
|
+
if (!Number.isInteger(subStepCount) || subStepCount < 1) {
|
|
82
|
+
throw new RangeError(`PhysicsWorld: subStepCount must be an integer ≥ 1, received ${subStepCount}.`);
|
|
83
|
+
}
|
|
84
|
+
this.subStepCount = subStepCount;
|
|
85
|
+
this.contactHertz = options.contactHertz ?? 30;
|
|
86
|
+
this.dampingRatio = options.dampingRatio ?? 10;
|
|
55
87
|
this._query = new QueryEngine(this._colliders);
|
|
56
88
|
}
|
|
57
89
|
/** Live bodies (read-only view). */
|
|
@@ -63,10 +95,24 @@ class PhysicsWorld {
|
|
|
63
95
|
return this._colliders;
|
|
64
96
|
}
|
|
65
97
|
// ── lifecycle ──────────────────────────────────────────────────────────
|
|
66
|
-
/**
|
|
67
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Add a body to the world: allocates the body and its collider ids, registers
|
|
100
|
+
* the colliders, computes the mass model and tracks the body for stepping.
|
|
101
|
+
* Construct the body freely first (`new PhysicsBody({ … })`), then add it.
|
|
102
|
+
* Safe to call inside an event callback — the body push is deferred to the end
|
|
103
|
+
* of the step, exactly like collider registration. Returns the body.
|
|
104
|
+
*
|
|
105
|
+
* @throws if the body has already been added to a world.
|
|
106
|
+
*/
|
|
107
|
+
add(body) {
|
|
68
108
|
this._assertAlive();
|
|
69
|
-
|
|
109
|
+
if (body.attached) {
|
|
110
|
+
throw new Error('PhysicsWorld.add: this body has already been added to a world.');
|
|
111
|
+
}
|
|
112
|
+
// Allocate the id + link/register colliders + aggregate mass now (matches the
|
|
113
|
+
// old createBody, which allocated the id synchronously); only the body-list
|
|
114
|
+
// push is deferred so it is safe inside an event dispatch.
|
|
115
|
+
body._attachToWorld(this, this._nextBodyId++);
|
|
70
116
|
this._defer(() => {
|
|
71
117
|
if (!body.destroyed) {
|
|
72
118
|
this._bodies.push(body);
|
|
@@ -74,10 +120,34 @@ class PhysicsWorld {
|
|
|
74
120
|
});
|
|
75
121
|
return body;
|
|
76
122
|
}
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Convenience: create a body carrying a single collider, add it to the world
|
|
125
|
+
* and bind it to `node` in one call. The node tracks `body.position` after each
|
|
126
|
+
* step. Returns the body. Equivalent to `new PhysicsBody(...)` + `add` + `bind`.
|
|
127
|
+
*/
|
|
128
|
+
attach(node, options) {
|
|
129
|
+
const body = new PhysicsBody({
|
|
130
|
+
...(options.type !== undefined && { type: options.type }),
|
|
131
|
+
...(options.position !== undefined && { position: options.position }),
|
|
132
|
+
...(options.angle !== undefined && { angle: options.angle }),
|
|
133
|
+
...(options.gravityScale !== undefined && { gravityScale: options.gravityScale }),
|
|
134
|
+
...(options.fixedRotation !== undefined && { fixedRotation: options.fixedRotation }),
|
|
135
|
+
colliders: [
|
|
136
|
+
new Collider({
|
|
137
|
+
shape: options.shape,
|
|
138
|
+
...(options.offset !== undefined && { offset: options.offset }),
|
|
139
|
+
...(options.rotation !== undefined && { rotation: options.rotation }),
|
|
140
|
+
...(options.density !== undefined && { density: options.density }),
|
|
141
|
+
...(options.friction !== undefined && { friction: options.friction }),
|
|
142
|
+
...(options.restitution !== undefined && { restitution: options.restitution }),
|
|
143
|
+
...(options.isSensor !== undefined && { isSensor: options.isSensor }),
|
|
144
|
+
...(options.filter !== undefined && { filter: options.filter }),
|
|
145
|
+
}),
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
this.add(body);
|
|
149
|
+
this.bind(body, node, options.binding);
|
|
150
|
+
return body;
|
|
81
151
|
}
|
|
82
152
|
/** Destroy a body and its colliders. Deferred when called inside a callback. */
|
|
83
153
|
destroyBody(body) {
|
|
@@ -89,20 +159,55 @@ class PhysicsWorld {
|
|
|
89
159
|
}
|
|
90
160
|
// ── stepping ───────────────────────────────────────────────────────────
|
|
91
161
|
/**
|
|
92
|
-
* Advance the world by `frameDeltaSeconds`. Accumulates into fixed
|
|
93
|
-
* runs detection
|
|
162
|
+
* Advance the world by `frameDeltaSeconds`. Accumulates into fixed steps; each
|
|
163
|
+
* fixed step runs detection once, then a TGS-Soft sub-step loop (integrate
|
|
164
|
+
* gravity, solve contacts with a soft bias, integrate positions, relax) and a
|
|
165
|
+
* restitution pass, then writes the accumulated motion into each body. Finally
|
|
166
|
+
* dispatches events and writes bound node transforms.
|
|
94
167
|
*/
|
|
95
168
|
step(frameDeltaSeconds) {
|
|
96
169
|
this._assertAlive();
|
|
97
170
|
const steps = this.timeStepper.advance(frameDeltaSeconds);
|
|
98
171
|
if (steps > 0) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
172
|
+
const subStepCount = this.subStepCount;
|
|
173
|
+
const h = this.timeStepper.fixedDelta / subStepCount;
|
|
174
|
+
const gravityX = this.gravity.x;
|
|
175
|
+
const gravityY = this.gravity.y;
|
|
176
|
+
const contactHertz = this.contactHertz;
|
|
177
|
+
const dampingRatio = this.dampingRatio;
|
|
178
|
+
for (let step = 0; step < steps; step++) {
|
|
179
|
+
// Detection runs once per fixed step (collider geometry is already current
|
|
180
|
+
// from the previous frame's finalize / attach / setTransform). TGS-Soft
|
|
181
|
+
// reuses the manifolds across the sub-steps below.
|
|
182
|
+
this._backend.detect(this._colliders);
|
|
183
|
+
this._backend.prepareSolve(h, contactHertz, dampingRatio);
|
|
184
|
+
for (let subStep = 0; subStep < subStepCount; subStep++) {
|
|
185
|
+
// Integrate gravity/forces over the sub-step (forces persist across
|
|
186
|
+
// sub-steps; cleared once per frame by `_finalizePosition`).
|
|
187
|
+
for (const body of this._bodies) {
|
|
188
|
+
body._integrateVelocity(h, gravityX, gravityY);
|
|
189
|
+
}
|
|
190
|
+
// Warm-start every sub-step (Box2D-v3 soft step): the relax pass leaves
|
|
191
|
+
// each contact's normal velocity at zero, so re-applying the
|
|
192
|
+
// accumulated impulse re-balances exactly this sub-step's gravity — the
|
|
193
|
+
// impulse converges to the per-sub-step load (m·h·g), not the per-frame
|
|
194
|
+
// load, which is what keeps tall stacks from pumping energy.
|
|
195
|
+
this._backend.warmStart();
|
|
196
|
+
// Main soft-bias velocity solve, integrate positions (accumulating
|
|
197
|
+
// per-body delta), then the bias-free relax pass.
|
|
198
|
+
this._backend.solveVelocities(true);
|
|
199
|
+
for (const body of this._bodies) {
|
|
200
|
+
body._integratePosition(h);
|
|
201
|
+
}
|
|
202
|
+
this._backend.solveVelocities(false);
|
|
203
|
+
}
|
|
204
|
+
// Separate restitution pass, then write the accumulated delta into each
|
|
205
|
+
// body's transform and re-sync collider geometry.
|
|
206
|
+
this._backend.applyRestitution();
|
|
207
|
+
for (const body of this._bodies) {
|
|
208
|
+
body._finalizePosition();
|
|
209
|
+
}
|
|
104
210
|
}
|
|
105
|
-
this._backend.detect(this._colliders);
|
|
106
211
|
this._dispatchEvents();
|
|
107
212
|
}
|
|
108
213
|
this._bindings.sync();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhysicsWorld.js","sources":["../../../src/PhysicsWorld.ts"],"sourcesContent":[null],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PhysicsWorld.js","sources":["../../../src/PhysicsWorld.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -11,9 +11,14 @@ import type { PhysicsBackend } from './PhysicsBackend';
|
|
|
11
11
|
export declare class NativePhysicsBackend implements PhysicsBackend {
|
|
12
12
|
readonly contactGraph: ContactGraph;
|
|
13
13
|
private readonly _broadPhase;
|
|
14
|
+
private readonly _solver;
|
|
14
15
|
private readonly _pairs;
|
|
15
16
|
get candidatePairs(): readonly CandidatePair[];
|
|
16
17
|
detect(colliders: readonly Collider[]): void;
|
|
18
|
+
prepareSolve(h: number, contactHertz: number, dampingRatio: number): void;
|
|
19
|
+
warmStart(): void;
|
|
20
|
+
solveVelocities(useBias: boolean): void;
|
|
21
|
+
applyRestitution(): void;
|
|
17
22
|
removeCollider(collider: Collider): void;
|
|
18
23
|
destroy(): void;
|
|
19
24
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SweepAndPrune } from '../broadphase/SweepAndPrune.js';
|
|
2
2
|
import { ContactGraph } from '../ContactGraph.js';
|
|
3
|
+
import { ContactSolver } from '../solver/ContactSolver.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The native, dependency-free backend: a {@link BroadPhase} (sweep-and-prune in
|
|
@@ -10,6 +11,7 @@ import { ContactGraph } from '../ContactGraph.js';
|
|
|
10
11
|
class NativePhysicsBackend {
|
|
11
12
|
contactGraph = new ContactGraph();
|
|
12
13
|
_broadPhase = new SweepAndPrune();
|
|
14
|
+
_solver = new ContactSolver();
|
|
13
15
|
_pairs = [];
|
|
14
16
|
get candidatePairs() {
|
|
15
17
|
return this._pairs;
|
|
@@ -18,6 +20,18 @@ class NativePhysicsBackend {
|
|
|
18
20
|
this._broadPhase.computePairs(colliders, this._pairs);
|
|
19
21
|
this.contactGraph.update(this._pairs);
|
|
20
22
|
}
|
|
23
|
+
prepareSolve(h, contactHertz, dampingRatio) {
|
|
24
|
+
this._solver.prepare(this.contactGraph.solidContacts, h, contactHertz, dampingRatio);
|
|
25
|
+
}
|
|
26
|
+
warmStart() {
|
|
27
|
+
this._solver.warmStart();
|
|
28
|
+
}
|
|
29
|
+
solveVelocities(useBias) {
|
|
30
|
+
this._solver.solveVelocities(useBias);
|
|
31
|
+
}
|
|
32
|
+
applyRestitution() {
|
|
33
|
+
this._solver.applyRestitution();
|
|
34
|
+
}
|
|
21
35
|
removeCollider(collider) {
|
|
22
36
|
this.contactGraph.removeCollider(collider);
|
|
23
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativePhysicsBackend.js","sources":["../../../../src/backend/NativePhysicsBackend.ts"],"sourcesContent":[null],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NativePhysicsBackend.js","sources":["../../../../src/backend/NativePhysicsBackend.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;AAOA;;;;;AAKG;MACU,oBAAoB,CAAA;AACf,IAAA,YAAY,GAAG,IAAI,YAAY,EAAE;AAEhC,IAAA,WAAW,GAAe,IAAI,aAAa,EAAE;AAC7C,IAAA,OAAO,GAAG,IAAI,aAAa,EAAE;IAC7B,MAAM,GAAoB,EAAE;AAE7C,IAAA,IAAW,cAAc,GAAA;QACvB,OAAO,IAAI,CAAC,MAAM;IACpB;AAEO,IAAA,MAAM,CAAC,SAA8B,EAAA;QAC1C,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC;QACrD,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;IACvC;AAEO,IAAA,YAAY,CAAC,CAAS,EAAE,YAAoB,EAAE,YAAoB,EAAA;AACvE,QAAA,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,aAAa,EAAE,CAAC,EAAE,YAAY,EAAE,YAAY,CAAC;IACtF;IAEO,SAAS,GAAA;AACd,QAAA,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE;IAC1B;AAEO,IAAA,eAAe,CAAC,OAAgB,EAAA;AACrC,QAAA,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC;IACvC;IAEO,gBAAgB,GAAA;AACrB,QAAA,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE;IACjC;AAEO,IAAA,cAAc,CAAC,QAAkB,EAAA;AACtC,QAAA,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,QAAQ,CAAC;IAC5C;IAEO,OAAO,GAAA;AACZ,QAAA,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE;AACzB,QAAA,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;IACxB;AACD;;;;"}
|
|
@@ -14,8 +14,16 @@ export interface PhysicsBackend {
|
|
|
14
14
|
readonly contactGraph: ContactGraph;
|
|
15
15
|
/** The latest broad-phase candidate pairs (read-only; for debug draw). */
|
|
16
16
|
readonly candidatePairs: readonly CandidatePair[];
|
|
17
|
-
/** Run one detection pass over `colliders`, refreshing the contact graph. */
|
|
17
|
+
/** Run one detection pass over `colliders`, refreshing the contact graph. Once per frame (TGS reuses the manifolds across sub-steps). */
|
|
18
18
|
detect(colliders: readonly Collider[]): void;
|
|
19
|
+
/** Build the per-frame contact constraints from the solid contacts. `h` is the sub-step duration; the soft factors derive from it plus `contactHertz`/`dampingRatio`. Call once per frame after {@link detect}. */
|
|
20
|
+
prepareSolve(h: number, contactHertz: number, dampingRatio: number): void;
|
|
21
|
+
/** Re-apply the cached warm-start impulses to the contacting bodies (first sub-step only). */
|
|
22
|
+
warmStart(): void;
|
|
23
|
+
/** One velocity pass over the solid contacts. `useBias` selects the main soft-bias pass; `false` is the relax pass. */
|
|
24
|
+
solveVelocities(useBias: boolean): void;
|
|
25
|
+
/** Restitution pass, run once per frame after the sub-step loop. */
|
|
26
|
+
applyRestitution(): void;
|
|
19
27
|
/** Forget any state referencing `collider` (called on destruction). */
|
|
20
28
|
removeCollider(collider: Collider): void;
|
|
21
29
|
/** Release all backend state. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SceneNode } from '@codexo/exojs';
|
|
2
2
|
import type { PhysicsBody } from '../PhysicsBody';
|
|
3
3
|
/** Options for {@link PhysicsWorld.bind}. */
|
|
4
4
|
export interface BindingOptions {
|
|
@@ -12,16 +12,16 @@ export interface BindingOptions {
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* A link between a {@link PhysicsBody} and a {@link SceneNode}. After each
|
|
15
|
-
* {@link PhysicsWorld.step}, the body's world position
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* {@link PhysicsWorld.step}, the body's world position **and rotation** are
|
|
16
|
+
* written onto the node (the body's angle is radians; the node's rotation is
|
|
17
|
+
* degrees). The node must be world-space-rooted; runtime scale is ignored and
|
|
18
|
+
* non-zero skew is rejected at bind time.
|
|
19
19
|
*/
|
|
20
20
|
export declare class PhysicsBinding {
|
|
21
21
|
readonly body: PhysicsBody;
|
|
22
22
|
readonly node: SceneNode;
|
|
23
23
|
readonly drive: 'body-to-node' | 'node-to-body';
|
|
24
24
|
constructor(body: PhysicsBody, node: SceneNode, drive?: 'body-to-node' | 'node-to-body');
|
|
25
|
-
/** Write the body's current transform onto the bound node. */
|
|
25
|
+
/** Write the body's current transform (position + rotation) onto the bound node. */
|
|
26
26
|
sync(): void;
|
|
27
27
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { MathUtils } from '@codexo/exojs';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* A link between a {@link PhysicsBody} and a {@link SceneNode}. After each
|
|
3
|
-
* {@link PhysicsWorld.step}, the body's world position
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* {@link PhysicsWorld.step}, the body's world position **and rotation** are
|
|
6
|
+
* written onto the node (the body's angle is radians; the node's rotation is
|
|
7
|
+
* degrees). The node must be world-space-rooted; runtime scale is ignored and
|
|
8
|
+
* non-zero skew is rejected at bind time.
|
|
7
9
|
*/
|
|
8
10
|
class PhysicsBinding {
|
|
9
11
|
body;
|
|
@@ -14,9 +16,10 @@ class PhysicsBinding {
|
|
|
14
16
|
this.node = node;
|
|
15
17
|
this.drive = drive;
|
|
16
18
|
}
|
|
17
|
-
/** Write the body's current transform onto the bound node. */
|
|
19
|
+
/** Write the body's current transform (position + rotation) onto the bound node. */
|
|
18
20
|
sync() {
|
|
19
21
|
this.node.setPosition(this.body.x, this.body.y);
|
|
22
|
+
this.node.setRotation(MathUtils.radiansToDegrees(this.body.angle));
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
25
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhysicsBinding.js","sources":["../../../../src/binding/PhysicsBinding.ts"],"sourcesContent":[null],"names":[],"mappings":"AAeA;;;;;;AAMG;MACU,cAAc,CAAA;AAEP,IAAA,IAAA;AACA,IAAA,IAAA;AACA,IAAA,KAAA;AAHlB,IAAA,WAAA,CACkB,IAAiB,EACjB,IAAe,EACf,QAAyC,cAAc,EAAA;QAFvD,IAAA,CAAA,IAAI,GAAJ,IAAI;QACJ,IAAA,CAAA,IAAI,GAAJ,IAAI;QACJ,IAAA,CAAA,KAAK,GAAL,KAAK;IACpB;;IAGI,IAAI,GAAA;AACT,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"PhysicsBinding.js","sources":["../../../../src/binding/PhysicsBinding.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAeA;;;;;;AAMG;MACU,cAAc,CAAA;AAEP,IAAA,IAAA;AACA,IAAA,IAAA;AACA,IAAA,KAAA;AAHlB,IAAA,WAAA,CACkB,IAAiB,EACjB,IAAe,EACf,QAAyC,cAAc,EAAA;QAFvD,IAAA,CAAA,IAAI,GAAJ,IAAI;QACJ,IAAA,CAAA,IAAI,GAAJ,IAAI;QACJ,IAAA,CAAA,KAAK,GAAL,KAAK;IACpB;;IAGI,IAAI,GAAA;AACT,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC/C,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpE;AACD;;;;"}
|
|
@@ -10,5 +10,6 @@ import type { BroadPhase, CandidatePair } from './BroadPhase';
|
|
|
10
10
|
*/
|
|
11
11
|
export declare class SweepAndPrune implements BroadPhase {
|
|
12
12
|
private readonly _sorted;
|
|
13
|
+
private readonly _pairPool;
|
|
13
14
|
computePairs(colliders: readonly Collider[], out: CandidatePair[]): CandidatePair[];
|
|
14
15
|
}
|