Skip to content

@gameable/vfx ​

Classes ​

ParticlePool ​

Six vec4 columns in a fixed ring; spawn copies a flat row of 24 floats.

Example ​

ts
import { ParticlePool } from 'gameable/vfx';
const pool = new ParticlePool(64);
const row = new Float32Array(24);
row[7] = 1;
pool.spawn(row);
pool.flush();
console.log(pool.alive(0)); // 1

Constructors ​

Constructor ​
ts
new ParticlePool(capacity?): ParticlePool;

Creates a fixed particle ring.

Parameters ​
ParameterTypeDefault valueDescription
capacitynumber4096Maximum particles in this pool.
Returns ​

ParticlePool

Properties ​

attributes ​
ts
readonly attributes: readonly InstancedBufferAttribute[];
capacity ​
ts
readonly capacity: number;
data ​
ts
readonly data: readonly Float32Array<ArrayBufferLike>[];

Methods ​

alive() ​
ts
alive(now): number;

Counts living rows using the shader clock precision; called only for explicit statistics.

Parameters ​
ParameterTypeDescription
nownumberHost time in seconds.
Returns ​

number

The number of living particles.

flush() ​
ts
flush(): void;

Marks only changed components; a wrapped interval becomes two ranges.

Returns ​

void

spawn() ​
ts
spawn(row): number;

Copies a0 through a5 into the next ring slot.

Parameters ​
ParameterTypeDescription
rowArrayLike<number>Caller-owned 24-float birth record.
Returns ​

number

The overwritten ring slot.

Interfaces ​

EffectCommand ​

Structural copy of jco's effect-cmd; retained fields are copied on receipt.

Example ​

ts
import type { EffectCommand } from 'gameable/vfx';
const command: EffectCommand = {
  action: 'play', effect: 0, preset: 'hit-sparks',
  position: { x: 0, y: 0, z: 0 }, direction: { x: 0, y: 1, z: 0 },
  scale: 1, intensity: 1,
};
console.log(command.preset);

Properties ​

action ​
ts
action: "stop" | "play" | "move";
direction ​
ts
direction: ParticleVector;
effect ​
ts
effect: number;
entity? ​
ts
optional entity?: number;
intensity ​
ts
intensity: number;
position ​
ts
position: ParticleVector;
preset ​
ts
preset: string;
scale ​
ts
scale: number;
tint? ​
ts
optional tint?: ParticleVector;

Particles ​

Host particle service. Reading stats.alive explicitly scans the two pools.

Example ​

ts
import { Scene } from 'three/webgpu';
import { createParticles, type Particles } from 'gameable/vfx';
const particles: Particles = createParticles(new Scene());
particles.update(0, 0);
console.log(particles.stats.alive); // 0
particles.dispose();

Properties ​

objects ​
ts
readonly objects: readonly Object3D<Object3DEventMap>[];
stats ​
ts
readonly stats: object;
alive ​
ts
readonly alive: number;
births ​
ts
readonly births: number;

Births in the last update.

emitters ​
ts
readonly emitters: number;
total ​
ts
readonly total: number;

Births since the system was made.

Methods ​

dispose() ​
ts
dispose(): void;
Returns ​

void

handle() ​
ts
handle(cmd): void;
Parameters ​
ParameterType
cmdEffectCommand
Returns ​

void

setCapacity() ​
ts
setCapacity(capacity): void;

Resize both pools at a quality change, preserving running emitters. Existing particles are released; subsequent births use the new capacity.

Parameters ​
ParameterTypeDescription
capacitynumberParticles per blend pool.
Returns ​

void

update() ​
ts
update(now, dt): void;
Parameters ​
ParameterType
nownumber
dtnumber
Returns ​

void


ParticlesOptions ​

Fixed capacities and optional entity lookup; RNG state is private to this system.

Example ​

ts
import { Scene } from 'three/webgpu';
import { createParticles, type ParticlesOptions } from 'gameable/vfx';
const options: ParticlesOptions = { capacity: 64, seed: 42 };
const particles = createParticles(new Scene(), options);
particles.dispose();

Properties ​

