UNPKG

ecspresso

Version:

A minimal Entity-Component-System library for typescript and javascript.

131 lines (130 loc) 5.76 kB
/** * Shared Narrowphase Module — 3D * * Provides contact-computing narrowphase tests and a generic collision * iteration pipeline for 3D collider pairs (AABB3D + Sphere). * * Mirrors the 2D narrowphase (`narrowphase.ts`) with an added Z axis. */ import type { SpatialIndex3D } from './spatial-hash3D'; /** * Contact result from a 3D narrowphase test. Normal points from A toward B. * * Narrowphase functions use this as an out-parameter: the caller owns the * struct, the function writes fields in place and returns `true` on hit. * The `onContact` callback in `detectCollisions3D` receives a shared module- * level instance — **subscribers must consume it synchronously and must not * retain the reference across frames**. */ export interface Contact3D { normalX: number; normalY: number; normalZ: number; /** Penetration depth (positive = overlapping) */ depth: number; } /** Collider shape discriminator for the flattened BaseColliderInfo3D layout. */ export declare const AABB3D_SHAPE = 0; export declare const SPHERE_SHAPE = 1; export type ColliderShape3D = typeof AABB3D_SHAPE | typeof SPHERE_SHAPE; /** * Minimum collider data shared by 3D collision and physics bundles. * * Flat layout (no nested sub-objects): the `shape` discriminator tells you * whether to read `halfWidth`/`halfHeight`/`halfDepth` (AABB3D) or `radius` * (Sphere). Unused fields are set to 0. * * Pool-friendly — all fields are assigned in place each frame. */ export interface BaseColliderInfo3D<L extends string = string> { entityId: number; x: number; y: number; z: number; layer: L; collidesWith: readonly L[]; /** * Bit assigned to `layer` from the lazy layer registry. Populated by * `fillBaseColliderInfo3D`. Used together with `collidesWithMask` to * replace per-pair `Array.includes` layer checks with a single AND. */ layerBit: number; /** OR of `getLayerBit3D` for every entry in `collidesWith`. */ collidesWithMask: number; shape: ColliderShape3D; halfWidth: number; halfHeight: number; halfDepth: number; radius: number; } export declare const getLayerBit3D: (layer: string) => number; export declare const getCollidesWithMask3D: (collidesWith: readonly string[]) => number; /** * Populate a `BaseColliderInfo3D` slot in place from raw component data. * Returns `true` if the slot was filled, `false` if the entity has no * collider (caller should skip it). * * If an entity has both AABB3D and sphere colliders, AABB3D wins and only * the AABB3D offset is applied. */ export declare function fillBaseColliderInfo3D<L extends string>(info: BaseColliderInfo3D<L>, entityId: number, x: number, y: number, z: number, layer: L, collidesWith: readonly L[], aabb3D: { width: number; height: number; depth: number; offsetX?: number; offsetY?: number; offsetZ?: number; } | undefined, sphere: { radius: number; offsetX?: number; offsetY?: number; offsetZ?: number; } | undefined): boolean; /** * Retrieve the optional spatialIndex3D resource, returning undefined when absent. * Centralizes the cross-plugin typed lookup so individual plugins don't each * need to import SpatialIndex3D or repeat the tryGetResource pattern. */ export declare function tryGetSpatialIndex3D(tryGetResource: <T>(key: string) => T | undefined): SpatialIndex3D | undefined; /** * Write an AABB3D-vs-AABB3D contact into `out`. Returns `true` if the * shapes overlap (out was filled), `false` otherwise. * * Resolves along the axis with minimum penetration depth. */ export declare function computeAABB3DvsAABB3D(ax: number, ay: number, az: number, ahw: number, ahh: number, ahd: number, bx: number, by: number, bz: number, bhw: number, bhh: number, bhd: number, out: Contact3D): boolean; /** * Write a sphere-vs-sphere contact into `out`. Returns `true` if the * spheres overlap. */ export declare function computeSphereVsSphere(ax: number, ay: number, az: number, ar: number, bx: number, by: number, bz: number, br: number, out: Contact3D): boolean; /** * Write an AABB3D-vs-sphere contact into `out`. Returns `true` if the * shapes overlap. * * Uses closest-point-on-AABB to sphere center. When the sphere center * is inside the AABB, resolves along the axis with minimum push distance. */ export declare function computeAABB3DvsSphere(aabbX: number, aabbY: number, aabbZ: number, ahw: number, ahh: number, ahd: number, sphereX: number, sphereY: number, sphereZ: number, radius: number, out: Contact3D): boolean; /** * Dispatch to the correct narrowphase function for the given pair and * write the contact into `out`. Returns `true` if the pair overlaps. */ export declare function computeContact3D(a: BaseColliderInfo3D, b: BaseColliderInfo3D, out: Contact3D): boolean; /** * Generic 3D collision detection pipeline: brute-force or broadphase, * with layer filtering and contact computation. * * `count` is the number of live entries at the front of `colliders`. * The array itself may be a grow-only pool — only indices `[0, count)` * are iterated, so trailing pool slots are ignored. * * `workingMap` is a caller-owned `Map<number, I>` used by the broadphase * path as an entityId → collider lookup. It is cleared and repopulated on * each call; callers should allocate it once and pass the same instance * every frame. * * Uses a context parameter forwarded to the callback to avoid * per-frame closure allocation. */ export declare function detectCollisions3D<I extends BaseColliderInfo3D, C>(colliders: I[], count: number, workingMap: Map<number, I>, spatialIndex: SpatialIndex3D | undefined, onContact: (a: I, b: I, contact: Contact3D, context: C) => void, context: C): void;