@codexo/exojs-physics 0.15.2 → 0.16.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 (201) hide show
  1. package/README.md +51 -23
  2. package/dist/esm/Aabb.d.ts +6 -10
  3. package/dist/esm/Aabb.d.ts.map +1 -0
  4. package/dist/esm/Aabb.js +9 -5
  5. package/dist/esm/Aabb.js.map +1 -1
  6. package/dist/esm/Collider.d.ts +62 -10
  7. package/dist/esm/Collider.d.ts.map +1 -0
  8. package/dist/esm/Collider.js +263 -157
  9. package/dist/esm/Collider.js.map +1 -1
  10. package/dist/esm/ContactGraph.d.ts +51 -5
  11. package/dist/esm/ContactGraph.d.ts.map +1 -0
  12. package/dist/esm/ContactGraph.js +282 -223
  13. package/dist/esm/ContactGraph.js.map +1 -1
  14. package/dist/esm/ContactModifier.d.ts +85 -0
  15. package/dist/esm/ContactModifier.d.ts.map +1 -0
  16. package/dist/esm/ContactModifier.js +49 -0
  17. package/dist/esm/ContactModifier.js.map +1 -0
  18. package/dist/esm/PhysicsBody.d.ts +56 -22
  19. package/dist/esm/PhysicsBody.d.ts.map +1 -0
  20. package/dist/esm/PhysicsBody.js +461 -443
  21. package/dist/esm/PhysicsBody.js.map +1 -1
  22. package/dist/esm/PhysicsWorld.d.ts +276 -43
  23. package/dist/esm/PhysicsWorld.d.ts.map +1 -0
  24. package/dist/esm/PhysicsWorld.js +890 -626
  25. package/dist/esm/PhysicsWorld.js.map +1 -1
  26. package/dist/esm/TimeStepper.d.ts +3 -2
  27. package/dist/esm/TimeStepper.d.ts.map +1 -0
  28. package/dist/esm/TimeStepper.js +56 -67
  29. package/dist/esm/TimeStepper.js.map +1 -1
  30. package/dist/esm/backend/NativePhysicsBackend.d.ts +8 -4
  31. package/dist/esm/backend/NativePhysicsBackend.d.ts.map +1 -0
  32. package/dist/esm/backend/NativePhysicsBackend.js +48 -41
  33. package/dist/esm/backend/NativePhysicsBackend.js.map +1 -1
  34. package/dist/esm/backend/PhysicsBackend.d.ts +9 -2
  35. package/dist/esm/backend/PhysicsBackend.d.ts.map +1 -0
  36. package/dist/esm/binding/BindingRegistry.d.ts +25 -4
  37. package/dist/esm/binding/BindingRegistry.d.ts.map +1 -0
  38. package/dist/esm/binding/BindingRegistry.js +70 -36
  39. package/dist/esm/binding/BindingRegistry.js.map +1 -1
  40. package/dist/esm/binding/PhysicsBinding.d.ts +22 -5
  41. package/dist/esm/binding/PhysicsBinding.d.ts.map +1 -0
  42. package/dist/esm/binding/PhysicsBinding.js +46 -21
  43. package/dist/esm/binding/PhysicsBinding.js.map +1 -1
  44. package/dist/esm/broadphase/AabbTreeBroadPhase.d.ts +68 -0
  45. package/dist/esm/broadphase/AabbTreeBroadPhase.d.ts.map +1 -0
  46. package/dist/esm/broadphase/AabbTreeBroadPhase.js +149 -0
  47. package/dist/esm/broadphase/AabbTreeBroadPhase.js.map +1 -0
  48. package/dist/esm/broadphase/BroadPhase.d.ts +7 -4
  49. package/dist/esm/broadphase/BroadPhase.d.ts.map +1 -0
  50. package/dist/esm/broadphase/index.d.ts +2 -1
  51. package/dist/esm/broadphase/index.d.ts.map +1 -0
  52. package/dist/esm/collision/CollisionProxy.d.ts +3 -2
  53. package/dist/esm/collision/CollisionProxy.d.ts.map +1 -0
  54. package/dist/esm/collision/Manifold.d.ts +5 -4
  55. package/dist/esm/collision/Manifold.d.ts.map +1 -0
  56. package/dist/esm/collision/Manifold.js +34 -25
  57. package/dist/esm/collision/Manifold.js.map +1 -1
  58. package/dist/esm/collision/chainAdjacency.d.ts +29 -0
  59. package/dist/esm/collision/chainAdjacency.d.ts.map +1 -0
  60. package/dist/esm/collision/chainAdjacency.js +44 -0
  61. package/dist/esm/collision/chainAdjacency.js.map +1 -0
  62. package/dist/esm/collision/dispatch.d.ts +35 -0
  63. package/dist/esm/collision/dispatch.d.ts.map +1 -0
  64. package/dist/esm/collision/dispatch.js +58 -0
  65. package/dist/esm/collision/dispatch.js.map +1 -0
  66. package/dist/esm/collision/index.d.ts +2 -0
  67. package/dist/esm/collision/index.d.ts.map +1 -0
  68. package/dist/esm/collision/narrowphase.d.ts +5 -2
  69. package/dist/esm/collision/narrowphase.d.ts.map +1 -0
  70. package/dist/esm/collision/narrowphase.js +524 -399
  71. package/dist/esm/collision/narrowphase.js.map +1 -1
  72. package/dist/esm/collision/segments.d.ts +19 -0
  73. package/dist/esm/collision/segments.d.ts.map +1 -0
  74. package/dist/esm/collision/segments.js +36 -0
  75. package/dist/esm/collision/segments.js.map +1 -0
  76. package/dist/esm/collision/sweep.d.ts +35 -0
  77. package/dist/esm/collision/sweep.d.ts.map +1 -0
  78. package/dist/esm/collision/sweep.js +437 -0
  79. package/dist/esm/collision/sweep.js.map +1 -0
  80. package/dist/esm/debug/PhysicsDebugDraw.d.ts +23 -4
  81. package/dist/esm/debug/PhysicsDebugDraw.d.ts.map +1 -0
  82. package/dist/esm/debug/PhysicsDebugDraw.js +230 -188
  83. package/dist/esm/debug/PhysicsDebugDraw.js.map +1 -1
  84. package/dist/esm/debug/index.d.ts +1 -0
  85. package/dist/esm/debug/index.d.ts.map +1 -0
  86. package/dist/esm/debug/index.js +3 -2
  87. package/dist/esm/events.d.ts +1 -0
  88. package/dist/esm/events.d.ts.map +1 -0
  89. package/dist/esm/index.d.ts +1 -0
  90. package/dist/esm/index.d.ts.map +1 -0
  91. package/dist/esm/index.js +24 -19
  92. package/dist/esm/joints/DistanceJoint.d.ts +4 -3
  93. package/dist/esm/joints/DistanceJoint.d.ts.map +1 -0
  94. package/dist/esm/joints/DistanceJoint.js +160 -172
  95. package/dist/esm/joints/DistanceJoint.js.map +1 -1
  96. package/dist/esm/joints/Joint.d.ts +5 -4
  97. package/dist/esm/joints/Joint.d.ts.map +1 -0
  98. package/dist/esm/joints/Joint.js +23 -21
  99. package/dist/esm/joints/Joint.js.map +1 -1
  100. package/dist/esm/joints/MouseJoint.d.ts +8 -7
  101. package/dist/esm/joints/MouseJoint.d.ts.map +1 -0
  102. package/dist/esm/joints/MouseJoint.js +132 -133
  103. package/dist/esm/joints/MouseJoint.js.map +1 -1
  104. package/dist/esm/joints/PrismaticJoint.d.ts +5 -4
  105. package/dist/esm/joints/PrismaticJoint.d.ts.map +1 -0
  106. package/dist/esm/joints/PrismaticJoint.js +211 -240
  107. package/dist/esm/joints/PrismaticJoint.js.map +1 -1
  108. package/dist/esm/joints/RevoluteJoint.d.ts +4 -3
  109. package/dist/esm/joints/RevoluteJoint.d.ts.map +1 -0
  110. package/dist/esm/joints/RevoluteJoint.js +191 -213
  111. package/dist/esm/joints/RevoluteJoint.js.map +1 -1
  112. package/dist/esm/joints/WeldJoint.d.ts +3 -2
  113. package/dist/esm/joints/WeldJoint.d.ts.map +1 -0
  114. package/dist/esm/joints/WeldJoint.js +156 -152
  115. package/dist/esm/joints/WeldJoint.js.map +1 -1
  116. package/dist/esm/joints/WheelJoint.d.ts +4 -3
  117. package/dist/esm/joints/WheelJoint.d.ts.map +1 -0
  118. package/dist/esm/joints/WheelJoint.js +228 -255
  119. package/dist/esm/joints/WheelJoint.js.map +1 -1
  120. package/dist/esm/math.d.ts +6 -9
  121. package/dist/esm/math.d.ts.map +1 -0
  122. package/dist/esm/math.js +37 -39
  123. package/dist/esm/math.js.map +1 -1
  124. package/dist/esm/physicsBuildInfo.d.ts +1 -0
  125. package/dist/esm/physicsBuildInfo.d.ts.map +1 -0
  126. package/dist/esm/physicsBuildInfo.js +6 -4
  127. package/dist/esm/physicsBuildInfo.js.map +1 -1
  128. package/dist/esm/public.d.ts +8 -3
  129. package/dist/esm/public.d.ts.map +1 -0
  130. package/dist/esm/query/QueryEngine.d.ts +56 -12
  131. package/dist/esm/query/QueryEngine.d.ts.map +1 -0
  132. package/dist/esm/query/QueryEngine.js +494 -237
  133. package/dist/esm/query/QueryEngine.js.map +1 -1
  134. package/dist/esm/query/SpatialIndex.d.ts +34 -0
  135. package/dist/esm/query/SpatialIndex.d.ts.map +1 -0
  136. package/dist/esm/shapes/AnyShape.d.ts +9 -4
  137. package/dist/esm/shapes/AnyShape.d.ts.map +1 -0
  138. package/dist/esm/shapes/BoxShape.d.ts +2 -1
  139. package/dist/esm/shapes/BoxShape.d.ts.map +1 -0
  140. package/dist/esm/shapes/BoxShape.js +37 -25
  141. package/dist/esm/shapes/BoxShape.js.map +1 -1
  142. package/dist/esm/shapes/CapsuleShape.d.ts +30 -0
  143. package/dist/esm/shapes/CapsuleShape.d.ts.map +1 -0
  144. package/dist/esm/shapes/CapsuleShape.js +88 -0
  145. package/dist/esm/shapes/CapsuleShape.js.map +1 -0
  146. package/dist/esm/shapes/ChainEdgeShape.d.ts +30 -0
  147. package/dist/esm/shapes/ChainEdgeShape.d.ts.map +1 -0
  148. package/dist/esm/shapes/ChainEdgeShape.js +66 -0
  149. package/dist/esm/shapes/ChainEdgeShape.js.map +1 -0
  150. package/dist/esm/shapes/ChainShape.d.ts +52 -0
  151. package/dist/esm/shapes/ChainShape.d.ts.map +1 -0
  152. package/dist/esm/shapes/ChainShape.js +85 -0
  153. package/dist/esm/shapes/ChainShape.js.map +1 -0
  154. package/dist/esm/shapes/CircleShape.d.ts +3 -4
  155. package/dist/esm/shapes/CircleShape.d.ts.map +1 -0
  156. package/dist/esm/shapes/CircleShape.js +27 -26
  157. package/dist/esm/shapes/CircleShape.js.map +1 -1
  158. package/dist/esm/shapes/PolygonShape.d.ts +7 -8
  159. package/dist/esm/shapes/PolygonShape.d.ts.map +1 -0
  160. package/dist/esm/shapes/PolygonShape.js +131 -162
  161. package/dist/esm/shapes/PolygonShape.js.map +1 -1
  162. package/dist/esm/shapes/SegmentShape.d.ts +31 -0
  163. package/dist/esm/shapes/SegmentShape.d.ts.map +1 -0
  164. package/dist/esm/shapes/SegmentShape.js +62 -0
  165. package/dist/esm/shapes/SegmentShape.js.map +1 -0
  166. package/dist/esm/shapes/Shape.d.ts +28 -17
  167. package/dist/esm/shapes/Shape.d.ts.map +1 -0
  168. package/dist/esm/shapes/Shape.js +12 -13
  169. package/dist/esm/shapes/Shape.js.map +1 -1
  170. package/dist/esm/shapes/convexParts.d.ts +21 -0
  171. package/dist/esm/shapes/convexParts.d.ts.map +1 -0
  172. package/dist/esm/shapes/convexParts.js +26 -0
  173. package/dist/esm/shapes/convexParts.js.map +1 -0
  174. package/dist/esm/shapes/decompose.d.ts +24 -0
  175. package/dist/esm/shapes/decompose.d.ts.map +1 -0
  176. package/dist/esm/shapes/decompose.js +272 -0
  177. package/dist/esm/shapes/decompose.js.map +1 -0
  178. package/dist/esm/shapes/index.d.ts +4 -0
  179. package/dist/esm/shapes/index.d.ts.map +1 -0
  180. package/dist/esm/solver/ContactSolver.d.ts +12 -5
  181. package/dist/esm/solver/ContactSolver.d.ts.map +1 -0
  182. package/dist/esm/solver/ContactSolver.js +425 -471
  183. package/dist/esm/solver/ContactSolver.js.map +1 -1
  184. package/dist/esm/solver/tolerances.d.ts +34 -0
  185. package/dist/esm/solver/tolerances.d.ts.map +1 -0
  186. package/dist/esm/solver/tolerances.js +38 -0
  187. package/dist/esm/solver/tolerances.js.map +1 -0
  188. package/dist/esm/sort.d.ts +5 -4
  189. package/dist/esm/sort.d.ts.map +1 -0
  190. package/dist/esm/sort.js +41 -51
  191. package/dist/esm/sort.js.map +1 -1
  192. package/dist/esm/types.d.ts +4 -8
  193. package/dist/esm/types.d.ts.map +1 -0
  194. package/dist/esm/types.js +15 -17
  195. package/dist/esm/types.js.map +1 -1
  196. package/package.json +6 -6
  197. package/dist/esm/broadphase/SweepAndPrune.d.ts +0 -15
  198. package/dist/esm/broadphase/SweepAndPrune.js +0 -77
  199. package/dist/esm/broadphase/SweepAndPrune.js.map +0 -1
  200. package/dist/esm/debug/index.js.map +0 -1
  201. package/dist/esm/index.js.map +0 -1
