@chocozhang/three-model-render 1.0.4 → 1.0.5

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.
@@ -22,37 +22,22 @@ function _interopNamespaceDefault(e) {
22
22
 
23
23
  var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
24
24
 
25
- /******************************************************************************
26
- Copyright (c) Microsoft Corporation.
27
-
28
- Permission to use, copy, modify, and/or distribute this software for any
29
- purpose with or without fee is hereby granted.
30
-
31
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
32
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
33
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
34
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
35
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
36
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
37
- PERFORMANCE OF THIS SOFTWARE.
38
- ***************************************************************************** */
39
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
40
-
41
-
42
- function __awaiter(thisArg, _arguments, P, generator) {
43
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
44
- return new (P || (P = Promise))(function (resolve, reject) {
45
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
46
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
47
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
48
- step((generator = generator.apply(thisArg, _arguments || [])).next());
49
- });
50
- }
51
-
52
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
53
- var e = new Error(message);
54
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
25
+ let globalConfig = {
26
+ dracoDecoderPath: '/draco/',
27
+ ktx2TranscoderPath: '/basis/',
55
28
  };
29
+ /**
30
+ * Update global loader configuration (e.g., set path to CDN)
31
+ */
32
+ function setLoaderConfig(config) {
33
+ globalConfig = { ...globalConfig, ...config };
34
+ }
35
+ /**
36
+ * Get current global loader configuration
37
+ */
38
+ function getLoaderConfig() {
39
+ return globalConfig;
40
+ }
56
41
 
57
42
  /**
58
43
  * @file modelLoader.ts
@@ -70,19 +55,22 @@ const DEFAULT_OPTIONS$1 = {
70
55
  maxTextureSize: null,
71
56
  useSimpleMaterials: false,
72
57
  skipSkinned: true,
58
+ useCache: true,
73
59
  };
60
+ const modelCache = new Map();
74
61
  /** Automatically determine which options to enable based on extension (smart judgment) */
75
62
  function normalizeOptions(url, opts) {
76
63
  const ext = (url.split('.').pop() || '').toLowerCase();
77
- const merged = Object.assign(Object.assign({}, DEFAULT_OPTIONS$1), opts);
64
+ const merged = { ...DEFAULT_OPTIONS$1, ...opts };
78
65
  if (ext === 'gltf' || ext === 'glb') {
66
+ const globalConfig = getLoaderConfig();
79
67
  // gltf/glb defaults to trying draco/ktx2 if user didn't specify
80
68
  if (merged.dracoDecoderPath === undefined)
81
- merged.dracoDecoderPath = '/draco/';
69
+ merged.dracoDecoderPath = globalConfig.dracoDecoderPath;
82
70
  if (merged.useKTX2 === undefined)
83
71
  merged.useKTX2 = true;
84
72
  if (merged.ktx2TranscoderPath === undefined)
85
- merged.ktx2TranscoderPath = '/basis/';
73
+ merged.ktx2TranscoderPath = globalConfig.ktx2TranscoderPath;
86
74
  }
87
75
  else {
88
76
  // fbx/obj/ply/stl etc. do not need draco/ktx2
@@ -92,103 +80,108 @@ function normalizeOptions(url, opts) {
92
80
  }
93
81
  return merged;
94
82
  }
95
- function loadModelByUrl(url_1) {
96
- return __awaiter(this, arguments, void 0, function* (url, options = {}) {
97
- var _a, _b;
98
- if (!url)
99
- throw new Error('url required');
100
- const ext = (url.split('.').pop() || '').toLowerCase();
101
- const opts = normalizeOptions(url, options);
102
- const manager = (_a = opts.manager) !== null && _a !== void 0 ? _a : new THREE__namespace.LoadingManager();
103
- let loader;
104
- if (ext === 'gltf' || ext === 'glb') {
105
- const { GLTFLoader } = yield import('three/examples/jsm/loaders/GLTFLoader.js');
106
- const gltfLoader = new GLTFLoader(manager);
107
- if (opts.dracoDecoderPath) {
108
- const { DRACOLoader } = yield import('three/examples/jsm/loaders/DRACOLoader.js');
109
- const draco = new DRACOLoader();
110
- draco.setDecoderPath(opts.dracoDecoderPath);
111
- gltfLoader.setDRACOLoader(draco);
112
- }
113
- if (opts.useKTX2 && opts.ktx2TranscoderPath) {
114
- const { KTX2Loader } = yield import('three/examples/jsm/loaders/KTX2Loader.js');
115
- const ktx2Loader = new KTX2Loader().setTranscoderPath(opts.ktx2TranscoderPath);
116
- gltfLoader.__ktx2Loader = ktx2Loader;
117
- }
118
- loader = gltfLoader;
119
- }
120
- else if (ext === 'fbx') {
121
- const { FBXLoader } = yield import('three/examples/jsm/loaders/FBXLoader.js');
122
- loader = new FBXLoader(manager);
123
- }
124
- else if (ext === 'obj') {
125
- const { OBJLoader } = yield import('three/examples/jsm/loaders/OBJLoader.js');
126
- loader = new OBJLoader(manager);
127
- }
128
- else if (ext === 'ply') {
129
- const { PLYLoader } = yield import('three/examples/jsm/loaders/PLYLoader.js');
130
- loader = new PLYLoader(manager);
131
- }
132
- else if (ext === 'stl') {
133
- const { STLLoader } = yield import('three/examples/jsm/loaders/STLLoader.js');
134
- loader = new STLLoader(manager);
83
+ async function loadModelByUrl(url, options = {}) {
84
+ if (!url)
85
+ throw new Error('url required');
86
+ const ext = (url.split('.').pop() || '').toLowerCase();
87
+ const opts = normalizeOptions(url, options);
88
+ const manager = opts.manager ?? new THREE__namespace.LoadingManager();
89
+ // Cache key includes URL and relevant optimization options
90
+ const cacheKey = `${url}_${opts.mergeGeometries}_${opts.maxTextureSize}_${opts.useSimpleMaterials}`;
91
+ if (opts.useCache && modelCache.has(cacheKey)) {
92
+ return modelCache.get(cacheKey).clone();
93
+ }
94
+ let loader;
95
+ if (ext === 'gltf' || ext === 'glb') {
96
+ const { GLTFLoader } = await import('three/examples/jsm/loaders/GLTFLoader.js');
97
+ const gltfLoader = new GLTFLoader(manager);
98
+ if (opts.dracoDecoderPath) {
99
+ const { DRACOLoader } = await import('three/examples/jsm/loaders/DRACOLoader.js');
100
+ const draco = new DRACOLoader();
101
+ draco.setDecoderPath(opts.dracoDecoderPath);
102
+ gltfLoader.setDRACOLoader(draco);
135
103
  }
136
- else {
137
- throw new Error(`Unsupported model extension: .${ext}`);
104
+ if (opts.useKTX2 && opts.ktx2TranscoderPath) {
105
+ const { KTX2Loader } = await import('three/examples/jsm/loaders/KTX2Loader.js');
106
+ const ktx2Loader = new KTX2Loader().setTranscoderPath(opts.ktx2TranscoderPath);
107
+ gltfLoader.__ktx2Loader = ktx2Loader;
138
108
  }
139
- const object = yield new Promise((resolve, reject) => {
140
- loader.load(url, (res) => {
141
- var _a;
142
- if (ext === 'gltf' || ext === 'glb') {
143
- const sceneObj = res.scene || res;
144
- // --- Critical: Expose animations to scene.userData (or scene.animations) ---
145
- // So the caller can access clips simply by getting sceneObj.userData.animations
146
- sceneObj.userData = (sceneObj === null || sceneObj === void 0 ? void 0 : sceneObj.userData) || {};
147
- sceneObj.userData.animations = (_a = res.animations) !== null && _a !== void 0 ? _a : [];
148
- resolve(sceneObj);
149
- }
150
- else {
151
- resolve(res);
152
- }
153
- }, undefined, (err) => reject(err));
154
- });
155
- // Optimize
156
- object.traverse((child) => {
157
- var _a, _b, _c;
158
- const mesh = child;
159
- if (mesh.isMesh && mesh.geometry && !mesh.geometry.isBufferGeometry) {
160
- try {
161
- mesh.geometry = (_c = (_b = (_a = new THREE__namespace.BufferGeometry()).fromGeometry) === null || _b === void 0 ? void 0 : _b.call(_a, mesh.geometry)) !== null && _c !== void 0 ? _c : mesh.geometry;
162
- }
163
- catch (_d) { }
109
+ loader = gltfLoader;
110
+ }
111
+ else if (ext === 'fbx') {
112
+ const { FBXLoader } = await import('three/examples/jsm/loaders/FBXLoader.js');
113
+ loader = new FBXLoader(manager);
114
+ }
115
+ else if (ext === 'obj') {
116
+ const { OBJLoader } = await import('three/examples/jsm/loaders/OBJLoader.js');
117
+ loader = new OBJLoader(manager);
118
+ }
119
+ else if (ext === 'ply') {
120
+ const { PLYLoader } = await import('three/examples/jsm/loaders/PLYLoader.js');
121
+ loader = new PLYLoader(manager);
122
+ }
123
+ else if (ext === 'stl') {
124
+ const { STLLoader } = await import('three/examples/jsm/loaders/STLLoader.js');
125
+ loader = new STLLoader(manager);
126
+ }
127
+ else {
128
+ throw new Error(`Unsupported model extension: .${ext}`);
129
+ }
130
+ const object = await new Promise((resolve, reject) => {
131
+ loader.load(url, (res) => {
132
+ if (ext === 'gltf' || ext === 'glb') {
133
+ const sceneObj = res.scene || res;
134
+ // --- Critical: Expose animations to scene.userData (or scene.animations) ---
135
+ // So the caller can access clips simply by getting sceneObj.userData.animations
136
+ sceneObj.userData = sceneObj?.userData || {};
137
+ sceneObj.userData.animations = res.animations ?? [];
138
+ resolve(sceneObj);
164
139
  }
165
- });
166
- if (opts.maxTextureSize && opts.maxTextureSize > 0)
167
- downscaleTexturesInObject(object, opts.maxTextureSize);
168
- if (opts.useSimpleMaterials) {
169
- object.traverse((child) => {
170
- const m = child.material;
171
- if (!m)
172
- return;
173
- if (Array.isArray(m))
174
- child.material = m.map((mat) => toSimpleMaterial(mat));
175
- else
176
- child.material = toSimpleMaterial(m);
177
- });
178
- }
179
- if (opts.mergeGeometries) {
180
- try {
181
- yield tryMergeGeometries(object, { skipSkinned: (_b = opts.skipSkinned) !== null && _b !== void 0 ? _b : true });
140
+ else {
141
+ resolve(res);
182
142
  }
183
- catch (e) {
184
- console.warn('mergeGeometries failed', e);
143
+ }, undefined, (err) => reject(err));
144
+ });
145
+ // Optimize
146
+ object.traverse((child) => {
147
+ const mesh = child;
148
+ if (mesh.isMesh && mesh.geometry && !mesh.geometry.isBufferGeometry) {
149
+ try {
150
+ mesh.geometry = new THREE__namespace.BufferGeometry().fromGeometry?.(mesh.geometry) ?? mesh.geometry;
185
151
  }
152
+ catch { }
186
153
  }
187
- return object;
188
154
  });
155
+ if (opts.maxTextureSize && opts.maxTextureSize > 0)
156
+ await downscaleTexturesInObject(object, opts.maxTextureSize);
157
+ if (opts.useSimpleMaterials) {
158
+ object.traverse((child) => {
159
+ const m = child.material;
160
+ if (!m)
161
+ return;
162
+ if (Array.isArray(m))
163
+ child.material = m.map((mat) => toSimpleMaterial(mat));
164
+ else
165
+ child.material = toSimpleMaterial(m);
166
+ });
167
+ }
168
+ if (opts.mergeGeometries) {
169
+ try {
170
+ await tryMergeGeometries(object, { skipSkinned: opts.skipSkinned ?? true });
171
+ }
172
+ catch (e) {
173
+ console.warn('mergeGeometries failed', e);
174
+ }
175
+ }
176
+ if (opts.useCache) {
177
+ modelCache.set(cacheKey, object);
178
+ return object.clone();
179
+ }
180
+ return object;
189
181
  }
190
- /** Runtime downscale textures in mesh to maxSize (canvas drawImage) to save GPU memory */
191
- function downscaleTexturesInObject(obj, maxSize) {
182
+ /** Runtime downscale textures in mesh to maxSize (createImageBitmap or canvas) to save GPU memory */
183
+ async function downscaleTexturesInObject(obj, maxSize) {
184
+ const tasks = [];
192
185
  obj.traverse((ch) => {
193
186
  if (!ch.isMesh)
194
187
  return;
@@ -207,27 +200,44 @@ function downscaleTexturesInObject(obj, maxSize) {
207
200
  const max = maxSize;
208
201
  if (image.width <= max && image.height <= max)
209
202
  return;
210
- // downscale using canvas (sync, may be heavy for many textures)
211
- try {
212
- const scale = Math.min(max / image.width, max / image.height);
213
- const canvas = document.createElement('canvas');
214
- canvas.width = Math.floor(image.width * scale);
215
- canvas.height = Math.floor(image.height * scale);
216
- const ctx = canvas.getContext('2d');
217
- if (ctx) {
218
- ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
219
- const newTex = new THREE__namespace.Texture(canvas);
220
- newTex.needsUpdate = true;
221
- // copy common settings (encoding etc)
222
- newTex.encoding = tex.encoding;
223
- mat[p] = newTex;
203
+ tasks.push((async () => {
204
+ try {
205
+ const scale = Math.min(max / image.width, max / image.height);
206
+ const newWidth = Math.floor(image.width * scale);
207
+ const newHeight = Math.floor(image.height * scale);
208
+ let newSource;
209
+ if (typeof createImageBitmap !== 'undefined') {
210
+ newSource = await createImageBitmap(image, {
211
+ resizeWidth: newWidth,
212
+ resizeHeight: newHeight,
213
+ resizeQuality: 'high'
214
+ });
215
+ }
216
+ else {
217
+ // Fallback for environments without createImageBitmap
218
+ const canvas = document.createElement('canvas');
219
+ canvas.width = newWidth;
220
+ canvas.height = newHeight;
221
+ const ctx = canvas.getContext('2d');
222
+ if (ctx) {
223
+ ctx.drawImage(image, 0, 0, newWidth, newHeight);
224
+ newSource = canvas;
225
+ }
226
+ }
227
+ if (newSource) {
228
+ const newTex = new THREE__namespace.Texture(newSource);
229
+ newTex.needsUpdate = true;
230
+ newTex.encoding = tex.encoding;
231
+ mat[p] = newTex;
232
+ }
224
233
  }
225
- }
226
- catch (e) {
227
- console.warn('downscale texture failed', e);
228
- }
234
+ catch (e) {
235
+ console.warn('downscale texture failed', e);
236
+ }
237
+ })());
229
238
  });
230
239
  });
240
+ await Promise.all(tasks);
231
241
  }
232
242
  /**
233
243
  * Try to merge geometries in object (Only merge: non-transparent, non-SkinnedMesh, attribute compatible BufferGeometry)
@@ -235,81 +245,77 @@ function downscaleTexturesInObject(obj, maxSize) {
235
245
  * - Merging will group by material UUID (different materials cannot be merged)
236
246
  * - Merge function is compatible with common export names of BufferGeometryUtils
237
247
  */
238
- function tryMergeGeometries(root, opts) {
239
- return __awaiter(this, void 0, void 0, function* () {
240
- // collect meshes by material uuid
241
- const groups = new Map();
242
- root.traverse((ch) => {
243
- var _a;
244
- if (!ch.isMesh)
245
- return;
246
- const mesh = ch;
247
- if (opts.skipSkinned && mesh.isSkinnedMesh)
248
- return;
249
- const mat = mesh.material;
250
- // don't merge transparent or morph-enabled or skinned meshes
251
- if (!mesh.geometry || mesh.visible === false)
252
- return;
253
- if (mat && mat.transparent)
254
- return;
255
- const geom = mesh.geometry.clone();
256
- mesh.updateWorldMatrix(true, false);
257
- geom.applyMatrix4(mesh.matrixWorld);
258
- // ensure attributes compatible? we'll rely on merge function to return null if incompatible
259
- const key = (mat && mat.uuid) || 'default';
260
- const bucket = (_a = groups.get(key)) !== null && _a !== void 0 ? _a : { material: mat !== null && mat !== void 0 ? mat : new THREE__namespace.MeshStandardMaterial(), geoms: [] };
261
- bucket.geoms.push(geom);
262
- groups.set(key, bucket);
263
- // mark for removal (we'll remove meshes after)
264
- mesh.userData.__toRemoveForMerge = true;
265
- });
266
- if (groups.size === 0)
248
+ async function tryMergeGeometries(root, opts) {
249
+ // collect meshes by material uuid
250
+ const groups = new Map();
251
+ root.traverse((ch) => {
252
+ if (!ch.isMesh)
267
253
  return;
268
- // dynamic import BufferGeometryUtils and find merge function name
269
- const bufUtilsMod = yield import('three/examples/jsm/utils/BufferGeometryUtils.js');
270
- // use || chain (avoid mixing ?? with || without parentheses)
271
- const mergeFn = bufUtilsMod.mergeBufferGeometries ||
272
- bufUtilsMod.mergeGeometries ||
273
- bufUtilsMod.mergeBufferGeometries || // defensive duplicate
274
- bufUtilsMod.mergeGeometries;
275
- if (!mergeFn)
276
- throw new Error('No merge function found in BufferGeometryUtils');
277
- // for each group, try merge
278
- for (const [key, { material, geoms }] of groups) {
279
- if (geoms.length <= 1) {
280
- // nothing to merge
281
- continue;
282
- }
283
- // call merge function - signature typically mergeBufferGeometries(array, useGroups)
284
- const merged = mergeFn(geoms, false);
285
- if (!merged) {
286
- console.warn('merge returned null for group', key);
287
- continue;
288
- }
289
- // create merged mesh at root (world-space geometry already applied)
290
- const mergedMesh = new THREE__namespace.Mesh(merged, material);
291
- root.add(mergedMesh);
254
+ const mesh = ch;
255
+ if (opts.skipSkinned && mesh.isSkinnedMesh)
256
+ return;
257
+ const mat = mesh.material;
258
+ // don't merge transparent or morph-enabled or skinned meshes
259
+ if (!mesh.geometry || mesh.visible === false)
260
+ return;
261
+ if (mat && mat.transparent)
262
+ return;
263
+ const geom = mesh.geometry.clone();
264
+ mesh.updateWorldMatrix(true, false);
265
+ geom.applyMatrix4(mesh.matrixWorld);
266
+ // ensure attributes compatible? we'll rely on merge function to return null if incompatible
267
+ const key = (mat && mat.uuid) || 'default';
268
+ const bucket = groups.get(key) ?? { material: mat ?? new THREE__namespace.MeshStandardMaterial(), geoms: [] };
269
+ bucket.geoms.push(geom);
270
+ groups.set(key, bucket);
271
+ // mark for removal (we'll remove meshes after)
272
+ mesh.userData.__toRemoveForMerge = true;
273
+ });
274
+ if (groups.size === 0)
275
+ return;
276
+ // dynamic import BufferGeometryUtils and find merge function name
277
+ const bufUtilsMod = await import('three/examples/jsm/utils/BufferGeometryUtils.js');
278
+ // use || chain (avoid mixing ?? with || without parentheses)
279
+ const mergeFn = bufUtilsMod.mergeBufferGeometries ||
280
+ bufUtilsMod.mergeGeometries ||
281
+ bufUtilsMod.mergeBufferGeometries || // defensive duplicate
282
+ bufUtilsMod.mergeGeometries;
283
+ if (!mergeFn)
284
+ throw new Error('No merge function found in BufferGeometryUtils');
285
+ // for each group, try merge
286
+ for (const [key, { material, geoms }] of groups) {
287
+ if (geoms.length <= 1) {
288
+ // nothing to merge
289
+ continue;
292
290
  }
293
- // now remove original meshes flagged for removal
294
- const toRemove = [];
295
- root.traverse((ch) => {
296
- var _a;
297
- if ((_a = ch.userData) === null || _a === void 0 ? void 0 : _a.__toRemoveForMerge)
298
- toRemove.push(ch);
299
- });
300
- toRemove.forEach((m) => {
301
- if (m.parent)
302
- m.parent.remove(m);
303
- // free original resources (geometries already cloned/applied), but careful with shared materials
304
- if (m.isMesh) {
305
- const mm = m;
306
- try {
307
- mm.geometry.dispose();
308
- }
309
- catch (_a) { }
310
- // we do NOT dispose material because it may be reused by mergedMesh
291
+ // call merge function - signature typically mergeBufferGeometries(array, useGroups)
292
+ const merged = mergeFn(geoms, false);
293
+ if (!merged) {
294
+ console.warn('merge returned null for group', key);
295
+ continue;
296
+ }
297
+ // create merged mesh at root (world-space geometry already applied)
298
+ const mergedMesh = new THREE__namespace.Mesh(merged, material);
299
+ root.add(mergedMesh);
300
+ }
301
+ // now remove original meshes flagged for removal
302
+ const toRemove = [];
303
+ root.traverse((ch) => {
304
+ if (ch.userData?.__toRemoveForMerge)
305
+ toRemove.push(ch);
306
+ });
307
+ toRemove.forEach((m) => {
308
+ if (m.parent)
309
+ m.parent.remove(m);
310
+ // free original resources (geometries already cloned/applied), but careful with shared materials
311
+ if (m.isMesh) {
312
+ const mm = m;
313
+ try {
314
+ mm.geometry.dispose();
311
315
  }
312
- });
316
+ catch { }
317
+ // we do NOT dispose material because it may be reused by mergedMesh
318
+ }
313
319
  });
314
320
  }
315
321
  /* ---------------------
@@ -326,7 +332,7 @@ function disposeObject(obj) {
326
332
  try {
327
333
  m.geometry.dispose();
328
334
  }
329
- catch (_a) { }
335
+ catch { }
330
336
  }
331
337
  const mat = m.material;
332
338
  if (mat) {
@@ -348,14 +354,14 @@ function disposeMaterial(mat) {
348
354
  try {
349
355
  mat[k].dispose();
350
356
  }
351
- catch (_a) { }
357
+ catch { }
352
358
  }
353
359
  });
354
360
  try {
355
361
  if (typeof mat.dispose === 'function')
356
362
  mat.dispose();
357
363
  }
358
- catch (_a) { }
364
+ catch { }
359
365
  }
360
366
  // Helper to convert to simple material (stub)
361
367
  function toSimpleMaterial(mat) {
@@ -398,100 +404,97 @@ const equirectCache = new Map();
398
404
  * @param paths string[] 6 image paths, order: [px, nx, py, ny, pz, nz]
399
405
  * @param opts SkyboxOptions
400
406
  */
401
- function loadCubeSkybox(renderer_1, scene_1, paths_1) {
402
- return __awaiter(this, arguments, void 0, function* (renderer, scene, paths, opts = {}) {
403
- var _a, _b;
404
- const options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), opts);
405
- if (!Array.isArray(paths) || paths.length !== 6)
406
- throw new Error('cube skybox requires 6 image paths');
407
- const key = paths.join('|');
408
- // Cache handling
409
- if (options.cache && cubeCache.has(key)) {
410
- const rec = cubeCache.get(key);
411
- rec.refCount += 1;
412
- // reapply to scene (in case it was removed)
413
- if (options.setAsBackground)
414
- scene.background = rec.handle.backgroundTexture;
415
- if (options.setAsEnvironment && rec.handle.envRenderTarget)
416
- scene.environment = rec.handle.envRenderTarget.texture;
417
- return rec.handle;
418
- }
419
- // Load cube texture
420
- const loader = new THREE__namespace.CubeTextureLoader();
421
- const texture = yield new Promise((resolve, reject) => {
422
- loader.load(paths, (tex) => resolve(tex), undefined, (err) => reject(err));
423
- });
424
- // Set encoding and mapping
425
- if (options.useSRGBEncoding)
426
- texture.encoding = THREE__namespace.sRGBEncoding;
427
- texture.mapping = THREE__namespace.CubeReflectionMapping;
428
- // apply as background if required
407
+ async function loadCubeSkybox(renderer, scene, paths, opts = {}) {
408
+ const options = { ...DEFAULT_OPTIONS, ...opts };
409
+ if (!Array.isArray(paths) || paths.length !== 6)
410
+ throw new Error('cube skybox requires 6 image paths');
411
+ const key = paths.join('|');
412
+ // Cache handling
413
+ if (options.cache && cubeCache.has(key)) {
414
+ const rec = cubeCache.get(key);
415
+ rec.refCount += 1;
416
+ // reapply to scene (in case it was removed)
429
417
  if (options.setAsBackground)
430
- scene.background = texture;
431
- // environment: use PMREM to produce a proper prefiltered env map for PBR
432
- let pmremGenerator = (_a = options.pmremGenerator) !== null && _a !== void 0 ? _a : new THREE__namespace.PMREMGenerator(renderer);
433
- (_b = pmremGenerator.compileCubemapShader) === null || _b === void 0 ? void 0 : _b.call(pmremGenerator);
434
- // fromCubemap might be available in your three.js; fallback to fromEquirectangular approach if not
435
- let envRenderTarget = null;
436
- if (pmremGenerator.fromCubemap) {
437
- envRenderTarget = pmremGenerator.fromCubemap(texture);
418
+ scene.background = rec.handle.backgroundTexture;
419
+ if (options.setAsEnvironment && rec.handle.envRenderTarget)
420
+ scene.environment = rec.handle.envRenderTarget.texture;
421
+ return rec.handle;
422
+ }
423
+ // Load cube texture
424
+ const loader = new THREE__namespace.CubeTextureLoader();
425
+ const texture = await new Promise((resolve, reject) => {
426
+ loader.load(paths, (tex) => resolve(tex), undefined, (err) => reject(err));
427
+ });
428
+ // Set encoding and mapping
429
+ if (options.useSRGBEncoding)
430
+ texture.encoding = THREE__namespace.sRGBEncoding;
431
+ texture.mapping = THREE__namespace.CubeReflectionMapping;
432
+ // apply as background if required
433
+ if (options.setAsBackground)
434
+ scene.background = texture;
435
+ // environment: use PMREM to produce a proper prefiltered env map for PBR
436
+ let pmremGenerator = options.pmremGenerator ?? new THREE__namespace.PMREMGenerator(renderer);
437
+ pmremGenerator.compileCubemapShader?.( /* optional */);
438
+ // fromCubemap might be available in your three.js; fallback to fromEquirectangular approach if not
439
+ let envRenderTarget = null;
440
+ if (pmremGenerator.fromCubemap) {
441
+ envRenderTarget = pmremGenerator.fromCubemap(texture);
442
+ }
443
+ else {
444
+ // Fallback: render cube to env map by using generator.fromEquirectangular with a converted equirect if needed.
445
+ // Simpler fallback: use the cube texture directly as environment (less correct for reflections).
446
+ envRenderTarget = null;
447
+ }
448
+ if (options.setAsEnvironment) {
449
+ if (envRenderTarget) {
450
+ scene.environment = envRenderTarget.texture;
438
451
  }
439
452
  else {
440
- // Fallback: render cube to env map by using generator.fromEquirectangular with a converted equirect if needed.
441
- // Simpler fallback: use the cube texture directly as environment (less correct for reflections).
442
- envRenderTarget = null;
453
+ // fallback: use cube texture directly (works but not prefiltered)
454
+ scene.environment = texture;
443
455
  }
444
- if (options.setAsEnvironment) {
456
+ }
457
+ const handle = {
458
+ key,
459
+ backgroundTexture: options.setAsBackground ? texture : null,
460
+ envRenderTarget: envRenderTarget,
461
+ pmremGenerator: options.pmremGenerator ? null : pmremGenerator, // only dispose if we created it
462
+ setAsBackground: !!options.setAsBackground,
463
+ setAsEnvironment: !!options.setAsEnvironment,
464
+ dispose() {
465
+ // remove from scene
466
+ if (options.setAsBackground && scene.background === texture)
467
+ scene.background = null;
468
+ if (options.setAsEnvironment && scene.environment) {
469
+ // only clear if it's the same texture we set
470
+ if (envRenderTarget && scene.environment === envRenderTarget.texture)
471
+ scene.environment = null;
472
+ else if (scene.environment === texture)
473
+ scene.environment = null;
474
+ }
475
+ // dispose resources only if not cached/shared
445
476
  if (envRenderTarget) {
446
- scene.environment = envRenderTarget.texture;
477
+ try {
478
+ envRenderTarget.dispose();
479
+ }
480
+ catch { }
447
481
  }
448
- else {
449
- // fallback: use cube texture directly (works but not prefiltered)
450
- scene.environment = texture;
482
+ try {
483
+ texture.dispose();
451
484
  }
452
- }
453
- const handle = {
454
- key,
455
- backgroundTexture: options.setAsBackground ? texture : null,
456
- envRenderTarget: envRenderTarget,
457
- pmremGenerator: options.pmremGenerator ? null : pmremGenerator, // only dispose if we created it
458
- setAsBackground: !!options.setAsBackground,
459
- setAsEnvironment: !!options.setAsEnvironment,
460
- dispose() {
461
- // remove from scene
462
- if (options.setAsBackground && scene.background === texture)
463
- scene.background = null;
464
- if (options.setAsEnvironment && scene.environment) {
465
- // only clear if it's the same texture we set
466
- if (envRenderTarget && scene.environment === envRenderTarget.texture)
467
- scene.environment = null;
468
- else if (scene.environment === texture)
469
- scene.environment = null;
470
- }
471
- // dispose resources only if not cached/shared
472
- if (envRenderTarget) {
473
- try {
474
- envRenderTarget.dispose();
475
- }
476
- catch (_a) { }
477
- }
485
+ catch { }
486
+ // dispose pmremGenerator we created
487
+ if (!options.pmremGenerator && pmremGenerator) {
478
488
  try {
479
- texture.dispose();
480
- }
481
- catch (_b) { }
482
- // dispose pmremGenerator we created
483
- if (!options.pmremGenerator && pmremGenerator) {
484
- try {
485
- pmremGenerator.dispose();
486
- }
487
- catch (_c) { }
489
+ pmremGenerator.dispose();
488
490
  }
491
+ catch { }
489
492
  }
490
- };
491
- if (options.cache)
492
- cubeCache.set(key, { handle, refCount: 1 });
493
- return handle;
494
- });
493
+ }
494
+ };
495
+ if (options.cache)
496
+ cubeCache.set(key, { handle, refCount: 1 });
497
+ return handle;
495
498
  }
496
499
  /**
497
500
  * Load Equirectangular/Single Image (Supports HDR via RGBELoader)
@@ -500,95 +503,90 @@ function loadCubeSkybox(renderer_1, scene_1, paths_1) {
500
503
  * @param url string - *.hdr, *.exr, *.jpg, *.png
501
504
  * @param opts SkyboxOptions
502
505
  */
503
- function loadEquirectSkybox(renderer_1, scene_1, url_1) {
504
- return __awaiter(this, arguments, void 0, function* (renderer, scene, url, opts = {}) {
505
- var _a, _b;
506
- const options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), opts);
507
- const key = url;
508
- if (options.cache && equirectCache.has(key)) {
509
- const rec = equirectCache.get(key);
510
- rec.refCount += 1;
511
- if (options.setAsBackground)
512
- scene.background = rec.handle.backgroundTexture;
513
- if (options.setAsEnvironment && rec.handle.envRenderTarget)
514
- scene.environment = rec.handle.envRenderTarget.texture;
515
- return rec.handle;
516
- }
517
- // Dynamically import RGBELoader (for .hdr/.exr), if loading normal jpg/png directly use TextureLoader
518
- const isHDR = /\.hdr$|\.exr$/i.test(url);
519
- let hdrTexture;
520
- if (isHDR) {
521
- const { RGBELoader } = yield import('three/examples/jsm/loaders/RGBELoader.js');
522
- hdrTexture = yield new Promise((resolve, reject) => {
523
- new RGBELoader().load(url, (tex) => resolve(tex), undefined, (err) => reject(err));
524
- });
525
- // RGBE textures typically use LinearEncoding
526
- hdrTexture.encoding = THREE__namespace.LinearEncoding;
527
- }
528
- else {
529
- // ordinary image - use TextureLoader
530
- const loader = new THREE__namespace.TextureLoader();
531
- hdrTexture = yield new Promise((resolve, reject) => {
532
- loader.load(url, (t) => resolve(t), undefined, (err) => reject(err));
533
- });
534
- if (options.useSRGBEncoding)
535
- hdrTexture.encoding = THREE__namespace.sRGBEncoding;
536
- }
537
- // PMREMGenerator to convert equirectangular to prefiltered cubemap (good for PBR)
538
- const pmremGenerator = (_a = options.pmremGenerator) !== null && _a !== void 0 ? _a : new THREE__namespace.PMREMGenerator(renderer);
539
- (_b = pmremGenerator.compileEquirectangularShader) === null || _b === void 0 ? void 0 : _b.call(pmremGenerator);
540
- const envRenderTarget = pmremGenerator.fromEquirectangular(hdrTexture);
541
- // envTexture to use for scene.environment
542
- const envTexture = envRenderTarget.texture;
543
- // set background and/or environment
544
- if (options.setAsBackground) {
545
- // for background it's ok to use the equirect texture directly or the envTexture
546
- // envTexture is cubemap-like and usually better for reflections; using it as background creates cube-projected look
547
- scene.background = envTexture;
548
- }
549
- if (options.setAsEnvironment) {
550
- scene.environment = envTexture;
551
- }
552
- // We can dispose the original hdrTexture (the PMREM target contains the needed data)
553
- try {
554
- hdrTexture.dispose();
555
- }
556
- catch (_c) { }
557
- const handle = {
558
- key,
559
- backgroundTexture: options.setAsBackground ? envTexture : null,
560
- envRenderTarget,
561
- pmremGenerator: options.pmremGenerator ? null : pmremGenerator,
562
- setAsBackground: !!options.setAsBackground,
563
- setAsEnvironment: !!options.setAsEnvironment,
564
- dispose() {
565
- if (options.setAsBackground && scene.background === envTexture)
566
- scene.background = null;
567
- if (options.setAsEnvironment && scene.environment === envTexture)
568
- scene.environment = null;
506
+ async function loadEquirectSkybox(renderer, scene, url, opts = {}) {
507
+ const options = { ...DEFAULT_OPTIONS, ...opts };
508
+ const key = url;
509
+ if (options.cache && equirectCache.has(key)) {
510
+ const rec = equirectCache.get(key);
511
+ rec.refCount += 1;
512
+ if (options.setAsBackground)
513
+ scene.background = rec.handle.backgroundTexture;
514
+ if (options.setAsEnvironment && rec.handle.envRenderTarget)
515
+ scene.environment = rec.handle.envRenderTarget.texture;
516
+ return rec.handle;
517
+ }
518
+ // Dynamically import RGBELoader (for .hdr/.exr), if loading normal jpg/png directly use TextureLoader
519
+ const isHDR = /\.hdr$|\.exr$/i.test(url);
520
+ let hdrTexture;
521
+ if (isHDR) {
522
+ const { RGBELoader } = await import('three/examples/jsm/loaders/RGBELoader.js');
523
+ hdrTexture = await new Promise((resolve, reject) => {
524
+ new RGBELoader().load(url, (tex) => resolve(tex), undefined, (err) => reject(err));
525
+ });
526
+ // RGBE textures typically use LinearEncoding
527
+ hdrTexture.encoding = THREE__namespace.LinearEncoding;
528
+ }
529
+ else {
530
+ // ordinary image - use TextureLoader
531
+ const loader = new THREE__namespace.TextureLoader();
532
+ hdrTexture = await new Promise((resolve, reject) => {
533
+ loader.load(url, (t) => resolve(t), undefined, (err) => reject(err));
534
+ });
535
+ if (options.useSRGBEncoding)
536
+ hdrTexture.encoding = THREE__namespace.sRGBEncoding;
537
+ }
538
+ // PMREMGenerator to convert equirectangular to prefiltered cubemap (good for PBR)
539
+ const pmremGenerator = options.pmremGenerator ?? new THREE__namespace.PMREMGenerator(renderer);
540
+ pmremGenerator.compileEquirectangularShader?.();
541
+ const envRenderTarget = pmremGenerator.fromEquirectangular(hdrTexture);
542
+ // envTexture to use for scene.environment
543
+ const envTexture = envRenderTarget.texture;
544
+ // set background and/or environment
545
+ if (options.setAsBackground) {
546
+ // for background it's ok to use the equirect texture directly or the envTexture
547
+ // envTexture is cubemap-like and usually better for reflections; using it as background creates cube-projected look
548
+ scene.background = envTexture;
549
+ }
550
+ if (options.setAsEnvironment) {
551
+ scene.environment = envTexture;
552
+ }
553
+ // We can dispose the original hdrTexture (the PMREM target contains the needed data)
554
+ try {
555
+ hdrTexture.dispose();
556
+ }
557
+ catch { }
558
+ const handle = {
559
+ key,
560
+ backgroundTexture: options.setAsBackground ? envTexture : null,
561
+ envRenderTarget,
562
+ pmremGenerator: options.pmremGenerator ? null : pmremGenerator,
563
+ setAsBackground: !!options.setAsBackground,
564
+ setAsEnvironment: !!options.setAsEnvironment,
565
+ dispose() {
566
+ if (options.setAsBackground && scene.background === envTexture)
567
+ scene.background = null;
568
+ if (options.setAsEnvironment && scene.environment === envTexture)
569
+ scene.environment = null;
570
+ try {
571
+ envRenderTarget.dispose();
572
+ }
573
+ catch { }
574
+ if (!options.pmremGenerator && pmremGenerator) {
569
575
  try {
570
- envRenderTarget.dispose();
571
- }
572
- catch (_a) { }
573
- if (!options.pmremGenerator && pmremGenerator) {
574
- try {
575
- pmremGenerator.dispose();
576
- }
577
- catch (_b) { }
576
+ pmremGenerator.dispose();
578
577
  }
578
+ catch { }
579
579
  }
580
- };
581
- if (options.cache)
582
- equirectCache.set(key, { handle, refCount: 1 });
583
- return handle;
584
- });
580
+ }
581
+ };
582
+ if (options.cache)
583
+ equirectCache.set(key, { handle, refCount: 1 });
584
+ return handle;
585
585
  }
586
- function loadSkybox(renderer_1, scene_1, params_1) {
587
- return __awaiter(this, arguments, void 0, function* (renderer, scene, params, opts = {}) {
588
- if (params.type === 'cube')
589
- return loadCubeSkybox(renderer, scene, params.paths, opts);
590
- return loadEquirectSkybox(renderer, scene, params.url, opts);
591
- });
586
+ async function loadSkybox(renderer, scene, params, opts = {}) {
587
+ if (params.type === 'cube')
588
+ return loadCubeSkybox(renderer, scene, params.paths, opts);
589
+ return loadEquirectSkybox(renderer, scene, params.url, opts);
592
590
  }
593
591
  /* -------------------------
594
592
  Cache / Reference Counting Helper Methods
@@ -798,10 +796,9 @@ class BlueSkyManager {
798
796
  * Usually called when the scene is completely destroyed or the application exits
799
797
  */
800
798
  destroy() {
801
- var _a;
802
799
  this.cancelLoad();
803
800
  this.dispose();
804
- (_a = this.pmremGen) === null || _a === void 0 ? void 0 : _a.dispose();
801
+ this.pmremGen?.dispose();
805
802
  this.isInitialized = false;
806
803
  this.loadingState = 'idle';
807
804
  }
@@ -817,9 +814,11 @@ const BlueSky = new BlueSkyManager();
817
814
  exports.BlueSky = BlueSky;
818
815
  exports.disposeMaterial = disposeMaterial;
819
816
  exports.disposeObject = disposeObject;
817
+ exports.getLoaderConfig = getLoaderConfig;
820
818
  exports.loadCubeSkybox = loadCubeSkybox;
821
819
  exports.loadEquirectSkybox = loadEquirectSkybox;
822
820
  exports.loadModelByUrl = loadModelByUrl;
823
821
  exports.loadSkybox = loadSkybox;
824
822
  exports.releaseSkybox = releaseSkybox;
823
+ exports.setLoaderConfig = setLoaderConfig;
825
824
  //# sourceMappingURL=index.js.map