import { MaterialEffect } from "../materials/MaterialEffect.js"; import { Sprite2DMaterial, Sprite2DMaterialOptions } from "../materials/Sprite2DMaterial.js"; import { SpriteBatch } from "../pipeline/SpriteBatch.js"; import { SortLayerName, SortLayerValue } from "../pipeline/sortLayers.js"; import { AlphaMap } from "../events/AlphaMap.js"; import { Sprite2DOptions, SpriteFrame } from "./types.js"; import { HitTestMode } from "../events/HitTestMode.js"; import { Registry } from "../orchestration/registry.js"; import { CAST_SHADOW_MASK, EFFECT_BIT_OFFSET, LIT_FLAG_MASK, RECEIVE_SHADOWS_MASK, ROTATED_FRAME_MASK } from "../materials/effectFlagBits.js"; import { BufferGeometry, Color, Intersection, Mesh, Object3D, Raycaster, Scene, Texture, Vector2, Vector3 } from "three"; import { Entity, World } from "koota"; //#region src/sprites/Sprite2D.d.ts interface FlatlandClipAncestor extends Object3D { _containsWorldPoint?(point: Vector3): boolean; _enrollHierarchySprite?(sprite: Sprite2D): void; _releaseHierarchySprite?(sprite: Sprite2D): void; _releaseDirectEnrollment?(sprite: Sprite2D): void; } declare class Sprite2D extends Mesh { geometry: BufferGeometry; material: Sprite2DMaterial; /** * Backing field for the `material` prototype accessor installed after * this class (see the `Object.defineProperty` call at the bottom of * this file — `Mesh` declares `material` as a plain data property, and * TypeScript disallows shadowing that with a class accessor (TS2611), * same reasoning as the `renderOrder` interception below). * * Declared with `declare` (ambient — no runtime class-field emission) * rather than as a real field. With `target: ES2022`, * `useDefineForClassFields` is on, so an uninitialized real field here * would be `[[Define]]`'d back to `undefined` immediately after * `super()` returns — wiping out the value the `material` setter just * wrote during `Mesh`'s constructor (`super(geometry, material)` calls * `this.material = material`, which runs before any of Sprite2D's own * field initializers). `_setupInstanceAttributes()`, called later in * this same constructor, needs the real material immediately, so it * can't tolerate that wipe the way `_renderOrderValue` does (that one * is gated by `_interceptionArmed` until construction finishes). * @internal */ _materialRef: Sprite2DMaterial; /** * Internal-only material write that preserves bootstrap/registry-default * bookkeeping — used by the `texture` setter's same-status default swap * (new texture, still an auto-managed default). Going through the * public `material` setter there would look identical to a user's * explicit override and would wrongly opt the sprite out of * auto-orchestration management. * @internal */ private _setMaterialInternal; /** * Own-geometry buffers for custom attributes (unbatched rendering). * Each entry maps an attribute name to its Float32Array (4 vertices) and component size. * @internal */ private _customBuffers; /** Stored tint color — observable proxy for R3F compat. */ private _tintColor; /** Anchor point (0-1) — observable proxy for R3F compat. */ private _anchor; /** Current frame */ private _frame; /** Source texture */ private _texture; /** Hit-test modes supported by this class. See spec §6. */ static readonly supportedHitTestModes: readonly HitTestMode[]; /** CPU-side alpha data for `'alpha'` hit-test mode. */ alphaMap: AlphaMap | null; /** Alpha value (0–1) below which a pixel is treated as transparent. */ alphaThreshold: number; /** Custom hit radius in local units (default 0.5 = inscribed circle of unit quad). */ private _hitRadius; /** Active hit-test strategy. */ private _hitTestMode; /** * True while R3F batch-root picking has nulled this sprite's `raycast` * to keep it out of R3F's per-object interaction list — the owning * SpriteBatch raycasts on its behalf via {@link Sprite2D._hitTestInto}. * Distinguishes that proxy-owned null from a user opt-out * (`raycast={null}` / `hitTestMode = 'none'`), which the batch must * respect. Managed by `react/batchPicking`. * @internal */ _pickProxied: boolean; /** Hit radius override in local units. Default 0.5 (inscribed half-width of unit quad). */ get hitRadius(): number; set hitRadius(value: number); /** Pointer hit-testing strategy. Setting `'none'` nulls the instance `raycast` property. */ get hitTestMode(): HitTestMode; set hitTestMode(value: HitTestMode); /** Pixel-perfect mode */ pixelPerfect: boolean; /** * Whether this sprite receives lighting from Flatland's LightEffect. * Stored as bit 0 of `_systemFlags` so lit/unlit sprites with the same * texture share the same material and batch together. * Default: `true` — set `lit = false` to opt out. */ get lit(): boolean; set lit(value: boolean); /** * Whether this sprite receives shadows from the SDF shadow pipeline. * Stored as bit 1 of `_systemFlags`. * Default: `true` — set `receiveShadows = false` to opt out. */ get receiveShadows(): boolean; set receiveShadows(value: boolean); /** * Per-sprite occluder radius used by shadow-casting effects (world * units). Consumed by any LightEffect that needs to know "how big is * this sprite as an occluder" — the SDF sphere-tracer uses it as the * self-silhouette escape distance; a future shadow-map effect would * use it for depth bias; an AO pass could use it as sample radius. * * `undefined` (default) means auto-resolve from `max(scale.x, scale.y)` * at batch-write time — tracks scale changes automatically, covers * sprite animation frames whose source size differs (AnimatedSprite2D * updates `scale` from `frame.sourceWidth/Height`). Assign a number * to override — useful when the visible body is tighter than the * quad's bounds or when the anchor pushes the silhouette off-center. */ private _shadowRadius; /** * Per-sprite occluder radius (world units) consumed by shadow-casting * LightEffects — e.g. {@link DefaultLightEffect}'s SDF sphere-tracer * uses it as the self-silhouette escape distance so a tracer launched * from inside the caster steps out cleanly. * * Returns `undefined` while in auto-resolve mode (default), in which * case `transformSyncSystem` writes `max(|scale.x|, |scale.y|)` into * the per-instance attribute each frame — covering animation frames * and runtime scale changes without manual updates. Assign a number * to override (useful when the visible body is tighter than the * quad's bounds, or when an off-center anchor pushes the silhouette). * Assign `undefined` to return to auto-resolve. */ get shadowRadius(): number | undefined; set shadowRadius(value: number | undefined); /** * Whether this sprite contributes its silhouette to the shadow-caster * occlusion pre-pass. Stored as bit 2 of `_systemFlags`. Default: `false` * — most sprites don't cast; opt in by setting to `true`. * * Consumed by the occlusion pre-pass shader, which masks the sprite's * alpha by this bit before the SDF seed. Flipping it takes effect on * the next frame with zero CPU rebuild (same model as `receiveShadows`). */ get castsShadow(): boolean; set castsShadow(value: boolean); /** * System-flag bitmask written to `instanceSystem.z`. * * Bits: * 0 — lit (default on) * 1 — receiveShadows (default on) * 2 — castsShadow (default off, opt in) * 3..23 — reserved for future system flags * * MaterialEffect enable bits live in a separate field * ({@link _effectFlags}) written to `instanceSystem.w`. * @internal */ _systemFlags: number; /** * MaterialEffect enable-bit bitmask written to `instanceSystem.w`. * * Bit N is set while the Nth registered MaterialEffect on this sprite's * material is currently active. 24 slots, bits 0..23. Separate from * {@link _systemFlags} so system flags don't compete with user-defined * effect capacity. * @internal */ _effectFlags: number; /** * Active MaterialEffect instances on this sprite. * @internal */ _effects: MaterialEffect[]; /** Index into the backing arrays (0 when standalone, eid when enrolled). */ _idx: number; /** @internal */ _uvX: number[]; /** @internal */ _uvY: number[]; /** @internal */ _uvW: number[]; /** @internal */ _uvH: number[]; /** @internal */ _colorR: number[]; /** @internal */ _colorG: number[]; /** @internal */ _colorB: number[]; /** @internal */ _colorA: number[]; /** @internal */ _flipXArr: number[]; /** @internal */ _flipYArr: number[]; /** @internal */ _layerArr: number[]; /** * The registered sortLayer name when assigned by name; null when the * sprite uses a raw numeric sortLayer. The numeric resolution always * lives in `_layerArr` — this only preserves the name for reads. * @internal */ _sortLayerName: string | null; /** * True once the user explicitly assigned a sortLayer (name or number). * SortLayerGroup respects explicit assignments and never overrides them. * @internal */ _sortLayerExplicit: boolean; /** * True once the user directly customized `renderOrder`, escaping the * sortLayer system — the sprite renders standalone from then on. * @internal */ _renderOrderOverridden: boolean; /** * Armed at the end of construction; gates the `renderOrder` setter so * three's `Object3D` constructor default assignment doesn't count as a * user override. * @internal */ private _interceptionArmed; /** Backing store for the intercepted `renderOrder` accessor. @internal */ private _renderOrderValue?; /** Authored visibility, separate from the batcher's source-mesh suppression. @internal */ _visibleValue: boolean; /** Whether texture/frame setup has made this sprite drawable. @internal */ private _contentReady; /** Whether auto-batching is suppressing this sprite's own Mesh draw. @internal */ private _batchSuppressed; /** * The auto-orchestration registry this sprite is tracked by, when it * was picked up from a vanilla scene (no SpriteGroup / Flatland). * @internal */ _autoRegistry: Registry | null; /** Enrolled by the nearest SpriteGroup while retaining a real source parent. @internal */ _hierarchyManaged: boolean; /** SpriteGroup that enrolled this retained source descendant. @internal */ _hierarchyOwner: FlatlandClipAncestor | null; /** Schedule already composed matrixWorld before SpriteGroup traverses this retained source. @internal */ _batchWorldFresh: boolean; /** Inline hierarchy-tracker snapshot; avoids a WeakMap lookup for every sprite. @internal */ _batchHierarchyState?: unknown; /** User material whose disposal currently blocks automatic re-enrollment. @internal */ _batchEnrollmentBlockedMaterial: Sprite2DMaterial | null; /** Terminal object-disposal latch; disposed sprites cannot be re-enrolled. @internal */ _disposed: boolean; /** * True while the material is the construction-time bootstrap default * (texture-only construction, resolved via the static shared cache so * an unmanaged standalone sprite still renders). Enrollment re-resolves * to a world-scoped default and clears this. Explicit materials and * effect-variant switches clear it too. * @internal */ _materialIsBootstrapDefault: boolean; /** * True when the current material came from a world/registry default * store. Dispose of such a material resurrects the sprite with a * fresh default instead of orphaning it. * @internal */ _materialWasRegistryDefault: boolean; /** * True while the material is a constants-effect variant resolved * through the module-global `Sprite2DMaterial.getShared` fallback * (an `addEffect` with constants ran before this sprite had a world * or auto-orchestration registry to resolve through). Enrollment * re-resolves to a world-scoped variant and clears this — the * constants-effect counterpart of `_materialIsBootstrapDefault`. * @internal */ _materialIsBootstrapVariant: boolean; /** * True when the current material came from a world/registry * effect-variant store. Dispose of such a material resurrects the * sprite with a fresh variant instead of orphaning it — the * constants-effect counterpart of `_materialWasRegistryDefault`. * @internal */ _materialWasRegistryVariant: boolean; /** * Scene whose prime-pending set still holds this sprite (Signal A * fired, no renderer seen yet). Cleared on registration or removal. * @internal */ _pendingPrimeScene: Scene | null; /** * True while this auto-orchestrated sprite is drawn by a batch. The * source Mesh is suppressed independently from authored `visible`, so * React Activity and user visibility survive promotion/demotion. * @internal */ _autoBatched: boolean; /** * Toggle the batcher's private source-mesh suppression without writing * authored visibility. React's renderer owns `visible`; batching must not. * @internal */ _setBatchSuppressed(value: boolean): void; /** Authored visibility before batch suppression is applied. @internal */ _isAuthoredVisible(): boolean; /** Visibility projected into a batch slot, excluding source-mesh suppression. @internal */ _batchVisibilityState(): boolean; /** Resolve this sprite's world-scoped batch registry, if assigned. */ private _registryCacheWorld; private _registryCache; private _registryData; /** Invalidate projected transform and visibility state in the assigned world. */ private _markTransformsDirty; /** Update internal draw readiness without taking ownership of authored visibility. @internal */ private _setContentReady; /** * Keep the logical Object3D's public visibility independent from whether * its own Mesh participates in Three's render-list projection. A batched * source remains a Mesh instance with authored geometry/material, but the * batch is its physical draw representation. Clearing Three's runtime type * discriminator prevents the duplicate source draw before render-list * insertion without corrupting `visible`, ancestor traversal, or raycasts. * @internal */ private _syncSourceMeshParticipation; /** Intercept an authored visibility write and invalidate its batch slot. @internal */ _setAuthoredVisible(value: boolean): void; /** * Resolve the scene-graph parent whose transform and visibility apply to * this batched sprite. Auto sprites retain their real parent; explicitly * managed sprites use their owning SpriteGroup as the graph boundary. * @internal */ _batchHierarchyParent(): Object3D | null; /** True when authored visibility and every source ancestor are visible. @internal */ _isHierarchyVisible(parentOverride?: Object3D | null): boolean; /** Test a world point against every Flatland clip ancestor. @internal */ private _isInsideHierarchyClips; /** * Trimmed-frame placement, baked into the matrix by `updateMatrix` * and `transformSyncSystem`: the quad shrinks to the trimmed rect * (scale factors) and shifts to its position within the source * bounds (offsets, unit-quad space, y-up). Identity for untrimmed * frames. * @internal */ _trimSX: number; /** @internal */ _trimSY: number; /** @internal */ _trimOX: number; /** @internal */ _trimOY: number; /** @internal */ _zIndexArr: number[]; /** * The ECS entity for this sprite (null until enrolled in a world). * @internal */ _entity: Entity | null; /** * The ECS world this sprite belongs to (set by SpriteGroup or Flatland). * @internal */ _flatlandWorld: World | null; /** * Cached batch references for O(1) direct-write dispatch from setters. * * Populated by `batchAssignSystem` once a slot is allocated; updated by * `batchReassignSystem` on cross-batch moves; cleared by * `batchRemoveSystem` on slot free. While `_entity !== null`, * `_batchMesh !== null` and `_batchSlot >= 0` is the invariant. * * Setters that need to write to GPU buffers (UV via setFrame, color * via tint/alpha, flip via flipX/flipY) read these directly instead * of routing through Koota's Changed channel and a per-frame * bufferSync system pass. * @internal */ _batchMesh: SpriteBatch | null; /** @internal */ _batchSlot: number; /** @internal */ _batchIdx: number; /** Owned per-sprite geometry (carries the instance-attribute buffers) */ private _geometry; /** * Instance attribute buffers for single-sprite rendering. * The synth quad indexes 4 vertices, so we need 4 copies of each value. */ /** * Interleaved per-vertex storage mirroring SpriteBatch's instance * layout. 4 vertices × 16 floats per vertex = 64 floats. Each * vertex carries the same instance data (no per-vertex variation on * standalone sprites). One buffer keeps the standalone draw under the * WebGPU vertex-buffer cap even with effectBuf* attributes present * (geo + 1 interleaved + effectBuf* vs geo + 4 + effectBuf*). * * Layout per vertex (offset in floats from vertex base): * 0..3 instanceUV (x, y, w, h) * 4..7 instanceColor (r, g, b, a) * 8..11 instanceSystem (flipX, flipY, sysFlags, enableBits) * 12..15 instanceExtras (shadowRadius, reserved, reserved, reserved) */ private _instanceDataBuffer; /** * Create a new Sprite2D. * Can be called with no arguments for R3F compatibility - set texture via property. */ constructor(options?: Sprite2DOptions); /** * Resolve the effective shadow radius for this sprite — either the * explicit user override or the auto-derived `max(scale.x, scale.y)`. * Called by both the standalone path (_updateOwnShadowRadius) and * `transformSyncSystem` when populating the batch's per-instance * `instanceShadowRadius` attribute. * @internal */ _resolveShadowRadius(): number; /** * Write the resolved shadow radius into `instanceExtras.x` * (interleaved core buffer, float offset 12 within each vertex's * stride of 16). * @internal */ private _updateOwnShadowRadius; /** * Push the resolved shadow radius to the enrolled SpriteBatch's * `instanceExtras.x`. Used when `shadowRadius` is imperatively set * by user code; the per-frame `transformSyncSystem` also writes this * value as part of the transform sync so scale-driven auto values * stay in lockstep with `instanceMatrix`. * @internal */ private _syncShadowRadiusToBatch; /** * Resolve a world- or registry-scoped default material for `texture`, * for a sprite that isn't holding a user-supplied material. * * Returns `null` when the sprite has neither an assigned world nor an * auto-orchestration registry yet — the pre-enrollment bootstrap * fallback (`Sprite2DMaterial.getShared`) covers that case instead. * @internal */ private _resolveWorldDefaultMaterial; /** * Resolve a world- or registry-scoped effect-variant material for * `texture` + `options` — the constants-effect counterpart of * `_resolveWorldDefaultMaterial`. * * Returns `null` when the sprite has neither an assigned world nor an * auto-orchestration registry yet — the pre-enrollment bootstrap * fallback (`Sprite2DMaterial.getShared`) covers that case instead. * @internal */ private _resolveWorldEffectVariant; /** * Get the current texture. */ get texture(): Texture | null; /** * Set a new texture. */ set texture(value: Texture | null); /** * Build a stable cache key fragment from effect constants. * Uses texture ID for Textures, String() for primitives. * @internal */ private _constantsKey; /** * Build the effectsKey for this sprite's currently-attached * constants-bearing effects (from `_effects` + `_constantsKey`). * Shared by `addEffect` (initial resolution, where the just-added * effect is already in `_effects`) and by enrollment/dispose * re-resolution, which rebuild the same key from the sprite's live * effect state. * @internal */ private _buildEffectsKey; /** * Options mirroring this sprite's current material config, for * re-resolving an effect-variant material (enrollment bootstrap * re-resolution, dispose resurrection, or a texture reassignment that * must not mutate a shared variant in place). * @internal */ _currentVariantOptions(): Sprite2DMaterialOptions; /** * Switch to a different shared material, carrying over all state. * @internal */ private _switchToMaterial; /** * Swap to a world-scoped default material (enrollment resolution or * dispose resurrection). Carries effects/uniforms via the standard * switch path, then re-marks the material as a registry default. * @internal */ _resolveDefaultMaterial(material: Sprite2DMaterial): void; /** * Swap to a world-scoped effect-variant material (enrollment * resolution or dispose resurrection) — the constants-effect * counterpart of `_resolveDefaultMaterial`. * @internal */ _resolveEffectVariantMaterial(material: Sprite2DMaterial): void; /** * Get the current frame. */ get frame(): SpriteFrame | null; /** * Set the current frame (R3F prop compatibility). */ set frame(value: SpriteFrame | null); /** * Set the current frame. * Note: Does not modify scale - call updateSize() manually if needed after first frame. */ setFrame(frame: SpriteFrame): this; /** * Get the anchor point. Returns the stored Vector2 (like Object3D.position). */ get anchor(): Vector2; /** * Set the anchor point. Accepts [x, y] array or Vector2. */ set anchor(value: Vector2 | [number, number]); /** * Set the anchor point (0-1). * (0, 0) = top-left, (0.5, 0.5) = center, (0.5, 1) = bottom-center * * The anchor offset is baked into the matrix transform — no * geometry rebuild. Writing `_anchor.set(...)` triggers the * observable.vector2 callback which marks the matrix dirty; the next * `updateMatrix` picks up the new value. */ setAnchor(x: number, y: number): this; /** * Get tint color. Returns a stored Color reference (like Material.color). * Mutating the returned Color triggers ECS sync via onChange callback. */ get tint(): Color; /** * Set tint color. Accepts Color, hex string, hex number, or [r, g, b] array (0-1). */ set tint(value: Color | string | number | [number, number, number]); /** * Get alpha/opacity. */ get alpha(): number; /** * Set alpha/opacity (0-1). */ set alpha(value: number); /** * Get flipX state. */ get flipX(): boolean; /** * Set flipX state. */ set flipX(value: boolean); /** * Get flipY state. */ get flipY(): boolean; /** * Set flipY state. */ set flipY(value: boolean); /** * Flip the sprite. */ flip(horizontal: boolean, vertical: boolean): this; /** * Get the sortLayer (primary sort key). Returns the registered name * when one was assigned; the numeric order otherwise. */ get sortLayer(): SortLayerValue; /** * Set the sortLayer (primary sort key) — a registered name (typed via * `SortLayerRegistry` augmentation) or a raw numeric order. Routes the * sprite to the batch matching its new run key on the next system pass. */ set sortLayer(value: SortLayerValue); /** * The numeric sortLayer order (names resolved). Hot-path accessor for * matrix Z-baking and run-key computation. * @internal */ get sortLayerValue(): number; /** * SortLayerGroup discipline path — identical to the public setter but * does NOT mark the assignment explicit, so a later direct * `sprite.sortLayer = …` (or a different group) can still take over. * @internal */ _applySortLayerFromGroup(name: SortLayerName): void; /** * Get z-index within layer (secondary sort key). */ get zIndex(): number; /** * Set z-index within layer (secondary sort key). * * Hot path. Every moving sprite in a y-sorted scene calls this every * frame, so the cost has to stay near-zero per call. * * The raw SoA write is unconditional — `transformSyncSystem` reads * `_zIndexArr` directly and bakes the value into the instance matrix * for the GPU depth test. That alone is enough for alphaTest+depthWrite * materials (GPU resolves order via the baked-in Z, no CPU sort needed). * * For non-gated materials, we flip the batch's `_sortDirty` flag so * `batchSortSystem` knows to re-sort this batch on its next pass. This * replaced the prior `Changed(SpriteZIndex)` channel — Koota's change * tracker enumerated every flip every frame even when the gate trivially * skipped the sort, costing ~7ms/frame in a 12k-sprite demo. The * per-batch boolean costs one ref read + one write. */ set zIndex(value: number); /** * Get the width of the sprite in world units. */ get width(): number; /** * Get the height of the sprite in world units. */ get height(): number; /** * Update the mesh scale based on frame size. */ private updateSize; /** * Mark the shared instance-data buffer dirty so three.js re-uploads * it on the next render. The four `InterleavedBufferAttribute` * views all point at the same underlying `InterleavedBuffer`, so * flipping `needsUpdate` on any one of them re-uploads the full * per-vertex stride. */ private _markInstanceDataDirty; /** * Update flip flags in own geometry buffer (standalone mode). Flip * lives in `instanceSystem.xy` per the interleaved layout. */ private _updateOwnFlip; /** * Update the effect enable-bits slot (instanceSystem.w = interleaved * offset 11) in own geometry buffer (standalone mode). Mirrors * SpriteBatch.writeEnableBits for the batched path. */ private _updateOwnEnableBits; /** * Set up instance attributes on the geometry for single-sprite rendering. * Uses one interleaved buffer (mirroring SpriteBatch) so batched and * standalone paths share the same shader attribute shape. Also * allocates buffers for custom attributes from the material's schema * (pure effect data — `effectBuf0`, `effectBuf1`, ...). */ _setupInstanceAttributes(): void; /** * Update the instanceUV attribute from current frame. * Writes to own geometry buffer only (standalone mode). */ private _updateOwnUV; /** * Update the instanceColor attribute from current tint and alpha. * Writes to own geometry buffer only (standalone mode). */ private _updateOwnColor; /** * Get world position (convenience method). */ getWorldPosition2D(): Vector2; /** * Add an effect instance to this sprite. * Auto-registers the effect type on the material if not already registered. * Sets the enable bit and writes effect data to packed buffers. * * @example * ```typescript * const dissolve = new DissolveEffect() * dissolve.progress = 0.5 * sprite.addEffect(dissolve) * ``` */ addEffect(effect: MaterialEffect): this; /** * Remove an effect instance from this sprite. * Clears the enable bit and resets effect data to defaults. * The effect type remains registered on the material (no shader change). */ removeEffect(effect: MaterialEffect): this; /** * Direct-write the sprite's current effect state (flags + active field * values) into its batch's packed effect buffers. Same pattern as the * color / zIndex setters — uses the cached `_batchMesh` + `_batchSlot` * refs instead of routing through Koota Changed events. * @internal */ private _writeEffectStateToBatch; /** * Build trait initialization data from an effect's current snapshot defaults. * @internal */ private _buildTraitData; /** * Write all packed effect data to own geometry buffers (standalone mode). * @internal */ _writeEffectDataOwn(): void; /** * Write a single float to a packed effect buffer slot in own geometry buffer. * @internal */ private _writePackedSlotOwn; /** * Sync both per-sprite flag words to the batch buffer for already- * batched sprites. Writes system flags + enable bits into * `instanceSystem.z/.w`, bypassing ECS change detection. * @internal */ _syncEffectFlagsToBatch(): void; /** * Auto-orchestration Signal A: walk the parent chain to the scene and * prime it. Explicitly-managed sprites (SpriteGroup / Flatland) skip * inside flatlandPrime via their assigned world. * @internal */ _onAddedToTree: () => void; /** * Auto-orchestration cleanup: dropped from the tree → out of the * registry (and any still-pending prime set). * @internal */ _onRemovedFromTree: () => void; /** Transfer hook used when an explicit SpriteGroup adopts an auto-batched source. @internal */ _releaseAutoOrchestration(): void; /** * Auto-orchestration Signal B: the sprite's own mesh is being drawn, * so the renderer and scene are in hand. One property check per draw * once registered — the hot path stays ~free. */ onBeforeRender: Mesh['onBeforeRender']; /** * Pointer raycast against the sprite's local Z=0 plane. * * The quad is a centered unit square ([-0.5, 0.5] in X and Y). Anchor and * scale are already baked into the world matrix by `updateMatrix()`, so this * method works entirely in centered-quad local space with no anchor math. */ raycast(raycaster: Raycaster, intersects: Intersection[]): void; /** * Narrow-phase hit test — the body of {@link Sprite2D.raycast}, * callable regardless of the public `raycast` property (which * `hitTestMode = 'none'` and R3F batch-root picking null). * `SpriteBatch.raycast` delegates broadphase candidates here; the mode * check lives inside so `'none'` still skips on every path. * @internal */ _hitTestInto(raycaster: Raycaster, intersects: Intersection[]): void; /** * Make `matrixWorld` current on demand. For an enrolled (batched) sprite, * `matrixWorldAutoUpdate` is off and the sprite is not a graph child, so * three's own traversal never composes it — do it here. For a standalone * sprite, defer to three's normal ancestor walk. This is the standard * three refresh contract, so any consumer (raycast, bounds, devtools) can * `sprite.updateWorldMatrix(true, false)` then read `matrixWorld`. */ updateWorldMatrix(updateParents: boolean, updateChildren: boolean): void; updateMatrixWorld(force?: boolean): void; /** * Compose this batched sprite's matrixWorld directly: local 2D TRS * (via the fast `updateMatrix`) with the owning SpriteGroup's world * affine folded in — the same 2D-affine ∘ 2D-affine math as * `transformSyncSystem`. Called via the `updateWorldMatrix` override. * @internal */ _composeBatchedMatrixWorld(updateParents?: boolean, parentOverride?: Object3D | null): void; /** * Intercepted `renderOrder` write path — three's inherited numeric * primitive, installed as a prototype accessor below the class body * (TS disallows overriding a data property with an accessor). * * A batched sprite isn't in three's render list (its batch is), so a * direct `renderOrder` write would otherwise be silently ignored. * Instead, an explicit user write escapes the sortLayer system: the * sprite demotes to standalone and renders with the custom order, * exactly as three documents for any Object3D. * @internal */ _setRenderOrder(value: number): void; /** * Wrap the inherited `Layers` instance with a Proxy that observes * `mask` writes. `enable`/`disable`/`toggle`/`set` all funnel through * `this.mask = …` internally, so a single set-trap covers every * mutation path. Reads pass straight through. * @internal */ private _wrapLayers; /** * Camera-mask mutation hook: mirror the new mask into the ECS so * `batchReassignSystem` routes the sprite to a batch with a matching * mask. Still batched — a custom mask never drops a sprite to * standalone, it just rides in a differently-masked batch. * @internal */ _onLayersMaskChanged(mask: number): void; /** * Drop out of batching to standalone rendering. Unenrolls from the * ECS (freeing the batch slot on the next system pass) and re-parents * the sprite under the batching group so its own Mesh draw resumes. * @internal */ _demoteToStandalone(): void; /** * Fast 2D matrix update — bypasses Three.js quaternion-based compose(). * * Three.js Object3D.updateMatrix() calls matrix.compose(position, quaternion, scale) * which does full 3D quaternion→matrix math (~20 multiplies). For 2D sprites we only * need position, scale, and optional Z-axis rotation — written directly to the matrix * elements. * * Also bakes in the layer/zIndex Z offset without save/restore of position.z. */ updateMatrix(): void; /** * Enroll this sprite in an ECS world. * Creates an entity with initial trait values from snapshot. * Called automatically by SpriteGroup when adding a sprite. * * @param world - The ECS world to enroll in (defaults to global world) * @internal */ _enrollInWorld(world?: World): void; /** * Unenroll this sprite from its ECS world. * Serializes trait values back to snapshot, then destroys the entity. * Called automatically when sprite is removed from SpriteGroup or disposed. * @internal */ _unenrollFromWorld(): void; /** * Get the ECS entity for this sprite (null if not enrolled). * @internal */ get entity(): Entity | null; /** * Dispose of resources. */ dispose(): void; /** * Copy Three.js object state while preserving authored visibility. A batched * source is internally suppressed, but that implementation detail must not * make the copied sprite authored-hidden. */ copy(source: this, recursive?: boolean): this; /** * Clone the sprite. */ clone(recursive?: boolean): this; } //#endregion export { CAST_SHADOW_MASK, EFFECT_BIT_OFFSET, LIT_FLAG_MASK, RECEIVE_SHADOWS_MASK, ROTATED_FRAME_MASK, Sprite2D }; //# sourceMappingURL=Sprite2D.d.ts.map