@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,395 @@
1
+ import type { Aabb } from './collide/index.ts';
2
+ import type { ConvexShape } from './shape.ts';
3
+
4
+ /**
5
+ * A heightfield as a collision shape: the samples, and every triangle generated from them.
6
+ *
7
+ * **The whole of this is a memory argument.** Terrain already collides, as a static triangle mesh —
8
+ * `meshShape` takes exactly the arrays a heightfield renderer hands back, so the geometry drawn is
9
+ * the geometry collided with, which is the property that matters most and the reason that path is
10
+ * still the right one for a level. What it costs is the triangles: measured on a 129-square field,
11
+ * the positions and indices alone are more than five times the bytes of the heights they were built
12
+ * from, before the tree over them is counted at all.
13
+ *
14
+ * A heightfield needs none of it. The samples *are* the geometry, the cell under a body is an index
15
+ * rather than a tree query, and every triangle, plane and edge classification is arithmetic on four
16
+ * numbers. So this carries the heights and nothing else.
17
+ *
18
+ * **What it is not is a second narrow phase.** `meshContact.ts` owns the contact rules — many
19
+ * manifolds rather than one, one-sided triangles, the interior-edge filter that stops a box
20
+ * stumbling on a flat seam — and this changes none of them. It is a *source of triangles*, and the
21
+ * only thing it replaces is where they come from: an index range instead of a tree, and four
22
+ * samples instead of an index buffer. That split is the 2026-08-13 rule applied to the one file in
23
+ * this package it would be most expensive to have two of.
24
+ *
25
+ * **The triangulation is stated here and mirrored in `@driftengine/terrain`**, which draws the
26
+ * picture this collides with. It has to be: `@driftengine/physics` imports no other engine package,
27
+ * so the rule cannot be shared as code. What is shared instead is an assertion — that package's
28
+ * `terrainCollision.test.ts` compares the two — which is the same arrangement `glDepthFunc` has with
29
+ * `DEPTH_COMPARE` for the same reason. Track L already paid once for two copies of this rule
30
+ * disagreeing, when a mesh was split one way and its query read the other, so the mirror is guarded
31
+ * rather than trusted.
32
+ */
33
+
34
+ export interface Heightfield {
35
+ /** Samples across x. At least two, since one sample is not a cell. */
36
+ readonly width: number;
37
+ /** Samples across z. */
38
+ readonly depth: number;
39
+ /** Metres between samples, the same both ways. */
40
+ readonly spacingM: number;
41
+ /** `width * depth` heights, row-major with x running fastest. Kept, not copied. */
42
+ readonly heights: Float32Array;
43
+ /**
44
+ * World position of sample `(0, 0)`, or nothing for a field at the origin. Its own y is added to
45
+ * every height.
46
+ *
47
+ * **Three numbers in an array rather than three fields, and that is the boundary doing its work.**
48
+ * `@driftengine/physics` imports no other engine package, so it cannot know what a `Terrain` is —
49
+ * but a `Terrain` *is* one of these, structurally: same names, same meanings, an `origin` that is
50
+ * a `Float32Array`. So `heightfieldShape(terrain)` type-checks with no import in either
51
+ * direction, and the seam is a shape rather than a dependency.
52
+ */
53
+ readonly origin?: ArrayLike<number>;
54
+ }
55
+
56
+ /** How many cells a field has across x, which is one fewer than its samples. */
57
+ function cellsX(field: Heightfield): number {
58
+ return field.width - 1;
59
+ }
60
+
61
+ function cellsZ(field: Heightfield): number {
62
+ return field.depth - 1;
63
+ }
64
+
65
+ /** One sample, by grid index, clamped — a query at the rim reads the rim. */
66
+ function sample(field: Heightfield, ix: number, iz: number): number {
67
+ const x = ix < 0 ? 0 : ix > field.width - 1 ? field.width - 1 : ix;
68
+ const z = iz < 0 ? 0 : iz > field.depth - 1 ? field.depth - 1 : iz;
69
+ return (field.origin?.[1] ?? 0) + (field.heights[z * field.width + x] ?? 0);
70
+ }
71
+
72
+ /** Where sample `(0, 0)` stands, along x and z. */
73
+ function originX(field: Heightfield): number {
74
+ return field.origin?.[0] ?? 0;
75
+ }
76
+
77
+ function originZ(field: Heightfield): number {
78
+ return field.origin?.[2] ?? 0;
79
+ }
80
+
81
+ /** Whether a lattice point is inside the field, which is what says an edge has a neighbour. */
82
+ function inside(field: Heightfield, ix: number, iz: number): boolean {
83
+ return ix >= 0 && iz >= 0 && ix < field.width && iz < field.depth;
84
+ }
85
+
86
+ /**
87
+ * The three corners of a triangle, in the field's own space.
88
+ *
89
+ * **The cell is split along `a`-`c`**, the diagonal from its own sample to the one across it, and
90
+ * the two halves are `a, d, c` and `a, c, b` — wound so the face normal points up. That is the same
91
+ * split `@driftengine/terrain`'s `heightfieldPatch` emits, and it has to be: a collider split the
92
+ * other way disagrees with the picture by up to the height of a cell everywhere but the
93
+ * anti-diagonal, and nothing about that failure is visible.
94
+ *
95
+ * `out` takes nine numbers.
96
+ */
97
+ export function fieldCorners(field: Heightfield, triangle: number, out: Float64Array): void {
98
+ const across = cellsX(field);
99
+ const cell = triangle >> 1;
100
+ const half = triangle & 1;
101
+ const cx = cell % across;
102
+ const cz = (cell - cx) / across;
103
+ const step = field.spacingM;
104
+ const x0 = originX(field) + cx * step;
105
+ const x1 = x0 + step;
106
+ const z0 = originZ(field) + cz * step;
107
+ const z1 = z0 + step;
108
+
109
+ if (half === 0) {
110
+ /* a, d, c */
111
+ out[0] = x0;
112
+ out[1] = sample(field, cx, cz);
113
+ out[2] = z0;
114
+ out[3] = x0;
115
+ out[4] = sample(field, cx, cz + 1);
116
+ out[5] = z1;
117
+ out[6] = x1;
118
+ out[7] = sample(field, cx + 1, cz + 1);
119
+ out[8] = z1;
120
+ return;
121
+ }
122
+ /* a, c, b */
123
+ out[0] = x0;
124
+ out[1] = sample(field, cx, cz);
125
+ out[2] = z0;
126
+ out[3] = x1;
127
+ out[4] = sample(field, cx + 1, cz + 1);
128
+ out[5] = z1;
129
+ out[6] = x1;
130
+ out[7] = sample(field, cx + 1, cz);
131
+ out[8] = z0;
132
+ }
133
+
134
+ /** Scratch for a plane derived from corners, so nothing here allocates in the tick. */
135
+ const CORNERS = new Float64Array(9);
136
+
137
+ /**
138
+ * The triangle's outward plane as `nx, ny, nz, d`, with `d = n · v0`.
139
+ *
140
+ * Normalised, and pointing up: a heightfield has no overhangs, so every face of it faces the sky.
141
+ * A degenerate triangle — two samples at the same place, which a spacing of zero would give and
142
+ * nothing else does — writes a zero normal, which the contact code skips exactly as it does for a
143
+ * degenerate mesh triangle.
144
+ */
145
+ export function fieldPlane(field: Heightfield, triangle: number, out: Float64Array): void {
146
+ fieldCorners(field, triangle, CORNERS);
147
+ const ax = CORNERS[0] ?? 0;
148
+ const ay = CORNERS[1] ?? 0;
149
+ const az = CORNERS[2] ?? 0;
150
+ const ux = (CORNERS[3] ?? 0) - ax;
151
+ const uy = (CORNERS[4] ?? 0) - ay;
152
+ const uz = (CORNERS[5] ?? 0) - az;
153
+ const vx = (CORNERS[6] ?? 0) - ax;
154
+ const vy = (CORNERS[7] ?? 0) - ay;
155
+ const vz = (CORNERS[8] ?? 0) - az;
156
+ let nx = uy * vz - uz * vy;
157
+ let ny = uz * vx - ux * vz;
158
+ let nz = ux * vy - uy * vx;
159
+ const length = Math.sqrt(nx * nx + ny * ny + nz * nz);
160
+ if (length > 1e-12) {
161
+ nx /= length;
162
+ ny /= length;
163
+ nz /= length;
164
+ } else {
165
+ nx = 0;
166
+ ny = 0;
167
+ nz = 0;
168
+ }
169
+ out[0] = nx;
170
+ out[1] = ny;
171
+ out[2] = nz;
172
+ out[3] = nx * ax + ny * ay + nz * az;
173
+ }
174
+
175
+ /** How far a neighbour's opposite sample must sit below the plane before the fold has a direction. */
176
+ const COPLANAR_M = 1e-5;
177
+
178
+ /** Scratch for the classification, which runs per candidate triangle inside the tick. */
179
+ const PLANE = new Float64Array(4);
180
+
181
+ /**
182
+ * Which of a triangle's three edges a contact normal may come off, as three bits.
183
+ *
184
+ * **Computed rather than precomputed, and that is the saving.** `meshShape` walks every edge of a
185
+ * level once at build time and stores three bits per triangle; a field's answer is four samples and
186
+ * a subtraction, so there is nothing to store. Edge `i` runs from local vertex `i` to `(i + 1) % 3`,
187
+ * matching `TriangleMesh.convexEdges` exactly, because `meshContact.ts` reads both the same way.
188
+ *
189
+ * The rule is `meshShape`'s: an edge is convex where the neighbouring triangle's opposite vertex
190
+ * sits *below* this triangle's plane, and where the edge has no neighbour at all. A coplanar seam is
191
+ * **not** convex, which is the whole of the interior-edge filter — a flat floor made of triangles
192
+ * must not push a body back along the joins.
193
+ *
194
+ * Each of the five edges a cell has meets its neighbour at a single lattice point, and the
195
+ * neighbour exists exactly when that point is inside the field:
196
+ *
197
+ * - half 0: edge 0 at `(cx - 1, cz)`, edge 1 at `(cx + 1, cz + 2)`, edge 2 — the diagonal — at
198
+ * `(cx + 1, cz)`, which is the other half's own third corner.
199
+ * - half 1: edge 0 — the same diagonal — at `(cx, cz + 1)`, edge 1 at `(cx + 2, cz + 1)`, edge 2 at
200
+ * `(cx, cz - 1)`.
201
+ */
202
+ export function fieldConvexEdges(field: Heightfield, triangle: number): number {
203
+ const across = cellsX(field);
204
+ const cell = triangle >> 1;
205
+ const half = triangle & 1;
206
+ const cx = cell % across;
207
+ const cz = (cell - cx) / across;
208
+ fieldPlane(field, triangle, PLANE);
209
+
210
+ let bits = 0b111;
211
+ for (let e = 0; e < 3; e++) {
212
+ let ox = 0;
213
+ let oz = 0;
214
+ if (half === 0) {
215
+ if (e === 0) {
216
+ ox = cx - 1;
217
+ oz = cz;
218
+ } else if (e === 1) {
219
+ ox = cx + 1;
220
+ oz = cz + 2;
221
+ } else {
222
+ ox = cx + 1;
223
+ oz = cz;
224
+ }
225
+ } else if (e === 0) {
226
+ ox = cx;
227
+ oz = cz + 1;
228
+ } else if (e === 1) {
229
+ ox = cx + 2;
230
+ oz = cz + 1;
231
+ } else {
232
+ ox = cx;
233
+ oz = cz - 1;
234
+ }
235
+
236
+ /* No neighbour is a boundary edge, and a boundary edge is convex: there is nothing on the other
237
+ side for a body to be caught between. */
238
+ if (!inside(field, ox, oz)) continue;
239
+
240
+ const px = originX(field) + ox * field.spacingM;
241
+ const py = sample(field, ox, oz);
242
+ const pz = originZ(field) + oz * field.spacingM;
243
+ const height =
244
+ px * (PLANE[0] ?? 0) + py * (PLANE[1] ?? 0) + pz * (PLANE[2] ?? 0) - (PLANE[3] ?? 0);
245
+ if (!(height < -COPLANAR_M)) bits &= ~(1 << e);
246
+ }
247
+ return bits;
248
+ }
249
+
250
+ /**
251
+ * The triangles a box could touch, written into `out`, as a count.
252
+ *
253
+ * **An index range rather than a tree**, which is the other half of what a heightfield buys: the
254
+ * cells a box covers are `(minX - originX) / spacing` to `(maxX - originX) / spacing`, and the same
255
+ * in z. There is no structure to build, to keep, or to rebuild when the field changes.
256
+ *
257
+ * **And a height test per cell, which is most of the saving in practice.** A body on a hillside
258
+ * covers a wide footprint in x and z and touches almost none of it, the rest being metres above or
259
+ * below — so a cell whose four samples all sit outside the box's own y range is skipped before the
260
+ * narrow phase ever sees it.
261
+ *
262
+ * Fills to `out.length` and stops, exactly as `DynamicTree.query` does, so the caller's grow-and-
263
+ * retry loop works unchanged.
264
+ */
265
+ export function fieldCandidates(field: Heightfield, box: Aabb, out: Int32Array): number {
266
+ const step = field.spacingM;
267
+ const across = cellsX(field);
268
+ const down = cellsZ(field);
269
+ const ox = originX(field);
270
+ const oz = originZ(field);
271
+ const fromX = Math.max(0, Math.floor((box.minX - ox) / step));
272
+ const toX = Math.min(across - 1, Math.floor((box.maxX - ox) / step));
273
+ const fromZ = Math.max(0, Math.floor((box.minZ - oz) / step));
274
+ const toZ = Math.min(down - 1, Math.floor((box.maxZ - oz) / step));
275
+
276
+ let count = 0;
277
+ for (let cz = fromZ; cz <= toZ; cz++) {
278
+ for (let cx = fromX; cx <= toX; cx++) {
279
+ const a = sample(field, cx, cz);
280
+ const b = sample(field, cx + 1, cz);
281
+ const c = sample(field, cx + 1, cz + 1);
282
+ const d = sample(field, cx, cz + 1);
283
+ const low = Math.min(a, b, c, d);
284
+ const high = Math.max(a, b, c, d);
285
+ if (high < box.minY || low > box.maxY) continue;
286
+ const cell = cz * across + cx;
287
+ if (count === out.length) return count;
288
+ out[count++] = cell * 2;
289
+ if (count === out.length) return count;
290
+ out[count++] = cell * 2 + 1;
291
+ }
292
+ }
293
+ return count;
294
+ }
295
+
296
+ /**
297
+ * Build a heightfield collision shape.
298
+ *
299
+ * Returns a `ConvexShape` whose `triangles` names the field — so every existing test of "is this a
300
+ * mesh" answers yes and every rule that follows from it holds: refused as a kinematic collider,
301
+ * refused on anything but a static body, and given a static body's own zero mass tensor. Its
302
+ * `vertices` are the eight corners of the field's bounds, so the broad phase measures the right box,
303
+ * and its `faceNormals` and `edgeDirs` are empty for the reason `meshShape` gives: a field has no
304
+ * separating axes worth enumerating, and handing the kinematic sweep the box's three would make a
305
+ * hillside a solid brick.
306
+ *
307
+ * **The heights are kept and not copied**, the same bargain `meshShape` makes with a level's
308
+ * vertices: a field is the thing being avoided allocating twice. A caller must not go on mutating
309
+ * them, which is stated here because nothing enforces it — and unlike a mesh there is nothing to
310
+ * rebuild if they do, so a caller who *wants* a changing field may mutate and the collider follows.
311
+ */
312
+ export function heightfieldShape(field: Heightfield): ConvexShape {
313
+ if (!(field.width >= 2)) {
314
+ throw new Error(`heightfieldShape: width must be at least 2, got ${field.width}`);
315
+ }
316
+ if (!(field.depth >= 2)) {
317
+ throw new Error(`heightfieldShape: depth must be at least 2, got ${field.depth}`);
318
+ }
319
+ if (!(field.spacingM > 0)) {
320
+ throw new Error(`heightfieldShape: spacing must be above zero, got ${field.spacingM}`);
321
+ }
322
+ if (field.heights.length !== field.width * field.depth) {
323
+ throw new Error(
324
+ `heightfieldShape: ${field.width} by ${field.depth} needs ${field.width * field.depth} ` +
325
+ `heights, got ${field.heights.length}`,
326
+ );
327
+ }
328
+
329
+ let low = Infinity;
330
+ let high = -Infinity;
331
+ for (let i = 0; i < field.heights.length; i++) {
332
+ const h = field.heights[i] ?? 0;
333
+ if (h < low) low = h;
334
+ if (h > high) high = h;
335
+ }
336
+ const baseX = originX(field);
337
+ const baseY = field.origin?.[1] ?? 0;
338
+ const baseZ = originZ(field);
339
+ const minX = baseX;
340
+ const maxX = baseX + cellsX(field) * field.spacingM;
341
+ const minY = baseY + low;
342
+ const maxY = baseY + high;
343
+ const minZ = baseZ;
344
+ const maxZ = baseZ + cellsZ(field) * field.spacingM;
345
+
346
+ return {
347
+ vertices: new Float32Array([
348
+ minX,
349
+ minY,
350
+ minZ,
351
+ maxX,
352
+ minY,
353
+ minZ,
354
+ minX,
355
+ maxY,
356
+ minZ,
357
+ maxX,
358
+ maxY,
359
+ minZ,
360
+ minX,
361
+ minY,
362
+ maxZ,
363
+ maxX,
364
+ minY,
365
+ maxZ,
366
+ minX,
367
+ maxY,
368
+ maxZ,
369
+ maxX,
370
+ maxY,
371
+ maxZ,
372
+ ]),
373
+ faceNormals: new Float32Array(0),
374
+ edgeDirs: new Float32Array(0),
375
+ radius: 0,
376
+ sideRadius: 0,
377
+ boundRadius: Math.sqrt(
378
+ Math.max(minX * minX, maxX * maxX) +
379
+ Math.max(minY * minY, maxY * maxY) +
380
+ Math.max(minZ * minZ, maxZ * maxZ),
381
+ ),
382
+ facePlanes: new Float32Array(0),
383
+ faceVertexStart: new Uint16Array(0),
384
+ faceVertexIndices: new Uint16Array(0),
385
+ triangles: {
386
+ positions: new Float32Array(0),
387
+ indices: new Uint32Array(0),
388
+ triangleCount: cellsX(field) * cellsZ(field) * 2,
389
+ planes: new Float32Array(0),
390
+ convexEdges: new Uint8Array(0),
391
+ tree: null,
392
+ field,
393
+ },
394
+ };
395
+ }
package/src/index.ts ADDED
@@ -0,0 +1,164 @@
1
+ /*! DriftEngine | Copyright 2026 Drift Technologies | Apache-2.0 | https://github.com/drftrun/driftengine */
2
+ /**
3
+ * Collision shapes, the spatial hash over them, and the swept kinematic sweep.
4
+ *
5
+ * **This package imports no other engine package**, the same shape as `@driftengine/drft` and
6
+ * `@driftengine/entities`. What it buys is a simulation that runs with no renderer in its module
7
+ * graph — in Node, in a worker, or on a server — which is what a server-authoritative consumer
8
+ * needs and what a peer dependency on core would have priced at core's whole gzipped weight.
9
+ *
10
+ * `@driftengine/core` re-exports every name here, so a consumer that reaches for collision through
11
+ * the core barrel is unaffected by where the files live. **Core depends on this package rather
12
+ * than the other way round**, which is the opposite direction from every other optional package
13
+ * and is not a choice: the cameras, the contact probe and the ribbon builder all need the sweep.
14
+ * **What would make it wrong** is the dynamics landing here becoming reachable from
15
+ * `createRenderer`, which `scripts/size-gate.test.mjs` measures rather than trusts.
16
+ */
17
+ export type { Aabb, Axis, Body, BodyPart } from './collide/index.ts';
18
+ export {
19
+ AXIS_X,
20
+ AXIS_Y,
21
+ AXIS_Z,
22
+ aabbFromCenter,
23
+ bodyBounds,
24
+ createBodyBounds,
25
+ ejectFromSolid,
26
+ moveAxis,
27
+ segmentHit,
28
+ } from './collide/index.ts';
29
+ export { faceCount, faceVertices } from './faces.ts';
30
+ export type { BodyDesc, BodyType } from './bodies.ts';
31
+ export { BODY_DYNAMIC, BODY_KINEMATIC, BODY_SLEEPING, BODY_STATIC, BodySet } from './bodies.ts';
32
+ export { DynamicTree } from './tree.ts';
33
+ export { MAX_BODIES, PairSet, layersInteract, pairA, pairB, pairKey } from './pairs.ts';
34
+ export type { Manifold, ShapePose } from './manifold.ts';
35
+ export { MAX_CONTACTS, collideShapes, createManifold } from './manifold.ts';
36
+ export type { Parallelism, WorldOptions } from './world.ts';
37
+ export { PhysicsWorld } from './world.ts';
38
+ export {
39
+ ContactConstraints,
40
+ LINEAR_SLOP,
41
+ MAX_BIAS_VELOCITY,
42
+ RESTITUTION_THRESHOLD,
43
+ applyRestitution,
44
+ isDynamicBody,
45
+ prepareContact,
46
+ solveContacts,
47
+ warmStart,
48
+ } from './solver.ts';
49
+ export { IslandSet } from './island.ts';
50
+ export type { FrictionModel } from './solver.ts';
51
+ export type { Executor, IslandOverlap, IslandSolver } from './executor.ts';
52
+ export { DisjointExecutor, SerialExecutor, ShuffledExecutor } from './executor.ts';
53
+ export type { IslandSolveState } from './islandSolve.ts';
54
+ export { solveIslandInto } from './islandSolve.ts';
55
+ /*
56
+ * **The pool is not here, and its absence is the point.** `workerPool.ts` constructs its worker with
57
+ * `new Worker(new URL(...))`, which a bundler rewrites at transform time — before tree-shaking
58
+ * decides the pool is unreachable — so re-exporting it from this barrel wrote a 12 KB gzipped
59
+ * worker chunk into the bundle of every consumer that merely imported `PhysicsWorld`.
60
+ *
61
+ * It lives at `@driftengine/physics/src/workers.ts`, which that file explains. A caller already had
62
+ * to name `createIslandPool` at the call site for 3.54.0's own reason, so this costs a specifier.
63
+ *
64
+ * The *types* stay reachable from here, because a type import emits nothing and a consumer typing a
65
+ * `PoolWorker` should not need a second specifier to do it.
66
+ */
67
+ export type { LanePlan, StagePlan } from './stage.ts';
68
+ export type { PoolOutcome, PoolRequest, PoolWorker } from './workerPool.ts';
69
+ export type { DofMode, JointDesc, JointType } from './joints.ts';
70
+ export {
71
+ DOF_COUNT,
72
+ DOF_FREE,
73
+ DOF_LIMITED,
74
+ DOF_LOCKED,
75
+ JOINT_CONE_TWIST,
76
+ JOINT_DISTANCE,
77
+ JOINT_FIXED,
78
+ JOINT_PRISMATIC,
79
+ JOINT_REVOLUTE,
80
+ JOINT_SIX_DOF,
81
+ JOINT_SPHERICAL,
82
+ JointSet,
83
+ resetJointImpulses,
84
+ solveJoints,
85
+ } from './joints.ts';
86
+ export type { QueryFilter, RayHit } from './query.ts';
87
+ export { createRayHit, isStaticBody, overlapWorld, raycastWorld, shapecastWorld } from './query.ts';
88
+ export type { EventKind } from './events.ts';
89
+ export { ContactEvents, EVENT_ENTER, EVENT_EXIT, EVENT_STAY } from './events.ts';
90
+ export { BODY_SENSOR } from './bodies.ts';
91
+ export type {
92
+ ControllerInput,
93
+ ControllerOptions,
94
+ ControllerState,
95
+ GroundProbe,
96
+ } from './controller.ts';
97
+ export { AIRBORNE, CharacterController, GROUNDED, SLIDING } from './controller.ts';
98
+ export type { PoseTarget, Ragdoll, RagdollOptions } from './ragdoll.ts';
99
+ export { ragdollFromBones } from './ragdoll.ts';
100
+ export type { TyreCurve, VehicleInput, VehicleOptions, WheelOptions } from './vehicle.ts';
101
+ export { Vehicle, defaultTyreCurve, sampleTyreCurve } from './vehicle.ts';
102
+ export type { SweptState, SweptStepOptions, WorldOpinion } from './sweptStep.ts';
103
+ export { SweptStep } from './sweptStep.ts';
104
+ export type { PlanarState } from './groundMotor.ts';
105
+ export { accelerateAlong } from './groundMotor.ts';
106
+ export type { Buoyancy, BuoyancyOptions, BuoyantState } from './buoyancy.ts';
107
+ export { applyBuoyancy, createBuoyancy } from './buoyancy.ts';
108
+ export type { EscapeResult, EscapeVeto, StallEscapeOptions, StallInput } from './stallEscape.ts';
109
+ export {
110
+ ESCAPE_EASED,
111
+ ESCAPE_IDLE,
112
+ ESCAPE_PUSHED,
113
+ ESCAPE_SEALED,
114
+ StallEscape,
115
+ } from './stallEscape.ts';
116
+ export type { ClothOptions } from './cloth.ts';
117
+ export { ClothBody, makeClothGrid } from './cloth.ts';
118
+ export type { Collider, ColliderBytes, ColliderGroup } from './colliderSet.ts';
119
+ export { ColliderSet, boxCollider, colliderFromShape } from './colliderSet.ts';
120
+ export { fingerprintBodies, fingerprintColliders } from './fingerprint.ts';
121
+ export type { MassProperties } from './mass.ts';
122
+ export { combineMassProperties, createMassProperties, shapeMassProperties } from './mass.ts';
123
+ export type { TriangleMesh } from './meshShape.ts';
124
+ export { meshShape } from './meshShape.ts';
125
+ /*
126
+ * A heightfield as a collider: the samples, and every triangle generated from them.
127
+ *
128
+ * `Heightfield` is deliberately shaped so that a `Terrain` from `@driftengine/terrain` *is* one —
129
+ * same names, same meanings — which is how a package that imports no other engine package still
130
+ * takes one. See `heightfieldShape.ts`.
131
+ */
132
+ export { heightfieldShape } from './heightfieldShape.ts';
133
+ export type { Heightfield } from './heightfieldShape.ts';
134
+ export type { ConvexShape } from './shape.ts';
135
+ export {
136
+ boxShape,
137
+ capsuleShape,
138
+ cylinderShape,
139
+ hullShape,
140
+ shapeBounds,
141
+ sphereShape,
142
+ } from './shape.ts';
143
+ /*
144
+ * Convex decomposition: offline, and the only thing in this package that is not meant for a frame.
145
+ *
146
+ * Exported from the barrel because it produces `ConvexShape` point sets and belongs beside
147
+ * `hullShape`. A consumer who never names it never bundles it — the package is `sideEffects: false`
148
+ * and `physics-only` measures the consequence.
149
+ */
150
+ /* A body made of several shapes: the half of convex decomposition that lives in the tick. */
151
+ export {
152
+ MAX_BODY_PARTS,
153
+ MAX_PAIR_MANIFOLDS,
154
+ collideCompound,
155
+ partsBoundRadius,
156
+ } from './compoundContact.ts';
157
+ export type { ConvexPart, Decomposition, DecomposeFill, DecomposeOptions } from './decompose.ts';
158
+ export {
159
+ DEFAULT_MAX_HULLS,
160
+ DEFAULT_RESOLUTION,
161
+ MAX_RESOLUTION,
162
+ SUPPORT_DIRECTIONS,
163
+ decomposeConvex,
164
+ } from './decompose.ts';