capacity? ​
ts
optional capacity?: number;
resolveEntity? ​
ts
optional resolveEntity?: ResolveEntity;
seed? ​
ts
optional seed?: number;

PostCommand ​

Structural copy of jco's post-cmd: omitted fields retain their current value.

Example ​

ts
import type { PostCommand } from 'gameable/vfx';
const command: PostCommand = { reset: false, exposure: 2 };
console.log(command.exposure);

Properties ​

bloom? ​
ts
optional bloom?: number;
bloomRadius? ​
ts
optional bloomRadius?: number;
bloomThreshold? ​
ts
optional bloomThreshold?: number;
contrast? ​
ts
optional contrast?: number;
exposure? ​
ts
optional exposure?: number;
fogColor? ​
ts
optional fogColor?: object;
x ​
ts
x: number;
y ​
ts
y: number;
z ​
ts
z: number;
fogDensity? ​
ts
optional fogDensity?: number;
reset ​
ts
reset: boolean;
saturation? ​
ts
optional saturation?: number;
tint? ​
ts
optional tint?: object;
x ​
ts
x: number;
y ​
ts
y: number;
z ​
ts
z: number;
toneMapping? ​
ts
optional toneMapping?: "none" | "aces" | "agx" | "neutral";
vignette? ​
ts
optional vignette?: number;

PostSettings ​

Resolved post settings. Colours are linear RGB.

Example ​

ts
import { NEUTRAL_POST, type PostSettings } from 'gameable/vfx';
const look: PostSettings = { ...NEUTRAL_POST, exposure: 2 };
console.log(look.exposure);

Properties ​

bloom ​
ts
readonly bloom: number;
bloomRadius ​
ts
readonly bloomRadius: number;
bloomThreshold ​
ts
readonly bloomThreshold: number;
contrast ​
ts
readonly contrast: number;
exposure ​
ts
readonly exposure: number;
fogColor ​
ts
readonly fogColor: readonly [number, number, number];
fogDensity ​
ts
readonly fogDensity: number;
saturation ​
ts
readonly saturation: number;
tint ​
ts
readonly tint: readonly [number, number, number];
toneMapping ​
ts
readonly toneMapping: NonNullable<"none" | "aces" | "agx" | "neutral" | undefined>;
vignette ​
ts
readonly vignette: number;

PostStack ​

A caller-owned post pass; detach it from the engine before disposal.

Example ​

ts
import { createEngine } from 'gameable/core';
import { createPostStack, type PostStack } from 'gameable/vfx';
const engine = await createEngine({ canvas: document.createElement('canvas') });
const post: PostStack = createPostStack(engine.renderer, engine.scene);
engine.setPost(post);
post.set({ reset: false, exposure: 1.2 });

Extends ​

Properties ​

active ​
ts
readonly active: boolean;

Whether a frame target and presentation pass are needed.

settings ​
ts
readonly settings: Readonly<PostSettings>;

The immutable resolved look.

Methods ​

dispose() ​
ts
dispose(): void;

Release owned targets, materials and fog.

Returns ​

void

present() ​
ts
present(renderer): void;

Present the target returned for this frame to the screen. Called only when target was non-null.

Parameters ​
ParameterType
rendererWebGPURenderer
Returns ​

void

Inherited from ​

FramePost.present

set() ​
ts
set(cmd): void;

Apply a partial look, resetting to neutral first when requested.

Parameters ​
ParameterType
cmdPostCommand
Returns ​

void

target() ​
ts
target(renderer): 
  | RenderTarget<Texture<unknown, TextureEventMap>, RenderTargetEventMap>
  | null;

Return a linear half-float target sized to the drawing buffer, or null to bypass processing.

Parameters ​
ParameterType
rendererWebGPURenderer
Returns ​

| RenderTarget<Texture<unknown, TextureEventMap>, RenderTargetEventMap> | null

Inherited from ​

FramePost.target


Vfx ​

The engine's effects: what the adapter's vfx option takes, plus the per-frame step.

Properties ​

particles ​
ts
readonly particles: Particles;

The particle system.

post ​
ts
readonly post: PostStack;

The post stack, installed on the engine with setPost.

Methods ​

dispose() ​
ts
dispose(): void;

