@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Codexo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @codexo/exojs-physics
2
+
3
+ Native 2D **collision, query and sensor** runtime for [ExoJS](https://github.com/Exoridus/ExoJS).
4
+
5
+ Zero production dependencies, ESM-only, version-locked with the core engine. This
6
+ first release ships a complete **collision/query world without dynamics**:
7
+ shapes, colliders, bodies, a broad phase, a manifold-generating narrow phase,
8
+ collision filters, sensors, events, spatial queries, scene-node binding and a
9
+ debug overlay. Forces, impulses and gravity integration (the rigid-body solver)
10
+ arrive in a follow-up — the public surface here is forward-compatible with it.
11
+
12
+ > **Library, not an extension.** Physics contributes no renderer or asset
13
+ > bindings, so there is no `/register` entry. Construct a `PhysicsWorld`
14
+ > directly. `@codexo/exojs` is a peer dependency.
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ npm install @codexo/exojs @codexo/exojs-physics
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```ts
25
+ import { Scene, Sprite, Vector, type Time } from '@codexo/exojs';
26
+ import { BoxShape, CircleShape, PhysicsWorld } from '@codexo/exojs-physics';
27
+
28
+ class GameScene extends Scene {
29
+ private readonly world = new PhysicsWorld({ gravity: new Vector(0, 980) });
30
+
31
+ public override onStart(): void {
32
+ // Static ground (an explicit static body + box collider).
33
+ this.world.createStaticCollider({ shape: new BoxShape(800, 32), position: new Vector(400, 600), friction: 0.9 });
34
+
35
+ // A kinematic platform you move yourself.
36
+ const platform = this.world.createBody({ type: 'kinematic', position: new Vector(200, 400) });
37
+ platform.createCollider({ shape: new BoxShape(120, 16) });
38
+
39
+ // A sensor trigger.
40
+ const trigger = this.world.createStaticCollider({ shape: new CircleShape(40), position: new Vector(600, 500), isSensor: true });
41
+ this.world.onSensorEnter.add(({ sensor, other }) => {
42
+ if (sensor === trigger) console.log('entered the trigger');
43
+ });
44
+ }
45
+
46
+ public override update(delta: Time): void {
47
+ this.world.step(delta.seconds); // fixed-step detection + events + binding
48
+ }
49
+ }
50
+ ```
51
+
52
+ ## What it does
53
+
54
+ | Area | API |
55
+ |---|---|
56
+ | World | `PhysicsWorld`, `step`, `gravity`, `timeStepper`, `destroy` |
57
+ | Bodies | `createBody` (`dynamic`/`static`/`kinematic`), `setTransform`, mass/inertia from colliders |
58
+ | Colliders | `createCollider`, `createStaticCollider`, density/friction/restitution, `isSensor`, filter, offset |
59
+ | Shapes | `CircleShape`, `PolygonShape` (convex-validated), `BoxShape` |
60
+ | Filtering | `CollisionFilter` (category/mask/group), `shouldCollide` |
61
+ | Events | `onCollisionStart` / `onCollisionEnd` / `onSensorEnter` / `onSensorExit` — immutable snapshots |
62
+ | Queries | `queryPoint`, `queryAabb` (+ `out` / `forEachAabbHit`), `rayCast`, `rayCastAll`, `overlapShape` |
63
+ | Binding | `bind(body, node)` — node tracks the body's position each step |
64
+ | Debug | `@codexo/exojs-physics/debug` → `PhysicsDebugDraw` (shapes/AABBs/contacts/normals/centres/broad-phase) |
65
+
66
+ ## Determinism & non-goals
67
+
68
+ Stepping is fully **caller-driven** and uses a fixed timestep with an
69
+ accumulator (`world.step(frameDeltaSeconds)`); the same build replays a scene
70
+ identically given the same inputs. There are **no rollback/lockstep determinism
71
+ guarantees across builds or machines** (floating-point reality). The package is
72
+ single-threaded and 2D only — no workers, GPU, 3D, soft bodies, fluids or
73
+ vehicles.
74
+
75
+ This release contains **no dynamics solver**: bodies move only via
76
+ `setTransform`. The narrow phase already produces full contact manifolds (normal,
77
+ 1–2 points, stable feature ids) so the solver can be added without changing the
78
+ public API.
79
+
80
+ ## License
81
+
82
+ MIT © Codexo
@@ -0,0 +1,14 @@
1
+ export interface Aabb {
2
+ minX: number;
3
+ minY: number;
4
+ maxX: number;
5
+ maxY: number;
6
+ }
7
+ /** Create a zero-extent AABB at the origin. */
8
+ export declare const createAabb: () => Aabb;
9
+ /** `true` when two AABBs overlap (touching edges count as overlapping). */
10
+ export declare const aabbOverlap: (a: Aabb, b: Aabb) => boolean;
11
+ /** `true` when `(x, y)` lies inside (or on the edge of) `aabb`. */
12
+ export declare const aabbContainsPoint: (aabb: Aabb, x: number, y: number) => boolean;
13
+ /** Grow `aabb` outward by `margin` on every side, in place. */
14
+ export declare const expandAabb: (aabb: Aabb, margin: number) => Aabb;
@@ -0,0 +1,12 @@
1
+ // Axis-aligned bounding box used by the broad phase and AABB queries. Stored as
2
+ // min/max extents (not centre + half-extent) because the sweep-and-prune broad
3
+ // phase and overlap tests read the edges directly.
4
+ /** Create a zero-extent AABB at the origin. */
5
+ const createAabb = () => ({ minX: 0, minY: 0, maxX: 0, maxY: 0 });
6
+ /** `true` when two AABBs overlap (touching edges count as overlapping). */
7
+ const aabbOverlap = (a, b) => a.minX <= b.maxX && a.maxX >= b.minX && a.minY <= b.maxY && a.maxY >= b.minY;
8
+ /** `true` when `(x, y)` lies inside (or on the edge of) `aabb`. */
9
+ const aabbContainsPoint = (aabb, x, y) => x >= aabb.minX && x <= aabb.maxX && y >= aabb.minY && y <= aabb.maxY;
10
+
11
+ export { aabbContainsPoint, aabbOverlap, createAabb };
12
+ //# sourceMappingURL=Aabb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Aabb.js","sources":["../../../src/Aabb.ts"],"sourcesContent":[null],"names":[],"mappings":"AAAA;AACA;AACA;AASA;AACO,MAAM,UAAU,GAAG,OAAa,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;AAE7E;AACO,MAAM,WAAW,GAAG,CAAC,CAAO,EAAE,CAAO,KAC1C,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;AAE1E;AACO,MAAM,iBAAiB,GAAG,CAAC,IAAU,EAAE,CAAS,EAAE,CAAS,KAChE,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC;;;;"}
@@ -0,0 +1,77 @@
1
+ import type { Aabb } from './Aabb';
2
+ import type { Mutable2D, Transform } from './math';
3
+ import type { PhysicsBody } from './PhysicsBody';
4
+ import type { Shape } from './shapes/Shape';
5
+ import type { CollisionFilter, VectorLike } from './types';
6
+ /** Construction options for a collider. */
7
+ export interface ColliderOptions {
8
+ /** The collision geometry. */
9
+ shape: Shape;
10
+ /** Density (mass per px²) for the owning body's mass; ignored for static/kinematic. Default `1`. */
11
+ density?: number;
12
+ /** Coulomb friction coefficient (used by the solver once dynamics ship). Default `0.2`. */
13
+ friction?: number;
14
+ /** Restitution / bounciness in `[0, 1]` (used by the solver once dynamics ship). Default `0`. */
15
+ restitution?: number;
16
+ /** When `true`, generates overlap events but no contact response. Default `false`. */
17
+ isSensor?: boolean;
18
+ /** Category/mask/group filter; partials merge over the defaults. */
19
+ filter?: Partial<CollisionFilter>;
20
+ /** Body-local offset of the shape origin. Default `(0, 0)`. */
21
+ offset?: VectorLike;
22
+ /** Body-local rotation of the shape in radians (compound colliders). Default `0`. */
23
+ rotation?: number;
24
+ }
25
+ /**
26
+ * Geometry attached to a {@link PhysicsBody}: a {@link Shape} plus a body-local
27
+ * offset/rotation, material (friction/restitution/density) and a collision
28
+ * filter. A body may own several colliders (compound). The collider also caches
29
+ * its world-space geometry — refreshed by {@link synchronize} — which the broad
30
+ * phase, narrow phase and queries read directly.
31
+ *
32
+ * Material fields and the filter are mutable; the shape and local placement are
33
+ * immutable (rebuild the collider to change geometry).
34
+ */
35
+ export declare class Collider {
36
+ readonly id: number;
37
+ readonly body: PhysicsBody;
38
+ readonly shape: Shape;
39
+ readonly offsetX: number;
40
+ readonly offsetY: number;
41
+ readonly localRotation: number;
42
+ density: number;
43
+ friction: number;
44
+ restitution: number;
45
+ isSensor: boolean;
46
+ readonly filter: CollisionFilter;
47
+ private readonly _localTransform;
48
+ private readonly _worldTransform;
49
+ private readonly _aabb;
50
+ private readonly _worldCenter;
51
+ private readonly _worldVertices;
52
+ private readonly _worldNormals;
53
+ private _destroyed;
54
+ constructor(id: number, body: PhysicsBody, options: ColliderOptions);
55
+ /** The collider's world AABB (valid after the latest {@link synchronize}). */
56
+ get aabb(): Readonly<Aabb>;
57
+ /** The collider's world transform (offset/rotation composed with the body's). */
58
+ get worldTransform(): Readonly<Transform>;
59
+ /** The collider's body-local transform (offset + local rotation). */
60
+ get localTransform(): Readonly<Transform>;
61
+ /** World-space circle centre (only meaningful for circle shapes). */
62
+ get worldCenter(): Readonly<Mutable2D>;
63
+ /** World-space polygon vertices `[x0, y0, …]` (only meaningful for polygon shapes). */
64
+ get worldVertices(): readonly number[];
65
+ /** World-space polygon outward normals `[x0, y0, …]` (only meaningful for polygon shapes). */
66
+ get worldNormals(): readonly number[];
67
+ /** `true` after the owning world has destroyed this collider. */
68
+ get destroyed(): boolean;
69
+ /**
70
+ * Recompute the cached world geometry from the body's transform. Called by the
71
+ * body on `setTransform`, on collider creation, and by the world before each
72
+ * detection pass.
73
+ */
74
+ synchronize(bodyTransform: Transform): void;
75
+ /** Internal: mark destroyed (called by the world). */
76
+ _markDestroyed(): void;
77
+ }
@@ -0,0 +1,138 @@
1
+ import { createAabb } from './Aabb.js';
2
+ import { createTransform, composeTransforms, applyTransform, applyRotation } from './math.js';
3
+ import { resolveFilter } from './types.js';
4
+
5
+ /**
6
+ * Geometry attached to a {@link PhysicsBody}: a {@link Shape} plus a body-local
7
+ * offset/rotation, material (friction/restitution/density) and a collision
8
+ * filter. A body may own several colliders (compound). The collider also caches
9
+ * its world-space geometry — refreshed by {@link synchronize} — which the broad
10
+ * phase, narrow phase and queries read directly.
11
+ *
12
+ * Material fields and the filter are mutable; the shape and local placement are
13
+ * immutable (rebuild the collider to change geometry).
14
+ */
15
+ class Collider {
16
+ id;
17
+ body;
18
+ shape;
19
+ offsetX;
20
+ offsetY;
21
+ localRotation;
22
+ density;
23
+ friction;
24
+ restitution;
25
+ isSensor;
26
+ filter;
27
+ _localTransform;
28
+ _worldTransform = createTransform();
29
+ _aabb = createAabb();
30
+ _worldCenter = { x: 0, y: 0 };
31
+ _worldVertices;
32
+ _worldNormals;
33
+ _destroyed = false;
34
+ constructor(id, body, options) {
35
+ const density = options.density ?? 1;
36
+ if (!Number.isFinite(density) || density < 0) {
37
+ throw new RangeError(`Collider: density must be a non-negative finite number, received ${density}.`);
38
+ }
39
+ this.id = id;
40
+ this.body = body;
41
+ this.shape = options.shape;
42
+ this.offsetX = options.offset?.x ?? 0;
43
+ this.offsetY = options.offset?.y ?? 0;
44
+ this.localRotation = options.rotation ?? 0;
45
+ this.density = density;
46
+ this.friction = options.friction ?? 0.2;
47
+ this.restitution = options.restitution ?? 0;
48
+ this.isSensor = options.isSensor ?? false;
49
+ this.filter = resolveFilter(options.filter);
50
+ this._localTransform = createTransform(this.offsetX, this.offsetY, this.localRotation);
51
+ if (this.shape.type === 'polygon') {
52
+ const polygon = this.shape;
53
+ this._worldVertices = new Array(polygon.count * 2).fill(0);
54
+ this._worldNormals = new Array(polygon.count * 2).fill(0);
55
+ }
56
+ else {
57
+ this._worldVertices = [];
58
+ this._worldNormals = [];
59
+ }
60
+ }
61
+ /** The collider's world AABB (valid after the latest {@link synchronize}). */
62
+ get aabb() {
63
+ return this._aabb;
64
+ }
65
+ /** The collider's world transform (offset/rotation composed with the body's). */
66
+ get worldTransform() {
67
+ return this._worldTransform;
68
+ }
69
+ /** The collider's body-local transform (offset + local rotation). */
70
+ get localTransform() {
71
+ return this._localTransform;
72
+ }
73
+ /** World-space circle centre (only meaningful for circle shapes). */
74
+ get worldCenter() {
75
+ return this._worldCenter;
76
+ }
77
+ /** World-space polygon vertices `[x0, y0, …]` (only meaningful for polygon shapes). */
78
+ get worldVertices() {
79
+ return this._worldVertices;
80
+ }
81
+ /** World-space polygon outward normals `[x0, y0, …]` (only meaningful for polygon shapes). */
82
+ get worldNormals() {
83
+ return this._worldNormals;
84
+ }
85
+ /** `true` after the owning world has destroyed this collider. */
86
+ get destroyed() {
87
+ return this._destroyed;
88
+ }
89
+ /**
90
+ * Recompute the cached world geometry from the body's transform. Called by the
91
+ * body on `setTransform`, on collider creation, and by the world before each
92
+ * detection pass.
93
+ */
94
+ synchronize(bodyTransform) {
95
+ const world = composeTransforms(bodyTransform, this._localTransform, this._worldTransform);
96
+ if (this.shape.type === 'circle') {
97
+ const radius = this.shape.radius;
98
+ this._worldCenter.x = world.x;
99
+ this._worldCenter.y = world.y;
100
+ this._aabb.minX = world.x - radius;
101
+ this._aabb.minY = world.y - radius;
102
+ this._aabb.maxX = world.x + radius;
103
+ this._aabb.maxY = world.y + radius;
104
+ return;
105
+ }
106
+ const polygon = this.shape;
107
+ const local = polygon.vertices;
108
+ const normals = polygon.normals;
109
+ const out = { x: 0, y: 0 };
110
+ let minX = Infinity;
111
+ let minY = Infinity;
112
+ let maxX = -Infinity;
113
+ let maxY = -Infinity;
114
+ for (let i = 0; i < polygon.count; i++) {
115
+ applyTransform(world, local[i * 2], local[i * 2 + 1], out);
116
+ this._worldVertices[i * 2] = out.x;
117
+ this._worldVertices[i * 2 + 1] = out.y;
118
+ minX = out.x < minX ? out.x : minX;
119
+ minY = out.y < minY ? out.y : minY;
120
+ maxX = out.x > maxX ? out.x : maxX;
121
+ maxY = out.y > maxY ? out.y : maxY;
122
+ applyRotation(world, normals[i * 2], normals[i * 2 + 1], out);
123
+ this._worldNormals[i * 2] = out.x;
124
+ this._worldNormals[i * 2 + 1] = out.y;
125
+ }
126
+ this._aabb.minX = minX;
127
+ this._aabb.minY = minY;
128
+ this._aabb.maxX = maxX;
129
+ this._aabb.maxY = maxY;
130
+ }
131
+ /** Internal: mark destroyed (called by the world). */
132
+ _markDestroyed() {
133
+ this._destroyed = true;
134
+ }
135
+ }
136
+
137
+ export { Collider };
138
+ //# sourceMappingURL=Collider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Collider.js","sources":["../../../src/Collider.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { CandidatePair } from './broadphase/BroadPhase';
2
+ import type { Collider } from './Collider';
3
+ import type { CollisionEvent, SensorEvent } from './events';
4
+ /**
5
+ * The persistent contact set. Each detection pass it diffs the currently
6
+ * touching collider pairs against the previous pass and produces immutable
7
+ * begin/end (and sensor enter/exit) event snapshots. Duplicate begin/end churn
8
+ * is suppressed by the persistent records, and the produced event arrays are
9
+ * sorted by collider id for deterministic dispatch (gate SG-D1/SG-X4).
10
+ *
11
+ * The graph holds no module-level state — each world owns one.
12
+ */
13
+ export declare class ContactGraph {
14
+ /** Immutable solid-contact begin snapshots produced by the latest {@link update}. */
15
+ readonly collisionStart: CollisionEvent[];
16
+ /** Immutable solid-contact end snapshots produced by the latest {@link update}. */
17
+ readonly collisionEnd: CollisionEvent[];
18
+ /** Immutable sensor-enter snapshots produced by the latest {@link update}. */
19
+ readonly sensorEnter: SensorEvent[];
20
+ /** Immutable sensor-exit snapshots produced by the latest {@link update}. */
21
+ readonly sensorExit: SensorEvent[];
22
+ private readonly _records;
23
+ private readonly _manifold;
24
+ /** Touching pairs currently tracked (for debug draw). */
25
+ get recordCount(): number;
26
+ /** Diff this pass's candidate pairs against the persistent set, collecting events. */
27
+ update(pairs: readonly CandidatePair[]): void;
28
+ /** Remove every record referencing `collider` (called when a collider is destroyed). */
29
+ removeCollider(collider: Collider): void;
30
+ /** Drop all records (world reset/destroy). */
31
+ clear(): void;
32
+ private _emitBegin;
33
+ private _emitEnd;
34
+ }
@@ -0,0 +1,157 @@
1
+ import { Manifold } from './collision/Manifold.js';
2
+ import { testOverlap, collide } from './collision/narrowphase.js';
3
+ import { shouldCollide } from './types.js';
4
+
5
+ /**
6
+ * The persistent contact set. Each detection pass it diffs the currently
7
+ * touching collider pairs against the previous pass and produces immutable
8
+ * begin/end (and sensor enter/exit) event snapshots. Duplicate begin/end churn
9
+ * is suppressed by the persistent records, and the produced event arrays are
10
+ * sorted by collider id for deterministic dispatch (gate SG-D1/SG-X4).
11
+ *
12
+ * The graph holds no module-level state — each world owns one.
13
+ */
14
+ class ContactGraph {
15
+ /** Immutable solid-contact begin snapshots produced by the latest {@link update}. */
16
+ collisionStart = [];
17
+ /** Immutable solid-contact end snapshots produced by the latest {@link update}. */
18
+ collisionEnd = [];
19
+ /** Immutable sensor-enter snapshots produced by the latest {@link update}. */
20
+ sensorEnter = [];
21
+ /** Immutable sensor-exit snapshots produced by the latest {@link update}. */
22
+ sensorExit = [];
23
+ _records = new Map();
24
+ _manifold = new Manifold();
25
+ /** Touching pairs currently tracked (for debug draw). */
26
+ get recordCount() {
27
+ return this._records.size;
28
+ }
29
+ /** Diff this pass's candidate pairs against the persistent set, collecting events. */
30
+ update(pairs) {
31
+ this.collisionStart.length = 0;
32
+ this.collisionEnd.length = 0;
33
+ this.sensorEnter.length = 0;
34
+ this.sensorExit.length = 0;
35
+ for (const record of this._records.values()) {
36
+ record.seen = false;
37
+ }
38
+ for (const pair of pairs) {
39
+ const a = pair.a;
40
+ const b = pair.b;
41
+ if (!shouldCollide(a.filter, b.filter)) {
42
+ continue;
43
+ }
44
+ const isSensor = a.isSensor || b.isSensor;
45
+ const touching = isSensor ? testOverlap(a, b) : collide(a, b, this._manifold);
46
+ const key = pairKey(a, b);
47
+ const record = this._records.get(key);
48
+ if (touching) {
49
+ if (!record) {
50
+ this._records.set(key, { a, b, isSensor, touching: true, seen: true });
51
+ this._emitBegin(a, b, isSensor);
52
+ }
53
+ else {
54
+ record.seen = true;
55
+ if (!record.touching) {
56
+ record.touching = true;
57
+ this._emitBegin(a, b, isSensor);
58
+ }
59
+ }
60
+ }
61
+ else if (record) {
62
+ if (record.touching) {
63
+ this._emitEnd(a, b, isSensor);
64
+ }
65
+ this._records.delete(key);
66
+ }
67
+ }
68
+ // Pairs that left the broad phase entirely while touching → fire end.
69
+ for (const [key, record] of this._records) {
70
+ if (!record.seen) {
71
+ if (record.touching) {
72
+ this._emitEnd(record.a, record.b, record.isSensor);
73
+ }
74
+ this._records.delete(key);
75
+ }
76
+ }
77
+ this.collisionStart.sort(byColliderPair);
78
+ this.collisionEnd.sort(byColliderPair);
79
+ this.sensorEnter.sort(bySensorPair);
80
+ this.sensorExit.sort(bySensorPair);
81
+ }
82
+ /** Remove every record referencing `collider` (called when a collider is destroyed). */
83
+ removeCollider(collider) {
84
+ for (const [key, record] of this._records) {
85
+ if (record.a === collider || record.b === collider) {
86
+ this._records.delete(key);
87
+ }
88
+ }
89
+ }
90
+ /** Drop all records (world reset/destroy). */
91
+ clear() {
92
+ this._records.clear();
93
+ }
94
+ _emitBegin(a, b, isSensor) {
95
+ if (isSensor) {
96
+ this.sensorEnter.push(makeSensorEvent(a, b));
97
+ }
98
+ else {
99
+ this.collisionStart.push(makeCollisionEvent(a, b, this._manifold));
100
+ }
101
+ }
102
+ _emitEnd(a, b, isSensor) {
103
+ if (isSensor) {
104
+ this.sensorExit.push(makeSensorEvent(a, b));
105
+ }
106
+ else {
107
+ this.collisionEnd.push(makeEndEvent(a, b));
108
+ }
109
+ }
110
+ }
111
+ /** Stable key for an unordered collider pair (`a.id < b.id` is guaranteed by the broad phase). */
112
+ const pairKey = (a, b) => `${a.id}:${b.id}`;
113
+ const makeCollisionEvent = (a, b, manifold) => {
114
+ const points = [];
115
+ for (let i = 0; i < manifold.pointCount; i++) {
116
+ const p = manifold.points[i];
117
+ points.push(Object.freeze({ x: p.x, y: p.y, penetration: p.penetration }));
118
+ }
119
+ return Object.freeze({
120
+ bodyA: a.body,
121
+ bodyB: b.body,
122
+ colliderA: a,
123
+ colliderB: b,
124
+ normal: Object.freeze({ x: manifold.normalX, y: manifold.normalY }),
125
+ points: Object.freeze(points),
126
+ });
127
+ };
128
+ const makeEndEvent = (a, b) => Object.freeze({
129
+ bodyA: a.body,
130
+ bodyB: b.body,
131
+ colliderA: a,
132
+ colliderB: b,
133
+ normal: Object.freeze({ x: 0, y: 0 }),
134
+ points: Object.freeze([]),
135
+ });
136
+ const makeSensorEvent = (a, b) => {
137
+ let sensor;
138
+ let other;
139
+ if (a.isSensor && b.isSensor) {
140
+ sensor = a.id < b.id ? a : b;
141
+ other = sensor === a ? b : a;
142
+ }
143
+ else if (a.isSensor) {
144
+ sensor = a;
145
+ other = b;
146
+ }
147
+ else {
148
+ sensor = b;
149
+ other = a;
150
+ }
151
+ return Object.freeze({ sensor, other });
152
+ };
153
+ const byColliderPair = (x, y) => x.colliderA.id - y.colliderA.id || x.colliderB.id - y.colliderB.id;
154
+ const bySensorPair = (x, y) => x.sensor.id - y.sensor.id || x.other.id - y.other.id;
155
+
156
+ export { ContactGraph };
157
+ //# sourceMappingURL=ContactGraph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContactGraph.js","sources":["../../../src/ContactGraph.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ import { Vector } from '@codexo/exojs';
2
+ import { Collider, type ColliderOptions } from './Collider';
3
+ import type { Transform } from './math';
4
+ import type { BodyType, VectorLike } from './types';
5
+ /** Construction options for a body. */
6
+ export interface BodyOptions {
7
+ /** Simulation role. Default `'dynamic'`. */
8
+ type?: BodyType;
9
+ /** Initial world position. Default `(0, 0)`. */
10
+ position?: VectorLike;
11
+ /** Initial rotation in radians. Default `0`. */
12
+ angle?: number;
13
+ /** Linear damping (applied once dynamics ship). Default `0`. */
14
+ linearDamping?: number;
15
+ /** Angular damping (applied once dynamics ship). Default `0`. */
16
+ angularDamping?: number;
17
+ /** Per-body multiplier on world gravity (applied once dynamics ship). Default `1`. */
18
+ gravityScale?: number;
19
+ /** When `true`, the body never rotates under contacts (infinite rotational inertia). Default `false`. */
20
+ fixedRotation?: boolean;
21
+ }
22
+ /**
23
+ * Internal hook the world hands to each body so colliders can be id-allocated
24
+ * and registered without the body importing the concrete world class.
25
+ */
26
+ export interface BodyOwner {
27
+ _allocateColliderId(): number;
28
+ _registerCollider(collider: Collider): void;
29
+ }
30
+ /**
31
+ * A rigid body: a world transform plus mass properties aggregated from its
32
+ * colliders. In this collision/query release a body holds and reports its
33
+ * transform and mass but is not integrated — it moves only via
34
+ * {@link setTransform} (game-driven / kinematic). Forces, impulses and gravity
35
+ * integration arrive with the dynamics solver.
36
+ */
37
+ export declare class PhysicsBody {
38
+ readonly id: number;
39
+ readonly type: BodyType;
40
+ /** Linear damping (inert until dynamics ship). */
41
+ linearDamping: number;
42
+ /** Angular damping (inert until dynamics ship). */
43
+ angularDamping: number;
44
+ /** Per-body gravity multiplier (inert until dynamics ship). */
45
+ gravityScale: number;
46
+ /** When `true`, rotational inertia is treated as infinite. */
47
+ fixedRotation: boolean;
48
+ /** Total mass (0 for static/kinematic). */
49
+ mass: number;
50
+ /** Inverse mass (`0` = immovable). */
51
+ invMass: number;
52
+ /** Rotational inertia about the centre of mass (0 for static/kinematic or fixed rotation). */
53
+ inertia: number;
54
+ /** Inverse rotational inertia (`0` = no angular response). */
55
+ invInertia: number;
56
+ private readonly _owner;
57
+ private readonly _transform;
58
+ private readonly _colliders;
59
+ private _comX;
60
+ private _comY;
61
+ private _massReady;
62
+ private _destroyed;
63
+ constructor(owner: BodyOwner, id: number, options?: BodyOptions);
64
+ /** World X. */
65
+ get x(): number;
66
+ /** World Y. */
67
+ get y(): number;
68
+ /** Rotation in radians. */
69
+ get angle(): number;
70
+ /** A fresh `Vector` copy of the body's world position. */
71
+ get position(): Vector;
72
+ /** The body's world transform (read-only; mutate via {@link setTransform}). */
73
+ get transform(): Readonly<Transform>;
74
+ /** The body's colliders (read-only view). */
75
+ get colliders(): readonly Collider[];
76
+ /** Centre of mass in body-local space (relative to the body origin). */
77
+ get centerOfMassX(): number;
78
+ /** Centre of mass in body-local space (relative to the body origin). */
79
+ get centerOfMassY(): number;
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(): boolean;
86
+ /** `true` after the owning world has destroyed this body. */
87
+ get destroyed(): boolean;
88
+ /** Attach a new collider, recomputing mass and synchronising world geometry. */
89
+ createCollider(options: ColliderOptions): Collider;
90
+ /**
91
+ * Set the body's world transform. Resets the body's cached collider geometry
92
+ * immediately so subsequent queries see the new placement. Returns `this`.
93
+ */
94
+ setTransform(position: VectorLike, angle?: number): this;
95
+ /** Refresh every collider's world geometry from the current transform. */
96
+ synchronizeColliders(): void;
97
+ /** Internal: detach a collider (called by the world during destruction). */
98
+ _removeCollider(collider: Collider): void;
99
+ /** Internal: mark destroyed (called by the world). */
100
+ _markDestroyed(): void;
101
+ /** Recompute mass, centre of mass and rotational inertia from the colliders. */
102
+ private _recomputeMass;
103
+ }