Physics · class
ColliderSet
Explained in Your first game, Rigid bodies.
class ColliderSetimport { ColliderSet } from '@driftengine/physics';Constructor
new
constructor(boxes: readonly Collider[], cellSize?: number)| Parameter | Type | Description |
|---|---|---|
boxes | readonly Collider[] | |
cellSize? | number |
Properties
| Name | Type | Description |
|---|---|---|
BASE_GROUPstaticreadonly | ColliderGroup | The group the constructor's own colliders belong to, which add never mints and remove
refuses.MoreReserved because of the slots nothing inserts. The constructor skips an A set meant to stream is constructed empty and takes everything through |
data | Float32Array | The packed bounds, six floats a slot.MoreA reference to this is valid until the next Its length is |
Accessors
| Name | Type | Description |
|---|---|---|
countget | number | How many colliders the set holds. |
capacityget | number | How many slots are allocated, live or not. The true bound of a walk over data. |
Methods
liveAt
liveAt(index: number): booleanWhether slot index holds a collider. Every slot of a set that has never had remove
called on it does.
| Parameter | Type | Description |
|---|---|---|
index | number |
add
add(boxes: readonly Collider[]): ColliderGroupTake a batch of colliders and hand back the handle that drops them again.
| Parameter | Type | Description |
|---|---|---|
boxes | readonly Collider[] |
More
The grid is updated for these colliders and for no others, which is the whole point: a consumer whose world streams was rebuilding a set at every region crossing, at about 2.1 µs a box, and paying a whole frame for it.
Slots come off the free list before the arrays grow, so a set that loads a ring of regions around a player settles at the ring's high-water mark instead of climbing with distance travelled. Live indices are untouched by this: a slot already in use stays where it is.
absorb
absorb(other: ColliderSet): ColliderGroupTake everything in another set as one group, without hashing any of it again.
| Parameter | Type | Description |
|---|---|---|
other | ColliderSet |
More
Copy and offset instead of recompute. One Float32Array.set moves the packed bounds, the
shapes are copied by reference, and each of the source's buckets is appended to this set's
with a constant added to every index. Nothing is floored, no shape bounds are recomputed, and
the result queries identically to a one-shot build of the union.
It appends past everything and skips the free list, because a constant offset is what
makes the bucket remap a copy: source slot i has to land at base + i and nothing may
disturb that.
Which is exactly why the source's holes come across as holes. A source that has had
remove called on it — every streamed set — cannot be compacted on the way in, because
skipping its holes would make the offset a function of position. Copying them while calling
them live would be worse: those slots carry the bytes of colliders that were deleted, and a
set reporting them would have resurrected geometry a consumer believed was gone. So liveness
is copied with the bytes, the holes land on this set's free list, and the next add fills
them. Requiring a dense source instead was the other option and was rejected: it would
refuse the only input this was asked for.
What a churned source does cost is its dead slots as bytes copied and space held until the
free list drains them. Bounded, visible in bytes(), and not a leak.
A copy and not a move. The source is unchanged and stays usable, which is what lets a caller keep a prototype region and stamp it more than once.
join(a, b, c) is this in a loop, which is why there is no second method for it.
remove
remove(group: ColliderGroup): voidDrop a group and everything in it.
| Parameter | Type | Description |
|---|---|---|
group | ColliderGroup |
More
Refuses the base group and a group it does not hold; a streamer double-freeing a region is a bug, and a silent no-op hides it until the memory it was meant to release turns up in a budget.
shapeAt
shapeAt(index: number): ConvexShape | undefinedThe volume collider index really is, or undefined where it is its box.
| Parameter | Type | Description |
|---|---|---|
index | number |
bounds
bounds(index: number, out: Aabb): voidThe packed bounds of collider index, written into out. Allocation-free.
| Parameter | Type | Description |
|---|---|---|
index | number | |
out | Aabb |
More
data is public and the stride is not. A reader outside this file that decodes
data[i * 6 + 1] by hand keeps working right up until the packing changes, and
then reports confident nonsense rather than failing.
query
query(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, out: Int32Array): number| Parameter | Type | Description |
|---|---|---|
minX | number | |
minY | number | |
minZ | number | |
maxX | number | |
maxY | number | |
maxZ | number | |
out | Int32Array |
bytes
bytes(): ColliderBytesWhat this set costs in memory.
More
Because a consumer can count its own bytes and cannot see the engine's, which makes a streaming budget half-measurable: the question "does the ring of regions around the player fit on a phone" has no answer without this.
O(n) over slots and over the index, and it allocates a Set to count each distinct shape
once. Not a per-frame call.
auditCells
auditCells(): numberHow many bucket entries name a slot nothing holds. Always zero.
More
The one defect in this structure that has no symptom of its own, which is why it gets a
check of its own. A remove that misses a bucket entry leaves the slot marked dead — so the
fingerprint skips it and is right, and nothing walks it by index — while the stale entry goes
on naming it: query returns it, and the sweep reads its stale bounds and collides with
geometry that is gone. No measurement of frame time, query cost or hash can see that.
O(total entries) and not something to call in a frame. For tests and for the streaming check.
Signatures
MAX_HITS = 512staticreadonly
static readonly MAX_HITS = 512