Take the particles off the scene and the post stack off the engine.

Returns ​

void

effect() ​
ts
effect(command): void;

Play, move or stop an effect. The command is the guest's pooled payload: it is copied, never kept.

Parameters ​
ParameterType
commandEffectCommand
Returns ​

void

setPost() ​
ts
setPost(command): void;

Change the post settings (partial; reset starts from neutral).

Parameters ​
ParameterType
commandPostCommand
Returns ​

void

update() ​
ts
update(dt): void;

Advance the particles one rendered frame.

Parameters ​
ParameterTypeDescription
dtnumberSeconds since the previous frame.
Returns ​

void

Nothing.


VfxOptions ​

Options for createVfx.

Properties ​

capacity? ​
ts
readonly optional capacity?: number;

Particles per pool (additive and alpha each). Default 4096.

post? ​
ts
readonly optional post?: Omit<PostCommand, "reset">;

The base look, applied before the first frame (the game's declared features.vfx.post).

seed? ​
ts
readonly optional seed?: number;

The host particle RNG's seed. Presentation only: it never reaches the guest.

Type Aliases ​

VfxPresetName ​

ts
type VfxPresetName = keyof typeof PRESETS;

Names accepted by the built-in particle emitter.

Example ​

ts
import { PRESETS, type VfxPresetName } from 'gameable/vfx';
const name: VfxPresetName = 'fire';
console.log(PRESETS[name].pool); // additive

Variables ​

NEUTRAL_POST ​

ts
const NEUTRAL_POST: PostSettings;

Neutral look: no target, no full-screen pass, no fog.

Example ​

ts
import { NEUTRAL_POST } from 'gameable/vfx';
console.log(NEUTRAL_POST.exposure); // 1

PRESETS ​

ts
const PRESETS: object;

Built-in particle presets in linear light, metres and seconds.

Type Declaration ​

dust ​
ts
readonly dust: object;
dust.alpha ​
ts
readonly alpha: readonly [0.4, 0];
dust.burst ​
ts
readonly burst: 22 = 22;
dust.colourEnd ​
ts
readonly colourEnd: readonly [0.23, 0.18, 0.12];
dust.colourStart ​
ts
readonly colourStart: readonly [0.3, 0.21, 0.12];
dust.drag ​
ts
readonly drag: 2 = 2;
dust.duration ​
ts
readonly duration: 0 = 0;
dust.gravity ​
ts
readonly gravity: 0.8 = 0.8;
dust.inherit ​
ts
readonly inherit: 0 = 0;
dust.life ​
ts
readonly life: readonly [0.4, 1];
dust.pool ​
ts
readonly pool: "alpha" = 'alpha';
dust.rate ​
ts
readonly rate: 0 = 0;
dust.size ​
ts
readonly size: readonly [0.1, 0.45];
dust.speed ​
ts
readonly speed: readonly [0.2, 1.4];
dust.spin ​
ts
readonly spin: readonly [-2, 2];
dust.spread ​
ts
readonly spread: 1.3 = 1.3;
dust.up ​
ts
readonly up: readonly [0, 1, 0];
explosion ​
ts
readonly explosion: object;
explosion.alpha ​
ts
readonly alpha: readonly [1, 0];
explosion.burst ​
ts
readonly burst: 100 = 100;
explosion.colourEnd ​
ts
readonly colourEnd: readonly [1.5, 0.08, 0.01];
explosion.colourStart ​
ts
readonly colourStart: readonly [9, 5, 1.5];
explosion.drag ​
ts
readonly drag: 3 = 3;
explosion.duration ​
ts
readonly duration: 0.3 = 0.3;
explosion.gravity ​
ts
readonly gravity: 3 = 3;
explosion.inherit ​
ts
readonly inherit: 0 = 0;
explosion.life ​
ts
readonly life: readonly [0.3, 1.2];
explosion.pool ​
ts
readonly pool: "additive" = 'additive';
explosion.rate ​
ts
readonly rate: 24 = 24;
explosion.size ​
ts
readonly size: readonly [0.55, 0.06];
explosion.speed ​
ts
readonly speed: readonly [0.5, 8];
explosion.spin ​
ts
readonly spin: readonly [-4, 4];
explosion.spread ​
ts
readonly spread: number = Math.PI;
explosion.up ​
ts
readonly up: readonly [0, 1, 0];
fire ​
ts
readonly fire: object;
fire.alpha ​
ts
readonly alpha: readonly [0.8, 0];
fire.burst ​
ts
readonly burst: 6 = 6;
fire.colourEnd ​
ts
readonly colourEnd: readonly [1.5, 0.025, 0.005];
fire.colourStart ​
ts
readonly colourStart: readonly [5, 1.5, 0.1];
fire.drag ​
ts
readonly drag: 0.8 = 0.8;
fire.duration ​
ts
readonly duration: number = Infinity;
fire.gravity ​
ts
readonly gravity: -1 = -1;
fire.inherit ​
ts
readonly inherit: 0 = 0;
fire.life ​
ts
readonly life: readonly [0.25, 0.65];
fire.pool ​
ts
readonly pool: "additive" = 'additive';
fire.rate ​
ts
readonly rate: 55 = 55;
fire.size ​
ts
readonly size: readonly [0.28, 0.025];
fire.speed ​
ts
readonly speed: readonly [0.8, 2];
fire.spin ​
ts
readonly spin: readonly [-3, 3];
fire.spread ​
ts
readonly spread: 0.35 = 0.35;
fire.up ​
ts
readonly up: readonly [0, 1, 0];
hit-sparks ​
ts
readonly hit-sparks: object;
hit-sparks.alpha ​
ts
readonly alpha: readonly [1, 0];
hit-sparks.burst ​
ts
readonly burst: 26 = 26;
hit-sparks.colourEnd ​
ts
readonly colourEnd: readonly [2, 0.2, 0.01];
hit-sparks.colourStart ​
ts
readonly colourStart: readonly [7, 4, 1.5];
hit-sparks.drag ​
ts
readonly drag: 2 = 2;
hit-sparks.duration ​
ts
readonly duration: 0 = 0;
hit-sparks.gravity ​
ts
readonly gravity: 9.8 = 9.8;
hit-sparks.inherit ​
ts
readonly inherit: 0 = 0;
hit-sparks.life ​
ts
readonly life: readonly [0.15, 0.4];
hit-sparks.pool ​
ts
readonly pool: "additive" = 'additive';
hit-sparks.rate ​
ts
readonly rate: 0 = 0;
hit-sparks.size ​
ts
readonly size: readonly [0.055, 0.005];
hit-sparks.speed ​
ts
readonly speed: readonly [3, 9];
hit-sparks.spin ​
ts
readonly spin: readonly [-9, 9];
hit-sparks.spread ​
ts
readonly spread: 1.1 = 1.1;
hit-sparks.up ​
ts
readonly up: readonly [0, 1, 0];
magic ​
ts
readonly magic: object;
magic.alpha ​
ts
readonly alpha: readonly [1, 0];
magic.burst ​
ts
readonly burst: 12 = 12;
magic.colourEnd ​
ts
readonly colourEnd: readonly [0.1, 2, 3];
magic.colourStart ​
ts
readonly colourStart: readonly [2.5, 0.3, 5];
magic.drag ​
ts
readonly drag: 0.6 = 0.6;
magic.duration ​
ts
readonly duration: number = Infinity;
magic.gravity ​
ts
readonly gravity: -0.15 = -0.15;
magic.inherit ​
ts
readonly inherit: 0 = 0;
magic.life ​
ts
readonly life: readonly [0.6, 1.5];
magic.pool ​
ts
readonly pool: "additive" = 'additive';
magic.rate ​
ts
readonly rate: 28 = 28;
magic.size ​
ts
readonly size: readonly [0.075, 0.015];
magic.speed ​
ts
readonly speed: readonly [0.15, 0.65];
magic.spin ​
ts
readonly spin: readonly [-6, 6];
magic.spread ​
ts
readonly spread: 1.5 = 1.5;
magic.up ​
ts
readonly up: readonly [0, 1, 0];
smoke ​
ts
readonly smoke: object;
smoke.alpha ​
ts
readonly alpha: readonly [0.35, 0];
smoke.burst ​
ts
readonly burst: 8 = 8;
smoke.colourEnd ​
ts
readonly colourEnd: readonly [0.18, 0.19, 0.2];
smoke.colourStart ​
ts
readonly colourStart: readonly [0.3, 0.32, 0.34];
smoke.drag ​
ts
readonly drag: 0.5 = 0.5;
smoke.duration ​
ts
readonly duration: number = Infinity;
smoke.gravity ​
ts
readonly gravity: -0.3 = -0.3;
smoke.inherit ​
ts
readonly inherit: 0 = 0;
smoke.life ​
ts
readonly life: readonly [1.5, 3];
smoke.pool ​
ts
readonly pool: "alpha" = 'alpha';
smoke.rate ​
ts
readonly rate: 18 = 18;
smoke.size ​
ts
readonly size: readonly [0.18, 1.2];
smoke.speed ​
ts
readonly speed: readonly [0.2, 0.6];
smoke.spin ​
ts
readonly spin: readonly [-0.5, 0.5];
smoke.spread ​
ts
readonly spread: 0.6 = 0.6;
smoke.up ​
ts
readonly up: readonly [0, 1, 0];
trail ​
ts
readonly trail: object;
trail.alpha ​
ts
readonly alpha: readonly [1, 0];
trail.burst ​
ts
readonly burst: 0 = 0;
trail.colourEnd ​
ts
readonly colourEnd: readonly [0.1, 0.3, 1];
trail.colourStart ​
ts
readonly colourStart: readonly [0.5, 2.5, 4];
trail.drag ​
ts
readonly drag: 0 = 0;
trail.duration ​
ts
readonly duration: number = Infinity;
trail.gravity ​
ts
readonly gravity: 0 = 0;
trail.inherit ​
ts
readonly inherit: 0.05 = 0.05;
trail.life ​
ts
readonly life: readonly [0.12, 0.3];
trail.pool ​
ts
readonly pool: "additive" = 'additive';
trail.rate ​
ts
readonly rate: 120 = 120;
trail.size ​
ts
readonly size: readonly [0.09, 0];
trail.speed ​
ts
readonly speed: readonly [0, 0];
trail.spin ​
ts
readonly spin: readonly [0, 0];
trail.spread ​
ts
readonly spread: 0 = 0;
trail.up ​
ts
readonly up: readonly [0, 1, 0];

Example ​

ts
import { PRESETS } from 'gameable/vfx';
console.log(PRESETS.trail.rate); // 120

Functions ​

createParticles() ​

ts
function createParticles(scene, options?): Particles;

Creates two scene-owned instanced billboard meshes without requiring a renderer.

Parameters ​

ParameterTypeDescription
sceneSceneScene that owns the particle meshes.
optionsParticlesOptionsPool capacity, seed and entity resolver.

Returns ​

Particles

A particle service with idempotent disposal.

Example ​

ts
import { Scene } from 'three/webgpu';
import { createParticles } from 'gameable/vfx';
const particles = createParticles(new Scene(), { seed: 42 });
particles.update(0, 0);
particles.dispose();

createPostStack() ​

ts
function createPostStack(renderer, scene): PostStack;

Grade the finished linear frame and apply the canvas sRGB output transform once. Uses RenderPipeline to supply BloomNode's render context and suppress the renderer's automatic tone mapping/output conversion. Bloom has its own internal blur passes; the final bloom addition, grade and output transform share one full-screen pass. Contrast pivots around 0.18 in linear light after tone mapping, matching gradePixel. No target or pipeline is created until a non-neutral look is requested.

Parameters ​

ParameterTypeDescription
rendererWebGPURendererEngine renderer used to present the frame.
sceneSceneScene receiving the optional exponential fog.

Returns ​

PostStack

A caller-owned post stack; detach it before disposal.

Example ​

ts
import { createEngine } from 'gameable/core';
import { createPostStack } from 'gameable/vfx';
const engine = await createEngine({ canvas: document.createElement('canvas') });
const post = createPostStack(engine.renderer, engine.scene);
engine.setPost(post);
post.set({ reset: false, bloom: 0.5, toneMapping: 'aces' });
// Later: engine.setPost(null); post.dispose();

createVfx() ​

ts
function createVfx(engine, options?): Vfx;

Bind particles and the post stack to a booted engine.

Parameters ​

ParameterTypeDescription
engineEngineA booted engine with a renderer.
optionsVfxOptionsPool capacity, RNG seed and the base look.

Returns ​

Vfx

The sink to hand the engine adapter as vfx, with update to call once per rendered frame.

Example ​

ts
import { createVfx } from 'gameable/vfx';

const vfx = createVfx(engine, { post: { bloom: 0.6 } });
const adapter = createEngineAdapter(engine, { vfx });

gradePixel() ​

ts
function gradePixel(
   rgbLinear, 
   uv, 
   s, 
   out
): void;

CPU reference of the linear grade (bloom and canvas encoding are separate). Order: exposure, r186 tone mapping, contrast around linear 0.18, Rec.709 saturation, tint, then radial vignette (squared centre distance, normalized to one at corners). Writes into the caller's output, which may alias rgbLinear. Neutral is an exact identity.

Parameters ​

ParameterTypeDescription
rgbLinearArrayLike<number>Input linear RGB channels.
uvArrayLike<number>Normalized screen coordinates.
sPostSettingsResolved grade settings.
out{ [n: number]: number; }Caller-owned output RGB storage, optionally aliasing the input.

Returns ​

void

Example ​

ts
import { gradePixel, NEUTRAL_POST } from 'gameable/vfx';
const out = new Float32Array(3);
gradePixel([0.2, 0.3, 0.4], [0.5, 0.5], NEUTRAL_POST, out);
console.log(out[0]); // approximately 0.2

isPreset() ​

ts
function isPreset(name): name is "hit-sparks" | "explosion" | "smoke" | "fire" | "magic" | "dust" | "trail";

Narrows a string to a built-in preset name.

Parameters ​

ParameterTypeDescription
namestringPreset name to check.

Returns ​

name is "hit-sparks" | "explosion" | "smoke" | "fire" | "magic" | "dust" | "trail"

Whether the name belongs to the built-in table.

Example ​

ts
import { isPreset, PRESETS } from 'gameable/vfx';
const name: string = 'smoke';
if (isPreset(name)) console.log(PRESETS[name].rate); // 18

needsPass() ​

ts
function needsPass(s): boolean;

Whether a full-screen pass is needed. Fog is handled by the scene alone. Bloom threshold and radius only affect a pass when bloom strength is nonzero.

Parameters ​

ParameterTypeDescription
sPostSettingsResolved settings to inspect.

Returns ​

boolean

Whether bloom or grading needs a full-screen pass.

Example ​

ts
import { needsPass, NEUTRAL_POST } from 'gameable/vfx';
console.log(needsPass(NEUTRAL_POST)); // false

particleAt() ​

ts
function particleAt(
   row, 
   age, 
   out
): void;

Evaluates a flat a0..a5 row at age in seconds, matching the vertex shader.

Parameters ​

ParameterTypeDescription
rowArrayLike<number>Flat 24-float particle birth record.
agenumberElapsed seconds since birth.
outParticleSampleCaller-owned sample to populate.

Returns ​

void

Example ​

ts
import { particleAt } from 'gameable/vfx';
const row = new Float32Array(24);
row[7] = 1; row[16] = 0.1;
const sample = { position: { x: 0, y: 0, z: 0 }, colour: { x: 0, y: 0, z: 0 },
  size: 0, alpha: 0, rotation: 0 };
particleAt(row, 0.5, sample);
console.log(sample.size); // approximately 0.05

resolvePost() ​

ts
function resolvePost(prev, cmd): PostSettings;

Reset first, then apply present fields. Exposure is floored to 1e-6.

Parameters ​

ParameterTypeDescription
prevPostSettingsPreviously resolved settings.
cmdPostCommandPartial settings to apply after an optional reset.

Returns ​

PostSettings

Immutable settings with bounded fields clamped.

Example ​

ts
import { resolvePost, NEUTRAL_POST } from 'gameable/vfx';
const look = resolvePost(NEUTRAL_POST, { reset: false, exposure: 2 });
console.log(look.exposure); // 2