API reference
Every public class, method, and event in @codexo/exojs. Generated from source.
classParticleSystem
The central coordinator of the particle pipeline. `ParticleSystem` is a Drawable that owns: - **SoA particle storage** — one typed array per attribute (position, velocity, scale, rotation, color, lifetime, ...), sized to a fixed capacity at construction. User code reads/writes via `system.posX[slot]`, `system.velX[slot]`, etc. - **Spawn modules** — write new particles into freshly allocated slots. - **Update modules** — mutate the live range each frame (forces, color blends, scale curves, drag, ...). Built-in modules ship both CPU and WGSL implementations; custom modules can opt into GPU acceleration by implementing `wgsl()`. - **Death modules** — fire once per dying particle, before its slot is recycled (sub-emitters, event hooks). **Auto-routing CPU vs GPU:** at first update, the system checks: if a `WebGpuBackend` was supplied AND every registered update module has `wgsl()`, the GPU path engages — a composite compute pipeline runs integration plus all module bodies in one dispatch and writes directly into the renderer's instance buffer (no CPU readback). Otherwise the CPU path runs the existing per-module `apply()` loops. **Per-frame order in update (CPU mode):** 1. Run every spawn module. 2. Integrate position from velocity, rotation from rotationSpeed, advance `elapsed`. 3. Run every update module on the live range. 4. Compact: scan `[0, liveCount)` forward, fire death modules on expired slots, copy survivors down. `liveCount` shrinks to the survivor count. **Per-frame order in update (GPU mode):** 1. Run every spawn module (CPU writes initial values into the spawn slot). 2. Detect expiries on CPU (via `elapsed >= lifetime`); fire death modules; set `lifetime[slot] = -1` sentinel + clear `alive[slot]` so the GPU shader skips them. **No compaction** — slots are recycled on next spawn. 3. Dispatch the composite compute pipeline. Integration + update modules + pack-instances run in one pass; the instance buffer is written directly. CPU SoA stays as-is for spawn writes. **Coordinate space:** particle positions are LOCAL to the system. The system's `getGlobalTransform()` is applied on top during rendering — both the WebGL2 and WebGPU shaders multiply `projection * translation * rotated`. Setting world-space positions on individual particles double-translates. Position the system itself via `system.setPosition(...)` and emit relative to `(0, 0)`.
import { ParticleSystem } from '@codexo/exojs-particles' The central coordinator of the particle pipeline. `ParticleSystem` is a Drawable that owns:
- **SoA particle storage** — one typed array per attribute (position, velocity, scale, rotation, color, lifetime, ...), sized to a fixed capacity at construction. User code reads/writes via `system.posX[slot]`, `system.velX[slot]`, etc. - **Spawn modules** — write new particles into freshly allocated slots. - **Update modules** — mutate the live range each frame (forces, color blends, scale curves, drag, ...). Built-in modules ship both CPU and WGSL implementations; custom modules can opt into GPU acceleration by implementing `wgsl()`. - **Death modules** — fire once per dying particle, before its slot is recycled (sub-emitters, event hooks).
**Auto-routing CPU vs GPU:** at first update, the system checks: if a `WebGpuBackend` was supplied AND every registered update module has `wgsl()`, the GPU path engages — a composite compute pipeline runs integration plus all module bodies in one dispatch and writes directly into the renderer's instance buffer (no CPU readback). Otherwise the CPU path runs the existing per-module `apply()` loops.
**Per-frame order in update (CPU mode):** 1. Run every spawn module. 2. Integrate position from velocity, rotation from rotationSpeed, advance `elapsed`. 3. Run every update module on the live range. 4. Compact: scan `[0, liveCount)` forward, fire death modules on expired slots, copy survivors down. `liveCount` shrinks to the survivor count.
**Per-frame order in update (GPU mode):** 1. Run every spawn module (CPU writes initial values into the spawn slot). 2. Detect expiries on CPU (via `elapsed >= lifetime`); fire death modules; set `lifetime[slot] = -1` sentinel + clear `alive[slot]` so the GPU shader skips them. **No compaction** — slots are recycled on next spawn. 3. Dispatch the composite compute pipeline. Integration + update modules + pack-instances run in one pass; the instance buffer is written directly. CPU SoA stays as-is for spawn writes.
**Coordinate space:** particle positions are LOCAL to the system. The system's `getGlobalTransform()` is applied on top during rendering — both the WebGL2 and WebGPU shaders multiply `projection * translation * rotated`. Setting world-space positions on individual particles double-translates. Position the system itself via `system.setPosition(...)` and emit relative to `(0, 0)`.
new(options?: ParticleSystemOptions): ParticleSystem new(texture: Texture, options?: ParticleSystemOptions): ParticleSystem new(texture: Texture, frames: readonly Rectangle[], options?: ParticleSystemOptions): ParticleSystem new(spritesheet: Spritesheet, options?: ParticleSystemOptions): ParticleSystem _invalidateBoundsCascade(): void _invalidateChildrenTransform(): void _invalidateSubtreeTransform(): void addDeathModule(mod: DeathModule): this addFilter(filter: Filter): this addSpawnModule(mod: SpawnModule): this addUpdateModule(mod: UpdateModule): this clearDeathModules(): this clearFilters(): this clearParticles(): this clearSpawnModules(): this clearUpdateModules(): this collidesWith(target: Collidable): CollisionResponse | null contains(x: number, y: number): boolean destroy(): void getBounds(): Rectangle getGlobalTransform(): Matrix getLocalBounds(): Rectangle getNormals(): Vector[] getTransform(): Matrix intersectsWith(target: Collidable): boolean invalidateCache(): this inView(view: View): boolean move(x: number, y: number): this project(axis: Vector, result: Interval): Interval removeFilter(filter: Filter): this render(backend: RenderBackend): this resetTextureFrame(): this rotate(degrees: number): this setAnchor(x: number, y: number): this setBlendMode(blendMode: BlendModes): this setOrigin(x: number, y: number): this setPosition(x: number, y: number): this setRotation(degrees: number): this setScale(x: number, y: number): this setSkew(x: number, y: number): this setTexture(texture: Texture): this setTextureFrame(frame: Rectangle): this setTint(color: Color): this spawn(): number update(delta: Time): this updateBounds(): this updateParentTransform(): this updateTransform(): this setInternalSpriteFactory(factory: object | null): void alive: Uint8Array capacity: number clip: boolean clipShape: Rectangle | Geometry | null collisionType: CollisionType color: Uint32Array cursor: string | null draggable: boolean elapsed: Float32Array flags: Flags<SceneNodeTransformFlags> lifetime: Float32Array liveCount: number posX: Float32Array posY: Float32Array preserveDrawOrder: boolean rotations: Float32Array rotationSpeeds: Float32Array scaleX: Float32Array scaleY: Float32Array textureIndex: Uint16Array velX: Float32Array velY: Float32Array aliveCount: number anchor: ObservableVector blendMode: BlendModes cacheAsBitmap: boolean cullable: boolean deathModules: readonly DeathModule[] filters: readonly Filter[] frames: readonly Rectangle[] gpuMode: boolean gpuState: ParticleGpuState | null hasAtlas: boolean interactive: boolean isAlignedBox: boolean mask: MaskSource origin: ObservableVector parent: Container | null pixelSnapMode: PixelSnapMode position: ObservableVector rotation: number scale: ObservableVector skewX: number skewY: number spawnModules: readonly SpawnModule[] texCoords: Uint32Array texture: Texture textureFrame: Rectangle tint: Color updateModules: readonly UpdateModule[] vertices: Float32Array visible: boolean x: number y: number zIndex: number onDrag: Signal<[InteractionEvent]> onDragEnd: Signal<[InteractionEvent]> onDragStart: Signal<[InteractionEvent]> onPointerDown: Signal<[InteractionEvent]> onPointerMove: Signal<[InteractionEvent]> onPointerOut: Signal<[InteractionEvent]> onPointerOver: Signal<[InteractionEvent]> onPointerTap: Signal<[InteractionEvent]> onPointerUp: Signal<[InteractionEvent]>