import { ColorTransformContext, ColorTransformFn, EffectMaterial } from "./EffectMaterial.js"; import { GlobalUniforms } from "../GlobalUniforms.js"; import { Texture } from "three"; import { NodeBuilder } from "three/webgpu"; import Node from "three/src/nodes/core/Node.js"; //#region src/materials/Sprite2DMaterial.d.ts interface Sprite2DMaterialOptions { map?: Texture; transparent?: boolean; alphaTest?: number; /** * Whether sprites using this material receive lighting. Part of the * material identity (batching) key — lit and unlit sprites can't share * a batch. Default `false`. */ lit?: boolean; /** * Use premultiplied alpha blending. * When true, the shader outputs `vec4(rgb * alpha, alpha)` and uses * `CustomBlending` with `OneFactor` / `OneMinusSrcAlphaFactor`. * This eliminates `Discard()` calls, improving performance on WebGL * by preserving early-z optimization. Depth writes are disabled since * transparent pixels produce (0,0,0,0) which blends to nothing. */ premultipliedAlpha?: boolean; /** * Effect buffer tier size in floats. * Buffers are allocated in tiers: 0, 4, 8, 16. * Default is 8 (2 vec4 buffers), covering most effect combinations. * Set to 0 for fully effect-free materials (no effect buffer overhead). */ effectTier?: number; /** Color transform function for custom effects (e.g., lighting) */ colorTransform?: ColorTransformFn; /** Global uniforms for auto-applying tint, time, etc. */ globalUniforms?: GlobalUniforms; /** Effect configuration key for material caching/batching */ effectsKey?: string; } /** * Compute the non-texture fragment of `Sprite2DMaterial`'s shared-cache * key (transparent, lit, colorTransform, alphaTest, premultipliedAlpha, * effectsKey). `getShared()` prefixes this with the texture id for its * flat module-global cache; world-scoped variant resolution * (`ecs/batchUtils.ts`'s `getWorldEffectVariant`) keys its per-world * store by texture identity already, so it uses this fragment alone. * Exported so both call sites build an identical key from one place. */ declare function sprite2DMaterialVariantKey(options?: Sprite2DMaterialOptions): string; /** * TSL-based material for 2D sprites. * * UNIFIED API: This material reads from instance attributes, which works for: * - Single sprites (Sprite2D sets attributes on its geometry) * - Batched sprites (SpriteBatch sets instanced attributes) * * Core instance attributes (always present): * - instanceUV (vec4): frame UV (x, y, width, height) in atlas * - instanceColor (vec4): tint color and alpha (r, g, b, a) * - instanceFlip (vec2): flip flags (x, y) where 1 = normal, -1 = flipped * * Effects are composed via `registerEffect()`, which packs effect data into * fixed-size vec4 buffers with per-sprite enable flags. */ declare class Sprite2DMaterial extends EffectMaterial { /** * Canonical three.js class identifier — every built-in material * overrides this (MeshBasicMaterial's `type` is `'MeshBasicMaterial'`, * etc.). Devtools / inspectors that walk the scene graph read `.type` * to categorise materials without `instanceof` checks. Subclasses * (e.g. future `TileMapMaterial`) should override again. */ type: string; /** * Cache of shared material instances, keyed by configuration. * Used by `getShared()` so sprites with identical config reuse the same material. */ private static _cache; /** * Get a shared material instance for the given options. * Materials with identical configuration (texture, transparent, lit, colorTransform) * return the same instance, enabling automatic batching. */ static getShared(options?: Sprite2DMaterialOptions): Sprite2DMaterial; /** * Unique batch ID for this material instance (used for batching). */ readonly batchId: number; private _spriteTexture; private _premultipliedAlpha; private _globalUniforms; /** * Synthesized corner UV varying — replaces the geometry `uv()` * attribute (the synth-quad geometry ships no uv buffer). On the * tight-mesh strategy this is the geometry `uv()` node instead. * @internal */ private _cornerUV; /** * True while this material renders through the tight-mesh path: * alpha-blend (`transparent`, no alphaTest) with polygon data * registered for its texture. Fixed per shader build — a strategy * flip bumps `_effectSchemaVersion` so batches rebuild with matching * geometry. * @internal */ _tightMesh: boolean; /** * Registry `version` this material's current geometry strategy was * last resolved against. Lets `_resolveGeometryStrategy` notice a * merge/degrade that changed the atlas's CONTENT (new frames folded * in, or a `complete` flip) even when `_tightMesh` itself didn't * flip — a plain presence check can't see that, but a stale `version` * still means the batch's baked-at-construction envelope is wrong. * @internal */ private _atlasMeshVersion; constructor(options?: Sprite2DMaterialOptions); /** * Sprite batches compose their instance transform in the custom * `positionNode`. Three.js evaluates hardware clip distances before that * position is available, so those distances describe the shared unit quad * instead of the transformed sprite. Keep clipping in the fragment stage, * where the view-position varying is produced after `positionNode` assigns * the synthesized, instance-transformed local position. * * @internal */ setupHardwareClipping(builder: NodeBuilder): void; /** * Three r185 applies its built-in instance transform before assigning a * custom `positionNode`. The synthesized quad uses `positionNode`, so the * default order would overwrite the transformed position and collapse every * instance back to the shared unit quad. Assign the synthesized corner first, * then apply the instance matrix. Tight-mesh materials keep Three's default * setup path because they read their position from geometry. * * @internal */ setupPosition(builder: NodeBuilder): Node<'vec3'>; /** * Get the global uniforms reference. */ get globalUniforms(): GlobalUniforms | null; /** * Set the global uniforms reference. * Triggers shader rebuild to include global tint. */ set globalUniforms(value: GlobalUniforms | null); /** * Gate _rebuildColorNode() — skip if no texture is set yet. * @internal */ protected _canBuildColor(): boolean; /** * Build the base color node for sprites. * Handles UV flip, atlas remapping, texture sampling, tint, and alpha test. * Called inside Fn() context by EffectMaterial._rebuildColorNode(). * @internal */ protected _buildBaseColor(): { color: Node<'vec4'>; uv: Node<'vec2'>; } | null; /** * Get the base sprite texture for channel providers. * @internal */ protected _getBaseTexture(): Texture | null; /** * Get the sprite texture. */ getTexture(): Texture | null; /** * Set the sprite texture. Re-resolves the geometry strategy: an * alpha-blend material whose atlas registered polygon meshes flips to * the tight-mesh path (geometry position/uv instead of vertexIndex * synthesis). A flip bumps the schema version so existing batches * rebuild with matching geometry. */ setTexture(value: Texture | null): void; /** * Re-resolve the tight-mesh/synth-quad geometry strategy. * * @param deferRebuild - When true, skip the `_rebuildColorNode()` call * even if the strategy flipped. Set by `_beforeEffectCapCheck()`, * which runs mid-`registerEffect()` before the effect buffer tier is * resized — rebuilding the color node there would read `bufNodes` at * the OLD (too-small) tier and crash on an out-of-range buffer index * for the effect that just pushed floats past it. `registerEffect` * rebuilds the color node itself once the tier is correct; this just * needs `_tightMesh`/`positionNode`/`_cornerUV` updated beforehand so * that later rebuild picks up the demoted strategy. * @internal */ _resolveGeometryStrategy(deferRebuild?: boolean): void; /** * Effective effect-float cap for a prospective total. A tight-mesh * material demotes to synth-quad (cap 24) rather than staying tight * (cap 16) the moment its effect floats exceed 16, so a late effect * registration that crosses 16 is measured against the synth cap it * will actually run under — not thrown against the stale tight cap. * Recomputes `wantsTight` from scratch (not `_tightMesh`) so it stays * a pure query, safe on `registerEffect`'s throw path. * @internal */ protected _effectFloatCap(prospectiveTotal: number): number; /** * After an effect commits, re-resolve the geometry strategy so a * material that crossed the 16-float tight-mesh cap actually demotes to * synth-quad. Deferred rebuild: `registerEffect` resizes the buffer * tier and rebuilds the color node itself right after this returns, so * we only need `_tightMesh`/`positionNode`/`_cornerUV` updated here (see * `_resolveGeometryStrategy`). Runs on the success path only. * @internal */ protected _applyEffectGeometryStrategy(): void; /** * Effect capacity depends on the geometry strategy: tight-mesh spends * 2 vertex-buffer bindings on geometry (position + uv), leaving 4 * effect buffers = 16 floats under WebGPU's 8-binding cap; the * index-only synth quad leaves 6 buffers = 24 floats. * @internal */ get maxEffectFloats(): number; /** * The construction options that participate in the shared-cache / * variant key (`sprite2DMaterialVariantKey`), read back from live * material state. Re-resolution paths (enrollment bootstrap, dispose * resurrection, texture reassignment) rebuild a variant from these so * the resurrected material preserves every key-bearing flag — notably * `alphaTest` (opaque + depth fast-path) and `premultipliedAlpha` * (`CustomBlending`), both of which change the shader / blend state and * were previously dropped on re-resolution. `effectsKey` is owned by * the sprite's live effect set, so callers layer it on; `lit` is a key * discriminant the constructor never consumes, so it is intentionally * not reconstructable here. * @internal */ get variantOptions(): Sprite2DMaterialOptions; /** * Clone this material. */ clone(): this; dispose(): void; } //#endregion export { type ColorTransformContext, type ColorTransformFn, Sprite2DMaterial, Sprite2DMaterialOptions, sprite2DMaterialVariantKey }; //# sourceMappingURL=Sprite2DMaterial.d.ts.map