import { d } from '../../gpu/kit/index'; import { Vec4StateArray, AgentHome2 } from './agentForces'; type AtomicU32 = d.atomicU32; type StorageTex = d.textureStorage2d<'rgba16float', 'write-only'>; /** The world-splat core: per-agent vec4 state + the energy/excitement accumulators. */ export interface WorldSplatLayout { readonly $: { readonly agents: Vec4StateArray; readonly accumE: AtomicU32[]; readonly accumS: AtomicU32[]; readonly params: { readonly aspect: number; readonly bodyR: number; }; }; } /** The ramp-resolve core: the two accumulators, the output texture, the colour endpoints. */ export interface RampResolveLayout { readonly $: { readonly accumE: AtomicU32[]; readonly accumS: AtomicU32[]; readonly outTex: StorageTex; readonly params: { readonly colA: d.v4f; readonly colB: d.v4f; }; }; } export interface OrientedWorldSplatConfig { shape: string; /** Square accumulator resolution. */ res: number; /** Sanity cap on the body half-width, in texels (the window itself is dynamic). */ splatRCap: number; /** Where the agent's heading comes from: its velocity (xy = vel) or its angle (z = θ). */ heading: 'velocity' | 'angle'; /** Excitement source: the per-agent envelope buffer, or local speed / params.speedNorm. */ agitation: 'buffer' | 'speed'; /** Stretch the round glow into a comet along the heading (velocity consumers). */ comet: boolean; /** Derive the position from a home part + stored offset (MagneticFilings). */ home?: AgentHome2; name: string; } /** * Rasterize one oriented agent into the accumulators by evaluating the baked shape's coverage * per texel in heading space (t along the heading, n perpendicular), in WORLD units so the * shape reads undistorted on any aspect. Hard shapes get a single-texel AA edge and over-drive * the fixed-point gain (interiors resolve fully opaque); the glow is a Gaussian falloff, comet- * stretched for the velocity consumers. The window is sized per agent from its ACTUAL accept * radius per axis and clipped once (the shared harness helper), and additive fixed-point * atomics are order-independent — no sort. Symmetric shapes (a plain dot, an un-stretched * glow) skip the heading rotation entirely — it cannot change a radial profile. */ declare function splatOrientedWorld(layout: WorldSplatLayout, cfg: OrientedWorldSplatConfig): (i: number) => void; export declare const pointSlot: { /** Placement: the stored position plus a circular orbital wander whose radius and rate * scale with `params.randomness`, phase + rate hashed per agent. Needs * `params.{time, randomness}`. */ readonly orbitalPlace: (layout: { readonly $: { readonly params: { readonly time: number; readonly randomness: number; }; }; }, cfg: { radius: number; rate: number; }) => PointWorldSplatConfig["place"]; /** Brightness: a hash-phased sinusoidal twinkle (blend set by `params.twinkle`) × the * presence cascade. Needs `params.{time, twinkle}`. */ readonly twinkleBrightness: (layout: { readonly $: { readonly params: { readonly time: number; readonly twinkle: number; }; }; }, cfg: { freq: number; }) => PointWorldSplatConfig["brightness"]; /** Body radius: the shared `params.bodyR` scaled by the presence cascade, floored. */ readonly presenceRadius: (layout: { readonly $: { readonly params: { readonly bodyR: number; }; }; }) => PointWorldSplatConfig["bodyRadius"]; }; export interface PointWorldSplatConfig { shape: string; res: number; /** Placement part: stored position → rendered position (the orbital wander). */ place: (fi: number, stored: d.v2f) => d.v2f; /** Brightness part: the twinkle × presence energy modulation. */ brightness: (fi: number) => number; /** Body-radius part: per-agent size (presence cascade), already floored. */ bodyRadius: (fi: number) => number; name: string; } /** * Rasterize one un-oriented mote with a LIVE softness uniform (`params.softness` feathers the * baked hard shapes toward the glow skirt at runtime; the glow shape is always fully soft — * its mix folds away and the fast texel fn emits the Gaussian skirt only). Placement, * brightness and per-agent size arrive as slot parts — they are the consumer's look. */ /** The point-splat core: agent state + the energy accumulator + the live softness. */ export interface PointWorldSplatLayout { readonly $: { readonly agents: Vec4StateArray; readonly accumE: AtomicU32[]; readonly params: { readonly aspect: number; readonly softness: number; }; }; } declare function splatPointWorld(layout: PointWorldSplatLayout, cfg: PointWorldSplatConfig): (i: number) => void; interface VolumeRenderLayout { readonly $: { readonly pos: Vec4StateArray; readonly vel: Vec4StateArray; readonly accumE: AtomicU32[]; readonly accumS: AtomicU32[]; readonly outTex: StorageTex; readonly params: { readonly colA: d.v4f; readonly colB: d.v4f; readonly centerX: number; readonly centerYv: number; readonly scale: number; readonly rotC: number; readonly rotS: number; readonly aspect: number; readonly size: number; readonly exposure: number; readonly softness: number; readonly speedColorK: number; }; }; } export interface VolumeSplatConfig { shape: string; outRes: number; maxSplatSize: number; /** Radial reject: a texel contributes while q = dist²/size² < extQ. */ extQ: number; names: { splat: string; glow: string; profile: string; }; } /** * Project a 3D swarm particle to screen (2D placement rotation + a soft perspective from its * z), then accumulate the particle's softness-blended profile into the energy buffer and a * speed-weighted copy into the second buffer (the resolve derives per-texel average speed for * the rest→excited ramp). Additive fixed-point atomics are order-independent — no z-sort. */ declare function splatVolume(layout: VolumeRenderLayout, cfg: VolumeSplatConfig): (i: number) => void; interface ReliefRenderLayout { readonly $: { readonly pos: Vec4StateArray; readonly col: Vec4StateArray; readonly accumR: AtomicU32[]; readonly accumG: AtomicU32[]; readonly accumB: AtomicU32[]; readonly accumW: AtomicU32[]; readonly outTex: StorageTex; readonly params: { readonly rowX: d.v4f; readonly rowY: d.v4f; readonly rowZ: d.v4f; readonly time: number; readonly gridW: number; readonly gridH: number; readonly outW: number; readonly outH: number; readonly depth: number; readonly wobbleAmp: number; readonly depthShading: number; readonly spacing: number; readonly particleSize: number; readonly aspect: number; readonly zoom: number; readonly transX: number; readonly transY: number; }; }; } export interface ReliefSplatConfig { shape: string; /** Cap on a particle's body radius in texels (the window itself is dynamic). */ splatRCap: number; /** Hard-edge anti-alias half-width, in render texels. */ aaW: number; /** Child alpha below which a particle is skipped entirely. */ minAlpha: number; /** Fixed-point scale for the atomic accumulators. */ fp: number; /** Idle-wobble base frequency (× params.time). */ wobbleFreq: number; /** Projection strength. */ perspK: number; name: string; } /** * The camera + colorFrom-layer splat variant: rotate the particle's 3D world position by the * camera rows, project through a perspective from screen center (near ones enlarge, brighten * and spread outward), then accumulate the shape's coverage as DEPTH-WEIGHTED colour into the * four fixed-point buffers. The weight (persp²·shade) makes near particles dominate the * per-texel average → stylized pseudo-occlusion, no z-sort. */ declare function splatRelief(layout: ReliefRenderLayout, cfg: ReliefSplatConfig): (x: number, y: number) => void; export interface RampResolveConfig { /** Baked colour-space mode for the rest→excited mix (the canonical mixColorsVariants path). */ colorSpace: number; res: number; /** Fixed-point inverse gain (1/255 for the world splats, 1/256 for the volume splat). */ invGain: number; /** Scale the energy by `params.exposure` (the volume consumer's brightness knob). */ exposure?: boolean; /** Trail canvas: 'on' (always composited), {baked: boolean} (read baked out at trails = 0, * write kept so scrubbing up never ghosts), or 'none' (no trail buffer in the layout). */ trails: 'on' | 'none' | { baked: boolean; }; name: string; } /** * Accumulators → colour: the rest→excited ramp mixes in the baked colour space, driven by the * per-texel average excitement (`accumS / accumE`). Most texels get no splat energy at all and * the endpoints are uniforms, so zero-energy texels skip the mix — RGB must still be colA (not * black) or the fragment's bilinear upsample would drag a dark fringe into every edge texel. */ declare function resolveRamp(layout: RampResolveLayout, cfg: RampResolveConfig): (x: number, y: number) => void; /** Accumulated energy → a single tint's alpha (`params.color`) — transparent between agents. */ declare function resolveTint(layout: { readonly $: { readonly accumE: AtomicU32[]; readonly outTex: StorageTex; readonly params: { readonly color: d.v4f; }; }; }, cfg: { res: number; name: string; }): (x: number, y: number) => void; /** * Weighted-average colour resolve (RGB/W — the fixed-point scale cancels) + a coverage alpha * from W (the relief consumer). */ declare function resolveWeightedColor(layout: ReliefRenderLayout, cfg: { fp: number; alphaK: number; name: string; }): (x: number, y: number) => void; export declare const splat: { readonly orientedWorld: typeof splatOrientedWorld; readonly pointWorld: typeof splatPointWorld; readonly volume: typeof splatVolume; readonly relief: typeof splatRelief; }; export declare const resolve: { readonly ramp: typeof resolveRamp; readonly tint: typeof resolveTint; readonly weightedColor: typeof resolveWeightedColor; }; export {}; //# sourceMappingURL=agentRender.d.ts.map