import * as THREE from "three"; import type { StudioEnvironment, StudioBackground } from "../core/types.js"; import { type LightDetectionResult } from "./light-detection.js"; /** * Configuration options for EnvironmentManager. */ interface EnvironmentManagerOptions { /** Override URLs for the "neutral" and "outdoor" HDR presets */ presetUrls?: Partial>; } /** * Manages environment maps for Studio mode. * * Handles three tiers of environment sources: * - **Tier 1 "studio"**: Procedural RoomEnvironment (bundled, zero network) * - **Tier 2 "neutral"/"outdoor"**: HDR presets loaded from configurable CDN URLs * - **Tier 3 custom URL**: User-provided HDR URL (same loading path as Tier 2) * * The environment map is used for IBL (image-based lighting) via * `scene.environment`. The scene background is configurable via the * `backgroundMode` parameter in `apply()` (grey, white, gradient, * blurred environment, or transparent). * * Features: * - PMREM generation and caching for all tiers * - In-flight promise deduplication (prevents duplicate loads on rapid switching) * - Lazy PMREMGenerator creation * - Fallback to "studio" on HDR load failure * - GPU resource tracking via gpuTracker */ declare class EnvironmentManager { /** Cached PMREM render targets keyed by environment name or URL */ private _cache; /** Cached light detection results keyed by environment name or URL */ private _lightDetectionCache; /** In-flight load promises keyed by environment name or URL */ private _inflight; /** Lazily-created PMREMGenerator instance */ private _pmremGenerator; /** Resolved preset URLs (defaults merged with user overrides) */ private _presetUrls; /** Whether 4K env maps are enabled (default false = 2K) */ private _use4k; /** User-provided URL overrides from constructor */ private _userOverrides; /** HDRLoader instance (created lazily on first HDR load) */ private _hdrLoader; /** The last loaded PMREM texture (stateful — used by apply() for IBL) */ private _currentTexture; /** Whether this manager has been disposed */ private _disposed; /** * Ortho env background workaround. * * Three.js cannot render PMREM/cubemap textures as scene.background with * orthographic cameras (renders as a tiny rectangle). We work around this by * rendering the env map to a render target using a virtual perspective camera, * then setting that 2D texture as scene.background. A 2D texture background * renders as a fullscreen quad regardless of camera projection, and the * transmission pass (glass refraction) also sees it correctly. */ private _bgScene; private _bgCamera; private _bgRenderTarget; private _orthoEnvMainScene; /** Whether the env background feature is active (ortho + environment background). */ private _envBackgroundActive; /** * Deferred-apply state: if apply() was called with backgroundMode "environment" * while _currentTexture was null, store the arguments so loadEnvironment() can * re-apply once the texture is ready. */ private _deferredApply; constructor(options?: EnvironmentManagerOptions); /** * Load or retrieve an environment map. * * Resolves the environment name to a loading strategy: * - `"studio"` -- procedural RoomEnvironment via PMREMGenerator.fromScene() * - `"neutral"` / `"outdoor"` -- HDR preset from configured CDN URL * - `"none"` -- returns null (caller should call `remove()` instead) * - Any other string -- treated as a custom HDR URL * * Results are cached. If a load is already in flight for the same key, * the existing promise is returned (no duplicate loads). * * @param name - Environment preset name or custom HDR URL * @param renderer - WebGL renderer (needed for PMREMGenerator) * @returns PMREM texture, or null for "none" */ loadEnvironment(name: StudioEnvironment | string, renderer: THREE.WebGLRenderer): Promise; /** * Apply the current environment map to the scene. * * Sets `scene.environment` for PBR/IBL reflections and configures * `scene.background` according to the selected background mode: * - `"grey"`: Neutral grey color (default, clean product-shot look) * - `"white"`: Pure white background (e-commerce / documentation style) * - `"gradient"`: Radial vignette gradient (light grey center → darker edges) * - `"environment"`: Blurred, dimmed PMREM environment as backdrop * (color-matched to IBL, eliminates edge-glow artifacts on reflective objects) * - `"transparent"`: No background (canvas alpha shows through) * * @param scene - The Three.js scene to apply the environment to * @param envIntensity - Environment intensity multiplier (0-3, default 1.0) * @param backgroundMode - Background mode * @param upIsZ - Whether the scene uses Z-up coordinates (default true) * @param ortho - Whether the camera is orthographic (env background falls back to gradient) * @param envRotationDeg - Environment map rotation in degrees (default 0) */ apply(scene: THREE.Scene, envIntensity: number, backgroundMode?: StudioBackground, upIsZ?: boolean, ortho?: boolean, envRotationDeg?: number): void; /** * Remove environment map from the scene. * * Clears `scene.environment`, `scene.background`, and resets * environment/background properties to defaults. * * @param scene - The Three.js scene to clear */ remove(scene: THREE.Scene): void; /** * Switch between 2K and 4K environment map resolution. * * Rebuilds preset URLs, evicts cached HDR presets (so they reload at * the new resolution), and reloads the current environment if one is * active. * * @param use4k - True for 4K, false for 2K * @param currentEnvName - The currently active environment name (to reload) * @param renderer - WebGL renderer (needed for reload) * @returns Promise that resolves when the new texture is ready */ setUse4kEnvMaps(use4k: boolean, currentEnvName: string, renderer: THREE.WebGLRenderer): Promise; /** Whether 4K env maps are currently enabled. */ get use4kEnvMaps(): boolean; /** * Whether an environment name is a Poly Haven preset (resolution-switchable). * Returns false for "studio", "none", and custom URLs. */ isPreset(name: string): boolean; /** * Whether the render-to-texture env background path is currently active. * When true, the caller must call updateEnvBackground() each frame. */ get isEnvBackgroundActive(): boolean; /** * Get cached light detection result for an environment. * * @param envName - Environment name or URL (same key used in loadEnvironment) * @returns Detection result, or null if not yet analyzed */ getLightDetection(envName: string): LightDetectionResult | null; /** * Update the env background render target (ortho camera workaround). * * Renders the PMREM env map to a 2D render target using a fixed-FOV virtual * perspective camera whose quaternion is synced with the main camera. The * resulting 2D texture is set as the main scene's background, giving a * world-space environment that tracks camera orbit — matching how * scene.environment (IBL reflections) already behaves. * * Called every frame from the render loop when isEnvBackgroundActive is true. * Only active in ortho mode (perspective uses native cubemap background). * * @param renderer - WebGL renderer * @param mainCamera - The active camera whose orientation to match */ updateEnvBackground(renderer: THREE.WebGLRenderer, mainCamera?: THREE.Camera): void; /** * Dispose all cached resources. * * Disposes all cached PMREM render targets (and their textures) and * the PMREMGenerator. After disposal, this manager cannot be used again. * * Call this on `viewer.dispose()`, NOT on `viewer.clear()` -- * the EnvironmentManager survives shape data clearing because * environments are independent of shape data. */ dispose(): void; /** * Set up the env background: a separate scene with the PMREM texture * as background and a fixed-FOV virtual perspective camera for rendering * to a 2D target. Used only for ortho cameras (perspective uses native * cubemap background). */ private _setupEnvBackground; /** * Tear down the env background state. */ private _teardownEnvBackground; /** * Resolve environment name to an HDR URL, if applicable. * * Returns null for "studio" (uses RoomEnvironment, no URL). * Returns the preset URL for "neutral"/"outdoor". * Returns the name itself for custom URLs. */ private _resolveUrl; /** * Get or create the PMREMGenerator (lazy initialization). */ private _ensurePmremGenerator; /** * Get or create the HDRLoader (lazy initialization). */ private _ensureHdrLoader; /** * Internal load dispatcher. * * Routes to RoomEnvironment generation or HDR loading based on the name. * On HDR failure, falls back to "studio" (RoomEnvironment). */ private _load; /** * Generate PMREM texture from the procedural RoomEnvironment. * * This is synchronous (no network), fast (~70ms), and always available. */ private _loadRoomEnvironment; /** * Load an HDR file and generate a PMREM texture from it. * * Uses HDRLoader to fetch the .hdr file, then PMREMGenerator.fromEquirectangular() * to create the PMREM cubemap. The source equirectangular HDR is disposed * after PMREM generation. The PMREM texture itself serves as both the IBL * environment and the background (in "environment" mode). * * @param url - URL of the .hdr file * @param cacheKey - Cache key for the resulting PMREM render target * @param renderer - WebGL renderer for PMREMGenerator * @returns PMREM texture * @throws If the HDR file cannot be loaded */ private _loadHdr; } /** Dispose lazy-cached gradient textures (called from EnvironmentManager.dispose). */ declare function disposeGradientTextures(): void; export { EnvironmentManager, disposeGradientTextures }; export type { EnvironmentManagerOptions };