@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
package/dist/query.js ADDED
@@ -0,0 +1,683 @@
1
+ import { BODY_STATIC } from './bodies.js';
2
+ import { closestOnCylinder, isCylinder, readCylinder } from './cylinder.js';
3
+ import { faceCount } from './faces.js';
4
+ import { collideShapes, createManifold } from './manifold.js';
5
+ import { fieldCandidates, fieldCorners, fieldPlane } from './heightfieldShape.js';
6
+ import { collideMesh, isMesh } from './meshContact.js';
7
+ import { DynamicTree } from './tree.js';
8
+ export function createRayHit() {
9
+ return { body: -1, fraction: 1, x: 0, y: 0, z: 0, nx: 0, ny: 0, nz: 0 };
10
+ }
11
+ const HITS_START = 256;
12
+ let hits = new Int32Array(HITS_START);
13
+ const box = { minX: 0, minY: 0, minZ: 0, maxX: 0, maxY: 0, maxZ: 0 };
14
+ const poseA = { x: 0, y: 0, z: 0, qx: 0, qy: 0, qz: 0, qw: 1 };
15
+ const poseB = { x: 0, y: 0, z: 0, qx: 0, qy: 0, qz: 0, qw: 1 };
16
+ const manifold = createManifold();
17
+ /**
18
+ * One manifold for a mesh answer, because both callers here want one plane rather than the set.
19
+ *
20
+ * An overlap is a yes or a no, and a shapecast advances against the nearest surface it can see.
21
+ * The full set is `PhysicsWorld`'s business, where every plane becomes a constraint.
22
+ */
23
+ const MESH_ONE = [createManifold()];
24
+ function copyManifold(from, to) {
25
+ to.nx = from.nx;
26
+ to.ny = from.ny;
27
+ to.nz = from.nz;
28
+ to.count = from.count;
29
+ to.curvedA = from.curvedA;
30
+ to.curvedB = from.curvedB;
31
+ to.points.set(from.points);
32
+ to.separations.set(from.separations);
33
+ to.featureIds.set(from.featureIds);
34
+ }
35
+ function admits(bodies, i, filter) {
36
+ if (!filter)
37
+ return true;
38
+ if (filter.ignore === i)
39
+ return false;
40
+ const layer = filter.layer ?? 0xffffffff;
41
+ const mask = filter.mask ?? 0xffffffff;
42
+ return ((bodies.layer[i] ?? 0) & mask) !== 0 && ((bodies.mask[i] ?? 0) & layer) !== 0;
43
+ }
44
+ /** Every body whose bounds the tree says could overlap `region`. Grows the scratch as needed. */
45
+ function candidates(tree, region) {
46
+ let found = tree.query(region, hits);
47
+ while (found === hits.length) {
48
+ hits = new Int32Array(hits.length * 2);
49
+ found = tree.query(region, hits);
50
+ }
51
+ return found;
52
+ }
53
+ function readPose(bodies, i, out) {
54
+ out.x = bodies.posX[i] ?? 0;
55
+ out.y = bodies.posY[i] ?? 0;
56
+ out.z = bodies.posZ[i] ?? 0;
57
+ out.qx = bodies.rotX[i] ?? 0;
58
+ out.qy = bodies.rotY[i] ?? 0;
59
+ out.qz = bodies.rotZ[i] ?? 0;
60
+ out.qw = bodies.rotW[i] ?? 1;
61
+ }
62
+ /**
63
+ * The nearest body a ray meets, or `false` if it meets none.
64
+ *
65
+ * A ray starting *inside* a body reports that body at fraction zero, with the normal of the face it
66
+ * is nearest to leaving through. Reporting nothing instead is the tempting alternative and is worse:
67
+ * a line-of-sight test would then say a wall it started inside was not there.
68
+ */
69
+ export function raycastWorld(bodies, tree, ox, oy, oz, dx, dy, dz, maxDistance, out, filter) {
70
+ const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
71
+ if (len === 0 || maxDistance <= 0)
72
+ return false;
73
+ const ux = dx / len;
74
+ const uy = dy / len;
75
+ const uz = dz / len;
76
+ const ex = ox + ux * maxDistance;
77
+ const ey = oy + uy * maxDistance;
78
+ const ez = oz + uz * maxDistance;
79
+ box.minX = Math.min(ox, ex);
80
+ box.minY = Math.min(oy, ey);
81
+ box.minZ = Math.min(oz, ez);
82
+ box.maxX = Math.max(ox, ex);
83
+ box.maxY = Math.max(oy, ey);
84
+ box.maxZ = Math.max(oz, ez);
85
+ const n = candidates(tree, box);
86
+ out.body = -1;
87
+ out.fraction = 1;
88
+ let best = Infinity;
89
+ for (let k = 0; k < n; k++) {
90
+ const i = hits[k] ?? 0;
91
+ if (!admits(bodies, i, filter))
92
+ continue;
93
+ const shape = bodies.shape[i];
94
+ if (!shape)
95
+ continue;
96
+ readPose(bodies, i, poseA);
97
+ if (!rayShape(shape, poseA, ox, oy, oz, ux, uy, uz, maxDistance))
98
+ continue;
99
+ if (RAY[0] < best || (RAY[0] === best && i < out.body)) {
100
+ best = RAY[0];
101
+ out.body = i;
102
+ out.fraction = RAY[0] / maxDistance;
103
+ out.x = ox + ux * RAY[0];
104
+ out.y = oy + uy * RAY[0];
105
+ out.z = oz + uz * RAY[0];
106
+ out.nx = RAY[1];
107
+ out.ny = RAY[2];
108
+ out.nz = RAY[3];
109
+ }
110
+ }
111
+ return out.body >= 0;
112
+ }
113
+ /** `[distance, nx, ny, nz]` of the last ray test. */
114
+ const RAY = new Float64Array(4);
115
+ const WORLD_PLANES = new Float32Array(64 * 4);
116
+ /**
117
+ * Slab test against a convex shape's world face planes, with the rounding radius folded in.
118
+ *
119
+ * A shape with no faces is a sphere or a capsule and takes the segment-to-segment path instead,
120
+ * which is what its radius makes it.
121
+ */
122
+ function rayShape(shape, pose, ox, oy, oz, ux, uy, uz, maxDistance) {
123
+ if (shape.triangles !== undefined) {
124
+ return rayMesh(shape, pose, ox, oy, oz, ux, uy, uz, maxDistance);
125
+ }
126
+ if (isCylinder(shape))
127
+ return rayCylinder(shape, pose, ox, oy, oz, ux, uy, uz, maxDistance);
128
+ const faces = faceCount(shape);
129
+ if (faces === 0)
130
+ return rayRound(shape, pose, ox, oy, oz, ux, uy, uz, maxDistance);
131
+ worldPlanes(shape, pose, faces);
132
+ let enter = 0;
133
+ let exit = maxDistance;
134
+ let nx = 0;
135
+ let ny = 0;
136
+ let nz = 0;
137
+ for (let f = 0; f < faces; f++) {
138
+ const px = WORLD_PLANES[f * 4] ?? 0;
139
+ const py = WORLD_PLANES[f * 4 + 1] ?? 0;
140
+ const pz = WORLD_PLANES[f * 4 + 2] ?? 0;
141
+ const d = (WORLD_PLANES[f * 4 + 3] ?? 0) + shape.radius;
142
+ const denom = px * ux + py * uy + pz * uz;
143
+ const dist = px * ox + py * oy + pz * oz - d;
144
+ if (denom === 0) {
145
+ if (dist > 0)
146
+ return false;
147
+ continue;
148
+ }
149
+ const t = -dist / denom;
150
+ if (denom < 0) {
151
+ if (t > enter) {
152
+ enter = t;
153
+ nx = px;
154
+ ny = py;
155
+ nz = pz;
156
+ }
157
+ }
158
+ else if (t < exit) {
159
+ exit = t;
160
+ }
161
+ if (enter > exit)
162
+ return false;
163
+ }
164
+ if (enter > maxDistance)
165
+ return false;
166
+ RAY[0] = enter;
167
+ if (enter === 0) {
168
+ // Started inside: the nearest face's outward normal is the honest answer.
169
+ let nearest = 0;
170
+ let best = -Infinity;
171
+ for (let f = 0; f < faces; f++) {
172
+ const v = (WORLD_PLANES[f * 4] ?? 0) * ox +
173
+ (WORLD_PLANES[f * 4 + 1] ?? 0) * oy +
174
+ (WORLD_PLANES[f * 4 + 2] ?? 0) * oz -
175
+ (WORLD_PLANES[f * 4 + 3] ?? 0);
176
+ if (v > best) {
177
+ best = v;
178
+ nearest = f;
179
+ }
180
+ }
181
+ nx = WORLD_PLANES[nearest * 4] ?? 0;
182
+ ny = WORLD_PLANES[nearest * 4 + 1] ?? 0;
183
+ nz = WORLD_PLANES[nearest * 4 + 2] ?? 0;
184
+ }
185
+ RAY[1] = nx;
186
+ RAY[2] = ny;
187
+ RAY[3] = nz;
188
+ return true;
189
+ }
190
+ /**
191
+ * A ray against a cylinder: a quadratic against the infinite side, and two discs for the caps.
192
+ *
193
+ * **Closed form rather than the march `rayRound` uses**, and the reason is that a cylinder has one.
194
+ * A capsule's distance field has no root a quadratic can find, so that function halves its way in
195
+ * at a fixed iteration count; a cylinder's side is a quadratic in the ray parameter and its caps
196
+ * are two plane intersections with one radial test each. Exact, and cheaper.
197
+ *
198
+ * A ray starting inside reports zero distance and the nearest surface's outward normal, which is
199
+ * the same answer `rayShape` gives for a polytope and is what a caller asking "what am I in"
200
+ * needs.
201
+ */
202
+ function rayCylinder(shape, pose, ox, oy, oz, ux, uy, uz, maxDistance) {
203
+ readCylinder(shape, pose, CYL);
204
+ const cx = CYL[0] ?? 0;
205
+ const cy = CYL[1] ?? 0;
206
+ const cz = CYL[2] ?? 0;
207
+ const ax = CYL[3] ?? 0;
208
+ const ay = CYL[4] ?? 0;
209
+ const az = CYL[5] ?? 0;
210
+ const h = CYL[6] ?? 0;
211
+ const r = (CYL[7] ?? 0) + shape.radius;
212
+ closestOnCylinder(CYL, ox, oy, oz, INSIDE);
213
+ if (INSIDE[6] <= shape.radius) {
214
+ RAY[0] = 0;
215
+ RAY[1] = INSIDE[3];
216
+ RAY[2] = INSIDE[4];
217
+ RAY[3] = INSIDE[5];
218
+ return true;
219
+ }
220
+ const dx = ox - cx;
221
+ const dy = oy - cy;
222
+ const dz = oz - cz;
223
+ const dAlong = dx * ax + dy * ay + dz * az;
224
+ const uAlong = ux * ax + uy * ay + uz * az;
225
+ const dpx = dx - ax * dAlong;
226
+ const dpy = dy - ay * dAlong;
227
+ const dpz = dz - az * dAlong;
228
+ const upx = ux - ax * uAlong;
229
+ const upy = uy - ay * uAlong;
230
+ const upz = uz - az * uAlong;
231
+ let best = Infinity;
232
+ let nx = 0;
233
+ let ny = 1;
234
+ let nz = 0;
235
+ const qa = upx * upx + upy * upy + upz * upz;
236
+ if (qa > 1e-12) {
237
+ const qb = 2 * (dpx * upx + dpy * upy + dpz * upz);
238
+ const qc = dpx * dpx + dpy * dpy + dpz * dpz - r * r;
239
+ const disc = qb * qb - 4 * qa * qc;
240
+ if (disc >= 0) {
241
+ const root = Math.sqrt(disc);
242
+ for (let i = 0; i < 2; i++) {
243
+ const t = (i === 0 ? -qb - root : -qb + root) / (2 * qa);
244
+ if (t < 0 || t >= best)
245
+ continue;
246
+ const along = dAlong + uAlong * t;
247
+ if (Math.abs(along) > h)
248
+ continue;
249
+ const px = dpx + upx * t;
250
+ const py = dpy + upy * t;
251
+ const pz = dpz + upz * t;
252
+ const len = Math.sqrt(px * px + py * py + pz * pz) || 1;
253
+ best = t;
254
+ nx = px / len;
255
+ ny = py / len;
256
+ nz = pz / len;
257
+ }
258
+ }
259
+ }
260
+ // The two caps: a plane each, kept only where the crossing is inside the disc.
261
+ if (Math.abs(uAlong) > 1e-12) {
262
+ for (let i = 0; i < 2; i++) {
263
+ const side = i === 0 ? 1 : -1;
264
+ const t = (side * h - dAlong) / uAlong;
265
+ if (t < 0 || t >= best)
266
+ continue;
267
+ const px = dpx + upx * t;
268
+ const py = dpy + upy * t;
269
+ const pz = dpz + upz * t;
270
+ if (px * px + py * py + pz * pz > r * r)
271
+ continue;
272
+ best = t;
273
+ nx = ax * side;
274
+ ny = ay * side;
275
+ nz = az * side;
276
+ }
277
+ }
278
+ if (best > maxDistance)
279
+ return false;
280
+ RAY[0] = best;
281
+ RAY[1] = nx;
282
+ RAY[2] = ny;
283
+ RAY[3] = nz;
284
+ return true;
285
+ }
286
+ const CYL = new Float64Array(11);
287
+ const INSIDE = new Float64Array(7);
288
+ /**
289
+ * A ray against a static triangle mesh: the segment's own box against the tree, then every
290
+ * candidate.
291
+ *
292
+ * **The tree is queried with the whole segment's bounds rather than walked**, and that is a cost
293
+ * stated rather than hidden: a long ray across a large mesh returns candidates along a box that is
294
+ * mostly empty, where a descent testing the ray against each node's box would return a handful.
295
+ * *What it buys* is that nothing new had to be built — `DynamicTree.query` is the tested thing this
296
+ * package already has. *What would make it wrong* is a consumer casting long rays through a dense
297
+ * level every frame, at which point the tree grows a ray descent and this calls it instead.
298
+ *
299
+ * Möller-Trumbore per triangle, which needs only the arithmetic the determinism gate permits: four
300
+ * cross products, three dots and one divide, with no transcendental anywhere. **Two-sided here**,
301
+ * unlike the contact path: a ray is a question about geometry rather than a body being pushed, and
302
+ * a line-of-sight test that saw through a wall from behind would be a worse answer than one that
303
+ * did not.
304
+ */
305
+ function rayMesh(shape, pose, ox, oy, oz, ux, uy, uz, maxDistance) {
306
+ const mesh = shape.triangles;
307
+ if (mesh === undefined)
308
+ return false;
309
+ /* The ray in the mesh's own frame, because the tree is in that frame. */
310
+ unrotatePose(pose, ox - pose.x, oy - pose.y, oz - pose.z, RAY_LOCAL, 0);
311
+ unrotatePose(pose, ux, uy, uz, RAY_LOCAL, 3);
312
+ const lx = RAY_LOCAL[0];
313
+ const ly = RAY_LOCAL[1];
314
+ const lz = RAY_LOCAL[2];
315
+ const dx = RAY_LOCAL[3];
316
+ const dy = RAY_LOCAL[4];
317
+ const dz = RAY_LOCAL[5];
318
+ const ex = lx + dx * maxDistance;
319
+ const ey = ly + dy * maxDistance;
320
+ const ez = lz + dz * maxDistance;
321
+ box.minX = Math.min(lx, ex);
322
+ box.minY = Math.min(ly, ey);
323
+ box.minZ = Math.min(lz, ez);
324
+ box.maxX = Math.max(lx, ex);
325
+ box.maxY = Math.max(ly, ey);
326
+ box.maxZ = Math.max(lz, ez);
327
+ /* A tree for a level, an index range for a heightfield. Both fill to the buffer's length and
328
+ stop, so the grow-and-retry is the same either way. See `heightfieldShape.ts`. */
329
+ const ask = () => mesh.field !== undefined
330
+ ? fieldCandidates(mesh.field, box, hits)
331
+ : (mesh.tree?.query(box, hits) ?? 0);
332
+ let found = ask();
333
+ while (found === hits.length) {
334
+ hits = new Int32Array(hits.length * 2);
335
+ found = ask();
336
+ }
337
+ let best = maxDistance;
338
+ let triangle = -1;
339
+ for (let i = 0; i < found; i++) {
340
+ const t = hits[i] ?? 0;
341
+ const distance = rayTriangle(mesh, t, lx, ly, lz, dx, dy, dz);
342
+ if (distance >= 0 && distance < best) {
343
+ best = distance;
344
+ triangle = t;
345
+ }
346
+ }
347
+ if (triangle < 0)
348
+ return false;
349
+ RAY[0] = best;
350
+ /* The face normal, turned to face the ray: a hit from behind reports the side it was hit on. */
351
+ trianglePlane(mesh, triangle, RAY_PLANE);
352
+ rotatePose(pose, RAY_PLANE[0] ?? 0, RAY_PLANE[1] ?? 0, RAY_PLANE[2] ?? 0, RAY_LOCAL, 6);
353
+ const facing = RAY_LOCAL[6] * ux + RAY_LOCAL[7] * uy + RAY_LOCAL[8] * uz;
354
+ const sign = facing > 0 ? -1 : 1;
355
+ RAY[1] = RAY_LOCAL[6] * sign;
356
+ RAY[2] = RAY_LOCAL[7] * sign;
357
+ RAY[3] = RAY_LOCAL[8] * sign;
358
+ return true;
359
+ }
360
+ const RAY_LOCAL = new Float64Array(9);
361
+ /** The current triangle's plane and corners, from whichever source the shape has. */
362
+ const RAY_PLANE = new Float64Array(4);
363
+ const RAY_CORNERS = new Float64Array(9);
364
+ /** A triangle's plane: read for a level, computed for a heightfield. */
365
+ function trianglePlane(mesh, triangle, out) {
366
+ if (mesh.field !== undefined) {
367
+ fieldPlane(mesh.field, triangle, out);
368
+ return;
369
+ }
370
+ out[0] = mesh.planes[triangle * 4] ?? 0;
371
+ out[1] = mesh.planes[triangle * 4 + 1] ?? 0;
372
+ out[2] = mesh.planes[triangle * 4 + 2] ?? 0;
373
+ out[3] = mesh.planes[triangle * 4 + 3] ?? 0;
374
+ }
375
+ /** A triangle's three corners, likewise. */
376
+ function triangleCorners(mesh, triangle, out) {
377
+ if (mesh.field !== undefined) {
378
+ fieldCorners(mesh.field, triangle, out);
379
+ return;
380
+ }
381
+ for (let k = 0; k < 3; k++) {
382
+ const index = mesh.indices[triangle * 3 + k] ?? 0;
383
+ out[k * 3] = mesh.positions[index * 3] ?? 0;
384
+ out[k * 3 + 1] = mesh.positions[index * 3 + 1] ?? 0;
385
+ out[k * 3 + 2] = mesh.positions[index * 3 + 2] ?? 0;
386
+ }
387
+ }
388
+ /** Möller-Trumbore. Returns the distance along the ray, or −1 for a miss. */
389
+ function rayTriangle(mesh, triangle, ox, oy, oz, dx, dy, dz) {
390
+ triangleCorners(mesh, triangle, RAY_CORNERS);
391
+ const ax = RAY_CORNERS[0] ?? 0;
392
+ const ay = RAY_CORNERS[1] ?? 0;
393
+ const az = RAY_CORNERS[2] ?? 0;
394
+ const e1x = (RAY_CORNERS[3] ?? 0) - ax;
395
+ const e1y = (RAY_CORNERS[4] ?? 0) - ay;
396
+ const e1z = (RAY_CORNERS[5] ?? 0) - az;
397
+ const e2x = (RAY_CORNERS[6] ?? 0) - ax;
398
+ const e2y = (RAY_CORNERS[7] ?? 0) - ay;
399
+ const e2z = (RAY_CORNERS[8] ?? 0) - az;
400
+ const px = dy * e2z - dz * e2y;
401
+ const py = dz * e2x - dx * e2z;
402
+ const pz = dx * e2y - dy * e2x;
403
+ const determinant = e1x * px + e1y * py + e1z * pz;
404
+ /* Parallel to the plane, from either side: no crossing to report. */
405
+ if (determinant > -1e-12 && determinant < 1e-12)
406
+ return -1;
407
+ const inverse = 1 / determinant;
408
+ const tx = ox - ax;
409
+ const ty = oy - ay;
410
+ const tz = oz - az;
411
+ const u = (tx * px + ty * py + tz * pz) * inverse;
412
+ if (u < 0 || u > 1)
413
+ return -1;
414
+ const qx = ty * e1z - tz * e1y;
415
+ const qy = tz * e1x - tx * e1z;
416
+ const qz = tx * e1y - ty * e1x;
417
+ const v = (dx * qx + dy * qy + dz * qz) * inverse;
418
+ if (v < 0 || u + v > 1)
419
+ return -1;
420
+ const distance = (e2x * qx + e2y * qy + e2z * qz) * inverse;
421
+ return distance >= 0 ? distance : -1;
422
+ }
423
+ function rotatePose(p, x, y, z, out, at) {
424
+ const tx = 2 * (p.qy * z - p.qz * y);
425
+ const ty = 2 * (p.qz * x - p.qx * z);
426
+ const tz = 2 * (p.qx * y - p.qy * x);
427
+ out[at] = x + p.qw * tx + (p.qy * tz - p.qz * ty);
428
+ out[at + 1] = y + p.qw * ty + (p.qz * tx - p.qx * tz);
429
+ out[at + 2] = z + p.qw * tz + (p.qx * ty - p.qy * tx);
430
+ }
431
+ function unrotatePose(p, x, y, z, out, at) {
432
+ const qx = -p.qx;
433
+ const qy = -p.qy;
434
+ const qz = -p.qz;
435
+ const qw = p.qw;
436
+ const tx = 2 * (qy * z - qz * y);
437
+ const ty = 2 * (qz * x - qx * z);
438
+ const tz = 2 * (qx * y - qy * x);
439
+ out[at] = x + qw * tx + (qy * tz - qz * ty);
440
+ out[at + 1] = y + qw * ty + (qz * tx - qx * tz);
441
+ out[at + 2] = z + qw * tz + (qx * ty - qy * tx);
442
+ }
443
+ /** A ray against a sphere or a capsule: the closest approach to its segment against the radius. */
444
+ function rayRound(shape, pose, ox, oy, oz, ux, uy, uz, maxDistance) {
445
+ const count = shape.vertices.length / 3;
446
+ rotatePoint(shape, pose, 0, SEG, 0);
447
+ if (count > 1)
448
+ rotatePoint(shape, pose, 1, SEG, 3);
449
+ else {
450
+ SEG[3] = SEG[0];
451
+ SEG[4] = SEG[1];
452
+ SEG[5] = SEG[2];
453
+ }
454
+ // March the ray, halving the step whenever it would pass through. Fixed count, no tolerance loop.
455
+ let t = 0;
456
+ for (let step = 0; step < 32; step++) {
457
+ const px = ox + ux * t;
458
+ const py = oy + uy * t;
459
+ const pz = oz + uz * t;
460
+ const d = pointSegmentDistance(px, py, pz) - shape.radius;
461
+ if (d < 1e-4) {
462
+ RAY[0] = t;
463
+ const cx = px - CLOSE[0];
464
+ const cy = py - CLOSE[1];
465
+ const cz = pz - CLOSE[2];
466
+ const cl = Math.sqrt(cx * cx + cy * cy + cz * cz) || 1;
467
+ RAY[1] = cx / cl;
468
+ RAY[2] = cy / cl;
469
+ RAY[3] = cz / cl;
470
+ return true;
471
+ }
472
+ t += d;
473
+ if (t > maxDistance)
474
+ return false;
475
+ }
476
+ return false;
477
+ }
478
+ const SEG = new Float64Array(6);
479
+ const CLOSE = new Float64Array(3);
480
+ function pointSegmentDistance(px, py, pz) {
481
+ const ax = SEG[0];
482
+ const ay = SEG[1];
483
+ const az = SEG[2];
484
+ const bx = SEG[3] - ax;
485
+ const by = SEG[4] - ay;
486
+ const bz = SEG[5] - az;
487
+ const bb = bx * bx + by * by + bz * bz;
488
+ let s = 0;
489
+ if (bb > 1e-12) {
490
+ s = ((px - ax) * bx + (py - ay) * by + (pz - az) * bz) / bb;
491
+ s = s < 0 ? 0 : s > 1 ? 1 : s;
492
+ }
493
+ CLOSE[0] = ax + bx * s;
494
+ CLOSE[1] = ay + by * s;
495
+ CLOSE[2] = az + bz * s;
496
+ const dx = px - CLOSE[0];
497
+ const dy = py - CLOSE[1];
498
+ const dz = pz - CLOSE[2];
499
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
500
+ }
501
+ function rotatePoint(shape, pose, index, out, at) {
502
+ const x = shape.vertices[index * 3] ?? 0;
503
+ const y = shape.vertices[index * 3 + 1] ?? 0;
504
+ const z = shape.vertices[index * 3 + 2] ?? 0;
505
+ const tx = 2 * (pose.qy * z - pose.qz * y);
506
+ const ty = 2 * (pose.qz * x - pose.qx * z);
507
+ const tz = 2 * (pose.qx * y - pose.qy * x);
508
+ out[at] = x + pose.qw * tx + (pose.qy * tz - pose.qz * ty) + pose.x;
509
+ out[at + 1] = y + pose.qw * ty + (pose.qz * tx - pose.qx * tz) + pose.y;
510
+ out[at + 2] = z + pose.qw * tz + (pose.qx * ty - pose.qy * tx) + pose.z;
511
+ }
512
+ function worldPlanes(shape, pose, faces) {
513
+ const { qx, qy, qz, qw } = pose;
514
+ const r0 = 1 - 2 * (qy * qy + qz * qz);
515
+ const r1 = 2 * (qx * qy - qz * qw);
516
+ const r2 = 2 * (qx * qz + qy * qw);
517
+ const r3 = 2 * (qx * qy + qz * qw);
518
+ const r4 = 1 - 2 * (qx * qx + qz * qz);
519
+ const r5 = 2 * (qy * qz - qx * qw);
520
+ const r6 = 2 * (qx * qz - qy * qw);
521
+ const r7 = 2 * (qy * qz + qx * qw);
522
+ const r8 = 1 - 2 * (qx * qx + qy * qy);
523
+ for (let f = 0; f < faces; f++) {
524
+ const x = shape.facePlanes[f * 4] ?? 0;
525
+ const y = shape.facePlanes[f * 4 + 1] ?? 0;
526
+ const z = shape.facePlanes[f * 4 + 2] ?? 0;
527
+ const d = shape.facePlanes[f * 4 + 3] ?? 0;
528
+ const nx = r0 * x + r1 * y + r2 * z;
529
+ const ny = r3 * x + r4 * y + r5 * z;
530
+ const nz = r6 * x + r7 * y + r8 * z;
531
+ WORLD_PLANES[f * 4] = nx;
532
+ WORLD_PLANES[f * 4 + 1] = ny;
533
+ WORLD_PLANES[f * 4 + 2] = nz;
534
+ // A plane through a rotated point at the same offset, translated onto the pose.
535
+ WORLD_PLANES[f * 4 + 3] = d + nx * pose.x + ny * pose.y + nz * pose.z;
536
+ }
537
+ }
538
+ /**
539
+ * Every body overlapping a shape at a pose. Fills `out` to its length and returns how many.
540
+ *
541
+ * A return equal to `out.length` means the buffer filled, and the caller has been told about that
542
+ * many rather than about all of them. Returning the true total instead would invite a loop over
543
+ * entries that were never written.
544
+ */
545
+ export function overlapWorld(bodies, tree, shape, pose, out, filter) {
546
+ const r = shape.boundRadius;
547
+ box.minX = pose.x - r;
548
+ box.minY = pose.y - r;
549
+ box.minZ = pose.z - r;
550
+ box.maxX = pose.x + r;
551
+ box.maxY = pose.y + r;
552
+ box.maxZ = pose.z + r;
553
+ const n = candidates(tree, box);
554
+ let found = 0;
555
+ // Ascending body index, because the tree's order is insertion history.
556
+ for (let i = 0; i < bodies.count && found < out.length; i++) {
557
+ let seen = false;
558
+ for (let k = 0; k < n; k++) {
559
+ if (hits[k] === i) {
560
+ seen = true;
561
+ break;
562
+ }
563
+ }
564
+ if (!seen || !admits(bodies, i, filter))
565
+ continue;
566
+ const other = bodies.shape[i];
567
+ if (!other)
568
+ continue;
569
+ readPose(bodies, i, poseB);
570
+ /* A mesh answers through its own path, which fills a set; overlap only needs "any". */
571
+ const touching = isMesh(shape) || isMesh(other)
572
+ ? collideMesh(shape, pose, other, poseB, 0, MESH_ONE) > 0
573
+ : collideShapes(shape, pose, other, poseB, 0, manifold);
574
+ if (touching)
575
+ out[found++] = i;
576
+ }
577
+ return found;
578
+ }
579
+ /**
580
+ * Sweep a shape from one point to another and report the first body it touches.
581
+ *
582
+ * Conservative advancement: step by the current separation, which cannot overshoot a contact, and
583
+ * stop after a fixed number of steps. A fixed count rather than a tolerance, because §3a forbids
584
+ * iterating to one.
585
+ */
586
+ export function shapecastWorld(bodies, tree, shape, pose, dx, dy, dz, out, filter) {
587
+ const travel = Math.sqrt(dx * dx + dy * dy + dz * dz);
588
+ if (travel === 0)
589
+ return false;
590
+ const r = shape.boundRadius;
591
+ box.minX = Math.min(pose.x, pose.x + dx) - r;
592
+ box.minY = Math.min(pose.y, pose.y + dy) - r;
593
+ box.minZ = Math.min(pose.z, pose.z + dz) - r;
594
+ box.maxX = Math.max(pose.x, pose.x + dx) + r;
595
+ box.maxY = Math.max(pose.y, pose.y + dy) + r;
596
+ box.maxZ = Math.max(pose.z, pose.z + dz) + r;
597
+ const n = candidates(tree, box);
598
+ out.body = -1;
599
+ out.fraction = 1;
600
+ let best = Infinity;
601
+ const startX = pose.x;
602
+ const startY = pose.y;
603
+ const startZ = pose.z;
604
+ for (let k = 0; k < n; k++) {
605
+ const i = hits[k] ?? 0;
606
+ if (!admits(bodies, i, filter))
607
+ continue;
608
+ const other = bodies.shape[i];
609
+ if (!other)
610
+ continue;
611
+ readPose(bodies, i, poseB);
612
+ let t = 0;
613
+ let touched = false;
614
+ let brushing = false;
615
+ for (let step = 0; step < 24; step++) {
616
+ poseA.x = startX + dx * t;
617
+ poseA.y = startY + dy * t;
618
+ poseA.z = startZ + dz * t;
619
+ poseA.qx = pose.qx;
620
+ poseA.qy = pose.qy;
621
+ poseA.qz = pose.qz;
622
+ poseA.qw = pose.qw;
623
+ /*
624
+ * A mesh fills a set and the advance wants the *nearest* plane, which is the deepest
625
+ * separation of the first entry the tree returned — good enough for a conservative step,
626
+ * since a step short of the true one converges rather than missing.
627
+ */
628
+ if (isMesh(shape) || isMesh(other)) {
629
+ if (collideMesh(shape, poseA, other, poseB, 1e9, MESH_ONE) === 0)
630
+ break;
631
+ const first = MESH_ONE[0];
632
+ if (first === undefined)
633
+ break;
634
+ copyManifold(first, manifold);
635
+ }
636
+ else if (!collideShapes(shape, poseA, other, poseB, 1e9, manifold))
637
+ break;
638
+ const gap = manifold.separations[0] ?? 0;
639
+ if (gap < 1e-4) {
640
+ /*
641
+ * A shape already touching a surface it is travelling *along* has not run into it.
642
+ *
643
+ * Without this, a character resting on the floor is reported as blocked at fraction zero by
644
+ * every horizontal sweep, and never moves. `manifold.n` points from the swept shape into the
645
+ * body, so travel into it is a positive dot; zero or negative is brushing past.
646
+ */
647
+ if (step === 0 && dx * manifold.nx + dy * manifold.ny + dz * manifold.nz <= 1e-9) {
648
+ brushing = true;
649
+ break;
650
+ }
651
+ touched = true;
652
+ break;
653
+ }
654
+ t += gap / travel;
655
+ if (t > 1)
656
+ break;
657
+ }
658
+ if (brushing || !touched || t > 1)
659
+ continue;
660
+ if (t < best || (t === best && i < out.body)) {
661
+ best = t;
662
+ out.body = i;
663
+ out.fraction = t;
664
+ out.x = startX + dx * t;
665
+ out.y = startY + dy * t;
666
+ out.z = startZ + dz * t;
667
+ /*
668
+ * The **surface** normal, pointing out of the body toward the sweeper, which is the same
669
+ * convention `raycastWorld` answers in. `manifold.n` points the other way — from the swept
670
+ * shape into the body — and returning that made every caller that used both queries read a
671
+ * floor as a wall.
672
+ */
673
+ out.nx = -manifold.nx;
674
+ out.ny = -manifold.ny;
675
+ out.nz = -manifold.nz;
676
+ }
677
+ }
678
+ return out.body >= 0;
679
+ }
680
+ /** Whether a body is one a query should treat as scenery. */
681
+ export function isStaticBody(bodies, i) {
682
+ return bodies.type[i] === BODY_STATIC;
683
+ }