@woosh/meep-engine 2.153.0 → 2.155.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.
- package/package.json +1 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +112 -0
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/ConvexHullShape3D.js +325 -0
- package/src/core/geom/vec3/v3_array_copy.d.ts +3 -3
- package/src/core/geom/vec3/v3_array_copy.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_array_copy.js +2 -2
- package/src/core/geom/vec3/v3_cross.d.ts +17 -0
- package/src/core/geom/vec3/v3_cross.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_cross.js +20 -0
- package/src/core/geom/vec3/v3_subtract.d.ts +16 -0
- package/src/core/geom/vec3/v3_subtract.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_subtract.js +19 -0
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +8 -0
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts +4 -0
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2D.js +21 -0
- package/src/engine/physics/PLAN.md +4 -4
- package/src/engine/physics/body/BodyStorage.d.ts +3 -1
- package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
- package/src/engine/physics/body/BodyStorage.js +452 -450
- package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -1
- package/src/engine/physics/body/SolverBodyState.js +6 -5
- package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
- package/src/engine/physics/broadphase/generate_pairs.js +9 -1
- package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -1
- package/src/engine/physics/ccd/linear_sweep.js +237 -238
- package/src/engine/physics/computeInterceptPoint.d.ts.map +1 -1
- package/src/engine/physics/computeInterceptPoint.js +8 -3
- package/src/engine/physics/contact/ManifoldStore.d.ts +0 -16
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +1 -38
- package/src/engine/physics/ecs/BodyKind.d.ts +3 -2
- package/src/engine/physics/ecs/BodyKind.d.ts.map +1 -1
- package/src/engine/physics/ecs/BodyKind.js +25 -24
- package/src/engine/physics/ecs/PhysicsEvents.d.ts +4 -5
- package/src/engine/physics/ecs/PhysicsEvents.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsEvents.js +15 -16
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +5 -30
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +13 -45
- package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts.map +1 -1
- package/src/engine/physics/ecs/RigidBodySerializationAdapter.js +85 -81
- package/src/engine/physics/ecs/is_sensor.d.ts +18 -0
- package/src/engine/physics/ecs/is_sensor.d.ts.map +1 -0
- package/src/engine/physics/ecs/is_sensor.js +27 -0
- package/src/engine/physics/events/ContactEventBuffer.d.ts +2 -1
- package/src/engine/physics/events/ContactEventBuffer.d.ts.map +1 -1
- package/src/engine/physics/events/ContactEventBuffer.js +84 -83
- package/src/engine/physics/gjk/gjk.d.ts +0 -26
- package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
- package/src/engine/physics/gjk/gjk.js +3 -52
- package/src/engine/physics/gjk/gjk_epa_penetration.d.ts +20 -0
- package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +1 -0
- package/src/engine/physics/gjk/gjk_epa_penetration.js +548 -0
- package/src/engine/physics/gjk/minkowski_support.d.ts +4 -9
- package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -1
- package/src/engine/physics/gjk/minkowski_support.js +70 -75
- package/src/engine/physics/gjk/mpr.d.ts +1 -1
- package/src/engine/physics/gjk/mpr.d.ts.map +1 -1
- package/src/engine/physics/gjk/mpr.js +362 -344
- package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
- package/src/engine/physics/island/IslandBuilder.js +431 -428
- package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_box_manifold.js +4 -81
- package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_triangle_contact.js +4 -39
- package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/capsule_contacts.js +459 -462
- package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/clip_against_axis_uv.js +4 -1
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +83 -0
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/convex_convex_manifold.js +425 -0
- package/src/engine/physics/narrowphase/convex_decomposition.d.ts +32 -0
- package/src/engine/physics/narrowphase/convex_decomposition.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/convex_decomposition.js +293 -0
- package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts +41 -0
- package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/mesh_convex_hull.js +106 -0
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.d.ts +8 -0
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +105 -102
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts +29 -0
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +69 -0
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_concave.js +152 -145
- package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/sphere_box_contact.js +132 -123
- package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
- package/src/engine/physics/queries/overlap_shape.js +16 -17
- package/src/engine/physics/queries/raycast.d.ts +5 -0
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +16 -8
- package/src/engine/physics/queries/shape_cast.d.ts.map +1 -1
- package/src/engine/physics/queries/shape_cast.js +13 -7
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +8 -11
- package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -1
- package/src/engine/physics/vehicle/RaycastVehicle.js +339 -333
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +0 -13
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +0 -1
- package/src/engine/physics/gjk/expanding_polytope_algorithm.js +0 -399
|
@@ -1,428 +1,431 @@
|
|
|
1
|
-
import { ceilPowerOfTwo } from "../../../core/binary/operations/ceilPowerOfTwo.js";
|
|
2
|
-
import { body_id_index } from "../body/BodyStorage.js";
|
|
3
|
-
import { BodyKind } from "../ecs/BodyKind.js";
|
|
4
|
-
import {
|
|
5
|
-
import { JOINT_WORLD } from "../ecs/Joint.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* -
|
|
30
|
-
* -
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* @
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
*
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* @
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* @
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
*
|
|
122
|
-
* @
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
* @param {
|
|
130
|
-
* @
|
|
131
|
-
* @
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* @param {
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
for (let
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
*
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
offsets[
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
this.
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
1
|
+
import { ceilPowerOfTwo } from "../../../core/binary/operations/ceilPowerOfTwo.js";
|
|
2
|
+
import { body_id_index } from "../body/BodyStorage.js";
|
|
3
|
+
import { BodyKind } from "../ecs/BodyKind.js";
|
|
4
|
+
import { is_sensor } from "../ecs/is_sensor.js";
|
|
5
|
+
import { JOINT_WORLD } from "../ecs/Joint.js";
|
|
6
|
+
import { uf_find, uf_init, uf_union } from "./union_find.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Partitions this frame's awake dynamic bodies + touched non-sensor contacts
|
|
10
|
+
* into connected components ("islands") via union-find. The solver iterates
|
|
11
|
+
* each island independently (so impulses converge inside an island without
|
|
12
|
+
* waiting for global passes) and the sleep test will eventually use island
|
|
13
|
+
* granularity for atomic whole-island sleep.
|
|
14
|
+
*
|
|
15
|
+
* Static and Kinematic bodies are not merged into islands — they act as
|
|
16
|
+
* constraint anchors. A 1000-block stack on a static floor is therefore one
|
|
17
|
+
* island of 1000 dynamic bodies, not 1001 — the floor anchors the island but
|
|
18
|
+
* does not enlarge it. Multiple separate piles all resting on the same static
|
|
19
|
+
* floor remain *separate* islands so each can sleep/wake independently.
|
|
20
|
+
*
|
|
21
|
+
* Sensor contacts are skipped entirely: they don't transmit constraint
|
|
22
|
+
* forces, so two bodies linked only by a sensor pair are not in the same
|
|
23
|
+
* structural island.
|
|
24
|
+
*
|
|
25
|
+
* Determinism contract:
|
|
26
|
+
* - Union-find uses union-by-min-index + path halving, so the canonical
|
|
27
|
+
* root of any component is the smallest body index of any of its members.
|
|
28
|
+
* - Islands are emitted sorted ascending by root index.
|
|
29
|
+
* - Bodies within an island are emitted sorted ascending by body index.
|
|
30
|
+
* - Contacts within an island are emitted sorted ascending by manifold slot id.
|
|
31
|
+
*
|
|
32
|
+
* Output layout is CSR-like — `body_offsets[i]..body_offsets[i+1]` indexes
|
|
33
|
+
* into `body_data` for island `i`, similarly for contacts.
|
|
34
|
+
*
|
|
35
|
+
* @author Alex Goldring
|
|
36
|
+
* @copyright Company Named Limited (c) 2026
|
|
37
|
+
*/
|
|
38
|
+
export class IslandBuilder {
|
|
39
|
+
|
|
40
|
+
constructor() {
|
|
41
|
+
/**
|
|
42
|
+
* Union-find parent table, indexed by body index (NOT packed id).
|
|
43
|
+
* Sized to `storage.high_water_mark` at `build` time; reallocated by
|
|
44
|
+
* doubling when the body pool grows past current capacity.
|
|
45
|
+
* @type {Uint32Array}
|
|
46
|
+
*/
|
|
47
|
+
this.parent = new Uint32Array(16);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Per-body island id. `island_of_body[idx] === -1` means the body
|
|
51
|
+
* is not in any island this frame (static, kinematic, or unallocated).
|
|
52
|
+
* @type {Int32Array}
|
|
53
|
+
*/
|
|
54
|
+
this.island_of_body = new Int32Array(16);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Number of islands this frame.
|
|
58
|
+
* @type {number}
|
|
59
|
+
*/
|
|
60
|
+
this.island_count = 0;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* CSR offsets into `body_data`. Length is `island_count + 1`. Bodies
|
|
64
|
+
* for island `i` live in `body_data[body_offsets[i]..body_offsets[i+1])`.
|
|
65
|
+
* @type {Uint32Array}
|
|
66
|
+
*/
|
|
67
|
+
this.body_offsets = new Uint32Array(2);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Flattened body indices, grouped by island, sorted ascending within
|
|
71
|
+
* each island.
|
|
72
|
+
* @type {Uint32Array}
|
|
73
|
+
*/
|
|
74
|
+
this.body_data = new Uint32Array(16);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Total number of body entries across all islands (= sum of island sizes).
|
|
78
|
+
* @type {number}
|
|
79
|
+
*/
|
|
80
|
+
this.body_total = 0;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* CSR offsets into `contact_data`. Length is `island_count + 1`.
|
|
84
|
+
* Manifold slot ids for island `i` live in
|
|
85
|
+
* `contact_data[contact_offsets[i]..contact_offsets[i+1])`.
|
|
86
|
+
* @type {Uint32Array}
|
|
87
|
+
*/
|
|
88
|
+
this.contact_offsets = new Uint32Array(2);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Flattened manifold slot ids, grouped by island, sorted ascending
|
|
92
|
+
* within each island.
|
|
93
|
+
* @type {Uint32Array}
|
|
94
|
+
*/
|
|
95
|
+
this.contact_data = new Uint32Array(16);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Total number of contact entries across all islands.
|
|
99
|
+
* @type {number}
|
|
100
|
+
*/
|
|
101
|
+
this.contact_total = 0;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Scratch: root body index → island id, or -1 if unassigned.
|
|
105
|
+
* Sized in lockstep with `parent`.
|
|
106
|
+
* @private
|
|
107
|
+
* @type {Int32Array}
|
|
108
|
+
*/
|
|
109
|
+
this.__root_to_island = new Int32Array(16);
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Scratch: list of distinct root indices observed this frame, used
|
|
113
|
+
* to sort them ascending before assigning island ids.
|
|
114
|
+
* @private
|
|
115
|
+
* @type {Uint32Array}
|
|
116
|
+
*/
|
|
117
|
+
this.__scratch_roots = new Uint32Array(16);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Scratch write-cursors, one per island.
|
|
121
|
+
* @private
|
|
122
|
+
* @type {Uint32Array}
|
|
123
|
+
*/
|
|
124
|
+
this.__cursors = new Uint32Array(2);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {RigidBody} rb
|
|
129
|
+
* @param {Array} collider_list collider list for the body (or undefined)
|
|
130
|
+
* @returns {boolean}
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
static __body_is_sensor(rb, collider_list) {
|
|
134
|
+
// v1 approximation: the body's primary (first) collider stands in for
|
|
135
|
+
// the whole body, matching the solver's pair_is_sensor / raycast checks.
|
|
136
|
+
const collider = (collider_list !== undefined && collider_list.length > 0)
|
|
137
|
+
? collider_list[0].collider
|
|
138
|
+
: null;
|
|
139
|
+
return is_sensor(rb, collider);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* (Re)build islands from `manifolds`' currently-touched non-sensor slots
|
|
144
|
+
* and the bodies in `storage`'s awake list.
|
|
145
|
+
*
|
|
146
|
+
* @param {BodyStorage} storage
|
|
147
|
+
* @param {ManifoldStore} manifolds
|
|
148
|
+
* @param {RigidBody[]} bodies sparse, indexed by body index
|
|
149
|
+
* @param {Array[]} body_collider_lists sparse, indexed by body index
|
|
150
|
+
* @param {Joint[]} joints live joints (sparse). Jointed dynamic-dynamic
|
|
151
|
+
* bodies are unioned into the same island so a chain / ragdoll sleeps
|
|
152
|
+
* and wakes as a unit. Callers with no joints pass an empty array.
|
|
153
|
+
*/
|
|
154
|
+
build(storage, manifolds, bodies, body_collider_lists, joints) {
|
|
155
|
+
const hwm = storage.high_water_mark;
|
|
156
|
+
this.__ensure_body_capacity(hwm);
|
|
157
|
+
|
|
158
|
+
const parent = this.parent;
|
|
159
|
+
uf_init(parent, hwm);
|
|
160
|
+
|
|
161
|
+
// --- Pass 1: union dynamic-dynamic pairs from touched non-sensor manifolds.
|
|
162
|
+
const live_count = manifolds.count;
|
|
163
|
+
for (let i = 0; i < live_count; i++) {
|
|
164
|
+
const slot = manifolds.live_at(i);
|
|
165
|
+
if (!manifolds.is_touched(slot)) continue;
|
|
166
|
+
const idxA = body_id_index(manifolds.bodyA(slot));
|
|
167
|
+
const idxB = body_id_index(manifolds.bodyB(slot));
|
|
168
|
+
const rbA = bodies[idxA];
|
|
169
|
+
const rbB = bodies[idxB];
|
|
170
|
+
if (rbA === undefined || rbB === undefined) continue;
|
|
171
|
+
if (IslandBuilder.__body_is_sensor(rbA, body_collider_lists[idxA])) continue;
|
|
172
|
+
if (IslandBuilder.__body_is_sensor(rbB, body_collider_lists[idxB])) continue;
|
|
173
|
+
if (rbA.kind === BodyKind.Dynamic && rbB.kind === BodyKind.Dynamic) {
|
|
174
|
+
uf_union(parent, idxA, idxB);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// --- Pass 1b: union dynamic-dynamic bodies connected by a joint, so a
|
|
179
|
+
// chain / ragdoll forms one island (and so sleeps/wakes as a unit).
|
|
180
|
+
// Joint-to-world and joint-to-static/kinematic anchor the island
|
|
181
|
+
// without enlarging it — same rule as a static contact. Stale joint
|
|
182
|
+
// references (body unlinked / slot reused) are filtered by the
|
|
183
|
+
// generation-checked `is_valid`. (Empty when the caller has no
|
|
184
|
+
// joints — the loop is then a no-op.)
|
|
185
|
+
const jn = joints.length;
|
|
186
|
+
for (let i = 0; i < jn; i++) {
|
|
187
|
+
const joint = joints[i];
|
|
188
|
+
if (joint === undefined || joint === null) continue;
|
|
189
|
+
if (joint._bodyIdB === JOINT_WORLD) continue;
|
|
190
|
+
if (!storage.is_valid(joint._bodyIdA) || !storage.is_valid(joint._bodyIdB)) continue;
|
|
191
|
+
const idxA = body_id_index(joint._bodyIdA);
|
|
192
|
+
const idxB = body_id_index(joint._bodyIdB);
|
|
193
|
+
const rbA = bodies[idxA];
|
|
194
|
+
const rbB = bodies[idxB];
|
|
195
|
+
if (rbA === undefined || rbB === undefined) continue;
|
|
196
|
+
if (rbA.kind === BodyKind.Dynamic && rbB.kind === BodyKind.Dynamic) {
|
|
197
|
+
uf_union(parent, idxA, idxB);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// --- Pass 2: collect distinct roots over awake dynamic bodies.
|
|
202
|
+
const island_of_body = this.island_of_body;
|
|
203
|
+
// Cheap reset: only the indices we may write are below hwm.
|
|
204
|
+
for (let i = 0; i < hwm; i++) island_of_body[i] = -1;
|
|
205
|
+
|
|
206
|
+
const root_to_island = this.__root_to_island;
|
|
207
|
+
for (let i = 0; i < hwm; i++) root_to_island[i] = -1;
|
|
208
|
+
|
|
209
|
+
const awake_count = storage.awake_count;
|
|
210
|
+
const scratch_roots = this.__scratch_roots;
|
|
211
|
+
let root_count = 0;
|
|
212
|
+
for (let ai = 0; ai < awake_count; ai++) {
|
|
213
|
+
const idx = storage.awake_at(ai);
|
|
214
|
+
const rb = bodies[idx];
|
|
215
|
+
if (rb === undefined) continue;
|
|
216
|
+
if (rb.kind !== BodyKind.Dynamic) continue;
|
|
217
|
+
const r = uf_find(parent, idx);
|
|
218
|
+
if (root_to_island[r] === -1) {
|
|
219
|
+
scratch_roots[root_count++] = r;
|
|
220
|
+
// Tag as collected — actual id assigned after sort.
|
|
221
|
+
root_to_island[r] = -2;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Sort distinct roots ascending so island id 0 has the smallest root.
|
|
226
|
+
if (root_count > 1) {
|
|
227
|
+
scratch_roots.subarray(0, root_count).sort();
|
|
228
|
+
}
|
|
229
|
+
for (let i = 0; i < root_count; i++) {
|
|
230
|
+
root_to_island[scratch_roots[i]] = i;
|
|
231
|
+
}
|
|
232
|
+
this.island_count = root_count;
|
|
233
|
+
|
|
234
|
+
// Assign island id to every awake dynamic body.
|
|
235
|
+
for (let ai = 0; ai < awake_count; ai++) {
|
|
236
|
+
const idx = storage.awake_at(ai);
|
|
237
|
+
const rb = bodies[idx];
|
|
238
|
+
if (rb === undefined) continue;
|
|
239
|
+
if (rb.kind !== BodyKind.Dynamic) continue;
|
|
240
|
+
const r = uf_find(parent, idx);
|
|
241
|
+
island_of_body[idx] = root_to_island[r];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
this.__build_body_csr(storage, bodies, awake_count, root_count);
|
|
245
|
+
this.__build_contact_csr(manifolds, bodies, body_collider_lists, root_count);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Fill `body_offsets` + `body_data` with awake dynamic bodies grouped
|
|
250
|
+
* by island, sorted ascending within each island.
|
|
251
|
+
* @private
|
|
252
|
+
*/
|
|
253
|
+
__build_body_csr(storage, bodies, awake_count, island_count) {
|
|
254
|
+
this.__ensure_island_count_capacity(island_count);
|
|
255
|
+
const offsets = this.body_offsets;
|
|
256
|
+
for (let i = 0; i <= island_count; i++) offsets[i] = 0;
|
|
257
|
+
|
|
258
|
+
// Count pass: offsets[i + 1] starts as the number of bodies in island i.
|
|
259
|
+
let total = 0;
|
|
260
|
+
for (let ai = 0; ai < awake_count; ai++) {
|
|
261
|
+
const idx = storage.awake_at(ai);
|
|
262
|
+
const rb = bodies[idx];
|
|
263
|
+
if (rb === undefined) continue;
|
|
264
|
+
if (rb.kind !== BodyKind.Dynamic) continue;
|
|
265
|
+
const isl = this.island_of_body[idx];
|
|
266
|
+
if (isl < 0) continue;
|
|
267
|
+
offsets[isl + 1]++;
|
|
268
|
+
total++;
|
|
269
|
+
}
|
|
270
|
+
// Prefix sum → start offsets.
|
|
271
|
+
for (let i = 0; i < island_count; i++) {
|
|
272
|
+
offsets[i + 1] += offsets[i];
|
|
273
|
+
}
|
|
274
|
+
this.body_total = total;
|
|
275
|
+
|
|
276
|
+
this.__ensure_body_data_capacity(total);
|
|
277
|
+
|
|
278
|
+
const cursors = this.__cursors;
|
|
279
|
+
for (let i = 0; i < island_count; i++) cursors[i] = offsets[i];
|
|
280
|
+
|
|
281
|
+
const data = this.body_data;
|
|
282
|
+
for (let ai = 0; ai < awake_count; ai++) {
|
|
283
|
+
const idx = storage.awake_at(ai);
|
|
284
|
+
const rb = bodies[idx];
|
|
285
|
+
if (rb === undefined) continue;
|
|
286
|
+
if (rb.kind !== BodyKind.Dynamic) continue;
|
|
287
|
+
const isl = this.island_of_body[idx];
|
|
288
|
+
if (isl < 0) continue;
|
|
289
|
+
data[cursors[isl]++] = idx;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Sort each island's body slice ascending.
|
|
293
|
+
for (let i = 0; i < island_count; i++) {
|
|
294
|
+
const start = offsets[i];
|
|
295
|
+
const end = offsets[i + 1];
|
|
296
|
+
if (end - start > 1) {
|
|
297
|
+
data.subarray(start, end).sort();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Fill `contact_offsets` + `contact_data` with touched non-sensor manifold
|
|
304
|
+
* slot ids grouped by island. A contact belongs to its dynamic
|
|
305
|
+
* participant's island (if both sides are dynamic they share an island
|
|
306
|
+
* by construction). Static-vs-static contacts are skipped.
|
|
307
|
+
* @private
|
|
308
|
+
*/
|
|
309
|
+
__build_contact_csr(manifolds, bodies, body_collider_lists, island_count) {
|
|
310
|
+
const offsets = this.contact_offsets;
|
|
311
|
+
for (let i = 0; i <= island_count; i++) offsets[i] = 0;
|
|
312
|
+
|
|
313
|
+
// Count pass.
|
|
314
|
+
const live_count = manifolds.count;
|
|
315
|
+
let total = 0;
|
|
316
|
+
for (let i = 0; i < live_count; i++) {
|
|
317
|
+
const slot = manifolds.live_at(i);
|
|
318
|
+
const isl = this.__island_of_slot(manifolds, slot, bodies, body_collider_lists);
|
|
319
|
+
if (isl < 0) continue;
|
|
320
|
+
offsets[isl + 1]++;
|
|
321
|
+
total++;
|
|
322
|
+
}
|
|
323
|
+
for (let i = 0; i < island_count; i++) {
|
|
324
|
+
offsets[i + 1] += offsets[i];
|
|
325
|
+
}
|
|
326
|
+
this.contact_total = total;
|
|
327
|
+
|
|
328
|
+
this.__ensure_contact_data_capacity(total);
|
|
329
|
+
|
|
330
|
+
const cursors = this.__cursors;
|
|
331
|
+
for (let i = 0; i < island_count; i++) cursors[i] = offsets[i];
|
|
332
|
+
|
|
333
|
+
const data = this.contact_data;
|
|
334
|
+
for (let i = 0; i < live_count; i++) {
|
|
335
|
+
const slot = manifolds.live_at(i);
|
|
336
|
+
const isl = this.__island_of_slot(manifolds, slot, bodies, body_collider_lists);
|
|
337
|
+
if (isl < 0) continue;
|
|
338
|
+
data[cursors[isl]++] = slot;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Sort each island's contact slice ascending by slot id.
|
|
342
|
+
for (let i = 0; i < island_count; i++) {
|
|
343
|
+
const start = offsets[i];
|
|
344
|
+
const end = offsets[i + 1];
|
|
345
|
+
if (end - start > 1) {
|
|
346
|
+
data.subarray(start, end).sort();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Resolve which island a manifold slot belongs to.
|
|
353
|
+
*
|
|
354
|
+
* Returns `-1` if the manifold is not touched this frame, is a sensor
|
|
355
|
+
* pair, or both bodies are non-dynamic (static-static, which the solver
|
|
356
|
+
* wouldn't act on anyway).
|
|
357
|
+
*
|
|
358
|
+
* @private
|
|
359
|
+
*/
|
|
360
|
+
__island_of_slot(manifolds, slot, bodies, body_collider_lists) {
|
|
361
|
+
if (!manifolds.is_touched(slot)) return -1;
|
|
362
|
+
const idxA = body_id_index(manifolds.bodyA(slot));
|
|
363
|
+
const idxB = body_id_index(manifolds.bodyB(slot));
|
|
364
|
+
const rbA = bodies[idxA];
|
|
365
|
+
const rbB = bodies[idxB];
|
|
366
|
+
if (rbA === undefined || rbB === undefined) return -1;
|
|
367
|
+
if (IslandBuilder.__body_is_sensor(rbA, body_collider_lists[idxA])) return -1;
|
|
368
|
+
if (IslandBuilder.__body_is_sensor(rbB, body_collider_lists[idxB])) return -1;
|
|
369
|
+
// Pick the dynamic side's island. If both are dynamic they share one by
|
|
370
|
+
// construction (pass-1 union). Guard `>= 0`: a dynamic body that is ASLEEP
|
|
371
|
+
// has island_of_body === -1 (islands cover only awake bodies). Returning
|
|
372
|
+
// that -1 would drop the contact from the island, so the live
|
|
373
|
+
// non-penetration constraint against the awake side would never be solved
|
|
374
|
+
// — e.g. an awake body resting on a freshly-slept neighbour falls through.
|
|
375
|
+
// Falling through to side B keeps the awake dynamic side's island.
|
|
376
|
+
const ia = this.island_of_body[idxA];
|
|
377
|
+
if (rbA.kind === BodyKind.Dynamic && ia >= 0) return ia;
|
|
378
|
+
const ib = this.island_of_body[idxB];
|
|
379
|
+
if (rbB.kind === BodyKind.Dynamic && ib >= 0) return ib;
|
|
380
|
+
return -1;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// --- Resize helpers ------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @private
|
|
387
|
+
* @param {number} n
|
|
388
|
+
*/
|
|
389
|
+
__ensure_body_capacity(n) {
|
|
390
|
+
if (this.parent.length < n) {
|
|
391
|
+
const cap = ceilPowerOfTwo(n);
|
|
392
|
+
this.parent = new Uint32Array(cap);
|
|
393
|
+
this.island_of_body = new Int32Array(cap);
|
|
394
|
+
this.__root_to_island = new Int32Array(cap);
|
|
395
|
+
this.__scratch_roots = new Uint32Array(cap);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* @private
|
|
401
|
+
* @param {number} n
|
|
402
|
+
*/
|
|
403
|
+
__ensure_island_count_capacity(n) {
|
|
404
|
+
if (this.body_offsets.length < n + 1) {
|
|
405
|
+
const cap = ceilPowerOfTwo(n + 1);
|
|
406
|
+
this.body_offsets = new Uint32Array(cap);
|
|
407
|
+
this.contact_offsets = new Uint32Array(cap);
|
|
408
|
+
this.__cursors = new Uint32Array(cap);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @private
|
|
414
|
+
* @param {number} n
|
|
415
|
+
*/
|
|
416
|
+
__ensure_body_data_capacity(n) {
|
|
417
|
+
if (this.body_data.length < n) {
|
|
418
|
+
this.body_data = new Uint32Array(ceilPowerOfTwo(n));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* @private
|
|
424
|
+
* @param {number} n
|
|
425
|
+
*/
|
|
426
|
+
__ensure_contact_data_capacity(n) {
|
|
427
|
+
if (this.contact_data.length < n) {
|
|
428
|
+
this.contact_data = new Uint32Array(ceilPowerOfTwo(n));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|