senangwebs-tour 1.0.3 → 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/swt-editor.css +36 -2
- package/dist/swt-editor.css.map +1 -1
- package/dist/swt-editor.js +3102 -209
- package/dist/swt-editor.js.map +1 -1
- package/dist/swt-editor.min.css +1 -1
- package/dist/swt-editor.min.js +1 -1
- package/dist/swt.js +292 -26
- package/dist/swt.js.map +1 -1
- package/dist/swt.min.js +1 -1
- package/package.json +2 -1
- package/src/AssetManager.js +13 -2
- package/src/HotspotManager.js +93 -20
- package/src/IconRenderer.js +123 -0
- package/src/SceneManager.js +7 -1
- package/src/editor/css/main.css +41 -2
- package/src/editor/js/editor.js +108 -23
- package/src/editor/js/export-manager.js +56 -9
- package/src/editor/js/hotspot-editor.js +10 -57
- package/src/editor/js/preview-controller.js +132 -105
- package/src/editor/js/ui-controller.js +91 -8
- package/src/editor/js/utils.js +1 -0
- package/src/index.js +56 -3
package/dist/swt.js
CHANGED
|
@@ -55829,8 +55829,19 @@ void main() {
|
|
|
55829
55829
|
* @returns {Promise} - Resolves when the asset is loaded
|
|
55830
55830
|
*/
|
|
55831
55831
|
async preloadImage(url, id) {
|
|
55832
|
-
if
|
|
55833
|
-
|
|
55832
|
+
// Check if asset exists and if the URL is the same
|
|
55833
|
+
const existingAsset = this.loadedAssets.get(id);
|
|
55834
|
+
if (existingAsset) {
|
|
55835
|
+
const existingSrc = existingAsset.getAttribute('src');
|
|
55836
|
+
if (existingSrc === url) {
|
|
55837
|
+
// Same URL, return cached asset
|
|
55838
|
+
return Promise.resolve(existingAsset);
|
|
55839
|
+
}
|
|
55840
|
+
// URL changed - remove old asset and create new one
|
|
55841
|
+
if (existingAsset.parentNode) {
|
|
55842
|
+
existingAsset.parentNode.removeChild(existingAsset);
|
|
55843
|
+
}
|
|
55844
|
+
this.loadedAssets.delete(id);
|
|
55834
55845
|
}
|
|
55835
55846
|
|
|
55836
55847
|
// Ensure assets element is ready
|
|
@@ -55989,15 +56000,21 @@ void main() {
|
|
|
55989
56000
|
* Transition to a new scene with fade effect
|
|
55990
56001
|
* @param {string} sceneId - The target scene ID
|
|
55991
56002
|
* @param {Object} sceneData - The scene configuration object
|
|
56003
|
+
* @param {Function} onSceneLoaded - Optional callback to run after scene loads but before fade-in
|
|
55992
56004
|
* @returns {Promise} - Resolves when the transition is complete
|
|
55993
56005
|
*/
|
|
55994
|
-
async transitionTo(sceneId, sceneData) {
|
|
56006
|
+
async transitionTo(sceneId, sceneData, onSceneLoaded = null) {
|
|
55995
56007
|
// Fade out
|
|
55996
56008
|
await this.fadeOut();
|
|
55997
56009
|
|
|
55998
56010
|
// Load new scene
|
|
55999
56011
|
await this.loadScene(sceneId, sceneData);
|
|
56000
56012
|
|
|
56013
|
+
// Call onSceneLoaded callback (e.g., to set camera position while screen is black)
|
|
56014
|
+
if (onSceneLoaded && typeof onSceneLoaded === 'function') {
|
|
56015
|
+
onSceneLoaded();
|
|
56016
|
+
}
|
|
56017
|
+
|
|
56001
56018
|
// Fade in
|
|
56002
56019
|
await this.fadeIn();
|
|
56003
56020
|
|
|
@@ -56092,13 +56109,17 @@ void main() {
|
|
|
56092
56109
|
* HotspotManager - Creates, manages, and removes hotspot entities in the A-Frame scene
|
|
56093
56110
|
*/
|
|
56094
56111
|
class HotspotManager {
|
|
56095
|
-
constructor(sceneEl, assetManager, defaultHotspotSettings = {}) {
|
|
56112
|
+
constructor(sceneEl, assetManager, defaultHotspotSettings = {}, iconRenderer = null) {
|
|
56096
56113
|
this.sceneEl = sceneEl;
|
|
56097
56114
|
this.assetManager = assetManager;
|
|
56098
56115
|
this.defaultSettings = defaultHotspotSettings;
|
|
56116
|
+
this.iconRenderer = iconRenderer;
|
|
56099
56117
|
this.activeHotspots = [];
|
|
56100
56118
|
this.tooltipEl = null;
|
|
56101
56119
|
this.tooltipCreated = false;
|
|
56120
|
+
this.iconDataUrls = new Map(); // Cache for generated icon data URLs
|
|
56121
|
+
this.sceneLoadCounter = 0; // Unique counter to prevent asset ID collisions between scene loads
|
|
56122
|
+
this.currentAssetPrefix = ''; // Current asset ID prefix for this scene load
|
|
56102
56123
|
|
|
56103
56124
|
// Listen for hover events
|
|
56104
56125
|
this.sceneEl.addEventListener('swt-hotspot-hover', (evt) => {
|
|
@@ -56177,20 +56198,50 @@ void main() {
|
|
|
56177
56198
|
this.createTooltip();
|
|
56178
56199
|
}
|
|
56179
56200
|
|
|
56180
|
-
//
|
|
56181
|
-
|
|
56201
|
+
// Increment scene load counter to ensure unique asset IDs for this scene load
|
|
56202
|
+
// This prevents A-Frame/THREE.js texture caching from reusing old icons
|
|
56203
|
+
this.sceneLoadCounter++;
|
|
56204
|
+
this.currentAssetPrefix = `hotspot-icon-${this.sceneLoadCounter}`;
|
|
56205
|
+
|
|
56206
|
+
// Clear previous icon data URLs cache
|
|
56207
|
+
this.iconDataUrls.clear();
|
|
56208
|
+
|
|
56209
|
+
// Process all hotspot icons SEQUENTIALLY to avoid race condition
|
|
56210
|
+
// (IconRenderer uses a shared renderContainer that would be corrupted by parallel generation)
|
|
56211
|
+
for (let index = 0; index < hotspots.length; index++) {
|
|
56212
|
+
const hotspot = hotspots[index];
|
|
56182
56213
|
const icon = hotspot.appearance?.icon || this.defaultSettings.icon;
|
|
56183
|
-
|
|
56184
|
-
|
|
56185
|
-
|
|
56214
|
+
const color = hotspot.appearance?.color || '#ffffff';
|
|
56215
|
+
|
|
56216
|
+
if (!icon) continue;
|
|
56217
|
+
|
|
56218
|
+
// Check if it's an image URL
|
|
56219
|
+
const isImageUrl = icon.startsWith('http') || icon.startsWith('data:') || icon.startsWith('/');
|
|
56220
|
+
|
|
56221
|
+
// Use unique asset ID that includes scene load counter
|
|
56222
|
+
const assetId = `${this.currentAssetPrefix}-${index}`;
|
|
56223
|
+
|
|
56224
|
+
if (isImageUrl) {
|
|
56225
|
+
// Preload as image asset
|
|
56226
|
+
try {
|
|
56227
|
+
await this.assetManager.preloadImage(icon, assetId);
|
|
56228
|
+
} catch (err) {
|
|
56186
56229
|
console.warn(`Failed to load icon for hotspot ${index}, will use color instead`);
|
|
56187
|
-
|
|
56188
|
-
|
|
56230
|
+
}
|
|
56231
|
+
} else if (this.iconRenderer) {
|
|
56232
|
+
// Generate icon data URL from SenangStart icon name
|
|
56233
|
+
try {
|
|
56234
|
+
const dataUrl = await this.iconRenderer.generateIconDataUrl(icon, color, 128);
|
|
56235
|
+
if (dataUrl) {
|
|
56236
|
+
this.iconDataUrls.set(index, dataUrl);
|
|
56237
|
+
// Preload the generated data URL as an asset
|
|
56238
|
+
await this.assetManager.preloadImage(dataUrl, assetId);
|
|
56239
|
+
}
|
|
56240
|
+
} catch (err) {
|
|
56241
|
+
console.warn(`Failed to generate icon for hotspot ${index}:`, err);
|
|
56242
|
+
}
|
|
56189
56243
|
}
|
|
56190
|
-
|
|
56191
|
-
});
|
|
56192
|
-
|
|
56193
|
-
await Promise.all(iconPromises);
|
|
56244
|
+
}
|
|
56194
56245
|
|
|
56195
56246
|
// Then create the hotspot entities
|
|
56196
56247
|
hotspots.forEach((hotspot, index) => {
|
|
@@ -56211,26 +56262,31 @@ void main() {
|
|
|
56211
56262
|
const pos = hotspot.position;
|
|
56212
56263
|
hotspotEl.setAttribute('position', `${pos.x} ${pos.y} ${pos.z}`);
|
|
56213
56264
|
|
|
56214
|
-
//
|
|
56265
|
+
// Get icon and color
|
|
56215
56266
|
const icon = hotspot.appearance?.icon || this.defaultSettings.icon;
|
|
56216
|
-
const
|
|
56217
|
-
|
|
56267
|
+
const color = hotspot.appearance?.color || '#4CC3D9';
|
|
56268
|
+
|
|
56269
|
+
// Check if we have a preloaded icon asset (either from URL or generated from icon name)
|
|
56270
|
+
// Use the unique asset prefix that includes scene load counter
|
|
56271
|
+
const assetId = `${this.currentAssetPrefix}-${index}`;
|
|
56272
|
+
const assetsEl = this.sceneEl.querySelector('a-assets');
|
|
56273
|
+
const assetEl = assetsEl ? assetsEl.querySelector(`#${assetId}`) : null;
|
|
56218
56274
|
|
|
56219
56275
|
let visualEl;
|
|
56220
56276
|
|
|
56221
|
-
// Check if icon was successfully loaded
|
|
56277
|
+
// Check if icon asset was successfully loaded/generated
|
|
56222
56278
|
if (icon && assetEl) {
|
|
56223
56279
|
visualEl = document.createElement('a-image');
|
|
56224
56280
|
visualEl.setAttribute('src', `#${assetId}`);
|
|
56225
56281
|
// Make images double-sided
|
|
56226
|
-
visualEl.setAttribute('material', 'side
|
|
56282
|
+
visualEl.setAttribute('material', 'side: double; transparent: true; alphaTest: 0.1');
|
|
56227
56283
|
} else {
|
|
56228
|
-
// Fallback to a plane
|
|
56284
|
+
// Fallback to a colored plane
|
|
56229
56285
|
visualEl = document.createElement('a-plane');
|
|
56230
|
-
visualEl.setAttribute('color',
|
|
56286
|
+
visualEl.setAttribute('color', color);
|
|
56231
56287
|
visualEl.setAttribute('width', '1');
|
|
56232
56288
|
visualEl.setAttribute('height', '1');
|
|
56233
|
-
// Make plane double-sided
|
|
56289
|
+
// Make plane double-sided
|
|
56234
56290
|
visualEl.setAttribute('material', 'side', 'double');
|
|
56235
56291
|
}
|
|
56236
56292
|
|
|
@@ -56258,6 +56314,9 @@ void main() {
|
|
|
56258
56314
|
* Remove all active hotspots from the scene
|
|
56259
56315
|
*/
|
|
56260
56316
|
removeAllHotspots() {
|
|
56317
|
+
// Remove hotspot icon assets from a-assets to prevent icon mixup on scene navigation
|
|
56318
|
+
this.removeHotspotIconAssets();
|
|
56319
|
+
|
|
56261
56320
|
this.activeHotspots.forEach(hotspot => {
|
|
56262
56321
|
if (hotspot.parentNode) {
|
|
56263
56322
|
hotspot.parentNode.removeChild(hotspot);
|
|
@@ -56265,12 +56324,43 @@ void main() {
|
|
|
56265
56324
|
});
|
|
56266
56325
|
this.activeHotspots = [];
|
|
56267
56326
|
|
|
56327
|
+
// Clear icon data URLs cache
|
|
56328
|
+
this.iconDataUrls.clear();
|
|
56329
|
+
|
|
56268
56330
|
// Hide tooltip
|
|
56269
56331
|
if (this.tooltipEl) {
|
|
56270
56332
|
this.tooltipEl.setAttribute('visible', 'false');
|
|
56271
56333
|
}
|
|
56272
56334
|
}
|
|
56273
56335
|
|
|
56336
|
+
/**
|
|
56337
|
+
* Remove all hotspot icon assets from a-assets element
|
|
56338
|
+
* This prevents icon mixup when navigating between scenes
|
|
56339
|
+
*/
|
|
56340
|
+
removeHotspotIconAssets() {
|
|
56341
|
+
const assetsEl = this.sceneEl.querySelector('a-assets');
|
|
56342
|
+
if (!assetsEl) return;
|
|
56343
|
+
|
|
56344
|
+
// Find and remove all assets with IDs matching hotspot-icon-* pattern
|
|
56345
|
+
const iconAssets = assetsEl.querySelectorAll('[id^="hotspot-icon-"]');
|
|
56346
|
+
iconAssets.forEach(asset => {
|
|
56347
|
+
if (asset.parentNode) {
|
|
56348
|
+
asset.parentNode.removeChild(asset);
|
|
56349
|
+
}
|
|
56350
|
+
});
|
|
56351
|
+
|
|
56352
|
+
// Also clear the asset manager's loaded assets for hotspot icons
|
|
56353
|
+
if (this.assetManager && this.assetManager.loadedAssets) {
|
|
56354
|
+
const keysToDelete = [];
|
|
56355
|
+
this.assetManager.loadedAssets.forEach((value, key) => {
|
|
56356
|
+
if (key.startsWith('hotspot-icon-')) {
|
|
56357
|
+
keysToDelete.push(key);
|
|
56358
|
+
}
|
|
56359
|
+
});
|
|
56360
|
+
keysToDelete.forEach(key => this.assetManager.loadedAssets.delete(key));
|
|
56361
|
+
}
|
|
56362
|
+
}
|
|
56363
|
+
|
|
56274
56364
|
/**
|
|
56275
56365
|
* Clean up the hotspot manager
|
|
56276
56366
|
*/
|
|
@@ -56282,6 +56372,130 @@ void main() {
|
|
|
56282
56372
|
}
|
|
56283
56373
|
}
|
|
56284
56374
|
|
|
56375
|
+
/**
|
|
56376
|
+
* IconRenderer - Converts SenangStart icon names to image data URLs for A-Frame
|
|
56377
|
+
*/
|
|
56378
|
+
class IconRenderer {
|
|
56379
|
+
constructor() {
|
|
56380
|
+
this.iconCache = new Map();
|
|
56381
|
+
this.renderContainer = null;
|
|
56382
|
+
}
|
|
56383
|
+
|
|
56384
|
+
/**
|
|
56385
|
+
* Initialize the hidden render container
|
|
56386
|
+
*/
|
|
56387
|
+
init() {
|
|
56388
|
+
if (this.renderContainer) return;
|
|
56389
|
+
|
|
56390
|
+
this.renderContainer = document.createElement('div');
|
|
56391
|
+
this.renderContainer.id = 'swt-icon-renderer';
|
|
56392
|
+
this.renderContainer.style.cssText = `
|
|
56393
|
+
position: absolute;
|
|
56394
|
+
left: -9999px;
|
|
56395
|
+
top: -9999px;
|
|
56396
|
+
width: 128px;
|
|
56397
|
+
height: 128px;
|
|
56398
|
+
pointer-events: none;
|
|
56399
|
+
visibility: hidden;
|
|
56400
|
+
`;
|
|
56401
|
+
document.body.appendChild(this.renderContainer);
|
|
56402
|
+
}
|
|
56403
|
+
|
|
56404
|
+
/**
|
|
56405
|
+
* Generate an image data URL from a SenangStart icon
|
|
56406
|
+
* @param {string} iconName - SenangStart icon name (e.g., 'arrow-right')
|
|
56407
|
+
* @param {string} color - Hex color for the icon
|
|
56408
|
+
* @param {number} size - Size in pixels (default 128)
|
|
56409
|
+
* @returns {Promise<string>} - Data URL of the icon image
|
|
56410
|
+
*/
|
|
56411
|
+
async generateIconDataUrl(iconName, color = '#ffffff', size = 128) {
|
|
56412
|
+
const cacheKey = `${iconName}-${color}-${size}`;
|
|
56413
|
+
|
|
56414
|
+
if (this.iconCache.has(cacheKey)) {
|
|
56415
|
+
return this.iconCache.get(cacheKey);
|
|
56416
|
+
}
|
|
56417
|
+
|
|
56418
|
+
this.init();
|
|
56419
|
+
|
|
56420
|
+
return new Promise((resolve, reject) => {
|
|
56421
|
+
// Create the ss-icon element
|
|
56422
|
+
this.renderContainer.innerHTML = `
|
|
56423
|
+
<ss-icon
|
|
56424
|
+
icon="${iconName}"
|
|
56425
|
+
thickness="2.5"
|
|
56426
|
+
style="color: ${color}; width: ${size}px; height: ${size}px; display: block;"
|
|
56427
|
+
></ss-icon>
|
|
56428
|
+
`;
|
|
56429
|
+
|
|
56430
|
+
// Wait for the custom element to render
|
|
56431
|
+
setTimeout(() => {
|
|
56432
|
+
try {
|
|
56433
|
+
const ssIcon = this.renderContainer.querySelector('ss-icon');
|
|
56434
|
+
if (!ssIcon || !ssIcon.shadowRoot) {
|
|
56435
|
+
console.warn(`Icon ${iconName} not rendered properly`);
|
|
56436
|
+
resolve(null);
|
|
56437
|
+
return;
|
|
56438
|
+
}
|
|
56439
|
+
|
|
56440
|
+
// Get the SVG from shadow root
|
|
56441
|
+
const svg = ssIcon.shadowRoot.querySelector('svg');
|
|
56442
|
+
if (!svg) {
|
|
56443
|
+
console.warn(`SVG not found for icon ${iconName}`);
|
|
56444
|
+
resolve(null);
|
|
56445
|
+
return;
|
|
56446
|
+
}
|
|
56447
|
+
|
|
56448
|
+
// Clone and prepare SVG
|
|
56449
|
+
const svgClone = svg.cloneNode(true);
|
|
56450
|
+
svgClone.setAttribute('width', size);
|
|
56451
|
+
svgClone.setAttribute('height', size);
|
|
56452
|
+
svgClone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
56453
|
+
|
|
56454
|
+
// Apply the color to all paths/elements
|
|
56455
|
+
svgClone.querySelectorAll('path, circle, rect, line, polyline, polygon').forEach(el => {
|
|
56456
|
+
el.setAttribute('stroke', color);
|
|
56457
|
+
// Keep fill as currentColor if it's set
|
|
56458
|
+
const fill = el.getAttribute('fill');
|
|
56459
|
+
if (fill && fill !== 'none') {
|
|
56460
|
+
el.setAttribute('fill', color);
|
|
56461
|
+
}
|
|
56462
|
+
});
|
|
56463
|
+
|
|
56464
|
+
// Convert SVG to data URL
|
|
56465
|
+
const svgString = new XMLSerializer().serializeToString(svgClone);
|
|
56466
|
+
const dataUrl = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString)));
|
|
56467
|
+
|
|
56468
|
+
// Cache the result
|
|
56469
|
+
this.iconCache.set(cacheKey, dataUrl);
|
|
56470
|
+
|
|
56471
|
+
resolve(dataUrl);
|
|
56472
|
+
} catch (error) {
|
|
56473
|
+
console.error('Error generating icon data URL:', error);
|
|
56474
|
+
resolve(null);
|
|
56475
|
+
}
|
|
56476
|
+
}, 100); // Wait for custom element to render
|
|
56477
|
+
});
|
|
56478
|
+
}
|
|
56479
|
+
|
|
56480
|
+
/**
|
|
56481
|
+
* Clear the icon cache
|
|
56482
|
+
*/
|
|
56483
|
+
clearCache() {
|
|
56484
|
+
this.iconCache.clear();
|
|
56485
|
+
}
|
|
56486
|
+
|
|
56487
|
+
/**
|
|
56488
|
+
* Destroy the renderer
|
|
56489
|
+
*/
|
|
56490
|
+
destroy() {
|
|
56491
|
+
if (this.renderContainer && this.renderContainer.parentNode) {
|
|
56492
|
+
this.renderContainer.parentNode.removeChild(this.renderContainer);
|
|
56493
|
+
}
|
|
56494
|
+
this.renderContainer = null;
|
|
56495
|
+
this.iconCache.clear();
|
|
56496
|
+
}
|
|
56497
|
+
}
|
|
56498
|
+
|
|
56285
56499
|
/**
|
|
56286
56500
|
* SenangWebs Tour (SWT) - Main Library Entry Point
|
|
56287
56501
|
* Version 1.0.3
|
|
@@ -56310,12 +56524,14 @@ void main() {
|
|
|
56310
56524
|
// Initialize managers
|
|
56311
56525
|
this.assetManager = new AssetManager(this.sceneEl);
|
|
56312
56526
|
this.sceneManager = new SceneManager(this.sceneEl, this.assetManager);
|
|
56527
|
+
this.iconRenderer = new IconRenderer();
|
|
56313
56528
|
|
|
56314
56529
|
const defaultHotspotSettings = this.config.settings?.defaultHotspot || {};
|
|
56315
56530
|
this.hotspotManager = new HotspotManager(
|
|
56316
56531
|
this.sceneEl,
|
|
56317
56532
|
this.assetManager,
|
|
56318
|
-
defaultHotspotSettings
|
|
56533
|
+
defaultHotspotSettings,
|
|
56534
|
+
this.iconRenderer
|
|
56319
56535
|
);
|
|
56320
56536
|
|
|
56321
56537
|
// Event listeners
|
|
@@ -56376,6 +56592,11 @@ void main() {
|
|
|
56376
56592
|
|
|
56377
56593
|
this.isStarted = true;
|
|
56378
56594
|
|
|
56595
|
+
// Set camera to starting position if set
|
|
56596
|
+
if (initialSceneData.startingPosition) {
|
|
56597
|
+
this.setCameraToStartingPosition(initialSceneData.startingPosition);
|
|
56598
|
+
}
|
|
56599
|
+
|
|
56379
56600
|
// Emit events
|
|
56380
56601
|
this.emit("scene-loaded", { sceneId: initialSceneId });
|
|
56381
56602
|
this.emit("tour-started", { sceneId: initialSceneId });
|
|
@@ -56411,8 +56632,13 @@ void main() {
|
|
|
56411
56632
|
// Remove old hotspots
|
|
56412
56633
|
this.hotspotManager.removeAllHotspots();
|
|
56413
56634
|
|
|
56414
|
-
// Transition to new scene
|
|
56415
|
-
await this.sceneManager.transitionTo(sceneId, sceneData)
|
|
56635
|
+
// Transition to new scene with callback to set camera position while screen is black
|
|
56636
|
+
await this.sceneManager.transitionTo(sceneId, sceneData, () => {
|
|
56637
|
+
// Set camera to starting position during transition (while screen is still black)
|
|
56638
|
+
if (sceneData.startingPosition) {
|
|
56639
|
+
this.setCameraToStartingPosition(sceneData.startingPosition);
|
|
56640
|
+
}
|
|
56641
|
+
});
|
|
56416
56642
|
|
|
56417
56643
|
// Create new hotspots
|
|
56418
56644
|
await this.hotspotManager.createHotspots(sceneData.hotspots || []);
|
|
@@ -56466,6 +56692,41 @@ void main() {
|
|
|
56466
56692
|
}
|
|
56467
56693
|
}
|
|
56468
56694
|
|
|
56695
|
+
/**
|
|
56696
|
+
* Set camera to a starting position immediately
|
|
56697
|
+
* Uses look-controls internal pitchObject/yawObject for proper A-Frame compatibility
|
|
56698
|
+
* @param {Object} startingPosition - Object with pitch and yaw in radians
|
|
56699
|
+
*/
|
|
56700
|
+
setCameraToStartingPosition(startingPosition) {
|
|
56701
|
+
if (!startingPosition) return;
|
|
56702
|
+
|
|
56703
|
+
const camera = this.sceneEl.querySelector("[camera]");
|
|
56704
|
+
if (!camera) return;
|
|
56705
|
+
|
|
56706
|
+
// Get look-controls component
|
|
56707
|
+
camera.components?.["look-controls"];
|
|
56708
|
+
|
|
56709
|
+
// Set rotation using look-controls internal objects (already in radians)
|
|
56710
|
+
const setRotation = () => {
|
|
56711
|
+
const cam = this.sceneEl.querySelector("[camera]");
|
|
56712
|
+
if (!cam) return;
|
|
56713
|
+
|
|
56714
|
+
const lc = cam.components?.["look-controls"];
|
|
56715
|
+
if (lc && lc.pitchObject && lc.yawObject) {
|
|
56716
|
+
lc.pitchObject.rotation.x = startingPosition.pitch;
|
|
56717
|
+
lc.yawObject.rotation.y = startingPosition.yaw;
|
|
56718
|
+
} else if (cam.object3D) {
|
|
56719
|
+
cam.object3D.rotation.set(startingPosition.pitch, startingPosition.yaw, 0);
|
|
56720
|
+
}
|
|
56721
|
+
};
|
|
56722
|
+
|
|
56723
|
+
// Set immediately and retry a few times to ensure it sticks
|
|
56724
|
+
setRotation();
|
|
56725
|
+
setTimeout(setRotation, 100);
|
|
56726
|
+
setTimeout(setRotation, 300);
|
|
56727
|
+
setTimeout(setRotation, 500);
|
|
56728
|
+
}
|
|
56729
|
+
|
|
56469
56730
|
/**
|
|
56470
56731
|
* Get the current scene ID
|
|
56471
56732
|
* @returns {string}
|
|
@@ -56520,6 +56781,11 @@ void main() {
|
|
|
56520
56781
|
this.hotspotManager.destroy();
|
|
56521
56782
|
this.sceneManager.destroy();
|
|
56522
56783
|
this.assetManager.destroy();
|
|
56784
|
+
|
|
56785
|
+
// Clean up icon renderer
|
|
56786
|
+
if (this.iconRenderer) {
|
|
56787
|
+
this.iconRenderer.destroy();
|
|
56788
|
+
}
|
|
56523
56789
|
|
|
56524
56790
|
this.isStarted = false;
|
|
56525
56791
|
}
|