@@ -1,245 +1,304 @@
1
- import { Manifold } from './collision/Manifold.js';
2
- import { testOverlap, collide } from './collision/narrowphase.js';
3
- import { sortInPlace } from './sort.js';
4
- import { shouldCollide } from './types.js';
1
+ import { shouldCollide } from "./types.js";
2
+ import { authoredCollider } from "./Collider.js";
3
+ import { Manifold } from "./collision/Manifold.js";
4
+ import { collide, testOverlap } from "./collision/narrowphase.js";
5
+ import { MutableContactModifierContext } from "./ContactModifier.js";
6
+ import { sortInPlace } from "./sort.js";
5
7
 
8
+ //#region src/ContactGraph.ts
6
9
  /**
7
- * The persistent contact set. Each detection pass it diffs the currently
8
- * touching collider pairs against the previous pass and produces immutable
9
- * begin/end (and sensor enter/exit) event snapshots. Duplicate begin/end churn
10
- * is suppressed by the persistent records, and the produced event arrays are
11
- * sorted by collider id for deterministic dispatch (gate SG-D1/SG-X4).
12
- *
13
- * Touching solid contacts are also collected into {@link solidContacts} (with a
14
- * warm-start impulse cache) for the dynamics solver. The graph holds no
15
- * module-level state each world owns one.
16
- */
17
- class ContactGraph {
18
- /** Immutable solid-contact begin snapshots produced by the latest {@link update}. */
19
- collisionStart = [];
20
- /** Immutable solid-contact end snapshots produced by the latest {@link update}. */
21
- collisionEnd = [];
22
- /** Immutable sensor-enter snapshots produced by the latest {@link update}. */
23
- sensorEnter = [];
24
- /** Immutable sensor-exit snapshots produced by the latest {@link update}. */
25
- sensorExit = [];
26
- /** Touching solid contacts this pass, in deterministic order — consumed by the solver. */
27
- solidContacts = [];
28
- // Integer pair-keys (`(a.id << 16) | b.id`, a.id < b.id guaranteed by the broad
29
- // phase) cheaper than string keys on the per-step solver hot path.
30
- _records = new Map();
31
- /** Touching pairs currently tracked (for debug draw). */
32
- get recordCount() {
33
- return this._records.size;
34
- }
35
- /** Diff this pass's candidate pairs against the persistent set, collecting events + solid contacts. */
36
- update(pairs) {
37
- this.collisionStart.length = 0;
38
- this.collisionEnd.length = 0;
39
- this.sensorEnter.length = 0;
40
- this.sensorExit.length = 0;
41
- this.solidContacts.length = 0;
42
- // eslint-disable-next-line unicorn/no-array-for-each -- forEach is the allocation-free path; for…of over a Map allocates an iterator every step (W7 10a).
43
- this._records.forEach(resetSeen);
44
- for (const pair of pairs) {
45
- const a = pair.a;
46
- const b = pair.b;
47
- if (!shouldCollide(a.filter, b.filter)) {
48
- continue;
49
- }
50
- const isSensor = a.isSensor || b.isSensor;
51
- const key = pairKey(a.id, b.id);
52
- const existing = this._records.get(key);
53
- const record = existing ?? createRecord(a, b, isSensor);
54
- const touching = isSensor ? testOverlap(a, b) : collide(a, b, record.manifold);
55
- if (touching) {
56
- record.seen = true;
57
- if (existing === undefined) {
58
- this._records.set(key, record);
59
- }
60
- if (!record.touching) {
61
- record.touching = true;
62
- this._emitBegin(record);
63
- }
64
- if (!isSensor) {
65
- warmStartMatch(record);
66
- this.solidContacts.push(record);
67
- }
68
- }
69
- else if (existing !== undefined) {
70
- if (record.touching) {
71
- this._emitEnd(record);
72
- }
73
- this._records.delete(key);
74
- }
75
- }
76
- // Pairs that left the broad phase entirely while touching → fire end. forEach
77
- // + thisArg is the allocation-free iteration (for…of allocates an entry tuple
78
- // per record each step); the thisArg binds `this`, so unbound-method is a
79
- // false positive here (W7 10a).
80
- // eslint-disable-next-line unicorn/no-array-for-each, @typescript-eslint/unbound-method
81
- this._records.forEach(this._removeIfUnseen, this);
82
- sortInPlace(this.collisionStart, byColliderPair);
83
- sortInPlace(this.collisionEnd, byColliderPair);
84
- sortInPlace(this.sensorEnter, bySensorPair);
85
- sortInPlace(this.sensorExit, bySensorPair);
86
- sortInPlace(this.solidContacts, byRecordPair);
87
- }
88
- /** Remove every record referencing `collider` (called when a collider is destroyed). */
89
- removeCollider(collider) {
90
- for (const [key, record] of this._records) {
91
- if (record.a === collider || record.b === collider) {
92
- this._records.delete(key);
93
- }
94
- }
95
- }
96
- /** Drop all records (world reset/destroy). */
97
- clear() {
98
- this._records.clear();
99
- }
100
- _emitBegin(record) {
101
- if (record.isSensor) {
102
- this.sensorEnter.push(makeSensorEvent(record.a, record.b));
103
- }
104
- else {
105
- this.collisionStart.push(makeCollisionEvent(record.a, record.b, record.manifold));
106
- }
107
- }
108
- _emitEnd(record) {
109
- if (record.isSensor) {
110
- this.sensorExit.push(makeSensorEvent(record.a, record.b));
111
- }
112
- else {
113
- this.collisionEnd.push(makeEndEvent(record.a, record.b));
114
- }
115
- }
116
- /**
117
- * `Map.forEach` callback (its `this` bound via the forEach thisArg) — drops a
118
- * record the latest pass did not see, firing an end event if it was touching.
119
- * A method reference + thisArg keeps the per-step iteration allocation-free,
120
- * unlike `for (const [key, record] of map)` which allocates an entry tuple per
121
- * record (~1000/step). Deleting the current entry during forEach is safe.
122
- */
123
- _removeIfUnseen(record, key) {
124
- if (!record.seen) {
125
- if (record.touching) {
126
- this._emitEnd(record);
127
- }
128
- this._records.delete(key);
129
- }
130
- }
131
- }
132
- /** `Map.forEach` callback — clears the per-pass `seen` flag (no iterator allocation). */
10
+ * The persistent contact set. Each detection pass it diffs the currently
11
+ * touching collider pairs against the previous pass and produces immutable
12
+ * begin/end (and sensor enter/exit) event snapshots. Duplicate begin/end churn
13
+ * is suppressed by the persistent records, and the produced event arrays are
14
+ * sorted by collider id for deterministic dispatch.
15
+ *
16
+ * Touching solid contacts are also collected into {@link solidContacts} (with a
17
+ * warm-start impulse cache) for the dynamics solver. The graph holds no
18
+ * module-level state - each world owns one.
19
+ *
20
+ * Records are keyed on the pair the solver sees, which for a chain collider is
21
+ * one of its edge proxies. Events are not: a body touching several edges of one
22
+ * chain is one authored contact, so the graph reference-counts the authored pair
23
+ * behind its records and emits begin/end only on the first and last of them.
24
+ */
25
+ var ContactGraph = class {
26
+ /** Immutable solid-contact begin snapshots produced by the latest {@link update}. */
27
+ collisionStart = [];
28
+ /** Immutable solid-contact end snapshots produced by the latest {@link update}. */
29
+ collisionEnd = [];
30
+ /** Immutable sensor-enter snapshots produced by the latest {@link update}. */
31
+ sensorEnter = [];
32
+ /** Immutable sensor-exit snapshots produced by the latest {@link update}. */
33
+ sensorExit = [];
34
+ /** Touching solid contacts this pass, in deterministic order - consumed by the solver. */
35
+ solidContacts = [];
36
+ _records = /* @__PURE__ */ new Map();
37
+ _authoredPairs = /* @__PURE__ */ new Map();
38
+ _modifierContext = new MutableContactModifierContext();
39
+ /** Touching pairs currently tracked (for debug draw). */
40
+ get recordCount() {
41
+ return this._records.size;
42
+ }
43
+ /** Diff this pass's candidate pairs against the persistent set, collecting events + solid contacts. */
44
+ update(pairs) {
45
+ this.collisionStart.length = 0;
46
+ this.collisionEnd.length = 0;
47
+ this.sensorEnter.length = 0;
48
+ this.sensorExit.length = 0;
49
+ this.solidContacts.length = 0;
50
+ this._records.forEach(resetSeen);
51
+ for (const pair of pairs) {
52
+ const a = pair.a;
53
+ const b = pair.b;
54
+ const ownerA = authoredCollider(a);
55
+ const ownerB = authoredCollider(b);
56
+ if (!shouldCollide(ownerA.filter, ownerB.filter)) continue;
57
+ const isSensor = ownerA.isSensor || ownerB.isSensor;
58
+ const key = pairKey(a.id, b.id);
59
+ const existing = this._records.get(key);
60
+ const record = existing ?? createRecord(a, b, ownerA, ownerB, isSensor);
61
+ if (isSensor ? testOverlap(a, b) : collide(a, b, record.manifold)) {
62
+ record.seen = true;
63
+ if (existing === void 0) this._records.set(key, record);
64
+ if (!record.touching) {
65
+ record.touching = true;
66
+ this._emitBegin(record);
67
+ }
68
+ if (!isSensor) {
69
+ record.enabled = true;
70
+ record.friction = Math.sqrt(ownerA.friction * ownerB.friction);
71
+ record.restitution = Math.max(ownerA.restitution, ownerB.restitution);
72
+ warmStartMatch(record);
73
+ this.solidContacts.push(record);
74
+ }
75
+ } else if (existing !== void 0) {
76
+ if (record.touching) this._emitEnd(record);
77
+ this._records.delete(key);
78
+ }
79
+ }
80
+ this._records.forEach(this._removeIfUnseen, this);
81
+ sortInPlace(this.collisionStart, byColliderPair);
82
+ sortInPlace(this.collisionEnd, byColliderPair);
83
+ sortInPlace(this.sensorEnter, bySensorPair);
84
+ sortInPlace(this.sensorExit, bySensorPair);
85
+ sortInPlace(this.solidContacts, byRecordPair);
86
+ }
87
+ /**
88
+ * Run `modifier` over every touching solid contact of this pass. Called after
89
+ * detection and before island building, so a contact the modifier disables
90
+ * neither reaches the solver nor couples its two bodies into one sleeping
91
+ * island. A disabled contact's warm-start cache is dropped, so re-enabling it
92
+ * later starts from zero instead of releasing a stale impulse.
93
+ */
94
+ applyModifier(modifier) {
95
+ const context = this._modifierContext;
96
+ for (const record of this.solidContacts) {
97
+ context.bind(record);
98
+ modifier(context);
99
+ context.flush(record);
100
+ if (!record.enabled) {
101
+ record.normalImpulse[0] = 0;
102
+ record.normalImpulse[1] = 0;
103
+ record.tangentImpulse[0] = 0;
104
+ record.tangentImpulse[1] = 0;
105
+ record.pointIds[0] = 0;
106
+ record.pointIds[1] = 0;
107
+ }
108
+ }
109
+ }
110
+ /**
111
+ * Remove every record referencing `collider` (called when a collider is
112
+ * destroyed). Passing an authored chain collider also drops the records of
113
+ * every edge proxy it owns.
114
+ */
115
+ removeCollider(collider) {
116
+ for (const [key, record] of this._records) if (record.a === collider || record.b === collider || record.ownerA === collider || record.ownerB === collider) {
117
+ if (record.touching) this._releaseAuthoredPair(record);
118
+ this._records.delete(key);
119
+ }
120
+ }
121
+ /** Drop all records (world reset/destroy). */
122
+ clear() {
123
+ this._records.clear();
124
+ this._authoredPairs.clear();
125
+ }
126
+ _emitBegin(record) {
127
+ if (!this._retainAuthoredPair(record)) return;
128
+ if (record.isSensor) this.sensorEnter.push(makeSensorEvent(record.ownerA, record.ownerB));
129
+ else this.collisionStart.push(makeCollisionEvent(record.ownerA, record.ownerB, record.manifold));
130
+ }
131
+ _emitEnd(record) {
132
+ if (!this._releaseAuthoredPair(record)) return;
133
+ if (record.isSensor) this.sensorExit.push(makeSensorEvent(record.ownerA, record.ownerB));
134
+ else this.collisionEnd.push(makeEndEvent(record.ownerA, record.ownerB));
135
+ }
136
+ /**
137
+ * Count one more solver contact against the authored pair, reporting whether
138
+ * it is the first - the only one that may emit a begin event. A record whose
139
+ * colliders are the authored ones needs no counting at all, so a world without
140
+ * chains never touches the map.
141
+ */
142
+ _retainAuthoredPair(record) {
143
+ if (record.a === record.ownerA && record.b === record.ownerB) return true;
144
+ const key = authoredPairKey(record);
145
+ const count = this._authoredPairs.get(key) ?? 0;
146
+ this._authoredPairs.set(key, count + 1);
147
+ return count === 0;
148
+ }
149
+ /** Counterpart of {@link _retainAuthoredPair}: `true` on the last contact of the pair. */
150
+ _releaseAuthoredPair(record) {
151
+ if (record.a === record.ownerA && record.b === record.ownerB) return true;
152
+ const key = authoredPairKey(record);
153
+ const count = (this._authoredPairs.get(key) ?? 1) - 1;
154
+ if (count <= 0) {
155
+ this._authoredPairs.delete(key);
156
+ return true;
157
+ }
158
+ this._authoredPairs.set(key, count);
159
+ return false;
160
+ }
161
+ /**
162
+ * `Map.forEach` callback (its `this` bound via the forEach thisArg) - drops a
163
+ * record the latest pass did not see, firing an end event if it was touching.
164
+ * A method reference + thisArg keeps the per-step iteration allocation-free,
165
+ * unlike `for (const [key, record] of map)` which allocates an entry tuple per
166
+ * record (~1000/step). Deleting the current entry during forEach is safe.
167
+ */
168
+ _removeIfUnseen(record, key) {
169
+ if (!record.seen) {
170
+ if (record.touching) this._emitEnd(record);
171
+ this._records.delete(key);
172
+ }
173
+ }
174
+ };
175
+ /** `Map.forEach` callback - clears the per-pass `seen` flag (no iterator allocation). */
133
176
  const resetSeen = (record) => {
134
- record.seen = false;
177
+ record.seen = false;
135
178
  };
