@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,340 @@
1
+ import type { ShapePose } from './manifold.ts';
2
+ import type { ConvexShape } from './shape.ts';
3
+
4
+ /**
5
+ * A cylinder resolved into world space, and the three questions contact generation asks of it.
6
+ *
7
+ * **This module is the whole of what `sideRadius` means geometrically**, kept apart from
8
+ * `cylinderContact.ts` so the arithmetic can be read and tested without a manifold in the way. A
9
+ * cylinder is the Minkowski sum of a segment and a disc perpendicular to it: its support along a
10
+ * direction `d` is the segment's support plus `radius` times the part of `d` that is perpendicular
11
+ * to the axis, which is one square root and no trigonometry.
12
+ *
13
+ * The three questions:
14
+ *
15
+ * - **How far does it reach along `d`** — `cylinderSupport`, which is what a separating-axis test
16
+ * needs and what makes the axis list exact for a curved surface it never enumerates.
17
+ * - **What is touching along `d`** — `cylinderFeature`, which answers with a cap disc, a side
18
+ * segment or a single rim point, because those are the only three a cylinder has.
19
+ * - **Where is the nearest surface point to `p`** — `closestOnCylinder`, which is exact in all
20
+ * four regions and is how a sphere, a capsule or a cloth particle meets one.
21
+ *
22
+ * **No `Math.cos` anywhere**, which is not a style choice: `scripts/determinism.test.mjs` refuses
23
+ * every function ECMAScript declines to specify precisely, and a cap disc generated by trigonometry
24
+ * per tick would be a different set of points on two engines. The eight directions come from a
25
+ * literal table instead, and everything else is arithmetic and `Math.sqrt`.
26
+ */
27
+
28
+ /** A cylinder in world space: centre, unit axis, half length, radius, and a spin-tracking basis. */
29
+ export const CYLINDER_FLOATS = 11;
30
+
31
+ /** How many points a cap disc contributes. Eight holds a disc flat without reducing to a square. */
32
+ export const CAP_POINTS = 8;
33
+
34
+ /**
35
+ * The eight unit directions of a cap disc, as literals.
36
+ *
37
+ * `Math.cos(k * Math.PI / 4)` is what these are, evaluated once by hand rather than per tick —
38
+ * `0.7071067811865476` is the double nearest √2/2. Written out because the determinism gate bans
39
+ * the call and because eight pairs are shorter than the loop that would derive them.
40
+ */
41
+ const CIRCLE = new Float64Array([
42
+ 1, 0, 0.7071067811865476, 0.7071067811865476, 0, 1, -0.7071067811865476, 0.7071067811865476, -1,
43
+ 0, -0.7071067811865476, -0.7071067811865476, 0, -1, 0.7071067811865476, -0.7071067811865476,
44
+ ]);
45
+
46
+ /**
47
+ * How parallel a direction must be to the axis before the whole cap is the touching feature, and
48
+ * how perpendicular before the whole side line is.
49
+ *
50
+ * Three degrees each way. **Generous on purpose**: a cylinder tilted by half a degree touches a
51
+ * plane at exactly one rim point, geometrically, and reporting that single point is what makes a
52
+ * resting wheel jitter. Two points get their separations measured against the reference plane
53
+ * individually, so the tilt is still resolved correctly — the constraint sees one contact deeper
54
+ * than the other, which is the truth — and the pair holds the shape steady meanwhile. Box-against-
55
+ * box clipping makes the same trade for the same reason.
56
+ */
57
+ const CAP_DOT = 0.99862953;
58
+ const SIDE_DOT = 0.05233596;
59
+
60
+ /** Whether a shape is a cylinder, which is the one question every dispatch above asks. */
61
+ export function isCylinder(shape: ConvexShape): boolean {
62
+ return shape.sideRadius > 0;
63
+ }
64
+
65
+ /**
66
+ * The pose a shape already in its own coordinates has.
67
+ *
68
+ * Exported because two callers work in a shape's local frame and would otherwise each declare
69
+ * their own identity quaternion — `readCylinder` costs nothing extra for one, since a rotation by
70
+ * the identity is the arithmetic it was going to do anyway.
71
+ */
72
+ export const LOCAL_POSE: ShapePose = { x: 0, y: 0, z: 0, qx: 0, qy: 0, qz: 0, qw: 1 };
73
+
74
+ /**
75
+ * Resolve a cylinder shape and its pose into `out`: centre, axis, half length, radius, basis.
76
+ *
77
+ * **The basis is the part worth explaining.** A cap disc's eight points have to be *the same eight
78
+ * points* from one tick to the next or their accumulated impulses are handed to the wrong contact,
79
+ * and a basis derived from the world axis alone stands still while the body spins about it. So it
80
+ * comes from the body's own local x, rotated by the pose and orthogonalised against the axis: it
81
+ * turns with the body, which is what a feature id has to do.
82
+ *
83
+ * A shape whose axis *is* local x — a cylinder baked into world space along that direction by a
84
+ * collider — falls back to local z, which cannot also be parallel to it.
85
+ */
86
+ export function readCylinder(shape: ConvexShape, pose: ShapePose, out: Float64Array): void {
87
+ rotate(pose, shape.vertices[0] ?? 0, shape.vertices[1] ?? 0, shape.vertices[2] ?? 0, END, 0);
88
+ rotate(pose, shape.vertices[3] ?? 0, shape.vertices[4] ?? 0, shape.vertices[5] ?? 0, END, 3);
89
+ out[0] = (END[0] + END[3]) / 2 + pose.x;
90
+ out[1] = (END[1] + END[4]) / 2 + pose.y;
91
+ out[2] = (END[2] + END[5]) / 2 + pose.z;
92
+ const dx = END[3] - END[0];
93
+ const dy = END[4] - END[1];
94
+ const dz = END[5] - END[2];
95
+ const length = Math.sqrt(dx * dx + dy * dy + dz * dz);
96
+ const inv = length > 1e-9 ? 1 / length : 0;
97
+ out[3] = length > 1e-9 ? dx * inv : 0;
98
+ out[4] = length > 1e-9 ? dy * inv : 1;
99
+ out[5] = length > 1e-9 ? dz * inv : 0;
100
+ out[6] = length / 2;
101
+ out[7] = shape.sideRadius;
102
+
103
+ rotate(pose, 1, 0, 0, END, 6);
104
+ let bx = END[6];
105
+ let by = END[7];
106
+ let bz = END[8];
107
+ let along = bx * out[3] + by * out[4] + bz * out[5];
108
+ if (Math.abs(along) > 0.9) {
109
+ rotate(pose, 0, 0, 1, END, 6);
110
+ bx = END[6];
111
+ by = END[7];
112
+ bz = END[8];
113
+ along = bx * out[3] + by * out[4] + bz * out[5];
114
+ }
115
+ bx -= out[3] * along;
116
+ by -= out[4] * along;
117
+ bz -= out[5] * along;
118
+ const blen = Math.sqrt(bx * bx + by * by + bz * bz) || 1;
119
+ out[8] = bx / blen;
120
+ out[9] = by / blen;
121
+ out[10] = bz / blen;
122
+ }
123
+
124
+ /** Scratch for the two endpoints and the candidate basis, so `readCylinder` allocates nothing. */
125
+ const END = new Float64Array(9);
126
+
127
+ /** How far the cylinder reaches along a unit direction: the highest `p · n` over its surface. */
128
+ export function cylinderSupport(cyl: Float64Array, nx: number, ny: number, nz: number): number {
129
+ const along = (cyl[3] ?? 0) * nx + (cyl[4] ?? 0) * ny + (cyl[5] ?? 0) * nz;
130
+ const across = Math.sqrt(Math.max(0, 1 - along * along));
131
+ return (
132
+ (cyl[0] ?? 0) * nx +
133
+ (cyl[1] ?? 0) * ny +
134
+ (cyl[2] ?? 0) * nz +
135
+ (cyl[6] ?? 0) * Math.abs(along) +
136
+ (cyl[7] ?? 0) * across
137
+ );
138
+ }
139
+
140
+ /** The lowest `p · n` over its surface, which is the support in the opposite direction. */
141
+ export function cylinderMin(cyl: Float64Array, nx: number, ny: number, nz: number): number {
142
+ const along = (cyl[3] ?? 0) * nx + (cyl[4] ?? 0) * ny + (cyl[5] ?? 0) * nz;
143
+ const across = Math.sqrt(Math.max(0, 1 - along * along));
144
+ return (
145
+ (cyl[0] ?? 0) * nx +
146
+ (cyl[1] ?? 0) * ny +
147
+ (cyl[2] ?? 0) * nz -
148
+ (cyl[6] ?? 0) * Math.abs(along) -
149
+ (cyl[7] ?? 0) * across
150
+ );
151
+ }
152
+
153
+ /**
154
+ * The feature touching along unit `n`, written xyz-packed into `out` with an index per point.
155
+ *
156
+ * Returns how many points were written, and **the count is the kind**: eight is a cap disc, two is
157
+ * the side line, one is a rim point. Nothing else can touch, which is why no separate tag is
158
+ * carried and why the caller reads the count instead.
159
+ *
160
+ * The indices are what a feature id is built from, and they are stable while the cylinder keeps its
161
+ * orientation: a cap point is its position around the basis, a side point is which end it is.
162
+ */
163
+ export function cylinderFeature(
164
+ cyl: Float64Array,
165
+ nx: number,
166
+ ny: number,
167
+ nz: number,
168
+ out: Float64Array,
169
+ ids: Int32Array,
170
+ ): number {
171
+ const cx = cyl[0] ?? 0;
172
+ const cy = cyl[1] ?? 0;
173
+ const cz = cyl[2] ?? 0;
174
+ const ax = cyl[3] ?? 0;
175
+ const ay = cyl[4] ?? 0;
176
+ const az = cyl[5] ?? 0;
177
+ const h = cyl[6] ?? 0;
178
+ const r = cyl[7] ?? 0;
179
+ const along = ax * nx + ay * ny + az * nz;
180
+
181
+ if (Math.abs(along) >= CAP_DOT) {
182
+ const side = along >= 0 ? 1 : -1;
183
+ const ux = cyl[8] ?? 0;
184
+ const uy = cyl[9] ?? 0;
185
+ const uz = cyl[10] ?? 0;
186
+ // The third basis vector, so the eight directions sweep the cap plane rather than a line.
187
+ const vx = ay * uz - az * uy;
188
+ const vy = az * ux - ax * uz;
189
+ const vz = ax * uy - ay * ux;
190
+ const ox = cx + ax * h * side;
191
+ const oy = cy + ay * h * side;
192
+ const oz = cz + az * h * side;
193
+ for (let i = 0; i < CAP_POINTS; i++) {
194
+ const c = CIRCLE[i * 2] ?? 0;
195
+ const s = CIRCLE[i * 2 + 1] ?? 0;
196
+ out[i * 3] = ox + (ux * c + vx * s) * r;
197
+ out[i * 3 + 1] = oy + (uy * c + vy * s) * r;
198
+ out[i * 3 + 2] = oz + (uz * c + vz * s) * r;
199
+ ids[i] = (side > 0 ? 0x10 : 0x20) | i;
200
+ }
201
+ return CAP_POINTS;
202
+ }
203
+
204
+ // The radial part of `n`, which is where the curved side reaches furthest.
205
+ let wx = nx - ax * along;
206
+ let wy = ny - ay * along;
207
+ let wz = nz - az * along;
208
+ const wlen = Math.sqrt(wx * wx + wy * wy + wz * wz) || 1;
209
+ wx /= wlen;
210
+ wy /= wlen;
211
+ wz /= wlen;
212
+
213
+ if (Math.abs(along) <= SIDE_DOT) {
214
+ out[0] = cx - ax * h + wx * r;
215
+ out[1] = cy - ay * h + wy * r;
216
+ out[2] = cz - az * h + wz * r;
217
+ out[3] = cx + ax * h + wx * r;
218
+ out[4] = cy + ay * h + wy * r;
219
+ out[5] = cz + az * h + wz * r;
220
+ ids[0] = 0x30;
221
+ ids[1] = 0x31;
222
+ return 2;
223
+ }
224
+
225
+ const side = along >= 0 ? 1 : -1;
226
+ out[0] = cx + ax * h * side + wx * r;
227
+ out[1] = cy + ay * h * side + wy * r;
228
+ out[2] = cz + az * h * side + wz * r;
229
+ ids[0] = side > 0 ? 0x40 : 0x41;
230
+ return 1;
231
+ }
232
+
233
+ /**
234
+ * The nearest point on a cylinder's surface to `p`, written as
235
+ * `[x, y, z, nx, ny, nz, signedDistance]` — the point, the outward direction there, and how far
236
+ * `p` sits from it. Negative distance means `p` is inside, and the direction is then the cheapest
237
+ * way out.
238
+ *
239
+ * Four regions, and each of them is exact: beside the side, past a cap, past a rim, or inside. The
240
+ * inside case picks the nearer of the side and the cap, which is what "cheapest way out" means and
241
+ * what keeps a deeply-buried particle from being pushed the long way through the solid.
242
+ */
243
+ export function closestOnCylinder(
244
+ cyl: Float64Array,
245
+ px: number,
246
+ py: number,
247
+ pz: number,
248
+ out: Float64Array,
249
+ ): void {
250
+ const cx = cyl[0] ?? 0;
251
+ const cy = cyl[1] ?? 0;
252
+ const cz = cyl[2] ?? 0;
253
+ const ax = cyl[3] ?? 0;
254
+ const ay = cyl[4] ?? 0;
255
+ const az = cyl[5] ?? 0;
256
+ const h = cyl[6] ?? 0;
257
+ const r = cyl[7] ?? 0;
258
+
259
+ const dx = px - cx;
260
+ const dy = py - cy;
261
+ const dz = pz - cz;
262
+ const t = dx * ax + dy * ay + dz * az;
263
+ let wx = dx - ax * t;
264
+ let wy = dy - ay * t;
265
+ let wz = dz - az * t;
266
+ const rho = Math.sqrt(wx * wx + wy * wy + wz * wz);
267
+ if (rho > 1e-9) {
268
+ wx /= rho;
269
+ wy /= rho;
270
+ wz /= rho;
271
+ } else {
272
+ // On the axis: any radial direction is as good as another, and the basis is the stable one.
273
+ wx = cyl[8] ?? 1;
274
+ wy = cyl[9] ?? 0;
275
+ wz = cyl[10] ?? 0;
276
+ }
277
+
278
+ if (rho <= r && Math.abs(t) <= h) {
279
+ const toSide = r - rho;
280
+ const toCap = h - Math.abs(t);
281
+ if (toSide <= toCap) {
282
+ out[0] = cx + ax * t + wx * r;
283
+ out[1] = cy + ay * t + wy * r;
284
+ out[2] = cz + az * t + wz * r;
285
+ out[3] = wx;
286
+ out[4] = wy;
287
+ out[5] = wz;
288
+ out[6] = -toSide;
289
+ return;
290
+ }
291
+ const side = t >= 0 ? 1 : -1;
292
+ out[0] = cx + ax * h * side + wx * rho;
293
+ out[1] = cy + ay * h * side + wy * rho;
294
+ out[2] = cz + az * h * side + wz * rho;
295
+ out[3] = ax * side;
296
+ out[4] = ay * side;
297
+ out[5] = az * side;
298
+ out[6] = -toCap;
299
+ return;
300
+ }
301
+
302
+ const tc = t < -h ? -h : t > h ? h : t;
303
+ const rc = rho < r ? rho : r;
304
+ const qx = cx + ax * tc + wx * rc;
305
+ const qy = cy + ay * tc + wy * rc;
306
+ const qz = cz + az * tc + wz * rc;
307
+ const ex = px - qx;
308
+ const ey = py - qy;
309
+ const ez = pz - qz;
310
+ const d = Math.sqrt(ex * ex + ey * ey + ez * ez);
311
+ out[0] = qx;
312
+ out[1] = qy;
313
+ out[2] = qz;
314
+ if (d > 1e-9) {
315
+ out[3] = ex / d;
316
+ out[4] = ey / d;
317
+ out[5] = ez / d;
318
+ } else {
319
+ out[3] = wx;
320
+ out[4] = wy;
321
+ out[5] = wz;
322
+ }
323
+ out[6] = d;
324
+ }
325
+
326
+ function rotate(
327
+ p: ShapePose,
328
+ x: number,
329
+ y: number,
330
+ z: number,
331
+ out: Float64Array,
332
+ at: number,
333
+ ): void {
334
+ const tx = 2 * (p.qy * z - p.qz * y);
335
+ const ty = 2 * (p.qz * x - p.qx * z);
336
+ const tz = 2 * (p.qx * y - p.qy * x);
337
+ out[at] = x + p.qw * tx + (p.qy * tz - p.qz * ty);
338
+ out[at + 1] = y + p.qw * ty + (p.qz * tx - p.qx * tz);
339
+ out[at + 2] = z + p.qw * tz + (p.qx * ty - p.qy * tx);
340
+ }