import { d } from '../../gpu/kit/index'; import { Vec4StateArray, AgentHome2 } from './agentForces'; type AtomicU32 = d.atomicU32; type StorageTex = d.textureStorage2d<'rgba16float', 'write-only'>; /** What an oriented splat's layout must name: `agents`, the `accumE` and `accumS` canvases, and `params.aspect` and `params.bodyR`. */ export interface WorldSplatLayout { readonly $: { readonly agents: Vec4StateArray; readonly accumE: AtomicU32[]; readonly accumS: AtomicU32[]; readonly params: { readonly aspect: number; readonly bodyR: number; }; }; } /** What a ramp resolve's layout must name: the two canvases, the output texture `outTex`, and the two colors `params.colA` and `params.colB`. */ export interface RampResolveLayout { readonly $: { readonly accumE: AtomicU32[]; readonly accumS: AtomicU32[]; readonly outTex: StorageTex; readonly params: { readonly colA: d.v4f; readonly colB: d.v4f; }; }; } /** How agents with a heading are drawn. */ export interface OrientedWorldSplatConfig { /** The shape name: `arrow`, `streak`, `square`, `dot`, `glow`. */ shape: string; /** The canvas side in texels. */ res: number; /** Largest body half-width accepted, in texels. A safety cap, not a size. */ splatRCap: number; /** Where the heading comes from: the agent's velocity, or a stored angle plus `home`. */ heading: 'velocity' | 'angle'; /** Where excitement comes from: a per-agent buffer, or speed over `params.speedNorm`. */ agitation: 'buffer' | 'speed'; /** Stretch a soft shape into a comet along the heading. */ comet: boolean; /** For `heading: 'angle'`: the resting position each stored offset is measured from. */ home?: AgentHome2; name: string; } /** * The splat for agents with a heading: the shape is drawn along the way the agent points, in * canvas units so it looks the same on any aspect. Hard shapes get a one-texel soft edge; * `glow` is a soft falloff, comet-stretched when `comet` is on. */ declare function splatOrientedWorld(layout: WorldSplatLayout, cfg: OrientedWorldSplatConfig): (i: number) => void; /** * The three parts a round mote is drawn with: where it sits (`orbitalPlace`), how bright it is * (`twinkleBrightness`) and how big it is (`presenceRadius`). Each is built over your layout * and handed to `renderAgents.pointWorld`. * * Every part varies per mote by a hash of its index, so a field of motes never moves in step. * * @example * ```ts * renderAgents.pointWorld(simLayout, {shape, res: 1024, place: pointSlot.orbitalPlace(simLayout, {radius: 0.035, rate: 1.4}), brightness: pointSlot.twinkleBrightness(simLayout, {freq: 2}), bodyRadius: pointSlot.presenceRadius(simLayout), names: {splat: 'motesSplat', resolve: 'motesResolve'}}) * ``` * @see renderAgents, splat */ export declare const pointSlot: { /** * The mote's drawn position: its stored position plus a slow circle of `radius` (canvas * heights) at `rate` turns per second, both scaled by `params.randomness` and varied per * mote. Reads `params.time`. * * @example * ```ts * place: pointSlot.orbitalPlace(simLayout, {radius: 0.035, rate: 1.4}) * ``` * @see twinkleBrightness, presenceRadius */ readonly orbitalPlace: (layout: { readonly $: { readonly params: { readonly time: number; readonly randomness: number; }; }; }, cfg: { radius: number; rate: number; }) => PointWorldSplatConfig["place"]; /** * The mote's brightness: a twinkle at `freq` cycles per second, blended in by * `params.twinkle` (0 steady, 1 full shimmer), times a per-mote presence so some motes read * nearer than others. Reads `params.time`. * * @example * ```ts * brightness: pointSlot.twinkleBrightness(simLayout, {freq: 2}) * ``` * @see orbitalPlace, presenceRadius */ readonly twinkleBrightness: (layout: { readonly $: { readonly params: { readonly time: number; readonly twinkle: number; }; }; }, cfg: { freq: number; }) => PointWorldSplatConfig["brightness"]; /** * The mote's size: `params.bodyR` scaled by the same per-mote presence as the brightness, * so the motes that read nearer are bigger and brighter together. * * @example * ```ts * bodyRadius: pointSlot.presenceRadius(simLayout) * ``` * @see twinkleBrightness */ readonly presenceRadius: (layout: { readonly $: { readonly params: { readonly bodyR: number; }; }; }) => PointWorldSplatConfig["bodyRadius"]; }; /** How round motes are drawn: the shape, the canvas size, and the three `pointSlot` parts. */ export interface PointWorldSplatConfig { /** The shape name: `dot`, `glow`, `square`. */ shape: string; /** The canvas side in texels. */ res: number; /** Where the mote is drawn, from its stored position. */ place: (fi: number, stored: d.v2f) => d.v2f; /** How bright the mote is. */ brightness: (fi: number) => number; /** The mote's body radius in canvas heights, already floored. */ bodyRadius: (fi: number) => number; name: string; } /** What a point splat's layout must name: `agents`, the `accumE` canvas, and `params.aspect` and `params.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; }; }; } /** How a 3D swarm is drawn: the shape, the output size, the largest particle, and the reach of each particle's profile. */ export interface VolumeSplatConfig { /** The shape name: `dot`, `glow`, `square`. */ shape: string; /** The output side in texels. */ outRes: number; /** Largest particle radius drawn, in texels. */ maxSplatSize: number; /** How far a particle's profile reaches, as distance² over size². 6 covers a soft glow. */ extQ: number; names: { splat: string; glow: string; profile: string; }; } /** * The splat for a 3D swarm: each particle is placed on screen through the layer's center, * scale and rotation with a soft perspective from its depth, then drawn with a profile that * `params.softness` blends from crisp to a glow. Speed is recorded alongside so the ramp * resolve can color fast particles. */ 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; }; }; } /** How particles standing on an image are drawn. */ export interface ReliefSplatConfig { /** The shape name: `dot`, `glow`, `square`. */ shape: string; /** Largest body radius drawn, in texels. */ splatRCap: number; /** Width of the soft edge on hard shapes, in texels. 0.75 is typical. */ aaW: number; /** Child alpha below which a particle is not drawn at all. */ minAlpha: number; /** Precision of the color accumulation. 1024 is typical. */ fp: number; /** How fast particles breathe when idle, in cycles per second of `params.time`. */ wobbleFreq: number; /** How strongly depth changes size and spread. */ perspK: number; name: string; } /** * The splat for particles standing on an image: each is placed through the camera rows with * perspective, then drawn in the child's color at its home. Nearer particles weigh more, so * they win the color where particles overlap, with no sorting. */ declare function splatRelief(layout: ReliefRenderLayout, cfg: ReliefSplatConfig): (x: number, y: number) => void; /** How the rest-to-excited color resolve behaves. */ export interface RampResolveConfig { /** The color space the two colors mix in, as the `colorSpace` prop's number. */ colorSpace: number; /** The canvas side in texels. */ res: number; /** The scale the splat accumulated at. `renderAgents` fills this in. */ invGain: number; /** Multiply brightness by `params.exposure`. */ exposure?: boolean; /** Motion trails: `'on'` always, `'none'` never, or `{baked}` for a layer whose trails prop can be 0. */ trails: 'on' | 'none' | { baked: boolean; }; name: string; } /** * The resolve that colors agents from a rest color to an excited color: where more excited * agents landed, the color leans toward `params.colB`. Empty texels are transparent. With * trails on, each frame fades into a persistent canvas by `params.trails`. */ declare function resolveRamp(layout: RampResolveLayout, cfg: RampResolveConfig): (x: number, y: number) => void; /** The resolve for one tint: `params.color` with an alpha from how much landed on each texel, 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; /** * The resolve for the image relief: the average of the colors that landed on each texel, * weighted so nearer particles win, with an alpha from the total coverage. `fp` must match * the splat's; `alphaK` sets how fast coverage turns opaque. */ declare function resolveWeightedColor(layout: ReliefRenderLayout, cfg: { fp: number; alphaK: number; name: string; }): (x: number, y: number) => void; /** * The step that draws every agent into the shared canvas, one variant per kind of agent. * `renderAgents` pairs each with its resolve; reach for `splat` alone when you need another * pairing. * * @example * ```ts * splat: {kernel: splat.orientedWorld(simLayout, {shape: 'arrow', res: 1024, splatRCap: 16, heading: 'velocity', agitation: 'buffer', comet: true, name: 'boidsSplat'}), threads: 'agents'} * ``` * @tip `relief` dispatches over a 2D grid (`threads: 'grid'`); the other three dispatch over `agents`. * @see resolve, renderAgents, pointSlot */ export declare const splat: { /** * Agents with a heading: arrows, streaks, squares, dots or comets drawn along the way they * point. * * @example * ```ts * splat: {kernel: splat.orientedWorld(simLayout, {shape: 'arrow', res: 1024, splatRCap: 16, heading: 'velocity', agitation: 'buffer', comet: true, name: 'boidsSplat'}), threads: 'agents'} * ``` * @see resolve.ramp, renderAgents.orientedWorld */ readonly orientedWorld: typeof splatOrientedWorld; /** * Round motes with a live softness, placed, brightened and sized by `pointSlot` parts. * * @example * ```ts * splat: {kernel: splat.pointWorld(simLayout, {shape: 'glow', res: 1024, place: pointSlot.orbitalPlace(simLayout, {radius: 0.035, rate: 1.4}), brightness: pointSlot.twinkleBrightness(simLayout, {freq: 2}), bodyRadius: pointSlot.presenceRadius(simLayout), name: 'motesSplat'}), threads: 'agents'} * ``` * @see resolve.tint, pointSlot, renderAgents.pointWorld */ readonly pointWorld: typeof splatPointWorld; /** * A 3D swarm in perspective, recording speed for the ramp resolve. * * @example * ```ts * splat: {kernel: splat.volume(simLayout, {shape: 'dot', outRes: 1024, maxSplatSize: 6, extQ: 2.6, names: {splat: 'swarmSplat', glow: 'swarmGlowProfile', profile: 'swarmProfile'}}), threads: 'agents'} * ``` * @see resolve.ramp, renderAgents.volume */ readonly volume: typeof splatVolume; /** * Particles standing on an image, drawn in the child's color through a camera. Dispatch it * over a 2D grid. * * @example * ```ts * splat: {kernel: splat.relief(fieldLayout, {shape: 'dot', splatRCap: 24, aaW: 0.75, minAlpha: 0.02, fp: 1024, wobbleFreq: 1.3, perspK: 0.9, name: 'reliefSplat'}), threads: 'grid'} * ``` * @see resolve.weightedColor, renderAgents.relief */ readonly relief: typeof splatRelief; }; /** * The step that turns the shared canvas into the output picture and clears it for the next * frame. Pair `ramp` with `orientedWorld` or `volume`, `tint` with `pointWorld`, * `weightedColor` with `relief`. * * @example * ```ts * resolve: {kernel: resolve.tint(simLayout, {res: 1024, name: 'motesResolve'}), threads: 'fixed', size: [1024, 1024]} * ``` * @tip Dispatch a resolve with `threads: 'fixed'` over the canvas size. It must touch every texel to clear it. * @see splat, renderAgents */ export declare const resolve: { /** * Rest-to-excited color from two colors, with optional motion trails. * * @example * ```ts * resolve: {kernel: resolve.ramp(simLayout, {colorSpace: 2, res: 1024, invGain: 1 / 255, trails: 'on', name: 'boidsResolve'}), threads: 'fixed', size: [1024, 1024]} * ``` * @see splat.orientedWorld, splat.volume */ readonly ramp: typeof resolveRamp; /** * One color, with an alpha from coverage. * * @example * ```ts * resolve: {kernel: resolve.tint(simLayout, {res: 1024, name: 'motesResolve'}), threads: 'fixed', size: [1024, 1024]} * ``` * @see splat.pointWorld */ readonly tint: typeof resolveTint; /** * The average of the colors that landed, nearer particles first. * * @example * ```ts * resolve: {kernel: resolve.weightedColor(fieldLayout, {fp: 1024, alphaK: 1.6, name: 'reliefResolve'}), threads: 'fixed', size: [outW, outH]} * ``` * @see splat.relief */ readonly weightedColor: typeof resolveWeightedColor; }; export {}; //# sourceMappingURL=agentRender.d.ts.map