@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,704 @@
1
+ import { CAP_POINTS, closestOnCylinder, cylinderFeature, cylinderMin, cylinderSupport, isCylinder, readCylinder, } from './cylinder.js';
2
+ import { faceCount, faceVertices } from './faces.js';
3
+ /**
4
+ * Contact manifolds where one of the two shapes is a cylinder.
5
+ *
6
+ * **Three cases, like `manifold.ts`'s own three, and the split is what makes each exact.** A
7
+ * cylinder has a curved side that no enumerated axis points along, so the separating-axis list
8
+ * cannot be read off the shape the way a polytope's is. What replaces it is a list built *against
9
+ * the other shape* — its faces, its edges crossed with the axis, and the radial and rim directions
10
+ * of each of its vertices — with the cylinder's own support answering every one of them exactly.
11
+ * That is the difference between this and the n-gon prism it replaces: a prism's support is exact
12
+ * only at its own vertices, and a rolling wheel spends most of its time between them.
13
+ *
14
+ * - **against a sphere or a capsule** is the closest point on the cylinder to each of the one or
15
+ * two points the round shape is built on, which needs no axis list at all;
16
+ * - **against a polytope** is that axis list, then the touching feature — cap disc, side line or
17
+ * rim point — clipped against the polytope's incident face;
18
+ * - **against another cylinder** is four axes and the same feature clipping on both sides.
19
+ *
20
+ * **What the axis list gives up**, said plainly because it is the one approximation here: an edge
21
+ * of the polytope cutting across the *rim circle* between two of its own vertices is covered by
22
+ * the rim directions of those vertices rather than by an axis of its own, so a very long edge
23
+ * meeting a very small rim at a shallow angle can report a normal a fraction of a degree off.
24
+ * **What would make it wrong** is a consumer resting cylinders on thin rails, where the answer is
25
+ * a per-edge closest-approach to the rim circle, which costs a quartic solve per edge.
26
+ *
27
+ * **The cost, so nobody has to guess.** A cylinder against a box is 6 face axes, 1 cap axis, 24
28
+ * edge crosses and 16 vertex axes, each folding a support over 8 vertices — about 400 multiply-adds
29
+ * before any clipping, against roughly 100 for box against box. A wheel is worth it; a world of
30
+ * cylinders is what `sideRadius` on a hull would not have been.
31
+ */
32
+ const CYL_A = new Float64Array(11);
33
+ const CYL_B = new Float64Array(11);
34
+ const CLOSEST = new Float64Array(7);
35
+ /** The polytope's points and face planes in world space. `hullShape` caps a shape at 64 points. */
36
+ const POLY_W = new Float64Array(64 * 3);
37
+ const POLY_P = new Float64Array(64 * 4);
38
+ const LOOP = new Uint16Array(64);
39
+ /**
40
+ * A supporting feature: a cap's eight points, a side's two, or a rim's one. Two buffers, because
41
+ * cylinder against cylinder has a feature on each side — and 32 points rather than eight, because
42
+ * `clampFaceIntoCap` borrows the second one to carry a polytope face's loop.
43
+ */
44
+ const FEAT_A = new Float64Array(32 * 3);
45
+ const FEAT_B = new Float64Array(32 * 3);
46
+ const FEAT_A_ID = new Int32Array(32);
47
+ const FEAT_B_ID = new Int32Array(32);
48
+ /** Clipping ping-pongs between these; a face may add a vertex per side plane it cuts. */
49
+ const CLIP_IN = new Float64Array(32 * 3);
50
+ const CLIP_OUT = new Float64Array(32 * 3);
51
+ const CLIP_IN_ID = new Int32Array(32);
52
+ const CLIP_OUT_ID = new Int32Array(32);
53
+ /** The winning separating axis of the last test, and its separation. */
54
+ let satSep = 0;
55
+ let satNx = 0;
56
+ let satNy = 1;
57
+ let satNz = 0;
58
+ /** How many world points and planes the polytope currently in `POLY_W` has. */
59
+ let polyPoints = 0;
60
+ let polyFaces = 0;
61
+ /**
62
+ * Fill `out` for a pair where at least one shape is a cylinder. Returns whether they are within
63
+ * `margin` of touching, exactly as `collideShapes` does — this is that function's fourth case.
64
+ */
65
+ export function collideCylinder(shapeA, poseA, shapeB, poseB, margin, out) {
66
+ out.count = 0;
67
+ const radii = shapeA.radius + shapeB.radius;
68
+ if (isCylinder(shapeA) && isCylinder(shapeB)) {
69
+ readCylinder(shapeA, poseA, CYL_A);
70
+ readCylinder(shapeB, poseB, CYL_B);
71
+ return cylinderCylinder(margin, radii, out);
72
+ }
73
+ const cylinderIsA = isCylinder(shapeA);
74
+ const other = cylinderIsA ? shapeB : shapeA;
75
+ const otherPose = cylinderIsA ? poseB : poseA;
76
+ readCylinder(cylinderIsA ? shapeA : shapeB, cylinderIsA ? poseA : poseB, CYL_A);
77
+ // Everything below points cylinder-to-other; the manifold's normal points A-to-B.
78
+ const flipped = !cylinderIsA;
79
+ if (faceCount(other) === 0)
80
+ return cylinderRound(other, otherPose, margin, radii, out, flipped);
81
+ return cylinderPolytope(other, otherPose, margin, radii, out, flipped);
82
+ }
83
+ /* ---------- against a sphere or a capsule ---------- */
84
+ function cylinderRound(round, pose, margin, radii, out, flipped) {
85
+ const points = round.vertices.length / 3;
86
+ const capacity = out.separations.length;
87
+ let count = 0;
88
+ let bestSep = Infinity;
89
+ let nx = 0;
90
+ let ny = 1;
91
+ let nz = 0;
92
+ for (let p = 0; p < points && count < capacity; p++) {
93
+ rotate(pose, round.vertices[p * 3] ?? 0, round.vertices[p * 3 + 1] ?? 0, round.vertices[p * 3 + 2] ?? 0);
94
+ closestOnCylinder(CYL_A, ROT[0] + pose.x, ROT[1] + pose.y, ROT[2] + pose.z, CLOSEST);
95
+ const separation = CLOSEST[6] - radii;
96
+ if (separation > margin)
97
+ continue;
98
+ const sx = flipped ? -CLOSEST[3] : CLOSEST[3];
99
+ const sy = flipped ? -CLOSEST[4] : CLOSEST[4];
100
+ const sz = flipped ? -CLOSEST[5] : CLOSEST[5];
101
+ if (separation < bestSep) {
102
+ bestSep = separation;
103
+ nx = sx;
104
+ ny = sy;
105
+ nz = sz;
106
+ }
107
+ out.points[count * 3] = CLOSEST[0];
108
+ out.points[count * 3 + 1] = CLOSEST[1];
109
+ out.points[count * 3 + 2] = CLOSEST[2];
110
+ out.separations[count] = separation;
111
+ // The round shape's own point, which is stable while it keeps its orientation.
112
+ out.featureIds[count] = 0x50000000 | p;
113
+ count++;
114
+ }
115
+ if (count === 0)
116
+ return false;
117
+ // Both surfaces are curved here: a ball or a capsule on one side, a cylinder on the other.
118
+ out.curvedA = true;
119
+ out.curvedB = true;
120
+ out.nx = nx;
121
+ out.ny = ny;
122
+ out.nz = nz;
123
+ out.count = count;
124
+ return true;
125
+ }
126
+ /* ---------- against a polytope ---------- */
127
+ function cylinderPolytope(poly, pose, margin, radii, out, flipped) {
128
+ polyPoints = poly.vertices.length / 3;
129
+ polyFaces = faceCount(poly);
130
+ toWorld(poly, pose);
131
+ toWorldPlanes(poly, pose);
132
+ satSep = -Infinity;
133
+ const ax = CYL_A[3] ?? 0;
134
+ const ay = CYL_A[4] ?? 0;
135
+ const az = CYL_A[5] ?? 0;
136
+ for (let f = 0; f < polyFaces; f++) {
137
+ fold(POLY_P[f * 4] ?? 0, POLY_P[f * 4 + 1] ?? 0, POLY_P[f * 4 + 2] ?? 0);
138
+ }
139
+ fold(ax, ay, az);
140
+ // Every edge of the polytope crossed with the axis: the edge-against-side case.
141
+ for (let f = 0; f < polyFaces; f++) {
142
+ const n = faceVertices(poly, f, LOOP);
143
+ for (let i = 0; i < n; i++) {
144
+ const v0 = (LOOP[i] ?? 0) * 3;
145
+ const v1 = (LOOP[(i + 1) % n] ?? 0) * 3;
146
+ const ex = (POLY_W[v1] ?? 0) - (POLY_W[v0] ?? 0);
147
+ const ey = (POLY_W[v1 + 1] ?? 0) - (POLY_W[v0 + 1] ?? 0);
148
+ const ez = (POLY_W[v1 + 2] ?? 0) - (POLY_W[v0 + 2] ?? 0);
149
+ fold(ey * az - ez * ay, ez * ax - ex * az, ex * ay - ey * ax);
150
+ }
151
+ }
152
+ // Every vertex, twice: the direction out from the axis, and the direction to the nearest rim.
153
+ const cx = CYL_A[0] ?? 0;
154
+ const cy = CYL_A[1] ?? 0;
155
+ const cz = CYL_A[2] ?? 0;
156
+ const h = CYL_A[6] ?? 0;
157
+ const r = CYL_A[7] ?? 0;
158
+ for (let i = 0; i < polyPoints; i++) {
159
+ const dx = (POLY_W[i * 3] ?? 0) - cx;
160
+ const dy = (POLY_W[i * 3 + 1] ?? 0) - cy;
161
+ const dz = (POLY_W[i * 3 + 2] ?? 0) - cz;
162
+ const t = dx * ax + dy * ay + dz * az;
163
+ const wx = dx - ax * t;
164
+ const wy = dy - ay * t;
165
+ const wz = dz - az * t;
166
+ fold(wx, wy, wz);
167
+ const rho = Math.sqrt(wx * wx + wy * wy + wz * wz);
168
+ if (rho < 1e-9)
169
+ continue;
170
+ const side = t >= 0 ? h : -h;
171
+ fold(dx - (ax * side + (wx / rho) * r), dy - (ay * side + (wy / rho) * r), dz - (az * side + (wz / rho) * r));
172
+ }
173
+ if (satSep - radii > margin)
174
+ return false;
175
+ return polytopeContacts(poly, margin, radii, out, flipped);
176
+ }
177
+ /**
178
+ * Turn the winning axis into contact points.
179
+ *
180
+ * **Which shape is the reference is decided by alignment, not by which is curved.** A cylinder
181
+ * lying on a floor has the floor's face as its reference and its own side line as the incident
182
+ * feature; a cylinder standing on a corner has its own cap as the reference and the corner as the
183
+ * incident one. Getting that backwards produces a separation measured against a plane the contact
184
+ * is nowhere near, which reads as a solver that will not settle.
185
+ */
186
+ function polytopeContacts(poly, margin, radii, out, flipped) {
187
+ const nx = satNx;
188
+ const ny = satNy;
189
+ const nz = satNz;
190
+ const cylCount = cylinderFeature(CYL_A, nx, ny, nz, FEAT_A, FEAT_A_ID);
191
+ let face = 0;
192
+ let opposed = Infinity;
193
+ for (let f = 0; f < polyFaces; f++) {
194
+ const d = (POLY_P[f * 4] ?? 0) * nx + (POLY_P[f * 4 + 1] ?? 0) * ny + (POLY_P[f * 4 + 2] ?? 0) * nz;
195
+ if (d < opposed) {
196
+ opposed = d;
197
+ face = f;
198
+ }
199
+ }
200
+ const capAligned = cylCount === CAP_POINTS
201
+ ? Math.abs((CYL_A[3] ?? 0) * nx + (CYL_A[4] ?? 0) * ny + (CYL_A[5] ?? 0) * nz)
202
+ : -1;
203
+ let count;
204
+ if (capAligned > Math.abs(opposed)) {
205
+ count = clampFaceIntoCap(poly, face, nx, ny, nz);
206
+ }
207
+ else {
208
+ count = clipFeatureAgainstFace(poly, face, cylCount);
209
+ measureAgainstPlane(count, POLY_P[face * 4] ?? 0, POLY_P[face * 4 + 1] ?? 0, POLY_P[face * 4 + 2] ?? 0, POLY_P[face * 4 + 3] ?? 0);
210
+ }
211
+ if (count === 0)
212
+ return false;
213
+ /*
214
+ * A cap is flat and turns with the body, so its anchor belongs in the body's frame like any
215
+ * face's. The side and the rim do not: a rolling cylinder touches the floor at its lowest point
216
+ * however far it has spun, and an anchor that turns with it climbs the side. The polytope's own
217
+ * anchor is never curved.
218
+ */
219
+ return emit(count, margin, radii, out, flipped, nx, ny, nz, cylCount !== CAP_POINTS, false);
220
+ }
221
+ /**
222
+ * The polytope's incident face, carried into `clampIntoCap` through the second feature buffer.
223
+ *
224
+ * One function does the clamping for both cases — a polytope face resting on a cap, and another
225
+ * cylinder's feature resting on one — because they differ only in where the points came from and
226
+ * what their ids should say. A face loop longer than the buffer is truncated, which cannot happen
227
+ * for any shape `hullShape` builds at the sizes it accepts.
228
+ */
229
+ function clampFaceIntoCap(poly, face, nx, ny, nz) {
230
+ const n = Math.min(faceVertices(poly, face, LOOP), 32);
231
+ for (let i = 0; i < n; i++) {
232
+ const v = (LOOP[i] ?? 0) * 3;
233
+ FEAT_B[i * 3] = POLY_W[v] ?? 0;
234
+ FEAT_B[i * 3 + 1] = POLY_W[v + 1] ?? 0;
235
+ FEAT_B[i * 3 + 2] = POLY_W[v + 2] ?? 0;
236
+ FEAT_B_ID[i] = (face << 8) | (LOOP[i] ?? 0);
237
+ }
238
+ return clampIntoCap(CYL_A, nx, ny, nz, FEAT_B, FEAT_B_ID, n, 0x51000000);
239
+ }
240
+ /** Signed distances of the clipped points from the reference plane, filled beside `CLIP_IN`. */
241
+ const SEPARATION = new Float64Array(32);
242
+ function measureAgainstPlane(count, nx, ny, nz, d) {
243
+ for (let i = 0; i < count; i++) {
244
+ SEPARATION[i] =
245
+ (CLIP_IN[i * 3] ?? 0) * nx +
246
+ (CLIP_IN[i * 3 + 1] ?? 0) * ny +
247
+ (CLIP_IN[i * 3 + 2] ?? 0) * nz -
248
+ d;
249
+ }
250
+ }
251
+ /**
252
+ * Clip the cylinder's touching feature against the side planes of the polytope's incident face.
253
+ *
254
+ * Three shapes of feature, three clips: a cap disc is a closed polygon and takes Sutherland-
255
+ * Hodgman; a side line is a segment and takes a parametric interval, which keeps both endpoints
256
+ * meaningful where treating it as a two-vertex polygon would duplicate them; a rim point is kept
257
+ * or dropped.
258
+ */
259
+ function clipFeatureAgainstFace(poly, face, cylCount) {
260
+ const refCount = faceVertices(poly, face, LOOP);
261
+ const rnx = POLY_P[face * 4] ?? 0;
262
+ const rny = POLY_P[face * 4 + 1] ?? 0;
263
+ const rnz = POLY_P[face * 4 + 2] ?? 0;
264
+ for (let i = 0; i < cylCount * 3; i++)
265
+ CLIP_IN[i] = FEAT_A[i] ?? 0;
266
+ for (let i = 0; i < cylCount; i++)
267
+ CLIP_IN_ID[i] = 0x52000000 | (face << 8) | (FEAT_A_ID[i] ?? 0);
268
+ let count = cylCount;
269
+ let lo = 0;
270
+ let hi = 1;
271
+ for (let e = 0; e < refCount && count > 0; e++) {
272
+ const v0 = (LOOP[e] ?? 0) * 3;
273
+ const v1 = (LOOP[(e + 1) % refCount] ?? 0) * 3;
274
+ const ex = (POLY_W[v1] ?? 0) - (POLY_W[v0] ?? 0);
275
+ const ey = (POLY_W[v1 + 1] ?? 0) - (POLY_W[v0 + 1] ?? 0);
276
+ const ez = (POLY_W[v1 + 2] ?? 0) - (POLY_W[v0 + 2] ?? 0);
277
+ let sx = ey * rnz - ez * rny;
278
+ let sy = ez * rnx - ex * rnz;
279
+ let sz = ex * rny - ey * rnx;
280
+ const len = Math.sqrt(sx * sx + sy * sy + sz * sz);
281
+ if (len === 0)
282
+ continue;
283
+ sx /= len;
284
+ sy /= len;
285
+ sz /= len;
286
+ const sd = sx * (POLY_W[v0] ?? 0) + sy * (POLY_W[v0 + 1] ?? 0) + sz * (POLY_W[v0 + 2] ?? 0);
287
+ if (cylCount >= 3) {
288
+ count = clipPolygon(count, sx, sy, sz, sd);
289
+ continue;
290
+ }
291
+ const d0 = (CLIP_IN[0] ?? 0) * sx + (CLIP_IN[1] ?? 0) * sy + (CLIP_IN[2] ?? 0) * sz - sd;
292
+ if (cylCount === 1) {
293
+ if (d0 > 0)
294
+ return 0;
295
+ continue;
296
+ }
297
+ const d1 = (CLIP_IN[3] ?? 0) * sx + (CLIP_IN[4] ?? 0) * sy + (CLIP_IN[5] ?? 0) * sz - sd;
298
+ if (d0 > 0 && d1 > 0)
299
+ return 0;
300
+ if (d0 !== d1) {
301
+ const t = d0 / (d0 - d1);
302
+ if (d0 > 0 && t > lo)
303
+ lo = t;
304
+ if (d1 > 0 && t < hi)
305
+ hi = t;
306
+ }
307
+ if (lo > hi)
308
+ return 0;
309
+ }
310
+ if (cylCount === 2 && count === 2) {
311
+ const x0 = CLIP_IN[0] ?? 0;
312
+ const y0 = CLIP_IN[1] ?? 0;
313
+ const z0 = CLIP_IN[2] ?? 0;
314
+ const dx = (CLIP_IN[3] ?? 0) - x0;
315
+ const dy = (CLIP_IN[4] ?? 0) - y0;
316
+ const dz = (CLIP_IN[5] ?? 0) - z0;
317
+ CLIP_IN[0] = x0 + dx * lo;
318
+ CLIP_IN[1] = y0 + dy * lo;
319
+ CLIP_IN[2] = z0 + dz * lo;
320
+ CLIP_IN[3] = x0 + dx * hi;
321
+ CLIP_IN[4] = y0 + dy * hi;
322
+ CLIP_IN[5] = z0 + dz * hi;
323
+ }
324
+ return count;
325
+ }
326
+ /** Sutherland-Hodgman against one side plane, in place over `CLIP_IN`. */
327
+ function clipPolygon(count, nx, ny, nz, d) {
328
+ let out = 0;
329
+ for (let i = 0; i < count; i++) {
330
+ const j = (i + 1) % count;
331
+ const ax = CLIP_IN[i * 3] ?? 0;
332
+ const ay = CLIP_IN[i * 3 + 1] ?? 0;
333
+ const az = CLIP_IN[i * 3 + 2] ?? 0;
334
+ const bx = CLIP_IN[j * 3] ?? 0;
335
+ const by = CLIP_IN[j * 3 + 1] ?? 0;
336
+ const bz = CLIP_IN[j * 3 + 2] ?? 0;
337
+ const da = ax * nx + ay * ny + az * nz - d;
338
+ const db = bx * nx + by * ny + bz * nz - d;
339
+ if (da <= 0 && out < 32) {
340
+ CLIP_OUT[out * 3] = ax;
341
+ CLIP_OUT[out * 3 + 1] = ay;
342
+ CLIP_OUT[out * 3 + 2] = az;
343
+ CLIP_OUT_ID[out] = CLIP_IN_ID[i] ?? 0;
344
+ out++;
345
+ }
346
+ if (da * db < 0 && out < 32) {
347
+ const t = da / (da - db);
348
+ CLIP_OUT[out * 3] = ax + (bx - ax) * t;
349
+ CLIP_OUT[out * 3 + 1] = ay + (by - ay) * t;
350
+ CLIP_OUT[out * 3 + 2] = az + (bz - az) * t;
351
+ CLIP_OUT_ID[out] = CLIP_IN_ID[i] ?? 0;
352
+ out++;
353
+ }
354
+ }
355
+ for (let i = 0; i < out * 3; i++)
356
+ CLIP_IN[i] = CLIP_OUT[i] ?? 0;
357
+ for (let i = 0; i < out; i++)
358
+ CLIP_IN_ID[i] = CLIP_OUT_ID[i] ?? 0;
359
+ return out;
360
+ }
361
+ /* ---------- against another cylinder ---------- */
362
+ function cylinderCylinder(margin, radii, out) {
363
+ satSep = -Infinity;
364
+ const ax = CYL_A[3] ?? 0;
365
+ const ay = CYL_A[4] ?? 0;
366
+ const az = CYL_A[5] ?? 0;
367
+ const bx = CYL_B[3] ?? 0;
368
+ const by = CYL_B[4] ?? 0;
369
+ const bz = CYL_B[5] ?? 0;
370
+ foldPair(ax, ay, az);
371
+ foldPair(bx, by, bz);
372
+ foldPair(ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx);
373
+ axisClosest();
374
+ foldPair(SEG[3] - SEG[0], SEG[4] - SEG[1], SEG[5] - SEG[2]);
375
+ if (satSep - radii > margin)
376
+ return false;
377
+ const nx = satNx;
378
+ const ny = satNy;
379
+ const nz = satNz;
380
+ const countA = cylinderFeature(CYL_A, nx, ny, nz, FEAT_A, FEAT_A_ID);
381
+ const countB = cylinderFeature(CYL_B, -nx, -ny, -nz, FEAT_B, FEAT_B_ID);
382
+ const alignA = countA === CAP_POINTS ? Math.abs(ax * nx + ay * ny + az * nz) : -1;
383
+ const alignB = countB === CAP_POINTS ? Math.abs(bx * nx + by * ny + bz * nz) : -1;
384
+ let count;
385
+ if (alignA >= alignB && alignA >= 0) {
386
+ count = clampIntoCap(CYL_A, nx, ny, nz, FEAT_B, FEAT_B_ID, countB, 0x53000000);
387
+ }
388
+ else if (alignB >= 0) {
389
+ count = clampIntoCap(CYL_B, -nx, -ny, -nz, FEAT_A, FEAT_A_ID, countA, 0x53000000);
390
+ }
391
+ else {
392
+ count = sideAgainstSide(countA, countB, radii);
393
+ }
394
+ if (count === 0)
395
+ return false;
396
+ return emit(count, margin, radii, out, false, nx, ny, nz, countA !== CAP_POINTS, countB !== CAP_POINTS);
397
+ }
398
+ /**
399
+ * A set of points pulled inside a cylinder's cap disc, with separations from that cap's plane.
400
+ *
401
+ * **A circle has no side planes to clip against**, so a point outside the disc is moved radially
402
+ * onto its rim rather than dropped. *What that gives up* is a contact placed at the rim where the
403
+ * true touching region ends slightly inside it, by at most the disc's curvature over one edge.
404
+ * *What dropping it instead would give up* is the contact entirely, which is worse by a wide
405
+ * margin.
406
+ *
407
+ * `dx, dy, dz` points from the cap's own cylinder toward whatever is resting on it, which is what
408
+ * picks which of the two caps is doing the work. `kind` is the high byte of the feature ids, so a
409
+ * contact made this way is never confused with one made another.
410
+ */
411
+ function clampIntoCap(cyl, dx, dy, dz, feature, ids, featureCount, kind) {
412
+ const side = (cyl[3] ?? 0) * dx + (cyl[4] ?? 0) * dy + (cyl[5] ?? 0) * dz >= 0 ? 1 : -1;
413
+ const ax = (cyl[3] ?? 0) * side;
414
+ const ay = (cyl[4] ?? 0) * side;
415
+ const az = (cyl[5] ?? 0) * side;
416
+ const h = cyl[6] ?? 0;
417
+ const r = cyl[7] ?? 0;
418
+ const px = (cyl[0] ?? 0) + ax * h;
419
+ const py = (cyl[1] ?? 0) + ay * h;
420
+ const pz = (cyl[2] ?? 0) + az * h;
421
+ const plane = px * ax + py * ay + pz * az;
422
+ let count = 0;
423
+ for (let i = 0; i < featureCount && count < 32; i++) {
424
+ const qx = feature[i * 3] ?? 0;
425
+ const qy = feature[i * 3 + 1] ?? 0;
426
+ const qz = feature[i * 3 + 2] ?? 0;
427
+ const t = (qx - px) * ax + (qy - py) * ay + (qz - pz) * az;
428
+ let wx = qx - px - ax * t;
429
+ let wy = qy - py - ay * t;
430
+ let wz = qz - pz - az * t;
431
+ const rho = Math.sqrt(wx * wx + wy * wy + wz * wz);
432
+ if (rho > r && rho > 1e-9) {
433
+ const scale = r / rho;
434
+ wx *= scale;
435
+ wy *= scale;
436
+ wz *= scale;
437
+ }
438
+ CLIP_IN[count * 3] = px + wx + ax * t;
439
+ CLIP_IN[count * 3 + 1] = py + wy + ay * t;
440
+ CLIP_IN[count * 3 + 2] = pz + wz + az * t;
441
+ CLIP_IN_ID[count] = kind | (ids[i] ?? 0);
442
+ SEPARATION[count] =
443
+ (CLIP_IN[count * 3] ?? 0) * ax +
444
+ (CLIP_IN[count * 3 + 1] ?? 0) * ay +
445
+ (CLIP_IN[count * 3 + 2] ?? 0) * az -
446
+ plane;
447
+ count++;
448
+ }
449
+ return count;
450
+ }
451
+ /**
452
+ * Two curved sides meeting, which is one point unless the two axes are parallel.
453
+ *
454
+ * Parallel is the case worth two contacts: two pipes lying side by side hold each other steady
455
+ * only if the contact has length. Crossed axes touch at exactly one point and reporting two would
456
+ * invent a stability the geometry does not have.
457
+ */
458
+ function sideAgainstSide(countA, countB, radii) {
459
+ const sep = satSep - radii;
460
+ if (countA === 2 && countB === 2) {
461
+ let ex = (FEAT_A[3] ?? 0) - (FEAT_A[0] ?? 0);
462
+ let ey = (FEAT_A[4] ?? 0) - (FEAT_A[1] ?? 0);
463
+ let ez = (FEAT_A[5] ?? 0) - (FEAT_A[2] ?? 0);
464
+ const elen = Math.sqrt(ex * ex + ey * ey + ez * ez);
465
+ let fx = (FEAT_B[3] ?? 0) - (FEAT_B[0] ?? 0);
466
+ let fy = (FEAT_B[4] ?? 0) - (FEAT_B[1] ?? 0);
467
+ let fz = (FEAT_B[5] ?? 0) - (FEAT_B[2] ?? 0);
468
+ const flen = Math.sqrt(fx * fx + fy * fy + fz * fz);
469
+ if (elen > 1e-9 && flen > 1e-9) {
470
+ ex /= elen;
471
+ ey /= elen;
472
+ ez /= elen;
473
+ fx /= flen;
474
+ fy /= flen;
475
+ fz /= flen;
476
+ if (Math.abs(ex * fx + ey * fy + ez * fz) > 0.999) {
477
+ // The overlap of B's segment on A's, in A's own parameter.
478
+ const b0 = ((FEAT_B[0] ?? 0) - (FEAT_A[0] ?? 0)) * ex +
479
+ ((FEAT_B[1] ?? 0) - (FEAT_A[1] ?? 0)) * ey +
480
+ ((FEAT_B[2] ?? 0) - (FEAT_A[2] ?? 0)) * ez;
481
+ const b1 = ((FEAT_B[3] ?? 0) - (FEAT_A[0] ?? 0)) * ex +
482
+ ((FEAT_B[4] ?? 0) - (FEAT_A[1] ?? 0)) * ey +
483
+ ((FEAT_B[5] ?? 0) - (FEAT_A[2] ?? 0)) * ez;
484
+ const lo = Math.max(0, Math.min(b0, b1));
485
+ const hi = Math.min(elen, Math.max(b0, b1));
486
+ if (hi > lo) {
487
+ for (let i = 0; i < 2; i++) {
488
+ const t = i === 0 ? lo : hi;
489
+ CLIP_IN[i * 3] = (FEAT_A[0] ?? 0) + ex * t;
490
+ CLIP_IN[i * 3 + 1] = (FEAT_A[1] ?? 0) + ey * t;
491
+ CLIP_IN[i * 3 + 2] = (FEAT_A[2] ?? 0) + ez * t;
492
+ CLIP_IN_ID[i] = 0x54000000 | i;
493
+ SEPARATION[i] = sep;
494
+ }
495
+ return 2;
496
+ }
497
+ }
498
+ }
499
+ }
500
+ // One point, midway between the two supports, which is where the constraint should act.
501
+ CLIP_IN[0] = ((FEAT_A[0] ?? 0) + (FEAT_B[0] ?? 0)) / 2;
502
+ CLIP_IN[1] = ((FEAT_A[1] ?? 0) + (FEAT_B[1] ?? 0)) / 2;
503
+ CLIP_IN[2] = ((FEAT_A[2] ?? 0) + (FEAT_B[2] ?? 0)) / 2;
504
+ CLIP_IN_ID[0] = 0x55000000 | ((FEAT_A_ID[0] ?? 0) << 8) | (FEAT_B_ID[0] ?? 0);
505
+ SEPARATION[0] = sep;
506
+ return 1;
507
+ }
508
+ /* ---------- shared ---------- */
509
+ /**
510
+ * Write the clipped points into the manifold, keeping the deepest where there are too many.
511
+ *
512
+ * `radii` is the uniform rounding both shapes may carry on top of their own geometry — a
513
+ * Minkowski sum with a ball, which shifts every separation by a constant and nothing else. That is
514
+ * the same treatment `polytopePolytope` gives it, and it is exact rather than an approximation.
515
+ */
516
+ function emit(count, margin, radii, out, flipped, nx, ny, nz, curvedCylinder, curvedOther) {
517
+ const capacity = out.separations.length;
518
+ let kept = 0;
519
+ for (let i = 0; i < count; i++) {
520
+ const separation = (SEPARATION[i] ?? 0) - radii;
521
+ if (separation > margin)
522
+ continue;
523
+ if (kept < capacity) {
524
+ out.points[kept * 3] = CLIP_IN[i * 3] ?? 0;
525
+ out.points[kept * 3 + 1] = CLIP_IN[i * 3 + 1] ?? 0;
526
+ out.points[kept * 3 + 2] = CLIP_IN[i * 3 + 2] ?? 0;
527
+ out.separations[kept] = separation;
528
+ out.featureIds[kept] = CLIP_IN_ID[i] ?? 0;
529
+ kept++;
530
+ continue;
531
+ }
532
+ let worst = 0;
533
+ for (let k = 1; k < capacity; k++) {
534
+ if ((out.separations[k] ?? 0) > (out.separations[worst] ?? 0))
535
+ worst = k;
536
+ }
537
+ if (separation < (out.separations[worst] ?? 0)) {
538
+ out.points[worst * 3] = CLIP_IN[i * 3] ?? 0;
539
+ out.points[worst * 3 + 1] = CLIP_IN[i * 3 + 1] ?? 0;
540
+ out.points[worst * 3 + 2] = CLIP_IN[i * 3 + 2] ?? 0;
541
+ out.separations[worst] = separation;
542
+ out.featureIds[worst] = CLIP_IN_ID[i] ?? 0;
543
+ }
544
+ }
545
+ if (kept === 0)
546
+ return false;
547
+ out.nx = flipped ? -nx : nx;
548
+ out.ny = flipped ? -ny : ny;
549
+ out.nz = flipped ? -nz : nz;
550
+ out.curvedA = flipped ? curvedOther : curvedCylinder;
551
+ out.curvedB = flipped ? curvedCylinder : curvedOther;
552
+ out.count = kept;
553
+ return true;
554
+ }
555
+ /** Fold one candidate axis, both ways round, into the best separation seen. */
556
+ function fold(x, y, z) {
557
+ const len = Math.sqrt(x * x + y * y + z * z);
558
+ if (len < 1e-9)
559
+ return;
560
+ const nx = x / len;
561
+ const ny = y / len;
562
+ const nz = z / len;
563
+ let sep = polyMin(nx, ny, nz) - cylinderSupport(CYL_A, nx, ny, nz);
564
+ if (sep > satSep) {
565
+ satSep = sep;
566
+ satNx = nx;
567
+ satNy = ny;
568
+ satNz = nz;
569
+ }
570
+ sep = cylinderMin(CYL_A, nx, ny, nz) - polyMax(nx, ny, nz);
571
+ if (sep > satSep) {
572
+ satSep = sep;
573
+ satNx = -nx;
574
+ satNy = -ny;
575
+ satNz = -nz;
576
+ }
577
+ }
578
+ /** The same, for two cylinders. */
579
+ function foldPair(x, y, z) {
580
+ const len = Math.sqrt(x * x + y * y + z * z);
581
+ if (len < 1e-9)
582
+ return;
583
+ const nx = x / len;
584
+ const ny = y / len;
585
+ const nz = z / len;
586
+ let sep = cylinderMin(CYL_B, nx, ny, nz) - cylinderSupport(CYL_A, nx, ny, nz);
587
+ if (sep > satSep) {
588
+ satSep = sep;
589
+ satNx = nx;
590
+ satNy = ny;
591
+ satNz = nz;
592
+ }
593
+ sep = cylinderMin(CYL_A, nx, ny, nz) - cylinderSupport(CYL_B, nx, ny, nz);
594
+ if (sep > satSep) {
595
+ satSep = sep;
596
+ satNx = -nx;
597
+ satNy = -ny;
598
+ satNz = -nz;
599
+ }
600
+ }
601
+ function polyMin(nx, ny, nz) {
602
+ let lo = Infinity;
603
+ for (let i = 0; i < polyPoints; i++) {
604
+ const d = (POLY_W[i * 3] ?? 0) * nx + (POLY_W[i * 3 + 1] ?? 0) * ny + (POLY_W[i * 3 + 2] ?? 0) * nz;
605
+ if (d < lo)
606
+ lo = d;
607
+ }
608
+ return lo;
609
+ }
610
+ function polyMax(nx, ny, nz) {
611
+ let hi = -Infinity;
612
+ for (let i = 0; i < polyPoints; i++) {
613
+ const d = (POLY_W[i * 3] ?? 0) * nx + (POLY_W[i * 3 + 1] ?? 0) * ny + (POLY_W[i * 3 + 2] ?? 0) * nz;
614
+ if (d > hi)
615
+ hi = d;
616
+ }
617
+ return hi;
618
+ }
619
+ /** The two closest points of the cylinders' axis segments, written into `SEG`. */
620
+ const SEG = new Float64Array(6);
621
+ function axisClosest() {
622
+ const px = (CYL_A[0] ?? 0) - (CYL_A[3] ?? 0) * (CYL_A[6] ?? 0);
623
+ const py = (CYL_A[1] ?? 0) - (CYL_A[4] ?? 0) * (CYL_A[6] ?? 0);
624
+ const pz = (CYL_A[2] ?? 0) - (CYL_A[5] ?? 0) * (CYL_A[6] ?? 0);
625
+ const dx = (CYL_A[3] ?? 0) * 2 * (CYL_A[6] ?? 0);
626
+ const dy = (CYL_A[4] ?? 0) * 2 * (CYL_A[6] ?? 0);
627
+ const dz = (CYL_A[5] ?? 0) * 2 * (CYL_A[6] ?? 0);
628
+ const qx = (CYL_B[0] ?? 0) - (CYL_B[3] ?? 0) * (CYL_B[6] ?? 0);
629
+ const qy = (CYL_B[1] ?? 0) - (CYL_B[4] ?? 0) * (CYL_B[6] ?? 0);
630
+ const qz = (CYL_B[2] ?? 0) - (CYL_B[5] ?? 0) * (CYL_B[6] ?? 0);
631
+ const ex = (CYL_B[3] ?? 0) * 2 * (CYL_B[6] ?? 0);
632
+ const ey = (CYL_B[4] ?? 0) * 2 * (CYL_B[6] ?? 0);
633
+ const ez = (CYL_B[5] ?? 0) * 2 * (CYL_B[6] ?? 0);
634
+ const rx = px - qx;
635
+ const ry = py - qy;
636
+ const rz = pz - qz;
637
+ const a = dx * dx + dy * dy + dz * dz;
638
+ const e = ex * ex + ey * ey + ez * ez;
639
+ const f = ex * rx + ey * ry + ez * rz;
640
+ let s = 0;
641
+ let t = 0;
642
+ if (a > 1e-12 || e > 1e-12) {
643
+ if (a <= 1e-12) {
644
+ t = clamp01(f / e);
645
+ }
646
+ else {
647
+ const c = dx * rx + dy * ry + dz * rz;
648
+ if (e <= 1e-12) {
649
+ s = clamp01(-c / a);
650
+ }
651
+ else {
652
+ const b = dx * ex + dy * ey + dz * ez;
653
+ const denom = a * e - b * b;
654
+ s = denom !== 0 ? clamp01((b * f - c * e) / denom) : 0;
655
+ t = (b * s + f) / e;
656
+ if (t < 0) {
657
+ t = 0;
658
+ s = clamp01(-c / a);
659
+ }
660
+ else if (t > 1) {
661
+ t = 1;
662
+ s = clamp01((b - c) / a);
663
+ }
664
+ }
665
+ }
666
+ }
667
+ SEG[0] = px + dx * s;
668
+ SEG[1] = py + dy * s;
669
+ SEG[2] = pz + dz * s;
670
+ SEG[3] = qx + ex * t;
671
+ SEG[4] = qy + ey * t;
672
+ SEG[5] = qz + ez * t;
673
+ }
674
+ function clamp01(v) {
675
+ return v < 0 ? 0 : v > 1 ? 1 : v;
676
+ }
677
+ const ROT = new Float64Array(3);
678
+ function rotate(p, x, y, z) {
679
+ const tx = 2 * (p.qy * z - p.qz * y);
680
+ const ty = 2 * (p.qz * x - p.qx * z);
681
+ const tz = 2 * (p.qx * y - p.qy * x);
682
+ ROT[0] = x + p.qw * tx + (p.qy * tz - p.qz * ty);
683
+ ROT[1] = y + p.qw * ty + (p.qz * tx - p.qx * tz);
684
+ ROT[2] = z + p.qw * tz + (p.qx * ty - p.qy * tx);
685
+ }
686
+ function toWorld(shape, pose) {
687
+ for (let i = 0; i < polyPoints; i++) {
688
+ rotate(pose, shape.vertices[i * 3] ?? 0, shape.vertices[i * 3 + 1] ?? 0, shape.vertices[i * 3 + 2] ?? 0);
689
+ POLY_W[i * 3] = ROT[0] + pose.x;
690
+ POLY_W[i * 3 + 1] = ROT[1] + pose.y;
691
+ POLY_W[i * 3 + 2] = ROT[2] + pose.z;
692
+ }
693
+ }
694
+ function toWorldPlanes(shape, pose) {
695
+ for (let f = 0; f < polyFaces; f++) {
696
+ rotate(pose, shape.facePlanes[f * 4] ?? 0, shape.facePlanes[f * 4 + 1] ?? 0, shape.facePlanes[f * 4 + 2] ?? 0);
697
+ const v = (shape.faceVertexIndices[shape.faceVertexStart[f] ?? 0] ?? 0) * 3;
698
+ POLY_P[f * 4] = ROT[0];
699
+ POLY_P[f * 4 + 1] = ROT[1];
700
+ POLY_P[f * 4 + 2] = ROT[2];
701
+ POLY_P[f * 4 + 3] =
702
+ ROT[0] * (POLY_W[v] ?? 0) + ROT[1] * (POLY_W[v + 1] ?? 0) + ROT[2] * (POLY_W[v + 2] ?? 0);
703
+ }
704
+ }