import { GlobalUniforms } from "./GlobalUniforms.js"; import { SortLayerConfig, SortLayerName } from "./pipeline/sortLayers.js"; import { Light2D } from "./lights/Light2D.js"; import { LightEffect } from "./lights/LightEffect.js"; import { WorldProvider } from "./ecs/world.js"; import { SpriteGroup } from "./pipeline/SpriteGroup.js"; import { PassEffect } from "./pipeline/PassEffect.js"; import { Color, ColorRepresentation, Group, Object3D, OrthographicCamera, RenderTarget, Scene, Texture } from "three"; import { RenderPipeline, WebGPURenderer } from "three/webgpu"; import { World } from "koota"; import Node from "three/src/nodes/core/Node.js"; import PassNode from "three/src/nodes/display/PassNode.js"; //#region src/Flatland.d.ts /** * Options for creating a Flatland instance. */ interface FlatlandOptions { /** * Human-readable name shown in the devtools consumer UI. Useful to * distinguish multiple Flatland instances (e.g. `name: 'main-game'` * vs `name: 'minimap'`) or a custom engine's provider from the * default. Default: `'flatland'`. */ name?: string; /** * Render target (null = render to viewport). Targets with NoColorSpace * default to sRGB; set LinearSRGBColorSpace explicitly for linear/HDR output. * Pass the target before its first GPU use so Three allocates the matching * attachment format. */ renderTarget?: RenderTarget | null; /** * Camera to use (null = use the internal orthographic camera). Flatland never * rewrites a supplied camera's frustum; its owner remains responsible for it. */ camera?: OrthographicCamera | null; /** Orthographic view size in pixels (default: 400) */ viewSize?: number; /** Clear before render (default: true) */ autoClear?: boolean; /** Background color */ clearColor?: ColorRepresentation; /** Background alpha (default: 1) */ clearAlpha?: number; /** Enable post-processing pipeline (default: false) */ postProcessing?: boolean; /** * Fixed aspect ratio. When omitted or set to `'auto'`, Flatland derives the aspect from * the renderer's viewport (or the render target) when its dimensions * change. Passing a value pins the internal camera aspect while lighting * still follows the surface. A camera supplied through `camera` keeps its * authored frustum. Calling resize() takes full manual size control; * assigning `aspect = 'auto'` restores automatic sizing. */ aspect?: number | 'auto'; } /** * Flatland - Unified 2D rendering pipeline for Three.js WebGPU. * * Combines sprite batching, post-processing, render targets, and global uniforms * into a single high-level API. Implements WorldProvider — one ECS world per Flatland * instance, shared between sprite batching and post-processing passes. * * @example * ```typescript * // Basic usage - render to viewport * const flatland = new Flatland({ viewSize: 400 }) * flatland.add(new Sprite2D({ texture })) * * // Render loop * function animate() { * flatland.render(renderer) * requestAnimationFrame(animate) * } * ``` * * @example * ```typescript * // Render to texture * import { RenderTarget } from 'three' * * const target = new RenderTarget(512, 512) * const flatland = new Flatland({ renderTarget: target }) * flatland.add(sprite) * * // Use texture on 3D mesh * mesh.material.map = flatland.texture * * // Render loop * flatland.render(renderer) // Renders to target * renderer.render(scene3D, camera3D) // Renders 3D with card * ``` * * @example * ```tsx * // React Three Fiber usage * import { Canvas, extend, useFrame, useThree } from '@react-three/fiber/webgpu' * import { Flatland, Sprite2D } from 'three-flatland/react' * * extend({ Flatland, Sprite2D }) * * function Scene() { * const flatlandRef = useRef(null) * const { renderer } = useThree() * * useFrame(() => { * flatlandRef.current?.render(renderer) * }) * * return ( * * * * ) * } * ``` */ declare class Flatland extends Group implements WorldProvider { /** Internal scene containing sprites */ readonly scene: Scene; /** * Declare (or redeclare) a named sort layer for use with * `sprite.sortLayer` and `SortLayerGroup`. Pair with a * `SortLayerRegistry` interface augmentation for typed names. */ declareSortLayer(name: SortLayerName, config: SortLayerConfig): SortLayerConfig; /** * Resolve a declared sort layer's config — the hook for placing * foreign objects relative to a layer: * * ```ts * skiaText.renderOrder = flatland.sortLayer('ui').renderOrder - 1 * ``` */ sortLayer(name: SortLayerName): SortLayerConfig; /** Internal sprite group for batching */ readonly spriteGroup: SpriteGroup; /** Global uniforms shared across all sprite materials */ readonly globals: GlobalUniforms; /** Camera for 2D rendering */ private _camera; /** Orthographic view size */ private _viewSize; /** Current aspect ratio */ private _aspect; /** * Whether the camera aspect is derived from renderer/render-target size. * An explicit `aspect` option, property assignment, or `resize()` call * switches the camera to manual aspect control. */ private _autoAspect; /** Whether renderer/render-target dimensions are sampled each frame. */ private _autoSurfaceSize; /** Last valid render-surface size — skips redundant per-frame resize work */ private _lastSyncedWidth; private _lastSyncedHeight; /** Whether the active camera is Flatland's managed internal camera. */ private _ownsCamera; /** Stable internal camera restored when R3F removes a custom camera prop. */ private _internalCamera; /** Render target (null = viewport) */ private _renderTarget; /** Render pipeline instance for post-processing */ private _renderPipeline; /** Pass node for post-processing input */ private _passNode; /** Output node for post-processing effects */ private _outputNode; /** Whether the render pipeline is enabled */ private _renderPipelineEnabled; /** Auto-clear before render */ autoClear: boolean; /** Clear color */ clearColor: Color; /** Clear alpha */ clearAlpha: number; /** Cached renderer reference */ private _renderer; /** Last render timestamp for delta time calculation (ms) */ private _lastRenderTime; /** Whether the render pipeline was auto-initialized (vs. manual setRenderPipeline) */ private _autoRenderPipeline; /** Reusable Vector2 to avoid per-frame allocations */ private _tempVec2; /** Reusable physical drawing-buffer size for surface-dependent GPU resources. */ private _drawingBufferSize; /** * Camera frustum bounds as TSL uniform nodes. Created once per Flatland * instance so effect shaders can capture stable references at build * time. Updated in render() from the camera bounds each frame; * `.value` mutation doesn't require a shader rebuild. */ private _worldSizeUniform; private _worldOffsetUniform; /** Active PassEffect instances */ private _passes; /** Auto-increment counter for insertion-ordered passes */ private _nextPassOrder; /** ECS: registry singleton entity */ private _postPassRegistryEntity; /** Active Light2D objects */ private _lights; /** Light data storage (lazy — created when first LightEffect is attached) */ private _lightStore; /** * Shadow pipeline lives on the ECS `ShadowPipeline` singleton trait and * is managed end-to-end by `shadowPipelineSystem`. Flatland does not * hold SDFGenerator / OcclusionPass references — it only bootstraps * the singleton entity and registers the system in the schedule. */ private _shadowPipelineEntity; /** Active LightEffect instance */ private _lightEffect; /** ECS: LightingContext singleton entity */ private _lightingContextEntity; /** All sprite materials tracked for colorTransform assignment */ private _spriteMaterials; /** Whether lighting systems are registered on the schedule */ private _lightingSystemsRegistered; constructor(options?: FlatlandOptions); /** * The ECS world for this Flatland instance. * Delegates to SpriteGroup's lazy-initialized world. */ get world(): World; /** * Create internal orthographic camera. */ private _createCamera; /** * Update camera frustum based on view size and aspect ratio. */ private _updateCameraFrustum; /** * The camera flatland renders its internal scene with. Read-only * access for event integration (portal `events.compute` re-casts * pointer rays from this camera — spec §8.1) and debugging. */ get camera(): OrthographicCamera; /** * Set a custom camera. Assigning the default camera read from a no-arg * Flatland instance restores this instance's own managed camera; this is the * property-removal path used by React Three Fiber. */ set camera(value: OrthographicCamera); /** * Get the view size. */ get viewSize(): number; /** * Set the view size. */ set viewSize(value: number); /** * Get the configured aspect mode. Returns `'auto'` while Flatland's internal * camera follows the render surface; use {@link resolvedAspect} for the * camera's current numeric ratio. A user-supplied camera keeps its authored * frustum regardless of this mode. */ get aspect(): number | 'auto'; /** * Current numeric camera aspect. For Flatland's internal camera this includes * the ratio resolved in auto mode; for a user-supplied camera it is derived * directly from that camera's authored orthographic frustum. */ get resolvedAspect(): number; /** * A number pins the internal camera ratio manually. Assigning `'auto'` * restores automatic internal-camera and effect sizing, including after * `resize()`. User-supplied cameras keep their authored frustum. The explicit * sentinel also lets R3F restore constructor defaults when an `aspect` JSX * prop is removed. Invalid numeric values are ignored. */ set aspect(value: number | 'auto'); /** * Get the render target (null = viewport). */ get renderTarget(): RenderTarget | null; /** * Set the render target. */ set renderTarget(value: RenderTarget | null); /** * Get the render target texture (or null if rendering to viewport). */ get texture(): Texture | null; /** * Get the render pipeline instance. */ get renderPipeline(): RenderPipeline | null; /** * Get the pass node for composing effects. */ get passNode(): PassNode | null; /** * Get/set the output node for post-processing effects. * Set this to apply TSL effect chains. */ get outputNode(): Node | null; set outputNode(value: Node); /** * Add objects to Flatland. * Sprites are routed to the internal SpriteGroup for batching. * Other objects are added directly to the internal scene. * * This overrides Group.add() to route children to the internal scene * rather than this Group, enabling proper rendering with Flatland's camera. */ add(...objects: Object3D[]): this; /** * Remove objects from Flatland. * This overrides Group.remove() to properly remove from internal scene/spriteGroup. */ remove(...objects: Object3D[]): this; /** * Remove all sprites and other objects from the internal scene. * Overrides Group.clear() to clear the internal scene. */ clear(): this; /** * Initialize the render pipeline with a given RenderPipeline instance. * Users should create the RenderPipeline and pass node themselves for flexibility. * * @example * ```typescript * import { RenderPipeline, pass } from 'three/webgpu' * import { crtComplete } from 'three-flatland' * * const pipeline = new RenderPipeline(renderer) * const scenePass = pass(flatland.scene, flatland.camera) * pipeline.outputNode = crtComplete(scenePass, uv(), { curvature: 0.1 }) * * flatland.setRenderPipeline(pipeline, scenePass) * ``` * Flatland preserves the pipeline's outputColorTransform setting. Set it to * false when a manual pipeline writes working-space color to a render target. */ setRenderPipeline(renderPipeline: RenderPipeline, passNode: PassNode): void; /** * Clear the render pipeline setup. */ clearRenderPipeline(): void; /** * Ensure the PostPassRegistry singleton entity exists in the world. */ private _ensurePostPassRegistry; /** * Add a post-processing pass to the pipeline. * Passes are applied in insertion order (or explicit order). Automatically enables post-processing. * * @param passEffect - PassEffect instance to add * @param order - Optional explicit order (default: auto-increment) * @returns this (for chaining) * * @example * ```typescript * import { CRTEffect, VignetteEffect } from 'three-flatland' * * const crt = new CRTEffect() * const vignette = new VignetteEffect() * flatland.addPass(crt).addPass(vignette) * crt.curvature = 0.3 // zero-cost uniform update * ``` */ addPass(passEffect: PassEffect, order?: number): this; /** * Remove a post-processing pass from the pipeline. * * @param passEffect - The same PassEffect instance passed to addPass() * @returns this (for chaining) */ removePass(passEffect: PassEffect): this; /** * Remove all post-processing passes from the pipeline. * Disables post-processing if it was auto-initialized. * * @returns this (for chaining) */ clearPasses(): this; /** * Get the current post-processing passes. */ get passes(): readonly PassEffect[]; /** * Mark the post-pass chain as structurally dirty. * Called by PassEffect.enabled setter. * @internal */ _markPostPassDirty(): void; /** * Get the active Light2D instances. */ get lights(): readonly Light2D[]; /** * Get the active LightEffect. */ get lighting(): LightEffect | null; /** * Set the lighting effect for this Flatland instance. * The LightEffect produces a ColorTransformFn that is applied to all lit sprites. * * Flatland owns the active effect's lifecycle while attached. Replacing it * or passing `null` calls `dispose()` before detachment so GPU resources are * released promptly. The effect instance remains reusable and will receive * a fresh `init → resize → update` sequence when reattached; callers should * not use effect-owned GPU resource handles while the effect is detached. * * @param lightEffect - LightEffect instance (or null to disable lighting) * @returns this (for chaining) * * @example * ```typescript * import { DefaultLightEffect } from '@three-flatland/presets' * * const lighting = new DefaultLightEffect() * flatland.setLighting(lighting) * lighting.ambientIntensity = 0.4 // zero-cost uniform update * ``` */ setLighting(lightEffect: LightEffect | null): this; /** * Mark lighting as structurally dirty (effect enabled/disabled). * @internal Called by LightEffect.enabled setter and _onDirty callback. */ _markLightingDirty(): void; /** @internal Re-apply the active effect resolution scale. */ _markLightingResizeDirty(): void; /** * Pending-rebuild guard. Coalesces multiple synchronous setter * calls inside one tick into a single `_doRebuildLightFn` run. * Without this, flipping N constants at once would trigger N * full TSL graph rebuilds. */ private _shaderRebuildPending; /** * Re-run the attached LightEffect's `_buildLightFn` and push the * fresh closure to every tracked lit material. Called from a * writable LightEffect constant setter when a compile-time toggle * changes (e.g. `glowEnabled`, `bandsEnabled`). * * Coalesces via microtask: the first call schedules, subsequent * synchronous calls are no-ops, the microtask runs once and reads * the latest constant values. * * @internal */ _rebuildLightFn(): void; private _doRebuildLightFn; /** * Ensure the LightingContext singleton entity exists. */ private _ensureLightingContext; /** * Register lighting systems on the world's SystemSchedule. * Adds a `prepend()` to insert them before existing sprite systems. */ private _ensureLightingSystems; /** * Ensure the ShadowPipeline singleton entity exists. `shadowPipelineSystem` * owns the rest of its lifecycle — Flatland only bootstraps the trait so * the system has something to find on first run. */ private _ensureShadowPipelineEntity; /** * WeakSet of sprites already warned about, so the same gap doesn't spam * the console every time a sprite is re-added or lighting is re-attached. */ private _channelWarnedSprites; private _pendingChannelValidation; /** * Devtools producer — owns the BroadcastChannel, subscribers, stats, * env, scratch message buffers, and the tick-building logic. * Flatland coordinates timing (begin/end around render) but doesn't * hold the data. * * `null` outside of the devtools build gate && `isDevtoolsActive()`. * Prod builds with no flags have this as a single `null` field and the * entire subsystem tree-shakes out. See `debug-protocol.ts` for the * gate contract. */ private _devtools; /** Set in dispose() so a still-resolving lazy devtools import() bails. */ private _disposed; /** * Dev-only check: for the currently attached lighting effect's declared * channel `requires`, ensure every lit sprite has at least one * MaterialEffect with `provides` covering it. * * Missing providers silently fall back to `channelDefaults` at runtime * (flat normals, etc.) which makes lighting look "off" without any * actionable signal. This helper logs a focused warning per sprite * identifying the specific missing channels. * * @param sprite If provided, validate only this sprite; otherwise walk * every sprite currently parented to the SpriteGroup. */ /** * Drain `_pendingChannelValidation` — runs sprite-by-sprite validation for * everything queued by `add()`. Called from `render()` so by the time * validation runs, R3F has finished mounting MaterialEffect children and * imperative callers have completed their `addEffect` chain. * * Public-by-name (with leading `_` to mark internal) so tests and headless * use cases can drain without a renderer. Production code never needs to * call this directly — `render()` handles it. * @internal */ _flushPendingChannelValidation(): void; private _validateLightingChannels; /** * Get the LightingContext data from the world singleton. */ private _getLightingContext; /** * Get the BatchRegistry data from the world singleton. */ private _getRegistry; /** * Render Flatland. */ render(renderer: WebGPURenderer): void; /** * Sync global uniforms from renderer state. * Called once per frame before rendering. */ private _syncGlobals; /** * Auto-initialize the render pipeline on first render if enabled, * and rebuild the pass chain when passes are added/removed. */ private _ensureRenderPipeline; /** Keep the pipeline's final color transform aligned with its destination. */ private _syncRenderPipelineOutputTransform; /** Apply Flatland's 2D-friendly default without overriding authored output. */ private _prepareRenderTarget; /** * Resize the rendering area, taking manual control of the aspect * ratio (the automatic per-render sync is disabled from here on). * * Zero, negative, or non-finite dimensions are ignored — a transient * unmeasured layout (R3F's first commit reports a 0×0 canvas) must * not latch a NaN/Infinity frustum, and must not disable the * automatic sync that will pick up the real size once it exists. */ resize(width: number, height: number): void; /** Guard against zero/negative/NaN dimensions. */ private _isValidSize; /** * Apply an explicit manual surface size. Dimensions are pre-validated. */ private _applyResize; /** * Queue a LightEffect resize for the lighting lifecycle system. Keeping * resize in the system guarantees init() runs first and preserves the * current surface size for effects attached after the first frame. */ private _queueLightEffectResize; /** * Track the physical render surface every frame: the render target's texel * dimensions when rendering to texture, otherwise the renderer's drawing * buffer size (logical canvas size multiplied by pixel ratio). * Camera aspect follows while auto mode is active; LightEffect sizing * follows unless resize() selected full manual size control because GPU * tile buffers remain surface-dependent when only camera aspect is pinned. */ private _syncSurfaceSize; /** * Clone for devtools/serialization compatibility. * Flatland manages internal scene, camera, and render pipeline that * cannot be meaningfully cloned. Returns a Group with cloned children. */ clone(recursive?: boolean): this; /** * Dispose of all resources. */ dispose(): void; } //#endregion export { Flatland, FlatlandOptions }; //# sourceMappingURL=Flatland.d.ts.map