@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.
package/dist/index.js CHANGED
@@ -67,8 +67,8 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
67
67
  };
68
68
  }
69
69
  // Configuration
70
- const enableCache = (options === null || options === void 0 ? void 0 : options.enableCache) !== false;
71
- const updateInterval = (options === null || options === void 0 ? void 0 : options.updateInterval) || 0;
70
+ const enableCache = options?.enableCache !== false;
71
+ const updateInterval = options?.updateInterval || 0;
72
72
  // Create label container, absolute positioning, attached to body
73
73
  const container = document.createElement('div');
74
74
  container.style.position = 'absolute';
@@ -84,11 +84,10 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
84
84
  let lastUpdateTime = 0;
85
85
  // Traverse all child models
86
86
  parentModel.traverse((child) => {
87
- var _a;
88
87
  // Only process Mesh or Group
89
88
  if ((child.isMesh || child.type === 'Group')) {
90
89
  // Dynamic matching of name to prevent undefined
91
- const labelText = (_a = Object.entries(modelLabelsMap).find(([key]) => child.name.includes(key))) === null || _a === void 0 ? void 0 : _a[1];
90
+ const labelText = Object.entries(modelLabelsMap).find(([key]) => child.name.includes(key))?.[1];
92
91
  if (!labelText)
93
92
  return; // Skip if no matching label
94
93
  // Create DOM label
@@ -96,11 +95,11 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
96
95
  el.innerText = labelText;
97
96
  // Styles defined in JS, can be overridden via options
98
97
  el.style.position = 'absolute';
99
- el.style.color = (options === null || options === void 0 ? void 0 : options.color) || '#fff';
100
- el.style.background = (options === null || options === void 0 ? void 0 : options.background) || 'rgba(0,0,0,0.6)';
101
- el.style.padding = (options === null || options === void 0 ? void 0 : options.padding) || '4px 8px';
102
- el.style.borderRadius = (options === null || options === void 0 ? void 0 : options.borderRadius) || '4px';
103
- el.style.fontSize = (options === null || options === void 0 ? void 0 : options.fontSize) || '14px';
98
+ el.style.color = options?.color || '#fff';
99
+ el.style.background = options?.background || 'rgba(0,0,0,0.6)';
100
+ el.style.padding = options?.padding || '4px 8px';
101
+ el.style.borderRadius = options?.borderRadius || '4px';
102
+ el.style.fontSize = options?.fontSize || '14px';
104
103
  el.style.transform = 'translate(-50%, -100%)'; // Position label directly above the model
105
104
  el.style.whiteSpace = 'nowrap';
106
105
  el.style.pointerEvents = 'none';
@@ -481,6 +480,67 @@ function initPostProcessing(renderer, scene, camera, options = {}) {
481
480
  };
482
481
  }
483
482
 
483
+ /**
484
+ * ResourceManager
485
+ * Handles tracking and disposal of Three.js objects to prevent memory leaks.
486
+ */
487
+ class ResourceManager {
488
+ constructor() {
489
+ this.geometries = new Set();
490
+ this.materials = new Set();
491
+ this.textures = new Set();
492
+ this.objects = new Set();
493
+ }
494
+ /**
495
+ * Track an object and its resources recursively
496
+ */
497
+ track(object) {
498
+ this.objects.add(object);
499
+ object.traverse((child) => {
500
+ if (child.isMesh) {
501
+ const mesh = child;
502
+ if (mesh.geometry)
503
+ this.geometries.add(mesh.geometry);
504
+ if (mesh.material) {
505
+ if (Array.isArray(mesh.material)) {
506
+ mesh.material.forEach(m => this.trackMaterial(m));
507
+ }
508
+ else {
509
+ this.trackMaterial(mesh.material);
510
+ }
511
+ }
512
+ }
513
+ });
514
+ return object;
515
+ }
516
+ trackMaterial(material) {
517
+ this.materials.add(material);
518
+ // Track textures in material
519
+ for (const value of Object.values(material)) {
520
+ if (value instanceof THREE__namespace.Texture) {
521
+ this.textures.add(value);
522
+ }
523
+ }
524
+ }
525
+ /**
526
+ * Dispose all tracked resources
527
+ */
528
+ dispose() {
529
+ this.geometries.forEach(g => g.dispose());
530
+ this.materials.forEach(m => m.dispose());
531
+ this.textures.forEach(t => t.dispose());
532
+ this.objects.forEach(obj => {
533
+ if (obj.parent) {
534
+ obj.parent.remove(obj);
535
+ }
536
+ });
537
+ this.geometries.clear();
538
+ this.materials.clear();
539
+ this.textures.clear();
540
+ this.objects.clear();
541
+ }
542
+ }
543
+
484
544
  /**
485
545
  * @file clickHandler.ts
486
546
  * @description
@@ -632,7 +692,6 @@ function createModelClickHandler(camera, scene, renderer, outlinePass, onClick,
632
692
  */
633
693
  class ArrowGuide {
634
694
  constructor(renderer, camera, scene, options) {
635
- var _a, _b, _c;
636
695
  this.renderer = renderer;
637
696
  this.camera = camera;
638
697
  this.scene = scene;
@@ -651,10 +710,10 @@ class ArrowGuide {
651
710
  // Config: Non-highlight opacity and brightness
652
711
  this.fadeOpacity = 0.5;
653
712
  this.fadeBrightness = 0.1;
654
- this.clickThreshold = (_a = options === null || options === void 0 ? void 0 : options.clickThreshold) !== null && _a !== void 0 ? _a : 10;
655
- this.ignoreRaycastNames = new Set((options === null || options === void 0 ? void 0 : options.ignoreRaycastNames) || []);
656
- this.fadeOpacity = (_b = options === null || options === void 0 ? void 0 : options.fadeOpacity) !== null && _b !== void 0 ? _b : 0.5;
657
- this.fadeBrightness = (_c = options === null || options === void 0 ? void 0 : options.fadeBrightness) !== null && _c !== void 0 ? _c : 0.1;
713
+ this.clickThreshold = options?.clickThreshold ?? 10;
714
+ this.ignoreRaycastNames = new Set(options?.ignoreRaycastNames || []);
715
+ this.fadeOpacity = options?.fadeOpacity ?? 0.5;
716
+ this.fadeBrightness = options?.fadeBrightness ?? 0.1;
658
717
  this.abortController = new AbortController();
659
718
  this.initEvents();
660
719
  }
@@ -944,12 +1003,11 @@ class LiquidFillerGroup {
944
1003
  this.abortController = new AbortController();
945
1004
  const modelArray = Array.isArray(models) ? models : [models];
946
1005
  modelArray.forEach(model => {
947
- var _a, _b, _c;
948
1006
  try {
949
1007
  const options = {
950
- color: (_a = defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.color) !== null && _a !== void 0 ? _a : 0x00ff00,
951
- opacity: (_b = defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.opacity) !== null && _b !== void 0 ? _b : 0.6,
952
- speed: (_c = defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.speed) !== null && _c !== void 0 ? _c : 0.05,
1008
+ color: defaultOptions?.color ?? 0x00ff00,
1009
+ opacity: defaultOptions?.opacity ?? 0.6,
1010
+ speed: defaultOptions?.speed ?? 0.05,
953
1011
  };
954
1012
  // Save original materials
955
1013
  const originalMaterials = new Map();
@@ -1196,7 +1254,6 @@ const EASING_FUNCTIONS = {
1196
1254
  * - Robust error handling
1197
1255
  */
1198
1256
  function followModels(camera, targets, options = {}) {
1199
- var _a, _b, _c, _d, _e, _f;
1200
1257
  // Cancel previous animation
1201
1258
  cancelFollow(camera);
1202
1259
  // Boundary check
@@ -1223,14 +1280,14 @@ function followModels(camera, targets, options = {}) {
1223
1280
  box.getBoundingSphere(sphere);
1224
1281
  const center = sphere.center.clone();
1225
1282
  const radiusBase = Math.max(0.001, sphere.radius);
1226
- const duration = (_a = options.duration) !== null && _a !== void 0 ? _a : 700;
1227
- const padding = (_b = options.padding) !== null && _b !== void 0 ? _b : 1.0;
1283
+ const duration = options.duration ?? 700;
1284
+ const padding = options.padding ?? 1.0;
1228
1285
  const minDistance = options.minDistance;
1229
1286
  const maxDistance = options.maxDistance;
1230
- const controls = (_c = options.controls) !== null && _c !== void 0 ? _c : null;
1231
- const azimuth = (_d = options.azimuth) !== null && _d !== void 0 ? _d : Math.PI / 4;
1232
- const elevation = (_e = options.elevation) !== null && _e !== void 0 ? _e : Math.PI / 4;
1233
- const easing = (_f = options.easing) !== null && _f !== void 0 ? _f : 'easeOut';
1287
+ const controls = options.controls ?? null;
1288
+ const azimuth = options.azimuth ?? Math.PI / 4;
1289
+ const elevation = options.elevation ?? Math.PI / 4;
1290
+ const easing = options.easing ?? 'easeOut';
1234
1291
  const onProgress = options.onProgress;
1235
1292
  // Get easing function
1236
1293
  const easingFn = EASING_FUNCTIONS[easing] || EASING_FUNCTIONS.easeOut;
@@ -1368,9 +1425,6 @@ function setView(camera, controls, targetObj, position = 'front', options = {})
1368
1425
  console.warn('setView: Failed to calculate bounding box');
1369
1426
  return Promise.reject(new Error('Invalid bounding box'));
1370
1427
  }
1371
- const center = box.getCenter(new THREE__namespace.Vector3());
1372
- const size = box.getSize(new THREE__namespace.Vector3());
1373
- const maxSize = Math.max(size.x, size.y, size.z);
1374
1428
  // Use mapping table for creating view angles
1375
1429
  const viewAngles = {
1376
1430
  'front': { azimuth: 0, elevation: 0 },
@@ -1422,37 +1476,22 @@ const ViewPresets = {
1422
1476
  top: (camera, controls, target, options) => setView(camera, controls, target, 'top', options)
1423
1477
  };
1424
1478
 
1425
- /******************************************************************************
1426
- Copyright (c) Microsoft Corporation.
1427
-
1428
- Permission to use, copy, modify, and/or distribute this software for any
1429
- purpose with or without fee is hereby granted.
1430
-
1431
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
1432
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
1433
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
1434
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1435
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1436
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1437
- PERFORMANCE OF THIS SOFTWARE.
1438
- ***************************************************************************** */
1439
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
1440
-
1441
-
1442
- function __awaiter(thisArg, _arguments, P, generator) {
1443
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1444
- return new (P || (P = Promise))(function (resolve, reject) {
1445
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1446
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1447
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1448
- step((generator = generator.apply(thisArg, _arguments || [])).next());
1449
- });
1450
- }
1451
-
1452
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
1453
- var e = new Error(message);
1454
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
1479
+ let globalConfig = {
1480
+ dracoDecoderPath: '/draco/',
1481
+ ktx2TranscoderPath: '/basis/',
1455
1482
  };
1483
+ /**
1484
+ * Update global loader configuration (e.g., set path to CDN)
1485
+ */
1486
+ function setLoaderConfig(config) {
1487
+ globalConfig = { ...globalConfig, ...config };
1488
+ }
1489
+ /**
1490
+ * Get current global loader configuration
1491
+ */
1492
+ function getLoaderConfig() {
1493
+ return globalConfig;
1494
+ }
1456
1495
 
1457
1496
  /**
1458
1497
  * @file modelLoader.ts
@@ -1470,19 +1509,22 @@ const DEFAULT_OPTIONS$1 = {
1470
1509
  maxTextureSize: null,
1471
1510
  useSimpleMaterials: false,
1472
1511
  skipSkinned: true,
1512
+ useCache: true,
1473
1513
  };
1514
+ const modelCache = new Map();
1474
1515
  /** Automatically determine which options to enable based on extension (smart judgment) */
1475
1516
  function normalizeOptions(url, opts) {
1476
1517
  const ext = (url.split('.').pop() || '').toLowerCase();
1477
- const merged = Object.assign(Object.assign({}, DEFAULT_OPTIONS$1), opts);
1518
+ const merged = { ...DEFAULT_OPTIONS$1, ...opts };
1478
1519
  if (ext === 'gltf' || ext === 'glb') {
1520
+ const globalConfig = getLoaderConfig();
1479
1521
  // gltf/glb defaults to trying draco/ktx2 if user didn't specify
1480
1522
  if (merged.dracoDecoderPath === undefined)
1481
- merged.dracoDecoderPath = '/draco/';
1523
+ merged.dracoDecoderPath = globalConfig.dracoDecoderPath;
1482
1524
  if (merged.useKTX2 === undefined)
1483
1525
  merged.useKTX2 = true;
1484
1526
  if (merged.ktx2TranscoderPath === undefined)
1485
- merged.ktx2TranscoderPath = '/basis/';
1527
+ merged.ktx2TranscoderPath = globalConfig.ktx2TranscoderPath;
1486
1528
  }
1487
1529
  else {
1488
1530
  // fbx/obj/ply/stl etc. do not need draco/ktx2
@@ -1492,103 +1534,108 @@ function normalizeOptions(url, opts) {
1492
1534
  }
1493
1535
  return merged;
1494
1536
  }
1495
- function loadModelByUrl(url_1) {
1496
- return __awaiter(this, arguments, void 0, function* (url, options = {}) {
1497
- var _a, _b;
1498
- if (!url)
1499
- throw new Error('url required');
1500
- const ext = (url.split('.').pop() || '').toLowerCase();
1501
- const opts = normalizeOptions(url, options);
1502
- const manager = (_a = opts.manager) !== null && _a !== void 0 ? _a : new THREE__namespace.LoadingManager();
1503
- let loader;
1504
- if (ext === 'gltf' || ext === 'glb') {
1505
- const { GLTFLoader } = yield import('three/examples/jsm/loaders/GLTFLoader.js');
1506
- const gltfLoader = new GLTFLoader(manager);
1507
- if (opts.dracoDecoderPath) {
1508
- const { DRACOLoader } = yield import('three/examples/jsm/loaders/DRACOLoader.js');
1509
- const draco = new DRACOLoader();
1510
- draco.setDecoderPath(opts.dracoDecoderPath);
1511
- gltfLoader.setDRACOLoader(draco);
1512
- }
1513
- if (opts.useKTX2 && opts.ktx2TranscoderPath) {
1514
- const { KTX2Loader } = yield import('three/examples/jsm/loaders/KTX2Loader.js');
1515
- const ktx2Loader = new KTX2Loader().setTranscoderPath(opts.ktx2TranscoderPath);
1516
- gltfLoader.__ktx2Loader = ktx2Loader;
1517
- }
1518
- loader = gltfLoader;
1519
- }
1520
- else if (ext === 'fbx') {
1521
- const { FBXLoader } = yield import('three/examples/jsm/loaders/FBXLoader.js');
1522
- loader = new FBXLoader(manager);
1523
- }
1524
- else if (ext === 'obj') {
1525
- const { OBJLoader } = yield import('three/examples/jsm/loaders/OBJLoader.js');
1526
- loader = new OBJLoader(manager);
1527
- }
1528
- else if (ext === 'ply') {
1529
- const { PLYLoader } = yield import('three/examples/jsm/loaders/PLYLoader.js');
1530
- loader = new PLYLoader(manager);
1531
- }
1532
- else if (ext === 'stl') {
1533
- const { STLLoader } = yield import('three/examples/jsm/loaders/STLLoader.js');
1534
- loader = new STLLoader(manager);
1537
+ async function loadModelByUrl(url, options = {}) {
1538
+ if (!url)
1539
+ throw new Error('url required');
1540
+ const ext = (url.split('.').pop() || '').toLowerCase();
1541
+ const opts = normalizeOptions(url, options);
1542
+ const manager = opts.manager ?? new THREE__namespace.LoadingManager();
1543
+ // Cache key includes URL and relevant optimization options
1544
+ const cacheKey = `${url}_${opts.mergeGeometries}_${opts.maxTextureSize}_${opts.useSimpleMaterials}`;
1545
+ if (opts.useCache && modelCache.has(cacheKey)) {
1546
+ return modelCache.get(cacheKey).clone();
1547
+ }
1548
+ let loader;
1549
+ if (ext === 'gltf' || ext === 'glb') {
1550
+ const { GLTFLoader } = await import('three/examples/jsm/loaders/GLTFLoader.js');
1551
+ const gltfLoader = new GLTFLoader(manager);
1552
+ if (opts.dracoDecoderPath) {
1553
+ const { DRACOLoader } = await import('three/examples/jsm/loaders/DRACOLoader.js');
1554
+ const draco = new DRACOLoader();
1555
+ draco.setDecoderPath(opts.dracoDecoderPath);
1556
+ gltfLoader.setDRACOLoader(draco);
1535
1557
  }
1536
- else {
1537
- throw new Error(`Unsupported model extension: .${ext}`);
1538
- }
1539
- const object = yield new Promise((resolve, reject) => {
1540
- loader.load(url, (res) => {
1541
- var _a;
1542
- if (ext === 'gltf' || ext === 'glb') {
1543
- const sceneObj = res.scene || res;
1544
- // --- Critical: Expose animations to scene.userData (or scene.animations) ---
1545
- // So the caller can access clips simply by getting sceneObj.userData.animations
1546
- sceneObj.userData = (sceneObj === null || sceneObj === void 0 ? void 0 : sceneObj.userData) || {};
1547
- sceneObj.userData.animations = (_a = res.animations) !== null && _a !== void 0 ? _a : [];
1548
- resolve(sceneObj);
1549
- }
1550
- else {
1551
- resolve(res);
1552
- }
1553
- }, undefined, (err) => reject(err));
1554
- });
1555
- // Optimize
1556
- object.traverse((child) => {
1557
- var _a, _b, _c;
1558
- const mesh = child;
1559
- if (mesh.isMesh && mesh.geometry && !mesh.geometry.isBufferGeometry) {
1560
- try {
1561
- 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;
1562
- }
1563
- catch (_d) { }
1564
- }
1565
- });
1566
- if (opts.maxTextureSize && opts.maxTextureSize > 0)
1567
- downscaleTexturesInObject(object, opts.maxTextureSize);
1568
- if (opts.useSimpleMaterials) {
1569
- object.traverse((child) => {
1570
- const m = child.material;
1571
- if (!m)
1572
- return;
1573
- if (Array.isArray(m))
1574
- child.material = m.map((mat) => toSimpleMaterial(mat));
1575
- else
1576
- child.material = toSimpleMaterial(m);
1577
- });
1558
+ if (opts.useKTX2 && opts.ktx2TranscoderPath) {
1559
+ const { KTX2Loader } = await import('three/examples/jsm/loaders/KTX2Loader.js');
1560
+ const ktx2Loader = new KTX2Loader().setTranscoderPath(opts.ktx2TranscoderPath);
1561
+ gltfLoader.__ktx2Loader = ktx2Loader;
1578
1562
  }
1579
- if (opts.mergeGeometries) {
1580
- try {
1581
- yield tryMergeGeometries(object, { skipSkinned: (_b = opts.skipSkinned) !== null && _b !== void 0 ? _b : true });
1563
+ loader = gltfLoader;
1564
+ }
1565
+ else if (ext === 'fbx') {
1566
+ const { FBXLoader } = await import('three/examples/jsm/loaders/FBXLoader.js');
1567
+ loader = new FBXLoader(manager);
1568
+ }
1569
+ else if (ext === 'obj') {
1570
+ const { OBJLoader } = await import('three/examples/jsm/loaders/OBJLoader.js');
1571
+ loader = new OBJLoader(manager);
1572
+ }
1573
+ else if (ext === 'ply') {
1574
+ const { PLYLoader } = await import('three/examples/jsm/loaders/PLYLoader.js');
1575
+ loader = new PLYLoader(manager);
1576
+ }
1577
+ else if (ext === 'stl') {
1578
+ const { STLLoader } = await import('three/examples/jsm/loaders/STLLoader.js');
1579
+ loader = new STLLoader(manager);
1580
+ }
1581
+ else {
1582
+ throw new Error(`Unsupported model extension: .${ext}`);
1583
+ }
1584
+ const object = await new Promise((resolve, reject) => {
1585
+ loader.load(url, (res) => {
1586
+ if (ext === 'gltf' || ext === 'glb') {
1587
+ const sceneObj = res.scene || res;
1588
+ // --- Critical: Expose animations to scene.userData (or scene.animations) ---
1589
+ // So the caller can access clips simply by getting sceneObj.userData.animations
1590
+ sceneObj.userData = sceneObj?.userData || {};
1591
+ sceneObj.userData.animations = res.animations ?? [];
1592
+ resolve(sceneObj);
1582
1593
  }
1583
- catch (e) {
1584
- console.warn('mergeGeometries failed', e);
1594
+ else {
1595
+ resolve(res);
1596
+ }
1597
+ }, undefined, (err) => reject(err));
1598
+ });
1599
+ // Optimize
1600
+ object.traverse((child) => {
1601
+ const mesh = child;
1602
+ if (mesh.isMesh && mesh.geometry && !mesh.geometry.isBufferGeometry) {
1603
+ try {
1604
+ mesh.geometry = new THREE__namespace.BufferGeometry().fromGeometry?.(mesh.geometry) ?? mesh.geometry;
1585
1605
  }
1606
+ catch { }
1586
1607
  }
1587
- return object;
1588
1608
  });
1609
+ if (opts.maxTextureSize && opts.maxTextureSize > 0)
1610
+ await downscaleTexturesInObject(object, opts.maxTextureSize);
1611
+ if (opts.useSimpleMaterials) {
1612
+ object.traverse((child) => {
1613
+ const m = child.material;
1614
+ if (!m)
1615
+ return;
1616
+ if (Array.isArray(m))
1617
+ child.material = m.map((mat) => toSimpleMaterial(mat));
1618
+ else
1619
+ child.material = toSimpleMaterial(m);
1620
+ });
1621
+ }
1622
+ if (opts.mergeGeometries) {
1623
+ try {
1624
+ await tryMergeGeometries(object, { skipSkinned: opts.skipSkinned ?? true });
1625
+ }
1626
+ catch (e) {
1627
+ console.warn('mergeGeometries failed', e);
1628
+ }
1629
+ }
1630
+ if (opts.useCache) {
1631
+ modelCache.set(cacheKey, object);
1632
+ return object.clone();
1633
+ }
1634
+ return object;
1589
1635
  }
1590
- /** Runtime downscale textures in mesh to maxSize (canvas drawImage) to save GPU memory */
1591
- function downscaleTexturesInObject(obj, maxSize) {
1636
+ /** Runtime downscale textures in mesh to maxSize (createImageBitmap or canvas) to save GPU memory */
1637
+ async function downscaleTexturesInObject(obj, maxSize) {
1638
+ const tasks = [];
1592
1639
  obj.traverse((ch) => {
1593
1640
  if (!ch.isMesh)
1594
1641
  return;
@@ -1607,27 +1654,44 @@ function downscaleTexturesInObject(obj, maxSize) {
1607
1654
  const max = maxSize;
1608
1655
  if (image.width <= max && image.height <= max)
1609
1656
  return;
1610
- // downscale using canvas (sync, may be heavy for many textures)
1611
- try {
1612
- const scale = Math.min(max / image.width, max / image.height);
1613
- const canvas = document.createElement('canvas');
1614
- canvas.width = Math.floor(image.width * scale);
1615
- canvas.height = Math.floor(image.height * scale);
1616
- const ctx = canvas.getContext('2d');
1617
- if (ctx) {
1618
- ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
1619
- const newTex = new THREE__namespace.Texture(canvas);
1620
- newTex.needsUpdate = true;
1621
- // copy common settings (encoding etc)
1622
- newTex.encoding = tex.encoding;
1623
- mat[p] = newTex;
1657
+ tasks.push((async () => {
1658
+ try {
1659
+ const scale = Math.min(max / image.width, max / image.height);
1660
+ const newWidth = Math.floor(image.width * scale);
1661
+ const newHeight = Math.floor(image.height * scale);
1662
+ let newSource;
1663
+ if (typeof createImageBitmap !== 'undefined') {
1664
+ newSource = await createImageBitmap(image, {
1665
+ resizeWidth: newWidth,
1666
+ resizeHeight: newHeight,
1667
+ resizeQuality: 'high'
1668
+ });
1669
+ }
1670
+ else {
1671
+ // Fallback for environments without createImageBitmap
1672
+ const canvas = document.createElement('canvas');
1673
+ canvas.width = newWidth;
1674
+ canvas.height = newHeight;
1675
+ const ctx = canvas.getContext('2d');
1676
+ if (ctx) {
1677
+ ctx.drawImage(image, 0, 0, newWidth, newHeight);
1678
+ newSource = canvas;
1679
+ }
1680
+ }
1681
+ if (newSource) {
1682
+ const newTex = new THREE__namespace.Texture(newSource);
1683
+ newTex.needsUpdate = true;
1684
+ newTex.encoding = tex.encoding;
1685
+ mat[p] = newTex;
1686
+ }
1624
1687
  }
1625
- }
1626
- catch (e) {
1627
- console.warn('downscale texture failed', e);
1628
- }
1688
+ catch (e) {
1689
+ console.warn('downscale texture failed', e);
1690
+ }
1691
+ })());
1629
1692
  });
1630
1693
  });
1694
+ await Promise.all(tasks);
1631
1695
  }
1632
1696
  /**
1633
1697
  * Try to merge geometries in object (Only merge: non-transparent, non-SkinnedMesh, attribute compatible BufferGeometry)
@@ -1635,81 +1699,77 @@ function downscaleTexturesInObject(obj, maxSize) {
1635
1699
  * - Merging will group by material UUID (different materials cannot be merged)
1636
1700
  * - Merge function is compatible with common export names of BufferGeometryUtils
1637
1701
  */
1638
- function tryMergeGeometries(root, opts) {
1639
- return __awaiter(this, void 0, void 0, function* () {
1640
- // collect meshes by material uuid
1641
- const groups = new Map();
1642
- root.traverse((ch) => {
1643
- var _a;
1644
- if (!ch.isMesh)
1645
- return;
1646
- const mesh = ch;
1647
- if (opts.skipSkinned && mesh.isSkinnedMesh)
1648
- return;
1649
- const mat = mesh.material;
1650
- // don't merge transparent or morph-enabled or skinned meshes
1651
- if (!mesh.geometry || mesh.visible === false)
1652
- return;
1653
- if (mat && mat.transparent)
1654
- return;
1655
- const geom = mesh.geometry.clone();
1656
- mesh.updateWorldMatrix(true, false);
1657
- geom.applyMatrix4(mesh.matrixWorld);
1658
- // ensure attributes compatible? we'll rely on merge function to return null if incompatible
1659
- const key = (mat && mat.uuid) || 'default';
1660
- const bucket = (_a = groups.get(key)) !== null && _a !== void 0 ? _a : { material: mat !== null && mat !== void 0 ? mat : new THREE__namespace.MeshStandardMaterial(), geoms: [] };
1661
- bucket.geoms.push(geom);
1662
- groups.set(key, bucket);
1663
- // mark for removal (we'll remove meshes after)
1664
- mesh.userData.__toRemoveForMerge = true;
1665
- });
1666
- if (groups.size === 0)
1702
+ async function tryMergeGeometries(root, opts) {
1703
+ // collect meshes by material uuid
1704
+ const groups = new Map();
1705
+ root.traverse((ch) => {
1706
+ if (!ch.isMesh)
1667
1707
  return;
1668
- // dynamic import BufferGeometryUtils and find merge function name
1669
- const bufUtilsMod = yield import('three/examples/jsm/utils/BufferGeometryUtils.js');
1670
- // use || chain (avoid mixing ?? with || without parentheses)
1671
- const mergeFn = bufUtilsMod.mergeBufferGeometries ||
1672
- bufUtilsMod.mergeGeometries ||
1673
- bufUtilsMod.mergeBufferGeometries || // defensive duplicate
1674
- bufUtilsMod.mergeGeometries;
1675
- if (!mergeFn)
1676
- throw new Error('No merge function found in BufferGeometryUtils');
1677
- // for each group, try merge
1678
- for (const [key, { material, geoms }] of groups) {
1679
- if (geoms.length <= 1) {
1680
- // nothing to merge
1681
- continue;
1682
- }
1683
- // call merge function - signature typically mergeBufferGeometries(array, useGroups)
1684
- const merged = mergeFn(geoms, false);
1685
- if (!merged) {
1686
- console.warn('merge returned null for group', key);
1687
- continue;
1708
+ const mesh = ch;
1709
+ if (opts.skipSkinned && mesh.isSkinnedMesh)
1710
+ return;
1711
+ const mat = mesh.material;
1712
+ // don't merge transparent or morph-enabled or skinned meshes
1713
+ if (!mesh.geometry || mesh.visible === false)
1714
+ return;
1715
+ if (mat && mat.transparent)
1716
+ return;
1717
+ const geom = mesh.geometry.clone();
1718
+ mesh.updateWorldMatrix(true, false);
1719
+ geom.applyMatrix4(mesh.matrixWorld);
1720
+ // ensure attributes compatible? we'll rely on merge function to return null if incompatible
1721
+ const key = (mat && mat.uuid) || 'default';
1722
+ const bucket = groups.get(key) ?? { material: mat ?? new THREE__namespace.MeshStandardMaterial(), geoms: [] };
1723
+ bucket.geoms.push(geom);
1724
+ groups.set(key, bucket);
1725
+ // mark for removal (we'll remove meshes after)
1726
+ mesh.userData.__toRemoveForMerge = true;
1727
+ });
1728
+ if (groups.size === 0)
1729
+ return;
1730
+ // dynamic import BufferGeometryUtils and find merge function name
1731
+ const bufUtilsMod = await import('three/examples/jsm/utils/BufferGeometryUtils.js');
1732
+ // use || chain (avoid mixing ?? with || without parentheses)
1733
+ const mergeFn = bufUtilsMod.mergeBufferGeometries ||
1734
+ bufUtilsMod.mergeGeometries ||
1735
+ bufUtilsMod.mergeBufferGeometries || // defensive duplicate
1736
+ bufUtilsMod.mergeGeometries;
1737
+ if (!mergeFn)
1738
+ throw new Error('No merge function found in BufferGeometryUtils');
1739
+ // for each group, try merge
1740
+ for (const [key, { material, geoms }] of groups) {
1741
+ if (geoms.length <= 1) {
1742
+ // nothing to merge
1743
+ continue;
1744
+ }
1745
+ // call merge function - signature typically mergeBufferGeometries(array, useGroups)
1746
+ const merged = mergeFn(geoms, false);
1747
+ if (!merged) {
1748
+ console.warn('merge returned null for group', key);
1749
+ continue;
1750
+ }
1751
+ // create merged mesh at root (world-space geometry already applied)
1752
+ const mergedMesh = new THREE__namespace.Mesh(merged, material);
1753
+ root.add(mergedMesh);
1754
+ }
1755
+ // now remove original meshes flagged for removal
1756
+ const toRemove = [];
1757
+ root.traverse((ch) => {
1758
+ if (ch.userData?.__toRemoveForMerge)
1759
+ toRemove.push(ch);
1760
+ });
1761
+ toRemove.forEach((m) => {
1762
+ if (m.parent)
1763
+ m.parent.remove(m);
1764
+ // free original resources (geometries already cloned/applied), but careful with shared materials
1765
+ if (m.isMesh) {
1766
+ const mm = m;
1767
+ try {
1768
+ mm.geometry.dispose();
1688
1769
  }
1689
- // create merged mesh at root (world-space geometry already applied)
1690
- const mergedMesh = new THREE__namespace.Mesh(merged, material);
1691
- root.add(mergedMesh);
1770
+ catch { }
1771
+ // we do NOT dispose material because it may be reused by mergedMesh
1692
1772
  }
1693
- // now remove original meshes flagged for removal
1694
- const toRemove = [];
1695
- root.traverse((ch) => {
1696
- var _a;
1697
- if ((_a = ch.userData) === null || _a === void 0 ? void 0 : _a.__toRemoveForMerge)
1698
- toRemove.push(ch);
1699
- });
1700
- toRemove.forEach((m) => {
1701
- if (m.parent)
1702
- m.parent.remove(m);
1703
- // free original resources (geometries already cloned/applied), but careful with shared materials
1704
- if (m.isMesh) {
1705
- const mm = m;
1706
- try {
1707
- mm.geometry.dispose();
1708
- }
1709
- catch (_a) { }
1710
- // we do NOT dispose material because it may be reused by mergedMesh
1711
- }
1712
- });
1713
1773
  });
1714
1774
  }
1715
1775
  /* ---------------------
@@ -1726,7 +1786,7 @@ function disposeObject(obj) {
1726
1786
  try {
1727
1787
  m.geometry.dispose();
1728
1788
  }
1729
- catch (_a) { }
1789
+ catch { }
1730
1790
  }
1731
1791
  const mat = m.material;
1732
1792
  if (mat) {
@@ -1748,14 +1808,14 @@ function disposeMaterial(mat) {
1748
1808
  try {
1749
1809
  mat[k].dispose();
1750
1810
  }
1751
- catch (_a) { }
1811
+ catch { }
1752
1812
  }
1753
1813
  });
1754
1814
  try {
1755
1815
  if (typeof mat.dispose === 'function')
1756
1816
  mat.dispose();
1757
1817
  }
1758
- catch (_a) { }
1818
+ catch { }
1759
1819
  }
1760
1820
  // Helper to convert to simple material (stub)
1761
1821
  function toSimpleMaterial(mat) {
@@ -1798,100 +1858,97 @@ const equirectCache = new Map();
1798
1858
  * @param paths string[] 6 image paths, order: [px, nx, py, ny, pz, nz]
1799
1859
  * @param opts SkyboxOptions
1800
1860
  */
1801
- function loadCubeSkybox(renderer_1, scene_1, paths_1) {
1802
- return __awaiter(this, arguments, void 0, function* (renderer, scene, paths, opts = {}) {
1803
- var _a, _b;
1804
- const options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), opts);
1805
- if (!Array.isArray(paths) || paths.length !== 6)
1806
- throw new Error('cube skybox requires 6 image paths');
1807
- const key = paths.join('|');
1808
- // Cache handling
1809
- if (options.cache && cubeCache.has(key)) {
1810
- const rec = cubeCache.get(key);
1811
- rec.refCount += 1;
1812
- // reapply to scene (in case it was removed)
1813
- if (options.setAsBackground)
1814
- scene.background = rec.handle.backgroundTexture;
1815
- if (options.setAsEnvironment && rec.handle.envRenderTarget)
1816
- scene.environment = rec.handle.envRenderTarget.texture;
1817
- return rec.handle;
1818
- }
1819
- // Load cube texture
1820
- const loader = new THREE__namespace.CubeTextureLoader();
1821
- const texture = yield new Promise((resolve, reject) => {
1822
- loader.load(paths, (tex) => resolve(tex), undefined, (err) => reject(err));
1823
- });
1824
- // Set encoding and mapping
1825
- if (options.useSRGBEncoding)
1826
- texture.encoding = THREE__namespace.sRGBEncoding;
1827
- texture.mapping = THREE__namespace.CubeReflectionMapping;
1828
- // apply as background if required
1861
+ async function loadCubeSkybox(renderer, scene, paths, opts = {}) {
1862
+ const options = { ...DEFAULT_OPTIONS, ...opts };
1863
+ if (!Array.isArray(paths) || paths.length !== 6)
1864
+ throw new Error('cube skybox requires 6 image paths');
1865
+ const key = paths.join('|');
1866
+ // Cache handling
1867
+ if (options.cache && cubeCache.has(key)) {
1868
+ const rec = cubeCache.get(key);
1869
+ rec.refCount += 1;
1870
+ // reapply to scene (in case it was removed)
1829
1871
  if (options.setAsBackground)
1830
- scene.background = texture;
1831
- // environment: use PMREM to produce a proper prefiltered env map for PBR
1832
- let pmremGenerator = (_a = options.pmremGenerator) !== null && _a !== void 0 ? _a : new THREE__namespace.PMREMGenerator(renderer);
1833
- (_b = pmremGenerator.compileCubemapShader) === null || _b === void 0 ? void 0 : _b.call(pmremGenerator);
1834
- // fromCubemap might be available in your three.js; fallback to fromEquirectangular approach if not
1835
- let envRenderTarget = null;
1836
- if (pmremGenerator.fromCubemap) {
1837
- envRenderTarget = pmremGenerator.fromCubemap(texture);
1872
+ scene.background = rec.handle.backgroundTexture;
1873
+ if (options.setAsEnvironment && rec.handle.envRenderTarget)
1874
+ scene.environment = rec.handle.envRenderTarget.texture;
1875
+ return rec.handle;
1876
+ }
1877
+ // Load cube texture
1878
+ const loader = new THREE__namespace.CubeTextureLoader();
1879
+ const texture = await new Promise((resolve, reject) => {
1880
+ loader.load(paths, (tex) => resolve(tex), undefined, (err) => reject(err));
1881
+ });
1882
+ // Set encoding and mapping
1883
+ if (options.useSRGBEncoding)
1884
+ texture.encoding = THREE__namespace.sRGBEncoding;
1885
+ texture.mapping = THREE__namespace.CubeReflectionMapping;
1886
+ // apply as background if required
1887
+ if (options.setAsBackground)
1888
+ scene.background = texture;
1889
+ // environment: use PMREM to produce a proper prefiltered env map for PBR
1890
+ let pmremGenerator = options.pmremGenerator ?? new THREE__namespace.PMREMGenerator(renderer);
1891
+ pmremGenerator.compileCubemapShader?.( /* optional */);
1892
+ // fromCubemap might be available in your three.js; fallback to fromEquirectangular approach if not
1893
+ let envRenderTarget = null;
1894
+ if (pmremGenerator.fromCubemap) {
1895
+ envRenderTarget = pmremGenerator.fromCubemap(texture);
1896
+ }
1897
+ else {
1898
+ // Fallback: render cube to env map by using generator.fromEquirectangular with a converted equirect if needed.
1899
+ // Simpler fallback: use the cube texture directly as environment (less correct for reflections).
1900
+ envRenderTarget = null;
1901
+ }
1902
+ if (options.setAsEnvironment) {
1903
+ if (envRenderTarget) {
1904
+ scene.environment = envRenderTarget.texture;
1838
1905
  }
1839
1906
  else {
1840
- // Fallback: render cube to env map by using generator.fromEquirectangular with a converted equirect if needed.
1841
- // Simpler fallback: use the cube texture directly as environment (less correct for reflections).
1842
- envRenderTarget = null;
1907
+ // fallback: use cube texture directly (works but not prefiltered)
1908
+ scene.environment = texture;
1843
1909
  }
1844
- if (options.setAsEnvironment) {
1845
- if (envRenderTarget) {
1846
- scene.environment = envRenderTarget.texture;
1910
+ }
1911
+ const handle = {
1912
+ key,
1913
+ backgroundTexture: options.setAsBackground ? texture : null,
1914
+ envRenderTarget: envRenderTarget,
1915
+ pmremGenerator: options.pmremGenerator ? null : pmremGenerator, // only dispose if we created it
1916
+ setAsBackground: !!options.setAsBackground,
1917
+ setAsEnvironment: !!options.setAsEnvironment,
1918
+ dispose() {
1919
+ // remove from scene
1920
+ if (options.setAsBackground && scene.background === texture)
1921
+ scene.background = null;
1922
+ if (options.setAsEnvironment && scene.environment) {
1923
+ // only clear if it's the same texture we set
1924
+ if (envRenderTarget && scene.environment === envRenderTarget.texture)
1925
+ scene.environment = null;
1926
+ else if (scene.environment === texture)
1927
+ scene.environment = null;
1847
1928
  }
1848
- else {
1849
- // fallback: use cube texture directly (works but not prefiltered)
1850
- scene.environment = texture;
1851
- }
1852
- }
1853
- const handle = {
1854
- key,
1855
- backgroundTexture: options.setAsBackground ? texture : null,
1856
- envRenderTarget: envRenderTarget,
1857
- pmremGenerator: options.pmremGenerator ? null : pmremGenerator, // only dispose if we created it
1858
- setAsBackground: !!options.setAsBackground,
1859
- setAsEnvironment: !!options.setAsEnvironment,
1860
- dispose() {
1861
- // remove from scene
1862
- if (options.setAsBackground && scene.background === texture)
1863
- scene.background = null;
1864
- if (options.setAsEnvironment && scene.environment) {
1865
- // only clear if it's the same texture we set
1866
- if (envRenderTarget && scene.environment === envRenderTarget.texture)
1867
- scene.environment = null;
1868
- else if (scene.environment === texture)
1869
- scene.environment = null;
1870
- }
1871
- // dispose resources only if not cached/shared
1872
- if (envRenderTarget) {
1873
- try {
1874
- envRenderTarget.dispose();
1875
- }
1876
- catch (_a) { }
1877
- }
1929
+ // dispose resources only if not cached/shared
1930
+ if (envRenderTarget) {
1878
1931
  try {
1879
- texture.dispose();
1932
+ envRenderTarget.dispose();
1880
1933
  }
1881
- catch (_b) { }
1882
- // dispose pmremGenerator we created
1883
- if (!options.pmremGenerator && pmremGenerator) {
1884
- try {
1885
- pmremGenerator.dispose();
1886
- }
1887
- catch (_c) { }
1934
+ catch { }
1935
+ }
1936
+ try {
1937
+ texture.dispose();
1938
+ }
1939
+ catch { }
1940
+ // dispose pmremGenerator we created
1941
+ if (!options.pmremGenerator && pmremGenerator) {
1942
+ try {
1943
+ pmremGenerator.dispose();
1888
1944
  }
1945
+ catch { }
1889
1946
  }
1890
- };
1891
- if (options.cache)
1892
- cubeCache.set(key, { handle, refCount: 1 });
1893
- return handle;
1894
- });
1947
+ }
1948
+ };
1949
+ if (options.cache)
1950
+ cubeCache.set(key, { handle, refCount: 1 });
1951
+ return handle;
1895
1952
  }
1896
1953
  /**
1897
1954
  * Load Equirectangular/Single Image (Supports HDR via RGBELoader)
@@ -1900,95 +1957,90 @@ function loadCubeSkybox(renderer_1, scene_1, paths_1) {
1900
1957
  * @param url string - *.hdr, *.exr, *.jpg, *.png
1901
1958
  * @param opts SkyboxOptions
1902
1959
  */
1903
- function loadEquirectSkybox(renderer_1, scene_1, url_1) {
1904
- return __awaiter(this, arguments, void 0, function* (renderer, scene, url, opts = {}) {
1905
- var _a, _b;
1906
- const options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), opts);
1907
- const key = url;
1908
- if (options.cache && equirectCache.has(key)) {
1909
- const rec = equirectCache.get(key);
1910
- rec.refCount += 1;
1911
- if (options.setAsBackground)
1912
- scene.background = rec.handle.backgroundTexture;
1913
- if (options.setAsEnvironment && rec.handle.envRenderTarget)
1914
- scene.environment = rec.handle.envRenderTarget.texture;
1915
- return rec.handle;
1916
- }
1917
- // Dynamically import RGBELoader (for .hdr/.exr), if loading normal jpg/png directly use TextureLoader
1918
- const isHDR = /\.hdr$|\.exr$/i.test(url);
1919
- let hdrTexture;
1920
- if (isHDR) {
1921
- const { RGBELoader } = yield import('three/examples/jsm/loaders/RGBELoader.js');
1922
- hdrTexture = yield new Promise((resolve, reject) => {
1923
- new RGBELoader().load(url, (tex) => resolve(tex), undefined, (err) => reject(err));
1924
- });
1925
- // RGBE textures typically use LinearEncoding
1926
- hdrTexture.encoding = THREE__namespace.LinearEncoding;
1927
- }
1928
- else {
1929
- // ordinary image - use TextureLoader
1930
- const loader = new THREE__namespace.TextureLoader();
1931
- hdrTexture = yield new Promise((resolve, reject) => {
1932
- loader.load(url, (t) => resolve(t), undefined, (err) => reject(err));
1933
- });
1934
- if (options.useSRGBEncoding)
1935
- hdrTexture.encoding = THREE__namespace.sRGBEncoding;
1936
- }
1937
- // PMREMGenerator to convert equirectangular to prefiltered cubemap (good for PBR)
1938
- const pmremGenerator = (_a = options.pmremGenerator) !== null && _a !== void 0 ? _a : new THREE__namespace.PMREMGenerator(renderer);
1939
- (_b = pmremGenerator.compileEquirectangularShader) === null || _b === void 0 ? void 0 : _b.call(pmremGenerator);
1940
- const envRenderTarget = pmremGenerator.fromEquirectangular(hdrTexture);
1941
- // envTexture to use for scene.environment
1942
- const envTexture = envRenderTarget.texture;
1943
- // set background and/or environment
1944
- if (options.setAsBackground) {
1945
- // for background it's ok to use the equirect texture directly or the envTexture
1946
- // envTexture is cubemap-like and usually better for reflections; using it as background creates cube-projected look
1947
- scene.background = envTexture;
1948
- }
1949
- if (options.setAsEnvironment) {
1950
- scene.environment = envTexture;
1951
- }
1952
- // We can dispose the original hdrTexture (the PMREM target contains the needed data)
1953
- try {
1954
- hdrTexture.dispose();
1955
- }
1956
- catch (_c) { }
1957
- const handle = {
1958
- key,
1959
- backgroundTexture: options.setAsBackground ? envTexture : null,
1960
- envRenderTarget,
1961
- pmremGenerator: options.pmremGenerator ? null : pmremGenerator,
1962
- setAsBackground: !!options.setAsBackground,
1963
- setAsEnvironment: !!options.setAsEnvironment,
1964
- dispose() {
1965
- if (options.setAsBackground && scene.background === envTexture)
1966
- scene.background = null;
1967
- if (options.setAsEnvironment && scene.environment === envTexture)
1968
- scene.environment = null;
1960
+ async function loadEquirectSkybox(renderer, scene, url, opts = {}) {
1961
+ const options = { ...DEFAULT_OPTIONS, ...opts };
1962
+ const key = url;
1963
+ if (options.cache && equirectCache.has(key)) {
1964
+ const rec = equirectCache.get(key);
1965
+ rec.refCount += 1;
1966
+ if (options.setAsBackground)
1967
+ scene.background = rec.handle.backgroundTexture;
1968
+ if (options.setAsEnvironment && rec.handle.envRenderTarget)
1969
+ scene.environment = rec.handle.envRenderTarget.texture;
1970
+ return rec.handle;
1971
+ }
1972
+ // Dynamically import RGBELoader (for .hdr/.exr), if loading normal jpg/png directly use TextureLoader
1973
+ const isHDR = /\.hdr$|\.exr$/i.test(url);
1974
+ let hdrTexture;
1975
+ if (isHDR) {
1976
+ const { RGBELoader } = await import('three/examples/jsm/loaders/RGBELoader.js');
1977
+ hdrTexture = await new Promise((resolve, reject) => {
1978
+ new RGBELoader().load(url, (tex) => resolve(tex), undefined, (err) => reject(err));
1979
+ });
1980
+ // RGBE textures typically use LinearEncoding
1981
+ hdrTexture.encoding = THREE__namespace.LinearEncoding;
1982
+ }
1983
+ else {
1984
+ // ordinary image - use TextureLoader
1985
+ const loader = new THREE__namespace.TextureLoader();
1986
+ hdrTexture = await new Promise((resolve, reject) => {
1987
+ loader.load(url, (t) => resolve(t), undefined, (err) => reject(err));
1988
+ });
1989
+ if (options.useSRGBEncoding)
1990
+ hdrTexture.encoding = THREE__namespace.sRGBEncoding;
1991
+ }
1992
+ // PMREMGenerator to convert equirectangular to prefiltered cubemap (good for PBR)
1993
+ const pmremGenerator = options.pmremGenerator ?? new THREE__namespace.PMREMGenerator(renderer);
1994
+ pmremGenerator.compileEquirectangularShader?.();
1995
+ const envRenderTarget = pmremGenerator.fromEquirectangular(hdrTexture);
1996
+ // envTexture to use for scene.environment
1997
+ const envTexture = envRenderTarget.texture;
1998
+ // set background and/or environment
1999
+ if (options.setAsBackground) {
2000
+ // for background it's ok to use the equirect texture directly or the envTexture
2001
+ // envTexture is cubemap-like and usually better for reflections; using it as background creates cube-projected look
2002
+ scene.background = envTexture;
2003
+ }
2004
+ if (options.setAsEnvironment) {
2005
+ scene.environment = envTexture;
2006
+ }
2007
+ // We can dispose the original hdrTexture (the PMREM target contains the needed data)
2008
+ try {
2009
+ hdrTexture.dispose();
2010
+ }
2011
+ catch { }
2012
+ const handle = {
2013
+ key,
2014
+ backgroundTexture: options.setAsBackground ? envTexture : null,
2015
+ envRenderTarget,
2016
+ pmremGenerator: options.pmremGenerator ? null : pmremGenerator,
2017
+ setAsBackground: !!options.setAsBackground,
2018
+ setAsEnvironment: !!options.setAsEnvironment,
2019
+ dispose() {
2020
+ if (options.setAsBackground && scene.background === envTexture)
2021
+ scene.background = null;
2022
+ if (options.setAsEnvironment && scene.environment === envTexture)
2023
+ scene.environment = null;
2024
+ try {
2025
+ envRenderTarget.dispose();
2026
+ }
2027
+ catch { }
2028
+ if (!options.pmremGenerator && pmremGenerator) {
1969
2029
  try {
1970
- envRenderTarget.dispose();
1971
- }
1972
- catch (_a) { }
1973
- if (!options.pmremGenerator && pmremGenerator) {
1974
- try {
1975
- pmremGenerator.dispose();
1976
- }
1977
- catch (_b) { }
2030
+ pmremGenerator.dispose();
1978
2031
  }
2032
+ catch { }
1979
2033
  }
1980
- };
1981
- if (options.cache)
1982
- equirectCache.set(key, { handle, refCount: 1 });
1983
- return handle;
1984
- });
2034
+ }
2035
+ };
2036
+ if (options.cache)
2037
+ equirectCache.set(key, { handle, refCount: 1 });
2038
+ return handle;
1985
2039
  }
1986
- function loadSkybox(renderer_1, scene_1, params_1) {
1987
- return __awaiter(this, arguments, void 0, function* (renderer, scene, params, opts = {}) {
1988
- if (params.type === 'cube')
1989
- return loadCubeSkybox(renderer, scene, params.paths, opts);
1990
- return loadEquirectSkybox(renderer, scene, params.url, opts);
1991
- });
2040
+ async function loadSkybox(renderer, scene, params, opts = {}) {
2041
+ if (params.type === 'cube')
2042
+ return loadCubeSkybox(renderer, scene, params.paths, opts);
2043
+ return loadEquirectSkybox(renderer, scene, params.url, opts);
1992
2044
  }
1993
2045
  /* -------------------------
1994
2046
  Cache / Reference Counting Helper Methods
@@ -2198,10 +2250,9 @@ class BlueSkyManager {
2198
2250
  * Usually called when the scene is completely destroyed or the application exits
2199
2251
  */
2200
2252
  destroy() {
2201
- var _a;
2202
2253
  this.cancelLoad();
2203
2254
  this.dispose();
2204
- (_a = this.pmremGen) === null || _a === void 0 ? void 0 : _a.dispose();
2255
+ this.pmremGen?.dispose();
2205
2256
  this.isInitialized = false;
2206
2257
  this.loadingState = 'idle';
2207
2258
  }
@@ -2235,20 +2286,19 @@ const BlueSky = new BlueSkyManager();
2235
2286
  * - RAF management optimization
2236
2287
  */
2237
2288
  function createModelsLabel(camera, renderer, parentModel, modelLabelsMap, options) {
2238
- var _a, _b, _c, _d, _e, _f;
2239
2289
  const cfg = {
2240
- fontSize: (options === null || options === void 0 ? void 0 : options.fontSize) || '12px',
2241
- color: (options === null || options === void 0 ? void 0 : options.color) || '#ffffff',
2242
- background: (options === null || options === void 0 ? void 0 : options.background) || '#1890ff',
2243
- padding: (options === null || options === void 0 ? void 0 : options.padding) || '6px 10px',
2244
- borderRadius: (options === null || options === void 0 ? void 0 : options.borderRadius) || '6px',
2245
- lift: (_a = options === null || options === void 0 ? void 0 : options.lift) !== null && _a !== void 0 ? _a : 100,
2246
- dotSize: (_b = options === null || options === void 0 ? void 0 : options.dotSize) !== null && _b !== void 0 ? _b : 6,
2247
- dotSpacing: (_c = options === null || options === void 0 ? void 0 : options.dotSpacing) !== null && _c !== void 0 ? _c : 2,
2248
- lineColor: (options === null || options === void 0 ? void 0 : options.lineColor) || 'rgba(200,200,200,0.7)',
2249
- lineWidth: (_d = options === null || options === void 0 ? void 0 : options.lineWidth) !== null && _d !== void 0 ? _d : 1,
2250
- updateInterval: (_e = options === null || options === void 0 ? void 0 : options.updateInterval) !== null && _e !== void 0 ? _e : 0, // Default update every frame
2251
- fadeInDuration: (_f = options === null || options === void 0 ? void 0 : options.fadeInDuration) !== null && _f !== void 0 ? _f : 300, // Fade-in duration
2290
+ fontSize: options?.fontSize || '12px',
2291
+ color: options?.color || '#ffffff',
2292
+ background: options?.background || '#1890ff',
2293
+ padding: options?.padding || '6px 10px',
2294
+ borderRadius: options?.borderRadius || '6px',
2295
+ lift: options?.lift ?? 100,
2296
+ dotSize: options?.dotSize ?? 6,
2297
+ dotSpacing: options?.dotSpacing ?? 2,
2298
+ lineColor: options?.lineColor || 'rgba(200,200,200,0.7)',
2299
+ lineWidth: options?.lineWidth ?? 1,
2300
+ updateInterval: options?.updateInterval ?? 0, // Default update every frame
2301
+ fadeInDuration: options?.fadeInDuration ?? 300, // Fade-in duration
2252
2302
  };
2253
2303
  const container = document.createElement('div');
2254
2304
  container.style.position = 'absolute';
@@ -2271,7 +2321,7 @@ function createModelsLabel(camera, renderer, parentModel, modelLabelsMap, option
2271
2321
  svg.style.zIndex = '1';
2272
2322
  container.appendChild(svg);
2273
2323
  let currentModel = parentModel;
2274
- let currentLabelsMap = Object.assign({}, modelLabelsMap);
2324
+ let currentLabelsMap = { ...modelLabelsMap };
2275
2325
  let labels = [];
2276
2326
  let isActive = true;
2277
2327
  let isPaused = false;
@@ -2352,9 +2402,8 @@ function createModelsLabel(camera, renderer, parentModel, modelLabelsMap, option
2352
2402
  if (!currentModel)
2353
2403
  return;
2354
2404
  currentModel.traverse((child) => {
2355
- var _a;
2356
2405
  if (child.isMesh || child.type === 'Group') {
2357
- const labelText = (_a = Object.entries(currentLabelsMap).find(([key]) => child.name.includes(key))) === null || _a === void 0 ? void 0 : _a[1];
2406
+ const labelText = Object.entries(currentLabelsMap).find(([key]) => child.name.includes(key))?.[1];
2358
2407
  if (!labelText)
2359
2408
  return;
2360
2409
  const wrapper = document.createElement('div');
@@ -2459,7 +2508,7 @@ function createModelsLabel(camera, renderer, parentModel, modelLabelsMap, option
2459
2508
  rebuildLabels();
2460
2509
  },
2461
2510
  updateLabelsMap(newMap) {
2462
- currentLabelsMap = Object.assign({}, newMap);
2511
+ currentLabelsMap = { ...newMap };
2463
2512
  rebuildLabels();
2464
2513
  },
2465
2514
  // Pause update
@@ -2557,205 +2606,197 @@ class GroupExploder {
2557
2606
  * @param newSet The new set of meshes
2558
2607
  * @param contextId Optional context ID to distinguish business scenarios
2559
2608
  */
2560
- setMeshes(newSet, options) {
2561
- return __awaiter(this, void 0, void 0, function* () {
2562
- var _a, _b;
2563
- const autoRestorePrev = (_a = options === null || options === void 0 ? void 0 : options.autoRestorePrev) !== null && _a !== void 0 ? _a : true;
2564
- const restoreDuration = (_b = options === null || options === void 0 ? void 0 : options.restoreDuration) !== null && _b !== void 0 ? _b : 300;
2565
- this.log(`setMeshes called. newSetSize=${newSet ? newSet.size : 0}, autoRestorePrev=${autoRestorePrev}`);
2566
- // If the newSet is null and currentSet is null -> nothing
2567
- if (!newSet && !this.currentSet) {
2568
- this.log('setMeshes: both newSet and currentSet are null, nothing to do');
2569
- return;
2570
- }
2571
- // If both exist and are the same reference, we still must detect content changes.
2572
- const sameReference = this.currentSet === newSet;
2573
- // Prepare prevSet snapshot (we copy current to prev)
2574
- if (this.currentSet) {
2575
- this.prevSet = this.currentSet;
2576
- this.prevStateMap = new Map(this.stateMap);
2577
- this.log(`setMeshes: backed up current->prev prevSetSize=${this.prevSet.size}`);
2578
- }
2579
- else {
2580
- this.prevSet = null;
2581
- this.prevStateMap = new Map();
2582
- }
2583
- // If we used to be exploded and need to restore prevSet, do that first (await)
2584
- if (this.prevSet && autoRestorePrev && this.isExploded) {
2585
- this.log('setMeshes: need to restore prevSet before applying newSet');
2586
- yield this.restoreSet(this.prevSet, this.prevStateMap, restoreDuration, { debug: true });
2587
- this.log('setMeshes: prevSet restore done');
2588
- this.prevStateMap.clear();
2589
- this.prevSet = null;
2590
- }
2591
- // Now register newSet: we clear and rebuild stateMap carefully.
2592
- // But we must handle the case where caller reuses same Set object and just mutated elements.
2593
- // We will compute additions and removals.
2594
- const oldSet = this.currentSet;
2595
- this.currentSet = newSet;
2596
- // If newSet is null -> simply clear stateMap
2597
- if (!this.currentSet) {
2598
- this.stateMap.clear();
2599
- this.log('setMeshes: newSet is null -> cleared stateMap');
2600
- this.isExploded = false;
2609
+ async setMeshes(newSet, options) {
2610
+ const autoRestorePrev = options?.autoRestorePrev ?? true;
2611
+ const restoreDuration = options?.restoreDuration ?? 300;
2612
+ this.log(`setMeshes called. newSetSize=${newSet ? newSet.size : 0}, autoRestorePrev=${autoRestorePrev}`);
2613
+ // If the newSet is null and currentSet is null -> nothing
2614
+ if (!newSet && !this.currentSet) {
2615
+ this.log('setMeshes: both newSet and currentSet are null, nothing to do');
2616
+ return;
2617
+ }
2618
+ // If both exist and are the same reference, we still must detect content changes.
2619
+ const sameReference = this.currentSet === newSet;
2620
+ // Prepare prevSet snapshot (we copy current to prev)
2621
+ if (this.currentSet) {
2622
+ this.prevSet = this.currentSet;
2623
+ this.prevStateMap = new Map(this.stateMap);
2624
+ this.log(`setMeshes: backed up current->prev prevSetSize=${this.prevSet.size}`);
2625
+ }
2626
+ else {
2627
+ this.prevSet = null;
2628
+ this.prevStateMap = new Map();
2629
+ }
2630
+ // If we used to be exploded and need to restore prevSet, do that first (await)
2631
+ if (this.prevSet && autoRestorePrev && this.isExploded) {
2632
+ this.log('setMeshes: need to restore prevSet before applying newSet');
2633
+ await this.restoreSet(this.prevSet, this.prevStateMap, restoreDuration, { debug: true });
2634
+ this.log('setMeshes: prevSet restore done');
2635
+ this.prevStateMap.clear();
2636
+ this.prevSet = null;
2637
+ }
2638
+ // Now register newSet: we clear and rebuild stateMap carefully.
2639
+ // But we must handle the case where caller reuses same Set object and just mutated elements.
2640
+ // We will compute additions and removals.
2641
+ const oldSet = this.currentSet;
2642
+ this.currentSet = newSet;
2643
+ // If newSet is null -> simply clear stateMap
2644
+ if (!this.currentSet) {
2645
+ this.stateMap.clear();
2646
+ this.log('setMeshes: newSet is null -> cleared stateMap');
2647
+ this.isExploded = false;
2648
+ return;
2649
+ }
2650
+ // If we have oldSet (could be same reference) then compute diffs
2651
+ if (oldSet) {
2652
+ // If same reference but size or content differs -> handle diffs
2653
+ const wasSameRef = sameReference;
2654
+ let added = [];
2655
+ let removed = [];
2656
+ // Build maps of membership
2657
+ const oldMembers = new Set(Array.from(oldSet));
2658
+ const newMembers = new Set(Array.from(this.currentSet));
2659
+ // find removals
2660
+ oldMembers.forEach((m) => {
2661
+ if (!newMembers.has(m))
2662
+ removed.push(m);
2663
+ });
2664
+ // find additions
2665
+ newMembers.forEach((m) => {
2666
+ if (!oldMembers.has(m))
2667
+ added.push(m);
2668
+ });
2669
+ if (wasSameRef && added.length === 0 && removed.length === 0) {
2670
+ // truly identical (no content changes)
2671
+ this.log('setMeshes: same reference and identical contents -> nothing to update');
2601
2672
  return;
2602
2673
  }
2603
- // If we have oldSet (could be same reference) then compute diffs
2604
- if (oldSet) {
2605
- // If same reference but size or content differs -> handle diffs
2606
- const wasSameRef = sameReference;
2607
- let added = [];
2608
- let removed = [];
2609
- // Build maps of membership
2610
- const oldMembers = new Set(Array.from(oldSet));
2611
- const newMembers = new Set(Array.from(this.currentSet));
2612
- // find removals
2613
- oldMembers.forEach((m) => {
2614
- if (!newMembers.has(m))
2615
- removed.push(m);
2616
- });
2617
- // find additions
2618
- newMembers.forEach((m) => {
2619
- if (!oldMembers.has(m))
2620
- added.push(m);
2621
- });
2622
- if (wasSameRef && added.length === 0 && removed.length === 0) {
2623
- // truly identical (no content changes)
2624
- this.log('setMeshes: same reference and identical contents -> nothing to update');
2625
- return;
2674
+ this.log(`setMeshes: diff detected -> added=${added.length}, removed=${removed.length}`);
2675
+ // Remove snapshots for removed meshes
2676
+ removed.forEach((m) => {
2677
+ if (this.stateMap.has(m)) {
2678
+ this.stateMap.delete(m);
2626
2679
  }
2627
- this.log(`setMeshes: diff detected -> added=${added.length}, removed=${removed.length}`);
2628
- // Remove snapshots for removed meshes
2629
- removed.forEach((m) => {
2630
- if (this.stateMap.has(m)) {
2631
- this.stateMap.delete(m);
2632
- }
2633
- });
2634
- // Ensure snapshots exist for current set members (create for newly added meshes)
2635
- yield this.ensureSnapshotsForSet(this.currentSet);
2636
- this.log(`setMeshes: after diff handling, stateMap size=${this.stateMap.size}`);
2637
- this.isExploded = false;
2638
- return;
2639
- }
2640
- else {
2641
- // no oldSet -> brand new registration
2642
- this.stateMap.clear();
2643
- yield this.ensureSnapshotsForSet(this.currentSet);
2644
- this.log(`setMeshes: recorded stateMap entries for newSet size=${this.stateMap.size}`);
2645
- this.isExploded = false;
2646
- return;
2647
- }
2648
- });
2680
+ });
2681
+ // Ensure snapshots exist for current set members (create for newly added meshes)
2682
+ await this.ensureSnapshotsForSet(this.currentSet);
2683
+ this.log(`setMeshes: after diff handling, stateMap size=${this.stateMap.size}`);
2684
+ this.isExploded = false;
2685
+ return;
2686
+ }
2687
+ else {
2688
+ // no oldSet -> brand new registration
2689
+ this.stateMap.clear();
2690
+ await this.ensureSnapshotsForSet(this.currentSet);
2691
+ this.log(`setMeshes: recorded stateMap entries for newSet size=${this.stateMap.size}`);
2692
+ this.isExploded = false;
2693
+ return;
2694
+ }
2649
2695
  }
2650
2696
  /**
2651
2697
  * ensureSnapshotsForSet: for each mesh in set, ensure stateMap has an entry.
2652
2698
  * If missing, record current matrixWorld as originalMatrixWorld (best-effort).
2653
2699
  */
2654
- ensureSnapshotsForSet(set) {
2655
- return __awaiter(this, void 0, void 0, function* () {
2656
- set.forEach((m) => {
2700
+ async ensureSnapshotsForSet(set) {
2701
+ set.forEach((m) => {
2702
+ try {
2703
+ m.updateMatrixWorld(true);
2704
+ }
2705
+ catch { }
2706
+ if (!this.stateMap.has(m)) {
2657
2707
  try {
2658
- m.updateMatrixWorld(true);
2708
+ this.stateMap.set(m, {
2709
+ originalParent: m.parent || null,
2710
+ originalMatrixWorld: (m.matrixWorld && m.matrixWorld.clone()) || new THREE__namespace.Matrix4().copy(m.matrix),
2711
+ });
2712
+ // Also store in userData for extra resilience
2713
+ m.userData.__originalMatrixWorld = this.stateMap.get(m).originalMatrixWorld.clone();
2659
2714
  }
2660
- catch (_a) { }
2661
- if (!this.stateMap.has(m)) {
2662
- try {
2663
- this.stateMap.set(m, {
2664
- originalParent: m.parent || null,
2665
- originalMatrixWorld: (m.matrixWorld && m.matrixWorld.clone()) || new THREE__namespace.Matrix4().copy(m.matrix),
2666
- });
2667
- // Also store in userData for extra resilience
2668
- m.userData.__originalMatrixWorld = this.stateMap.get(m).originalMatrixWorld.clone();
2669
- }
2670
- catch (e) {
2671
- this.log(`ensureSnapshotsForSet: failed to snapshot mesh ${m.name || m.id}: ${e.message}`);
2672
- }
2715
+ catch (e) {
2716
+ this.log(`ensureSnapshotsForSet: failed to snapshot mesh ${m.name || m.id}: ${e.message}`);
2673
2717
  }
2674
- });
2718
+ }
2675
2719
  });
2676
2720
  }
2677
2721
  /**
2678
2722
  * explode: compute targets first, compute targetBound using targets + mesh radii,
2679
2723
  * animate camera to that targetBound, then animate meshes to targets.
2680
2724
  */
2681
- explode(opts) {
2682
- return __awaiter(this, void 0, void 0, function* () {
2683
- var _a;
2684
- if (!this.currentSet || this.currentSet.size === 0) {
2685
- this.log('explode: empty currentSet, nothing to do');
2686
- return;
2725
+ async explode(opts) {
2726
+ if (!this.currentSet || this.currentSet.size === 0) {
2727
+ this.log('explode: empty currentSet, nothing to do');
2728
+ return;
2729
+ }
2730
+ const { spacing = 2, duration = 1000, lift = 0.5, cameraPadding = 1.5, mode = 'spiral', dimOthers = { enabled: true, opacity: 0.25 }, debug = false, } = opts || {};
2731
+ this.log(`explode called. setSize=${this.currentSet.size}, mode=${mode}, spacing=${spacing}, duration=${duration}, lift=${lift}, dim=${dimOthers.enabled}`);
2732
+ this.cancelAnimations();
2733
+ const meshes = Array.from(this.currentSet);
2734
+ // ensure snapshots exist for any meshes that may have been added after initial registration
2735
+ await this.ensureSnapshotsForSet(this.currentSet);
2736
+ // compute center/radius from current meshes (fallback)
2737
+ const initial = this.computeBoundingSphereForMeshes(meshes);
2738
+ const center = initial.center;
2739
+ const baseRadius = Math.max(1, initial.radius);
2740
+ this.log(`explode: initial center=${center.toArray().map((n) => n.toFixed(3))}, baseRadius=${baseRadius.toFixed(3)}`);
2741
+ // compute targets (pure calculation)
2742
+ const targets = this.computeTargetsByMode(meshes, center, baseRadius + spacing, { lift, mode });
2743
+ this.log(`explode: computed ${targets.length} target positions`);
2744
+ // compute target-based bounding sphere (targets + per-mesh radius)
2745
+ const targetBound = this.computeBoundingSphereForPositionsAndMeshes(targets, meshes);
2746
+ this.log(`explode: targetBound center=${targetBound.center.toArray().map((n) => n.toFixed(3))}, radius=${targetBound.radius.toFixed(3)}`);
2747
+ await this.animateCameraToFit(targetBound.center, targetBound.radius, { duration: Math.min(600, duration), padding: cameraPadding });
2748
+ this.log('explode: camera animation to target bound completed');
2749
+ // apply dim if needed with context id
2750
+ const contextId = dimOthers?.enabled ? this.applyDimToOthers(meshes, dimOthers.opacity ?? 0.25, { debug }) : null;
2751
+ if (contextId)
2752
+ this.log(`explode: applied dim for context ${contextId}`);
2753
+ // capture starts after camera move
2754
+ const starts = meshes.map((m) => {
2755
+ const v = new THREE__namespace.Vector3();
2756
+ try {
2757
+ m.getWorldPosition(v);
2687
2758
  }
2688
- const { spacing = 2, duration = 1000, lift = 0.5, cameraPadding = 1.5, mode = 'spiral', dimOthers = { enabled: true, opacity: 0.25 }, debug = false, } = opts || {};
2689
- this.log(`explode called. setSize=${this.currentSet.size}, mode=${mode}, spacing=${spacing}, duration=${duration}, lift=${lift}, dim=${dimOthers.enabled}`);
2690
- this.cancelAnimations();
2691
- const meshes = Array.from(this.currentSet);
2692
- // ensure snapshots exist for any meshes that may have been added after initial registration
2693
- yield this.ensureSnapshotsForSet(this.currentSet);
2694
- // compute center/radius from current meshes (fallback)
2695
- const initial = this.computeBoundingSphereForMeshes(meshes);
2696
- const center = initial.center;
2697
- const baseRadius = Math.max(1, initial.radius);
2698
- this.log(`explode: initial center=${center.toArray().map((n) => n.toFixed(3))}, baseRadius=${baseRadius.toFixed(3)}`);
2699
- // compute targets (pure calculation)
2700
- const targets = this.computeTargetsByMode(meshes, center, baseRadius + spacing, { lift, mode });
2701
- this.log(`explode: computed ${targets.length} target positions`);
2702
- // compute target-based bounding sphere (targets + per-mesh radius)
2703
- const targetBound = this.computeBoundingSphereForPositionsAndMeshes(targets, meshes);
2704
- this.log(`explode: targetBound center=${targetBound.center.toArray().map((n) => n.toFixed(3))}, radius=${targetBound.radius.toFixed(3)}`);
2705
- yield this.animateCameraToFit(targetBound.center, targetBound.radius, { duration: Math.min(600, duration), padding: cameraPadding });
2706
- this.log('explode: camera animation to target bound completed');
2707
- // apply dim if needed with context id
2708
- const contextId = (dimOthers === null || dimOthers === void 0 ? void 0 : dimOthers.enabled) ? this.applyDimToOthers(meshes, (_a = dimOthers.opacity) !== null && _a !== void 0 ? _a : 0.25, { debug }) : null;
2709
- if (contextId)
2710
- this.log(`explode: applied dim for context ${contextId}`);
2711
- // capture starts after camera move
2712
- const starts = meshes.map((m) => {
2713
- const v = new THREE__namespace.Vector3();
2714
- try {
2715
- m.getWorldPosition(v);
2716
- }
2717
- catch (_a) {
2718
- // fallback to originalMatrixWorld if available
2719
- const st = this.stateMap.get(m);
2720
- if (st)
2721
- v.setFromMatrixPosition(st.originalMatrixWorld);
2722
- }
2723
- return v;
2724
- });
2725
- const startTime = performance.now();
2726
- const total = Math.max(1, duration);
2727
- const tick = (now) => {
2728
- const t = Math.min(1, (now - startTime) / total);
2729
- const eased = easeInOutQuad(t);
2730
- for (let i = 0; i < meshes.length; i++) {
2731
- const m = meshes[i];
2732
- const s = starts[i];
2733
- const tar = targets[i];
2734
- const cur = s.clone().lerp(tar, eased);
2735
- if (m.parent) {
2736
- const local = cur.clone();
2737
- m.parent.worldToLocal(local);
2738
- m.position.copy(local);
2739
- }
2740
- else {
2741
- m.position.copy(cur);
2742
- }
2743
- m.updateMatrix();
2744
- }
2745
- if (this.controls && typeof this.controls.update === 'function')
2746
- this.controls.update();
2747
- if (t < 1) {
2748
- this.animId = requestAnimationFrame(tick);
2759
+ catch {
2760
+ // fallback to originalMatrixWorld if available
2761
+ const st = this.stateMap.get(m);
2762
+ if (st)
2763
+ v.setFromMatrixPosition(st.originalMatrixWorld);
2764
+ }
2765
+ return v;
2766
+ });
2767
+ const startTime = performance.now();
2768
+ const total = Math.max(1, duration);
2769
+ const tick = (now) => {
2770
+ const t = Math.min(1, (now - startTime) / total);
2771
+ const eased = easeInOutQuad(t);
2772
+ for (let i = 0; i < meshes.length; i++) {
2773
+ const m = meshes[i];
2774
+ const s = starts[i];
2775
+ const tar = targets[i];
2776
+ const cur = s.clone().lerp(tar, eased);
2777
+ if (m.parent) {
2778
+ const local = cur.clone();
2779
+ m.parent.worldToLocal(local);
2780
+ m.position.copy(local);
2749
2781
  }
2750
2782
  else {
2751
- this.animId = null;
2752
- this.isExploded = true;
2753
- this.log(`explode: completed. contextId=${contextId !== null && contextId !== void 0 ? contextId : 'none'}`);
2783
+ m.position.copy(cur);
2754
2784
  }
2755
- };
2756
- this.animId = requestAnimationFrame(tick);
2757
- return;
2758
- });
2785
+ m.updateMatrix();
2786
+ }
2787
+ if (this.controls && typeof this.controls.update === 'function')
2788
+ this.controls.update();
2789
+ if (t < 1) {
2790
+ this.animId = requestAnimationFrame(tick);
2791
+ }
2792
+ else {
2793
+ this.animId = null;
2794
+ this.isExploded = true;
2795
+ this.log(`explode: completed. contextId=${contextId ?? 'none'}`);
2796
+ }
2797
+ };
2798
+ this.animId = requestAnimationFrame(tick);
2799
+ return;
2759
2800
  }
2760
2801
  /**
2761
2802
  * Restore all exploded meshes to their original transform:
@@ -2778,7 +2819,7 @@ class GroupExploder {
2778
2819
  */
2779
2820
  restoreSet(set, stateMap, duration = 400, opts) {
2780
2821
  if (!set || set.size === 0) {
2781
- if (opts === null || opts === void 0 ? void 0 : opts.debug)
2822
+ if (opts?.debug)
2782
2823
  this.log('restoreSet: empty set, nothing to restore');
2783
2824
  return Promise.resolve();
2784
2825
  }
@@ -2791,12 +2832,12 @@ class GroupExploder {
2791
2832
  try {
2792
2833
  m.updateMatrixWorld(true);
2793
2834
  }
2794
- catch (_a) { }
2835
+ catch { }
2795
2836
  const s = new THREE__namespace.Vector3();
2796
2837
  try {
2797
2838
  m.getWorldPosition(s);
2798
2839
  }
2799
- catch (_b) {
2840
+ catch {
2800
2841
  s.set(0, 0, 0);
2801
2842
  }
2802
2843
  starts.push(s);
@@ -2894,7 +2935,7 @@ class GroupExploder {
2894
2935
  });
2895
2936
  }
2896
2937
  // material dim with context id
2897
- applyDimToOthers(explodingMeshes, opacity = 0.25, opts) {
2938
+ applyDimToOthers(explodingMeshes, opacity = 0.25, _opts) {
2898
2939
  const contextId = `ctx_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
2899
2940
  const explodingSet = new Set(explodingMeshes);
2900
2941
  const touched = new Set();
@@ -2905,11 +2946,10 @@ class GroupExploder {
2905
2946
  if (explodingSet.has(mesh))
2906
2947
  return;
2907
2948
  const applyMat = (mat) => {
2908
- var _a;
2909
2949
  if (!this.materialSnaps.has(mat)) {
2910
2950
  this.materialSnaps.set(mat, {
2911
2951
  transparent: !!mat.transparent,
2912
- opacity: (_a = mat.opacity) !== null && _a !== void 0 ? _a : 1,
2952
+ opacity: mat.opacity ?? 1,
2913
2953
  depthWrite: mat.depthWrite,
2914
2954
  });
2915
2955
  }
@@ -2936,7 +2976,7 @@ class GroupExploder {
2936
2976
  return contextId;
2937
2977
  }
2938
2978
  // clean contexts for meshes (restore materials whose contexts are removed)
2939
- cleanContextsForMeshes(meshes) {
2979
+ cleanContextsForMeshes(_meshes) {
2940
2980
  // conservative strategy: for each context we created, delete it and restore materials accordingly
2941
2981
  for (const [contextId, mats] of Array.from(this.contextMaterials.entries())) {
2942
2982
  mats.forEach((mat) => {
@@ -3050,7 +3090,7 @@ class GroupExploder {
3050
3090
  }
3051
3091
  }
3052
3092
  }
3053
- catch (_a) {
3093
+ catch {
3054
3094
  radius = 0;
3055
3095
  }
3056
3096
  if (!isFinite(radius) || radius < 0 || radius > 1e8)
@@ -3073,10 +3113,9 @@ class GroupExploder {
3073
3113
  }
3074
3114
  // computeTargetsByMode (unchanged logic but pure function)
3075
3115
  computeTargetsByMode(meshes, center, baseRadius, opts) {
3076
- var _a, _b;
3077
3116
  const n = meshes.length;
3078
- const lift = (_a = opts.lift) !== null && _a !== void 0 ? _a : 0.5;
3079
- const mode = (_b = opts.mode) !== null && _b !== void 0 ? _b : 'ring';
3117
+ const lift = opts.lift ?? 0.5;
3118
+ const mode = opts.mode ?? 'ring';
3080
3119
  const targets = [];
3081
3120
  if (mode === 'ring') {
3082
3121
  for (let i = 0; i < n; i++) {
@@ -3118,9 +3157,8 @@ class GroupExploder {
3118
3157
  return targets;
3119
3158
  }
3120
3159
  animateCameraToFit(targetCenter, targetRadius, opts) {
3121
- var _a, _b, _c, _d;
3122
- const duration = (_a = opts === null || opts === void 0 ? void 0 : opts.duration) !== null && _a !== void 0 ? _a : 600;
3123
- const padding = (_b = opts === null || opts === void 0 ? void 0 : opts.padding) !== null && _b !== void 0 ? _b : 1.5;
3160
+ const duration = opts?.duration ?? 600;
3161
+ const padding = opts?.padding ?? 1.5;
3124
3162
  if (!(this.camera instanceof THREE__namespace.PerspectiveCamera)) {
3125
3163
  if (this.controls && this.controls.target) {
3126
3164
  // Fallback for non-PerspectiveCamera
@@ -3132,14 +3170,13 @@ class GroupExploder {
3132
3170
  const endPos = endTarget.clone().add(dir.multiplyScalar(dist));
3133
3171
  const startTime = performance.now();
3134
3172
  const tick = (now) => {
3135
- var _a;
3136
3173
  const t = Math.min(1, (now - startTime) / duration);
3137
3174
  const k = easeInOutQuad(t);
3138
3175
  if (this.controls && this.controls.target) {
3139
3176
  this.controls.target.lerpVectors(startTarget, endTarget, k);
3140
3177
  }
3141
3178
  this.camera.position.lerpVectors(startPos, endPos, k);
3142
- if ((_a = this.controls) === null || _a === void 0 ? void 0 : _a.update)
3179
+ if (this.controls?.update)
3143
3180
  this.controls.update();
3144
3181
  if (t < 1) {
3145
3182
  this.cameraAnimId = requestAnimationFrame(tick);
@@ -3162,8 +3199,8 @@ class GroupExploder {
3162
3199
  const distH = targetRadius / Math.sin(Math.min(fov, fov * aspect) / 2); // approximate
3163
3200
  const dist = Math.max(distV, distH) * padding;
3164
3201
  const startPos = this.camera.position.clone();
3165
- const startTarget = ((_c = this.controls) === null || _c === void 0 ? void 0 : _c.target) ? this.controls.target.clone() : new THREE__namespace.Vector3(); // assumption
3166
- if (!((_d = this.controls) === null || _d === void 0 ? void 0 : _d.target)) {
3202
+ const startTarget = this.controls?.target ? this.controls.target.clone() : new THREE__namespace.Vector3(); // assumption
3203
+ if (!this.controls?.target) {
3167
3204
  this.camera.getWorldDirection(startTarget);
3168
3205
  startTarget.add(startPos);
3169
3206
  }
@@ -3176,13 +3213,12 @@ class GroupExploder {
3176
3213
  return new Promise((resolve) => {
3177
3214
  const startTime = performance.now();
3178
3215
  const tick = (now) => {
3179
- var _a, _b;
3180
3216
  const t = Math.min(1, (now - startTime) / duration);
3181
3217
  const k = easeInOutQuad(t);
3182
3218
  this.camera.position.lerpVectors(startPos, endPos, k);
3183
3219
  if (this.controls && this.controls.target) {
3184
3220
  this.controls.target.lerpVectors(startTarget, endTarget, k);
3185
- (_b = (_a = this.controls).update) === null || _b === void 0 ? void 0 : _b.call(_a);
3221
+ this.controls.update?.();
3186
3222
  }
3187
3223
  else {
3188
3224
  this.camera.lookAt(endTarget); // simple lookAt if no controls
@@ -3231,183 +3267,134 @@ class GroupExploder {
3231
3267
  * @file autoSetup.ts
3232
3268
  * @description
3233
3269
  * Automatically sets up the camera and basic lighting scene based on the model's bounding box.
3234
- *
3235
- * @best-practice
3236
- * - Call `autoSetupCameraAndLight` after loading a model to get a quick "good looking" scene.
3237
- * - Returns a handle to dispose lights or update intensity later.
3238
3270
  */
3239
3271
  /**
3240
- * Automatically setup camera and basic lighting - Optimized
3241
- *
3242
- * Features:
3243
- * - Adds light intensity adjustment method
3244
- * - Improved error handling
3245
- * - Optimized dispose logic
3246
- *
3247
- * - camera: THREE.PerspectiveCamera (will be moved and pointed at model center)
3248
- * - scene: THREE.Scene (newly created light group will be added to the scene)
3249
- * - model: THREE.Object3D loaded model (arbitrary transform/coordinates)
3250
- * - options: Optional configuration (see AutoSetupOptions)
3251
- *
3252
- * Returns AutoSetupHandle, caller should call handle.dispose() when component unmounts/switches
3272
+ * Fit camera to object bounding box
3253
3273
  */
3254
- function autoSetupCameraAndLight(camera, scene, model, options = {}) {
3255
- var _a, _b, _c, _d, _e, _f, _g;
3256
- // Boundary check
3257
- if (!camera || !scene || !model) {
3258
- throw new Error('autoSetupCameraAndLight: camera, scene, model are required');
3259
- }
3274
+ function fitCameraToObject(camera, object, padding = 1.2, elevation = 0.2) {
3275
+ const box = new THREE__namespace.Box3().setFromObject(object);
3276
+ if (!isFinite(box.min.x))
3277
+ return { center: new THREE__namespace.Vector3(), radius: 0 };
3278
+ const sphere = new THREE__namespace.Sphere();
3279
+ box.getBoundingSphere(sphere);
3280
+ const center = sphere.center.clone();
3281
+ const radius = Math.max(0.001, sphere.radius);
3282
+ const fov = (camera.fov * Math.PI) / 180;
3283
+ const halfFov = fov / 2;
3284
+ const sinHalfFov = Math.max(Math.sin(halfFov), 0.001);
3285
+ const distance = (radius * padding) / sinHalfFov;
3286
+ const dir = new THREE__namespace.Vector3(0, Math.sin(elevation), Math.cos(elevation)).normalize();
3287
+ const desiredPos = center.clone().add(dir.multiplyScalar(distance));
3288
+ camera.position.copy(desiredPos);
3289
+ camera.lookAt(center);
3290
+ camera.near = Math.max(0.001, radius / 1000);
3291
+ camera.far = Math.max(1000, radius * 50);
3292
+ camera.updateProjectionMatrix();
3293
+ return { center, radius };
3294
+ }
3295
+ /**
3296
+ * Setup default lighting for a model
3297
+ */
3298
+ function setupDefaultLights(scene, model, options = {}) {
3299
+ const box = new THREE__namespace.Box3().setFromObject(model);
3300
+ const sphere = new THREE__namespace.Sphere();
3301
+ box.getBoundingSphere(sphere);
3302
+ const center = sphere.center.clone();
3303
+ const radius = Math.max(0.001, sphere.radius);
3260
3304
  const opts = {
3261
- padding: (_a = options.padding) !== null && _a !== void 0 ? _a : 1.2,
3262
- elevation: (_b = options.elevation) !== null && _b !== void 0 ? _b : 0.2,
3263
- enableShadows: (_c = options.enableShadows) !== null && _c !== void 0 ? _c : false,
3264
- shadowMapSize: (_d = options.shadowMapSize) !== null && _d !== void 0 ? _d : 1024,
3265
- directionalCount: (_e = options.directionalCount) !== null && _e !== void 0 ? _e : 4,
3266
- setMeshShadowProps: (_f = options.setMeshShadowProps) !== null && _f !== void 0 ? _f : true,
3267
- renderer: (_g = options.renderer) !== null && _g !== void 0 ? _g : null,
3305
+ padding: options.padding ?? 1.2,
3306
+ elevation: options.elevation ?? 0.2,
3307
+ enableShadows: options.enableShadows ?? false,
3308
+ shadowMapSize: options.shadowMapSize ?? 1024,
3309
+ directionalCount: options.directionalCount ?? 4,
3310
+ setMeshShadowProps: options.setMeshShadowProps ?? true,
3311
+ renderer: options.renderer ?? null,
3268
3312
  };
3269
- try {
3270
- // --- 1) Calculate bounding data
3271
- const box = new THREE__namespace.Box3().setFromObject(model);
3272
- // Check bounding box validity
3273
- if (!isFinite(box.min.x)) {
3274
- throw new Error('autoSetupCameraAndLight: Invalid bounding box');
3313
+ if (opts.renderer && opts.enableShadows) {
3314
+ opts.renderer.shadowMap.enabled = true;
3315
+ opts.renderer.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
3316
+ }
3317
+ const lightsGroup = new THREE__namespace.Group();
3318
+ lightsGroup.name = 'autoSetupLightsGroup';
3319
+ lightsGroup.position.copy(center);
3320
+ scene.add(lightsGroup);
3321
+ const hemi = new THREE__namespace.HemisphereLight(0xffffff, 0x444444, 0.6);
3322
+ hemi.position.set(0, radius * 2.0, 0);
3323
+ lightsGroup.add(hemi);
3324
+ const ambient = new THREE__namespace.AmbientLight(0xffffff, 0.25);
3325
+ lightsGroup.add(ambient);
3326
+ const dirCount = Math.max(1, Math.floor(opts.directionalCount));
3327
+ const dirs = [new THREE__namespace.Vector3(0, 1, 0)];
3328
+ for (let i = 0; i < dirCount; i++) {
3329
+ const angle = (i / dirCount) * Math.PI * 2;
3330
+ const v = new THREE__namespace.Vector3(Math.cos(angle), 0.3, Math.sin(angle)).normalize();
3331
+ dirs.push(v);
3332
+ }
3333
+ const shadowCamSize = Math.max(1, radius * 1.5);
3334
+ dirs.forEach((d, i) => {
3335
+ const light = new THREE__namespace.DirectionalLight(0xffffff, i === 0 ? 1.5 : 1.2);
3336
+ light.position.copy(d.clone().multiplyScalar(radius * 2.5));
3337
+ light.target.position.copy(center);
3338
+ light.name = `auto_dir_${i}`;
3339
+ lightsGroup.add(light);
3340
+ lightsGroup.add(light.target);
3341
+ if (opts.enableShadows) {
3342
+ light.castShadow = true;
3343
+ light.shadow.mapSize.width = opts.shadowMapSize;
3344
+ light.shadow.mapSize.height = opts.shadowMapSize;
3345
+ const cam = light.shadow.camera;
3346
+ const s = shadowCamSize;
3347
+ cam.left = -s;
3348
+ cam.right = s;
3349
+ cam.top = s;
3350
+ cam.bottom = -s;
3351
+ cam.near = 0.1;
3352
+ cam.far = radius * 10 + 50;
3353
+ light.shadow.bias = -5e-4;
3275
3354
  }
3276
- const sphere = new THREE__namespace.Sphere();
3277
- box.getBoundingSphere(sphere);
3278
- const center = sphere.center.clone();
3279
- const radius = Math.max(0.001, sphere.radius);
3280
- // --- 2) Calculate camera position
3281
- const fov = (camera.fov * Math.PI) / 180;
3282
- const halfFov = fov / 2;
3283
- const sinHalfFov = Math.max(Math.sin(halfFov), 0.001);
3284
- const distance = (radius * opts.padding) / sinHalfFov;
3285
- const dir = new THREE__namespace.Vector3(0, Math.sin(opts.elevation), Math.cos(opts.elevation)).normalize();
3286
- const desiredPos = center.clone().add(dir.multiplyScalar(distance));
3287
- camera.position.copy(desiredPos);
3288
- camera.lookAt(center);
3289
- camera.near = Math.max(0.001, radius / 1000);
3290
- camera.far = Math.max(1000, radius * 50);
3291
- camera.updateProjectionMatrix();
3292
- // --- 3) Enable Shadows
3293
- if (opts.renderer && opts.enableShadows) {
3294
- opts.renderer.shadowMap.enabled = true;
3295
- opts.renderer.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
3296
- }
3297
- // --- 4) Create Lights Group
3298
- const lightsGroup = new THREE__namespace.Group();
3299
- lightsGroup.name = 'autoSetupLightsGroup';
3300
- lightsGroup.position.copy(center);
3301
- scene.add(lightsGroup);
3302
- // 4.1 Basic Light
3303
- const hemi = new THREE__namespace.HemisphereLight(0xffffff, 0x444444, 0.6);
3304
- hemi.name = 'auto_hemi';
3305
- hemi.position.set(0, radius * 2.0, 0);
3306
- lightsGroup.add(hemi);
3307
- const ambient = new THREE__namespace.AmbientLight(0xffffff, 0.25);
3308
- ambient.name = 'auto_ambient';
3309
- lightsGroup.add(ambient);
3310
- // 4.2 Directional Lights
3311
- const dirCount = Math.max(1, Math.floor(opts.directionalCount));
3312
- const directionalLights = [];
3313
- const dirs = [];
3314
- dirs.push(new THREE__namespace.Vector3(0, 1, 0));
3315
- for (let i = 0; i < Math.max(1, dirCount); i++) {
3316
- const angle = (i / Math.max(1, dirCount)) * Math.PI * 2;
3317
- const v = new THREE__namespace.Vector3(Math.cos(angle), 0.3, Math.sin(angle)).normalize();
3318
- dirs.push(v);
3319
- }
3320
- const shadowCamSize = Math.max(1, radius * 1.5);
3321
- for (let i = 0; i < dirs.length; i++) {
3322
- const d = dirs[i];
3323
- const light = new THREE__namespace.DirectionalLight(0xffffff, i === 0 ? 1.5 : 1.2);
3324
- light.position.copy(d.clone().multiplyScalar(radius * 2.5));
3325
- light.target.position.copy(center);
3326
- light.name = `auto_dir_${i}`;
3327
- lightsGroup.add(light);
3328
- lightsGroup.add(light.target);
3329
- if (opts.enableShadows) {
3330
- light.castShadow = true;
3331
- light.shadow.mapSize.width = opts.shadowMapSize;
3332
- light.shadow.mapSize.height = opts.shadowMapSize;
3333
- const cam = light.shadow.camera;
3334
- const s = shadowCamSize;
3335
- cam.left = -s;
3336
- cam.right = s;
3337
- cam.top = s;
3338
- cam.bottom = -s;
3339
- cam.near = 0.1;
3340
- cam.far = radius * 10 + 50;
3341
- light.shadow.bias = -0.0005;
3342
- }
3343
- directionalLights.push(light);
3344
- }
3345
- // 4.3 Point Light Fill
3346
- const fill1 = new THREE__namespace.PointLight(0xffffff, 0.5, radius * 4);
3347
- fill1.position.copy(center).add(new THREE__namespace.Vector3(radius * 0.5, 0.2 * radius, 0));
3348
- fill1.name = 'auto_fill1';
3349
- lightsGroup.add(fill1);
3350
- const fill2 = new THREE__namespace.PointLight(0xffffff, 0.3, radius * 3);
3351
- fill2.position.copy(center).add(new THREE__namespace.Vector3(-radius * 0.5, -0.2 * radius, 0));
3352
- fill2.name = 'auto_fill2';
3353
- lightsGroup.add(fill2);
3354
- // --- 5) Set Mesh Shadow Props
3355
- if (opts.setMeshShadowProps) {
3356
- model.traverse((ch) => {
3357
- if (ch.isMesh) {
3358
- const mesh = ch;
3359
- const isSkinned = mesh.isSkinnedMesh;
3360
- mesh.castShadow = opts.enableShadows && !isSkinned ? true : mesh.castShadow;
3361
- mesh.receiveShadow = opts.enableShadows ? true : mesh.receiveShadow;
3355
+ });
3356
+ if (opts.setMeshShadowProps) {
3357
+ model.traverse((ch) => {
3358
+ if (ch.isMesh) {
3359
+ const mesh = ch;
3360
+ const isSkinned = mesh.isSkinnedMesh;
3361
+ mesh.castShadow = opts.enableShadows && !isSkinned ? true : mesh.castShadow;
3362
+ mesh.receiveShadow = opts.enableShadows ? true : mesh.receiveShadow;
3363
+ }
3364
+ });
3365
+ }
3366
+ const handle = {
3367
+ lightsGroup,
3368
+ center,
3369
+ radius,
3370
+ updateLightIntensity(factor) {
3371
+ lightsGroup.traverse((node) => {
3372
+ if (node.isLight) {
3373
+ const light = node;
3374
+ light.intensity *= factor; // Simple implementation
3362
3375
  }
3363
3376
  });
3364
- }
3365
- // --- 6) Return handle ---
3366
- const handle = {
3367
- lightsGroup,
3368
- center,
3369
- radius,
3370
- // Update light intensity
3371
- updateLightIntensity(factor) {
3372
- lightsGroup.traverse((node) => {
3373
- if (node.isLight) {
3374
- const light = node;
3375
- const originalIntensity = parseFloat(light.name.split('_').pop() || '1');
3376
- light.intensity = originalIntensity * Math.max(0, factor);
3377
- }
3378
- });
3379
- },
3380
- dispose: () => {
3381
- try {
3382
- // Remove lights group
3383
- if (lightsGroup.parent)
3384
- lightsGroup.parent.remove(lightsGroup);
3385
- // Dispose shadow resources
3386
- lightsGroup.traverse((node) => {
3387
- if (node.isLight) {
3388
- const l = node;
3389
- if (l.shadow && l.shadow.map) {
3390
- try {
3391
- l.shadow.map.dispose();
3392
- }
3393
- catch (err) {
3394
- console.warn('Failed to dispose shadow map:', err);
3395
- }
3396
- }
3397
- }
3398
- });
3399
- }
3400
- catch (error) {
3401
- console.error('autoSetupCameraAndLight: dispose failed', error);
3377
+ },
3378
+ dispose: () => {
3379
+ if (lightsGroup.parent)
3380
+ lightsGroup.parent.remove(lightsGroup);
3381
+ lightsGroup.traverse((node) => {
3382
+ if (node.isLight) {
3383
+ const l = node;
3384
+ if (l.shadow && l.shadow.map)
3385
+ l.shadow.map.dispose();
3402
3386
  }
3403
- }
3404
- };
3405
- return handle;
3406
- }
3407
- catch (error) {
3408
- console.error('autoSetupCameraAndLight: setup failed', error);
3409
- throw error;
3410
- }
3387
+ });
3388
+ }
3389
+ };
3390
+ return handle;
3391
+ }
3392
+ /**
3393
+ * Automatically setup camera and basic lighting (Combine fitCameraToObject and setupDefaultLights)
3394
+ */
3395
+ function autoSetupCameraAndLight(camera, scene, model, options = {}) {
3396
+ fitCameraToObject(camera, model, options.padding, options.elevation);
3397
+ return setupDefaultLights(scene, model, options);
3411
3398
  }
3412
3399
 
3413
3400
  /**
@@ -3417,14 +3404,15 @@ function autoSetupCameraAndLight(camera, scene, model, options = {}) {
3417
3404
  * @packageDocumentation
3418
3405
  */
3419
3406
  // Core utilities
3420
- // Version
3421
- const VERSION = '1.0.0';
3407
+ // Version (keep in sync with package.json)
3408
+ const VERSION = '1.0.4';
3422
3409
 
3423
3410
  exports.ArrowGuide = ArrowGuide;
3424
3411
  exports.BlueSky = BlueSky;
3425
3412
  exports.FOLLOW_ANGLES = FOLLOW_ANGLES;
3426
3413
  exports.GroupExploder = GroupExploder;
3427
3414
  exports.LiquidFillerGroup = LiquidFillerGroup;
3415
+ exports.ResourceManager = ResourceManager;
3428
3416
  exports.VERSION = VERSION;
3429
3417
  exports.ViewPresets = ViewPresets;
3430
3418
  exports.addChildModelLabels = addChildModelLabels;
@@ -3436,12 +3424,16 @@ exports.createModelsLabel = createModelsLabel;
3436
3424
  exports.disposeMaterial = disposeMaterial;
3437
3425
  exports.disposeObject = disposeObject;
3438
3426
  exports.enableHoverBreath = enableHoverBreath;
3427
+ exports.fitCameraToObject = fitCameraToObject;
3439
3428
  exports.followModels = followModels;
3429
+ exports.getLoaderConfig = getLoaderConfig;
3440
3430
  exports.initPostProcessing = initPostProcessing;
3441
3431
  exports.loadCubeSkybox = loadCubeSkybox;
3442
3432
  exports.loadEquirectSkybox = loadEquirectSkybox;
3443
3433
  exports.loadModelByUrl = loadModelByUrl;
3444
3434
  exports.loadSkybox = loadSkybox;
3445
3435
  exports.releaseSkybox = releaseSkybox;
3436
+ exports.setLoaderConfig = setLoaderConfig;
3446
3437
  exports.setView = setView;
3438
+ exports.setupDefaultLights = setupDefaultLights;
3447
3439
  //# sourceMappingURL=index.js.map