@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.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/package.json +32 -0
- package/src/builders/ConvexHullBuilder.ts +437 -0
- package/src/builders/ConvexHullBuilder2d.ts +344 -0
- package/src/builders/ConvexHullBuilder3d.ts +1689 -0
- package/src/builders/HeightMapBuilder.ts +414 -0
- package/src/builders/TriangleMeshBuilder.ts +92 -0
- package/src/collision/CastShapesModule.ts +184 -0
- package/src/collision/CollideShapesModule.ts +152 -0
- package/src/collision/HeightMapCaster.ts +38 -0
- package/src/collision/HeightMapCollider.ts +33 -0
- package/src/collision/TriangleCaster.ts +249 -0
- package/src/collision/TriangleCollider.ts +308 -0
- package/src/collision/TriangleCollider2.ts +379 -0
- package/src/collision/activeEdge.ts +146 -0
- package/src/collision/cast/cast.ts +139 -0
- package/src/collision/cast/castCompoundVsCompound.ts +59 -0
- package/src/collision/cast/castCompoundVsConvex.ts +116 -0
- package/src/collision/cast/castConvexVsCompound.ts +123 -0
- package/src/collision/cast/castConvexVsConvex.ts +213 -0
- package/src/collision/cast/castConvexVsHeightMap.ts +73 -0
- package/src/collision/cast/castConvexVsTriangleMesh.ts +56 -0
- package/src/collision/cast/castRayVsCompound.ts +44 -0
- package/src/collision/cast/castRayVsConvex.ts +45 -0
- package/src/collision/cast/castRayVsHeightMap.ts +58 -0
- package/src/collision/cast/castRayVsTriangleMesh.ts +58 -0
- package/src/collision/closestPoints/closestPoints.ts +23 -0
- package/src/collision/closestPoints/computeBarycentricCoordinates2d.ts +32 -0
- package/src/collision/closestPoints/computeBarycentricCoordinates3d.ts +81 -0
- package/src/collision/closestPoints/computeClosestPointOnLine.ts +30 -0
- package/src/collision/closestPoints/computeClosestPointOnTetrahedron.ts +96 -0
- package/src/collision/closestPoints/computeClosestPointOnTriangle.ts +195 -0
- package/src/collision/closestPoints/isOriginOutsideOfPlane.ts +25 -0
- package/src/collision/closestPoints/isOriginOutsideOfTrianglePlanes.ts +72 -0
- package/src/collision/collide/collide.ts +146 -0
- package/src/collision/collide/collideCompoundVsCompound.ts +60 -0
- package/src/collision/collide/collideCompoundVsConvex.ts +59 -0
- package/src/collision/collide/collideCompoundVsHeightMap.ts +73 -0
- package/src/collision/collide/collideCompoundVsTriangleMesh.ts +56 -0
- package/src/collision/collide/collideConvexVsCompound.ts +57 -0
- package/src/collision/collide/collideConvexVsConvex.ts +225 -0
- package/src/collision/collide/collideConvexVsConvexImp.ts +236 -0
- package/src/collision/collide/collideConvexVsHeightMap.ts +53 -0
- package/src/collision/collide/collideConvexVsTriangleMesh.ts +58 -0
- package/src/collision/collide/collideHeightMapVsCompound.ts +69 -0
- package/src/collision/collide/collideHeightMapVsConvex.ts +53 -0
- package/src/collision/collide/collideSphereVsSphere.ts +81 -0
- package/src/collision/collide/collideTriangleMeshVsCompound.ts +58 -0
- package/src/collision/collide/collideTriangleMeshVsConvex.ts +58 -0
- package/src/collision/epa/EpaConvexHullBuilder.ts +397 -0
- package/src/collision/epa/StaticArray.ts +154 -0
- package/src/collision/epa/TriangleFactory.ts +32 -0
- package/src/collision/epa/arrays.ts +99 -0
- package/src/collision/epa/binaryHeap.ts +82 -0
- package/src/collision/epa/structs.ts +227 -0
- package/src/collision/gjk/GjkModule.ts +864 -0
- package/src/collision/gjk/PenetrationDepthModule.ts +493 -0
- package/src/collision/gjk/SupportPoints.ts +50 -0
- package/src/collision/imp/MinkowskiDifference.ts +36 -0
- package/src/collision/imp/computeExploredDistanceLowerUpperBound.ts +40 -0
- package/src/collision/imp/finalizeImpResult.ts +69 -0
- package/src/collision/imp/findContactImp.ts +196 -0
- package/src/collision/imp/imp.ts +28 -0
- package/src/collision/imp/incrementalMinimumDistanceExploreDirection.ts +207 -0
- package/src/collision/mpr/findPortal.ts +152 -0
- package/src/collision/mpr/mpr.ts +29 -0
- package/src/collision/mpr/updatePortal.ts +52 -0
- package/src/constraints/BaseConstraint.ts +50 -0
- package/src/constraints/ConstraintOptions.ts +22 -0
- package/src/constraints/ConstraintSolver.ts +119 -0
- package/src/constraints/DistanceConstraint.ts +229 -0
- package/src/constraints/FixedConstraint.ts +203 -0
- package/src/constraints/HingeConstraint.ts +460 -0
- package/src/constraints/PointConstraint.ts +108 -0
- package/src/constraints/components/AngleComponent.ts +226 -0
- package/src/constraints/components/AxisComponent.ts +263 -0
- package/src/constraints/components/HingeComponent.ts +215 -0
- package/src/constraints/components/Motor.ts +36 -0
- package/src/constraints/components/PointConstraintComponent.ts +179 -0
- package/src/constraints/components/RotationEulerComponent.ts +139 -0
- package/src/constraints/components/Spring.ts +30 -0
- package/src/constraints/components/SpringComponent.ts +71 -0
- package/src/constraints/types.ts +6 -0
- package/src/helpers.ts +147 -0
- package/src/index.ts +50 -0
- package/src/math/BasicTransform.ts +19 -0
- package/src/math/NumberValue.ts +13 -0
- package/src/math/isometry.ts +64 -0
- package/src/math/mat3.ts +529 -0
- package/src/math/mat4.ts +588 -0
- package/src/math/quat.ts +193 -0
- package/src/math/scalar.ts +81 -0
- package/src/math/tensor.ts +17 -0
- package/src/math/vec3.ts +589 -0
- package/src/math/vec4.ts +10 -0
- package/src/physics/Body.ts +581 -0
- package/src/physics/CollisionFilter.ts +52 -0
- package/src/physics/SleepModule.ts +163 -0
- package/src/physics/broadphase/BodyPairsModule.ts +363 -0
- package/src/physics/broadphase/BvhModule.ts +237 -0
- package/src/physics/broadphase/BvhTree.ts +803 -0
- package/src/physics/broadphase/ConstraintPairsModule.ts +385 -0
- package/src/physics/broadphase/TriangleMeshBvhTree.ts +379 -0
- package/src/physics/manifold/ContactManifold.ts +227 -0
- package/src/physics/manifold/ContactManifoldModule.ts +623 -0
- package/src/physics/manifold/Face.ts +119 -0
- package/src/physics/manifold/ManifoldCache.ts +116 -0
- package/src/physics/manifold/clipping/clipPolyVsEdge.ts +131 -0
- package/src/physics/manifold/clipping/clipPolyVsPlane.ts +73 -0
- package/src/physics/manifold/clipping/clipPolyVsPoly.ts +72 -0
- package/src/physics/narrowphase/CollideBodiesModule.ts +755 -0
- package/src/physics/solver/ContactConstraintModule.ts +659 -0
- package/src/physics/solver/ManifoldConstraint.ts +420 -0
- package/src/physics/solver/estimateCollisionResponse.ts +146 -0
- package/src/shape/Aabb.ts +400 -0
- package/src/shape/Box.ts +231 -0
- package/src/shape/Capsule.ts +332 -0
- package/src/shape/CompoundShape.ts +288 -0
- package/src/shape/Convex.ts +130 -0
- package/src/shape/ConvexHull.ts +423 -0
- package/src/shape/Cylinder.ts +313 -0
- package/src/shape/HeightMap.ts +511 -0
- package/src/shape/Line.ts +14 -0
- package/src/shape/Plane.ts +116 -0
- package/src/shape/Ray.ts +81 -0
- package/src/shape/Segment.ts +25 -0
- package/src/shape/Shape.ts +77 -0
- package/src/shape/Sphere.ts +181 -0
- package/src/shape/TransformedShape.ts +51 -0
- package/src/shape/Triangle.ts +122 -0
- package/src/shape/TriangleMesh.ts +186 -0
- package/src/types.ts +1 -0
- package/src/world.ts +1335 -0
- package/tests/BodyPairsModule.test.ts +71 -0
- package/tests/BvhTree.test.ts +406 -0
- package/tests/test.md +642 -0
- package/tests/vec3.test.ts +12 -0
- package/tsconfig.json +20 -0
- package/vite.config.js +40 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { assert } from "../../helpers";
|
|
2
|
+
import { Vec3 } from "../../math/vec3";
|
|
3
|
+
import { TriangleFactory } from "./TriangleFactory";
|
|
4
|
+
import { Edges, NewTriangles, Points, TriangleQueue } from "./arrays";
|
|
5
|
+
import { Triangle } from "./structs";
|
|
6
|
+
|
|
7
|
+
interface StackEntry {
|
|
8
|
+
triangle: Triangle | null;
|
|
9
|
+
edge: number;
|
|
10
|
+
iter: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let iteration = 0;
|
|
14
|
+
|
|
15
|
+
export interface EpaConvexHullBuilderSettings {
|
|
16
|
+
// Due to the Euler characteristic (https://en.wikipedia.org/wiki/Euler_characteristic) we know that Vertices - Edges + Faces = 2
|
|
17
|
+
// In our case we only have triangles and they are always fully connected, so each edge is shared exactly between 2 faces: Edges = Faces * 3 / 2
|
|
18
|
+
// Substituting: Vertices = Faces / 2 + 2 which is approximately Faces / 2.
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Maximum number of triangles in the convex hull.
|
|
22
|
+
*/
|
|
23
|
+
maxTriangles: number;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maximum number of points in the convex hull.
|
|
27
|
+
*/
|
|
28
|
+
maxPoints: number;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Maximum edge length to consider when finding edges.
|
|
32
|
+
*/
|
|
33
|
+
maxEdgeLength: number;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Minimum area of a triangle before it is added to the priority queue.
|
|
37
|
+
*/
|
|
38
|
+
minTriangleArea: number;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Epsilon value used to determine if a point is in the interior of a triangle.
|
|
42
|
+
*/
|
|
43
|
+
barycentricEpsilon: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Link triangle edge to other triangle edge
|
|
47
|
+
function linkTriangle(inT1: Triangle, inEdge1: number, inT2: Triangle, inEdge2: number) {
|
|
48
|
+
// #v-ifdef DEV
|
|
49
|
+
assert(inEdge1 >= 0 && inEdge1 < 3, "Edge index 1 out of bounds");
|
|
50
|
+
assert(inEdge2 >= 0 && inEdge2 < 3, "Edge index 2 out of bounds");
|
|
51
|
+
// #v-endif
|
|
52
|
+
|
|
53
|
+
const e1 = inT1.edge[inEdge1];
|
|
54
|
+
const e2 = inT2.edge[inEdge2];
|
|
55
|
+
|
|
56
|
+
// #v-ifdef DEV
|
|
57
|
+
// Check not connected yet
|
|
58
|
+
assert(e1.neighbourTriangle === null, "Edge 1 already has a neighbour triangle");
|
|
59
|
+
assert(e2.neighbourTriangle === null, "Edge 2 already has a neighbour triangle");
|
|
60
|
+
// #v-endif
|
|
61
|
+
|
|
62
|
+
// #v-ifdef DEV
|
|
63
|
+
// Check vertices match
|
|
64
|
+
assert(
|
|
65
|
+
e1.startIndex === inT2.getNextEdge(inEdge2).startIndex,
|
|
66
|
+
"Edge 1 start index does not match Edge 2 next edge start index"
|
|
67
|
+
);
|
|
68
|
+
assert(
|
|
69
|
+
e2.startIndex === inT1.getNextEdge(inEdge1).startIndex,
|
|
70
|
+
"Edge 2 start index does not match Edge 1 next edge start index"
|
|
71
|
+
);
|
|
72
|
+
// #v-endif
|
|
73
|
+
|
|
74
|
+
// Link up
|
|
75
|
+
e1.neighbourTriangle = inT2;
|
|
76
|
+
e1.neighbourEdge = inEdge2;
|
|
77
|
+
e2.neighbourTriangle = inT1;
|
|
78
|
+
e2.neighbourEdge = inEdge1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class EpaConvexHullBuilder {
|
|
82
|
+
settings: EpaConvexHullBuilderSettings;
|
|
83
|
+
triangleFactory: TriangleFactory;
|
|
84
|
+
triangleQueue: TriangleQueue;
|
|
85
|
+
positions: Points | null;
|
|
86
|
+
stack: Array<StackEntry>;
|
|
87
|
+
edges: Edges;
|
|
88
|
+
|
|
89
|
+
static createDefaultSettings(): EpaConvexHullBuilderSettings {
|
|
90
|
+
return {
|
|
91
|
+
maxTriangles: 256,
|
|
92
|
+
maxPoints: 256 / 2,
|
|
93
|
+
maxEdgeLength: 128,
|
|
94
|
+
minTriangleArea: 1e-10,
|
|
95
|
+
barycentricEpsilon: 1e-3,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
constructor(settings: Partial<EpaConvexHullBuilderSettings> = {}) {
|
|
100
|
+
this.settings = {
|
|
101
|
+
...EpaConvexHullBuilder.createDefaultSettings(),
|
|
102
|
+
...settings,
|
|
103
|
+
};
|
|
104
|
+
this.triangleFactory = new TriangleFactory(
|
|
105
|
+
this.settings.maxTriangles,
|
|
106
|
+
this.settings.minTriangleArea,
|
|
107
|
+
this.settings.barycentricEpsilon
|
|
108
|
+
);
|
|
109
|
+
this.triangleQueue = new TriangleQueue(this.settings.maxTriangles);
|
|
110
|
+
this.positions = null;
|
|
111
|
+
|
|
112
|
+
const stack = new Array<StackEntry>(this.settings.maxEdgeLength);
|
|
113
|
+
for (let i = 0; i < this.settings.maxEdgeLength; ++i) {
|
|
114
|
+
stack[i] = { triangle: null, edge: 0, iter: -1 };
|
|
115
|
+
}
|
|
116
|
+
this.stack = stack;
|
|
117
|
+
|
|
118
|
+
this.edges = new Edges(this.settings.maxEdgeLength);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Create a new triangle
|
|
122
|
+
createTriangle(inIdx1: number, inIdx2: number, inIdx3: number) {
|
|
123
|
+
// Call provider to create triangle
|
|
124
|
+
|
|
125
|
+
const t = this.triangleFactory.createTriangle(inIdx1, inIdx2, inIdx3, this.positions!.data());
|
|
126
|
+
if (!t) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return t;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Initialize the hull with 3 points
|
|
134
|
+
initialize(inIdx1: number, inIdx2: number, inIdx3: number) {
|
|
135
|
+
// console.log(iteration);
|
|
136
|
+
// if (iteration === 289) {
|
|
137
|
+
// debugger;
|
|
138
|
+
// }
|
|
139
|
+
// iteration++;
|
|
140
|
+
// Release triangles
|
|
141
|
+
this.triangleFactory.clear();
|
|
142
|
+
this.triangleQueue.clear();
|
|
143
|
+
|
|
144
|
+
// Create triangles (back to back)
|
|
145
|
+
const t1 = this.createTriangle(inIdx1, inIdx2, inIdx3);
|
|
146
|
+
const t2 = this.createTriangle(inIdx1, inIdx3, inIdx2);
|
|
147
|
+
|
|
148
|
+
// #v-ifdef DEV
|
|
149
|
+
// assert
|
|
150
|
+
if (!t1 || !t2) {
|
|
151
|
+
throw new Error("Failed to create triangles");
|
|
152
|
+
}
|
|
153
|
+
// #v-endif
|
|
154
|
+
|
|
155
|
+
// Link triangles edges
|
|
156
|
+
linkTriangle(t1, 0, t2, 2);
|
|
157
|
+
linkTriangle(t1, 1, t2, 1);
|
|
158
|
+
linkTriangle(t1, 2, t2, 0);
|
|
159
|
+
// debugger;
|
|
160
|
+
|
|
161
|
+
// Always add both triangles to the priority queue
|
|
162
|
+
this.triangleQueue.pushBack(t1);
|
|
163
|
+
this.triangleQueue.pushBack(t2);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check if there's another triangle to process from the queue
|
|
167
|
+
hasNextTriangle() {
|
|
168
|
+
return !this.triangleQueue.empty();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Access to the next closest triangle to the origin (won't remove it from the queue).
|
|
172
|
+
peekClosestTriangleInQueue() {
|
|
173
|
+
return this.triangleQueue.peekClosest();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Access to the next closest triangle to the origin and remove it from the queue.
|
|
177
|
+
popClosestTriangleFromQueue() {
|
|
178
|
+
return this.triangleQueue.popClosest();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Find the triangle on which inPosition is the furthest to the front
|
|
182
|
+
// Note this function works as long as all points added have been added with AddPoint(..., FLT_MAX).
|
|
183
|
+
findFacingTriangle(inPosition: Vec3, outBestDistSq: { value: number }) {
|
|
184
|
+
let best: Triangle | null = null;
|
|
185
|
+
let bestDistSq = 0.0;
|
|
186
|
+
|
|
187
|
+
for (const t of this.triangleQueue) {
|
|
188
|
+
// #v-ifdef DEV
|
|
189
|
+
// assert
|
|
190
|
+
if (!t) {
|
|
191
|
+
throw new Error("Triangle is null in triangle queue");
|
|
192
|
+
}
|
|
193
|
+
// #v-endif
|
|
194
|
+
|
|
195
|
+
if (!t.removed) {
|
|
196
|
+
vectorAB.subtractVectors(inPosition, t.centroid);
|
|
197
|
+
const dot = t.normal.dot(vectorAB);
|
|
198
|
+
if (dot > 0.0) {
|
|
199
|
+
const distSq = (dot * dot) / t.normal.squaredLength();
|
|
200
|
+
if (distSq > bestDistSq) {
|
|
201
|
+
best = t;
|
|
202
|
+
bestDistSq = distSq;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
outBestDistSq.value = bestDistSq;
|
|
209
|
+
return best;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Free a triangle
|
|
213
|
+
freeTriangle(inT: Triangle) {
|
|
214
|
+
// #v-ifdef DEV
|
|
215
|
+
assert(inT.removed, "Triangle is not removed, cannot free it");
|
|
216
|
+
for (const edge of inT.edge) {
|
|
217
|
+
assert(edge.neighbourTriangle === null, "Edge still has a neighbour triangle when freeing triangle");
|
|
218
|
+
}
|
|
219
|
+
// #v-endif
|
|
220
|
+
|
|
221
|
+
this.triangleFactory.freeTriangle(inT);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Unlink this triangle
|
|
225
|
+
unlinkTriangle(inT: Triangle) {
|
|
226
|
+
// Unlink from neighbours
|
|
227
|
+
for (let i = 0; i < 3; ++i) {
|
|
228
|
+
const edge = inT.edge[i];
|
|
229
|
+
if (edge.neighbourTriangle !== null) {
|
|
230
|
+
const neighbourEdge = edge.neighbourTriangle.edge[edge.neighbourEdge];
|
|
231
|
+
|
|
232
|
+
// #v-ifdef DEV
|
|
233
|
+
// Validate that neighbour points to us
|
|
234
|
+
assert(neighbourEdge.neighbourTriangle === inT, "Neighbour edge does not point to this triangle");
|
|
235
|
+
assert(neighbourEdge.neighbourEdge === i, "Neighbour edge does not point to this triangle edge");
|
|
236
|
+
// #v-endif
|
|
237
|
+
|
|
238
|
+
// Unlink
|
|
239
|
+
neighbourEdge.neighbourTriangle = null;
|
|
240
|
+
edge.neighbourTriangle = null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// If this triangle is not in the priority queue, we can delete it now
|
|
245
|
+
if (!inT.inQueue) {
|
|
246
|
+
this.freeTriangle(inT);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Given one triangle that faces inVertex, find the edges of the triangles that are not facing inVertex.
|
|
251
|
+
// Will flag all those triangles for removal.
|
|
252
|
+
findEdge(inFacingTriangle: Triangle, inVertex: Vec3, outEdges: Edges) {
|
|
253
|
+
// #v-ifdef DEV
|
|
254
|
+
// Assert that we were given an empty array
|
|
255
|
+
assert(outEdges.empty(), "Output edges array is not empty");
|
|
256
|
+
// #v-endif
|
|
257
|
+
|
|
258
|
+
// #v-ifdef DEV
|
|
259
|
+
// Should start with a facing triangle
|
|
260
|
+
assert(inFacingTriangle.isFacing(inVertex), "Facing triangle does not face the vertex");
|
|
261
|
+
// #v-endif
|
|
262
|
+
|
|
263
|
+
// Flag as removed
|
|
264
|
+
inFacingTriangle.removed = true;
|
|
265
|
+
|
|
266
|
+
// Instead of recursing, we build our own stack with the information we need
|
|
267
|
+
const stack: StackEntry[] = this.stack;
|
|
268
|
+
let curStackPos = 0;
|
|
269
|
+
|
|
270
|
+
// Start with the triangle / edge provided
|
|
271
|
+
stack[0].triangle = inFacingTriangle;
|
|
272
|
+
stack[0].edge = 0;
|
|
273
|
+
stack[0].iter = -1; // Start with edge 0 (is incremented below before use)
|
|
274
|
+
|
|
275
|
+
// Next index that we expect to find, if we don't then there are 'islands'
|
|
276
|
+
let nextExpectedStartIdx = -1;
|
|
277
|
+
|
|
278
|
+
while (true) {
|
|
279
|
+
// Get current stack entry
|
|
280
|
+
const curEntry = stack[curStackPos];
|
|
281
|
+
|
|
282
|
+
// Next iteration
|
|
283
|
+
if (++curEntry.iter >= 3) {
|
|
284
|
+
// This triangle needs to be removed, unlink it now
|
|
285
|
+
this.unlinkTriangle(curEntry.triangle!);
|
|
286
|
+
|
|
287
|
+
// Pop from stack
|
|
288
|
+
if (--curStackPos < 0) {
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
// Visit neighbour
|
|
293
|
+
const e = curEntry.triangle!.edge[(curEntry.edge + curEntry.iter) % 3];
|
|
294
|
+
const n = e.neighbourTriangle;
|
|
295
|
+
if (n !== null && !n.removed) {
|
|
296
|
+
// Check if vertex is on the front side of this triangle
|
|
297
|
+
if (n.isFacing(inVertex)) {
|
|
298
|
+
// Vertex on front, this triangle needs to be removed
|
|
299
|
+
n.removed = true;
|
|
300
|
+
|
|
301
|
+
// Add element to the stack of elements to visit
|
|
302
|
+
curStackPos++;
|
|
303
|
+
// #v-ifdef DEV
|
|
304
|
+
assert(curStackPos < this.settings.maxEdgeLength, "Stack overflow in findEdge");
|
|
305
|
+
// #v-endif
|
|
306
|
+
const newEntry = stack[curStackPos];
|
|
307
|
+
newEntry.triangle = n;
|
|
308
|
+
newEntry.edge = e.neighbourEdge;
|
|
309
|
+
newEntry.iter = 0; // Is incremented before use, we don't need to test this edge again since we came from it
|
|
310
|
+
} else {
|
|
311
|
+
// Detect if edge doesn't connect to previous edge, if this happens we have found and 'island' which means
|
|
312
|
+
// the newly added point is so close to the triangles of the hull that we classified some (nearly) coplanar
|
|
313
|
+
// triangles as before and some behind the point. At this point we just abort adding the point because
|
|
314
|
+
// we've reached numerical precision.
|
|
315
|
+
// Note that we do not need to test if the first and last edge connect, since when there are islands
|
|
316
|
+
// there should be at least 2 disconnects.
|
|
317
|
+
if (e.startIndex !== nextExpectedStartIdx && nextExpectedStartIdx !== -1) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Next expected index is the start index of our neighbour's edge
|
|
322
|
+
nextExpectedStartIdx = n.edge[e.neighbourEdge].startIndex;
|
|
323
|
+
|
|
324
|
+
// Vertex behind, keep edge
|
|
325
|
+
outEdges.pushBackCopy(e);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// debugger;
|
|
332
|
+
// console.log(outEdges.front()!.startIndex, nextExpectedStartIdx, outEdges.size());
|
|
333
|
+
// #v-ifdef DEV
|
|
334
|
+
// Assert that we have a fully connected loop
|
|
335
|
+
assert(
|
|
336
|
+
outEdges.empty() || outEdges.front()!.startIndex === nextExpectedStartIdx,
|
|
337
|
+
"Output edges do not form a fully connected loop"
|
|
338
|
+
);
|
|
339
|
+
// #v-endif
|
|
340
|
+
|
|
341
|
+
// When we start with two triangles facing away from each other and adding a point that is on the plane,
|
|
342
|
+
// sometimes we consider the point in front of both causing both triangles to be removed resulting in an empty edge list.
|
|
343
|
+
// In this case we fail to add the point which will result in no collision reported (the shapes are contacting in 1 point so there's 0 penetration)
|
|
344
|
+
return outEdges.size() >= 3;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Add a new point to the convex hull
|
|
348
|
+
addPoint(inFacingTriangle: Triangle, inIdx: number, inClosestDistSq: number, outTriangles: NewTriangles) {
|
|
349
|
+
// #v-ifdef DEV
|
|
350
|
+
if (!this.positions) {
|
|
351
|
+
throw new Error("Positions array is not initialized");
|
|
352
|
+
}
|
|
353
|
+
// #v-endif
|
|
354
|
+
|
|
355
|
+
// Get position
|
|
356
|
+
const pos = this.positions.values[inIdx];
|
|
357
|
+
|
|
358
|
+
// Find edge of convex hull of triangles that are not facing the new vertex
|
|
359
|
+
const edges = this.edges;
|
|
360
|
+
edges.clear();
|
|
361
|
+
|
|
362
|
+
if (!this.findEdge(inFacingTriangle, pos, edges)) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Create new triangles
|
|
367
|
+
const numEdges = edges.size();
|
|
368
|
+
for (let i = 0; i < numEdges; ++i) {
|
|
369
|
+
// Create new triangle
|
|
370
|
+
const nt = this.createTriangle(edges.values[i].startIndex, edges.values[(i + 1) % numEdges].startIndex, inIdx);
|
|
371
|
+
|
|
372
|
+
if (!nt) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
outTriangles.pushBack(nt);
|
|
377
|
+
|
|
378
|
+
// Check if we need to put this triangle in the priority queue
|
|
379
|
+
if (
|
|
380
|
+
(nt.closestPointInterior && nt.closestLengthSq < inClosestDistSq) || // For the main algorithm
|
|
381
|
+
nt.closestLengthSq < 0.0 // For when the origin is not inside the hull yet
|
|
382
|
+
) {
|
|
383
|
+
this.triangleQueue.pushBack(nt);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Link edges
|
|
388
|
+
for (let i = 0; i < numEdges; ++i) {
|
|
389
|
+
linkTriangle(outTriangles.values[i]!, 0, edges.values[i].neighbourTriangle!, edges.values[i].neighbourEdge);
|
|
390
|
+
linkTriangle(outTriangles.values[i]!, 1, outTriangles.values[(i + 1) % numEdges]!, 2);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const vectorAB = /*@__PURE__*/ Vec3.create();
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
interface Copyable<T> {
|
|
2
|
+
copy(value: T): void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export class StaticArray<T extends Copyable<any>> {
|
|
6
|
+
values: T[];
|
|
7
|
+
_size: number;
|
|
8
|
+
|
|
9
|
+
constructor(capacity: number) {
|
|
10
|
+
this.values = new Array(capacity).fill(null);
|
|
11
|
+
this._size = 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// initialize(values: T[]) {
|
|
15
|
+
// if (values.length > this.values.length) {
|
|
16
|
+
// throw new Error("values array exceeds static array capacity");
|
|
17
|
+
// }
|
|
18
|
+
|
|
19
|
+
// this._size = Math.min(values.length, this.values.length);
|
|
20
|
+
// for (let i = 0; i < this._size; i++) {
|
|
21
|
+
// this.values[i] = values[i];
|
|
22
|
+
// }
|
|
23
|
+
// return this;
|
|
24
|
+
// }
|
|
25
|
+
|
|
26
|
+
clear() {
|
|
27
|
+
this._size = 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* returns the size of the static array.
|
|
32
|
+
*/
|
|
33
|
+
size() {
|
|
34
|
+
return this._size;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* returns whether the static array is empty.
|
|
39
|
+
*/
|
|
40
|
+
empty() {
|
|
41
|
+
return this._size === 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* returns the first element of the static array.
|
|
46
|
+
*/
|
|
47
|
+
front() {
|
|
48
|
+
if (this._size === 0) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return this.values[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* returns the last element of the static array.
|
|
56
|
+
*/
|
|
57
|
+
back() {
|
|
58
|
+
if (this._size === 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return this.values[this._size - 1];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* returns the beginning index of the static array.
|
|
66
|
+
*/
|
|
67
|
+
begin() {
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* returns the end index of the static array.
|
|
73
|
+
*/
|
|
74
|
+
end() {
|
|
75
|
+
return this._size;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* returns the actual array of the static array.
|
|
80
|
+
*/
|
|
81
|
+
data() {
|
|
82
|
+
return this.values;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Pushes a value to the end of the static array.
|
|
87
|
+
*/
|
|
88
|
+
pushBack(value: T) {
|
|
89
|
+
if (this._size >= this.values.length) {
|
|
90
|
+
debugger;
|
|
91
|
+
throw new Error("cannot push to a full array");
|
|
92
|
+
}
|
|
93
|
+
this.values[this._size++] = value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pushBackCopy(value: T) {
|
|
97
|
+
if (this._size >= this.values.length) {
|
|
98
|
+
throw new Error("cannot push to a full array");
|
|
99
|
+
}
|
|
100
|
+
// Assuming T is a type that can be copied
|
|
101
|
+
this.values[this._size++].copy(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Removes the last value from the static array and returns it.
|
|
106
|
+
*/
|
|
107
|
+
popBack() {
|
|
108
|
+
if (this._size === 0) {
|
|
109
|
+
throw new Error("cannot pop from an empty array");
|
|
110
|
+
}
|
|
111
|
+
const value = this.values[--this._size];
|
|
112
|
+
// this.values[this._size] = null; // clear the slot
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
popBackCopy() {
|
|
117
|
+
if (this._size === 0) {
|
|
118
|
+
throw new Error("cannot pop from an empty array");
|
|
119
|
+
}
|
|
120
|
+
const value = this.values[--this._size];
|
|
121
|
+
// this.values[this._size] = null; // clear the slot
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
at(index: number) {
|
|
126
|
+
if (index >= this._size) {
|
|
127
|
+
throw new Error("index out of range");
|
|
128
|
+
}
|
|
129
|
+
return this.values[index] as T;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
set(index: number, value: T) {
|
|
133
|
+
if (index >= this._size) {
|
|
134
|
+
throw new Error("index out of range");
|
|
135
|
+
}
|
|
136
|
+
this.values[index] = value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
[Symbol.iterator](): Iterator<T> {
|
|
140
|
+
let index = 0;
|
|
141
|
+
const size = this._size;
|
|
142
|
+
const values = this.values;
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
next(): IteratorResult<T> {
|
|
146
|
+
if (index < size) {
|
|
147
|
+
return { value: values[index++] as T, done: false };
|
|
148
|
+
} else {
|
|
149
|
+
return { value: undefined, done: true };
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { initTriangle, Triangle } from "./structs";
|
|
2
|
+
import { ArrayPool } from "../../helpers";
|
|
3
|
+
import { Vec3 } from "../../math/vec3";
|
|
4
|
+
|
|
5
|
+
export class TriangleFactory {
|
|
6
|
+
pool: ArrayPool<Triangle>;
|
|
7
|
+
minTriangleArea: number;
|
|
8
|
+
barycentricEpsilon: number;
|
|
9
|
+
|
|
10
|
+
constructor(maxTriangles: number, minTriangleArea: number, barycentricEpsilon: number) {
|
|
11
|
+
this.pool = new ArrayPool(Triangle, maxTriangles);
|
|
12
|
+
this.minTriangleArea = minTriangleArea;
|
|
13
|
+
this.barycentricEpsilon = barycentricEpsilon;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
clear() {
|
|
17
|
+
this.pool.clear();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
createTriangle(inIdx0: number, inIdx1: number, inIdx2: number, inPositions: Vec3[]) {
|
|
21
|
+
const triangle = this.pool.create();
|
|
22
|
+
if (!triangle) {
|
|
23
|
+
return null; // Pool is exhausted
|
|
24
|
+
}
|
|
25
|
+
initTriangle(triangle, inIdx0, inIdx1, inIdx2, inPositions, this.minTriangleArea, this.barycentricEpsilon);
|
|
26
|
+
return triangle;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
freeTriangle(inT: Triangle) {
|
|
30
|
+
this.pool.remove(inT);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { PoolClass } from "monomorph";
|
|
2
|
+
import { Vec3 } from "../../math/vec3";
|
|
3
|
+
import { binaryHeapPop, binaryHeapPush } from "./binaryHeap";
|
|
4
|
+
import { StaticArray } from "./StaticArray";
|
|
5
|
+
import { Edge, Triangle } from "./structs";
|
|
6
|
+
|
|
7
|
+
class PointsBase extends StaticArray<Vec3> {
|
|
8
|
+
pool: PoolClass<Vec3>;
|
|
9
|
+
constructor(capacity: number) {
|
|
10
|
+
super(capacity);
|
|
11
|
+
this.pool = new Vec3.Pool(capacity);
|
|
12
|
+
for (let i = 0; i < capacity; i++) {
|
|
13
|
+
const instance = this.pool.create();
|
|
14
|
+
this.pushBack(instance);
|
|
15
|
+
}
|
|
16
|
+
this.clear();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class Points extends PointsBase {
|
|
21
|
+
getSizeRef() {
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class Edges extends StaticArray<Edge> {
|
|
27
|
+
pool: PoolClass<Edge>;
|
|
28
|
+
constructor(capacity: number) {
|
|
29
|
+
super(capacity);
|
|
30
|
+
this.pool = new Edge.Pool(capacity);
|
|
31
|
+
for (let i = 0; i < capacity; i++) {
|
|
32
|
+
const instance = this.pool.create();
|
|
33
|
+
this.pushBack(instance);
|
|
34
|
+
}
|
|
35
|
+
this.clear();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class NewTriangles extends StaticArray<Triangle> {
|
|
40
|
+
constructor(capacity: number) {
|
|
41
|
+
super(capacity);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class Triangles extends StaticArray<Triangle> {
|
|
46
|
+
constructor(capacity: number) {
|
|
47
|
+
super(capacity);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Specialized triangles list that keeps them sorted on closest distance to origin
|
|
53
|
+
*/
|
|
54
|
+
export class TriangleQueue extends Triangles {
|
|
55
|
+
constructor(capacity: number) {
|
|
56
|
+
super(capacity);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Function to sort triangles on closest distance to origin
|
|
61
|
+
*/
|
|
62
|
+
static triangleSorter(inT1: Triangle, inT2: Triangle) {
|
|
63
|
+
return inT1!.closestLengthSq > inT2!.closestLengthSq;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Add triangle to the list
|
|
68
|
+
*/
|
|
69
|
+
pushBack(inT: Triangle) {
|
|
70
|
+
// Add to base
|
|
71
|
+
super.pushBack(inT);
|
|
72
|
+
|
|
73
|
+
// Mark in queue
|
|
74
|
+
inT!.inQueue = true;
|
|
75
|
+
|
|
76
|
+
// Resort heap
|
|
77
|
+
binaryHeapPush(this.values, this.begin(), this.end(), TriangleQueue.triangleSorter);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Peek the next closest triangle without removing it
|
|
82
|
+
*/
|
|
83
|
+
peekClosest() {
|
|
84
|
+
return this.front();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get next closest triangle
|
|
89
|
+
*/
|
|
90
|
+
popClosest() {
|
|
91
|
+
// Move closest to end
|
|
92
|
+
binaryHeapPop(this.values, this.begin(), this.end(), TriangleQueue.triangleSorter);
|
|
93
|
+
|
|
94
|
+
// Remove last triangle
|
|
95
|
+
const t = this.back();
|
|
96
|
+
this.popBack();
|
|
97
|
+
return t;
|
|
98
|
+
}
|
|
99
|
+
}
|