@driftengine/physics 3.61.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 (136) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +503 -0
  4. package/dist/bodies.d.ts +121 -0
  5. package/dist/bodies.js +298 -0
  6. package/dist/buoyancy.d.ts +46 -0
  7. package/dist/buoyancy.js +45 -0
  8. package/dist/cloth.d.ts +226 -0
  9. package/dist/cloth.js +587 -0
  10. package/dist/collide/aabb.d.ts +10 -0
  11. package/dist/collide/aabb.js +11 -0
  12. package/dist/collide/body.d.ts +202 -0
  13. package/dist/collide/body.js +142 -0
  14. package/dist/collide/eject.d.ts +39 -0
  15. package/dist/collide/eject.js +142 -0
  16. package/dist/collide/index.d.ts +18 -0
  17. package/dist/collide/index.js +5 -0
  18. package/dist/collide/scratch.d.ts +8 -0
  19. package/dist/collide/scratch.js +9 -0
  20. package/dist/collide/segment.d.ts +12 -0
  21. package/dist/collide/segment.js +109 -0
  22. package/dist/collide/sweep.d.ts +3 -0
  23. package/dist/collide/sweep.js +1036 -0
  24. package/dist/colliderSet.d.ts +239 -0
  25. package/dist/colliderSet.js +546 -0
  26. package/dist/compoundContact.d.ts +70 -0
  27. package/dist/compoundContact.js +221 -0
  28. package/dist/controller.d.ts +351 -0
  29. package/dist/controller.js +641 -0
  30. package/dist/cylinder.d.ts +78 -0
  31. package/dist/cylinder.js +294 -0
  32. package/dist/cylinderContact.d.ts +7 -0
  33. package/dist/cylinderContact.js +704 -0
  34. package/dist/decompose.d.ts +105 -0
  35. package/dist/decompose.js +979 -0
  36. package/dist/events.d.ts +43 -0
  37. package/dist/events.js +95 -0
  38. package/dist/executor.d.ts +147 -0
  39. package/dist/executor.js +173 -0
  40. package/dist/faces.d.ts +26 -0
  41. package/dist/faces.js +34 -0
  42. package/dist/fingerprint.d.ts +61 -0
  43. package/dist/fingerprint.js +139 -0
  44. package/dist/groundMotor.d.ts +34 -0
  45. package/dist/groundMotor.js +41 -0
  46. package/dist/heightfieldShape.d.ts +128 -0
  47. package/dist/heightfieldShape.js +328 -0
  48. package/dist/index.d.ts +73 -0
  49. package/dist/index.js +47 -0
  50. package/dist/island.d.ts +55 -0
  51. package/dist/island.js +227 -0
  52. package/dist/islandSolve.d.ts +70 -0
  53. package/dist/islandSolve.js +171 -0
  54. package/dist/islandWorker.d.ts +18 -0
  55. package/dist/islandWorker.js +125 -0
  56. package/dist/joints.d.ts +180 -0
  57. package/dist/joints.js +1094 -0
  58. package/dist/manifold.d.ts +89 -0
  59. package/dist/manifold.js +608 -0
  60. package/dist/mass.d.ts +53 -0
  61. package/dist/mass.js +353 -0
  62. package/dist/meshContact.d.ts +57 -0
  63. package/dist/meshContact.js +393 -0
  64. package/dist/meshShape.d.ts +76 -0
  65. package/dist/meshShape.js +175 -0
  66. package/dist/pairs.d.ts +62 -0
  67. package/dist/pairs.js +154 -0
  68. package/dist/query.d.ts +71 -0
  69. package/dist/query.js +683 -0
  70. package/dist/ragdoll.d.ts +135 -0
  71. package/dist/ragdoll.js +489 -0
  72. package/dist/shape.d.ts +177 -0
  73. package/dist/shape.js +533 -0
  74. package/dist/solver.d.ts +136 -0
  75. package/dist/solver.js +601 -0
  76. package/dist/stage.d.ts +167 -0
  77. package/dist/stage.js +350 -0
  78. package/dist/stallEscape.d.ts +141 -0
  79. package/dist/stallEscape.js +254 -0
  80. package/dist/sweptStep.d.ts +173 -0
  81. package/dist/sweptStep.js +472 -0
  82. package/dist/tree.d.ts +61 -0
  83. package/dist/tree.js +348 -0
  84. package/dist/vehicle.d.ts +154 -0
  85. package/dist/vehicle.js +343 -0
  86. package/dist/workerPool.d.ts +107 -0
  87. package/dist/workerPool.js +239 -0
  88. package/dist/workers.d.ts +37 -0
  89. package/dist/workers.js +35 -0
  90. package/dist/world.d.ts +298 -0
  91. package/dist/world.js +712 -0
  92. package/package.json +55 -0
  93. package/src/bodies.ts +369 -0
  94. package/src/buoyancy.ts +78 -0
  95. package/src/cloth.ts +734 -0
  96. package/src/collide/aabb.ts +28 -0
  97. package/src/collide/body.ts +336 -0
  98. package/src/collide/eject.ts +156 -0
  99. package/src/collide/index.ts +26 -0
  100. package/src/collide/scratch.ts +10 -0
  101. package/src/collide/segment.ts +127 -0
  102. package/src/collide/sweep.ts +1132 -0
  103. package/src/colliderSet.ts +637 -0
  104. package/src/compoundContact.ts +284 -0
  105. package/src/controller.ts +817 -0
  106. package/src/cylinder.ts +340 -0
  107. package/src/cylinderContact.ts +825 -0
  108. package/src/decompose.ts +1198 -0
  109. package/src/events.ts +108 -0
  110. package/src/executor.ts +224 -0
  111. package/src/faces.ts +37 -0
  112. package/src/fingerprint.ts +146 -0
  113. package/src/groundMotor.ts +53 -0
  114. package/src/heightfieldShape.ts +395 -0
  115. package/src/index.ts +164 -0
  116. package/src/island.ts +219 -0
  117. package/src/islandSolve.ts +218 -0
  118. package/src/islandWorker.ts +141 -0
  119. package/src/joints.ts +1367 -0
  120. package/src/manifold.ts +798 -0
  121. package/src/mass.ts +424 -0
  122. package/src/meshContact.ts +443 -0
  123. package/src/meshShape.ts +261 -0
  124. package/src/pairs.ts +169 -0
  125. package/src/query.ts +844 -0
  126. package/src/ragdoll.ts +662 -0
  127. package/src/shape.ts +656 -0
  128. package/src/solver.ts +873 -0
  129. package/src/stage.ts +406 -0
  130. package/src/stallEscape.ts +362 -0
  131. package/src/sweptStep.ts +609 -0
  132. package/src/tree.ts +366 -0
  133. package/src/vehicle.ts +489 -0
  134. package/src/workerPool.ts +283 -0
  135. package/src/workers.ts +38 -0
  136. package/src/world.ts +961 -0
