@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/tree.js ADDED
@@ -0,0 +1,348 @@
1
+ /**
2
+ * A dynamic bounding-volume tree over moving proxies.
3
+ *
4
+ * **Two structures answer two questions, and this is the second one.** `ColliderSet`'s uniform hash
5
+ * is right for static geometry: built once, from solids of known extent, never touched again. It is
6
+ * wrong for bodies, whose sizes vary by orders of magnitude and which move every tick — a large
7
+ * body straddles hundreds of cells and every body rehashes. *What would make this wrong* is a
8
+ * measurement showing the tree beats the hash on static geometry too, at which point they collapse
9
+ * into one and the hash goes.
10
+ *
11
+ * **Fat bounds are what make it incremental.** A proxy re-inserts only when its tight box leaves
12
+ * the margin it was given, so a body drifting slowly costs nothing but a comparison.
13
+ *
14
+ * **Nothing downstream may see this tree.** Insertion order decides tree shape and tree shape
15
+ * decides enumeration order, and with sequential impulses enumeration order changes the result.
16
+ * `pairs.ts` sorts by body index for exactly that reason, and this file's ordering is therefore
17
+ * free to be whatever the heuristic produces.
18
+ */
19
+ /** How far a proxy's stored box is grown past its tight one, in metres. */
20
+ const MARGIN = 0.1;
21
+ /** A node with no child. Chosen over −1 so the arrays can stay unsigned. */
22
+ export const NIL = 0x7fffffff;
23
+ export class DynamicTree {
24
+ root = NIL;
25
+ /** minX, minY, minZ, maxX, maxY, maxZ per node. */
26
+ bounds;
27
+ parent;
28
+ child1;
29
+ child2;
30
+ /** The caller's own id for a leaf; −1 on an internal node. */
31
+ proxy;
32
+ height;
33
+ capacity;
34
+ free = 0;
35
+ nodeCount = 0;
36
+ stack;
37
+ constructor(capacity = 64) {
38
+ this.capacity = Math.max(2, capacity | 0);
39
+ this.bounds = new Float32Array(this.capacity * 6);
40
+ this.parent = new Int32Array(this.capacity);
41
+ this.child1 = new Int32Array(this.capacity);
42
+ this.child2 = new Int32Array(this.capacity);
43
+ this.proxy = new Int32Array(this.capacity);
44
+ this.height = new Int32Array(this.capacity);
45
+ this.stack = new Int32Array(64);
46
+ this.resetFreeList(0);
47
+ }
48
+ /** How many nodes are in use. A tree of n leaves has 2n − 1 nodes. */
49
+ get count() {
50
+ return this.nodeCount;
51
+ }
52
+ insert(proxyId, box) {
53
+ const leaf = this.allocate();
54
+ const at = leaf * 6;
55
+ this.bounds[at] = box.minX - MARGIN;
56
+ this.bounds[at + 1] = box.minY - MARGIN;
57
+ this.bounds[at + 2] = box.minZ - MARGIN;
58
+ this.bounds[at + 3] = box.maxX + MARGIN;
59
+ this.bounds[at + 4] = box.maxY + MARGIN;
60
+ this.bounds[at + 5] = box.maxZ + MARGIN;
61
+ this.proxy[leaf] = proxyId;
62
+ this.height[leaf] = 0;
63
+ this.insertLeaf(leaf);
64
+ return leaf;
65
+ }
66
+ remove(leaf) {
67
+ this.removeLeaf(leaf);
68
+ this.freeNode(leaf);
69
+ }
70
+ /**
71
+ * Re-place a proxy whose tight box has moved. Returns whether the tree changed.
72
+ *
73
+ * A move entirely inside the fat box is free: the stored bounds still enclose the proxy, so no
74
+ * query can miss it. That is the whole point of the margin, and the reason it is a *conservative*
75
+ * enlargement rather than a tolerance.
76
+ */
77
+ move(leaf, box) {
78
+ const at = leaf * 6;
79
+ if (box.minX >= (this.bounds[at] ?? 0) &&
80
+ box.minY >= (this.bounds[at + 1] ?? 0) &&
81
+ box.minZ >= (this.bounds[at + 2] ?? 0) &&
82
+ box.maxX <= (this.bounds[at + 3] ?? 0) &&
83
+ box.maxY <= (this.bounds[at + 4] ?? 0) &&
84
+ box.maxZ <= (this.bounds[at + 5] ?? 0)) {
85
+ return false;
86
+ }
87
+ const id = this.proxy[leaf] ?? 0;
88
+ this.removeLeaf(leaf);
89
+ this.bounds[at] = box.minX - MARGIN;
90
+ this.bounds[at + 1] = box.minY - MARGIN;
91
+ this.bounds[at + 2] = box.minZ - MARGIN;
92
+ this.bounds[at + 3] = box.maxX + MARGIN;
93
+ this.bounds[at + 4] = box.maxY + MARGIN;
94
+ this.bounds[at + 5] = box.maxZ + MARGIN;
95
+ this.proxy[leaf] = id;
96
+ this.insertLeaf(leaf);
97
+ return true;
98
+ }
99
+ /** Fill `out` with every proxy id whose stored box overlaps `box`. Returns how many. */
100
+ query(box, out) {
101
+ if (this.root === NIL)
102
+ return 0;
103
+ let top = 0;
104
+ this.stack[top++] = this.root;
105
+ let found = 0;
106
+ while (top > 0) {
107
+ const node = this.stack[--top] ?? NIL;
108
+ if (node === NIL)
109
+ continue;
110
+ const at = node * 6;
111
+ if ((this.bounds[at] ?? 0) > box.maxX ||
112
+ (this.bounds[at + 3] ?? 0) < box.minX ||
113
+ (this.bounds[at + 1] ?? 0) > box.maxY ||
114
+ (this.bounds[at + 4] ?? 0) < box.minY ||
115
+ (this.bounds[at + 2] ?? 0) > box.maxZ ||
116
+ (this.bounds[at + 5] ?? 0) < box.minZ) {
117
+ continue;
118
+ }
119
+ if (this.child1[node] === NIL) {
120
+ if (found < out.length)
121
+ out[found] = this.proxy[node] ?? 0;
122
+ found++;
123
+ continue;
124
+ }
125
+ if (top + 2 > this.stack.length)
126
+ this.stack = growInt(this.stack, this.stack.length * 2);
127
+ this.stack[top++] = this.child1[node] ?? NIL;
128
+ this.stack[top++] = this.child2[node] ?? NIL;
129
+ }
130
+ return found > out.length ? out.length : found;
131
+ }
132
+ insertLeaf(leaf) {
133
+ if (this.root === NIL) {
134
+ this.root = leaf;
135
+ this.parent[leaf] = NIL;
136
+ return;
137
+ }
138
+ // Descend to the sibling whose union with the leaf costs the least surface area.
139
+ let node = this.root;
140
+ while (this.child1[node] !== NIL) {
141
+ const c1 = this.child1[node] ?? NIL;
142
+ const c2 = this.child2[node] ?? NIL;
143
+ node = this.unionArea(leaf, c1) < this.unionArea(leaf, c2) ? c1 : c2;
144
+ }
145
+ const sibling = node;
146
+ const oldParent = this.parent[sibling] ?? NIL;
147
+ const newParent = this.allocate();
148
+ this.parent[newParent] = oldParent;
149
+ this.proxy[newParent] = -1;
150
+ this.union(newParent, leaf, sibling);
151
+ this.height[newParent] = (this.height[sibling] ?? 0) + 1;
152
+ if (oldParent !== NIL) {
153
+ if (this.child1[oldParent] === sibling)
154
+ this.child1[oldParent] = newParent;
155
+ else
156
+ this.child2[oldParent] = newParent;
157
+ }
158
+ else {
159
+ this.root = newParent;
160
+ }
161
+ this.child1[newParent] = sibling;
162
+ this.child2[newParent] = leaf;
163
+ this.parent[sibling] = newParent;
164
+ this.parent[leaf] = newParent;
165
+ this.refit(this.parent[leaf] ?? NIL);
166
+ }
167
+ removeLeaf(leaf) {
168
+ if (leaf === this.root) {
169
+ this.root = NIL;
170
+ return;
171
+ }
172
+ const parent = this.parent[leaf] ?? NIL;
173
+ const grand = this.parent[parent] ?? NIL;
174
+ const sibling = this.child1[parent] === leaf ? (this.child2[parent] ?? NIL) : (this.child1[parent] ?? NIL);
175
+ if (grand !== NIL) {
176
+ if (this.child1[grand] === parent)
177
+ this.child1[grand] = sibling;
178
+ else
179
+ this.child2[grand] = sibling;
180
+ this.parent[sibling] = grand;
181
+ this.freeNode(parent);
182
+ this.refit(grand);
183
+ }
184
+ else {
185
+ this.root = sibling;
186
+ this.parent[sibling] = NIL;
187
+ this.freeNode(parent);
188
+ }
189
+ }
190
+ refit(from) {
191
+ let node = from;
192
+ while (node !== NIL) {
193
+ node = this.balance(node);
194
+ const c1 = this.child1[node] ?? NIL;
195
+ const c2 = this.child2[node] ?? NIL;
196
+ this.union(node, c1, c2);
197
+ const h1 = this.height[c1] ?? 0;
198
+ const h2 = this.height[c2] ?? 0;
199
+ this.height[node] = (h1 > h2 ? h1 : h2) + 1;
200
+ node = this.parent[node] ?? NIL;
201
+ }
202
+ }
203
+ /**
204
+ * One AVL-style rotation where a subtree leans by more than one, returning the new subtree root.
205
+ *
206
+ * **Without this the surface-area descent builds a chain, and it does so on the most ordinary
207
+ * scene there is.** Measured: sixty-four boxes in a row along x came out as a tree of height 63,
208
+ * because each new box unions most cheaply with the rightmost leaf and the descent therefore
209
+ * walks the right spine every time. A wall, a stack and a floor strip are all that shape, and a
210
+ * chain makes every query linear, which is the entire cost the tree exists to avoid. A grid was
211
+ * height 14 and hid it.
212
+ *
213
+ * The heuristic chooses *where* to insert and the rotation keeps the result usable; neither
214
+ * substitutes for the other. **What this costs** is a rotation test per node on the way up, all
215
+ * of it integer comparison. **What would make it wrong** is nothing measured yet; the alternative
216
+ * is a full rebuild, which is dearer and not incremental.
217
+ */
218
+ balance(a) {
219
+ if (this.child1[a] === NIL || (this.height[a] ?? 0) < 2)
220
+ return a;
221
+ const b = this.child1[a] ?? NIL;
222
+ const c = this.child2[a] ?? NIL;
223
+ const lean = (this.height[c] ?? 0) - (this.height[b] ?? 0);
224
+ if (lean > 1)
225
+ return this.rotate(a, c, b);
226
+ if (lean < -1)
227
+ return this.rotate(a, b, c);
228
+ return a;
229
+ }
230
+ /** Pull `up` above `a`, keeping `keep` as `a`'s remaining child. */
231
+ rotate(a, up, keep) {
232
+ const f = this.child1[up] ?? NIL;
233
+ const g = this.child2[up] ?? NIL;
234
+ const grand = this.parent[a] ?? NIL;
235
+ this.child1[up] = a;
236
+ this.parent[up] = grand;
237
+ this.parent[a] = up;
238
+ if (grand !== NIL) {
239
+ if (this.child1[grand] === a)
240
+ this.child1[grand] = up;
241
+ else
242
+ this.child2[grand] = up;
243
+ }
244
+ else {
245
+ this.root = up;
246
+ }
247
+ // The taller grandchild rises with `up`; the shorter one drops to `a`.
248
+ const tall = (this.height[f] ?? 0) > (this.height[g] ?? 0) ? f : g;
249
+ const short = tall === f ? g : f;
250
+ this.child2[up] = tall;
251
+ this.child2[a] = short;
252
+ this.child1[a] = keep;
253
+ this.parent[short] = a;
254
+ this.union(a, keep, short);
255
+ this.union(up, a, tall);
256
+ const hk = this.height[keep] ?? 0;
257
+ const hs = this.height[short] ?? 0;
258
+ this.height[a] = (hk > hs ? hk : hs) + 1;
259
+ const ha = this.height[a] ?? 0;
260
+ const ht = this.height[tall] ?? 0;
261
+ this.height[up] = (ha > ht ? ha : ht) + 1;
262
+ return up;
263
+ }
264
+ union(into, a, b) {
265
+ const t = into * 6;
266
+ const p = a * 6;
267
+ const q = b * 6;
268
+ for (let k = 0; k < 3; k++) {
269
+ const pa = this.bounds[p + k] ?? 0;
270
+ const qb = this.bounds[q + k] ?? 0;
271
+ this.bounds[t + k] = pa < qb ? pa : qb;
272
+ const pc = this.bounds[p + k + 3] ?? 0;
273
+ const qc = this.bounds[q + k + 3] ?? 0;
274
+ this.bounds[t + k + 3] = pc > qc ? pc : qc;
275
+ }
276
+ }
277
+ /** Surface area of the box enclosing two nodes. The heuristic, and only `+ - *`. */
278
+ unionArea(a, b) {
279
+ const p = a * 6;
280
+ const q = b * 6;
281
+ const ax = this.bounds[p] ?? 0;
282
+ const bx = this.bounds[q] ?? 0;
283
+ const ay = this.bounds[p + 1] ?? 0;
284
+ const by = this.bounds[q + 1] ?? 0;
285
+ const az = this.bounds[p + 2] ?? 0;
286
+ const bz = this.bounds[q + 2] ?? 0;
287
+ const cx = this.bounds[p + 3] ?? 0;
288
+ const dx = this.bounds[q + 3] ?? 0;
289
+ const cy = this.bounds[p + 4] ?? 0;
290
+ const dy = this.bounds[q + 4] ?? 0;
291
+ const cz = this.bounds[p + 5] ?? 0;
292
+ const dz = this.bounds[q + 5] ?? 0;
293
+ const w = (cx > dx ? cx : dx) - (ax < bx ? ax : bx);
294
+ const h = (cy > dy ? cy : dy) - (ay < by ? ay : by);
295
+ const d = (cz > dz ? cz : dz) - (az < bz ? az : bz);
296
+ return w * h + h * d + d * w;
297
+ }
298
+ allocate() {
299
+ if (this.free === NIL)
300
+ this.growNodes();
301
+ const node = this.free;
302
+ this.free = this.parent[node] ?? NIL;
303
+ this.parent[node] = NIL;
304
+ this.child1[node] = NIL;
305
+ this.child2[node] = NIL;
306
+ this.height[node] = 0;
307
+ this.nodeCount++;
308
+ return node;
309
+ }
310
+ freeNode(node) {
311
+ this.parent[node] = this.free;
312
+ this.child1[node] = NIL;
313
+ this.child2[node] = NIL;
314
+ this.height[node] = -1;
315
+ this.free = node;
316
+ this.nodeCount--;
317
+ }
318
+ growNodes() {
319
+ const old = this.capacity;
320
+ this.capacity *= 2;
321
+ this.bounds = growFloat(this.bounds, this.capacity * 6);
322
+ this.parent = growInt(this.parent, this.capacity);
323
+ this.child1 = growInt(this.child1, this.capacity);
324
+ this.child2 = growInt(this.child2, this.capacity);
325
+ this.proxy = growInt(this.proxy, this.capacity);
326
+ this.height = growInt(this.height, this.capacity);
327
+ this.resetFreeList(old);
328
+ }
329
+ resetFreeList(from) {
330
+ for (let i = from; i < this.capacity - 1; i++) {
331
+ this.parent[i] = i + 1;
332
+ this.height[i] = -1;
333
+ }
334
+ this.parent[this.capacity - 1] = NIL;
335
+ this.height[this.capacity - 1] = -1;
336
+ this.free = from;
337
+ }
338
+ }
339
+ function growFloat(a, n) {
340
+ const out = new Float32Array(n);
341
+ out.set(a);
342
+ return out;
343
+ }
344
+ function growInt(a, n) {
345
+ const out = new Int32Array(n);
346
+ out.set(a);
347
+ return out;
348
+ }
@@ -0,0 +1,154 @@
1
+ import type { QueryFilter } from './query.ts';
2
+ import type { PhysicsWorld } from './world.ts';
3
+ import type { GroundProbe } from './controller.ts';
4
+ /**
5
+ * A raycast vehicle: a chassis body, and wheels that are rays rather than bodies.
6
+ *
7
+ * **Rigid wheels on joints are the obvious alternative and are unstable in practice.** They need
8
+ * joint stiffness and substep counts that nobody wants to pay, which is why Bullet, Godot and Unity
9
+ * all raycast. *What that costs, said where the decision is:* a raycast wheel cannot wedge in
10
+ * geometry, cannot collide with another wheel, and a vehicle on its side behaves as a box rather
11
+ * than as a car. *What would reverse it* is a game about vehicles interlocking with terrain.
12
+ *
13
+ * **The tyre curve is a table the caller supplies.** Pacejka's magic formula is `sin(atan(…))` and
14
+ * both are banned from the tick, so grip is sampled from a curve and linearly interpolated. This is
15
+ * the one place in Track B where the arithmetic contract produced a *better* API than the closed
16
+ * form it replaced: a curve is edited by moving a point, and a magic formula is edited by guessing
17
+ * at a coefficient nobody can picture.
18
+ *
19
+ * Everything here is applied as an impulse at a point on the chassis, so the vehicle's roll, pitch
20
+ * and weight transfer fall out of the solver rather than being modelled separately.
21
+ */
22
+ /**
23
+ * Grip against slip, as a lookup table.
24
+ *
25
+ * `force[i]` is the fraction of the load available as lateral or longitudinal force at
26
+ * `slip[i]`. Both arrays are the same length and `slip` must ascend. Beyond the last entry the
27
+ * final value is held, which is what a tyre past its peak does.
28
+ */
29
+ export interface TyreCurve {
30
+ readonly slip: Float32Array;
31
+ readonly force: Float32Array;
32
+ }
33
+ /** A curve that rises to a peak and falls away, which is what a tyre does. */
34
+ export declare function defaultTyreCurve(peak?: number): TyreCurve;
35
+ export interface WheelOptions {
36
+ /** Where the suspension is anchored, in the chassis's local frame. */
37
+ x: number;
38
+ y: number;
39
+ z: number;
40
+ radius?: number;
41
+ /** How far the wheel may travel below its anchor. */
42
+ suspensionTravel?: number;
43
+ /** Spring rate, in newtons per metre of compression. */
44
+ stiffness?: number;
45
+ /** Damping, in newtons per metre per second. */
46
+ damping?: number;
47
+ /** Whether steering input turns this wheel. */
48
+ steers?: boolean;
49
+ /** Whether engine and brake force reach it. */
50
+ driven?: boolean;
51
+ }
52
+ export interface VehicleOptions {
53
+ wheels: readonly WheelOptions[];
54
+ /** Maximum steering angle, as the tangent of the angle rather than the angle. */
55
+ maxSteerTangent?: number;
56
+ /** Newtons at full throttle, shared across driven wheels. */
57
+ engineForce?: number;
58
+ /** Newtons at full brake. */
59
+ brakeForce?: number;
60
+ longitudinal?: TyreCurve;
61
+ lateral?: TyreCurve;
62
+ filter?: QueryFilter;
63
+ /**
64
+ * A surface the wheels can rest on that is not a body.
65
+ *
66
+ * The same seam `CharacterController` takes, and here for the same reason plus one: a consumer
67
+ * whose ground is a height function had a character that stood on it and a car that fell through
68
+ * it, so the world's floor was two things that had to be kept in step by hand. It was reported
69
+ * that way — a hundred lines of ground query written consumer-side, and then written again for
70
+ * everything else that stands on it.
71
+ *
72
+ * Consulted only where the wheel's ray found no body, on the same ruling the controller carries:
73
+ * a body is a thing that is there and a surface is a description of where the ground is.
74
+ */
75
+ ground?: GroundProbe;
76
+ }
77
+ export interface VehicleInput {
78
+ /** −1 to 1. */
79
+ throttle: number;
80
+ /** 0 to 1. */
81
+ brake: number;
82
+ /** −1 to 1, scaled by `maxSteerTangent`. */
83
+ steer: number;
84
+ }
85
+ export declare class Vehicle {
86
+ readonly chassis: number;
87
+ readonly wheelCount: number;
88
+ /** Whether each wheel found ground on the last update. */
89
+ readonly grounded: Uint8Array;
90
+ /** How far each wheel's suspension is compressed, in metres. */
91
+ readonly compression: Float32Array;
92
+ /** Where each wheel's contact is, xyz-packed. Meaningful where `grounded`. */
93
+ readonly contact: Float32Array;
94
+ private readonly local;
95
+ private readonly radius;
96
+ private readonly travel;
97
+ private readonly stiffness;
98
+ private readonly damping;
99
+ private readonly steers;
100
+ private readonly driven;
101
+ private readonly maxSteerTangent;
102
+ private readonly engineForce;
103
+ private readonly brakeForce;
104
+ private readonly longitudinal;
105
+ private readonly lateral;
106
+ /**
107
+ * The suspension ray must ignore the car it holds up.
108
+ *
109
+ * A wheel anchor sits *inside* the chassis by construction — that is what a suspension mount is —
110
+ * and a ray starting inside a body reports that body at fraction zero, which is the right answer
111
+ * to the question a line-of-sight test asks and the wrong one here. Left unfiltered, every wheel
112
+ * found the chassis at zero distance, read the suspension as fully compressed, and pushed the car
113
+ * against itself: it sank onto its own chassis and sat there.
114
+ */
115
+ private readonly filter;
116
+ private readonly hit;
117
+ private readonly probe;
118
+ /** Scratch for the probe, claimed once: every wheel asks it every tick. */
119
+ private readonly probed;
120
+ /**
121
+ * Every wheel's impulse, gathered before any is applied: `rx ry rz px py pz` per wheel.
122
+ *
123
+ * **Applying them one at a time makes the car curve.** Each wheel then sees a velocity the
124
+ * previous wheel has already changed, so the left front's lateral impulse biases what the right
125
+ * front computes, and the asymmetry accumulates into a yaw rather than cancelling. Measured on a
126
+ * symmetric car under straight throttle: 0.011 metres of drift after 2 metres, 0.64 after 39, and
127
+ * **5.49 after 155** — a curve, not a wobble. Forces at an instant are simultaneous, and gathering
128
+ * them is both the correct physics and the cheaper fix.
129
+ */
130
+ private readonly pending;
131
+ constructor(chassis: number, options: VehicleOptions);
132
+ /** Advance the vehicle one fixed tick. Call before `world.step`. */
133
+ update(world: PhysicsWorld, dt: number, input: VehicleInput): void;
134
+ /** Add an impulse at a wheel's anchor to what will be applied at the end of the pass. */
135
+ /**
136
+ * Rest a wheel on the analytic surface, filling `hit` as a ray against a body would have.
137
+ *
138
+ * Everything downstream — the spring, the damper, the tyre frame flattened onto the ground —
139
+ * reads `hit`, so filling it is the whole of the wiring and no grip code learns that a surface
140
+ * exists.
141
+ *
142
+ * **The column is the anchor's own and the drop is measured along the chassis's down**, which is
143
+ * exact while the chassis is upright and off by its tilt when it is not: the true contact sits a
144
+ * little to the side of the column that was sampled, and on a slope that column is a little
145
+ * higher or lower than the contact. At the lean angles a car spends its life at the error is
146
+ * millimetres, and the alternative is solving a ray against an arbitrary height function, which
147
+ * is an iteration this package does not allow itself. A vehicle on its roof finds no ground here
148
+ * at all, because there is no downward intersection to find, which is the honest answer.
149
+ */
150
+ private probeUnder;
151
+ private gather;
152
+ }
153
+ /** Sample a curve at `at`, holding the last value beyond its end. */
154
+ export declare function sampleTyreCurve(curve: TyreCurve, at: number): number;