@perplexdotgg/bounce 1.0.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 (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/package.json +32 -0
  4. package/src/builders/ConvexHullBuilder.ts +437 -0
  5. package/src/builders/ConvexHullBuilder2d.ts +344 -0
  6. package/src/builders/ConvexHullBuilder3d.ts +1689 -0
  7. package/src/builders/HeightMapBuilder.ts +414 -0
  8. package/src/builders/TriangleMeshBuilder.ts +92 -0
  9. package/src/collision/CastShapesModule.ts +184 -0
  10. package/src/collision/CollideShapesModule.ts +152 -0
  11. package/src/collision/HeightMapCaster.ts +38 -0
  12. package/src/collision/HeightMapCollider.ts +33 -0
  13. package/src/collision/TriangleCaster.ts +249 -0
  14. package/src/collision/TriangleCollider.ts +308 -0
  15. package/src/collision/TriangleCollider2.ts +379 -0
  16. package/src/collision/activeEdge.ts +146 -0
  17. package/src/collision/cast/cast.ts +139 -0
  18. package/src/collision/cast/castCompoundVsCompound.ts +59 -0
  19. package/src/collision/cast/castCompoundVsConvex.ts +116 -0
  20. package/src/collision/cast/castConvexVsCompound.ts +123 -0
  21. package/src/collision/cast/castConvexVsConvex.ts +213 -0
  22. package/src/collision/cast/castConvexVsHeightMap.ts +73 -0
  23. package/src/collision/cast/castConvexVsTriangleMesh.ts +56 -0
  24. package/src/collision/cast/castRayVsCompound.ts +44 -0
  25. package/src/collision/cast/castRayVsConvex.ts +45 -0
  26. package/src/collision/cast/castRayVsHeightMap.ts +58 -0
  27. package/src/collision/cast/castRayVsTriangleMesh.ts +58 -0
  28. package/src/collision/closestPoints/closestPoints.ts +23 -0
  29. package/src/collision/closestPoints/computeBarycentricCoordinates2d.ts +32 -0
  30. package/src/collision/closestPoints/computeBarycentricCoordinates3d.ts +81 -0
  31. package/src/collision/closestPoints/computeClosestPointOnLine.ts +30 -0
  32. package/src/collision/closestPoints/computeClosestPointOnTetrahedron.ts +96 -0
  33. package/src/collision/closestPoints/computeClosestPointOnTriangle.ts +195 -0
  34. package/src/collision/closestPoints/isOriginOutsideOfPlane.ts +25 -0
  35. package/src/collision/closestPoints/isOriginOutsideOfTrianglePlanes.ts +72 -0
  36. package/src/collision/collide/collide.ts +146 -0
  37. package/src/collision/collide/collideCompoundVsCompound.ts +60 -0
  38. package/src/collision/collide/collideCompoundVsConvex.ts +59 -0
  39. package/src/collision/collide/collideCompoundVsHeightMap.ts +73 -0
  40. package/src/collision/collide/collideCompoundVsTriangleMesh.ts +56 -0
  41. package/src/collision/collide/collideConvexVsCompound.ts +57 -0
  42. package/src/collision/collide/collideConvexVsConvex.ts +225 -0
  43. package/src/collision/collide/collideConvexVsConvexImp.ts +236 -0
  44. package/src/collision/collide/collideConvexVsHeightMap.ts +53 -0
  45. package/src/collision/collide/collideConvexVsTriangleMesh.ts +58 -0
  46. package/src/collision/collide/collideHeightMapVsCompound.ts +69 -0
  47. package/src/collision/collide/collideHeightMapVsConvex.ts +53 -0
  48. package/src/collision/collide/collideSphereVsSphere.ts +81 -0
  49. package/src/collision/collide/collideTriangleMeshVsCompound.ts +58 -0
  50. package/src/collision/collide/collideTriangleMeshVsConvex.ts +58 -0
  51. package/src/collision/epa/EpaConvexHullBuilder.ts +397 -0
  52. package/src/collision/epa/StaticArray.ts +154 -0
  53. package/src/collision/epa/TriangleFactory.ts +32 -0
  54. package/src/collision/epa/arrays.ts +99 -0
  55. package/src/collision/epa/binaryHeap.ts +82 -0
  56. package/src/collision/epa/structs.ts +227 -0
  57. package/src/collision/gjk/GjkModule.ts +864 -0
  58. package/src/collision/gjk/PenetrationDepthModule.ts +493 -0
  59. package/src/collision/gjk/SupportPoints.ts +50 -0
  60. package/src/collision/imp/MinkowskiDifference.ts +36 -0
  61. package/src/collision/imp/computeExploredDistanceLowerUpperBound.ts +40 -0
  62. package/src/collision/imp/finalizeImpResult.ts +69 -0
  63. package/src/collision/imp/findContactImp.ts +196 -0
  64. package/src/collision/imp/imp.ts +28 -0
  65. package/src/collision/imp/incrementalMinimumDistanceExploreDirection.ts +207 -0
  66. package/src/collision/mpr/findPortal.ts +152 -0
  67. package/src/collision/mpr/mpr.ts +29 -0
  68. package/src/collision/mpr/updatePortal.ts +52 -0
  69. package/src/constraints/BaseConstraint.ts +50 -0
  70. package/src/constraints/ConstraintOptions.ts +22 -0
  71. package/src/constraints/ConstraintSolver.ts +119 -0
  72. package/src/constraints/DistanceConstraint.ts +229 -0
  73. package/src/constraints/FixedConstraint.ts +203 -0
  74. package/src/constraints/HingeConstraint.ts +460 -0
  75. package/src/constraints/PointConstraint.ts +108 -0
  76. package/src/constraints/components/AngleComponent.ts +226 -0
  77. package/src/constraints/components/AxisComponent.ts +263 -0
  78. package/src/constraints/components/HingeComponent.ts +215 -0
  79. package/src/constraints/components/Motor.ts +36 -0
  80. package/src/constraints/components/PointConstraintComponent.ts +179 -0
  81. package/src/constraints/components/RotationEulerComponent.ts +139 -0
  82. package/src/constraints/components/Spring.ts +30 -0
  83. package/src/constraints/components/SpringComponent.ts +71 -0
  84. package/src/constraints/types.ts +6 -0
  85. package/src/helpers.ts +147 -0
  86. package/src/index.ts +50 -0
  87. package/src/math/BasicTransform.ts +19 -0
  88. package/src/math/NumberValue.ts +13 -0
  89. package/src/math/isometry.ts +64 -0
  90. package/src/math/mat3.ts +529 -0
  91. package/src/math/mat4.ts +588 -0
  92. package/src/math/quat.ts +193 -0
  93. package/src/math/scalar.ts +81 -0
  94. package/src/math/tensor.ts +17 -0
  95. package/src/math/vec3.ts +589 -0
  96. package/src/math/vec4.ts +10 -0
  97. package/src/physics/Body.ts +581 -0
  98. package/src/physics/CollisionFilter.ts +52 -0
  99. package/src/physics/SleepModule.ts +163 -0
  100. package/src/physics/broadphase/BodyPairsModule.ts +363 -0
  101. package/src/physics/broadphase/BvhModule.ts +237 -0
  102. package/src/physics/broadphase/BvhTree.ts +803 -0
  103. package/src/physics/broadphase/ConstraintPairsModule.ts +385 -0
  104. package/src/physics/broadphase/TriangleMeshBvhTree.ts +379 -0
  105. package/src/physics/manifold/ContactManifold.ts +227 -0
  106. package/src/physics/manifold/ContactManifoldModule.ts +623 -0
  107. package/src/physics/manifold/Face.ts +119 -0
  108. package/src/physics/manifold/ManifoldCache.ts +116 -0
  109. package/src/physics/manifold/clipping/clipPolyVsEdge.ts +131 -0
  110. package/src/physics/manifold/clipping/clipPolyVsPlane.ts +73 -0
  111. package/src/physics/manifold/clipping/clipPolyVsPoly.ts +72 -0
  112. package/src/physics/narrowphase/CollideBodiesModule.ts +755 -0
  113. package/src/physics/solver/ContactConstraintModule.ts +659 -0
  114. package/src/physics/solver/ManifoldConstraint.ts +420 -0
  115. package/src/physics/solver/estimateCollisionResponse.ts +146 -0
  116. package/src/shape/Aabb.ts +400 -0
  117. package/src/shape/Box.ts +231 -0
  118. package/src/shape/Capsule.ts +332 -0
  119. package/src/shape/CompoundShape.ts +288 -0
  120. package/src/shape/Convex.ts +130 -0
  121. package/src/shape/ConvexHull.ts +423 -0
  122. package/src/shape/Cylinder.ts +313 -0
  123. package/src/shape/HeightMap.ts +511 -0
  124. package/src/shape/Line.ts +14 -0
  125. package/src/shape/Plane.ts +116 -0
  126. package/src/shape/Ray.ts +81 -0
  127. package/src/shape/Segment.ts +25 -0
  128. package/src/shape/Shape.ts +77 -0
  129. package/src/shape/Sphere.ts +181 -0
  130. package/src/shape/TransformedShape.ts +51 -0
  131. package/src/shape/Triangle.ts +122 -0
  132. package/src/shape/TriangleMesh.ts +186 -0
  133. package/src/types.ts +1 -0
  134. package/src/world.ts +1335 -0
  135. package/tests/BodyPairsModule.test.ts +71 -0
  136. package/tests/BvhTree.test.ts +406 -0
  137. package/tests/test.md +642 -0
  138. package/tests/vec3.test.ts +12 -0
  139. package/tsconfig.json +20 -0
  140. package/vite.config.js +40 -0
@@ -0,0 +1,163 @@
1
+ import { Body, BodyType } from "./Body";
2
+ import { BodyPairNode, BodyPairsModule } from "./broadphase/BodyPairsModule";
3
+ import { ConstraintPairNode, ConstraintPairsModule } from "./broadphase/ConstraintPairsModule";
4
+
5
+ function wakeBodyPair(node: BodyPairNode) {
6
+ node.bodyA!.isSleeping = false;
7
+ node.bodyA!.timeWithoutMoving = 0;
8
+
9
+ node.bodyB!.isSleeping = false;
10
+ node.bodyB!.timeWithoutMoving = 0;
11
+ return false;
12
+ }
13
+
14
+ export interface SleepOptions {
15
+ isSleepingEnabled: boolean;
16
+ sleepVelocityThreshold: number;
17
+ sleepSpinThreshold: number;
18
+ sleepTimeThreshold: number;
19
+ }
20
+
21
+ export class SleepModule {
22
+ options: SleepOptions;
23
+ contactPairs: BodyPairsModule;
24
+ constraintPairs: ConstraintPairsModule;
25
+
26
+ _areAllConnectedBodyPairsReadyToSleep: boolean;
27
+
28
+ constructor(contactPairs: BodyPairsModule, constraintPairs: ConstraintPairsModule, options?: Partial<SleepOptions>) {
29
+ this.options = {
30
+ isSleepingEnabled: true,
31
+ sleepVelocityThreshold: 0.03,
32
+ sleepSpinThreshold: 0.03,
33
+ sleepTimeThreshold: 0.5,
34
+ ...(options || {}),
35
+ };
36
+ this.contactPairs = contactPairs;
37
+ this.constraintPairs = constraintPairs;
38
+
39
+ this._areAllConnectedBodyPairsReadyToSleep = true;
40
+ }
41
+
42
+ wakeBodyUp(body: Body) {
43
+ if (!body.isSleeping) {
44
+ return;
45
+ }
46
+
47
+ body.isSleeping = false;
48
+ body.timeWithoutMoving = 0;
49
+
50
+ if (body.firstPotentialPairEdge) {
51
+ this.contactPairs.walk(body.firstPotentialPairEdge.node!, wakeBodyPair);
52
+ }
53
+
54
+ if (body.firstPotentialConstraintPairEdge) {
55
+ this.constraintPairs.walk(body.firstPotentialConstraintPairEdge.node!, wakeBodyPair);
56
+ }
57
+ }
58
+
59
+ updateBodySleepStates(bodies: typeof Body.Pool | typeof Body.ReferenceList, timeStepSizeSeconds: number) {
60
+ if (!this.options.isSleepingEnabled) {
61
+ return;
62
+ }
63
+
64
+ const sleepVelocityThresholdSquared = this.options.sleepVelocityThreshold ** 2;
65
+ const sleepSpinThresholdSquared = this.options.sleepSpinThreshold ** 2;
66
+
67
+ for (const body of bodies) {
68
+ const velocityMagnitudeSquared = body.linearVelocity.squaredLength();
69
+ const spinMagnitudeSquared = body.angularVelocity.squaredLength();
70
+
71
+ if (!body.isSleepingEnabled) {
72
+ continue;
73
+ }
74
+
75
+ if (
76
+ velocityMagnitudeSquared < sleepVelocityThresholdSquared &&
77
+ spinMagnitudeSquared < sleepSpinThresholdSquared
78
+ ) {
79
+ body.timeWithoutMoving += timeStepSizeSeconds;
80
+ } else {
81
+ body.timeWithoutMoving = 0;
82
+ }
83
+ }
84
+
85
+ buildAndSleepIslands(bodies, this.options.sleepTimeThreshold);
86
+ }
87
+
88
+ isBodyPairAwake(bodyA: Body, bodyB: Body) {
89
+ return bodyA.isSleeping === false && bodyB.isSleeping === false;
90
+ }
91
+ }
92
+
93
+ let sleepVisitGeneration = 0;
94
+ const stack: Body[] = [];
95
+ const island: Body[] = [];
96
+
97
+ function buildAndSleepIslands(bodies: typeof Body.Pool | typeof Body.ReferenceList, sleepTimeThreshold: number) {
98
+ const visitGeneration = ++sleepVisitGeneration;
99
+
100
+ for (const seedBody of bodies) {
101
+ if (seedBody.type === BodyType.static) {
102
+ continue;
103
+ }
104
+ if (seedBody.visitGeneration === visitGeneration) {
105
+ continue;
106
+ }
107
+ if (seedBody.isSleeping) {
108
+ continue;
109
+ }
110
+
111
+ // start a new island from this body
112
+ stack.length = 0;
113
+ stack.push(seedBody);
114
+ island.length = 0;
115
+ let allReady = true;
116
+
117
+ while (stack.length) {
118
+ const body = stack.pop()!;
119
+ if (body.visitGeneration === visitGeneration) {
120
+ continue;
121
+ }
122
+
123
+ body.visitGeneration = visitGeneration;
124
+ island.push(body);
125
+
126
+ const isCurrentBodyReadyToSleep = body.isReadyToSleep(sleepTimeThreshold);
127
+ if (!isCurrentBodyReadyToSleep) {
128
+ allReady = false;
129
+ }
130
+
131
+ // traverse contacts
132
+ for (let e = body.firstPotentialPairEdge; e; e = e.next) {
133
+ const node = e.node!;
134
+ const other = node.bodyA === body ? node.bodyB! : node.bodyA!;
135
+ if (other.type !== BodyType.static && other.visitGeneration !== visitGeneration) {
136
+ stack.push(other);
137
+ }
138
+ }
139
+
140
+ // traverse constraints
141
+ for (let e = body.firstPotentialConstraintPairEdge; e; e = e.next) {
142
+ const node = e.node!;
143
+ const other = node.bodyA === body ? node.bodyB! : node.bodyA!;
144
+ if (other.type !== BodyType.static && other.visitGeneration !== visitGeneration) {
145
+ stack.push(other);
146
+ }
147
+ }
148
+ }
149
+
150
+ // set sleep for the entire island
151
+ if (allReady) {
152
+ for (const body of island) {
153
+ // body.sleep();
154
+ body.isSleeping = true;
155
+ }
156
+ } else {
157
+ for (const body of island) {
158
+ // body.wakeUp();
159
+ body.isSleeping = false;
160
+ }
161
+ }
162
+ }
163
+ }
@@ -0,0 +1,363 @@
1
+ import {
2
+ createClass,
3
+ LazyReferenceType,
4
+ PoolClass,
5
+ PropertyDefinitionMap,
6
+ PropertyDefinitionReference,
7
+ } from "monomorph";
8
+ import { Body, BodyType } from "../Body";
9
+
10
+ const bodyPairNodeProps = {
11
+ bodyA: LazyReferenceType((() => Body) as () => never) as PropertyDefinitionReference<Body | null, true>,
12
+ bodyB: LazyReferenceType((() => Body) as () => never) as PropertyDefinitionReference<Body | null, true>,
13
+ edgeA: LazyReferenceType((() => BodyPairEdge) as () => never) as PropertyDefinitionReference<
14
+ BodyPairEdge | null,
15
+ true
16
+ >,
17
+ edgeB: LazyReferenceType((() => BodyPairEdge) as () => never) as PropertyDefinitionReference<
18
+ BodyPairEdge | null,
19
+ true
20
+ >,
21
+ } as const satisfies PropertyDefinitionMap;
22
+
23
+ export class BodyPairNode extends createClass<BodyPairNode, typeof bodyPairNodeProps>(bodyPairNodeProps) {
24
+ containsStaticBody() {
25
+ return this.bodyA!.type === BodyType.static || this.bodyB!.type === BodyType.static;
26
+ }
27
+ }
28
+
29
+ const bodyPairEdgeProps = {
30
+ node: LazyReferenceType((() => BodyPairNode) as () => never) as PropertyDefinitionReference<
31
+ BodyPairNode | null,
32
+ true
33
+ >,
34
+ next: LazyReferenceType((() => BodyPairEdge) as () => never) as PropertyDefinitionReference<
35
+ BodyPairEdge | null,
36
+ true
37
+ >,
38
+ prev: LazyReferenceType((() => BodyPairEdge) as () => never) as PropertyDefinitionReference<
39
+ BodyPairEdge | null,
40
+ true
41
+ >,
42
+ } as const satisfies PropertyDefinitionMap;
43
+
44
+ export class BodyPairEdge extends createClass<BodyPairEdge, typeof bodyPairEdgeProps>(bodyPairEdgeProps) {}
45
+
46
+ export class BodyPairsModule {
47
+ bodyPool: PoolClass<Body>;
48
+ nodesPool: PoolClass<BodyPairNode>;
49
+ edgesPool: PoolClass<BodyPairEdge>;
50
+
51
+ walkStack: BodyPairNode[];
52
+ walkVisited: Set<BodyPairNode>;
53
+
54
+ bodiesToSleep: Set<Body>;
55
+ _sleepTimeThreshold: number;
56
+ _allReady: boolean;
57
+
58
+ constructor(bodyPool: PoolClass<Body>, nodesPool: PoolClass<BodyPairNode>, edgesPool: PoolClass<BodyPairEdge>) {
59
+ this.bodyPool = bodyPool;
60
+ this.nodesPool = nodesPool;
61
+ this.edgesPool = edgesPool;
62
+
63
+ this.walkStack = [];
64
+ this.walkVisited = new Set();
65
+
66
+ this.bodiesToSleep = new Set();
67
+ this._sleepTimeThreshold = 0.5;
68
+ this._allReady = true;
69
+ }
70
+
71
+ toArray(array: number[] | Float32Array | Float64Array, startOffset = 0): number {
72
+ startOffset = this.nodesPool.toArray(array, startOffset);
73
+ startOffset = this.edgesPool.toArray(array, startOffset);
74
+ return startOffset;
75
+ }
76
+
77
+ fromArray(array: number[] | Float32Array | Float64Array, startOffset = 0): number {
78
+ let initialStartOffset = startOffset;
79
+
80
+ // everything but references, since we have circular references
81
+ // we do the references below
82
+ startOffset = this.edgesPool.fromArrayNoReferences(array, startOffset);
83
+
84
+ startOffset = this.nodesPool.fromArray(array, {
85
+ bodyA: this.bodyPool,
86
+ bodyB: this.bodyPool,
87
+ edgeA: this.edgesPool,
88
+ edgeB: this.edgesPool,
89
+ }, startOffset);
90
+
91
+ this.edgesPool.fromArrayOnlyReferences(array, {
92
+ node: this.nodesPool,
93
+ next: this.edgesPool,
94
+ prev: this.edgesPool,
95
+ }, initialStartOffset);
96
+
97
+ return startOffset;
98
+ }
99
+
100
+ _checkReadyToSleep = (neighbor: BodyPairNode) => {
101
+ this.walkVisited.add(neighbor);
102
+ const bodyA = neighbor.bodyA!;
103
+ const bodyB = neighbor.bodyB!;
104
+
105
+ if (bodyA.type === BodyType.dynamic) {
106
+ this.bodiesToSleep.add(bodyA);
107
+ if (!bodyA.isReadyToSleep(this._sleepTimeThreshold)) {
108
+ this._allReady = false;
109
+ return true;
110
+ }
111
+ }
112
+
113
+ if (bodyB.type === BodyType.dynamic) {
114
+ this.bodiesToSleep.add(bodyB);
115
+ if (!bodyB.isReadyToSleep(this._sleepTimeThreshold)) {
116
+ this._allReady = false;
117
+ return true;
118
+ }
119
+ }
120
+
121
+ if (
122
+ !neighbor.bodyA!.isReadyToSleep(this._sleepTimeThreshold) ||
123
+ !neighbor.bodyB!.isReadyToSleep(this._sleepTimeThreshold)
124
+ ) {
125
+ this._allReady = false;
126
+ return true;
127
+ }
128
+ return false;
129
+ };
130
+
131
+ /**
132
+ * creates a body pair. O(1)
133
+ * @param bodyA
134
+ * @param bodyB
135
+ * @returns
136
+ */
137
+ createPair(bodyA: Body, bodyB: Body) {
138
+ // if like-type, ensure bodyA has the lower index
139
+ if (bodyA.type === bodyB.type && bodyA.index > bodyB.index) {
140
+ const temp = bodyA;
141
+ bodyA = bodyB;
142
+ bodyB = temp;
143
+ }
144
+ // if unlike-type, ensure bodyA is the dynamic
145
+ if (bodyA.type !== bodyB.type && bodyA.type !== BodyType.dynamic) {
146
+ const temp = bodyA;
147
+ bodyA = bodyB;
148
+ bodyB = temp;
149
+ }
150
+
151
+ // allocations
152
+ const node = /*@__PURE__*/ BodyPairNode.create(undefined, this.nodesPool);
153
+ node.bodyA = null;
154
+ node.bodyB = null;
155
+ node.edgeA = null;
156
+ node.edgeB = null;
157
+
158
+ const edgeA = /*@__PURE__*/ BodyPairEdge.create(undefined, this.edgesPool);
159
+ edgeA.next = null;
160
+ edgeA.prev = null;
161
+ edgeA.node = null;
162
+
163
+ const edgeB = /*@__PURE__*/ BodyPairEdge.create(undefined, this.edgesPool);
164
+ edgeB.next = null;
165
+ edgeB.prev = null;
166
+ edgeB.node = null;
167
+
168
+ // wire up node
169
+ node.bodyA = bodyA;
170
+ node.bodyB = bodyB;
171
+ node.edgeA = edgeA;
172
+ node.edgeB = edgeB;
173
+
174
+ // wire up edgeA
175
+ edgeA.node = node;
176
+ edgeA.prev = null;
177
+ edgeA.next = bodyA.firstPotentialPairEdge;
178
+ if (bodyA.firstPotentialPairEdge) {
179
+ bodyA.firstPotentialPairEdge.prev = edgeA;
180
+ }
181
+ bodyA.firstPotentialPairEdge = edgeA;
182
+
183
+ // wire up edgeB
184
+ edgeB.node = node;
185
+ edgeB.prev = null;
186
+ edgeB.next = bodyB.firstPotentialPairEdge;
187
+ if (bodyB.firstPotentialPairEdge) {
188
+ bodyB.firstPotentialPairEdge.prev = edgeB;
189
+ }
190
+ bodyB.firstPotentialPairEdge = edgeB;
191
+
192
+ return node;
193
+ }
194
+
195
+ /**
196
+ * destroys a body pair. O(k) where k is the number of pairs involving bodyA
197
+ */
198
+ destroyPair(bodyA: Body, bodyB: Body) {
199
+ let edge = bodyA.firstPotentialPairEdge;
200
+
201
+ // walk through the edges of bodyA
202
+ while (edge) {
203
+ // find the node
204
+ const node = edge.node!;
205
+ if (node.bodyB !== bodyB) {
206
+ edge = edge.next;
207
+ continue;
208
+ }
209
+
210
+ // unlink edgeA from bodyA
211
+ const edgeA = node.edgeA!;
212
+ if (edgeA.prev) {
213
+ edgeA.prev.next = edgeA.next;
214
+ } else {
215
+ bodyA.firstPotentialPairEdge = edgeA.next;
216
+ }
217
+ if (edgeA.next) {
218
+ edgeA.next.prev = edgeA.prev;
219
+ }
220
+
221
+ // unlink edgeB from bodyB
222
+ const edgeB = node.edgeB!;
223
+ if (edgeB.prev) {
224
+ edgeB.prev.next = edgeB.next;
225
+ } else {
226
+ bodyB.firstPotentialPairEdge = edgeB.next;
227
+ }
228
+ if (edgeB.next) {
229
+ edgeB.next.prev = edgeB.prev;
230
+ }
231
+
232
+ // destroy the objects
233
+ edgeA.destroy();
234
+ edgeB.destroy();
235
+ node.destroy();
236
+
237
+ return true;
238
+ }
239
+
240
+ return false;
241
+ }
242
+
243
+ /**
244
+ * destroys all pairs of involving a single body. O(k) where k is the number of pairs involving body
245
+ */
246
+ destroyAllPairsOfOne(body: Body) {
247
+ let destroyed = false;
248
+
249
+ // walk through the edges of body
250
+ let edge = body.firstPotentialPairEdge;
251
+ while (edge) {
252
+ const nextEdge = edge.next as BodyPairEdge | null;
253
+
254
+ const node = edge.node!;
255
+ const edgeA = node.edgeA!;
256
+ const edgeB = node.edgeB!;
257
+
258
+ const otherBody = node.bodyA === body ? node.bodyB! : node.bodyA!;
259
+ const otherEdge = node.bodyA === body ? edgeB : edgeA;
260
+
261
+ // unlink the other edge from the other body
262
+ if (otherEdge.prev) {
263
+ otherEdge.prev.next = otherEdge.next;
264
+ } else {
265
+ otherBody.firstPotentialPairEdge = otherEdge.next;
266
+ }
267
+ if (otherEdge.next) {
268
+ otherEdge.next.prev = otherEdge.prev;
269
+ }
270
+
271
+ // destroy the objects
272
+ edgeA.destroy();
273
+ edgeB.destroy();
274
+ node.destroy();
275
+
276
+ edge = nextEdge;
277
+ destroyed = true;
278
+ }
279
+
280
+ body.firstPotentialPairEdge = null;
281
+
282
+ return destroyed;
283
+ }
284
+
285
+ *iteratePairsOfOne(body: Body): IterableIterator<BodyPairNode> {
286
+ let edge = body.firstPotentialPairEdge;
287
+ while (edge) {
288
+ yield edge.node!;
289
+ edge = edge.next;
290
+ }
291
+ }
292
+
293
+ *iteratePairs(): IterableIterator<BodyPairNode> {
294
+ for (const node of this.nodesPool) {
295
+ yield node;
296
+ }
297
+ }
298
+
299
+ walk(startNode: BodyPairNode, onNode: (node: BodyPairNode) => boolean) {
300
+ const visited = this.walkVisited;
301
+ visited.clear();
302
+
303
+ const stack = this.walkStack;
304
+ stack.length = 0;
305
+ stack.push(startNode);
306
+
307
+ while (stack.length > 0) {
308
+ const node = stack.pop()!;
309
+ if (visited.has(node)) {
310
+ continue;
311
+ }
312
+ visited.add(node);
313
+
314
+ if (onNode(node)) {
315
+ return;
316
+ }
317
+
318
+ const bodyA = node.bodyA!;
319
+ const bodyB = node.bodyB!;
320
+
321
+ // only walk through dynamic bodies — prevents static bridges
322
+ if (bodyA.type === BodyType.dynamic) {
323
+ for (const neighbor of this.iteratePairsOfOne(bodyA)) {
324
+ if (!visited.has(neighbor)) {
325
+ stack.push(neighbor);
326
+ }
327
+ }
328
+ }
329
+
330
+ if (bodyB.type === BodyType.dynamic) {
331
+ for (const neighbor of this.iteratePairsOfOne(bodyB)) {
332
+ if (!visited.has(neighbor)) {
333
+ stack.push(neighbor);
334
+ }
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ sleepAllReadyBodies(sleepTimeThreshold: number) {
341
+ this.walkVisited.clear();
342
+ this.bodiesToSleep.clear();
343
+ this._sleepTimeThreshold = sleepTimeThreshold;
344
+ this._allReady = true;
345
+
346
+ for (const node of this.nodesPool) {
347
+ if (this.walkVisited.has(node)) {
348
+ continue;
349
+ }
350
+
351
+ this.bodiesToSleep.clear();
352
+ this._allReady = true;
353
+
354
+ this.walk(node, this._checkReadyToSleep);
355
+
356
+ if (this._allReady) {
357
+ for (const body of this.bodiesToSleep) {
358
+ body.sleep();
359
+ }
360
+ }
361
+ }
362
+ }
363
+ }