@@ -0,0 +1,443 @@
1
+ import type { Aabb } from './collide/index.ts';
2
+ import { fieldCandidates, fieldConvexEdges, fieldCorners, fieldPlane } from './heightfieldShape.ts';
3
+ import { collideShapes, createManifold } from './manifold.ts';
4
+ import type { Manifold, ShapePose } from './manifold.ts';
5
+ import type { ConvexShape } from './shape.ts';
6
+ import { shapeBounds } from './shape.ts';
7
+
8
+ /**
9
+ * Contacts between a convex shape and a static triangle mesh.
10
+ *
11
+ * **Many manifolds, not one, and that is the whole structural difference.** Every other pair in
12
+ * this engine has a single contact plane; a body in the corner of a mesh room has two or three, and
13
+ * reducing them to one would pick a wall and let the body through the others. So this fills a set,
14
+ * and `PhysicsWorld` prepares a constraint per entry — which the solver already supports, because
15
+ * `prepareContact` appends rather than replaces.
16
+ *
17
+ * **Each triangle goes through `collideShapes` unchanged**, as a three-point polytope written into
18
+ * one scratch shape. That is the reuse that makes this small: the separating-axis test, the
19
+ * clipping, the sphere and capsule paths and the cylinder's own module are all exact against a
20
+ * triangle already, and a second implementation of any of them would be a second thing to be wrong.
21
+ * *What it costs* is that the moving shape is transformed into world space once per candidate
22
+ * triangle rather than once per pair — eight vertices times twenty candidates for a box on a floor.
23
+ * *What would make it wrong* is a consumer resting a 64-point hull on a dense mesh, where the
24
+ * answer is to hoist the transform out of `collideShapes` rather than to write a second narrow
25
+ * phase.
26
+ *
27
+ * **Two sources of triangles and one set of rules.** A level's triangles are stored and found
28
+ * through a tree; a heightfield's are generated from four samples and found by index. Everything
29
+ * below is written once for both — the one-sided rule, the many manifolds, the interior-edge
30
+ * filter — and the only thing that differs is where a triangle's corners, plane and edge
31
+ * classification come from. `heightfieldShape.ts` is the other source, and the split is the
32
+ * 2026-08-13 rule applied to the one file here it would be most expensive to have two of.
33
+ *
34
+ * **One-sided.** A contact whose normal points into the back of a triangle is dropped. A level mesh
35
+ * is a surface rather than a solid, so "which side" is the only thing that distinguishes standing
36
+ * on a floor from being pushed up through it, and a two-sided triangle catches anything that has
37
+ * already sunk below it and fires it out the wrong way.
38
+ *
39
+ * **And the interior-edge filter, which is the part that decides whether it feels right.** A box
40
+ * sliding across two coplanar triangles meets their shared edge, where the narrow phase finds an
41
+ * edge normal rather than the face's and shoves the box backwards along it — a character stumbling
42
+ * on a flat floor once a metre. `meshShape` classifies every edge once as convex or not; a normal
43
+ * that came off a non-convex edge is replaced with the face's, and the separation is recomputed
44
+ * from the face plane. *What that gives up:* a genuinely convex ridge still produces its edge
45
+ * normal, which is correct, so a consumer who wants a ridge softened has to bevel it.
46
+ */
47
+
48
+ /** How many contact planes one pair may produce. A body in a corner needs three; eight is generous. */
49
+ export const MAX_MESH_MANIFOLDS = 8;
50
+
51
+ /** How parallel a contact normal must be to the face before it counts as coming off the face. */
52
+ const FACE_DOT = 0.999;
53
+
54
+ /** The candidate list from the tree, grown rather than capped. */
55
+ let candidates = new Int32Array(256);
56
+ const region: Aabb = { minX: 0, minY: 0, minZ: 0, maxX: 0, maxY: 0, maxZ: 0 };
57
+ const local: Aabb = { minX: 0, minY: 0, minZ: 0, maxX: 0, maxY: 0, maxZ: 0 };
58
+ const scratch = createManifold();
59
+ const rot = new Float32Array(9);
60
+
61
+ /**
62
+ * A triangle as a polytope, rewritten per candidate.
63
+ *
64
+ * One face rather than two, which is what makes the pair one-sided at the level below as well:
65
+ * `collideShapes` looks for a reference face and there is only the front one to find.
66
+ */
67
+ const TRIANGLE: ConvexShape = {
68
+ vertices: new Float32Array(9),
69
+ faceNormals: new Float32Array(3),
70
+ edgeDirs: new Float32Array(9),
71
+ radius: 0,
72
+ sideRadius: 0,
73
+ boundRadius: 0,
74
+ facePlanes: new Float32Array(4),
75
+ faceVertexStart: new Uint16Array([0, 3]),
76
+ faceVertexIndices: new Uint16Array([0, 1, 2]),
77
+ };
78
+
79
+ /**
80
+ * Whether a shape is a static triangle mesh, which is the one question the dispatch asks.
81
+ *
82
+ * Takes an absent shape as *not* a mesh, because a compound body has no single shape and the
83
+ * dispatch asks about both sides before it knows which kind of pair it has.
84
+ */
85
+ export function isMesh(shape: ConvexShape | undefined): boolean {
86
+ return shape?.triangles !== undefined;
87
+ }
88
+
89
+ /**
90
+ * Fill `out` with up to `out.length` manifolds and return how many.
91
+ *
92
+ * `out` is the caller's, allocated once — this runs in the tick. The normal of each manifold points
93
+ * from `shapeA` to `shapeB`, exactly as `collideShapes` promises, whichever of the two is the mesh.
94
+ */
95
+ export function collideMesh(
96
+ shapeA: ConvexShape,
97
+ poseA: ShapePose,
98
+ shapeB: ConvexShape,
99
+ poseB: ShapePose,
100
+ margin: number,
101
+ out: readonly Manifold[],
102
+ ): number {
103
+ const meshIsA = isMesh(shapeA);
104
+ const mesh = (meshIsA ? shapeA : shapeB).triangles;
105
+ if (mesh === undefined) return 0;
106
+ const meshPose = meshIsA ? poseA : poseB;
107
+ const other = meshIsA ? shapeB : shapeA;
108
+ const otherPose = meshIsA ? poseB : poseA;
109
+ /* Two meshes never touch: both are static, so nothing would move if they did. */
110
+ if (isMesh(other)) return 0;
111
+
112
+ /*
113
+ * The moving shape's bounds, carried into the mesh's own frame — because the tree is in that
114
+ * frame and rebuilding it per frame is exactly what static means it never has to do. The box is
115
+ * transformed rather than the tree: a rotated box's axis-aligned bounds are a little larger than
116
+ * the shape, which costs a few extra candidates and nothing else.
117
+ */
118
+ shapeBounds(other, region);
119
+ worldRegion(region, otherPose, meshPose, margin, local);
120
+ let found = candidatesFor(mesh, local);
121
+ while (found === candidates.length) {
122
+ candidates = new Int32Array(candidates.length * 2);
123
+ found = candidatesFor(mesh, local);
124
+ }
125
+
126
+ let count = 0;
127
+ for (let i = 0; i < found; i++) {
128
+ const triangle = candidates[i] ?? 0;
129
+ if (!writeTriangle(mesh, triangle)) continue;
130
+ /* A to B, with the triangle standing in for the mesh wherever the mesh stood. */
131
+ const hit = meshIsA
132
+ ? collideShapes(TRIANGLE, meshPose, other, otherPose, margin, scratch)
133
+ : collideShapes(other, otherPose, TRIANGLE, meshPose, margin, scratch);
134
+ if (!hit) continue;
135
+ /* The face normal in world, pointing away from the front of the triangle. */
136
+ planeOf(mesh, triangle);
137
+ rotateInto(meshPose, PLANE, 0, FACE);
138
+ /* Mesh-to-body, whichever way the manifold was asked for. */
139
+ const sign = meshIsA ? 1 : -1;
140
+ const away =
141
+ sign * scratch.nx * FACE[0] + sign * scratch.ny * FACE[1] + sign * scratch.nz * FACE[2];
142
+ /* One-sided: a normal pointing into the back of the triangle is a body already through it. */
143
+ if (away <= 0) continue;
144
+ if (away < FACE_DOT) filterInteriorEdge(mesh, triangle, meshPose, sign);
145
+
146
+ const target = out[count];
147
+ if (target === undefined) break;
148
+ copyInto(scratch, target, triangle);
149
+ count++;
150
+ if (count === out.length) break;
151
+ }
152
+ return count;
153
+ }
154
+
155
+ /** `[nx, ny, nz]` of the current triangle's world face normal. */
156
+ const FACE = new Float64Array(3);
157
+ /** The current triangle's plane in the mesh's own frame, from wherever it came. */
158
+ const PLANE = new Float32Array(4);
159
+ /** The current triangle's three corners, likewise. */
160
+ const CORNERS = new Float64Array(9);
161
+
162
+ /**
163
+ * The candidates a box could touch: a tree query for a level, an index range for a field.
164
+ *
165
+ * Both fill to `candidates.length` and stop, so the caller's grow-and-retry loop is the same one
166
+ * either way.
167
+ */
168
+ function candidatesFor(mesh: NonNullable<ConvexShape['triangles']>, local: Aabb): number {
169
+ if (mesh.field !== undefined) return fieldCandidates(mesh.field, local, candidates);
170
+ return mesh.tree?.query(local, candidates) ?? 0;
171
+ }
172
+
173
+ /** The current triangle's plane into `PLANE`: read for a level, computed for a field. */
174
+ function planeOf(mesh: NonNullable<ConvexShape['triangles']>, triangle: number): void {
175
+ if (mesh.field !== undefined) {
176
+ fieldPlane(mesh.field, triangle, PLANE_SCRATCH);
177
+ PLANE[0] = PLANE_SCRATCH[0] ?? 0;
178
+ PLANE[1] = PLANE_SCRATCH[1] ?? 0;
179
+ PLANE[2] = PLANE_SCRATCH[2] ?? 0;
180
+ PLANE[3] = PLANE_SCRATCH[3] ?? 0;
181
+ return;
182
+ }
183
+ PLANE[0] = mesh.planes[triangle * 4] ?? 0;
184
+ PLANE[1] = mesh.planes[triangle * 4 + 1] ?? 0;
185
+ PLANE[2] = mesh.planes[triangle * 4 + 2] ?? 0;
186
+ PLANE[3] = mesh.planes[triangle * 4 + 3] ?? 0;
187
+ }
188
+
189
+ const PLANE_SCRATCH = new Float64Array(4);
190
+
191
+ /** The current triangle's corners into `CORNERS`, in the mesh's own frame. */
192
+ function cornersOf(mesh: NonNullable<ConvexShape['triangles']>, triangle: number): void {
193
+ if (mesh.field !== undefined) {
194
+ fieldCorners(mesh.field, triangle, CORNERS);
195
+ return;
196
+ }
197
+ for (let k = 0; k < 3; k++) {
198
+ const index = mesh.indices[triangle * 3 + k] ?? 0;
199
+ CORNERS[k * 3] = mesh.positions[index * 3] ?? 0;
200
+ CORNERS[k * 3 + 1] = mesh.positions[index * 3 + 1] ?? 0;
201
+ CORNERS[k * 3 + 2] = mesh.positions[index * 3 + 2] ?? 0;
202
+ }
203
+ }
204
+
205
+ /** Which of a triangle's edges may produce a normal: stored for a level, computed for a field. */
206
+ function convexEdgesOf(mesh: NonNullable<ConvexShape['triangles']>, triangle: number): number {
207
+ if (mesh.field !== undefined) return fieldConvexEdges(mesh.field, triangle);
208
+ return mesh.convexEdges[triangle] ?? 0;
209
+ }
210
+
211
+ /** One corner of a triangle, in world. */
212
+ function worldCorner(
213
+ mesh: NonNullable<ConvexShape['triangles']>,
214
+ triangle: number,
215
+ corner: number,
216
+ pose: ShapePose,
217
+ out: Float64Array,
218
+ at: number,
219
+ ): void {
220
+ cornersOf(mesh, triangle);
221
+ poseRotation(pose);
222
+ const x = CORNERS[corner * 3] ?? 0;
223
+ const y = CORNERS[corner * 3 + 1] ?? 0;
224
+ const z = CORNERS[corner * 3 + 2] ?? 0;
225
+ out[at] = (rot[0] ?? 0) * x + (rot[1] ?? 0) * y + (rot[2] ?? 0) * z + pose.x;
226
+ out[at + 1] = (rot[3] ?? 0) * x + (rot[4] ?? 0) * y + (rot[5] ?? 0) * z + pose.y;
227
+ out[at + 2] = (rot[6] ?? 0) * x + (rot[7] ?? 0) * y + (rot[8] ?? 0) * z + pose.z;
228
+ }
229
+
230
+ /**
231
+ * Replace an edge normal with the face's, where the edge it came off is not a real one.
232
+ *
233
+ * The deepest contact point decides which edge, by distance to each of the three in the triangle's
234
+ * own plane — a vertex contact falls to whichever of its two edges is nearer, and both are treated
235
+ * the same way, which is what the filter wants.
236
+ *
237
+ * The separations are recomputed from the face plane rather than scaled, because the direction has
238
+ * changed and a scaled depth would hold a body off the floor by the cosine of an angle nobody
239
+ * chose.
240
+ */
241
+ function filterInteriorEdge(
242
+ mesh: NonNullable<ConvexShape['triangles']>,
243
+ triangle: number,
244
+ meshPose: ShapePose,
245
+ sign: number,
246
+ ): void {
247
+ let deepest = 0;
248
+ for (let i = 1; i < scratch.count; i++) {
249
+ if ((scratch.separations[i] ?? 0) < (scratch.separations[deepest] ?? 0)) deepest = i;
250
+ }
251
+ const px = scratch.points[deepest * 3] ?? 0;
252
+ const py = scratch.points[deepest * 3 + 1] ?? 0;
253
+ const pz = scratch.points[deepest * 3 + 2] ?? 0;
254
+
255
+ let nearest = 0;
256
+ let best = Infinity;
257
+ for (let e = 0; e < 3; e++) {
258
+ worldCorner(mesh, triangle, e, meshPose, EDGE, 0);
259
+ worldCorner(mesh, triangle, (e + 1) % 3, meshPose, EDGE, 3);
260
+ const distance = pointSegmentSquared(px, py, pz);
261
+ if (distance < best) {
262
+ best = distance;
263
+ nearest = e;
264
+ }
265
+ }
266
+ /* A convex edge is a real one: the ridge is there and the body should feel it. */
267
+ if ((convexEdgesOf(mesh, triangle) >> nearest) & 1) return;
268
+
269
+ scratch.nx = sign * FACE[0];
270
+ scratch.ny = sign * FACE[1];
271
+ scratch.nz = sign * FACE[2];
272
+ worldPlaneAnchor(mesh, triangle, meshPose, ANCHOR);
273
+ const plane = FACE[0] * ANCHOR[0] + FACE[1] * ANCHOR[1] + FACE[2] * ANCHOR[2];
274
+ for (let i = 0; i < scratch.count; i++) {
275
+ scratch.separations[i] =
276
+ (scratch.points[i * 3] ?? 0) * FACE[0] +
277
+ (scratch.points[i * 3 + 1] ?? 0) * FACE[1] +
278
+ (scratch.points[i * 3 + 2] ?? 0) * FACE[2] -
279
+ plane;
280
+ }
281
+ }
282
+
283
+ const EDGE = new Float64Array(6);
284
+ const ANCHOR = new Float64Array(3);
285
+
286
+ function pointSegmentSquared(px: number, py: number, pz: number): number {
287
+ const ax = EDGE[0];
288
+ const ay = EDGE[1];
289
+ const az = EDGE[2];
290
+ const ex = EDGE[3] - ax;
291
+ const ey = EDGE[4] - ay;
292
+ const ez = EDGE[5] - az;
293
+ const ee = ex * ex + ey * ey + ez * ez;
294
+ let t = ee > 1e-12 ? ((px - ax) * ex + (py - ay) * ey + (pz - az) * ez) / ee : 0;
295
+ t = t < 0 ? 0 : t > 1 ? 1 : t;
296
+ const dx = px - (ax + ex * t);
297
+ const dy = py - (ay + ey * t);
298
+ const dz = pz - (az + ez * t);
299
+ return dx * dx + dy * dy + dz * dz;
300
+ }
301
+
302
+ /** Write one triangle into the scratch shape. Returns false for a degenerate one. */
303
+ function writeTriangle(mesh: NonNullable<ConvexShape['triangles']>, triangle: number): boolean {
304
+ planeOf(mesh, triangle);
305
+ const nx = PLANE[0] ?? 0;
306
+ const ny = PLANE[1] ?? 0;
307
+ const nz = PLANE[2] ?? 0;
308
+ if (nx === 0 && ny === 0 && nz === 0) return false;
309
+
310
+ cornersOf(mesh, triangle);
311
+ const v = TRIANGLE.vertices;
312
+ for (let k = 0; k < 9; k++) v[k] = CORNERS[k] ?? 0;
313
+ TRIANGLE.faceNormals[0] = nx;
314
+ TRIANGLE.faceNormals[1] = ny;
315
+ TRIANGLE.faceNormals[2] = nz;
316
+ TRIANGLE.facePlanes[0] = nx;
317
+ TRIANGLE.facePlanes[1] = ny;
318
+ TRIANGLE.facePlanes[2] = nz;
319
+ TRIANGLE.facePlanes[3] = PLANE[3] ?? 0;
320
+ for (let e = 0; e < 3; e++) {
321
+ const j = (e + 1) % 3;
322
+ TRIANGLE.edgeDirs[e * 3] = (v[j * 3] ?? 0) - (v[e * 3] ?? 0);
323
+ TRIANGLE.edgeDirs[e * 3 + 1] = (v[j * 3 + 1] ?? 0) - (v[e * 3 + 1] ?? 0);
324
+ TRIANGLE.edgeDirs[e * 3 + 2] = (v[j * 3 + 2] ?? 0) - (v[e * 3 + 2] ?? 0);
325
+ }
326
+ return true;
327
+ }
328
+
329
+ /**
330
+ * Copy a manifold out of the scratch, folding the triangle into every feature id.
331
+ *
332
+ * **Warm starting is why the triangle has to be in the id.** Impulses persist across ticks by
333
+ * feature id, and two triangles under one body produce the same clipping features — so without
334
+ * this a box on a seam would hand triangle A's accumulated impulse to triangle B every tick, which
335
+ * reads as a stack that will not settle.
336
+ */
337
+ function copyInto(from: Manifold, to: Manifold, triangle: number): void {
338
+ to.nx = from.nx;
339
+ to.ny = from.ny;
340
+ to.nz = from.nz;
341
+ to.count = from.count;
342
+ to.curvedA = from.curvedA;
343
+ to.curvedB = from.curvedB;
344
+ for (let i = 0; i < from.count; i++) {
345
+ to.points[i * 3] = from.points[i * 3] ?? 0;
346
+ to.points[i * 3 + 1] = from.points[i * 3 + 1] ?? 0;
347
+ to.points[i * 3 + 2] = from.points[i * 3 + 2] ?? 0;
348
+ to.separations[i] = from.separations[i] ?? 0;
349
+ to.featureIds[i] = (((triangle & 0xffff) << 12) ^ (from.featureIds[i] ?? 0)) | 0;
350
+ }
351
+ }
352
+
353
+ /** The triangle's first corner, in world, which is a point the face plane passes through. */
354
+ function worldPlaneAnchor(
355
+ mesh: NonNullable<ConvexShape['triangles']>,
356
+ triangle: number,
357
+ pose: ShapePose,
358
+ out: Float64Array,
359
+ ): void {
360
+ worldCorner(mesh, triangle, 0, pose, out, 0);
361
+ }
362
+
363
+ /** Rotate a direction out of a packed array into `out`. */
364
+ function rotateInto(pose: ShapePose, source: Float32Array, at: number, out: Float64Array): void {
365
+ poseRotation(pose);
366
+ const x = source[at] ?? 0;
367
+ const y = source[at + 1] ?? 0;
368
+ const z = source[at + 2] ?? 0;
369
+ out[0] = (rot[0] ?? 0) * x + (rot[1] ?? 0) * y + (rot[2] ?? 0) * z;
370
+ out[1] = (rot[3] ?? 0) * x + (rot[4] ?? 0) * y + (rot[5] ?? 0) * z;
371
+ out[2] = (rot[6] ?? 0) * x + (rot[7] ?? 0) * y + (rot[8] ?? 0) * z;
372
+ }
373
+
374
+ /**
375
+ * The moving shape's world bounds, expressed in the mesh's frame and grown by the margin.
376
+ *
377
+ * Eight corners through the inverse of the mesh's pose, re-bounded: a rotated box's axis-aligned
378
+ * bounds in another frame are larger than the shape, which costs a few candidates the narrow phase
379
+ * then rejects and is far cheaper than transforming the geometry.
380
+ */
381
+ function worldRegion(
382
+ bounds: Aabb,
383
+ otherPose: ShapePose,
384
+ meshPose: ShapePose,
385
+ margin: number,
386
+ out: Aabb,
387
+ ): void {
388
+ out.minX = Infinity;
389
+ out.minY = Infinity;
390
+ out.minZ = Infinity;
391
+ out.maxX = -Infinity;
392
+ out.maxY = -Infinity;
393
+ out.maxZ = -Infinity;
394
+ for (let corner = 0; corner < 8; corner++) {
395
+ const x = corner & 1 ? bounds.maxX : bounds.minX;
396
+ const y = corner & 2 ? bounds.maxY : bounds.minY;
397
+ const z = corner & 4 ? bounds.maxZ : bounds.minZ;
398
+ /* Out of the shape's own frame into the world, then into the mesh's. */
399
+ poseRotation(otherPose);
400
+ const wx = (rot[0] ?? 0) * x + (rot[1] ?? 0) * y + (rot[2] ?? 0) * z + otherPose.x;
401
+ const wy = (rot[3] ?? 0) * x + (rot[4] ?? 0) * y + (rot[5] ?? 0) * z + otherPose.y;
402
+ const wz = (rot[6] ?? 0) * x + (rot[7] ?? 0) * y + (rot[8] ?? 0) * z + otherPose.z;
403
+ unrotate(meshPose, wx - meshPose.x, wy - meshPose.y, wz - meshPose.z, ANCHOR);
404
+ out.minX = Math.min(out.minX, ANCHOR[0]);
405
+ out.minY = Math.min(out.minY, ANCHOR[1]);
406
+ out.minZ = Math.min(out.minZ, ANCHOR[2]);
407
+ out.maxX = Math.max(out.maxX, ANCHOR[0]);
408
+ out.maxY = Math.max(out.maxY, ANCHOR[1]);
409
+ out.maxZ = Math.max(out.maxZ, ANCHOR[2]);
410
+ }
411
+ out.minX -= margin;
412
+ out.minY -= margin;
413
+ out.minZ -= margin;
414
+ out.maxX += margin;
415
+ out.maxY += margin;
416
+ out.maxZ += margin;
417
+ }
418
+
419
+ function poseRotation(p: ShapePose): void {
420
+ const { qx: x, qy: y, qz: z, qw: w } = p;
421
+ rot[0] = 1 - 2 * (y * y + z * z);
422
+ rot[1] = 2 * (x * y - z * w);
423
+ rot[2] = 2 * (x * z + y * w);
424
+ rot[3] = 2 * (x * y + z * w);
425
+ rot[4] = 1 - 2 * (x * x + z * z);
426
+ rot[5] = 2 * (y * z - x * w);
427
+ rot[6] = 2 * (x * z - y * w);
428
+ rot[7] = 2 * (y * z + x * w);
429
+ rot[8] = 1 - 2 * (x * x + y * y);
430
+ }
431
+
432
+ function unrotate(p: ShapePose, x: number, y: number, z: number, out: Float64Array): void {
433
+ const qx = -p.qx;
434
+ const qy = -p.qy;
435
+ const qz = -p.qz;
436
+ const qw = p.qw;
437
+ const tx = 2 * (qy * z - qz * y);
438
+ const ty = 2 * (qz * x - qx * z);
439
+ const tz = 2 * (qx * y - qy * x);
440
+ out[0] = x + qw * tx + (qy * tz - qz * ty);
441
+ out[1] = y + qw * ty + (qz * tx - qx * tz);
442
+ out[2] = z + qw * tz + (qx * ty - qy * tx);
443
+ }