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