three-cad-viewer 4.3.4 → 4.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scene/clipping.d.ts +6 -0
- package/dist/three-cad-viewer.esm.js +20 -5
- package/dist/three-cad-viewer.esm.js.map +1 -1
- package/dist/three-cad-viewer.esm.min.js +1 -1
- package/dist/three-cad-viewer.js +20 -5
- package/dist/three-cad-viewer.min.js +1 -1
- package/package.json +2 -3
- package/src/_version.ts +0 -1
- package/src/camera/camera.ts +0 -445
- package/src/camera/controls/CADOrbitControls.ts +0 -241
- package/src/camera/controls/CADTrackballControls.ts +0 -598
- package/src/camera/controls.ts +0 -380
- package/src/core/patches.ts +0 -16
- package/src/core/studio-manager.ts +0 -652
- package/src/core/types.ts +0 -892
- package/src/core/viewer-state.ts +0 -784
- package/src/core/viewer.ts +0 -4821
- package/src/index.ts +0 -151
- package/src/rendering/environment.ts +0 -840
- package/src/rendering/light-detection.ts +0 -327
- package/src/rendering/material-factory.ts +0 -735
- package/src/rendering/material-presets.ts +0 -289
- package/src/rendering/raycast.ts +0 -291
- package/src/rendering/room-environment.ts +0 -192
- package/src/rendering/studio-composer.ts +0 -577
- package/src/rendering/studio-floor.ts +0 -108
- package/src/rendering/texture-cache.ts +0 -324
- package/src/rendering/tree-model.ts +0 -542
- package/src/rendering/triplanar.ts +0 -329
- package/src/scene/animation.ts +0 -343
- package/src/scene/axes.ts +0 -108
- package/src/scene/bbox.ts +0 -223
- package/src/scene/clipping.ts +0 -640
- package/src/scene/grid.ts +0 -864
- package/src/scene/nestedgroup.ts +0 -1444
- package/src/scene/objectgroup.ts +0 -866
- package/src/scene/orientation.ts +0 -259
- package/src/scene/render-shape.ts +0 -634
- package/src/tools/cad_tools/measure.ts +0 -811
- package/src/tools/cad_tools/select.ts +0 -100
- package/src/tools/cad_tools/tools.ts +0 -231
- package/src/tools/cad_tools/ui.ts +0 -454
- package/src/tools/cad_tools/zebra.ts +0 -369
- package/src/types/html.d.ts +0 -5
- package/src/types/n8ao.d.ts +0 -28
- package/src/types/three-augmentation.d.ts +0 -60
- package/src/ui/display.ts +0 -3295
- package/src/ui/index.html +0 -505
- package/src/ui/info.ts +0 -177
- package/src/ui/slider.ts +0 -206
- package/src/ui/toolbar.ts +0 -347
- package/src/ui/treeview.ts +0 -945
- package/src/utils/decode-instances.ts +0 -233
- package/src/utils/font.ts +0 -60
- package/src/utils/gpu-tracker.ts +0 -265
- package/src/utils/logger.ts +0 -92
- package/src/utils/sizeof.ts +0 -116
- package/src/utils/timer.ts +0 -69
- package/src/utils/utils.ts +0 -446
|
@@ -1,840 +0,0 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
|
-
import { CleanRoomEnvironment } from "./room-environment.js";
|
|
3
|
-
import { HDRLoader } from "three/examples/jsm/loaders/HDRLoader.js";
|
|
4
|
-
import type { StudioEnvironment, StudioBackground } from "../core/types.js";
|
|
5
|
-
import { gpuTracker } from "../utils/gpu-tracker.js";
|
|
6
|
-
import { logger } from "../utils/logger.js";
|
|
7
|
-
import {
|
|
8
|
-
detectDominantLights,
|
|
9
|
-
getDefaultLights,
|
|
10
|
-
type LightDetectionResult,
|
|
11
|
-
} from "./light-detection.js";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Neutral grey background color for Studio mode.
|
|
15
|
-
* A medium grey that works for both light and dark objects, and provides
|
|
16
|
-
* a sensible backdrop for transmission/glass materials.
|
|
17
|
-
*/
|
|
18
|
-
const STUDIO_BACKGROUND_GREY = new THREE.Color(0.18, 0.18, 0.18);
|
|
19
|
-
|
|
20
|
-
/** Scratch vector for per-frame getDrawingBufferSize() calls. */
|
|
21
|
-
const _bgSizeVec = new THREE.Vector2();
|
|
22
|
-
|
|
23
|
-
/** Dark grey background for high-contrast viewing of light materials. */
|
|
24
|
-
const STUDIO_BACKGROUND_DARKGREY = new THREE.Color(0.03, 0.03, 0.03);
|
|
25
|
-
|
|
26
|
-
/** White background color for clean product shots. */
|
|
27
|
-
const STUDIO_BACKGROUND_WHITE = new THREE.Color(1.0, 1.0, 1.0);
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Create a radial vignette gradient texture (512×512 canvas).
|
|
31
|
-
* Light center fading to darker edges.
|
|
32
|
-
*/
|
|
33
|
-
function _createGradientTexture(centerColor: string, edgeColor: string): THREE.Texture {
|
|
34
|
-
const size = 512;
|
|
35
|
-
const canvas = document.createElement("canvas");
|
|
36
|
-
canvas.width = size;
|
|
37
|
-
canvas.height = size;
|
|
38
|
-
const ctx = canvas.getContext("2d")!;
|
|
39
|
-
|
|
40
|
-
const cx = size / 2;
|
|
41
|
-
const cy = size / 2;
|
|
42
|
-
const radius = size * 0.7;
|
|
43
|
-
const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius);
|
|
44
|
-
gradient.addColorStop(0, centerColor);
|
|
45
|
-
gradient.addColorStop(1, edgeColor);
|
|
46
|
-
|
|
47
|
-
ctx.fillStyle = edgeColor;
|
|
48
|
-
ctx.fillRect(0, 0, size, size);
|
|
49
|
-
ctx.fillStyle = gradient;
|
|
50
|
-
ctx.fillRect(0, 0, size, size);
|
|
51
|
-
|
|
52
|
-
const texture = new THREE.CanvasTexture(canvas);
|
|
53
|
-
texture.colorSpace = THREE.SRGBColorSpace;
|
|
54
|
-
return texture;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Lazy-cached gradient textures */
|
|
58
|
-
let _gradientTexture: THREE.Texture | null = null;
|
|
59
|
-
let _gradientDarkTexture: THREE.Texture | null = null;
|
|
60
|
-
|
|
61
|
-
function _getGradientTexture(): THREE.Texture {
|
|
62
|
-
if (!_gradientTexture) _gradientTexture = _createGradientTexture("#f0f0f0", "#c8c8c8");
|
|
63
|
-
return _gradientTexture;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function _getGradientDarkTexture(): THREE.Texture {
|
|
67
|
-
if (!_gradientDarkTexture) _gradientDarkTexture = _createGradientTexture("#808080", "#606060");
|
|
68
|
-
return _gradientDarkTexture;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Poly Haven CDN base URL for HDR files.
|
|
73
|
-
* Pattern: {BASE}/{resolution}/{slug}_{resolution}.hdr
|
|
74
|
-
*/
|
|
75
|
-
const PH_BASE = "https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr";
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Default HDR preset slugs (Poly Haven asset names).
|
|
79
|
-
*
|
|
80
|
-
* All maps from Poly Haven (CC0 license, permissive CORS).
|
|
81
|
-
* Resolution is selected at runtime via `use4kEnvMaps`.
|
|
82
|
-
* Host applications can override URLs via the `presetUrls` constructor option.
|
|
83
|
-
*
|
|
84
|
-
* Curated for CAD/product visualization: neutral white studios that make
|
|
85
|
-
* every material look good, plus two overcast outdoor options for context.
|
|
86
|
-
*/
|
|
87
|
-
const DEFAULT_PRESET_SLUGS: string[] = [
|
|
88
|
-
// --- Studio: Soft / neutral ---
|
|
89
|
-
"studio_small_08",
|
|
90
|
-
"studio_small_03",
|
|
91
|
-
"white_studio_05",
|
|
92
|
-
"white_studio_03",
|
|
93
|
-
"photo_studio_01",
|
|
94
|
-
"studio_small_09",
|
|
95
|
-
"cyclorama_hard_light",
|
|
96
|
-
|
|
97
|
-
// --- Outdoor ---
|
|
98
|
-
"canary_wharf",
|
|
99
|
-
"kiara_1_dawn",
|
|
100
|
-
"empty_warehouse_01",
|
|
101
|
-
"san_giuseppe_bridge",
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
/** Build preset URL map for the given resolution tier. */
|
|
105
|
-
function _buildPresetUrls(use4k: boolean): Record<string, string> {
|
|
106
|
-
const res = use4k ? "4k" : "2k";
|
|
107
|
-
const urls: Record<string, string> = {};
|
|
108
|
-
for (const slug of DEFAULT_PRESET_SLUGS) {
|
|
109
|
-
urls[slug] = `${PH_BASE}/${res}/${slug}_${res}.hdr`;
|
|
110
|
-
}
|
|
111
|
-
return urls;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Configuration options for EnvironmentManager.
|
|
116
|
-
*/
|
|
117
|
-
interface EnvironmentManagerOptions {
|
|
118
|
-
/** Override URLs for the "neutral" and "outdoor" HDR presets */
|
|
119
|
-
presetUrls?: Partial<Record<string, string>>;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Manages environment maps for Studio mode.
|
|
124
|
-
*
|
|
125
|
-
* Handles three tiers of environment sources:
|
|
126
|
-
* - **Tier 1 "studio"**: Procedural RoomEnvironment (bundled, zero network)
|
|
127
|
-
* - **Tier 2 "neutral"/"outdoor"**: HDR presets loaded from configurable CDN URLs
|
|
128
|
-
* - **Tier 3 custom URL**: User-provided HDR URL (same loading path as Tier 2)
|
|
129
|
-
*
|
|
130
|
-
* The environment map is used for IBL (image-based lighting) via
|
|
131
|
-
* `scene.environment`. The scene background is configurable via the
|
|
132
|
-
* `backgroundMode` parameter in `apply()` (grey, white, gradient,
|
|
133
|
-
* blurred environment, or transparent).
|
|
134
|
-
*
|
|
135
|
-
* Features:
|
|
136
|
-
* - PMREM generation and caching for all tiers
|
|
137
|
-
* - In-flight promise deduplication (prevents duplicate loads on rapid switching)
|
|
138
|
-
* - Lazy PMREMGenerator creation
|
|
139
|
-
* - Fallback to "studio" on HDR load failure
|
|
140
|
-
* - GPU resource tracking via gpuTracker
|
|
141
|
-
*/
|
|
142
|
-
class EnvironmentManager {
|
|
143
|
-
/** Cached PMREM render targets keyed by environment name or URL */
|
|
144
|
-
private _cache: Map<string, THREE.WebGLRenderTarget> = new Map();
|
|
145
|
-
|
|
146
|
-
/** Cached light detection results keyed by environment name or URL */
|
|
147
|
-
private _lightDetectionCache: Map<string, LightDetectionResult> = new Map();
|
|
148
|
-
|
|
149
|
-
/** In-flight load promises keyed by environment name or URL */
|
|
150
|
-
private _inflight: Map<string, Promise<THREE.Texture>> = new Map();
|
|
151
|
-
|
|
152
|
-
/** Lazily-created PMREMGenerator instance */
|
|
153
|
-
private _pmremGenerator: THREE.PMREMGenerator | null = null;
|
|
154
|
-
|
|
155
|
-
/** Resolved preset URLs (defaults merged with user overrides) */
|
|
156
|
-
private _presetUrls: Record<string, string>;
|
|
157
|
-
|
|
158
|
-
/** Whether 4K env maps are enabled (default false = 2K) */
|
|
159
|
-
private _use4k: boolean = false;
|
|
160
|
-
|
|
161
|
-
/** User-provided URL overrides from constructor */
|
|
162
|
-
private _userOverrides: Record<string, string> = {};
|
|
163
|
-
|
|
164
|
-
/** HDRLoader instance (created lazily on first HDR load) */
|
|
165
|
-
private _hdrLoader: HDRLoader | null = null;
|
|
166
|
-
|
|
167
|
-
/** The last loaded PMREM texture (stateful — used by apply() for IBL) */
|
|
168
|
-
private _currentTexture: THREE.Texture | null = null;
|
|
169
|
-
|
|
170
|
-
/** Whether this manager has been disposed */
|
|
171
|
-
private _disposed = false;
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Ortho env background workaround.
|
|
175
|
-
*
|
|
176
|
-
* Three.js cannot render PMREM/cubemap textures as scene.background with
|
|
177
|
-
* orthographic cameras (renders as a tiny rectangle). We work around this by
|
|
178
|
-
* rendering the env map to a render target using a virtual perspective camera,
|
|
179
|
-
* then setting that 2D texture as scene.background. A 2D texture background
|
|
180
|
-
* renders as a fullscreen quad regardless of camera projection, and the
|
|
181
|
-
* transmission pass (glass refraction) also sees it correctly.
|
|
182
|
-
*/
|
|
183
|
-
private _bgScene: THREE.Scene | null = null;
|
|
184
|
-
private _bgCamera: THREE.PerspectiveCamera | null = null;
|
|
185
|
-
private _bgRenderTarget: THREE.WebGLRenderTarget | null = null;
|
|
186
|
-
private _orthoEnvMainScene: THREE.Scene | null = null;
|
|
187
|
-
|
|
188
|
-
/** Whether the env background feature is active (ortho + environment background). */
|
|
189
|
-
private _envBackgroundActive: boolean = false;
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Deferred-apply state: if apply() was called with backgroundMode "environment"
|
|
193
|
-
* while _currentTexture was null, store the arguments so loadEnvironment() can
|
|
194
|
-
* re-apply once the texture is ready.
|
|
195
|
-
*/
|
|
196
|
-
private _deferredApply: {
|
|
197
|
-
scene: THREE.Scene;
|
|
198
|
-
envIntensity: number;
|
|
199
|
-
upIsZ: boolean;
|
|
200
|
-
ortho: boolean;
|
|
201
|
-
} | null = null;
|
|
202
|
-
|
|
203
|
-
constructor(options: EnvironmentManagerOptions = {}) {
|
|
204
|
-
this._userOverrides = (options.presetUrls as Record<string, string> | undefined) ?? {};
|
|
205
|
-
this._presetUrls = {
|
|
206
|
-
..._buildPresetUrls(false),
|
|
207
|
-
...this._userOverrides,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Load or retrieve an environment map.
|
|
213
|
-
*
|
|
214
|
-
* Resolves the environment name to a loading strategy:
|
|
215
|
-
* - `"studio"` -- procedural RoomEnvironment via PMREMGenerator.fromScene()
|
|
216
|
-
* - `"neutral"` / `"outdoor"` -- HDR preset from configured CDN URL
|
|
217
|
-
* - `"none"` -- returns null (caller should call `remove()` instead)
|
|
218
|
-
* - Any other string -- treated as a custom HDR URL
|
|
219
|
-
*
|
|
220
|
-
* Results are cached. If a load is already in flight for the same key,
|
|
221
|
-
* the existing promise is returned (no duplicate loads).
|
|
222
|
-
*
|
|
223
|
-
* @param name - Environment preset name or custom HDR URL
|
|
224
|
-
* @param renderer - WebGL renderer (needed for PMREMGenerator)
|
|
225
|
-
* @returns PMREM texture, or null for "none"
|
|
226
|
-
*/
|
|
227
|
-
async loadEnvironment(
|
|
228
|
-
name: StudioEnvironment | string,
|
|
229
|
-
renderer: THREE.WebGLRenderer,
|
|
230
|
-
): Promise<THREE.Texture | null> {
|
|
231
|
-
if (this._disposed) {
|
|
232
|
-
logger.warn("EnvironmentManager.loadEnvironment() called after dispose");
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (name === "none") {
|
|
237
|
-
this._currentTexture = null;
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Check cache first (name is the cache key for presets; URL string for custom)
|
|
242
|
-
const cacheKey = name;
|
|
243
|
-
const cached = this._cache.get(cacheKey);
|
|
244
|
-
if (cached) {
|
|
245
|
-
logger.debug(`Environment "${cacheKey}" loaded from cache`);
|
|
246
|
-
this._currentTexture = cached.texture;
|
|
247
|
-
return cached.texture;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Check in-flight promise — await and set _currentTexture
|
|
251
|
-
const inflight = this._inflight.get(cacheKey);
|
|
252
|
-
if (inflight) {
|
|
253
|
-
logger.debug(`Environment "${cacheKey}" already loading, reusing promise`);
|
|
254
|
-
const texture = await inflight;
|
|
255
|
-
this._currentTexture = texture;
|
|
256
|
-
return texture;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Start new load
|
|
260
|
-
const promise = this._load(name, renderer);
|
|
261
|
-
this._inflight.set(cacheKey, promise);
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
const texture = await promise;
|
|
265
|
-
this._currentTexture = texture;
|
|
266
|
-
|
|
267
|
-
// Self-healing: if apply() was called with "environment" background
|
|
268
|
-
// while texture was null, re-apply now that the texture is ready.
|
|
269
|
-
if (this._deferredApply) {
|
|
270
|
-
const { scene, envIntensity, upIsZ, ortho } = this._deferredApply;
|
|
271
|
-
this._deferredApply = null;
|
|
272
|
-
this.apply(scene, envIntensity, "environment", upIsZ, ortho);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return texture;
|
|
276
|
-
} finally {
|
|
277
|
-
this._inflight.delete(cacheKey);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Apply the current environment map to the scene.
|
|
283
|
-
*
|
|
284
|
-
* Sets `scene.environment` for PBR/IBL reflections and configures
|
|
285
|
-
* `scene.background` according to the selected background mode:
|
|
286
|
-
* - `"grey"`: Neutral grey color (default, clean product-shot look)
|
|
287
|
-
* - `"white"`: Pure white background (e-commerce / documentation style)
|
|
288
|
-
* - `"gradient"`: Radial vignette gradient (light grey center → darker edges)
|
|
289
|
-
* - `"environment"`: Blurred, dimmed PMREM environment as backdrop
|
|
290
|
-
* (color-matched to IBL, eliminates edge-glow artifacts on reflective objects)
|
|
291
|
-
* - `"transparent"`: No background (canvas alpha shows through)
|
|
292
|
-
*
|
|
293
|
-
* @param scene - The Three.js scene to apply the environment to
|
|
294
|
-
* @param envIntensity - Environment intensity multiplier (0-3, default 1.0)
|
|
295
|
-
* @param backgroundMode - Background mode
|
|
296
|
-
* @param upIsZ - Whether the scene uses Z-up coordinates (default true)
|
|
297
|
-
* @param ortho - Whether the camera is orthographic (env background falls back to gradient)
|
|
298
|
-
* @param envRotationDeg - Environment map rotation in degrees (default 0)
|
|
299
|
-
*/
|
|
300
|
-
apply(
|
|
301
|
-
scene: THREE.Scene,
|
|
302
|
-
envIntensity: number,
|
|
303
|
-
backgroundMode: StudioBackground = "grey",
|
|
304
|
-
upIsZ: boolean = true,
|
|
305
|
-
ortho: boolean = false,
|
|
306
|
-
envRotationDeg: number = 0,
|
|
307
|
-
): void {
|
|
308
|
-
const rotY = (envRotationDeg * Math.PI) / 180;
|
|
309
|
-
if (this._currentTexture) {
|
|
310
|
-
scene.environment = this._currentTexture;
|
|
311
|
-
scene.environmentIntensity = envIntensity;
|
|
312
|
-
// HDR maps assume Y-up; rotate 90° around X to align with Z-up scenes.
|
|
313
|
-
// Additional rotation for user-controlled azimuthal rotation.
|
|
314
|
-
if (upIsZ) {
|
|
315
|
-
scene.environmentRotation.set(Math.PI / 2, 0, rotY);
|
|
316
|
-
} else {
|
|
317
|
-
scene.environmentRotation.set(0, rotY, 0);
|
|
318
|
-
}
|
|
319
|
-
} else {
|
|
320
|
-
scene.environment = null;
|
|
321
|
-
scene.environmentIntensity = 1.0;
|
|
322
|
-
scene.environmentRotation.set(0, 0, 0);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Clear deferred-apply if switching away from "environment" background
|
|
326
|
-
if (backgroundMode !== "environment") {
|
|
327
|
-
this._deferredApply = null;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Configure background based on mode
|
|
331
|
-
switch (backgroundMode) {
|
|
332
|
-
case "white":
|
|
333
|
-
scene.background = STUDIO_BACKGROUND_WHITE;
|
|
334
|
-
scene.backgroundIntensity = 1.0;
|
|
335
|
-
scene.backgroundBlurriness = 0;
|
|
336
|
-
this._teardownEnvBackground();
|
|
337
|
-
break;
|
|
338
|
-
case "gradient":
|
|
339
|
-
scene.background = _getGradientTexture();
|
|
340
|
-
scene.backgroundIntensity = 1.0;
|
|
341
|
-
scene.backgroundBlurriness = 0;
|
|
342
|
-
this._teardownEnvBackground();
|
|
343
|
-
break;
|
|
344
|
-
case "gradient-dark":
|
|
345
|
-
scene.background = _getGradientDarkTexture();
|
|
346
|
-
scene.backgroundIntensity = 1.0;
|
|
347
|
-
scene.backgroundBlurriness = 0;
|
|
348
|
-
this._teardownEnvBackground();
|
|
349
|
-
break;
|
|
350
|
-
case "environment":
|
|
351
|
-
if (this._currentTexture) {
|
|
352
|
-
// Always use render-to-texture with a fixed-FOV bgCamera so the
|
|
353
|
-
// background zoom level is identical in perspective and ortho modes.
|
|
354
|
-
this._setupEnvBackground(scene, this._currentTexture, upIsZ, rotY);
|
|
355
|
-
this._deferredApply = null;
|
|
356
|
-
} else {
|
|
357
|
-
// No environment loaded — fall back to grey.
|
|
358
|
-
// Record deferred-apply so loadEnvironment() can re-apply once ready.
|
|
359
|
-
this._deferredApply = { scene, envIntensity, upIsZ, ortho };
|
|
360
|
-
scene.background = STUDIO_BACKGROUND_GREY;
|
|
361
|
-
scene.backgroundIntensity = 1.0;
|
|
362
|
-
scene.backgroundBlurriness = 0;
|
|
363
|
-
this._teardownEnvBackground();
|
|
364
|
-
}
|
|
365
|
-
break;
|
|
366
|
-
case "transparent":
|
|
367
|
-
scene.background = null;
|
|
368
|
-
scene.backgroundIntensity = 1.0;
|
|
369
|
-
scene.backgroundBlurriness = 0;
|
|
370
|
-
this._teardownEnvBackground();
|
|
371
|
-
break;
|
|
372
|
-
case "darkgrey":
|
|
373
|
-
scene.background = STUDIO_BACKGROUND_DARKGREY;
|
|
374
|
-
scene.backgroundIntensity = 1.0;
|
|
375
|
-
scene.backgroundBlurriness = 0;
|
|
376
|
-
this._teardownEnvBackground();
|
|
377
|
-
break;
|
|
378
|
-
case "grey":
|
|
379
|
-
default:
|
|
380
|
-
scene.background = STUDIO_BACKGROUND_GREY;
|
|
381
|
-
scene.backgroundIntensity = 1.0;
|
|
382
|
-
scene.backgroundBlurriness = 0;
|
|
383
|
-
this._teardownEnvBackground();
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Remove environment map from the scene.
|
|
391
|
-
*
|
|
392
|
-
* Clears `scene.environment`, `scene.background`, and resets
|
|
393
|
-
* environment/background properties to defaults.
|
|
394
|
-
*
|
|
395
|
-
* @param scene - The Three.js scene to clear
|
|
396
|
-
*/
|
|
397
|
-
remove(scene: THREE.Scene): void {
|
|
398
|
-
this._deferredApply = null;
|
|
399
|
-
this._teardownEnvBackground();
|
|
400
|
-
scene.environment = null;
|
|
401
|
-
scene.background = null;
|
|
402
|
-
scene.environmentIntensity = 1.0;
|
|
403
|
-
scene.environmentRotation.set(0, 0, 0);
|
|
404
|
-
scene.backgroundIntensity = 1.0;
|
|
405
|
-
scene.backgroundBlurriness = 0;
|
|
406
|
-
scene.backgroundRotation.set(0, 0, 0);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Switch between 2K and 4K environment map resolution.
|
|
411
|
-
*
|
|
412
|
-
* Rebuilds preset URLs, evicts cached HDR presets (so they reload at
|
|
413
|
-
* the new resolution), and reloads the current environment if one is
|
|
414
|
-
* active.
|
|
415
|
-
*
|
|
416
|
-
* @param use4k - True for 4K, false for 2K
|
|
417
|
-
* @param currentEnvName - The currently active environment name (to reload)
|
|
418
|
-
* @param renderer - WebGL renderer (needed for reload)
|
|
419
|
-
* @returns Promise that resolves when the new texture is ready
|
|
420
|
-
*/
|
|
421
|
-
async setUse4kEnvMaps(
|
|
422
|
-
use4k: boolean,
|
|
423
|
-
currentEnvName: string,
|
|
424
|
-
renderer: THREE.WebGLRenderer,
|
|
425
|
-
): Promise<THREE.Texture | null> {
|
|
426
|
-
if (use4k === this._use4k) return this._currentTexture;
|
|
427
|
-
this._use4k = use4k;
|
|
428
|
-
|
|
429
|
-
// Rebuild preset URLs at the new resolution
|
|
430
|
-
this._presetUrls = {
|
|
431
|
-
..._buildPresetUrls(use4k),
|
|
432
|
-
...this._userOverrides,
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
// Evict cached HDR presets (they point to the old resolution).
|
|
436
|
-
// "studio" (RoomEnvironment) is procedural and unaffected.
|
|
437
|
-
for (const slug of DEFAULT_PRESET_SLUGS) {
|
|
438
|
-
const cached = this._cache.get(slug);
|
|
439
|
-
if (cached) {
|
|
440
|
-
gpuTracker.untrack("texture", cached.texture);
|
|
441
|
-
cached.dispose();
|
|
442
|
-
this._cache.delete(slug);
|
|
443
|
-
this._lightDetectionCache.delete(slug);
|
|
444
|
-
logger.debug(`Evicted cached environment "${slug}" for resolution switch`);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Reload the current environment at the new resolution
|
|
449
|
-
if (currentEnvName && currentEnvName !== "none" && currentEnvName !== "studio") {
|
|
450
|
-
return this.loadEnvironment(currentEnvName, renderer);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
return this._currentTexture;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/** Whether 4K env maps are currently enabled. */
|
|
457
|
-
get use4kEnvMaps(): boolean {
|
|
458
|
-
return this._use4k;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Whether an environment name is a Poly Haven preset (resolution-switchable).
|
|
463
|
-
* Returns false for "studio", "none", and custom URLs.
|
|
464
|
-
*/
|
|
465
|
-
isPreset(name: string): boolean {
|
|
466
|
-
return DEFAULT_PRESET_SLUGS.includes(name);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Whether the render-to-texture env background path is currently active.
|
|
471
|
-
* When true, the caller must call updateEnvBackground() each frame.
|
|
472
|
-
*/
|
|
473
|
-
get isEnvBackgroundActive(): boolean {
|
|
474
|
-
return this._envBackgroundActive;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Get cached light detection result for an environment.
|
|
479
|
-
*
|
|
480
|
-
* @param envName - Environment name or URL (same key used in loadEnvironment)
|
|
481
|
-
* @returns Detection result, or null if not yet analyzed
|
|
482
|
-
*/
|
|
483
|
-
getLightDetection(envName: string): LightDetectionResult | null {
|
|
484
|
-
return this._lightDetectionCache.get(envName) ?? null;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Update the env background render target (ortho camera workaround).
|
|
489
|
-
*
|
|
490
|
-
* Renders the PMREM env map to a 2D render target using a fixed-FOV virtual
|
|
491
|
-
* perspective camera whose quaternion is synced with the main camera. The
|
|
492
|
-
* resulting 2D texture is set as the main scene's background, giving a
|
|
493
|
-
* world-space environment that tracks camera orbit — matching how
|
|
494
|
-
* scene.environment (IBL reflections) already behaves.
|
|
495
|
-
*
|
|
496
|
-
* Called every frame from the render loop when isEnvBackgroundActive is true.
|
|
497
|
-
* Only active in ortho mode (perspective uses native cubemap background).
|
|
498
|
-
*
|
|
499
|
-
* @param renderer - WebGL renderer
|
|
500
|
-
* @param mainCamera - The active camera whose orientation to match
|
|
501
|
-
*/
|
|
502
|
-
updateEnvBackground(
|
|
503
|
-
renderer: THREE.WebGLRenderer,
|
|
504
|
-
mainCamera?: THREE.Camera,
|
|
505
|
-
): void {
|
|
506
|
-
if (
|
|
507
|
-
!this._envBackgroundActive ||
|
|
508
|
-
!this._bgScene ||
|
|
509
|
-
!this._bgCamera ||
|
|
510
|
-
!this._orthoEnvMainScene
|
|
511
|
-
) {
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// Match viewport size for the render target
|
|
516
|
-
const size = renderer.getDrawingBufferSize(_bgSizeVec);
|
|
517
|
-
const w = size.x;
|
|
518
|
-
const h = size.y;
|
|
519
|
-
|
|
520
|
-
if (!this._bgRenderTarget || this._bgRenderTarget.width !== w || this._bgRenderTarget.height !== h) {
|
|
521
|
-
this._bgRenderTarget?.dispose();
|
|
522
|
-
this._bgRenderTarget = new THREE.WebGLRenderTarget(w, h);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Match viewport aspect ratio
|
|
526
|
-
const aspect = w / h;
|
|
527
|
-
if (this._bgCamera.aspect !== aspect) {
|
|
528
|
-
this._bgCamera.aspect = aspect;
|
|
529
|
-
this._bgCamera.updateProjectionMatrix();
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Sync bgCamera orientation with the main camera so the background
|
|
533
|
-
// rotates with orbit, matching the world-space IBL reflections.
|
|
534
|
-
if (mainCamera) {
|
|
535
|
-
this._bgCamera.quaternion.copy(mainCamera.quaternion);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Render env background to the render target
|
|
539
|
-
renderer.setRenderTarget(this._bgRenderTarget);
|
|
540
|
-
renderer.clear();
|
|
541
|
-
renderer.render(this._bgScene, this._bgCamera);
|
|
542
|
-
renderer.setRenderTarget(null);
|
|
543
|
-
|
|
544
|
-
// Set the 2D texture as the main scene's background
|
|
545
|
-
this._orthoEnvMainScene.background = this._bgRenderTarget.texture;
|
|
546
|
-
this._orthoEnvMainScene.backgroundIntensity = 1.0;
|
|
547
|
-
this._orthoEnvMainScene.backgroundBlurriness = 0;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* Dispose all cached resources.
|
|
552
|
-
*
|
|
553
|
-
* Disposes all cached PMREM render targets (and their textures) and
|
|
554
|
-
* the PMREMGenerator. After disposal, this manager cannot be used again.
|
|
555
|
-
*
|
|
556
|
-
* Call this on `viewer.dispose()`, NOT on `viewer.clear()` --
|
|
557
|
-
* the EnvironmentManager survives shape data clearing because
|
|
558
|
-
* environments are independent of shape data.
|
|
559
|
-
*/
|
|
560
|
-
dispose(): void {
|
|
561
|
-
this._disposed = true;
|
|
562
|
-
this._currentTexture = null;
|
|
563
|
-
this._deferredApply = null;
|
|
564
|
-
this._teardownEnvBackground();
|
|
565
|
-
this._bgScene = null;
|
|
566
|
-
this._bgCamera = null;
|
|
567
|
-
if (this._bgRenderTarget) {
|
|
568
|
-
this._bgRenderTarget.dispose();
|
|
569
|
-
this._bgRenderTarget = null;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Dispose all cached PMREM render targets (disposes textures too)
|
|
573
|
-
for (const [key, renderTarget] of this._cache) {
|
|
574
|
-
gpuTracker.untrack("texture", renderTarget.texture);
|
|
575
|
-
renderTarget.dispose();
|
|
576
|
-
logger.debug(`Disposed cached environment render target: ${key}`);
|
|
577
|
-
}
|
|
578
|
-
this._cache.clear();
|
|
579
|
-
this._lightDetectionCache.clear();
|
|
580
|
-
|
|
581
|
-
// Clear in-flight promises (they'll resolve but won't be cached)
|
|
582
|
-
this._inflight.clear();
|
|
583
|
-
|
|
584
|
-
// Dispose PMREMGenerator
|
|
585
|
-
if (this._pmremGenerator) {
|
|
586
|
-
this._pmremGenerator.dispose();
|
|
587
|
-
this._pmremGenerator = null;
|
|
588
|
-
logger.debug("Disposed PMREMGenerator");
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Null HDR loader reference (no explicit dispose needed)
|
|
592
|
-
this._hdrLoader = null;
|
|
593
|
-
|
|
594
|
-
// Dispose module-level gradient textures
|
|
595
|
-
disposeGradientTextures();
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// ---------------------------------------------------------------------------
|
|
599
|
-
// Private helpers
|
|
600
|
-
// ---------------------------------------------------------------------------
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* Set up the env background: a separate scene with the PMREM texture
|
|
604
|
-
* as background and a fixed-FOV virtual perspective camera for rendering
|
|
605
|
-
* to a 2D target. Used only for ortho cameras (perspective uses native
|
|
606
|
-
* cubemap background).
|
|
607
|
-
*/
|
|
608
|
-
private _setupEnvBackground(
|
|
609
|
-
mainScene: THREE.Scene,
|
|
610
|
-
texture: THREE.Texture,
|
|
611
|
-
upIsZ: boolean,
|
|
612
|
-
rotY: number = 0,
|
|
613
|
-
): void {
|
|
614
|
-
if (!this._bgScene) {
|
|
615
|
-
this._bgScene = new THREE.Scene();
|
|
616
|
-
}
|
|
617
|
-
if (!this._bgCamera) {
|
|
618
|
-
this._bgCamera = new THREE.PerspectiveCamera(50, 1, 0.1, 10);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// bgCamera orientation is synced with the main camera in
|
|
622
|
-
// updateEnvBackground() — no fixed rotation needed here.
|
|
623
|
-
|
|
624
|
-
this._bgScene.background = texture;
|
|
625
|
-
this._bgScene.backgroundIntensity = 1.0;
|
|
626
|
-
this._bgScene.backgroundBlurriness = 0;
|
|
627
|
-
if (upIsZ) {
|
|
628
|
-
this._bgScene.backgroundRotation.set(Math.PI / 2, 0, rotY);
|
|
629
|
-
} else {
|
|
630
|
-
this._bgScene.backgroundRotation.set(0, rotY, 0);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
this._orthoEnvMainScene = mainScene;
|
|
634
|
-
this._envBackgroundActive = true;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
/**
|
|
638
|
-
* Tear down the env background state.
|
|
639
|
-
*/
|
|
640
|
-
private _teardownEnvBackground(): void {
|
|
641
|
-
this._envBackgroundActive = false;
|
|
642
|
-
this._orthoEnvMainScene = null;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* Resolve environment name to an HDR URL, if applicable.
|
|
647
|
-
*
|
|
648
|
-
* Returns null for "studio" (uses RoomEnvironment, no URL).
|
|
649
|
-
* Returns the preset URL for "neutral"/"outdoor".
|
|
650
|
-
* Returns the name itself for custom URLs.
|
|
651
|
-
*/
|
|
652
|
-
private _resolveUrl(name: string): string | null {
|
|
653
|
-
if (name === "studio") {
|
|
654
|
-
return null;
|
|
655
|
-
}
|
|
656
|
-
const presetUrl = this._presetUrls[name];
|
|
657
|
-
if (presetUrl) {
|
|
658
|
-
return presetUrl;
|
|
659
|
-
}
|
|
660
|
-
return name;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Get or create the PMREMGenerator (lazy initialization).
|
|
665
|
-
*/
|
|
666
|
-
private _ensurePmremGenerator(
|
|
667
|
-
renderer: THREE.WebGLRenderer,
|
|
668
|
-
): THREE.PMREMGenerator {
|
|
669
|
-
if (!this._pmremGenerator) {
|
|
670
|
-
this._pmremGenerator = new THREE.PMREMGenerator(renderer);
|
|
671
|
-
logger.debug("Created PMREMGenerator");
|
|
672
|
-
}
|
|
673
|
-
return this._pmremGenerator;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
/**
|
|
677
|
-
* Get or create the HDRLoader (lazy initialization).
|
|
678
|
-
*/
|
|
679
|
-
private _ensureHdrLoader(): HDRLoader {
|
|
680
|
-
if (!this._hdrLoader) {
|
|
681
|
-
this._hdrLoader = new HDRLoader();
|
|
682
|
-
logger.debug("Created HDRLoader");
|
|
683
|
-
}
|
|
684
|
-
return this._hdrLoader;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/**
|
|
688
|
-
* Internal load dispatcher.
|
|
689
|
-
*
|
|
690
|
-
* Routes to RoomEnvironment generation or HDR loading based on the name.
|
|
691
|
-
* On HDR failure, falls back to "studio" (RoomEnvironment).
|
|
692
|
-
*/
|
|
693
|
-
private async _load(
|
|
694
|
-
name: string,
|
|
695
|
-
renderer: THREE.WebGLRenderer,
|
|
696
|
-
): Promise<THREE.Texture> {
|
|
697
|
-
if (name === "studio") {
|
|
698
|
-
return this._loadRoomEnvironment(name, renderer);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
const url = this._resolveUrl(name)!;
|
|
702
|
-
|
|
703
|
-
try {
|
|
704
|
-
return await this._loadHdr(url, name, renderer);
|
|
705
|
-
} catch (error) {
|
|
706
|
-
if (this._disposed) throw error;
|
|
707
|
-
const displayName =
|
|
708
|
-
name in this._presetUrls ? name : `custom URL (${url})`;
|
|
709
|
-
logger.warn(
|
|
710
|
-
`Could not load environment "${displayName}", using default "studio" environment.`,
|
|
711
|
-
error instanceof Error ? error.message : error,
|
|
712
|
-
);
|
|
713
|
-
// Fall back to RoomEnvironment
|
|
714
|
-
return this._loadRoomEnvironment("studio", renderer);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
/**
|
|
719
|
-
* Generate PMREM texture from the procedural RoomEnvironment.
|
|
720
|
-
*
|
|
721
|
-
* This is synchronous (no network), fast (~70ms), and always available.
|
|
722
|
-
*/
|
|
723
|
-
private _loadRoomEnvironment(
|
|
724
|
-
cacheKey: string,
|
|
725
|
-
renderer: THREE.WebGLRenderer,
|
|
726
|
-
): THREE.Texture {
|
|
727
|
-
if (this._disposed) {
|
|
728
|
-
throw new Error("EnvironmentManager was disposed");
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// Check cache again (fallback path may re-enter with "studio" key)
|
|
732
|
-
const cached = this._cache.get(cacheKey);
|
|
733
|
-
if (cached) {
|
|
734
|
-
return cached.texture;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
const pmremGenerator = this._ensurePmremGenerator(renderer);
|
|
738
|
-
const roomScene = new CleanRoomEnvironment();
|
|
739
|
-
const renderTarget = pmremGenerator.fromScene(roomScene, 0, 0.1, 100, {
|
|
740
|
-
size: 2048,
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
// Dispose the intermediate scene (not needed after PMREM generation)
|
|
744
|
-
roomScene.traverse((child) => {
|
|
745
|
-
if (child instanceof THREE.Mesh) {
|
|
746
|
-
child.geometry.dispose();
|
|
747
|
-
if (Array.isArray(child.material)) {
|
|
748
|
-
child.material.forEach((m) => m.dispose());
|
|
749
|
-
} else {
|
|
750
|
-
child.material.dispose();
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
// Cache render target and track its texture
|
|
756
|
-
this._cache.set(cacheKey, renderTarget);
|
|
757
|
-
gpuTracker.trackTexture(renderTarget.texture, `PMREM environment: ${cacheKey}`);
|
|
758
|
-
|
|
759
|
-
// Cache default light detection for procedural environment
|
|
760
|
-
this._lightDetectionCache.set(cacheKey, getDefaultLights());
|
|
761
|
-
|
|
762
|
-
logger.debug(`Generated RoomEnvironment PMREM, cached as "${cacheKey}"`);
|
|
763
|
-
|
|
764
|
-
return renderTarget.texture;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
/**
|
|
768
|
-
* Load an HDR file and generate a PMREM texture from it.
|
|
769
|
-
*
|
|
770
|
-
* Uses HDRLoader to fetch the .hdr file, then PMREMGenerator.fromEquirectangular()
|
|
771
|
-
* to create the PMREM cubemap. The source equirectangular texture is disposed
|
|
772
|
-
* after PMREM generation. The PMREM texture itself serves as both the IBL
|
|
773
|
-
* environment and the background (in "environment" mode).
|
|
774
|
-
*
|
|
775
|
-
* @param url - URL of the .hdr file
|
|
776
|
-
* @param cacheKey - Cache key for the resulting PMREM render target
|
|
777
|
-
* @param renderer - WebGL renderer for PMREMGenerator
|
|
778
|
-
* @returns PMREM texture
|
|
779
|
-
* @throws If the HDR file cannot be loaded
|
|
780
|
-
*/
|
|
781
|
-
private async _loadHdr(
|
|
782
|
-
url: string,
|
|
783
|
-
cacheKey: string,
|
|
784
|
-
renderer: THREE.WebGLRenderer,
|
|
785
|
-
): Promise<THREE.Texture> {
|
|
786
|
-
const loader = this._ensureHdrLoader();
|
|
787
|
-
const pmremGenerator = this._ensurePmremGenerator(renderer);
|
|
788
|
-
|
|
789
|
-
logger.debug(`Loading HDR environment from: ${url}`);
|
|
790
|
-
|
|
791
|
-
// Load HDR file with timeout (30 seconds) to prevent indefinite blocking
|
|
792
|
-
const hdrTexture = await Promise.race([
|
|
793
|
-
loader.loadAsync(url),
|
|
794
|
-
new Promise<never>((_, reject) =>
|
|
795
|
-
setTimeout(() => reject(new Error(`HDR load timed out after 30 s: ${url}`)), 30_000),
|
|
796
|
-
),
|
|
797
|
-
]);
|
|
798
|
-
|
|
799
|
-
// Bail out if disposed while loading
|
|
800
|
-
if (this._disposed) {
|
|
801
|
-
hdrTexture.dispose();
|
|
802
|
-
throw new Error("EnvironmentManager was disposed during HDR load");
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// Generate PMREM cubemap from equirectangular HDR
|
|
806
|
-
const renderTarget = pmremGenerator.fromEquirectangular(hdrTexture);
|
|
807
|
-
|
|
808
|
-
// Analyze HDR pixel data for dominant light sources BEFORE disposing.
|
|
809
|
-
// hdrTexture.image.data is Uint16Array (HalfFloatType) from HDRLoader.
|
|
810
|
-
if (hdrTexture.image?.data && hdrTexture.image.width && hdrTexture.image.height) {
|
|
811
|
-
const result = detectDominantLights(
|
|
812
|
-
hdrTexture.image.data as Uint16Array,
|
|
813
|
-
hdrTexture.image.width as number,
|
|
814
|
-
hdrTexture.image.height as number,
|
|
815
|
-
);
|
|
816
|
-
this._lightDetectionCache.set(cacheKey, result);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
// Dispose the source equirectangular texture (PMREM is now in GPU memory).
|
|
820
|
-
hdrTexture.dispose();
|
|
821
|
-
|
|
822
|
-
// Cache render target and track its texture
|
|
823
|
-
this._cache.set(cacheKey, renderTarget);
|
|
824
|
-
gpuTracker.trackTexture(renderTarget.texture, `PMREM environment: ${cacheKey}`);
|
|
825
|
-
logger.debug(
|
|
826
|
-
`Loaded HDR environment from "${url}", cached as "${cacheKey}"`,
|
|
827
|
-
);
|
|
828
|
-
|
|
829
|
-
return renderTarget.texture;
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
/** Dispose lazy-cached gradient textures (called from EnvironmentManager.dispose). */
|
|
834
|
-
function disposeGradientTextures(): void {
|
|
835
|
-
if (_gradientTexture) { _gradientTexture.dispose(); _gradientTexture = null; }
|
|
836
|
-
if (_gradientDarkTexture) { _gradientDarkTexture.dispose(); _gradientDarkTexture = null; }
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
export { EnvironmentManager, disposeGradientTextures };
|
|
840
|
-
export type { EnvironmentManagerOptions };
|