136
179
  /**
137
- * Stride for packing two collider ids into one pair key. Multiplying by this
138
- * (rather than a 32-bit `<<`) keeps the key collision-free up to ~67M (2^26)
139
- * ids per world, within JS's 2^53 safe-integer range.
140
- * @internal
141
- */
142
- const pairKeyStride = 0x4000000; // 2^26
180
+ * Stride for packing two collider ids into one pair key. Multiplying by this
181
+ * (rather than a 32-bit `<<`) keeps the key collision-free up to ~67M (2^26)
182
+ * ids per world, within JS's 2^53 safe-integer range.
183
+ * @internal
184
+ */
185
+ const pairKeyStride = 67108864;
143
186
  /**
144
- * Integer key for an unordered collider pair (`aId < bId` is guaranteed by the
145
- * broad phase). The previous `(aId << 16) | bId` silently collided once any id
146
- * reached 65536, because JS bitwise operators wrap at 32 bits.
147
- * @internal
148
- */
187
+ * Integer key for an unordered collider pair (`aId < bId` is guaranteed by the
188
+ * broad phase). The previous `(aId << 16) | bId` silently collided once any id
189
+ * reached 65536, because JS bitwise operators wrap at 32 bits.
190
+ * @internal
191
+ */
149
192
  const pairKey = (aId, bId) => aId * pairKeyStride + bId;
