import { d } from './index'; /** * The exponential-scale noise framing: aspect-correct the UV (x scaled, y left alone), scale by * `exp(scale)`, and offset by `seed`. Returns the sampling position for a 2D/3D noise field. * * `exp(scale)` rather than `scale` is what makes the UI slider feel linear — each +1 doubles-ish * the frequency — and it is why every noise texture's `scale` prop runs over a small signed range * (−2…5) rather than a cell count. * * `seed` is added to BOTH axes after scaling (not before), which is why changing the seed slides * the pattern rather than rescaling it. */ export declare const aspectScaledDomain: import('typegpu').TgpuFn<(uv: d.Vec2f, viewport: d.Vec2f, scale: d.F32, seed: d.F32) => d.Vec2f>; /** * The per-pixel framing: floor the device-pixel position into `grain`-sized cells, offset by * `seed`. For patterns that are defined on the pixel grid rather than in UV space (blue noise, * ordered dither) — the `floor` is load-bearing, it is what makes the result piecewise-constant per * cell instead of resampling the hash continuously. * * `viewport` must be the EFFECTIVE viewport (`effectiveViewportSize ?? ctx.viewportSize`) so a * resize-fit box grains at its own pixel scale rather than the canvas's. */ export declare const pixelGridDomain: import('typegpu').TgpuFn<(uv: d.Vec2f, viewport: d.Vec2f, grain: d.F32, seed: d.F32) => d.Vec2f>; /** * Lift a 2D position into 3D by walking the third axis with time: `vec3(pos.xy, t * rate)`. * * This is the difference between noise that MORPHS in place and noise that slides. Offsetting the * 2D position by time translates the pattern across the screen; walking a third axis of a 3D noise * evolves it without moving it, which is what every "…that morphs over time" generator wants. * `rate` scales the evolution independently of the node's `speed` prop. */ export declare const timeAxisDomain: import('typegpu').TgpuFn<(pos: d.Vec2f, t: d.F32, rate: d.F32) => d.Vec3f>; /** Per-octave drift shape — see {@link fbmGated}. */ export type FbmDrift = 'goldenAngle' | 'timeSeed'; export interface FbmGatedOptions { /** * The fixed loop count. STRUCTURAL: it is baked into the emitted WGSL, so the octave slider's * MAXIMUM belongs here and its current value is a runtime gate (see the `octaveCount` argument). * Unrolling exactly the active count instead would recompile on every slider step. */ maxOctaves: number; /** * How successive octaves are decorrelated: * - `goldenAngle` — each octave's domain is offset by `animTime · (cos(i·GOLDEN), sin(i·GOLDEN))`. * Incommensurate directions, so octaves interfere rather than translating together. * - `timeSeed` — each octave gets its own time and seed offset (`animTime + i·stride.x`, * `seed + i·stride.y`), for field functions that animate internally. */ drift: FbmDrift; /** Per-octave (time, seed) stride for `drift: 'timeSeed'`. Folded to literals. */ stride?: [number, number]; /** Emitted WGSL identifier base. The option key is appended, so configs cannot collide. */ name?: string; } /** * The gated fractal-sum loop, as a builder: sum `maxOctaves` evaluations of `fieldFn` at * geometrically increasing frequency and decreasing amplitude, with octaves at or beyond a RUNTIME * `octaveCount` contributing exactly zero. * * Returns a `tgpu.fn` yielding `vec2(accumulated, totalWeight)` — NOT a normalized value. The * normalization tail is deliberately left to the caller because the consumers disagree about it * (one divides raw and remaps to [0,1]; the other guards the divide and applies a per-mode scale), * and folding those into an option record would have been four more flags for no shared code. * * The emitted signature depends on `drift`: * * - `goldenAngle`: `(base: vec2f, baseFreq, lacunarity, persistence, animTime, seedOffset: vec2f, * octaveCount) → vec2f`, calling `fieldFn(coord: vec2f) → f32`. * - `timeSeed`: `(base: vec2f, baseFreq, lacunarity, persistence, animTime, seed, extra: vec3f, * octaveCount) → vec2f`, calling `fieldFn(oUV, animT, seedOff, extra.x, extra.y, extra.z) → f32`. * The `extra` vec3 is the C1 struct-over-scalars rule applied cheaply: field functions with their * own parameters (jitter, metric, reduction) pass them through one bundle. * * NAMING CONSEQUENCE: the captured field function is emitted into WGSL under the BUILDER's local * identifier (`octaveField`), not its own name — a transpiled body registers its externals by the * identifier the source text uses. So a shader that passes `noise.mxNoiseFloat2` will see * `fn octaveField(p: vec2f)` in its WGSL. Harmless (the resolver uniquifies collisions), but it means * a snapshot diff over an fbm migration shows the field fn renamed as well as the loop moved. * * The active-octave gate is spelled per drift mode (`i32` compare for `goldenAngle`, `f32` compare * for `timeSeed`), matching what each consumer emitted before migration. The two spellings are * equivalent for integral counts. * * TRAP: the accumulator/weight/frequency locals are initialized from `d.f32(…)`, not bare literals * — an integer-valued literal initializer transpiles to i32 and truncates the whole sum. */ export declare function fbmGated(fieldFn: FbmField2, options: FbmGatedOptions & { drift: 'goldenAngle'; }): FbmGoldenSumFn; export declare function fbmGated(fieldFn: FbmFieldParametric, options: FbmGatedOptions & { drift: 'timeSeed'; }): FbmTimeSeedSumFn; /** A field function of one 2D position, for `drift: 'goldenAngle'`. */ export type FbmField2 = (coord: d.v2f) => number; /** A field function with its own time/seed/parameter arguments, for `drift: 'timeSeed'`. */ export type FbmFieldParametric = (oUV: d.v2f, animT: number, seedOff: number, a: number, b: number, c: number) => number; /** `drift: 'goldenAngle'` output — callable from a body, returning `vec2(accumulated, totalWeight)`. */ export type FbmGoldenSumFn = (base: d.v2f, baseFreq: number, lacunarity: number, persistence: number, animTime: number, seedOffset: d.v2f, octaveCount: number) => d.v2f; /** `drift: 'timeSeed'` output — callable from a body, returning `vec2(accumulated, totalWeight)`. */ export type FbmTimeSeedSumFn = (base: d.v2f, baseFreq: number, lacunarity: number, persistence: number, animTime: number, seed: number, extra: d.v3f, octaveCount: number) => d.v2f; export interface DomainWarp2Options { /** * The four 3D offsets that decorrelate the warp samples — two per level, one per displacement * component — applied as `pos + offset`. Defaults to the canonical Inigo-Quilez set (as used by * Plasma). * * WHICH COMPONENTS TIME-SCALE IS FIXED PER SLOT, not inferred from the values. `q[0]` is the only * entry whose x and y are time RATES (emitted as `vec3f(t·x, t·y, z)`, so its third component is * a plain spatial z); `q[1]`, `r[0]` and `r[1]` all take x/y as plain spatial offsets and their * third component as a time rate (`vec3f(x, y, t·z)`). Changing a slot's layout means editing the * builder body, not the table. */ offsets?: { /** Level 1: `q[0]` is `[xTimeRate, yTimeRate, z]`; `q[1]` is `[x, y, zTimeRate]`. */ q: [[number, number, number], [number, number, number]]; /** Level 2: both entries are `[x, y, zTimeRate]`. */ r: [[number, number, number], [number, number, number]]; }; /** Emitted WGSL identifier base. */ name?: string; } /** * Two-level Inigo-Quilez domain warping over a 3D field function: sample the field twice to build a * 2D displacement, displace the sampling position, do it again from the displaced position, then * return the position to sample the field at for the final value. * * Both levels displace the ORIGINAL position (`pos + d`), not the previous level's — level 2 only * uses the level-1 result to decide WHERE to take its displacement samples. That is what keeps the * warp from compounding into mush at high `warpAmount`. * * The emitted signature is `(pos: vec3f, t: f32, warpAmount: f32) → vec3f`: the final sampling * position, so the caller owns the last field evaluation and any remap of it. * * Which offset components are multiplied by `t` is fixed per slot rather than inferred from the * values — see {@link DomainWarp2Options.offsets} and {@link IQ_WARP_OFFSETS}. */ export declare function domainWarp2(fieldFn: Warp3Field, options?: DomainWarp2Options): Warp3Fn; /** * Single-level Inigo-Quilez domain warp — the first level of {@link domainWarp2} on its own (two * field reads instead of four). The mobile tier for warped recipes: half the noise cost, the same * organic displacement family with shallower folds. Same slot layout and default offsets as level 1 * of the two-level warp, so a recipe can switch levels at build time without touching its look data. */ export declare function domainWarp1(fieldFn: Warp3Field, options?: DomainWarp2Options): Warp3Fn; /** A 3D scalar field function (`mxNoiseFloat3`, `perlin13`, …). */ export type Warp3Field = (p: d.v3f) => number; /** {@link domainWarp2}'s output: `(pos, t, warpAmount) → final sampling position`. */ export type Warp3Fn = (pos: d.v3f, t: number, warpAmount: number) => d.v3f; /** * Radial falloff coverage mask — the GPU body behind std's `radialMask` noun (Vignette's * lattice-free radial field). Aspect-corrected so the falloff is circular, not oval. `center` * takes the transformPosition STORED value `(x, 1 - y)`; the `1.0 - center.y` here recovers the * authored y (the standard center double-flip). Returns 0 inside `radius` (clear) rising to 1 at * `radius + falloff` (full coverage). Pure — CPU-executable as a DualFn for golden tests. */ export declare const radialFalloffMask: import('typegpu').TgpuFn<(uv: d.Vec2f, aspect: d.F32, center: d.Vec2f, radius: d.F32, falloff: d.F32) => d.F32>; /** * One point of an organically scattered, gently drifting constellation, in aspect-corrected UV * space ([0, aspect] × [0, 1]). Golden-angle spiral scatter (even coverage, no clumping) + hashed * jitter + a slow per-point Lissajous drift. Every hash input carries an irrational additive * offset so seed=0 / fi=0 never feeds hash(0). */ export declare const spiralScatter: import('typegpu').TgpuFn<(fi: d.F32, count: d.F32, seed: d.F32, drift: d.F32, aspect: d.F32, animTime: d.F32) => d.Vec2f>; /** * A smooth scalar field from a scattered constellation: each {@link spiralScatter} point carries a * golden-ratio low-discrepancy unit value (evenly spread over [0,1], spatially decorrelated from * the spiral — some neighbours sit close in value, some far), and the query blends them by * inverse distance raised to `power`. The mesh-gradient genre primitive. Fixed-8 loop, * runtime-gated by `count` — a disabled point contributes exactly 0; accumulators init from * d.f32 (integer-valued literal → i32 trap). */ /** * {@link scatterField} with the constellation supplied instead of recomputed: `anchors` holds the * 8 {@link spiralScatter} positions packed 4-per-vec4 (`[i >> 1].xy` for even i, `.zw` for odd — * a uniform array needs a 16-byte stride, so array is not an option). The anchors depend only * on count/seed/drift/aspect/time — none of which vary per pixel — so a consumer computes them ONCE * per frame on the CPU (`utilities/scatterAnchors.ts`, the bit-for-bit mirror of spiralScatter) * and writes them through an extraFields array. Per pixel this removes 8 × (4 hashes + 4 trig + * sqrt) of pixel-invariant work; the blend itself is unchanged, and the per-point unit value * `ti` is the same golden-ratio sequence as before (its seed hash is hoisted out of the loop — * it never depended on i). */ export declare const scatterFieldAnchored: import('typegpu').TgpuFn<(q: d.Vec2f, count: d.F32, seed: d.F32, anchors: d.WgslArray, power: d.F32) => d.F32>; export declare const scatterField: import('typegpu').TgpuFn<(q: d.Vec2f, count: d.F32, seed: d.F32, drift: d.F32, aspect: d.F32, animTime: d.F32, power: d.F32) => d.F32>; /** * One Inigo-Quilez domain-warp level over a CHAINED position (2 noise reads): displaces `pos` by * two decorrelated MaterialX noise samples and returns `vec4(warpedX, warpedY, nx, ny)` — the * samples ride along for tail stages (edge hardness, blend factors, surface light). `k` and * `strength` stay separate args so the displacement keeps the left-assoc `n · k · strength` * rounding. * * [B] Gate-C candidate vs {@link domainWarp2}: the offset literals overlap the canonical set, * but this level displaces the CHAINED position (level 2 reads level 1's output) where * domainWarp2 offsets the original — a drop-in swap is pixel-breaking. Gate C decides whether * chained consumers adopt domainWarp2 or domainWarp2 grows a `chained` mode. */ export declare const chainedWarpStep: import('typegpu').TgpuFn<(pos: d.Vec2f, scale: d.F32, z: d.F32, offA: d.Vec2f, offB: d.Vec2f, k: d.F32, strength: d.F32) => d.Vec4f>; //# sourceMappingURL=fields.d.ts.map