@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,803 @@
1
+ import {
2
+ createClass,
3
+ LazyReferenceListType,
4
+ LazyReferenceType,
5
+ MonomorphInstance,
6
+ MonomorphType,
7
+ NumberType,
8
+ PropertyDefinitionMap,
9
+ PropertyDefinitionReference,
10
+ PropertyDefinitionReferenceList,
11
+ WithPool,
12
+ } from "monomorph";
13
+ import { Aabb } from "../../shape/Aabb";
14
+ import { assert } from "../../helpers";
15
+ import { Vec3 } from "../../math/vec3";
16
+ import { Body } from "../Body";
17
+ import { Ray } from "../../shape/Ray";
18
+ import { shouldPairCollide } from "../CollisionFilter";
19
+
20
+ const expandedObjectBounds = /*@__PURE__*/ Aabb.create();
21
+ const leftAabb = /*@__PURE__*/ Aabb.create();
22
+ const rightAabb = /*@__PURE__*/ Aabb.create();
23
+ const centroidBounds = /*@__PURE__*/ Aabb.create();
24
+ const extent = /*@__PURE__*/ Vec3.create();
25
+ const ray = /*@__PURE__*/ Ray.create();
26
+ const halfExtents = /*@__PURE__*/ Vec3.create();
27
+ const nodeBounds = /*@__PURE__*/ Aabb.create();
28
+ const bodyBounds = /*@__PURE__*/ Aabb.create();
29
+
30
+ const bvhNodeProps = {
31
+ parent: LazyReferenceType((() => BvhNode) as () => never) as PropertyDefinitionReference<BvhNode | null, true>,
32
+ left: LazyReferenceType((() => BvhNode) as () => never) as PropertyDefinitionReference<BvhNode | null, true>,
33
+ right: LazyReferenceType((() => BvhNode) as () => never) as PropertyDefinitionReference<BvhNode | null, true>,
34
+ computedBounds: MonomorphType(Aabb, undefined, true),
35
+ height: NumberType(0),
36
+ objects: LazyReferenceListType((() => Body) as () => never) as PropertyDefinitionReferenceList<Body, Body extends MonomorphInstance<infer P, infer I> ? P : never, Body extends MonomorphInstance<infer P, infer I> ? I : never>,
37
+ } as const satisfies PropertyDefinitionMap;
38
+
39
+ export class BvhNode extends createClass<BvhNode, typeof bvhNodeProps>(bvhNodeProps) {
40
+ // objects: Body[] = [];
41
+
42
+ isLeaf() {
43
+ return this.left === null && this.right === null;
44
+ }
45
+
46
+ isRoot() {
47
+ return this.parent === null;
48
+ }
49
+ }
50
+
51
+ function sortByX(a: Body, b: Body) {
52
+ return a.computedBounds.centroid.x - b.computedBounds.centroid.x;
53
+ }
54
+
55
+ function sortByY(a: Body, b: Body) {
56
+ return a.computedBounds.centroid.y - b.computedBounds.centroid.y;
57
+ }
58
+
59
+ function sortByZ(a: Body, b: Body) {
60
+ return a.computedBounds.centroid.z - b.computedBounds.centroid.z;
61
+ }
62
+
63
+ const sortByAxisFunctions = {
64
+ 0: sortByX,
65
+ 1: sortByY,
66
+ 2: sortByZ,
67
+ } as const;
68
+
69
+ export type BvhTreeOptions = {
70
+ maxDepth: number;
71
+ maxObjectsPerLeaf: number;
72
+ expansionMargin: number;
73
+ };
74
+
75
+ const sortedObjects: WithPool<Body>[] = [];
76
+
77
+ export class BvhTree {
78
+ options: BvhTreeOptions;
79
+ nodes: typeof BvhNode.ReferenceList;
80
+ bodyPool: typeof Body.Pool;
81
+ root: BvhNode | null = null;
82
+
83
+ optimizeQueue: BvhNode[];
84
+ optimizeQueueHead: number;
85
+
86
+ intersectStack: BvhNode[];
87
+
88
+ walkStack: BvhNode[];
89
+
90
+ dirtyObjects: Set<WithPool<Body>>;
91
+
92
+ constructor(options: BvhTreeOptions, bvhNodePool: typeof BvhNode.Pool, bodyPool: typeof Body.Pool) {
93
+ this.options = options;
94
+ this.bodyPool = bodyPool;
95
+ this.nodes = new BvhNode.ReferenceList(bvhNodePool);
96
+ this.root = null;
97
+
98
+ this.optimizeQueue = [];
99
+ this.optimizeQueueHead = 0;
100
+
101
+ this.intersectStack = [];
102
+
103
+ this.walkStack = [];
104
+
105
+ this.dirtyObjects = new Set<WithPool<Body>>();
106
+ }
107
+
108
+ markObjectAsDirty(object: WithPool<Body>) {
109
+ this.dirtyObjects.add(object);
110
+ }
111
+
112
+ updateDirtyObjects() {
113
+ for (const object of this.dirtyObjects) {
114
+ if (this.update(object, false)) {
115
+ }
116
+ }
117
+ this.dirtyObjects.clear();
118
+ }
119
+
120
+ rebalance(node: BvhNode): BvhNode {
121
+ if (node.isLeaf() || node.height < 2) {
122
+ return node;
123
+ }
124
+
125
+ const left = node.left!;
126
+ const right = node.right!;
127
+ const balance = right.height - left.height;
128
+
129
+ // Rotate right up
130
+ if (balance > 1) {
131
+ const rightLeft = right.left!;
132
+ const rightRight = right.right!;
133
+
134
+ // Swap node and right
135
+ right.left = node;
136
+ right.parent = node.parent;
137
+ node.parent = right;
138
+
139
+ if (right.parent) {
140
+ if (right.parent.left === node) {
141
+ right.parent.left = right;
142
+ } else {
143
+ right.parent.right = right;
144
+ }
145
+ } else {
146
+ this.root = right;
147
+ }
148
+
149
+ if (rightLeft.height > rightRight.height) {
150
+ right.right = rightLeft;
151
+ node.right = rightRight;
152
+ rightRight.parent = node;
153
+
154
+ node.computedBounds.unionAabbs(left.computedBounds, rightRight.computedBounds);
155
+ right.computedBounds.unionAabbs(node.computedBounds, rightLeft.computedBounds);
156
+
157
+ node.height = Math.max(left.height, rightRight.height) + 1;
158
+ right.height = Math.max(node.height, rightLeft.height) + 1;
159
+ } else {
160
+ right.right = rightRight;
161
+ node.right = rightLeft;
162
+ rightLeft.parent = node;
163
+
164
+ node.computedBounds.unionAabbs(left.computedBounds, rightLeft.computedBounds);
165
+ right.computedBounds.unionAabbs(node.computedBounds, rightRight.computedBounds);
166
+
167
+ node.height = Math.max(left.height, rightLeft.height) + 1;
168
+ right.height = Math.max(node.height, rightRight.height) + 1;
169
+ }
170
+
171
+ return right;
172
+ }
173
+
174
+ // Rotate left up
175
+ if (balance < -1) {
176
+ const leftLeft = left.left!;
177
+ const leftRight = left.right!;
178
+
179
+ // Swap node and left
180
+ left.left = node;
181
+ left.parent = node.parent;
182
+ node.parent = left;
183
+
184
+ if (left.parent) {
185
+ if (left.parent.left === node) {
186
+ left.parent.left = left;
187
+ } else {
188
+ left.parent.right = left;
189
+ }
190
+ } else {
191
+ this.root = left;
192
+ }
193
+
194
+ if (leftLeft.height > leftRight.height) {
195
+ left.right = leftLeft;
196
+ node.left = leftRight;
197
+ leftRight.parent = node;
198
+
199
+ node.computedBounds.unionAabbs(right.computedBounds, leftRight.computedBounds);
200
+ left.computedBounds.unionAabbs(node.computedBounds, leftLeft.computedBounds);
201
+
202
+ node.height = Math.max(right.height, leftRight.height) + 1;
203
+ left.height = Math.max(node.height, leftLeft.height) + 1;
204
+ } else {
205
+ left.right = leftRight;
206
+ node.left = leftLeft;
207
+ leftLeft.parent = node;
208
+
209
+ node.computedBounds.unionAabbs(right.computedBounds, leftLeft.computedBounds);
210
+ left.computedBounds.unionAabbs(node.computedBounds, leftRight.computedBounds);
211
+
212
+ node.height = Math.max(right.height, leftLeft.height) + 1;
213
+ left.height = Math.max(node.height, leftRight.height) + 1;
214
+ }
215
+
216
+ return left;
217
+ }
218
+
219
+ return node;
220
+ }
221
+
222
+ refit(node: BvhNode | null) {
223
+ let didRefit = false;
224
+ while (node) {
225
+ didRefit = true;
226
+ if (node.isLeaf()) {
227
+ node.computedBounds.min.fromArray([+Infinity, +Infinity, +Infinity]);
228
+ node.computedBounds.max.fromArray([-Infinity, -Infinity, -Infinity]);
229
+
230
+ for (const object of node.objects) {
231
+ node.computedBounds.unionAabb(object.computedBounds);
232
+ }
233
+
234
+ node.height = 0;
235
+ } else {
236
+ const left = node.left!;
237
+ const right = node.right!;
238
+
239
+ node.computedBounds.unionAabbs(left.computedBounds, right.computedBounds);
240
+ node.height = Math.max(left.height, right.height) + 1;
241
+
242
+ if (node.height > 2) {
243
+ // this.tryRotation(node);
244
+ this.rebalance(node);
245
+ }
246
+ }
247
+
248
+ node = node.parent;
249
+ }
250
+
251
+ return didRefit;
252
+ }
253
+
254
+ optimizeOncePerFrame(maxNodesToCheck: number) {
255
+ if (!this.root) {
256
+ return;
257
+ }
258
+
259
+ const queue = this.optimizeQueue;
260
+ let head = this.optimizeQueueHead;
261
+ let tail = 0;
262
+
263
+ // init the queue if empty
264
+ if (head === 0 && queue.length === 0) {
265
+ queue.push(this.root);
266
+ tail = 1;
267
+ } else {
268
+ tail = queue.length;
269
+ }
270
+
271
+ let count = 0;
272
+
273
+ while (head < tail && count < maxNodesToCheck) {
274
+ const node = queue[head++];
275
+ if (!node.isLeaf()) {
276
+ // this.tryRotation(node);
277
+ this.rebalance(node);
278
+ count++;
279
+
280
+ if (node.left) {
281
+ queue.push(node.left);
282
+ }
283
+
284
+ if (node.right) {
285
+ queue.push(node.right);
286
+ }
287
+ }
288
+ }
289
+
290
+ // reset if done or save head for next call
291
+ if (head >= queue.length) {
292
+ queue.length = 0;
293
+ this.optimizeQueueHead = 0;
294
+ } else {
295
+ this.optimizeQueueHead = head;
296
+ }
297
+ }
298
+
299
+ splitObjects(outLeft: BvhNode, outRight: BvhNode, nodeToSplit: BvhNode) {
300
+ const objects = nodeToSplit.objects;
301
+ const leftObjects = outLeft.objects;
302
+ const rightObjects = outRight.objects;
303
+
304
+ if (objects.length < 2) {
305
+ throw new Error("Cannot split fewer than 2 objects.");
306
+ }
307
+
308
+ // expand centroid bounds
309
+ centroidBounds.min.fromArray([+Infinity, +Infinity, +Infinity]);
310
+ centroidBounds.max.fromArray([-Infinity, -Infinity, -Infinity]);
311
+
312
+ sortedObjects.length = 0;
313
+ for (const obj of objects) {
314
+ centroidBounds.expandToPoint(obj.computedBounds.centroid);
315
+ sortedObjects.push(obj);
316
+ }
317
+
318
+ centroidBounds.computeExtents(extent);
319
+ const axis = centroidBounds.computeLargestAxis();
320
+
321
+ sortedObjects.sort(sortByAxisFunctions[axis]);
322
+ let mid = Math.floor(sortedObjects.length / 2);
323
+ if (mid <= 0 || mid >= sortedObjects.length) {
324
+ mid = 1;
325
+ }
326
+
327
+ for (let i = 0; i < mid; i++) {
328
+ leftObjects.push(sortedObjects[i]);
329
+ sortedObjects[i].node = outLeft;
330
+ }
331
+
332
+ for (let i = mid; i < sortedObjects.length; i++) {
333
+ rightObjects.push(sortedObjects[i]);
334
+ sortedObjects[i].node = outRight;
335
+ }
336
+ }
337
+
338
+ createBvhNode() {
339
+ return this.nodes.create({
340
+ objects: {
341
+ pool: this.bodyPool,
342
+ },
343
+ });
344
+ };
345
+
346
+ destroyBvhNode(node: WithPool<BvhNode>) {
347
+ // this.nodes.remove(node);
348
+ node.destroy();
349
+ }
350
+
351
+ insert(object: WithPool<Body>, shouldUpdateDirtyObjects: boolean = true) {
352
+ expandedObjectBounds.expandAabb(object.computedBounds, this.options.expansionMargin);
353
+
354
+ // case: tree is empty
355
+ if (!this.root) {
356
+ const root = this.createBvhNode();
357
+ root.computedBounds.copy(expandedObjectBounds);
358
+ root.objects.push(object);
359
+ root.height = 0;
360
+ this.root = root;
361
+ object.node = root;
362
+ return true;
363
+ }
364
+
365
+ // find the best-fit leaf node
366
+ let best = this.root;
367
+ while (!best.isLeaf()) {
368
+ const left = best.left!;
369
+ const right = best.right!;
370
+
371
+ leftAabb.unionAabbs(best.computedBounds, left.computedBounds);
372
+ rightAabb.unionAabbs(best.computedBounds, right.computedBounds);
373
+
374
+ const leftCost = leftAabb.computeSurfaceArea();
375
+ const rightCost = rightAabb.computeSurfaceArea();
376
+
377
+ best = leftCost < rightCost ? left : right;
378
+ }
379
+
380
+ // #v-ifdef DEV
381
+ // dev assert best.isLeaf();
382
+ assert(best.isLeaf(), "Expected best node to be a leaf node.");
383
+ // #v-endif
384
+
385
+ // case: best leaf node is not full
386
+ if (best.objects.length < this.options.maxObjectsPerLeaf || best.height >= this.options.maxDepth) {
387
+ best.objects.push(object);
388
+ best.computedBounds.unionAabb(expandedObjectBounds);
389
+ object.node = best;
390
+ return this.refit(best);
391
+ }
392
+
393
+ // case: best leaf node is full, need to split
394
+ const left = this.createBvhNode();
395
+ const right = this.createBvhNode();
396
+
397
+ // partition the objects in the two new leaves
398
+ // we push the object to best temporarily so it can be included in the split
399
+ best.objects.push(object);
400
+ this.splitObjects(left, right, best);
401
+ // we clear best's objects now
402
+ best.objects.length = 0;
403
+
404
+ // update left leaf
405
+ left.computedBounds.copy(left.objects.getAtIndex(0)!.computedBounds);
406
+ for (let i = 1; i < left.objects.length; i++) {
407
+ left.computedBounds.unionAabb(left.objects.getAtIndex(i)!.computedBounds);
408
+ }
409
+ left.parent = best;
410
+ left.height = 0;
411
+
412
+ // update right leaf
413
+ right.computedBounds.copy(right.objects.getAtIndex(0)!.computedBounds);
414
+ for (let i = 1; i < right.objects.length; i++) {
415
+ right.computedBounds.unionAabb(right.objects.getAtIndex(i)!.computedBounds);
416
+ }
417
+ right.parent = best;
418
+ right.height = 0;
419
+
420
+ // update best node
421
+ best.left = left;
422
+ best.right = right;
423
+ best.computedBounds.unionAabbs(left.computedBounds, right.computedBounds);
424
+ best.height = 1;
425
+
426
+ return this.refit(best);
427
+ }
428
+
429
+ remove(object: WithPool<Body>, shouldRemoveFromDirtyObjects: boolean = false) {
430
+ const node = object.node as WithPool<BvhNode> | null;
431
+
432
+ if (shouldRemoveFromDirtyObjects) {
433
+ this.dirtyObjects.delete(object);
434
+ }
435
+
436
+ if (!node) {
437
+ return;
438
+ }
439
+
440
+ const wasFoundAndRemoved = node.objects.remove(object) > -1; // fastRemove(node.objects, object);
441
+ if (!wasFoundAndRemoved) {
442
+ return;
443
+ }
444
+
445
+ object.node = null;
446
+
447
+ // If there are still objects in the node, just refit and exit
448
+ if (node.objects.length > 0) {
449
+ return this.refit(node);
450
+ }
451
+
452
+ // If the node is the root, and it's now empty
453
+ if (node.isRoot()) {
454
+ this.destroyBvhNode(node);
455
+ this.root = null;
456
+ return false;
457
+ }
458
+
459
+ const parent = node.parent!;
460
+ const sibling = parent.left === node ? parent.right! : parent.left!;
461
+
462
+ sibling.parent = parent.parent;
463
+ if (parent.isRoot()) {
464
+ this.root = sibling;
465
+ } else {
466
+ const grandparent = parent.parent!;
467
+ if (grandparent.left === parent) {
468
+ grandparent.left = sibling;
469
+ } else {
470
+ grandparent.right = sibling;
471
+ }
472
+ }
473
+
474
+ this.destroyBvhNode(parent as WithPool<BvhNode>);
475
+ this.destroyBvhNode(node);
476
+
477
+ return this.refit(sibling);
478
+ }
479
+
480
+ update(object: WithPool<Body>, shouldUpdateDirtyObjects: boolean = true) {
481
+ const node = object.node;
482
+ if (!node) {
483
+ this.insert(object);
484
+ return false;
485
+ }
486
+
487
+ // avoid refit if there will be no change to node bounds
488
+ if (node.computedBounds.enclosesAabb(object.computedBounds)) {
489
+ return false;
490
+ }
491
+
492
+ this.remove(object);
493
+ return this.insert(object);
494
+ }
495
+
496
+ intersectBody(onHit: (Body: Body) => boolean, body: Body, shouldUpdateDirtyObjects: boolean = true) {
497
+ // console.log("intersect body", body);
498
+ if (!this.root) {
499
+ return;
500
+ }
501
+
502
+ if (shouldUpdateDirtyObjects) {
503
+ this.updateDirtyObjects();
504
+ }
505
+
506
+ const stack = this.intersectStack;
507
+ stack.length = 0;
508
+ stack.push(this.root);
509
+
510
+ while (stack.length > 0) {
511
+ const node = stack.pop();
512
+
513
+ if (!node) {
514
+ continue;
515
+ }
516
+
517
+ // skip this node if it doesn't intersect the AABB
518
+ if (!node.computedBounds.intersectsAabb(body.computedBounds)) {
519
+ continue;
520
+ }
521
+
522
+ // if the node is not a leaf, push its children onto the stack and continue
523
+ if (!node.isLeaf()) {
524
+ stack.push(node.left!);
525
+ stack.push(node.right!);
526
+ continue;
527
+ }
528
+
529
+ // if the node is a leaf, check its objects
530
+ for (const otherBody of node.objects) {
531
+ if (body === otherBody) {
532
+ // console.log("skipping self");
533
+ continue;
534
+ }
535
+
536
+ if (
537
+ shouldPairCollide(
538
+ body.belongsToGroups,
539
+ body.collidesWithGroups,
540
+ otherBody.belongsToGroups,
541
+ otherBody.collidesWithGroups
542
+ ) === false
543
+ ) {
544
+ continue;
545
+ }
546
+
547
+ if (!body.computedBounds.intersectsAabb(otherBody.computedBounds)) {
548
+ continue;
549
+ }
550
+
551
+ if (onHit(otherBody)) {
552
+ return;
553
+ }
554
+ }
555
+ }
556
+ }
557
+
558
+ walk(onNode: (node: BvhNode) => boolean) {
559
+ if (!this.root) {
560
+ return;
561
+ }
562
+
563
+ const stack = this.walkStack;
564
+ stack.length = 0;
565
+ stack.push(this.root);
566
+
567
+ while (stack.length > 0) {
568
+ const node = stack.pop();
569
+
570
+ if (!node) {
571
+ continue;
572
+ }
573
+
574
+ if (onNode(node)) {
575
+ return;
576
+ }
577
+
578
+ if (!node.isLeaf()) {
579
+ stack.push(node.left!);
580
+ stack.push(node.right!);
581
+ }
582
+ }
583
+ }
584
+
585
+ isEmpty() {
586
+ return this.root === null;
587
+ }
588
+
589
+ visitBodiesWithCollision(
590
+ bodyVisitor: BodyVisitor,
591
+ bounds: Aabb,
592
+ belongsToGroups: number,
593
+ collidesWithGroups: number,
594
+ shouldUpdateDirtyObjects: boolean = true
595
+ ): void {
596
+ // exit if the tree is empty
597
+ if (!this.root) {
598
+ return;
599
+ }
600
+
601
+ if (shouldUpdateDirtyObjects) {
602
+ this.updateDirtyObjects();
603
+ }
604
+
605
+ const stack = this.intersectStack;
606
+ stack.length = 0;
607
+ stack.push(this.root);
608
+
609
+ while (stack.length > 0) {
610
+ const node = stack.pop();
611
+
612
+ if (!node) {
613
+ continue;
614
+ }
615
+
616
+ // skip this node if it doesn't intersect the AABB
617
+ if (node.computedBounds.intersectsAabb(bounds) === false) {
618
+ continue;
619
+ }
620
+
621
+ // if the node is not a leaf, push its children onto the stack and continue
622
+ if (!node.isLeaf()) {
623
+ stack.push(node.left!);
624
+ stack.push(node.right!);
625
+ continue;
626
+ }
627
+
628
+ // if the node is a leaf, check its objects
629
+ for (const body of node.objects) {
630
+ if (
631
+ shouldPairCollide(body.belongsToGroups, body.collidesWithGroups, belongsToGroups, collidesWithGroups) ===
632
+ false
633
+ ) {
634
+ continue;
635
+ }
636
+
637
+ if (!body.computedBounds.intersectsAabb(bounds)) {
638
+ continue;
639
+ }
640
+
641
+ // visit the body
642
+ bodyVisitor.visit(body);
643
+
644
+ // break if visitor requests exit
645
+ if (bodyVisitor.shouldExit) {
646
+ return;
647
+ }
648
+ }
649
+ }
650
+ }
651
+
652
+ raycastBodies(
653
+ bodyVisitor: BodyVisitor,
654
+ ray: Ray,
655
+ belongsToGroups: number,
656
+ collidesWithGroups: number,
657
+ shouldUpdateDirtyObjects: boolean = true
658
+ ) {
659
+ // exit if the tree is empty
660
+ if (!this.root) {
661
+ return;
662
+ }
663
+
664
+ if (shouldUpdateDirtyObjects) {
665
+ this.updateDirtyObjects();
666
+ }
667
+
668
+ const stack = this.intersectStack;
669
+ stack.length = 0;
670
+ stack.push(this.root);
671
+
672
+ while (stack.length > 0) {
673
+ const node = stack.pop();
674
+
675
+ if (!node) {
676
+ continue;
677
+ }
678
+
679
+ // skip branch if the node's aabb does not intersect with the ray
680
+ nodeBounds.copy(node.computedBounds);
681
+ if (ray.intersectsAabb(nodeBounds) === false) {
682
+ continue;
683
+ }
684
+
685
+ // if the node is not a leaf, push its children onto the stack and continue
686
+ if (!node.isLeaf()) {
687
+ stack.push(node.left!);
688
+ stack.push(node.right!);
689
+ continue;
690
+ }
691
+
692
+ // if the node is a leaf, check its objects
693
+ for (const body of node.objects) {
694
+ if (
695
+ shouldPairCollide(body.belongsToGroups, body.collidesWithGroups, belongsToGroups, collidesWithGroups) ===
696
+ false
697
+ ) {
698
+ continue;
699
+ }
700
+
701
+ // skip if bounds do not collide
702
+ bodyBounds.copy(body.computedBounds);
703
+ if (ray.intersectsAabb(bodyBounds) === false) {
704
+ continue;
705
+ }
706
+
707
+ // visit the body
708
+ bodyVisitor.visit(body);
709
+
710
+ // break if visitor requests exit
711
+ if (bodyVisitor.shouldExit) {
712
+ return;
713
+ }
714
+ }
715
+ }
716
+ }
717
+
718
+ visitBodiesWithCast(
719
+ bodyVisitor: BodyVisitor,
720
+ bounds: Aabb,
721
+ displacement: Vec3,
722
+ belongsToGroups: number,
723
+ collidesWithGroups: number,
724
+ shouldUpdateDirtyObjects: boolean = true
725
+ ): void {
726
+ // exit if the tree is empty
727
+ if (!this.root) {
728
+ return;
729
+ }
730
+
731
+ if (shouldUpdateDirtyObjects) {
732
+ this.updateDirtyObjects();
733
+ }
734
+
735
+ // aabb cast is done by the following method
736
+ // 1. shrink the shape aabb by its own extents down to a point
737
+ // 2. expand the block aabb by the extents of the shape aabb
738
+ // 3. cast the point by the displacement against the expanded block aabb (raycast vs aabb test)
739
+
740
+ bounds.computeCentroid(ray.origin);
741
+ ray.direction.normalizeVector(displacement);
742
+ ray.length = displacement.length();
743
+
744
+ bounds.computeHalfExtents(halfExtents);
745
+
746
+ const stack = this.intersectStack;
747
+ stack.length = 0;
748
+ stack.push(this.root);
749
+
750
+ while (stack.length > 0) {
751
+ const node = stack.pop();
752
+
753
+ if (!node) {
754
+ continue;
755
+ }
756
+
757
+ // skip branch if the node's expanded aabb does not intersect with the shape's ray
758
+ nodeBounds.copy(node.computedBounds);
759
+ nodeBounds.expandByVector(halfExtents);
760
+ if (ray.intersectsAabb(nodeBounds) === false) {
761
+ continue;
762
+ }
763
+
764
+ // if the node is not a leaf, push its children onto the stack and continue
765
+ if (!node.isLeaf()) {
766
+ stack.push(node.left!);
767
+ stack.push(node.right!);
768
+ continue;
769
+ }
770
+
771
+ // if the node is a leaf, check its objects
772
+ for (const body of node.objects) {
773
+ if (
774
+ shouldPairCollide(body.belongsToGroups, body.collidesWithGroups, belongsToGroups, collidesWithGroups) ===
775
+ false
776
+ ) {
777
+ continue;
778
+ }
779
+
780
+ // expand the object aabb by the shape's aabb
781
+ // skip if bounds do not collide
782
+ bodyBounds.copy(body.computedBounds);
783
+ bodyBounds.expandByVector(halfExtents);
784
+ if (ray.intersectsAabb(bodyBounds) === false) {
785
+ continue;
786
+ }
787
+
788
+ // visit the body
789
+ bodyVisitor.visit(body);
790
+
791
+ // break if visitor requests exit
792
+ if (bodyVisitor.shouldExit) {
793
+ return;
794
+ }
795
+ }
796
+ }
797
+ }
798
+ }
799
+
800
+ export interface BodyVisitor {
801
+ shouldExit: boolean;
802
+ visit(body: Body): void;
803
+ }