150
- const createRecord = (a, b, isSensor) => ({
151
- a,
152
- b,
153
- isSensor,
154
- touching: false,
155
- seen: true,
156
- manifold: new Manifold(),
157
- normalImpulse: [0, 0],
158
- tangentImpulse: [0, 0],
159
- pointIds: [0, 0],
193
+ const authoredPairKey = (record) => {
194
+ const first = record.ownerA.id;
195
+ const second = record.ownerB.id;
196
+ return first < second ? pairKey(first, second) : pairKey(second, first);
197
+ };
198
+ const createRecord = (a, b, ownerA, ownerB, isSensor) => ({
199
+ a,
200
+ b,
201
+ ownerA,
202
+ ownerB,
203
+ isSensor,
204
+ touching: false,
205
+ seen: true,
206
+ enabled: true,
207
+ friction: 0,
208
+ restitution: 0,
209
+ manifold: new Manifold(),
210
+ normalImpulse: [0, 0],
211
+ tangentImpulse: [0, 0],
212
+ pointIds: [0, 0]
160
213
  });
161
214
  /**
162
- * Map the previously accumulated impulses onto the new manifold points by feature
163
- * id (warm-starting). Unmatched points start at zero; the cache is re-keyed to the
164
- * new ids. Runs after `collide` has refreshed `record.manifold`.
165
- */
215
+ * Map the previously accumulated impulses onto the new manifold points by feature
216
+ * id (warm-starting). Unmatched points start at zero; the cache is re-keyed to the
217
+ * new ids. Runs after `collide` has refreshed `record.manifold`.
218
+ */
166
219
  const warmStartMatch = (record) => {
167
- const manifold = record.manifold;
168
- const pn0 = record.normalImpulse[0];
169
- const pn1 = record.normalImpulse[1];
170
- const pt0 = record.tangentImpulse[0];
171
- const pt1 = record.tangentImpulse[1];
172
- const pid0 = record.pointIds[0];
173
- const pid1 = record.pointIds[1];
174
- for (let i = 0; i < 2; i++) {
175
- if (i < manifold.pointCount) {
176
- // i {0,1} and within pointCount, so the manifold point exists.
177
- const id = (i === 0 ? manifold.points[0] : manifold.points[1]).id;
178
- let normal = 0;
179
- let tangent = 0;
180
- if (id === pid0) {
181
- normal = pn0;
182
- tangent = pt0;
183
- }
184
- else if (id === pid1) {
185
- normal = pn1;
186
- tangent = pt1;
187
- }
188
- record.normalImpulse[i] = normal;
189
- record.tangentImpulse[i] = tangent;
190
- record.pointIds[i] = id;
191
- }
192
- else {
193
- record.normalImpulse[i] = 0;
194
- record.tangentImpulse[i] = 0;
195
- record.pointIds[i] = 0;
196
- }
197
- }
220
+ const manifold = record.manifold;
221
+ const pn0 = record.normalImpulse[0];
222
+ const pn1 = record.normalImpulse[1];
223
+ const pt0 = record.tangentImpulse[0];
224
+ const pt1 = record.tangentImpulse[1];
225
+ const pid0 = record.pointIds[0];
226
+ const pid1 = record.pointIds[1];
227
+ for (let i = 0; i < 2; i++) if (i < manifold.pointCount) {
228
+ const id = (i === 0 ? manifold.points[0] : manifold.points[1]).id;
229
+ let normal = 0;
230
+ let tangent = 0;
231
+ if (id === pid0) {
232
+ normal = pn0;
233
+ tangent = pt0;
234
+ } else if (id === pid1) {
235
+ normal = pn1;
236
+ tangent = pt1;
237
+ }
238
+ record.normalImpulse[i] = normal;
239
+ record.tangentImpulse[i] = tangent;
240
+ record.pointIds[i] = id;
241
+ } else {
242
+ record.normalImpulse[i] = 0;
243
+ record.tangentImpulse[i] = 0;
244
+ record.pointIds[i] = 0;
245
+ }
198
246
  };
199
247
  const makeCollisionEvent = (a, b, manifold) => {
200
- const points = [];
201
- for (let i = 0; i < manifold.pointCount; i++) {
202
- // i in 0..pointCount-1 and pointCount 2, so the manifold point exists.
203
- const p = i === 0 ? manifold.points[0] : manifold.points[1];
204
- points.push(Object.freeze({ x: p.x, y: p.y, penetration: p.penetration }));
205
- }
206
- return Object.freeze({
207
- bodyA: a.body,
208
- bodyB: b.body,
209
- colliderA: a,
210
- colliderB: b,
211
- normal: Object.freeze({ x: manifold.normalX, y: manifold.normalY }),
212
- points: Object.freeze(points),
213
- });
248
+ const points = [];
249
+ for (let i = 0; i < manifold.pointCount; i++) {
250
+ const p = i === 0 ? manifold.points[0] : manifold.points[1];
251
+ points.push(Object.freeze({
252
+ x: p.x,
253
+ y: p.y,
254
+ penetration: p.penetration
255
+ }));
256
+ }
257
+ return Object.freeze({
258
+ bodyA: a.body,
259
+ bodyB: b.body,
260
+ colliderA: a,
261
+ colliderB: b,
262
+ normal: Object.freeze({
263
+ x: manifold.normalX,
264
+ y: manifold.normalY
265
+ }),
266
+ points: Object.freeze(points)
267
+ });
214
268
  };
215
269
  const makeEndEvent = (a, b) => Object.freeze({
216
- bodyA: a.body,
217
- bodyB: b.body,
218
- colliderA: a,
219
- colliderB: b,
220
- normal: Object.freeze({ x: 0, y: 0 }),
221
- points: Object.freeze([]),
270
+ bodyA: a.body,
271
+ bodyB: b.body,
272
+ colliderA: a,
273
+ colliderB: b,
274
+ normal: Object.freeze({
275
+ x: 0,
276
+ y: 0
277
+ }),
278
+ points: Object.freeze([])
222
279
  });
223
280
  const makeSensorEvent = (a, b) => {
224
- let sensor;
225
- let other;
226
- if (a.isSensor && b.isSensor) {
227
- sensor = a.id < b.id ? a : b;
228
- other = sensor === a ? b : a;
229
- }
230
- else if (a.isSensor) {
231
- sensor = a;
232
- other = b;
233
- }
234
- else {
235
- sensor = b;
236
- other = a;
237
- }
238
- return Object.freeze({ sensor, other });
281
+ let sensor;
282
+ let other;
283
+ if (a.isSensor && b.isSensor) {
284
+ sensor = a.id < b.id ? a : b;
285
+ other = sensor === a ? b : a;
286
+ } else if (a.isSensor) {
287
+ sensor = a;
288
+ other = b;
289
+ } else {
290
+ sensor = b;
291
+ other = a;
292
+ }
293
+ return Object.freeze({
294
+ sensor,
295
+ other
296
+ });
239
297
  };
240
298
  const byColliderPair = (x, y) => x.colliderA.id - y.colliderA.id || x.colliderB.id - y.colliderB.id;
241
299
  const bySensorPair = (x, y) => x.sensor.id - y.sensor.id || x.other.id - y.other.id;
242
300
  const byRecordPair = (x, y) => x.a.id - y.a.id || x.b.id - y.b.id;
243
301
 
302
+ //#endregion
244
303
  export { ContactGraph, pairKey, pairKeyStride };
245
- //# sourceMappingURL=ContactGraph.js.map
304
+ //# sourceMappingURL=ContactGraph.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ContactGraph.js","sources":["../../../src/ContactGraph.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ContactGraph.js","names":[],"sources":["../../src/ContactGraph.ts"],"sourcesContent":["import type { CandidatePair } from './broadphase/BroadPhase';\nimport type { Collider } from './Collider';\nimport { authoredCollider } from './Collider';\nimport { Manifold } from './collision/Manifold';\nimport { collide, testOverlap } from './collision/narrowphase';\nimport type { ContactModifier } from './ContactModifier';\nimport { MutableContactModifierContext } from './ContactModifier';\nimport type { CollisionEvent, ContactPoint, SensorEvent } from './events';\nimport { sortInPlace } from './sort';\nimport { shouldCollide } from './types';\n\n/**\n * Persistent per-pair contact state. For solid contacts it carries a manifold\n * reused across passes plus the accumulated normal/tangent impulses (warm-start\n * cache), keyed by manifold-point feature ids. Consumed by the {@link ContactSolver}.\n */\nexport interface ContactRecord {\n readonly a: Collider;\n readonly b: Collider;\n /**\n * Authored collider behind {@link a} - itself, unless `a` is an engine-owned\n * chain edge proxy, in which case it is the chain collider the caller made.\n * Solver identity is the proxy pair; public identity is the authored pair.\n */\n readonly ownerA: Collider;\n readonly ownerB: Collider;\n readonly isSensor: boolean;\n touching: boolean;\n seen: boolean;\n /**\n * Per-step solver participation. Re-derived to `true` for every touching solid\n * contact each pass, then optionally cleared by the world's `ContactModifier`.\n */\n enabled: boolean;\n /** Per-step Coulomb friction, re-derived from the two colliders each pass. */\n friction: number;\n /** Per-step restitution, re-derived from the two colliders each pass. */\n restitution: number;\n /** Persistent manifold (solid contacts), refreshed each pass by the narrow phase. */\n readonly manifold: Manifold;\n /** Accumulated normal impulse per contact point, carried across steps (warm-start). */\n readonly normalImpulse: [number, number];\n /** Accumulated tangent impulse per contact point, carried across steps (warm-start). */\n readonly tangentImpulse: [number, number];\n /** Feature ids the cached impulses belong to (for warm-start matching). */\n readonly pointIds: [number, number];\n}\n\n/**\n * The persistent contact set. Each detection pass it diffs the currently\n * touching collider pairs against the previous pass and produces immutable\n * begin/end (and sensor enter/exit) event snapshots. Duplicate begin/end churn\n * is suppressed by the persistent records, and the produced event arrays are\n * sorted by collider id for deterministic dispatch.\n *\n * Touching solid contacts are also collected into {@link solidContacts} (with a\n * warm-start impulse cache) for the dynamics solver. The graph holds no\n * module-level state - each world owns one.\n *\n * Records are keyed on the pair the solver sees, which for a chain collider is\n * one of its edge proxies. Events are not: a body touching several edges of one\n * chain is one authored contact, so the graph reference-counts the authored pair\n * behind its records and emits begin/end only on the first and last of them.\n */\nexport class ContactGraph {\n /** Immutable solid-contact begin snapshots produced by the latest {@link update}. */\n public readonly collisionStart: CollisionEvent[] = [];\n /** Immutable solid-contact end snapshots produced by the latest {@link update}. */\n public readonly collisionEnd: CollisionEvent[] = [];\n /** Immutable sensor-enter snapshots produced by the latest {@link update}. */\n public readonly sensorEnter: SensorEvent[] = [];\n /** Immutable sensor-exit snapshots produced by the latest {@link update}. */\n public readonly sensorExit: SensorEvent[] = [];\n /** Touching solid contacts this pass, in deterministic order - consumed by the solver. */\n public readonly solidContacts: ContactRecord[] = [];\n\n // Integer pair-keys (`(a.id << 16) | b.id`, a.id < b.id guaranteed by the broad\n // phase) - cheaper than string keys on the per-step solver hot path.\n private readonly _records = new Map<number, ContactRecord>();\n // Live solver contacts per authored collider pair. Only pairs whose records\n // are engine-owned proxies appear here; everything else emits directly.\n private readonly _authoredPairs = new Map<number, number>();\n private readonly _modifierContext = new MutableContactModifierContext();\n\n /** Touching pairs currently tracked (for debug draw). */\n public get recordCount(): number {\n return this._records.size;\n }\n\n /** Diff this pass's candidate pairs against the persistent set, collecting events + solid contacts. */\n public update(pairs: readonly CandidatePair[]): void {\n this.collisionStart.length = 0;\n this.collisionEnd.length = 0;\n this.sensorEnter.length = 0;\n this.sensorExit.length = 0;\n this.solidContacts.length = 0;\n\n this._records.forEach(resetSeen);\n\n for (const pair of pairs) {\n const a = pair.a;\n const b = pair.b;\n\n // Material, filter and sensor flag live on the authored collider; an edge\n // proxy carries none of its own, so a chain stays one collider to configure.\n const ownerA = authoredCollider(a);\n const ownerB = authoredCollider(b);\n\n if (!shouldCollide(ownerA.filter, ownerB.filter)) {\n continue;\n }\n\n const isSensor = ownerA.isSensor || ownerB.isSensor;\n const key = pairKey(a.id, b.id);\n const existing = this._records.get(key);\n const record = existing ?? createRecord(a, b, ownerA, ownerB, isSensor);\n const touching = isSensor ? testOverlap(a, b) : collide(a, b, record.manifold);\n\n if (touching) {\n record.seen = true;\n\n if (existing === undefined) {\n this._records.set(key, record);\n }\n\n if (!record.touching) {\n record.touching = true;\n this._emitBegin(record);\n }\n\n if (!isSensor) {\n // Per-step controls start from the collider-derived defaults every\n // pass, so a modifier that skipped a contact last step does not leak\n // into this one.\n record.enabled = true;\n record.friction = Math.sqrt(ownerA.friction * ownerB.friction);\n record.restitution = Math.max(ownerA.restitution, ownerB.restitution);\n warmStartMatch(record);\n this.solidContacts.push(record);\n }\n } else if (existing !== undefined) {\n if (record.touching) {\n this._emitEnd(record);\n }\n\n this._records.delete(key);\n }\n }\n\n // Pairs that left the broad phase entirely while touching → fire end. forEach\n // + thisArg is the allocation-free iteration (for...of allocates an entry tuple\n // per record each step); the thisArg binds `this`, so unbound-method is a\n // false positive here.\n // eslint-disable-next-line @typescript-eslint/unbound-method -- the thisArg above binds it\n this._records.forEach(this._removeIfUnseen, this);\n\n sortInPlace(this.collisionStart, byColliderPair);\n sortInPlace(this.collisionEnd, byColliderPair);\n sortInPlace(this.sensorEnter, bySensorPair);\n sortInPlace(this.sensorExit, bySensorPair);\n sortInPlace(this.solidContacts, byRecordPair);\n }\n\n /**\n * Run `modifier` over every touching solid contact of this pass. Called after\n * detection and before island building, so a contact the modifier disables\n * neither reaches the solver nor couples its two bodies into one sleeping\n * island. A disabled contact's warm-start cache is dropped, so re-enabling it\n * later starts from zero instead of releasing a stale impulse.\n */\n public applyModifier(modifier: ContactModifier): void {\n const context = this._modifierContext;\n\n for (const record of this.solidContacts) {\n context.bind(record);\n modifier(context);\n context.flush(record);\n\n if (!record.enabled) {\n record.normalImpulse[0] = 0;\n record.normalImpulse[1] = 0;\n record.tangentImpulse[0] = 0;\n record.tangentImpulse[1] = 0;\n record.pointIds[0] = 0;\n record.pointIds[1] = 0;\n }\n }\n }\n\n /**\n * Remove every record referencing `collider` (called when a collider is\n * destroyed). Passing an authored chain collider also drops the records of\n * every edge proxy it owns.\n */\n public removeCollider(collider: Collider): void {\n for (const [key, record] of this._records) {\n if (record.a === collider || record.b === collider || record.ownerA === collider || record.ownerB === collider) {\n if (record.touching) {\n this._releaseAuthoredPair(record);\n }\n\n this._records.delete(key);\n }\n }\n }\n\n /** Drop all records (world reset/destroy). */\n public clear(): void {\n this._records.clear();\n this._authoredPairs.clear();\n }\n\n private _emitBegin(record: ContactRecord): void {\n if (!this._retainAuthoredPair(record)) {\n return;\n }\n\n if (record.isSensor) {\n this.sensorEnter.push(makeSensorEvent(record.ownerA, record.ownerB));\n } else {\n this.collisionStart.push(makeCollisionEvent(record.ownerA, record.ownerB, record.manifold));\n }\n }\n\n private _emitEnd(record: ContactRecord): void {\n if (!this._releaseAuthoredPair(record)) {\n return;\n }\n\n if (record.isSensor) {\n this.sensorExit.push(makeSensorEvent(record.ownerA, record.ownerB));\n } else {\n this.collisionEnd.push(makeEndEvent(record.ownerA, record.ownerB));\n }\n }\n\n /**\n * Count one more solver contact against the authored pair, reporting whether\n * it is the first - the only one that may emit a begin event. A record whose\n * colliders are the authored ones needs no counting at all, so a world without\n * chains never touches the map.\n */\n private _retainAuthoredPair(record: ContactRecord): boolean {\n if (record.a === record.ownerA && record.b === record.ownerB) {\n return true;\n }\n\n const key = authoredPairKey(record);\n const count = this._authoredPairs.get(key) ?? 0;\n\n this._authoredPairs.set(key, count + 1);\n\n return count === 0;\n }\n\n /** Counterpart of {@link _retainAuthoredPair}: `true` on the last contact of the pair. */\n private _releaseAuthoredPair(record: ContactRecord): boolean {\n if (record.a === record.ownerA && record.b === record.ownerB) {\n return true;\n }\n\n const key = authoredPairKey(record);\n const count = (this._authoredPairs.get(key) ?? 1) - 1;\n\n if (count <= 0) {\n this._authoredPairs.delete(key);\n\n return true;\n }\n\n this._authoredPairs.set(key, count);\n\n return false;\n }\n\n /**\n * `Map.forEach` callback (its `this` bound via the forEach thisArg) - drops a\n * record the latest pass did not see, firing an end event if it was touching.\n * A method reference + thisArg keeps the per-step iteration allocation-free,\n * unlike `for (const [key, record] of map)` which allocates an entry tuple per\n * record (~1000/step). Deleting the current entry during forEach is safe.\n */\n private _removeIfUnseen(record: ContactRecord, key: number): void {\n if (!record.seen) {\n if (record.touching) {\n this._emitEnd(record);\n }\n\n this._records.delete(key);\n }\n }\n}\n\n/** `Map.forEach` callback - clears the per-pass `seen` flag (no iterator allocation). */\nconst resetSeen = (record: ContactRecord): void => {\n record.seen = false;\n};\n\n/**\n * Stride for packing two collider ids into one pair key. Multiplying by this\n * (rather than a 32-bit `<<`) keeps the key collision-free up to ~67M (2^26)\n * ids per world, within JS's 2^53 safe-integer range.\n * @internal\n */\nexport const pairKeyStride = 0x4000000; // 2^26\n\n/**\n * Integer key for an unordered collider pair (`aId < bId` is guaranteed by the\n * broad phase). The previous `(aId << 16) | bId` silently collided once any id\n * reached 65536, because JS bitwise operators wrap at 32 bits.\n * @internal\n */\nexport const pairKey = (aId: number, bId: number): number => aId * pairKeyStride + bId;\n\nconst authoredPairKey = (record: ContactRecord): number => {\n const first = record.ownerA.id;\n const second = record.ownerB.id;\n\n return first < second ? pairKey(first, second) : pairKey(second, first);\n};\n\nconst createRecord = (a: Collider, b: Collider, ownerA: Collider, ownerB: Collider, isSensor: boolean): ContactRecord => ({\n a,\n b,\n ownerA,\n ownerB,\n isSensor,\n touching: false,\n seen: true,\n enabled: true,\n friction: 0,\n restitution: 0,\n manifold: new Manifold(),\n normalImpulse: [0, 0],\n tangentImpulse: [0, 0],\n pointIds: [0, 0],\n});\n\n/**\n * Map the previously accumulated impulses onto the new manifold points by feature\n * id (warm-starting). Unmatched points start at zero; the cache is re-keyed to the\n * new ids. Runs after `collide` has refreshed `record.manifold`.\n */\nconst warmStartMatch = (record: ContactRecord): void => {\n const manifold = record.manifold;\n const pn0 = record.normalImpulse[0];\n const pn1 = record.normalImpulse[1];\n const pt0 = record.tangentImpulse[0];\n const pt1 = record.tangentImpulse[1];\n const pid0 = record.pointIds[0];\n const pid1 = record.pointIds[1];\n\n for (let i = 0; i < 2; i++) {\n if (i < manifold.pointCount) {\n // i ∈ {0,1} and within pointCount, so the manifold point exists.\n const id = (i === 0 ? manifold.points[0] : manifold.points[1]).id;\n let normal = 0;\n let tangent = 0;\n\n if (id === pid0) {\n normal = pn0;\n tangent = pt0;\n } else if (id === pid1) {\n normal = pn1;\n tangent = pt1;\n }\n\n record.normalImpulse[i] = normal;\n record.tangentImpulse[i] = tangent;\n record.pointIds[i] = id;\n } else {\n record.normalImpulse[i] = 0;\n record.tangentImpulse[i] = 0;\n record.pointIds[i] = 0;\n }\n }\n};\n\nconst makeCollisionEvent = (a: Collider, b: Collider, manifold: Manifold): CollisionEvent => {\n const points: ContactPoint[] = [];\n\n for (let i = 0; i < manifold.pointCount; i++) {\n // i in 0..pointCount-1 and pointCount ≤ 2, so the manifold point exists.\n const p = i === 0 ? manifold.points[0] : manifold.points[1];\n\n points.push(Object.freeze({ x: p.x, y: p.y, penetration: p.penetration }));\n }\n\n return Object.freeze({\n bodyA: a.body,\n bodyB: b.body,\n colliderA: a,\n colliderB: b,\n normal: Object.freeze({ x: manifold.normalX, y: manifold.normalY }),\n points: Object.freeze(points),\n });\n};\n\nconst makeEndEvent = (a: Collider, b: Collider): CollisionEvent =>\n Object.freeze({\n bodyA: a.body,\n bodyB: b.body,\n colliderA: a,\n colliderB: b,\n normal: Object.freeze({ x: 0, y: 0 }),\n points: Object.freeze([] as ContactPoint[]),\n });\n\nconst makeSensorEvent = (a: Collider, b: Collider): SensorEvent => {\n let sensor: Collider;\n let other: Collider;\n\n if (a.isSensor && b.isSensor) {\n sensor = a.id < b.id ? a : b;\n other = sensor === a ? b : a;\n } else if (a.isSensor) {\n sensor = a;\n other = b;\n } else {\n sensor = b;\n other = a;\n }\n\n return Object.freeze({ sensor, other });\n};\n\nconst byColliderPair = (x: CollisionEvent, y: CollisionEvent): number => x.colliderA.id - y.colliderA.id || x.colliderB.id - y.colliderB.id;\n\nconst bySensorPair = (x: SensorEvent, y: SensorEvent): number => x.sensor.id - y.sensor.id || x.other.id - y.other.id;\n\nconst byRecordPair = (x: ContactRecord, y: ContactRecord): number => x.a.id - y.a.id || x.b.id - y.b.id;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgEA,IAAa,eAAb,MAA0B;;CAExB,AAAgB,iBAAmC,CAAC;;CAEpD,AAAgB,eAAiC,CAAC;;CAElD,AAAgB,cAA6B,CAAC;;CAE9C,AAAgB,aAA4B,CAAC;;CAE7C,AAAgB,gBAAiC,CAAC;CAIlD,AAAiB,2BAAW,IAAI,IAA2B;CAG3D,AAAiB,iCAAiB,IAAI,IAAoB;CAC1D,AAAiB,mBAAmB,IAAI,8BAA8B;;CAGtE,IAAW,cAAsB;EAC/B,OAAO,KAAK,SAAS;CACvB;;CAGA,AAAO,OAAO,OAAuC;EACnD,KAAK,eAAe,SAAS;EAC7B,KAAK,aAAa,SAAS;EAC3B,KAAK,YAAY,SAAS;EAC1B,KAAK,WAAW,SAAS;EACzB,KAAK,cAAc,SAAS;EAE5B,KAAK,SAAS,QAAQ,SAAS;EAE/B,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,IAAI,KAAK;GACf,MAAM,IAAI,KAAK;GAIf,MAAM,SAAS,iBAAiB,CAAC;GACjC,MAAM,SAAS,iBAAiB,CAAC;GAEjC,IAAI,CAAC,cAAc,OAAO,QAAQ,OAAO,MAAM,GAC7C;GAGF,MAAM,WAAW,OAAO,YAAY,OAAO;GAC3C,MAAM,MAAM,QAAQ,EAAE,IAAI,EAAE,EAAE;GAC9B,MAAM,WAAW,KAAK,SAAS,IAAI,GAAG;GACtC,MAAM,SAAS,YAAY,aAAa,GAAG,GAAG,QAAQ,QAAQ,QAAQ;GAGtE,IAFiB,WAAW,YAAY,GAAG,CAAC,IAAI,QAAQ,GAAG,GAAG,OAAO,QAAQ,GAE/D;IACZ,OAAO,OAAO;IAEd,IAAI,aAAa,QACf,KAAK,SAAS,IAAI,KAAK,MAAM;IAG/B,IAAI,CAAC,OAAO,UAAU;KACpB,OAAO,WAAW;KAClB,KAAK,WAAW,MAAM;IACxB;IAEA,IAAI,CAAC,UAAU;KAIb,OAAO,UAAU;KACjB,OAAO,WAAW,KAAK,KAAK,OAAO,WAAW,OAAO,QAAQ;KAC7D,OAAO,cAAc,KAAK,IAAI,OAAO,aAAa,OAAO,WAAW;KACpE,eAAe,MAAM;KACrB,KAAK,cAAc,KAAK,MAAM;IAChC;GACF,OAAO,IAAI,aAAa,QAAW;IACjC,IAAI,OAAO,UACT,KAAK,SAAS,MAAM;IAGtB,KAAK,SAAS,OAAO,GAAG;GAC1B;EACF;EAOA,KAAK,SAAS,QAAQ,KAAK,iBAAiB,IAAI;EAEhD,YAAY,KAAK,gBAAgB,cAAc;EAC/C,YAAY,KAAK,cAAc,cAAc;EAC7C,YAAY,KAAK,aAAa,YAAY;EAC1C,YAAY,KAAK,YAAY,YAAY;EACzC,YAAY,KAAK,eAAe,YAAY;CAC9C;;;;;;;;CASA,AAAO,cAAc,UAAiC;EACpD,MAAM,UAAU,KAAK;EAErB,KAAK,MAAM,UAAU,KAAK,eAAe;GACvC,QAAQ,KAAK,MAAM;GACnB,SAAS,OAAO;GAChB,QAAQ,MAAM,MAAM;GAEpB,IAAI,CAAC,OAAO,SAAS;IACnB,OAAO,cAAc,KAAK;IAC1B,OAAO,cAAc,KAAK;IAC1B,OAAO,eAAe,KAAK;IAC3B,OAAO,eAAe,KAAK;IAC3B,OAAO,SAAS,KAAK;IACrB,OAAO,SAAS,KAAK;GACvB;EACF;CACF;;;;;;CAOA,AAAO,eAAe,UAA0B;EAC9C,KAAK,MAAM,CAAC,KAAK,WAAW,KAAK,UAC/B,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,OAAO,WAAW,YAAY,OAAO,WAAW,UAAU;GAC9G,IAAI,OAAO,UACT,KAAK,qBAAqB,MAAM;GAGlC,KAAK,SAAS,OAAO,GAAG;EAC1B;CAEJ;;CAGA,AAAO,QAAc;EACnB,KAAK,SAAS,MAAM;EACpB,KAAK,eAAe,MAAM;CAC5B;CAEA,AAAQ,WAAW,QAA6B;EAC9C,IAAI,CAAC,KAAK,oBAAoB,MAAM,GAClC;EAGF,IAAI,OAAO,UACT,KAAK,YAAY,KAAK,gBAAgB,OAAO,QAAQ,OAAO,MAAM,CAAC;OAEnE,KAAK,eAAe,KAAK,mBAAmB,OAAO,QAAQ,OAAO,QAAQ,OAAO,QAAQ,CAAC;CAE9F;CAEA,AAAQ,SAAS,QAA6B;EAC5C,IAAI,CAAC,KAAK,qBAAqB,MAAM,GACnC;EAGF,IAAI,OAAO,UACT,KAAK,WAAW,KAAK,gBAAgB,OAAO,QAAQ,OAAO,MAAM,CAAC;OAElE,KAAK,aAAa,KAAK,aAAa,OAAO,QAAQ,OAAO,MAAM,CAAC;CAErE;;;;;;;CAQA,AAAQ,oBAAoB,QAAgC;EAC1D,IAAI,OAAO,MAAM,OAAO,UAAU,OAAO,MAAM,OAAO,QACpD,OAAO;EAGT,MAAM,MAAM,gBAAgB,MAAM;EAClC,MAAM,QAAQ,KAAK,eAAe,IAAI,GAAG,KAAK;EAE9C,KAAK,eAAe,IAAI,KAAK,QAAQ,CAAC;EAEtC,OAAO,UAAU;CACnB;;CAGA,AAAQ,qBAAqB,QAAgC;EAC3D,IAAI,OAAO,MAAM,OAAO,UAAU,OAAO,MAAM,OAAO,QACpD,OAAO;EAGT,MAAM,MAAM,gBAAgB,MAAM;EAClC,MAAM,SAAS,KAAK,eAAe,IAAI,GAAG,KAAK,KAAK;EAEpD,IAAI,SAAS,GAAG;GACd,KAAK,eAAe,OAAO,GAAG;GAE9B,OAAO;EACT;EAEA,KAAK,eAAe,IAAI,KAAK,KAAK;EAElC,OAAO;CACT;;;;;;;;CASA,AAAQ,gBAAgB,QAAuB,KAAmB;EAChE,IAAI,CAAC,OAAO,MAAM;GAChB,IAAI,OAAO,UACT,KAAK,SAAS,MAAM;GAGtB,KAAK,SAAS,OAAO,GAAG;EAC1B;CACF;AACF;;AAGA,MAAM,aAAa,WAAgC;CACjD,OAAO,OAAO;AAChB;;;;;;;AAQA,MAAa,gBAAgB;;;;;;;AAQ7B,MAAa,WAAW,KAAa,QAAwB,MAAM,gBAAgB;AAEnF,MAAM,mBAAmB,WAAkC;CACzD,MAAM,QAAQ,OAAO,OAAO;CAC5B,MAAM,SAAS,OAAO,OAAO;CAE7B,OAAO,QAAQ,SAAS,QAAQ,OAAO,MAAM,IAAI,QAAQ,QAAQ,KAAK;AACxE;AAEA,MAAM,gBAAgB,GAAa,GAAa,QAAkB,QAAkB,cAAsC;CACxH;CACA;CACA;CACA;CACA;CACA,UAAU;CACV,MAAM;CACN,SAAS;CACT,UAAU;CACV,aAAa;CACb,UAAU,IAAI,SAAS;CACvB,eAAe,CAAC,GAAG,CAAC;CACpB,gBAAgB,CAAC,GAAG,CAAC;CACrB,UAAU,CAAC,GAAG,CAAC;AACjB;;;;;;AAOA,MAAM,kBAAkB,WAAgC;CACtD,MAAM,WAAW,OAAO;CACxB,MAAM,MAAM,OAAO,cAAc;CACjC,MAAM,MAAM,OAAO,cAAc;CACjC,MAAM,MAAM,OAAO,eAAe;CAClC,MAAM,MAAM,OAAO,eAAe;CAClC,MAAM,OAAO,OAAO,SAAS;CAC7B,MAAM,OAAO,OAAO,SAAS;CAE7B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACrB,IAAI,IAAI,SAAS,YAAY;EAE3B,MAAM,MAAM,MAAM,IAAI,SAAS,OAAO,KAAK,SAAS,OAAO,GAAE,CAAE;EAC/D,IAAI,SAAS;EACb,IAAI,UAAU;EAEd,IAAI,OAAO,MAAM;GACf,SAAS;GACT,UAAU;EACZ,OAAO,IAAI,OAAO,MAAM;GACtB,SAAS;GACT,UAAU;EACZ;EAEA,OAAO,cAAc,KAAK;EAC1B,OAAO,eAAe,KAAK;EAC3B,OAAO,SAAS,KAAK;CACvB,OAAO;EACL,OAAO,cAAc,KAAK;EAC1B,OAAO,eAAe,KAAK;EAC3B,OAAO,SAAS,KAAK;CACvB;AAEJ;AAEA,MAAM,sBAAsB,GAAa,GAAa,aAAuC;CAC3F,MAAM,SAAyB,CAAC;CAEhC,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,YAAY,KAAK;EAE5C,MAAM,IAAI,MAAM,IAAI,SAAS,OAAO,KAAK,SAAS,OAAO;EAEzD,OAAO,KAAK,OAAO,OAAO;GAAE,GAAG,EAAE;GAAG,GAAG,EAAE;GAAG,aAAa,EAAE;EAAY,CAAC,CAAC;CAC3E;CAEA,OAAO,OAAO,OAAO;EACnB,OAAO,EAAE;EACT,OAAO,EAAE;EACT,WAAW;EACX,WAAW;EACX,QAAQ,OAAO,OAAO;GAAE,GAAG,SAAS;GAAS,GAAG,SAAS;EAAQ,CAAC;EAClE,QAAQ,OAAO,OAAO,MAAM;CAC9B,CAAC;AACH;AAEA,MAAM,gBAAgB,GAAa,MACjC,OAAO,OAAO;CACZ,OAAO,EAAE;CACT,OAAO,EAAE;CACT,WAAW;CACX,WAAW;CACX,QAAQ,OAAO,OAAO;EAAE,GAAG;EAAG,GAAG;CAAE,CAAC;CACpC,QAAQ,OAAO,OAAO,CAAC,CAAmB;AAC5C,CAAC;AAEH,MAAM,mBAAmB,GAAa,MAA6B;CACjE,IAAI;CACJ,IAAI;CAEJ,IAAI,EAAE,YAAY,EAAE,UAAU;EAC5B,SAAS,EAAE,KAAK,EAAE,KAAK,IAAI;EAC3B,QAAQ,WAAW,IAAI,IAAI;CAC7B,OAAO,IAAI,EAAE,UAAU;EACrB,SAAS;EACT,QAAQ;CACV,OAAO;EACL,SAAS;EACT,QAAQ;CACV;CAEA,OAAO,OAAO,OAAO;EAAE;EAAQ;CAAM,CAAC;AACxC;AAEA,MAAM,kBAAkB,GAAmB,MAA8B,EAAE,UAAU,KAAK,EAAE,UAAU,MAAM,EAAE,UAAU,KAAK,EAAE,UAAU;AAEzI,MAAM,gBAAgB,GAAgB,MAA2B,EAAE,OAAO,KAAK,EAAE,OAAO,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM;AAEnH,MAAM,gBAAgB,GAAkB,MAA6B,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE"}