ecspresso
Version:
A minimal Entity-Component-System library for typescript and javascript.
120 lines (119 loc) • 5.42 kB
TypeScript
/**
* Spatial Hash Grid 3D
*
* Uniform-grid spatial hash for broadphase collision detection and
* proximity queries in 3D. Pure data structure, no ECS dependencies.
*/
export interface SpatialEntry3D {
entityId: number;
x: number;
y: number;
z: number;
halfW: number;
halfH: number;
halfD: number;
/** Generation stamp used by query functions to dedup multi-cell hits without a Set. Internal. */
_lastSeenGen: number;
/** Rebuild generation when this entry was last inserted. Internal. */
_aliveGen: number;
}
/**
* A cell bucket — entries plus the alive-gen at which the bucket was last
* filled. Buckets are reset lazily on the next insert in a new generation
* (see `insertEntity3D`); queries skip buckets whose `_gen` is stale.
*
* Internal — exposed only through `SpatialHashGrid3D.cells`.
*/
interface CellBucket3D extends Array<SpatialEntry3D> {
_gen: number;
}
export interface SpatialHashGrid3D {
cellSize: number;
invCellSize: number;
cells: Map<number, CellBucket3D>;
/**
* Dense, indexed by entityId. Holes are `undefined`. Entries from previous
* rebuilds remain in place for in-place reuse (zero allocation in steady
* state); liveness is determined by `entry._aliveGen === grid._aliveGen`.
* Internal — read live entries via `getLiveEntry3D` / `liveEntryCount3D` helpers.
*
* High-water-mark grows with max entityId ever inserted; despawned ids
* leave their slot occupied by a stale entry. Acceptable when the entity
* manager recycles ids or peak count is bounded.
*/
entries: (SpatialEntry3D | undefined)[];
/** Monotonic counter bumped by each `clearGrid3D` call. Internal. */
_aliveGen: number;
/** Monotonic counter bumped on each query; entries record their last-seen gen for O(1) dedup. Internal. */
_queryGen: number;
}
/**
* Hash a cell coordinate triple to a single integer key.
* Uses large-prime XOR to distribute values.
*/
export declare function hashCell3D(cx: number, cy: number, cz: number): number;
/**
* Create a new empty 3D spatial hash grid.
*/
export declare function createGrid3D(cellSize: number): SpatialHashGrid3D;
/**
* Prepare the grid for a rebuild.
*
* O(1): bumps the alive-generation counter so entries inserted prior to this
* call are implicitly stale. `getLiveEntry3D` / `liveEntryCount3D` filter
* entries by the current gen; queries skip buckets whose own `_gen` lags
* behind the alive gen; `insertEntity3D` resets a bucket's `length` lazily
* the first time it is touched in a new generation.
*
* Existing `SpatialEntry3D` objects and `CellBucket3D` arrays remain in
* place for reuse, so steady-state rebuilds allocate zero entries and zero
* buckets, regardless of how many cells have ever been touched.
*/
export declare function clearGrid3D(grid: SpatialHashGrid3D): void;
/**
* Insert an entity into all overlapping cells of the grid.
*/
export declare function insertEntity3D(grid: SpatialHashGrid3D, entityId: number, x: number, y: number, z: number, halfW: number, halfH: number, halfD: number): void;
/**
* Collect entity IDs from all cells overlapping the given 3D box.
*
* Appends to `result` (caller clears/truncates first if reusing). Multi-cell
* entries are deduplicated via a per-grid generation stamp on each
* `SpatialEntry3D`.
*
* When `minId` is provided, only entries with `entityId > minId` are added —
* used for symmetric broadphase pair generation.
*/
export declare function gridQueryBox3D(grid: SpatialHashGrid3D, minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: number[], minId?: number): void;
/**
* Collect entity IDs within a sphere. AABB-to-point distance filter against
* the cells overlapping the sphere's bounding box. Appends to `result`.
*/
export declare function gridQueryRadius3D(grid: SpatialHashGrid3D, cx: number, cy: number, cz: number, radius: number, result: number[]): void;
/**
* Get the current-generation entry for an entityId, or `undefined` if the
* entity isn't in the index for this rebuild. Stale entries from previous
* rebuilds remain in `entries` for in-place reuse but are filtered here.
*/
export declare function getLiveEntry3D(grid: SpatialHashGrid3D, entityId: number): SpatialEntry3D | undefined;
/**
* Count entries inserted in the current rebuild generation. Linear scan —
* intended for tests and diagnostics, not hot paths.
*/
export declare function liveEntryCount3D(grid: SpatialHashGrid3D): number;
/**
* High-level spatial index API for 3D broadphase queries.
*
* Defined here (the utility layer) so that narrowphase3D can accept it
* without importing the ECS plugin. The spatial-index3D plugin creates
* an object that implements this interface and registers it as a resource.
*/
export interface SpatialIndex3D {
readonly grid: SpatialHashGrid3D;
queryBox(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): number[];
queryBoxInto(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: number[], minId?: number): void;
queryRadius(cx: number, cy: number, cz: number, radius: number): number[];
queryRadiusInto(cx: number, cy: number, cz: number, radius: number, result: number[]): void;
getEntry(entityId: number): SpatialEntry3D | undefined;
}
export {};