@codexo/exojs-physics 0.13.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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/dist/esm/Aabb.d.ts +14 -0
  4. package/dist/esm/Aabb.js +12 -0
  5. package/dist/esm/Aabb.js.map +1 -0
  6. package/dist/esm/Collider.d.ts +77 -0
  7. package/dist/esm/Collider.js +138 -0
  8. package/dist/esm/Collider.js.map +1 -0
  9. package/dist/esm/ContactGraph.d.ts +34 -0
  10. package/dist/esm/ContactGraph.js +157 -0
  11. package/dist/esm/ContactGraph.js.map +1 -0
  12. package/dist/esm/PhysicsBody.d.ts +103 -0
  13. package/dist/esm/PhysicsBody.js +186 -0
  14. package/dist/esm/PhysicsBody.js.map +1 -0
  15. package/dist/esm/PhysicsWorld.d.ts +126 -0
  16. package/dist/esm/PhysicsWorld.js +253 -0
  17. package/dist/esm/PhysicsWorld.js.map +1 -0
  18. package/dist/esm/TimeStepper.d.ts +39 -0
  19. package/dist/esm/TimeStepper.js +70 -0
  20. package/dist/esm/TimeStepper.js.map +1 -0
  21. package/dist/esm/backend/NativePhysicsBackend.d.ts +19 -0
  22. package/dist/esm/backend/NativePhysicsBackend.js +31 -0
  23. package/dist/esm/backend/NativePhysicsBackend.js.map +1 -0
  24. package/dist/esm/backend/PhysicsBackend.d.ts +23 -0
  25. package/dist/esm/binding/BindingRegistry.d.ts +21 -0
  26. package/dist/esm/binding/BindingRegistry.js +40 -0
  27. package/dist/esm/binding/BindingRegistry.js.map +1 -0
  28. package/dist/esm/binding/PhysicsBinding.d.ts +27 -0
  29. package/dist/esm/binding/PhysicsBinding.js +24 -0
  30. package/dist/esm/binding/PhysicsBinding.js.map +1 -0
  31. package/dist/esm/broadphase/BroadPhase.d.ts +22 -0
  32. package/dist/esm/broadphase/SweepAndPrune.d.ts +14 -0
  33. package/dist/esm/broadphase/SweepAndPrune.js +48 -0
  34. package/dist/esm/broadphase/SweepAndPrune.js.map +1 -0
  35. package/dist/esm/broadphase/index.d.ts +2 -0
  36. package/dist/esm/collision/CollisionProxy.d.ts +14 -0
  37. package/dist/esm/collision/Manifold.d.ts +33 -0
  38. package/dist/esm/collision/Manifold.js +28 -0
  39. package/dist/esm/collision/Manifold.js.map +1 -0
  40. package/dist/esm/collision/index.d.ts +3 -0
  41. package/dist/esm/collision/narrowphase.d.ts +14 -0
  42. package/dist/esm/collision/narrowphase.js +377 -0
  43. package/dist/esm/collision/narrowphase.js.map +1 -0
  44. package/dist/esm/debug/PhysicsDebugDraw.d.ts +46 -0
  45. package/dist/esm/debug/PhysicsDebugDraw.js +171 -0
  46. package/dist/esm/debug/PhysicsDebugDraw.js.map +1 -0
  47. package/dist/esm/debug/index.d.ts +1 -0
  48. package/dist/esm/debug/index.js +2 -0
  49. package/dist/esm/debug/index.js.map +1 -0
  50. package/dist/esm/events.d.ts +35 -0
  51. package/dist/esm/index.d.ts +1 -0
  52. package/dist/esm/index.js +12 -0
  53. package/dist/esm/index.js.map +1 -0
  54. package/dist/esm/math.d.ts +32 -0
  55. package/dist/esm/math.js +48 -0
  56. package/dist/esm/math.js.map +1 -0
  57. package/dist/esm/physicsBuildInfo.d.ts +15 -0
  58. package/dist/esm/physicsBuildInfo.js +8 -0
  59. package/dist/esm/physicsBuildInfo.js.map +1 -0
  60. package/dist/esm/public.d.ts +21 -0
  61. package/dist/esm/query/QueryEngine.d.ts +51 -0
  62. package/dist/esm/query/QueryEngine.js +246 -0
  63. package/dist/esm/query/QueryEngine.js.map +1 -0
  64. package/dist/esm/shapes/BoxShape.d.ts +11 -0
  65. package/dist/esm/shapes/BoxShape.js +29 -0
  66. package/dist/esm/shapes/BoxShape.js.map +1 -0
  67. package/dist/esm/shapes/CircleShape.d.ts +16 -0
  68. package/dist/esm/shapes/CircleShape.js +30 -0
  69. package/dist/esm/shapes/CircleShape.js.map +1 -0
  70. package/dist/esm/shapes/PolygonShape.d.ts +26 -0
  71. package/dist/esm/shapes/PolygonShape.js +156 -0
  72. package/dist/esm/shapes/PolygonShape.js.map +1 -0
  73. package/dist/esm/shapes/Shape.d.ts +26 -0
  74. package/dist/esm/shapes/Shape.js +16 -0
  75. package/dist/esm/shapes/Shape.js.map +1 -0
  76. package/dist/esm/shapes/index.d.ts +4 -0
  77. package/dist/esm/types.d.ts +39 -0
  78. package/dist/esm/types.js +28 -0
  79. package/dist/esm/types.js.map +1 -0
  80. package/package.json +51 -0
