import { Expr, GpuFragmentParams } from '../../gpu/contract'; import { PropRef } from '../values'; import { ArgSpec } from '../invoke'; /** What a generator's `paint:` field takes: a function that returns the color at this pixel. */ export type Paint = (params: GpuFragmentParams) => Expr; /** * A number per pixel, computed from a coordinate. * * Distance fields (`dist.*`) and `flowField` take a uv coordinate (0–1 across the canvas, y * down). Noise fields take a 3D coordinate, so wrap them in `scaledVolume`. */ export type Field = (params: GpuFragmentParams, coord: Expr) => Expr; /** * A color for a field value between 0 and 1. * * Build one with `pair` (two colors) or `stops` (a multi-stop ramp), then hand it to `rampOver`. */ export type Palette = (t: Expr, params: GpuFragmentParams) => Expr; /** * A number input: a prop `p('name')`, a plain number, or a prop remapped as `{base, add, mul}`. * * The remapped form resolves to `(base + add) * mul`. Use it when a slider's range is not the * unit the word wants, such as a 0–100 slider that should become a small bias. * * @example * ```ts * threshold(field, {low: 0.3, high: 0.7, bias: {base: p('blend'), add: -50, mul: 0.006}}) * ``` */ export type FieldScalar = ArgSpec | { base: ArgSpec; add?: number; mul?: number; }; /** * @internal * Compile-time color-space mode reader — robust to a raw string (a preset loaded before the * prop transform ran) as well as the bridge-mapped number. */ export declare function colorSpaceModeOf(raw: unknown): number; /** * @internal * Pick the color-mix body for the compile-time color space bound by `space` (linear when * absent). The mode is a compile-time prop value, so it JS-branches to a specialised variant * and participates in the structural hash. */ export declare function mixColorsIn(space: PropRef | undefined, params: GpuFragmentParams): import('typegpu').TgpuFn<(colorA: import('typegpu/data').Vec4f, colorB: import('typegpu/data').Vec4f, t: import('typegpu/data').F32) => import('typegpu/data').Vec4f> | import('typegpu').TgpuFn<(colorA: import('typegpu/data').Vec4f, colorB: import('typegpu/data').Vec4f, t: import('typegpu/data').F32) => import('typegpu/data').Vec4f> | import('typegpu').TgpuFn<(colorA: import('typegpu/data').Vec4f, colorB: import('typegpu/data').Vec4f, t: import('typegpu/data').F32) => import('typegpu/data').Vec4f> | import('typegpu').TgpuFn<(colorA: import('typegpu/data').Vec4f, colorB: import('typegpu/data').Vec4f, t: import('typegpu/data').F32) => import('typegpu/data').Vec4f> | import('typegpu').TgpuFn<(colorA: import('typegpu/data').Vec4f, colorB: import('typegpu/data').Vec4f, t: import('typegpu/data').F32) => import('typegpu/data').Vec4f> | import('typegpu').TgpuFn<(colorA: import('typegpu/data').Vec4f, colorB: import('typegpu/data').Vec4f, t: import('typegpu/data').F32) => import('typegpu/data').Vec4f>; /** * A three-color blend: `a` to `b` by the first value, then on to `c` by the second. * * Returns a function you call with two 0–1 values and the params. Both mixes happen in the * color space named by the `space` prop (mark it `compileTime`). * * @example * ```ts * paint: (params) => colorLadder3({a: p('colorA'), b: p('colorB'), c: p('colorC'), space: p('colorSpace')})(t1, t2, params) * ``` * @see pair, quadBlend */ export declare function colorLadder3(slots: { a: PropRef; b: PropRef; c: PropRef; space: PropRef; }): (t1: Expr, t2: Expr, params: GpuFragmentParams) => Expr; /** * A palette of two color props, mixed in the color space named by the `space` prop. * * Mark the `space` prop `compileTime`. Field value 0 gives `a`, 1 gives `b`. * * @example * ```ts * paint: rampOver(dist.radial({center: p('center'), radius: p('radius'), aspect: 1, skew: 0}), pair(p('colorA'), p('colorB'), p('colorSpace'))) * ``` * @see stops, standardPalette */ export declare function pair(a: PropRef, b: PropRef, space: PropRef): Palette; /** * The standard multi-stop palette, read from the `stops`, `colorA` and `colorB` props. * * Declare `stops: colorStopsPropConfig()` plus `colorA` and `colorB` on the definition. When * the user has set two or more stops the ramp uses them, otherwise it mixes `colorA` to * `colorB`. Both mix in the color space named by the `space` prop (mark it `compileTime`). * * @example * ```ts * paint: rampOver(dist.linear({from: p('start'), to: p('end'), angle: p('angle')}), stops(p('colorSpace')), {edges: p('edges')}) * ``` * @tip The prop names are fixed: `stops`, `colorA`, `colorB`. Use `pair` when yours differ. * @see pair, standardPalette */ export declare function stops(space: PropRef): Palette; /** * A paint that colors each pixel by looking its field value up in a palette. * * Pass an `edges` prop (values `stretch`, `transparent`, `mirror`, `wrap`, marked * `compileTime`) to choose what happens where the field runs past 0–1: hold the end colors, * fade to transparent, bounce back, or repeat. Without it the value goes to the palette as * is, which is right for fields that already stay in range such as `rings` or `tone`. * * @example * ```ts * paint: rampOver(dist.linear({from: p('start'), to: p('end'), angle: p('angle')}), stops(p('colorSpace')), {edges: p('edges')}) * ``` * @tip This is the whole recipe for a gradient: a `dist.*` field, a palette, done. * @see dist, pair, stops, rings, tiles */ export declare function rampOver(field: Field, palette: Palette, opts?: { edges?: PropRef; }): Paint; /** * Distance fields: the shape of a gradient, as a number that grows away from a line or point. * * Each takes position props made with `transformPosition` and returns 0 at the start and 1 * at the end, unbounded beyond. Feed one to `rampOver`. * * @see rampOver, rings, tiles */ export declare const dist: { /** * A straight gradient from one point to another. * * 0 at `from`, 1 at `to`. `angle` in degrees turns the whole gradient about the midpoint. * * @example * ```ts * paint: rampOver(dist.linear({from: p('start'), to: p('end'), angle: p('angle')}), stops(p('colorSpace')), {edges: p('edges')}) * ``` * @see rampOver */ linear(slots: { from: PropRef; to: PropRef; angle: FieldScalar; }): Field; /** * A round or elliptical gradient spreading out from a center. * * 0 at `center`, 1 at `radius` (a fraction of the canvas height). `aspect` 1 is a circle, * larger values squeeze it horizontally. `skew` in degrees tilts the ellipse. * * @example * ```ts * paint: rampOver(rings(dist.radial({center: p('center'), radius: p('radius'), aspect: p('aspect'), skew: p('skewAngle')}), p('repeat')), stops(p('colorSpace'))) * ``` * @see rings */ radial(slots: { center: PropRef; radius: FieldScalar; aspect: FieldScalar; skew: FieldScalar; }): Field; /** * A sweep around a center, like a clock hand: 0 to 1 once around. * * The sweep starts at the left of the center and runs counter-clockwise. `rotation` in * degrees moves the start. Wrap it in `tiles` to repeat the sweep. * * @example * ```ts * paint: rampOver(tiles(dist.conic({center: p('center'), rotation: p('rotation')}), p('repeat')), stops(p('colorSpace'))) * ``` * @see tiles */ conic(slots: { center: PropRef; rotation: FieldScalar; }): Field; /** * A diamond-shaped gradient spreading out from a center. * * 0 at `center`, 1 at `size` (a fraction of the canvas height). `roundness` 0 is a sharp * diamond and 1 is a square. `rotation` in degrees tilts it. * * @example * ```ts * paint: rampOver(rings(dist.diamond({center: p('center'), size: p('size'), rotation: p('rotation'), roundness: p('roundness')}), p('repeat')), stops(p('colorSpace'))) * ``` * @see rings */ diamond(slots: { center: PropRef; size: FieldScalar; rotation: FieldScalar; roundness: FieldScalar; }): Field; /** * A turning spiral stripe: 0 on the background, 1 on the stroke. * * It spins on the layer's clock, so declare `animatedTime: {speed: 'speed'}`. `scale` sets * how many turns fit, `width` (0–2) the stroke thickness, `falloff` (0–1) thins the stroke * away from the center, `softness` (0–1) blurs its edge. Unlike the other distance fields * this one is already 0–1, so pair it with a palette of background and stroke colors. * * @example * ```ts * paint: rampOver(dist.spiral({center: p('center'), scale: p('scale'), width: p('strokeWidth'), falloff: p('strokeFalloff'), softness: p('softness')}), pair(p('background'), p('stroke'), p('colorSpace'))) * ``` */ spiral(slots: { center: PropRef; scale: FieldScalar; width: FieldScalar; falloff: FieldScalar; softness: FieldScalar; }): Field; }; /** * Repeats a field as concentric bands. * * `count` above 1 restarts the field every 1/count, so a radial gradient becomes rings. At 1 * or below the field is clamped to 0–1, and values under 1 stretch the gradient further out. * * @example * ```ts * paint: rampOver(rings(dist.radial({center: p('center'), radius: p('radius'), aspect: 1, skew: 0}), p('repeat')), stops(p('colorSpace'))) * ``` * @tip Each band restarts at the first color rather than bouncing back. For a mirrored repeat, pass `edges` to `rampOver` instead. * @see tiles, rampOver */ export declare function rings(field: Field, count: FieldScalar): Field; /** * Repeats a field `count` times, always wrapping. * * Made for sweeps: `tiles(dist.conic(...), 4)` gives four sectors. Any count works, including * fractions. * * @example * ```ts * paint: rampOver(tiles(dist.conic({center: p('center'), rotation: p('rotation')}), p('repeat')), stops(p('colorSpace'))) * ``` * @see rings */ export declare function tiles(field: Field, count: FieldScalar): Field; /** * Reshapes a 0–1 field with glow, contrast and balance controls, then flips it. * * `glow` is a spread control: 5 leaves the field alone, lower values pull the bright parts * into tight cores, higher values spread them into a wash. `contrast` scales around the * middle, 1 is neutral. `balance` is 0–100 with 50 neutral. `invert` is always true: the * result is flipped so the palette's first color lands where the field is high. * * @example * ```ts * paint: rampOver(tone(scaledVolume(noiseField('mx3'), {scale: p('scale')}), {glow: p('intensity'), contrast: p('contrast'), balance: p('balance'), invert: true}), stops(p('colorSpace'))) * ``` * @tip Raising `balance` pushes more of the canvas toward the first color. * @see threshold, noiseField */ export declare function tone(field: Field, opts: { glow: FieldScalar; contrast: FieldScalar; balance: FieldScalar; invert: true; }): Field; /** * Turns a signed field (−1 to 1) into a soft 0–1 mask. * * The field is first mapped to 0–1 and shifted by `bias`, then eased from 0 at `low` to 1 at * `high`. Bring `low` and `high` together for a hard edge. * * @example * ```ts * paint: rampOver(threshold(flowField({detail: p('detail')}), {low: 0.3, high: 0.7, bias: {base: p('blend'), add: -50, mul: 0.006}}), stops(p('colorSpace'))) * ``` * @see flowField, tone */ export declare function threshold(field: Field, opts: { low: number; high: number; bias?: FieldScalar; }): Field; /** * Evaluates a field once and lets several words read the same result. * * Wrap a field in `share` when it feeds two places, such as a `threshold` and a `pulse`. * * @example * ```ts * const pattern = share(flowField({detail: p('detail')})) * // then: threshold(pattern, ...) and pulse(pattern, ...) * ``` * @tip One result per layer, whatever coordinate it is asked for. Do not use it to sample the same field at two offsets. * @see flowField, pulse */ export declare function share(field: Field): Field; /** Noise bases usable as a {@link noiseField} — unit-range ([0,1]) samples over a vec3 coordinate. */ declare const NOISE_BASES: { /** MaterialX 3D noise, unit-biased. Hash-based (bit-cast) → GPU-only. */ readonly mx3: { readonly fn: () => import('typegpu').TgpuFn<(pos: import('typegpu/data').Vec3f) => import('typegpu/data').F32>; readonly hint: "unitNoise3"; }; /** 3D Perlin gradient noise, ~[0,1]. Hash-based (bit-cast) → GPU-only. */ readonly perlin: { readonly fn: () => import('typegpu').TgpuFn<(p: import('typegpu/data').Vec3f) => import('typegpu/data').F32>; readonly hint: "perlin13"; }; /** 3D value noise — soft blocky cells (Quilez permutation), [0,1]. Pure float. */ readonly value: { readonly fn: () => import('typegpu').TgpuFn<(p: import('typegpu/data').Vec3f) => import('typegpu/data').F32>; readonly hint: "value13"; }; /** RAW MaterialX 3D noise, signed [-1,1] — pair with a signed tone. GPU-only. */ readonly mx3signed: { readonly fn: () => import('typegpu').TgpuFn<(p: import('typegpu/data').Vec3f) => import('typegpu/data').F32>; readonly hint: "mxNoiseFloat3"; }; }; /** * The noise flavors `noiseField` can sample. * * `mx3` is smooth all-purpose noise, `perlin` classic gradient noise, `value` softer and * blockier. All three return 0–1. `mx3signed` returns −1 to 1 for use with `threshold`. */ export type NoiseBasis = keyof typeof NOISE_BASES; /** * A field of smooth noise in the chosen flavor. * * Noise needs a 3D coordinate, so put it inside `scaledVolume` (and `warped` for motion). * * @example * ```ts * paint: rampOver(scaledVolume(noiseField('mx3'), {scale: p('scale')}), stops(p('colorSpace'))) * ``` * @see scaledVolume, warped, tone */ export declare function noiseField(basis: NoiseBasis): Field; /** * Makes a field flow like liquid by bending its coordinate with moving noise. * * `amount` sets how far the coordinate is pushed (0 is off), `amountScale` multiplies it so * a 0–1 slider can reach further. The motion runs on the layer's clock times `timeScale`, so * declare `animatedTime: {speed: 'speed'}`. Needs a 3D coordinate: put it inside `scaledVolume`. * * @example * ```ts * scaledVolume(warped(noiseField('mx3'), {amount: p('warp'), amountScale: 4, timeScale: 0.125}), {scale: p('scale')}) * ``` * @see noiseField, scaledVolume, warpedPoint */ export declare function warped(field: Field, opts: { amount: FieldScalar; amountScale?: number; timeScale?: number; }): Field; /** * Gives a noise field a zoomable, aspect-correct coordinate. * * Turns the pixel's uv into a 3D point so `noiseField` and `warped` can read it. `scale` zooms * exponentially: 1 is neutral and each step up magnifies about 2.7 times. A 0–4 slider works * well. * * @example * ```ts * paint: rampOver(scaledVolume(noiseField('perlin'), {scale: p('scale')}), stops(p('colorSpace'))) * ``` * @see noiseField, warped */ export declare function scaledVolume(field: Field, opts: { scale: FieldScalar; }): Field; /** * A swirling, animated wave pattern from −1 to 1. * * `detail` sets how fine the swirls are. It moves on the layer's clock, so declare * `animatedTime: {speed: 'speed'}`. Feed it to `threshold` for color bands or to `pulse` for * a shimmer. * * @example * ```ts * paint: rampOver(threshold(flowField({detail: p('detail')}), {low: 0.3, high: 0.7}), stops(p('colorSpace'))) * ``` * @see threshold, pulse, share */ export declare function flowField(slots: { detail: FieldScalar; }): Field; /** * A gentle brightness pulse that follows a pattern field, hovering around 1. * * The result swings between 1 − `depth` and 1 + `depth`, so `depth` around 0.02 is subtle. * `speed` is how fast it breathes, `span` how much the pattern staggers the phase. Use it as * the gain in `shimmered`. * * @example * ```ts * paint: shimmered(rampOver(threshold(pattern, {low: 0.3, high: 0.7}), stops(p('colorSpace'))), pulse(pattern, {speed: 2.5, span: 8, depth: 0.015})) * ``` * @see shimmered, flowField */ export declare function pulse(field: Field, opts: { speed: number; span: number; depth: number; }): Field; /** * A rainbow color for a hue value: 0 is red, cycling through the spectrum and back every 1. * * Takes any number and wraps it. Returns an rgb color with no alpha. * * @example * ```ts * const rgb = hueWheel(math.add(math.mul(angle, 0.42), animatedTime(params))) * ``` */ export declare function hueWheel(h: Expr): Expr; /** * Multiplies a paint's color and alpha by a gain field, for a slow shimmer over a pattern. * * The gain is read at the raw canvas uv. Build it with `pulse`. * * @example * ```ts * paint: shimmered(rampOver(field, stops(p('colorSpace'))), pulse(field, {speed: 2.5, span: 8, depth: 0.015})) * ``` * @see pulse */ export declare function shimmered(paint: Paint, gain: Field): Paint; /** * A mesh-gradient field: up to 8 drifting points scattered across the canvas, blended by nearness. * * Each point carries a fixed 0–1 value and the result is their distance-weighted average, so * a palette lookup gives soft patches of color. `at` is an aspect-corrected coordinate (x runs * 0 to the aspect ratio, y 0–1). `count` is 1–8, `seed` reshuffles the layout, `drift` (0–1) * sets how far points wander on `time`, `softness` sharpens the patches as it rises. Pass * `anchors` from `scatteredAnchors` to skip recomputing the points per pixel. * * @example * ```ts * const t = scatterField({at: uv, count: uniformOf(p('count'), params), seed, drift: uniformOf(p('drift'), params), aspect, time, softness: 3}) * return standardPalette(t, params) * ``` * @see scatteredAnchors, wrapRamp, standardPalette */ export declare function scatterField(opts: { at: Expr; count: Expr | number; seed: Expr | number; drift: Expr | number; aspect: Expr | number; time: Expr | number; softness: Expr | number; /** The points precomputed each frame by `scatteredAnchors`. When given, `drift`, `aspect` and `time` are ignored here. */ anchors?: Expr; }): Expr; /** * Computes the `scatterField` points once per frame instead of once per pixel. * * Declare a field on the definition, `extraFields: {meshAnchors: {schema: schema.arrayOf(schema.vec4f, 4), initial: new Array(16).fill(0)}}`, * name it in `field`, and pass the result as `anchors` to `scatterField`. The points follow * the layer's clock, so declare `animatedTime`. * * @example * ```ts * const anchors = scatteredAnchors(params, {count: p('count'), seed: p('seed'), drift: p('drift'), field: 'meshAnchors'}) * const t = scatterField({at: uv, count: uniformOf(p('count'), params), seed, drift: 0, aspect: 1, time: 0, softness: 3, anchors}) * ``` * @see scatterField */ export declare function scatteredAnchors(params: GpuFragmentParams, slots: { count: PropRef; seed: PropRef; drift: PropRef; field: string; }): Expr; /** * Runs a palette value back and forth `cycles` times so a ramp repeats without a seam. * * At `cycles` 1 the value passes through unchanged. Where the bands get thinner than a pixel * they fade to the palette's middle instead of flickering. Use it on the value before the * palette lookup, in a `paint:` function. * * @example * ```ts * return standardPalette(wrapRamp({t: field, cycles: math.add(1, math.mul(uniformOf(p('wrapping'), params), 6))}), params) * ``` * @tip Not for use inside a `guarded` branch. Reach for `foldRamp` there. * @see foldRamp, scatterField */ export declare function wrapRamp(opts: { t: Expr; cycles: Expr | number; }): Expr; /** * Folds any number into 0–1 as a seamless zigzag: up from 0 to 1, back down, every 2 units. * * Scale the value first to set the cycle length. Safe anywhere, including inside a `guarded` * branch. * * @example * ```ts * const rgb = stops(p('colorSpace'))(foldRamp(phase), params).member('rgb') * ``` * @see wrapRamp */ export declare function foldRamp(t: Expr): Expr; /** * A 3D position pushed around by moving noise, for reading several fields at one warped spot. * * `at` is the 3D point to warp, `time` drives the motion, `amount` how far it is pushed. * `levels` 2 (default) is the full warp, 1 is a cheaper single pass for low-power devices. * Use `warped` instead when one field is enough. * * @example * ```ts * const q = warpedPoint({at: math.vec3(x, y, seed), time: math.mul(t, 0.3), amount: 0.5, levels: isMobileGpuViewport() ? 1 : 2}) * ``` * @see warped, warpStep */ export declare function warpedPoint(opts: { at: Expr; time: Expr | number; amount: Expr | number; levels?: 1 | 2; }): Expr; /** * One step of a chained warp: pushes a 2D position by two noise reads and hands them back. * * Returns a vec4: `xy` is the warped position, `zw` the two noise values, useful for shading * later. Chain steps by feeding one step's `xy` into the next. `scale` sets the noise * frequency, `z` is a time or seed slice, `offsets` two fixed 2D offsets that keep the reads * apart, and the push distance is `reach` times `strength`. * * @example * ```ts * const level1 = warpStep({at: uv, scale: 1.2, z: math.mul(t, 0.5), offsets: [[5.2, 1.3], [1.7, 9.2]], reach: 0.4, strength: uniformOf(p('warp'), params)}) * const level2 = warpStep({at: level1.member('xy'), scale: 2.4, z: t, offsets: [[8.3, 2.8], [2.1, 6.4]], reach: 0.25, strength: uniformOf(p('warp'), params)}) * ``` * @see warpedPoint, quadBlend */ export declare function warpStep(opts: { at: Expr; scale: number; z: Expr | number; offsets: [[number, number], [number, number]]; reach: number; strength: Expr | number; }): Expr; /** * The color for a 0–1 value from the standard palette props: `colorA`, `colorB`, `stops`, `colorSpace`. * * Declare those four props (`stops: colorStopsPropConfig()`, `colorSpace` marked `compileTime`). * It has the shape of a `Palette`, so it drops straight into `rampOver`, or call it yourself * inside a `paint:` function. * * @example * ```ts * paint: rampOver(scaledVolume(noiseField('mx3'), {scale: p('scale')}), standardPalette) * ``` * @see stops, pair */ export declare function standardPalette(t: Expr, params: GpuFragmentParams): Expr; /** * A four-color blend on a square: `a` to `b` across the top, `c` to `d` across the bottom, then top to bottom. * * Returns a function you call with two 0–1 values (across, then down) and the params. Mixes * in the color space named by the `space` prop (mark it `compileTime`). For any space other * than linear, declare `extraFields` named `convA`, `convB`, `convC`, `convD`, each * `{schema: schema.vec3f, initial: [0, 0, 0]}`. * * @example * ```ts * const base = quadBlend({a: p('colorA'), b: p('colorB'), c: p('colorC'), d: p('colorD'), space: p('colorSpace')})(t1, t2, params) * ``` * @see colorLadder3, pair */ export declare function quadBlend(slots: { a: PropRef; b: PropRef; c: PropRef; d: PropRef; space: PropRef; }): (t1: Expr, t2: Expr, params: GpuFragmentParams) => Expr; export {}; //# sourceMappingURL=fields.d.ts.map