@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,450 +1,452 @@
|
|
|
1
|
-
import { assert } from "../../../core/assert.js";
|
|
2
|
-
import { BodyKind } from "../ecs/BodyKind.js";
|
|
3
|
-
|
|
4
|
-
const DEFAULT_INITIAL_CAPACITY = 16;
|
|
5
|
-
const MIN_CAPACITY = 4;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Mask for the 8-bit generation field encoded in a packed body id.
|
|
9
|
-
* @type {number}
|
|
10
|
-
*/
|
|
11
|
-
const GENERATION_MASK = 0xFF;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Bit width of the generation field. The remaining bits are the body index;
|
|
15
|
-
* 24-bit indices give us 16M live bodies which is comfortably above the design
|
|
16
|
-
* target of "millions".
|
|
17
|
-
* @type {number}
|
|
18
|
-
*/
|
|
19
|
-
const GENERATION_BITS = 8;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Sentinel returned by {@link BodyStorage#awake_position_of} and friends when a
|
|
23
|
-
* body is not in the awake set.
|
|
24
|
-
* @type {number}
|
|
25
|
-
*/
|
|
26
|
-
export const BODY_INDEX_ABSENT = -1;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Pack a body index and generation into a single integer handle.
|
|
30
|
-
*
|
|
31
|
-
* @param {number} index
|
|
32
|
-
* @param {number} generation
|
|
33
|
-
* @returns {number}
|
|
34
|
-
*/
|
|
35
|
-
export function pack_body_id(index, generation) {
|
|
36
|
-
return (index << GENERATION_BITS) | (generation & GENERATION_MASK);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
41
|
-
* @param {number} packed
|
|
42
|
-
* @returns {number}
|
|
43
|
-
*/
|
|
44
|
-
export function body_id_index(packed) {
|
|
45
|
-
return packed >>> GENERATION_BITS;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
*
|
|
50
|
-
* @param {number} packed
|
|
51
|
-
* @returns {number}
|
|
52
|
-
*/
|
|
53
|
-
export function body_id_generation(packed) {
|
|
54
|
-
return packed & GENERATION_MASK;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Structure-of-arrays pool for rigid bodies.
|
|
59
|
-
*
|
|
60
|
-
* Owns:
|
|
61
|
-
* - per-body identity (entity, generation, kind, flags),
|
|
62
|
-
* - a dense list of awake body indices (the simulation hot iteration target),
|
|
63
|
-
* - a min-heap of free body indices so reuse order is deterministic regardless
|
|
64
|
-
* of how interleaved allocate / free calls have been.
|
|
65
|
-
*
|
|
66
|
-
* The pool grows by doubling when the high-water mark reaches capacity; arrays
|
|
67
|
-
* are replaced wholesale on grow, so callers must not retain references to the
|
|
68
|
-
* raw typed arrays across allocate-after-grow boundaries.
|
|
69
|
-
*
|
|
70
|
-
* Determinism: allocate always reuses the lowest free index. Two pools given the
|
|
71
|
-
* same sequence of allocate/free calls observe identical body indices.
|
|
72
|
-
*
|
|
73
|
-
* @author Alex Goldring
|
|
74
|
-
* @copyright Company Named Limited (c) 2026
|
|
75
|
-
*/
|
|
76
|
-
export class BodyStorage {
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
*
|
|
80
|
-
* @param {number} [initial_capacity]
|
|
81
|
-
*/
|
|
82
|
-
constructor(initial_capacity = DEFAULT_INITIAL_CAPACITY) {
|
|
83
|
-
assert.isNonNegativeInteger(initial_capacity, 'initial_capacity');
|
|
84
|
-
|
|
85
|
-
const cap = Math.max(MIN_CAPACITY, initial_capacity);
|
|
86
|
-
|
|
87
|
-
this.__capacity = cap;
|
|
88
|
-
// High-water mark of slot indices ever issued. Slots in [0, count) are
|
|
89
|
-
// either currently allocated or sitting on the free heap.
|
|
90
|
-
this.__count = 0;
|
|
91
|
-
|
|
92
|
-
this.__entities = new Int32Array(cap);
|
|
93
|
-
this.__generations = new Uint8Array(cap);
|
|
94
|
-
this.__kinds = new Uint8Array(cap);
|
|
95
|
-
this.__flags = new Uint32Array(cap);
|
|
96
|
-
|
|
97
|
-
// 1 = allocated, 0 = free. Cheaper than scanning the free heap.
|
|
98
|
-
this.__alive = new Uint8Array(cap);
|
|
99
|
-
|
|
100
|
-
// Awake set: dense list of awake body indices + reverse map.
|
|
101
|
-
this.__awake_list = new Uint32Array(cap);
|
|
102
|
-
this.__awake_pos = new Int32Array(cap);
|
|
103
|
-
this.__awake_count = 0;
|
|
104
|
-
|
|
105
|
-
// Min-heap of free body indices for deterministic reuse.
|
|
106
|
-
this.__free_heap = new Uint32Array(cap);
|
|
107
|
-
this.__free_count = 0;
|
|
108
|
-
|
|
109
|
-
// Entity → body-index map (one body per entity). Keeps {@link
|
|
110
|
-
// index_of_entity} O(1) instead of an O(N) scan over the slot table on
|
|
111
|
-
// every collider attach / detach and joint link. Maintained on
|
|
112
|
-
// allocate / free; never grows with the typed arrays (a plain Map).
|
|
113
|
-
this.__entity_to_index = new Map();
|
|
114
|
-
|
|
115
|
-
// Initialise reverse map to BODY_INDEX_ABSENT.
|
|
116
|
-
this.__awake_pos.fill(BODY_INDEX_ABSENT);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Currently allocated body count (live, regardless of awake/sleeping).
|
|
121
|
-
* @returns {number}
|
|
122
|
-
*/
|
|
123
|
-
get size() {
|
|
124
|
-
return this.__count - this.__free_count;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Total slot capacity.
|
|
129
|
-
*
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
*
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* @
|
|
158
|
-
*
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.
|
|
175
|
-
this.
|
|
176
|
-
this.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
this.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
*
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
*
|
|
247
|
-
* @
|
|
248
|
-
*
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
*
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
this.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
this.
|
|
388
|
-
this.
|
|
389
|
-
this.
|
|
390
|
-
this.
|
|
391
|
-
this.
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
next_pos
|
|
397
|
-
this.__awake_pos
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
heap
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
heap[
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
heap[
|
|
445
|
-
i = smallest;
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
1
|
+
import { assert } from "../../../core/assert.js";
|
|
2
|
+
import { BodyKind } from "../ecs/BodyKind.js";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_INITIAL_CAPACITY = 16;
|
|
5
|
+
const MIN_CAPACITY = 4;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Mask for the 8-bit generation field encoded in a packed body id.
|
|
9
|
+
* @type {number}
|
|
10
|
+
*/
|
|
11
|
+
const GENERATION_MASK = 0xFF;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Bit width of the generation field. The remaining bits are the body index;
|
|
15
|
+
* 24-bit indices give us 16M live bodies which is comfortably above the design
|
|
16
|
+
* target of "millions".
|
|
17
|
+
* @type {number}
|
|
18
|
+
*/
|
|
19
|
+
const GENERATION_BITS = 8;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Sentinel returned by {@link BodyStorage#awake_position_of} and friends when a
|
|
23
|
+
* body is not in the awake set.
|
|
24
|
+
* @type {number}
|
|
25
|
+
*/
|
|
26
|
+
export const BODY_INDEX_ABSENT = -1;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Pack a body index and generation into a single integer handle.
|
|
30
|
+
*
|
|
31
|
+
* @param {number} index
|
|
32
|
+
* @param {number} generation
|
|
33
|
+
* @returns {number}
|
|
34
|
+
*/
|
|
35
|
+
export function pack_body_id(index, generation) {
|
|
36
|
+
return (index << GENERATION_BITS) | (generation & GENERATION_MASK);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param {number} packed
|
|
42
|
+
* @returns {number}
|
|
43
|
+
*/
|
|
44
|
+
export function body_id_index(packed) {
|
|
45
|
+
return packed >>> GENERATION_BITS;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
* @param {number} packed
|
|
51
|
+
* @returns {number}
|
|
52
|
+
*/
|
|
53
|
+
export function body_id_generation(packed) {
|
|
54
|
+
return packed & GENERATION_MASK;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Structure-of-arrays pool for rigid bodies.
|
|
59
|
+
*
|
|
60
|
+
* Owns:
|
|
61
|
+
* - per-body identity (entity, generation, kind, flags),
|
|
62
|
+
* - a dense list of awake body indices (the simulation hot iteration target),
|
|
63
|
+
* - a min-heap of free body indices so reuse order is deterministic regardless
|
|
64
|
+
* of how interleaved allocate / free calls have been.
|
|
65
|
+
*
|
|
66
|
+
* The pool grows by doubling when the high-water mark reaches capacity; arrays
|
|
67
|
+
* are replaced wholesale on grow, so callers must not retain references to the
|
|
68
|
+
* raw typed arrays across allocate-after-grow boundaries.
|
|
69
|
+
*
|
|
70
|
+
* Determinism: allocate always reuses the lowest free index. Two pools given the
|
|
71
|
+
* same sequence of allocate/free calls observe identical body indices.
|
|
72
|
+
*
|
|
73
|
+
* @author Alex Goldring
|
|
74
|
+
* @copyright Company Named Limited (c) 2026
|
|
75
|
+
*/
|
|
76
|
+
export class BodyStorage {
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
*
|
|
80
|
+
* @param {number} [initial_capacity]
|
|
81
|
+
*/
|
|
82
|
+
constructor(initial_capacity = DEFAULT_INITIAL_CAPACITY) {
|
|
83
|
+
assert.isNonNegativeInteger(initial_capacity, 'initial_capacity');
|
|
84
|
+
|
|
85
|
+
const cap = Math.max(MIN_CAPACITY, initial_capacity);
|
|
86
|
+
|
|
87
|
+
this.__capacity = cap;
|
|
88
|
+
// High-water mark of slot indices ever issued. Slots in [0, count) are
|
|
89
|
+
// either currently allocated or sitting on the free heap.
|
|
90
|
+
this.__count = 0;
|
|
91
|
+
|
|
92
|
+
this.__entities = new Int32Array(cap);
|
|
93
|
+
this.__generations = new Uint8Array(cap);
|
|
94
|
+
this.__kinds = new Uint8Array(cap);
|
|
95
|
+
this.__flags = new Uint32Array(cap);
|
|
96
|
+
|
|
97
|
+
// 1 = allocated, 0 = free. Cheaper than scanning the free heap.
|
|
98
|
+
this.__alive = new Uint8Array(cap);
|
|
99
|
+
|
|
100
|
+
// Awake set: dense list of awake body indices + reverse map.
|
|
101
|
+
this.__awake_list = new Uint32Array(cap);
|
|
102
|
+
this.__awake_pos = new Int32Array(cap);
|
|
103
|
+
this.__awake_count = 0;
|
|
104
|
+
|
|
105
|
+
// Min-heap of free body indices for deterministic reuse.
|
|
106
|
+
this.__free_heap = new Uint32Array(cap);
|
|
107
|
+
this.__free_count = 0;
|
|
108
|
+
|
|
109
|
+
// Entity → body-index map (one body per entity). Keeps {@link
|
|
110
|
+
// index_of_entity} O(1) instead of an O(N) scan over the slot table on
|
|
111
|
+
// every collider attach / detach and joint link. Maintained on
|
|
112
|
+
// allocate / free; never grows with the typed arrays (a plain Map).
|
|
113
|
+
this.__entity_to_index = new Map();
|
|
114
|
+
|
|
115
|
+
// Initialise reverse map to BODY_INDEX_ABSENT.
|
|
116
|
+
this.__awake_pos.fill(BODY_INDEX_ABSENT);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Currently allocated body count (live, regardless of awake/sleeping).
|
|
121
|
+
* @returns {number}
|
|
122
|
+
*/
|
|
123
|
+
get size() {
|
|
124
|
+
return this.__count - this.__free_count;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Total slot capacity. Starts at the (power-of-two) default and grows by
|
|
129
|
+
* doubling; a caller-supplied non-power-of-two initial capacity is honoured
|
|
130
|
+
* as-is, so this is not guaranteed to be a power of two.
|
|
131
|
+
* @returns {number}
|
|
132
|
+
*/
|
|
133
|
+
get capacity() {
|
|
134
|
+
return this.__capacity;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Number of awake bodies.
|
|
139
|
+
* @returns {number}
|
|
140
|
+
*/
|
|
141
|
+
get awake_count() {
|
|
142
|
+
return this.__awake_count;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* High-water mark of slot indices ever issued. Useful when callers maintain
|
|
147
|
+
* per-slot side-tables.
|
|
148
|
+
* @returns {number}
|
|
149
|
+
*/
|
|
150
|
+
get high_water_mark() {
|
|
151
|
+
return this.__count;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Allocate a body slot for `entity`. The new body starts in the awake set
|
|
156
|
+
* with default kind {@link BodyKind.Dynamic} and zero flags; the caller
|
|
157
|
+
* may override these via {@link set_kind} / {@link set_flags}.
|
|
158
|
+
*
|
|
159
|
+
* @param {number} entity
|
|
160
|
+
* @returns {number} packed body id
|
|
161
|
+
*/
|
|
162
|
+
allocate(entity) {
|
|
163
|
+
let index;
|
|
164
|
+
|
|
165
|
+
if (this.__free_count > 0) {
|
|
166
|
+
index = this.__heap_pop();
|
|
167
|
+
} else {
|
|
168
|
+
if (this.__count === this.__capacity) {
|
|
169
|
+
this.__grow();
|
|
170
|
+
}
|
|
171
|
+
index = this.__count++;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.__entities[index] = entity;
|
|
175
|
+
this.__kinds[index] = BodyKind.Dynamic;
|
|
176
|
+
this.__flags[index] = 0;
|
|
177
|
+
this.__alive[index] = 1;
|
|
178
|
+
this.__entity_to_index.set(entity, index);
|
|
179
|
+
|
|
180
|
+
// Insert into awake set.
|
|
181
|
+
const awake_pos = this.__awake_count++;
|
|
182
|
+
this.__awake_list[awake_pos] = index;
|
|
183
|
+
this.__awake_pos[index] = awake_pos;
|
|
184
|
+
|
|
185
|
+
return pack_body_id(index, this.__generations[index]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Release a body slot previously returned by {@link allocate}. Bumps the
|
|
190
|
+
* generation so any old packed id becomes stale.
|
|
191
|
+
*
|
|
192
|
+
* @param {number} packed_body_id
|
|
193
|
+
*/
|
|
194
|
+
free(packed_body_id) {
|
|
195
|
+
const index = body_id_index(packed_body_id);
|
|
196
|
+
|
|
197
|
+
assert.equal(this.is_valid(packed_body_id), true, 'free() called on stale or unknown body id');
|
|
198
|
+
|
|
199
|
+
// Remove from awake set if present.
|
|
200
|
+
const awake_pos = this.__awake_pos[index];
|
|
201
|
+
if (awake_pos !== BODY_INDEX_ABSENT) {
|
|
202
|
+
this.__awake_remove_at(awake_pos);
|
|
203
|
+
this.__awake_pos[index] = BODY_INDEX_ABSENT;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.__alive[index] = 0;
|
|
207
|
+
|
|
208
|
+
// Drop the entity → index mapping (the slot still holds the old entity
|
|
209
|
+
// value until reallocation, so delete by it now while it's valid).
|
|
210
|
+
this.__entity_to_index.delete(this.__entities[index]);
|
|
211
|
+
|
|
212
|
+
// Bump generation; wraps mod 256.
|
|
213
|
+
this.__generations[index] = (this.__generations[index] + 1) & GENERATION_MASK;
|
|
214
|
+
|
|
215
|
+
this.__heap_push(index);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
*
|
|
220
|
+
* @param {number} packed_body_id
|
|
221
|
+
* @returns {boolean}
|
|
222
|
+
*/
|
|
223
|
+
is_valid(packed_body_id) {
|
|
224
|
+
const index = body_id_index(packed_body_id);
|
|
225
|
+
if (index < 0 || index >= this.__count) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
if (this.__alive[index] !== 1) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
return this.__generations[index] === body_id_generation(packed_body_id);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @param {number} index body index (NOT a packed id)
|
|
236
|
+
* @returns {number} entity for the body, or -1 if the slot is free.
|
|
237
|
+
*/
|
|
238
|
+
entity_at(index) {
|
|
239
|
+
if (this.__alive[index] !== 1) {
|
|
240
|
+
return -1;
|
|
241
|
+
}
|
|
242
|
+
return this.__entities[index];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Body index for `entity`, or {@link BODY_INDEX_ABSENT} if no live body owns
|
|
247
|
+
* it. O(1) reverse of {@link entity_at} — the lookup callers use on the
|
|
248
|
+
* link / attach / joint paths instead of scanning the slot table.
|
|
249
|
+
* @param {number} entity
|
|
250
|
+
* @returns {number}
|
|
251
|
+
*/
|
|
252
|
+
index_of_entity(entity) {
|
|
253
|
+
const idx = this.__entity_to_index.get(entity);
|
|
254
|
+
return idx === undefined ? BODY_INDEX_ABSENT : idx;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @param {number} index
|
|
259
|
+
* @returns {number}
|
|
260
|
+
*/
|
|
261
|
+
generation_at(index) {
|
|
262
|
+
return this.__generations[index];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @param {number} index
|
|
267
|
+
* @returns {BodyKind|number}
|
|
268
|
+
*/
|
|
269
|
+
kind_at(index) {
|
|
270
|
+
return this.__kinds[index];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @param {number} index
|
|
275
|
+
* @param {BodyKind|number} kind
|
|
276
|
+
*/
|
|
277
|
+
set_kind(index, kind) {
|
|
278
|
+
this.__kinds[index] = kind;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* @param {number} index
|
|
283
|
+
* @returns {number}
|
|
284
|
+
*/
|
|
285
|
+
flags_at(index) {
|
|
286
|
+
return this.__flags[index];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @param {number} index
|
|
291
|
+
* @param {number} flags
|
|
292
|
+
*/
|
|
293
|
+
set_flags(index, flags) {
|
|
294
|
+
this.__flags[index] = flags;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Index of `index` in the awake list, or {@link BODY_INDEX_ABSENT} if asleep.
|
|
299
|
+
* @param {number} index
|
|
300
|
+
* @returns {number}
|
|
301
|
+
*/
|
|
302
|
+
awake_position_of(index) {
|
|
303
|
+
return this.__awake_pos[index];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
*
|
|
308
|
+
* @param {number} index
|
|
309
|
+
* @returns {boolean}
|
|
310
|
+
*/
|
|
311
|
+
is_awake(index) {
|
|
312
|
+
return this.__awake_pos[index] !== BODY_INDEX_ABSENT;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Read the body index at position `i` in the dense awake list (0-based).
|
|
317
|
+
* @param {number} i
|
|
318
|
+
* @returns {number}
|
|
319
|
+
*/
|
|
320
|
+
awake_at(i) {
|
|
321
|
+
return this.__awake_list[i];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Add `index` to the awake set if not already present.
|
|
326
|
+
* @param {number} index
|
|
327
|
+
*/
|
|
328
|
+
mark_awake(index) {
|
|
329
|
+
if (this.__awake_pos[index] !== BODY_INDEX_ABSENT) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const pos = this.__awake_count++;
|
|
333
|
+
this.__awake_list[pos] = index;
|
|
334
|
+
this.__awake_pos[index] = pos;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Remove `index` from the awake set if present.
|
|
339
|
+
* @param {number} index
|
|
340
|
+
*/
|
|
341
|
+
mark_sleeping(index) {
|
|
342
|
+
const pos = this.__awake_pos[index];
|
|
343
|
+
if (pos === BODY_INDEX_ABSENT) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
this.__awake_remove_at(pos);
|
|
347
|
+
this.__awake_pos[index] = BODY_INDEX_ABSENT;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Swap-with-last removal at position `pos` in the dense awake list.
|
|
352
|
+
* @private
|
|
353
|
+
* @param {number} pos
|
|
354
|
+
*/
|
|
355
|
+
__awake_remove_at(pos) {
|
|
356
|
+
const last_pos = --this.__awake_count;
|
|
357
|
+
if (pos !== last_pos) {
|
|
358
|
+
const swapped_index = this.__awake_list[last_pos];
|
|
359
|
+
this.__awake_list[pos] = swapped_index;
|
|
360
|
+
this.__awake_pos[swapped_index] = pos;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @private
|
|
366
|
+
*/
|
|
367
|
+
__grow() {
|
|
368
|
+
const new_cap = this.__capacity << 1;
|
|
369
|
+
this.__capacity = new_cap;
|
|
370
|
+
|
|
371
|
+
const grow_int32 = (a) => {
|
|
372
|
+
const next = new Int32Array(new_cap);
|
|
373
|
+
next.set(a);
|
|
374
|
+
return next;
|
|
375
|
+
};
|
|
376
|
+
const grow_uint8 = (a) => {
|
|
377
|
+
const next = new Uint8Array(new_cap);
|
|
378
|
+
next.set(a);
|
|
379
|
+
return next;
|
|
380
|
+
};
|
|
381
|
+
const grow_uint32 = (a) => {
|
|
382
|
+
const next = new Uint32Array(new_cap);
|
|
383
|
+
next.set(a);
|
|
384
|
+
return next;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
this.__entities = grow_int32(this.__entities);
|
|
388
|
+
this.__generations = grow_uint8(this.__generations);
|
|
389
|
+
this.__kinds = grow_uint8(this.__kinds);
|
|
390
|
+
this.__flags = grow_uint32(this.__flags);
|
|
391
|
+
this.__alive = grow_uint8(this.__alive);
|
|
392
|
+
this.__awake_list = grow_uint32(this.__awake_list);
|
|
393
|
+
this.__free_heap = grow_uint32(this.__free_heap);
|
|
394
|
+
|
|
395
|
+
// Awake position has signed sentinel — needs fill on the grown portion.
|
|
396
|
+
const next_pos = new Int32Array(new_cap);
|
|
397
|
+
next_pos.set(this.__awake_pos);
|
|
398
|
+
next_pos.fill(BODY_INDEX_ABSENT, this.__awake_pos.length);
|
|
399
|
+
this.__awake_pos = next_pos;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// --- Min-heap of free indices --------------------------------------------
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* @private
|
|
406
|
+
* @param {number} index
|
|
407
|
+
*/
|
|
408
|
+
__heap_push(index) {
|
|
409
|
+
const heap = this.__free_heap;
|
|
410
|
+
let i = this.__free_count++;
|
|
411
|
+
heap[i] = index;
|
|
412
|
+
|
|
413
|
+
while (i > 0) {
|
|
414
|
+
const parent = (i - 1) >>> 1;
|
|
415
|
+
if (heap[parent] > heap[i]) {
|
|
416
|
+
const tmp = heap[parent];
|
|
417
|
+
heap[parent] = heap[i];
|
|
418
|
+
heap[i] = tmp;
|
|
419
|
+
i = parent;
|
|
420
|
+
} else {
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* @private
|
|
428
|
+
* @returns {number}
|
|
429
|
+
*/
|
|
430
|
+
__heap_pop() {
|
|
431
|
+
const heap = this.__free_heap;
|
|
432
|
+
const result = heap[0];
|
|
433
|
+
const new_count = --this.__free_count;
|
|
434
|
+
if (new_count > 0) {
|
|
435
|
+
heap[0] = heap[new_count];
|
|
436
|
+
let i = 0;
|
|
437
|
+
while (true) {
|
|
438
|
+
const l = (i << 1) + 1;
|
|
439
|
+
const r = l + 1;
|
|
440
|
+
let smallest = i;
|
|
441
|
+
if (l < new_count && heap[l] < heap[smallest]) smallest = l;
|
|
442
|
+
if (r < new_count && heap[r] < heap[smallest]) smallest = r;
|
|
443
|
+
if (smallest === i) break;
|
|
444
|
+
const tmp = heap[i];
|
|
445
|
+
heap[i] = heap[smallest];
|
|
446
|
+
heap[smallest] = tmp;
|
|
447
|
+
i = smallest;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return result;
|
|
451
|
+
}
|
|
452
|
+
}
|