@@ -0,0 +1,186 @@
1
+ import { Vector } from '@codexo/exojs';
2
+ import { Collider } from './Collider.js';
3
+ import { createTransform, setTransform, applyTransform } from './math.js';
4
+
5
+ /**
6
+ * A rigid body: a world transform plus mass properties aggregated from its
7
+ * colliders. In this collision/query release a body holds and reports its
8
+ * transform and mass but is not integrated — it moves only via
9
+ * {@link setTransform} (game-driven / kinematic). Forces, impulses and gravity
10
+ * integration arrive with the dynamics solver.
11
+ */
12
+ class PhysicsBody {
13
+ id;
14
+ type;
15
+ /** Linear damping (inert until dynamics ship). */
16
+ linearDamping;
17
+ /** Angular damping (inert until dynamics ship). */
18
+ angularDamping;
19
+ /** Per-body gravity multiplier (inert until dynamics ship). */
20
+ gravityScale;
21
+ /** When `true`, rotational inertia is treated as infinite. */
22
+ fixedRotation;
23
+ /** Total mass (0 for static/kinematic). */
24
+ mass = 0;
25
+ /** Inverse mass (`0` = immovable). */
26
+ invMass = 0;
27
+ /** Rotational inertia about the centre of mass (0 for static/kinematic or fixed rotation). */
28
+ inertia = 0;
29
+ /** Inverse rotational inertia (`0` = no angular response). */
30
+ invInertia = 0;
31
+ _owner;
32
+ _transform;
33
+ _colliders = [];
34
+ _comX = 0;
35
+ _comY = 0;
36
+ _massReady = false;
37
+ _destroyed = false;
38
+ constructor(owner, id, options = {}) {
39
+ this.id = id;
40
+ this.type = options.type ?? 'dynamic';
41
+ this._owner = owner;
42
+ this._transform = createTransform(options.position?.x ?? 0, options.position?.y ?? 0, options.angle ?? 0);
43
+ this.linearDamping = options.linearDamping ?? 0;
44
+ this.angularDamping = options.angularDamping ?? 0;
45
+ this.gravityScale = options.gravityScale ?? 1;
46
+ this.fixedRotation = options.fixedRotation ?? false;
47
+ }
48
+ /** World X. */
49
+ get x() {
50
+ return this._transform.x;
51
+ }
52
+ /** World Y. */
53
+ get y() {
54
+ return this._transform.y;
55
+ }
56
+ /** Rotation in radians. */
57
+ get angle() {
58
+ return this._transform.angle;
59
+ }
60
+ /** A fresh `Vector` copy of the body's world position. */
61
+ get position() {
62
+ return new Vector(this._transform.x, this._transform.y);
63
+ }
64
+ /** The body's world transform (read-only; mutate via {@link setTransform}). */
65
+ get transform() {
66
+ return this._transform;
67
+ }
68
+ /** The body's colliders (read-only view). */
69
+ get colliders() {
70
+ return this._colliders;
71
+ }
72
+ /** Centre of mass in body-local space (relative to the body origin). */
73
+ get centerOfMassX() {
74
+ return this._comX;
75
+ }
76
+ /** Centre of mass in body-local space (relative to the body origin). */
77
+ get centerOfMassY() {
78
+ return this._comY;
79
+ }
80
+ /**
81
+ * `true` when the body is ready to simulate. Static/kinematic bodies are
82
+ * always ready (infinite-mass semantics); a dynamic body is ready only once
83
+ * it has at least one positive-density collider.
84
+ */
85
+ get isMassReady() {
86
+ return this.type !== 'dynamic' || this._massReady;
87
+ }
88
+ /** `true` after the owning world has destroyed this body. */
89
+ get destroyed() {
90
+ return this._destroyed;
91
+ }
92
+ /** Attach a new collider, recomputing mass and synchronising world geometry. */
93
+ createCollider(options) {
94
+ if (this._destroyed) {
95
+ throw new Error('PhysicsBody: cannot create a collider on a destroyed body.');
96
+ }
97
+ // Collider imports PhysicsBody type-only (erased), so this value import is
98
+ // one-directional — no runtime cycle.
99
+ const collider = new Collider(this._owner._allocateColliderId(), this, options);
100
+ this._colliders.push(collider);
101
+ this._recomputeMass();
102
+ collider.synchronize(this._transform);
103
+ this._owner._registerCollider(collider);
104
+ return collider;
105
+ }
106
+ /**
107
+ * Set the body's world transform. Resets the body's cached collider geometry
108
+ * immediately so subsequent queries see the new placement. Returns `this`.
109
+ */
110
+ setTransform(position, angle = this._transform.angle) {
111
+ if (this._destroyed) {
112
+ throw new Error('PhysicsBody: cannot move a destroyed body.');
113
+ }
114
+ setTransform(this._transform, position.x, position.y, angle);
115
+ for (const collider of this._colliders) {
116
+ collider.synchronize(this._transform);
117
+ }
118
+ return this;
119
+ }
120
+ /** Refresh every collider's world geometry from the current transform. */
121
+ synchronizeColliders() {
122
+ for (const collider of this._colliders) {
123
+ collider.synchronize(this._transform);
124
+ }
125
+ }
126
+ /** Internal: detach a collider (called by the world during destruction). */
127
+ _removeCollider(collider) {
128
+ const index = this._colliders.indexOf(collider);
129
+ if (index !== -1) {
130
+ this._colliders.splice(index, 1);
131
+ this._recomputeMass();
132
+ }
133
+ }
134
+ /** Internal: mark destroyed (called by the world). */
135
+ _markDestroyed() {
136
+ this._destroyed = true;
137
+ }
138
+ /** Recompute mass, centre of mass and rotational inertia from the colliders. */
139
+ _recomputeMass() {
140
+ if (this.type !== 'dynamic') {
141
+ this.mass = 0;
142
+ this.invMass = 0;
143
+ this.inertia = 0;
144
+ this.invInertia = 0;
145
+ this._comX = 0;
146
+ this._comY = 0;
147
+ this._massReady = false;
148
+ return;
149
+ }
150
+ let mass = 0;
151
+ let cx = 0;
152
+ let cy = 0;
153
+ let inertiaOrigin = 0;
154
+ const point = { x: 0, y: 0 };
155
+ for (const collider of this._colliders) {
156
+ const shape = collider.shape;
157
+ const m = collider.density * shape.area;
158
+ if (m <= 0) {
159
+ continue;
160
+ }
161
+ // Shape centroid expressed in body-local space (offset + local rotation).
162
+ applyTransform(collider.localTransform, shape.centroidX, shape.centroidY, point);
163
+ mass += m;
164
+ cx += m * point.x;
165
+ cy += m * point.y;
166
+ // Parallel-axis to the body origin: I_origin += I_centroid + m·d².
167
+ inertiaOrigin += collider.density * shape.unitInertia + m * (point.x * point.x + point.y * point.y);
168
+ }
169
+ if (mass > 0) {
170
+ cx /= mass;
171
+ cy /= mass;
172
+ }
173
+ this.mass = mass;
174
+ this.invMass = mass > 0 ? 1 / mass : 0;
175
+ this._comX = cx;
176
+ this._comY = cy;
177
+ // Shift inertia from the body origin to the centre of mass.
178
+ const inertiaCom = inertiaOrigin - mass * (cx * cx + cy * cy);
179
+ this.inertia = this.fixedRotation ? 0 : inertiaCom;
180
+ this.invInertia = this.fixedRotation || inertiaCom <= 0 ? 0 : 1 / inertiaCom;
181
+ this._massReady = mass > 0;
182
+ }
183
+ }
184
+
185
+ export { PhysicsBody };
186
+ //# sourceMappingURL=PhysicsBody.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PhysicsBody.js","sources":["../../../src/PhysicsBody.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ import type { SceneNode } from '@codexo/exojs';
2
+ import { Signal, Vector } from '@codexo/exojs';
3
+ import type { Aabb } from './Aabb';
4
+ import type { PhysicsBackend } from './backend/PhysicsBackend';
5
+ import type { BindingOptions, PhysicsBinding } from './binding/PhysicsBinding';
6
+ import type { Collider, ColliderOptions } from './Collider';
7
+ import type { CollisionEvent, SensorEvent } from './events';
8
+ import type { BodyOptions, BodyOwner } from './PhysicsBody';
9
+ import { PhysicsBody } from './PhysicsBody';
10
+ import type { QueryFilter, RayHit } from './query/QueryEngine';
11
+ import type { Shape } from './shapes/Shape';
12
+ import { TimeStepper } from './TimeStepper';
13
+ import type { VectorLike } from './types';
14
+ /** Construction options for a {@link PhysicsWorld}. */
15
+ export interface PhysicsWorldOptions {
16
+ /** Gravity in px/s² (+Y down). Stored; applied once the dynamics solver ships. Default `(0, 0)`. */
17
+ gravity?: VectorLike;
18
+ /** Fixed timestep in seconds. Default `1 / 60`. */
19
+ fixedDelta?: number;
20
+ /** Maximum sub-steps per `step` (spiral-of-death guard). Default `8`. */
21
+ maxSubSteps?: number;
22
+ /** Solver velocity iterations (stored for forward-compat). Default `8`. */
23
+ velocityIterations?: number;
24
+ /** Solver position iterations (stored for forward-compat). Default `3`. */
25
+ positionIterations?: number;
26
+ /** Interpolate bound nodes between sub-steps (no effect until dynamics integrate). Default `true`. */
27
+ interpolation?: boolean;
28
+ }
29
+ /** {@link PhysicsWorld.createStaticCollider} options: a collider plus its static body placement. */
30
+ export interface StaticColliderOptions extends ColliderOptions {
31
+ /** World position of the implicit static body. Default `(0, 0)`. */
32
+ position?: VectorLike;
33
+ /** Rotation (radians) of the implicit static body. Default `0`. */
34
+ angle?: number;
35
+ }
36
+ /**
37
+ * The collision/query world: owns bodies, colliders, the detection backend,
38
+ * bindings, the query engine and the fixed-step accumulator. Stepped by the
39
+ * caller (commonly from a `Scene.update`), it runs broad- and narrow-phase
40
+ * detection, fires immutable contact/sensor events, and writes bound node
41
+ * transforms. It holds **no module-level state**, so any number of worlds run
42
+ * in isolation (gate I-1).
43
+ *
44
+ * This release performs collision detection, sensors, events, queries and
45
+ * binding; bodies move only via {@link PhysicsBody.setTransform}. Gravity,
46
+ * forces and impulse integration arrive with the dynamics solver.
47
+ */
48
+ export declare class PhysicsWorld implements BodyOwner {
49
+ /** Fires when two solid colliders begin touching. Argument is an immutable snapshot. */
50
+ readonly onCollisionStart: Signal<[CollisionEvent]>;
51
+ /** Fires when two solid colliders stop touching (or one is destroyed). */
52
+ readonly onCollisionEnd: Signal<[CollisionEvent]>;
53
+ /** Fires when a collider enters a sensor. */
54
+ readonly onSensorEnter: Signal<[SensorEvent]>;
55
+ /** Fires when a collider leaves a sensor. */
56
+ readonly onSensorExit: Signal<[SensorEvent]>;
57
+ /** World gravity (px/s², +Y down). Stored until dynamics ship. */
58
+ readonly gravity: Vector;
59
+ /** The fixed-step accumulator. */
60
+ readonly timeStepper: TimeStepper;
61
+ /** Whether bound nodes interpolate between sub-steps (no effect until dynamics). */
62
+ readonly interpolation: boolean;
63
+ /** Solver velocity iterations (stored for forward-compat). */
64
+ readonly velocityIterations: number;
65
+ /** Solver position iterations (stored for forward-compat). */
66
+ readonly positionIterations: number;
67
+ private readonly _backend;
68
+ private readonly _bodies;
69
+ private readonly _colliders;
70
+ private readonly _bindings;
71
+ private readonly _query;
72
+ private readonly _commands;
73
+ private _nextBodyId;
74
+ private _nextColliderId;
75
+ private _dispatching;
76
+ private _destroyed;
77
+ constructor(options?: PhysicsWorldOptions);
78
+ /** Live bodies (read-only view). */
79
+ get bodies(): readonly PhysicsBody[];
80
+ /** Live colliders (read-only view). */
81
+ get colliders(): readonly Collider[];
82
+ /** Create a body. Safe to call inside an event callback (deferred to end of step). */
83
+ createBody(options?: BodyOptions): PhysicsBody;
84
+ /** Sugar: an explicit static body carrying a single collider. The body is addressable via `collider.body`. */
85
+ createStaticCollider(options: StaticColliderOptions): Collider;
86
+ /** Destroy a body and its colliders. Deferred when called inside a callback. */
87
+ destroyBody(body: PhysicsBody): void;
88
+ /** Destroy a single collider, recomputing its body's mass. Deferred when called inside a callback. */
89
+ destroyCollider(collider: Collider): void;
90
+ /**
91
+ * Advance the world by `frameDeltaSeconds`. Accumulates into fixed sub-steps,
92
+ * runs detection, dispatches events, then writes bound node transforms.
93
+ */
94
+ step(frameDeltaSeconds: number): void;
95
+ /** Link a body to a scene node; the node tracks the body after each step. */
96
+ bind(body: PhysicsBody, node: SceneNode, options?: BindingOptions): PhysicsBinding;
97
+ /** Remove a body↔node link. */
98
+ unbind(body: PhysicsBody): void;
99
+ /** Colliders containing `point`. Fresh array. */
100
+ queryPoint(point: VectorLike, filter?: QueryFilter): Collider[];
101
+ /** Colliders whose AABB overlaps `bounds`. Writes into `out` (cleared) if given. */
102
+ queryAabb(bounds: Aabb, filter?: QueryFilter, out?: Collider[]): Collider[];
103
+ /** Invoke `callback` for each collider whose AABB overlaps `bounds`. Allocation-free. */
104
+ forEachAabbHit(bounds: Aabb, filter: QueryFilter | undefined, callback: (collider: Collider) => void): void;
105
+ /** Nearest collider hit by the ray, or `null`. */
106
+ rayCast(origin: VectorLike, direction: VectorLike, filter?: QueryFilter, maxDistance?: number): RayHit | null;
107
+ /** All collider hits along the ray, sorted by distance. Writes into `out` (cleared) if given. */
108
+ rayCastAll(origin: VectorLike, direction: VectorLike, filter?: QueryFilter, out?: RayHit[], maxDistance?: number): RayHit[];
109
+ /** Colliders overlapping `shape` placed at `position`/`angle`. Fresh array. */
110
+ overlapShape(shape: Shape, position: VectorLike, filter?: QueryFilter, angle?: number): Collider[];
111
+ /** Release every body, collider, binding and backend resource. */
112
+ destroy(): void;
113
+ _allocateColliderId(): number;
114
+ _registerCollider(collider: Collider): void;
115
+ /** The detection backend (internal; consumed by the debug draw layer). */
116
+ get backend(): PhysicsBackend;
117
+ private _dispatchEvents;
118
+ /** Run `command` now, or queue it when inside an event dispatch (deferred to end of step). */
119
+ private _defer;
120
+ private _drainCommands;
121
+ private _removeBody;
122
+ private _teardownBody;
123
+ private _removeCollider;
124
+ private _detachCollider;
125
+ private _assertAlive;
126
+ }
@@ -0,0 +1,253 @@
1
+ import { Signal, Vector } from '@codexo/exojs';
2
+ import { NativePhysicsBackend } from './backend/NativePhysicsBackend.js';
3
+ import { BindingRegistry } from './binding/BindingRegistry.js';
4
+ import { PhysicsBody } from './PhysicsBody.js';
5
+ import { QueryEngine } from './query/QueryEngine.js';
6
+ import { TimeStepper } from './TimeStepper.js';
7
+
8
+ /**
9
+ * The collision/query world: owns bodies, colliders, the detection backend,
10
+ * bindings, the query engine and the fixed-step accumulator. Stepped by the
11
+ * caller (commonly from a `Scene.update`), it runs broad- and narrow-phase
12
+ * detection, fires immutable contact/sensor events, and writes bound node
13
+ * transforms. It holds **no module-level state**, so any number of worlds run
14
+ * in isolation (gate I-1).
15
+ *
16
+ * This release performs collision detection, sensors, events, queries and
17
+ * binding; bodies move only via {@link PhysicsBody.setTransform}. Gravity,
18
+ * forces and impulse integration arrive with the dynamics solver.
19
+ */
20
+ class PhysicsWorld {
21
+ /** Fires when two solid colliders begin touching. Argument is an immutable snapshot. */
22
+ onCollisionStart = new Signal();
23
+ /** Fires when two solid colliders stop touching (or one is destroyed). */
24
+ onCollisionEnd = new Signal();
25
+ /** Fires when a collider enters a sensor. */
26
+ onSensorEnter = new Signal();
27
+ /** Fires when a collider leaves a sensor. */
28
+ onSensorExit = new Signal();
29
+ /** World gravity (px/s², +Y down). Stored until dynamics ship. */
30
+ gravity;
31
+ /** The fixed-step accumulator. */
32
+ timeStepper;
33
+ /** Whether bound nodes interpolate between sub-steps (no effect until dynamics). */
34
+ interpolation;
35
+ /** Solver velocity iterations (stored for forward-compat). */
36
+ velocityIterations;
37
+ /** Solver position iterations (stored for forward-compat). */
38
+ positionIterations;
39
+ _backend = new NativePhysicsBackend();
40
+ _bodies = [];
41
+ _colliders = [];
42
+ _bindings = new BindingRegistry();
43
+ _query;
44
+ _commands = [];
45
+ _nextBodyId = 1;
46
+ _nextColliderId = 1;
47
+ _dispatching = false;
48
+ _destroyed = false;
49
+ constructor(options = {}) {
50
+ this.gravity = new Vector(options.gravity?.x ?? 0, options.gravity?.y ?? 0);
51
+ this.timeStepper = new TimeStepper({ fixedDelta: options.fixedDelta, maxSubSteps: options.maxSubSteps });
52
+ this.interpolation = options.interpolation ?? true;
53
+ this.velocityIterations = options.velocityIterations ?? 8;
54
+ this.positionIterations = options.positionIterations ?? 3;
55
+ this._query = new QueryEngine(this._colliders);
56
+ }
57
+ /** Live bodies (read-only view). */
58
+ get bodies() {
59
+ return this._bodies;
60
+ }
61
+ /** Live colliders (read-only view). */
62
+ get colliders() {
63
+ return this._colliders;
64
+ }
65
+ // ── lifecycle ──────────────────────────────────────────────────────────
66
+ /** Create a body. Safe to call inside an event callback (deferred to end of step). */
67
+ createBody(options) {
68
+ this._assertAlive();
69
+ const body = new PhysicsBody(this, this._nextBodyId++, options);
70
+ this._defer(() => {
71
+ if (!body.destroyed) {
72
+ this._bodies.push(body);
73
+ }
74
+ });
75
+ return body;
76
+ }
77
+ /** Sugar: an explicit static body carrying a single collider. The body is addressable via `collider.body`. */
78
+ createStaticCollider(options) {
79
+ const body = this.createBody({ type: 'static', position: options.position, angle: options.angle });
80
+ return body.createCollider(options);
81
+ }
82
+ /** Destroy a body and its colliders. Deferred when called inside a callback. */
83
+ destroyBody(body) {
84
+ this._defer(() => this._removeBody(body));
85
+ }
86
+ /** Destroy a single collider, recomputing its body's mass. Deferred when called inside a callback. */
87
+ destroyCollider(collider) {
88
+ this._defer(() => this._removeCollider(collider));
89
+ }
90
+ // ── stepping ───────────────────────────────────────────────────────────
91
+ /**
92
+ * Advance the world by `frameDeltaSeconds`. Accumulates into fixed sub-steps,
93
+ * runs detection, dispatches events, then writes bound node transforms.
94
+ */
95
+ step(frameDeltaSeconds) {
96
+ this._assertAlive();
97
+ const steps = this.timeStepper.advance(frameDeltaSeconds);
98
+ if (steps > 0) {
99
+ // Without dynamics, geometry is unchanged across sub-steps, so a single
100
+ // detection pass per frame suffices; the dynamics solver will run per
101
+ // sub-step here.
102
+ for (const body of this._bodies) {
103
+ body.synchronizeColliders();
104
+ }
105
+ this._backend.detect(this._colliders);
106
+ this._dispatchEvents();
107
+ }
108
+ this._bindings.sync();
109
+ this._drainCommands();
110
+ }
111
+ // ── binding ────────────────────────────────────────────────────────────
112
+ /** Link a body to a scene node; the node tracks the body after each step. */
113
+ bind(body, node, options) {
114
+ return this._bindings.bind(body, node, options);
115
+ }
116
+ /** Remove a body↔node link. */
117
+ unbind(body) {
118
+ this._bindings.unbind(body);
119
+ }
120
+ // ── queries ────────────────────────────────────────────────────────────
121
+ /** Colliders containing `point`. Fresh array. */
122
+ queryPoint(point, filter) {
123
+ return this._query.queryPoint(point, filter);
124
+ }
125
+ /** Colliders whose AABB overlaps `bounds`. Writes into `out` (cleared) if given. */
126
+ queryAabb(bounds, filter, out) {
127
+ return this._query.queryAabb(bounds, filter, out);
128
+ }
129
+ /** Invoke `callback` for each collider whose AABB overlaps `bounds`. Allocation-free. */
130
+ forEachAabbHit(bounds, filter, callback) {
131
+ this._query.forEachAabbHit(bounds, filter, callback);
132
+ }
133
+ /** Nearest collider hit by the ray, or `null`. */
134
+ rayCast(origin, direction, filter, maxDistance) {
135
+ return this._query.rayCast(origin, direction, filter, maxDistance);
136
+ }
137
+ /** All collider hits along the ray, sorted by distance. Writes into `out` (cleared) if given. */
138
+ rayCastAll(origin, direction, filter, out, maxDistance) {
139
+ return this._query.rayCastAll(origin, direction, filter, out, maxDistance);
140
+ }
141
+ /** Colliders overlapping `shape` placed at `position`/`angle`. Fresh array. */
142
+ overlapShape(shape, position, filter, angle) {
143
+ return this._query.overlapShape(shape, position, filter, angle);
144
+ }
145
+ /** Release every body, collider, binding and backend resource. */
146
+ destroy() {
147
+ if (this._destroyed) {
148
+ return;
149
+ }
150
+ this._destroyed = true;
151
+ for (const body of this._bodies) {
152
+ body._markDestroyed();
153
+ }
154
+ this._bodies.length = 0;
155
+ this._colliders.length = 0;
156
+ this._commands.length = 0;
157
+ this._bindings.clear();
158
+ this._backend.destroy();
159
+ this.onCollisionStart.destroy();
160
+ this.onCollisionEnd.destroy();
161
+ this.onSensorEnter.destroy();
162
+ this.onSensorExit.destroy();
163
+ }
164
+ // ── BodyOwner ──────────────────────────────────────────────────────────
165
+ _allocateColliderId() {
166
+ return this._nextColliderId++;
167
+ }
168
+ _registerCollider(collider) {
169
+ this._defer(() => {
170
+ if (!collider.destroyed) {
171
+ this._colliders.push(collider);
172
+ }
173
+ });
174
+ }
175
+ // ── internals ──────────────────────────────────────────────────────────
176
+ /** The detection backend (internal; consumed by the debug draw layer). */
177
+ get backend() {
178
+ return this._backend;
179
+ }
180
+ _dispatchEvents() {
181
+ const graph = this._backend.contactGraph;
182
+ this._dispatching = true;
183
+ for (const event of graph.collisionEnd) {
184
+ this.onCollisionEnd.dispatch(event);
185
+ }
186
+ for (const event of graph.sensorExit) {
187
+ this.onSensorExit.dispatch(event);
188
+ }
189
+ for (const event of graph.collisionStart) {
190
+ this.onCollisionStart.dispatch(event);
191
+ }
192
+ for (const event of graph.sensorEnter) {
193
+ this.onSensorEnter.dispatch(event);
194
+ }
195
+ this._dispatching = false;
196
+ }
197
+ /** Run `command` now, or queue it when inside an event dispatch (deferred to end of step). */
198
+ _defer(command) {
199
+ if (this._dispatching) {
200
+ this._commands.push(command);
201
+ }
202
+ else {
203
+ command();
204
+ }
205
+ }
206
+ _drainCommands() {
207
+ if (this._commands.length === 0) {
208
+ return;
209
+ }
210
+ const commands = this._commands.splice(0, this._commands.length);
211
+ for (const command of commands) {
212
+ command();
213
+ }
214
+ }
215
+ _removeBody(body) {
216
+ const index = this._bodies.indexOf(body);
217
+ if (index === -1) {
218
+ // Never added (created and destroyed within the same dispatch) — still
219
+ // tear down its colliders and mark it dead.
220
+ this._teardownBody(body);
221
+ return;
222
+ }
223
+ this._bodies.splice(index, 1);
224
+ this._teardownBody(body);
225
+ }
226
+ _teardownBody(body) {
227
+ for (const collider of body.colliders) {
228
+ this._detachCollider(collider);
229
+ }
230
+ this._bindings.unbind(body);
231
+ body._markDestroyed();
232
+ }
233
+ _removeCollider(collider) {
234
+ this._detachCollider(collider);
235
+ collider.body._removeCollider(collider);
236
+ }
237
+ _detachCollider(collider) {
238
+ const index = this._colliders.indexOf(collider);
239
+ if (index !== -1) {
240
+ this._colliders.splice(index, 1);
241
+ }
242
+ this._backend.removeCollider(collider);
243
+ collider._markDestroyed();
244
+ }
245
+ _assertAlive() {
246
+ if (this._destroyed) {
247
+ throw new Error('PhysicsWorld: the world has been destroyed.');
248
+ }
249
+ }
250
+ }
251
+
252
+ export { PhysicsWorld };
253
+ //# sourceMappingURL=PhysicsWorld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PhysicsWorld.js","sources":["../../../src/PhysicsWorld.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;AA0CA;;;;;;;;;;;AAWG;MACU,YAAY,CAAA;;AAEP,IAAA,gBAAgB,GAAG,IAAI,MAAM,EAAoB;;AAEjD,IAAA,cAAc,GAAG,IAAI,MAAM,EAAoB;;AAE/C,IAAA,aAAa,GAAG,IAAI,MAAM,EAAiB;;AAE3C,IAAA,YAAY,GAAG,IAAI,MAAM,EAAiB;;AAG1C,IAAA,OAAO;;AAEP,IAAA,WAAW;;AAEX,IAAA,aAAa;;AAEb,IAAA,kBAAkB;;AAElB,IAAA,kBAAkB;AAEjB,IAAA,QAAQ,GAAmB,IAAI,oBAAoB,EAAE;IACrD,OAAO,GAAkB,EAAE;IAC3B,UAAU,GAAe,EAAE;AAC3B,IAAA,SAAS,GAAG,IAAI,eAAe,EAAE;AACjC,IAAA,MAAM;IACN,SAAS,GAAsB,EAAE;IAE1C,WAAW,GAAG,CAAC;IACf,eAAe,GAAG,CAAC;IACnB,YAAY,GAAG,KAAK;IACpB,UAAU,GAAG,KAAK;AAE1B,IAAA,WAAA,CAAmB,UAA+B,EAAE,EAAA;QAClD,IAAI,CAAC,OAAO,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QAC3E,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;QACxG,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,IAAI;QAClD,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,CAAC;QACzD,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,CAAC;QACzD,IAAI,CAAC,MAAM,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;IAChD;;AAGA,IAAA,IAAW,MAAM,GAAA;QACf,OAAO,IAAI,CAAC,OAAO;IACrB;;AAGA,IAAA,IAAW,SAAS,GAAA;QAClB,OAAO,IAAI,CAAC,UAAU;IACxB;;;AAKO,IAAA,UAAU,CAAC,OAAqB,EAAA;QACrC,IAAI,CAAC,YAAY,EAAE;AAEnB,QAAA,MAAM,IAAI,GAAG,IAAI,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC;AAE/D,QAAA,IAAI,CAAC,MAAM,CAAC,MAAK;AACf,YAAA,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;AACnB,gBAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YACzB;AACF,QAAA,CAAC,CAAC;AAEF,QAAA,OAAO,IAAI;IACb;;AAGO,IAAA,oBAAoB,CAAC,OAA8B,EAAA;QACxD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;AAElG,QAAA,OAAO,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;IACrC;;AAGO,IAAA,WAAW,CAAC,IAAiB,EAAA;AAClC,QAAA,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAC3C;;AAGO,IAAA,eAAe,CAAC,QAAkB,EAAA;AACvC,QAAA,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IACnD;;AAIA;;;AAGG;AACI,IAAA,IAAI,CAAC,iBAAyB,EAAA;QACnC,IAAI,CAAC,YAAY,EAAE;QAEnB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC;AAEzD,QAAA,IAAI,KAAK,GAAG,CAAC,EAAE;;;;AAIb,YAAA,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE;gBAC/B,IAAI,CAAC,oBAAoB,EAAE;YAC7B;YAEA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YACrC,IAAI,CAAC,eAAe,EAAE;QACxB;AAEA,QAAA,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;QACrB,IAAI,CAAC,cAAc,EAAE;IACvB;;;AAKO,IAAA,IAAI,CAAC,IAAiB,EAAE,IAAe,EAAE,OAAwB,EAAA;AACtE,QAAA,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;IACjD;;AAGO,IAAA,MAAM,CAAC,IAAiB,EAAA;AAC7B,QAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC;IAC7B;;;IAKO,UAAU,CAAC,KAAiB,EAAE,MAAoB,EAAA;QACvD,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC;IAC9C;;AAGO,IAAA,SAAS,CAAC,MAAY,EAAE,MAAoB,EAAE,GAAgB,EAAA;AACnE,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC;IACnD;;AAGO,IAAA,cAAc,CAAC,MAAY,EAAE,MAA+B,EAAE,QAAsC,EAAA;QACzG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;IACtD;;AAGO,IAAA,OAAO,CAAC,MAAkB,EAAE,SAAqB,EAAE,MAAoB,EAAE,WAAoB,EAAA;AAClG,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC;IACpE;;IAGO,UAAU,CAAC,MAAkB,EAAE,SAAqB,EAAE,MAAoB,EAAE,GAAc,EAAE,WAAoB,EAAA;AACrH,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,CAAC;IAC5E;;AAGO,IAAA,YAAY,CAAC,KAAY,EAAE,QAAoB,EAAE,MAAoB,EAAE,KAAc,EAAA;AAC1F,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;IACjE;;IAGO,OAAO,GAAA;AACZ,QAAA,IAAI,IAAI,CAAC,UAAU,EAAE;YACnB;QACF;AAEA,QAAA,IAAI,CAAC,UAAU,GAAG,IAAI;AAEtB,QAAA,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE;YAC/B,IAAI,CAAC,cAAc,EAAE;QACvB;AAEA,QAAA,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;AACvB,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC;AAC1B,QAAA,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;AACzB,QAAA,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE;AACtB,QAAA,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE;AACvB,QAAA,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE;AAC/B,QAAA,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;AAC7B,QAAA,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE;AAC5B,QAAA,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE;IAC7B;;IAIO,mBAAmB,GAAA;AACxB,QAAA,OAAO,IAAI,CAAC,eAAe,EAAE;IAC/B;AAEO,IAAA,iBAAiB,CAAC,QAAkB,EAAA;AACzC,QAAA,IAAI,CAAC,MAAM,CAAC,MAAK;AACf,YAAA,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE;AACvB,gBAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;YAChC;AACF,QAAA,CAAC,CAAC;IACJ;;;AAKA,IAAA,IAAW,OAAO,GAAA;QAChB,OAAO,IAAI,CAAC,QAAQ;IACtB;IAEQ,eAAe,GAAA;AACrB,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY;AAExC,QAAA,IAAI,CAAC,YAAY,GAAG,IAAI;AAExB,QAAA,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,YAAY,EAAE;AACtC,YAAA,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC;QACrC;AAEA,QAAA,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,UAAU,EAAE;AACpC,YAAA,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC;QACnC;AAEA,QAAA,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,cAAc,EAAE;AACxC,YAAA,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvC;AAEA,QAAA,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE;AACrC,YAAA,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC;QACpC;AAEA,QAAA,IAAI,CAAC,YAAY,GAAG,KAAK;IAC3B;;AAGQ,IAAA,MAAM,CAAC,OAAmB,EAAA;AAChC,QAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AACrB,YAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;QAC9B;aAAO;AACL,YAAA,OAAO,EAAE;QACX;IACF;IAEQ,cAAc,GAAA;QACpB,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE;YAC/B;QACF;AAEA,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;AAEhE,QAAA,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;AAC9B,YAAA,OAAO,EAAE;QACX;IACF;AAEQ,IAAA,WAAW,CAAC,IAAiB,EAAA;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;AAExC,QAAA,IAAI,KAAK,KAAK,EAAE,EAAE;;;AAGhB,YAAA,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;YAExB;QACF;QAEA,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;AAC7B,QAAA,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;IAC1B;AAEQ,IAAA,aAAa,CAAC,IAAiB,EAAA;AACrC,QAAA,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE;AACrC,YAAA,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC;QAChC;AAEA,QAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC;QAC3B,IAAI,CAAC,cAAc,EAAE;IACvB;AAEQ,IAAA,eAAe,CAAC,QAAkB,EAAA;AACxC,QAAA,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC;AAC9B,QAAA,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC;IACzC;AAEQ,IAAA,eAAe,CAAC,QAAkB,EAAA;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC;AAE/C,QAAA,IAAI,KAAK,KAAK,EAAE,EAAE;YAChB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAClC;AAEA,QAAA,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC;QACtC,QAAQ,CAAC,cAAc,EAAE;IAC3B;IAEQ,YAAY,GAAA;AAClB,QAAA,IAAI,IAAI,CAAC,UAAU,EAAE;AACnB,YAAA,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC;QAChE;IACF;AACD;;;;"}
@@ -0,0 +1,39 @@
1
+ /** Construction options for {@link TimeStepper}. */
2
+ export interface TimeStepperOptions {
3
+ /** Fixed simulation timestep in seconds. Default `1 / 60`. Must be > 0. */
4
+ fixedDelta?: number;
5
+ /** Maximum sub-steps consumed per `advance` call (spiral-of-death guard). Default `8`. Must be ≥ 1. */
6
+ maxSubSteps?: number;
7
+ }
8
+ /**
9
+ * Fixed-timestep accumulator (package-owned; no engine clock dependency). Each
10
+ * frame, {@link advance} adds the variable frame delta to an internal
11
+ * accumulator and reports how many whole fixed sub-steps to run
12
+ * (`floor(accumulator / fixedDelta)`), clamped to {@link maxSubSteps}. When the
13
+ * clamp trips, the backlog beyond the clamp is discarded so a slow frame cannot
14
+ * cascade into an unbounded catch-up (the "spiral of death").
15
+ *
16
+ * {@link alpha} is the leftover fraction of a step `[0, 1)`, for interpolating
17
+ * bound transforms between sub-steps.
18
+ *
19
+ * The stepper holds no mutable module-level state — two stepper instances are
20
+ * fully independent (world isolation, gate I-1).
21
+ */
22
+ export declare class TimeStepper {
23
+ readonly fixedDelta: number;
24
+ readonly maxSubSteps: number;
25
+ private _accumulator;
26
+ constructor(options?: TimeStepperOptions);
27
+ /** Unconsumed accumulated time (seconds) currently held, always `< fixedDelta` after an `advance`. */
28
+ get accumulator(): number;
29
+ /** Interpolation fraction in `[0, 1)` = `accumulator / fixedDelta`. */
30
+ get alpha(): number;
31
+ /**
32
+ * Accumulate `frameDeltaSeconds` and return the number of fixed sub-steps to
33
+ * run this frame. Non-finite or non-positive deltas contribute nothing and
34
+ * return `0`.
35
+ */
36
+ advance(frameDeltaSeconds: number): number;
37
+ /** Clear the accumulator (e.g. after a teleport or a pause). */
38
+ reset(): void;
39
+ }