@perplexdotgg/bounce 1.0.0 → 1.0.2
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.
- package/build/bounce.d.ts +39501 -0
- package/build/bounce.js +17166 -0
- package/package.json +1 -1
- package/src/builders/ConvexHullBuilder.ts +0 -437
- package/src/builders/ConvexHullBuilder2d.ts +0 -344
- package/src/builders/ConvexHullBuilder3d.ts +0 -1689
- package/src/builders/HeightMapBuilder.ts +0 -414
- package/src/builders/TriangleMeshBuilder.ts +0 -92
- package/src/collision/CastShapesModule.ts +0 -184
- package/src/collision/CollideShapesModule.ts +0 -152
- package/src/collision/HeightMapCaster.ts +0 -38
- package/src/collision/HeightMapCollider.ts +0 -33
- package/src/collision/TriangleCaster.ts +0 -249
- package/src/collision/TriangleCollider.ts +0 -308
- package/src/collision/TriangleCollider2.ts +0 -379
- package/src/collision/activeEdge.ts +0 -146
- package/src/collision/cast/cast.ts +0 -139
- package/src/collision/cast/castCompoundVsCompound.ts +0 -59
- package/src/collision/cast/castCompoundVsConvex.ts +0 -116
- package/src/collision/cast/castConvexVsCompound.ts +0 -123
- package/src/collision/cast/castConvexVsConvex.ts +0 -213
- package/src/collision/cast/castConvexVsHeightMap.ts +0 -73
- package/src/collision/cast/castConvexVsTriangleMesh.ts +0 -56
- package/src/collision/cast/castRayVsCompound.ts +0 -44
- package/src/collision/cast/castRayVsConvex.ts +0 -45
- package/src/collision/cast/castRayVsHeightMap.ts +0 -58
- package/src/collision/cast/castRayVsTriangleMesh.ts +0 -58
- package/src/collision/closestPoints/closestPoints.ts +0 -23
- package/src/collision/closestPoints/computeBarycentricCoordinates2d.ts +0 -32
- package/src/collision/closestPoints/computeBarycentricCoordinates3d.ts +0 -81
- package/src/collision/closestPoints/computeClosestPointOnLine.ts +0 -30
- package/src/collision/closestPoints/computeClosestPointOnTetrahedron.ts +0 -96
- package/src/collision/closestPoints/computeClosestPointOnTriangle.ts +0 -195
- package/src/collision/closestPoints/isOriginOutsideOfPlane.ts +0 -25
- package/src/collision/closestPoints/isOriginOutsideOfTrianglePlanes.ts +0 -72
- package/src/collision/collide/collide.ts +0 -146
- package/src/collision/collide/collideCompoundVsCompound.ts +0 -60
- package/src/collision/collide/collideCompoundVsConvex.ts +0 -59
- package/src/collision/collide/collideCompoundVsHeightMap.ts +0 -73
- package/src/collision/collide/collideCompoundVsTriangleMesh.ts +0 -56
- package/src/collision/collide/collideConvexVsCompound.ts +0 -57
- package/src/collision/collide/collideConvexVsConvex.ts +0 -225
- package/src/collision/collide/collideConvexVsConvexImp.ts +0 -236
- package/src/collision/collide/collideConvexVsHeightMap.ts +0 -53
- package/src/collision/collide/collideConvexVsTriangleMesh.ts +0 -58
- package/src/collision/collide/collideHeightMapVsCompound.ts +0 -69
- package/src/collision/collide/collideHeightMapVsConvex.ts +0 -53
- package/src/collision/collide/collideSphereVsSphere.ts +0 -81
- package/src/collision/collide/collideTriangleMeshVsCompound.ts +0 -58
- package/src/collision/collide/collideTriangleMeshVsConvex.ts +0 -58
- package/src/collision/epa/EpaConvexHullBuilder.ts +0 -397
- package/src/collision/epa/StaticArray.ts +0 -154
- package/src/collision/epa/TriangleFactory.ts +0 -32
- package/src/collision/epa/arrays.ts +0 -99
- package/src/collision/epa/binaryHeap.ts +0 -82
- package/src/collision/epa/structs.ts +0 -227
- package/src/collision/gjk/GjkModule.ts +0 -864
- package/src/collision/gjk/PenetrationDepthModule.ts +0 -493
- package/src/collision/gjk/SupportPoints.ts +0 -50
- package/src/collision/imp/MinkowskiDifference.ts +0 -36
- package/src/collision/imp/computeExploredDistanceLowerUpperBound.ts +0 -40
- package/src/collision/imp/finalizeImpResult.ts +0 -69
- package/src/collision/imp/findContactImp.ts +0 -196
- package/src/collision/imp/imp.ts +0 -28
- package/src/collision/imp/incrementalMinimumDistanceExploreDirection.ts +0 -207
- package/src/collision/mpr/findPortal.ts +0 -152
- package/src/collision/mpr/mpr.ts +0 -29
- package/src/collision/mpr/updatePortal.ts +0 -52
- package/src/constraints/BaseConstraint.ts +0 -50
- package/src/constraints/ConstraintOptions.ts +0 -22
- package/src/constraints/ConstraintSolver.ts +0 -119
- package/src/constraints/DistanceConstraint.ts +0 -229
- package/src/constraints/FixedConstraint.ts +0 -203
- package/src/constraints/HingeConstraint.ts +0 -460
- package/src/constraints/PointConstraint.ts +0 -108
- package/src/constraints/components/AngleComponent.ts +0 -226
- package/src/constraints/components/AxisComponent.ts +0 -263
- package/src/constraints/components/HingeComponent.ts +0 -215
- package/src/constraints/components/Motor.ts +0 -36
- package/src/constraints/components/PointConstraintComponent.ts +0 -179
- package/src/constraints/components/RotationEulerComponent.ts +0 -139
- package/src/constraints/components/Spring.ts +0 -30
- package/src/constraints/components/SpringComponent.ts +0 -71
- package/src/constraints/types.ts +0 -6
- package/src/helpers.ts +0 -147
- package/src/index.ts +0 -50
- package/src/math/BasicTransform.ts +0 -19
- package/src/math/NumberValue.ts +0 -13
- package/src/math/isometry.ts +0 -64
- package/src/math/mat3.ts +0 -529
- package/src/math/mat4.ts +0 -588
- package/src/math/quat.ts +0 -193
- package/src/math/scalar.ts +0 -81
- package/src/math/tensor.ts +0 -17
- package/src/math/vec3.ts +0 -589
- package/src/math/vec4.ts +0 -10
- package/src/physics/Body.ts +0 -581
- package/src/physics/CollisionFilter.ts +0 -52
- package/src/physics/SleepModule.ts +0 -163
- package/src/physics/broadphase/BodyPairsModule.ts +0 -363
- package/src/physics/broadphase/BvhModule.ts +0 -237
- package/src/physics/broadphase/BvhTree.ts +0 -803
- package/src/physics/broadphase/ConstraintPairsModule.ts +0 -385
- package/src/physics/broadphase/TriangleMeshBvhTree.ts +0 -379
- package/src/physics/manifold/ContactManifold.ts +0 -227
- package/src/physics/manifold/ContactManifoldModule.ts +0 -623
- package/src/physics/manifold/Face.ts +0 -119
- package/src/physics/manifold/ManifoldCache.ts +0 -116
- package/src/physics/manifold/clipping/clipPolyVsEdge.ts +0 -131
- package/src/physics/manifold/clipping/clipPolyVsPlane.ts +0 -73
- package/src/physics/manifold/clipping/clipPolyVsPoly.ts +0 -72
- package/src/physics/narrowphase/CollideBodiesModule.ts +0 -755
- package/src/physics/solver/ContactConstraintModule.ts +0 -659
- package/src/physics/solver/ManifoldConstraint.ts +0 -420
- package/src/physics/solver/estimateCollisionResponse.ts +0 -146
- package/src/shape/Aabb.ts +0 -400
- package/src/shape/Box.ts +0 -231
- package/src/shape/Capsule.ts +0 -332
- package/src/shape/CompoundShape.ts +0 -288
- package/src/shape/Convex.ts +0 -130
- package/src/shape/ConvexHull.ts +0 -423
- package/src/shape/Cylinder.ts +0 -313
- package/src/shape/HeightMap.ts +0 -511
- package/src/shape/Line.ts +0 -14
- package/src/shape/Plane.ts +0 -116
- package/src/shape/Ray.ts +0 -81
- package/src/shape/Segment.ts +0 -25
- package/src/shape/Shape.ts +0 -77
- package/src/shape/Sphere.ts +0 -181
- package/src/shape/TransformedShape.ts +0 -51
- package/src/shape/Triangle.ts +0 -122
- package/src/shape/TriangleMesh.ts +0 -186
- package/src/types.ts +0 -1
- package/src/world.ts +0 -1335
- package/tests/BodyPairsModule.test.ts +0 -71
- package/tests/BvhTree.test.ts +0 -406
- package/tests/test.md +0 -642
- package/tests/vec3.test.ts +0 -12
|
@@ -1,803 +0,0 @@
|
|
|
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
|
-
}
|